diff options
| -rw-r--r-- | core/cds/inc/cds_reg_service.h | 15 | ||||
| -rw-r--r-- | core/cds/src/cds_reg_service.c | 10 | ||||
| -rw-r--r-- | core/hdd/inc/wlan_hdd_main.h | 37 | ||||
| -rw-r--r-- | core/hdd/src/wlan_hdd_cfg80211.h | 10 | ||||
| -rw-r--r-- | core/hdd/src/wlan_hdd_hostapd.c | 184 | ||||
| -rw-r--r-- | core/hdd/src/wlan_hdd_ioctl.c | 244 | ||||
| -rw-r--r-- | core/hdd/src/wlan_hdd_main.c | 7 | ||||
| -rw-r--r-- | core/hdd/src/wlan_hdd_softap_tx_rx.c | 2 | ||||
| -rw-r--r-- | core/hdd/src/wlan_hdd_wext.c | 6 |
9 files changed, 509 insertions, 6 deletions
diff --git a/core/cds/inc/cds_reg_service.h b/core/cds/inc/cds_reg_service.h index fb84151b01df..b64d9d6763c4 100644 --- a/core/cds/inc/cds_reg_service.h +++ b/core/cds/inc/cds_reg_service.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2017 The Linux Foundation. All rights reserved. + * Copyright (c) 2014-2018 The Linux Foundation. All rights reserved. * * Previously licensed under the ISC license by Qualcomm Atheros, Inc. * @@ -385,6 +385,19 @@ QDF_STATUS cds_get_channel_list_with_power(struct channel_power *base_channels, uint8_t *num_base_channels); +/** + * cds_set_channel_state() - API to set the channel state in reg table + * @chan_num - input channel enum + * @state - state of the channel to be set + * CHANNEL_STATE_DISABLE + * CHANNEL_STATE_DFS + * CHANNEL_STATE_ENABLE + * CHANNEL_STATE_INVALID + * + * Return: Void + */ +void cds_set_channel_state(uint32_t chan_num, enum channel_state state); + enum channel_state cds_get_channel_state(uint32_t chan_num); QDF_STATUS cds_get_dfs_region(enum dfs_region *dfs_reg); QDF_STATUS cds_put_dfs_region(enum dfs_region dfs_reg); diff --git a/core/cds/src/cds_reg_service.c b/core/cds/src/cds_reg_service.c index 4ebfe3cedafa..5e8cc979950d 100644 --- a/core/cds/src/cds_reg_service.c +++ b/core/cds/src/cds_reg_service.c @@ -224,6 +224,16 @@ enum channel_enum cds_get_channel_enum(uint32_t chan_num) return INVALID_CHANNEL; } +void cds_set_channel_state(uint32_t chan_num, enum channel_state state) +{ + enum channel_enum chan_enum; + + chan_enum = cds_get_channel_enum(chan_num); + if (INVALID_CHANNEL == chan_enum) + return; + + reg_channels[chan_enum].state = state; +} /** * cds_get_channel_state() - get the channel state diff --git a/core/hdd/inc/wlan_hdd_main.h b/core/hdd/inc/wlan_hdd_main.h index 82081c84c900..6cf2fe2c27ea 100644 --- a/core/hdd/inc/wlan_hdd_main.h +++ b/core/hdd/inc/wlan_hdd_main.h @@ -1883,6 +1883,33 @@ enum hdd_sta_smps_param { }; /** + * struct hdd_cache_channel_info - Structure of the channel info + * which needs to be cached + * @channel_num: channel number + * @reg_status: Current regulatory status of the channel + * Enable + * Disable + * DFS + * Invalid + * @wiphy_status: Current wiphy status + */ +struct hdd_cache_channel_info { + uint32_t channel_num; + enum channel_state reg_status; + uint32_t wiphy_status; +}; + +/** + * struct hdd_cache_channels - Structure of the channels to be cached + * @num_channels: Number of channels to be cached + * @channel_info: Structure of the channel info + */ +struct hdd_cache_channels { + uint32_t num_channels; + struct hdd_cache_channel_info *channel_info; +}; + +/** * struct hdd_context_s * @adapter_nodes: an array of adapter nodes for keeping track of hdd adapters */ @@ -2204,6 +2231,8 @@ struct hdd_context_s { /* mutex lock to block concurrent access */ struct mutex power_stats_lock; #endif + struct hdd_cache_channels *original_channels; + qdf_mutex_t cache_channel_lock; }; int hdd_validate_channel_and_bandwidth(hdd_adapter_t *adapter, @@ -3088,4 +3117,12 @@ void hdd_driver_memdump_deinit(void); */ bool hdd_is_cli_iface_up(hdd_context_t *hdd_ctx); +/** + * wlan_hdd_free_cache_channels() - Free the cache channels list + * @hdd_ctx: Pointer to HDD context + * + * Return: None + */ +void wlan_hdd_free_cache_channels(hdd_context_t *hdd_ctx); + #endif /* end #if !defined(WLAN_HDD_MAIN_H) */ diff --git a/core/hdd/src/wlan_hdd_cfg80211.h b/core/hdd/src/wlan_hdd_cfg80211.h index a07b47866c13..c4078264cde9 100644 --- a/core/hdd/src/wlan_hdd_cfg80211.h +++ b/core/hdd/src/wlan_hdd_cfg80211.h @@ -706,4 +706,14 @@ QDF_STATUS wlan_hdd_send_sta_authorized_event( hdd_adapter_t *pAdapter, hdd_context_t *pHddCtx, const struct qdf_mac_addr *mac_addr); + +/** + * wlan_hdd_restore_channels() - Restore the channels which were cached + * and disabled in wlan_hdd_disable_channels api. + * @hdd_ctx: Pointer to the HDD context + * + * Return: 0 on success, Error code on failure + */ +int wlan_hdd_restore_channels(hdd_context_t *hdd_ctx); + #endif diff --git a/core/hdd/src/wlan_hdd_hostapd.c b/core/hdd/src/wlan_hdd_hostapd.c index 5b9c00d6a54b..d3f32319367d 100644 --- a/core/hdd/src/wlan_hdd_hostapd.c +++ b/core/hdd/src/wlan_hdd_hostapd.c @@ -5278,7 +5278,7 @@ static int __iw_get_ap_freq(struct net_device *dev, return -EIO; } status = hdd_wlan_get_freq(channel, &freq); - if (true == status) { + if (0 == status) { /* Set Exponent parameter as 6 (MHZ) in struct * iw_freq * iwlist & iwconfig command * shows frequency into proper @@ -5290,7 +5290,7 @@ static int __iw_get_ap_freq(struct net_device *dev, } else { channel = pHddApCtx->operatingChannel; status = hdd_wlan_get_freq(channel, &freq); - if (true == status) { + if (0 == status) { /* Set Exponent parameter as 6 (MHZ) in struct iw_freq * iwlist & iwconfig command shows frequency into proper * format (2.412 GHz instead of 246.2 MHz) @@ -7906,6 +7906,173 @@ static void hdd_check_and_disconnect_sta_on_invalid_channel( } /** + * wlan_hdd_get_wiphy_channel() - Get wiphy channel + * @wiphy: Pointer to wiphy structure + * @freq: Frequency of the channel for which the wiphy hw value is required + * + * Return: wiphy channel for valid frequency else return NULL + */ +static struct ieee80211_channel *wlan_hdd_get_wiphy_channel( + struct wiphy *wiphy, + uint32_t freq) +{ + uint32_t band_num, channel_num; + struct ieee80211_channel *wiphy_channel = NULL; + + for (band_num = 0; band_num < HDD_NUM_NL80211_BANDS; band_num++) { + for (channel_num = 0; channel_num < + wiphy->bands[band_num]->n_channels; + channel_num++) { + wiphy_channel = &(wiphy->bands[band_num]-> + channels[channel_num]); + if (wiphy_channel->center_freq == freq) + return wiphy_channel; + } + } + return wiphy_channel; +} + +int wlan_hdd_restore_channels(hdd_context_t *hdd_ctx) +{ + struct hdd_cache_channels *cache_chann; + struct wiphy *wiphy; + int freq, status, rf_channel; + int i; + struct ieee80211_channel *wiphy_channel = NULL; + + ENTER(); + + if (!hdd_ctx) { + hdd_err("HDD Context is NULL"); + return -EINVAL; + } + + wiphy = hdd_ctx->wiphy; + if (!wiphy) { + hdd_err("Wiphy is NULL"); + return -EINVAL; + } + + qdf_mutex_acquire(&hdd_ctx->cache_channel_lock); + + cache_chann = hdd_ctx->original_channels; + + if (!cache_chann || !cache_chann->num_channels) { + qdf_mutex_release(&hdd_ctx->cache_channel_lock); + hdd_err("channel list is NULL or num channels are zero"); + return -EINVAL; + } + + for (i = 0; i < cache_chann->num_channels; i++) { + status = hdd_wlan_get_freq( + cache_chann->channel_info[i].channel_num, + &freq); + if (status) + continue; + + wiphy_channel = wlan_hdd_get_wiphy_channel(wiphy, freq); + if (!wiphy_channel) + continue; + rf_channel = wiphy_channel->hw_value; + /* + * Restore the orginal states of the channels + * only if we have cached non zero values + */ + if (cache_chann->channel_info[i].reg_status) + cds_set_channel_state(rf_channel, + cache_chann-> + channel_info[i].reg_status); + + if (cache_chann->channel_info[i].wiphy_status && wiphy_channel) + wiphy_channel->flags = + cache_chann->channel_info[i].wiphy_status; + } + + qdf_mutex_release(&hdd_ctx->cache_channel_lock); + + status = sme_update_channel_list(hdd_ctx->hHal); + if (status) + hdd_err("Can't Restore channel list"); + EXIT(); + + return 0; +} + +/** + * wlan_hdd_disable_channels() - Cache the channels + * and current state of the channels from the channel list + * received in the command and disable the channels on the + * wiphy and reg table. + * @hdd_ctx: Pointer to hdd context + * + * Return: 0 on success, Error code on failure + */ +static int wlan_hdd_disable_channels(hdd_context_t *hdd_ctx) +{ + struct hdd_cache_channels *cache_chann; + struct wiphy *wiphy; + int freq, status, rf_channel; + int i; + struct ieee80211_channel *wiphy_channel = NULL; + + ENTER(); + + if (!hdd_ctx) { + hdd_err("HDD Context is NULL"); + return -EINVAL; + } + + wiphy = hdd_ctx->wiphy; + if (!wiphy) { + hdd_err("Wiphy is NULL"); + return -EINVAL; + } + + qdf_mutex_acquire(&hdd_ctx->cache_channel_lock); + cache_chann = hdd_ctx->original_channels; + + if (!cache_chann || !cache_chann->num_channels) { + qdf_mutex_release(&hdd_ctx->cache_channel_lock); + hdd_err("channel list is NULL or num channels are zero"); + return -EINVAL; + } + + for (i = 0; i < cache_chann->num_channels; i++) { + status = hdd_wlan_get_freq( + cache_chann->channel_info[i].channel_num, + &freq); + if (status) + continue; + wiphy_channel = wlan_hdd_get_wiphy_channel(wiphy, freq); + if (!wiphy_channel) + continue; + rf_channel = wiphy_channel->hw_value; + /* + * Cache the current states of + * the channels + */ + cache_chann->channel_info[i].reg_status = + cds_get_channel_state( + rf_channel); + cache_chann->channel_info[i].wiphy_status = + wiphy_channel->flags; + hdd_debug("Disable channel %d reg_stat %d wiphy_stat 0x%x", + cache_chann->channel_info[i].channel_num, + cache_chann->channel_info[i].reg_status, + wiphy_channel->flags); + + cds_set_channel_state(rf_channel, CHANNEL_STATE_DISABLE); + wiphy_channel->flags |= IEEE80211_CHAN_DISABLED; + } + + qdf_mutex_release(&hdd_ctx->cache_channel_lock); + status = sme_update_channel_list(hdd_ctx->hHal); + + EXIT(); + return status; +} + +/** * wlan_hdd_cfg80211_start_bss() - start bss * @pHostapdAdapter: Pointer to hostapd adapter * @params: Pointer to start bss beacon parameters @@ -8017,6 +8184,17 @@ int wlan_hdd_cfg80211_start_bss(hdd_adapter_t *pHostapdAdapter, pHddCtx); } + if (pHostapdAdapter->device_mode == QDF_SAP_MODE) { + /* + * Disable the channels received in command + * SET_DISABLE_CHANNEL_LIST + */ + status = wlan_hdd_disable_channels(pHddCtx); + if (!QDF_IS_STATUS_SUCCESS(status)) + hdd_err("Disable channel list fail"); + hdd_check_and_disconnect_sta_on_invalid_channel(pHddCtx); + } + pConfig = &pHostapdAdapter->sessionCtx.ap.sapConfig; pBeacon = pHostapdAdapter->sessionCtx.ap.beacon; @@ -8623,6 +8801,8 @@ int wlan_hdd_cfg80211_start_bss(hdd_adapter_t *pHostapdAdapter, return 0; error: + if (pHostapdAdapter->device_mode == QDF_SAP_MODE) + wlan_hdd_restore_channels(pHddCtx); /* Revert the indoor to passive marking if START BSS fails */ if (iniConfig->disable_indoor_channel) { hdd_update_indoor_channel(pHddCtx, false); diff --git a/core/hdd/src/wlan_hdd_ioctl.c b/core/hdd/src/wlan_hdd_ioctl.c index daf7cd8f4fe5..42f96973ff0f 100644 --- a/core/hdd/src/wlan_hdd_ioctl.c +++ b/core/hdd/src/wlan_hdd_ioctl.c @@ -6858,6 +6858,249 @@ static int drv_cmd_set_channel_switch(hdd_adapter_t *adapter, return 0; } +void wlan_hdd_free_cache_channels(hdd_context_t *hdd_ctx) +{ + ENTER(); + + if (!hdd_ctx->original_channels) + return; + + qdf_mutex_acquire(&hdd_ctx->cache_channel_lock); + hdd_ctx->original_channels->num_channels = 0; + qdf_mem_free(hdd_ctx->original_channels->channel_info); + hdd_ctx->original_channels->channel_info = NULL; + qdf_mem_free(hdd_ctx->original_channels); + hdd_ctx->original_channels = NULL; + qdf_mutex_release(&hdd_ctx->cache_channel_lock); + + EXIT(); +} + +/** + * hdd_alloc_chan_cache() - Allocate the memory to cache the channel + * info for the channels received in command SET_DISABLE_CHANNEL_LIST + * @hdd_ctx: Pointer to HDD context + * @num_chan: Number of channels for which memory needs to + * be allocated + * + * Return: 0 on success and error code on failure + */ +static int hdd_alloc_chan_cache(hdd_context_t *hdd_ctx, int num_chan) +{ + hdd_ctx->original_channels = + qdf_mem_malloc(sizeof(struct hdd_cache_channels)); + if (!hdd_ctx->original_channels) { + hdd_err("QDF_MALLOC_ERR"); + return -ENOMEM; + } + hdd_ctx->original_channels->num_channels = num_chan; + hdd_ctx->original_channels->channel_info = + qdf_mem_malloc(num_chan * + sizeof(struct hdd_cache_channel_info)); + if (!hdd_ctx->original_channels->channel_info) { + hdd_err("QDF_MALLOC_ERR"); + hdd_ctx->original_channels->num_channels = 0; + qdf_mem_free(hdd_ctx->original_channels); + hdd_ctx->original_channels = NULL; + return -ENOMEM; + } + return 0; +} + +/** + * hdd_parse_disable_chan_cmd() - Parse the channel list received + * in command. + * @adapter: pointer to hdd adapter + * @ptr: Pointer to the command string + * + * This function parses the channel list received in the command. + * command should be a string having format + * SET_DISABLE_CHANNEL_LIST <num of channels> + * <channels separated by spaces>. + * If the command comes multiple times than this function will compare + * the channels received in the command with the channles cached in the + * first command, if the channel list matches with the cached channles, + * it returns success otherwise returns failure. + * + * Return: 0 on success, Error code on failure + */ + +static int hdd_parse_disable_chan_cmd(hdd_adapter_t *adapter, uint8_t *ptr) +{ + hdd_context_t *hdd_ctx = WLAN_HDD_GET_CTX(adapter); + uint8_t *param; + int j, i, temp_int, ret = 0, num_channels; + int parsed_channels[MAX_CHANNEL]; + bool is_command_repeated = false; + + if (NULL == hdd_ctx) { + hdd_err("HDD Context is NULL"); + return -EINVAL; + } + + param = strnchr(ptr, strlen(ptr), ' '); + /*no argument after the command*/ + if (NULL == param) + return -EINVAL; + + /*no space after the command*/ + else if (SPACE_ASCII_VALUE != *param) + return -EINVAL; + + param++; + + /*removing empty spaces*/ + while ((SPACE_ASCII_VALUE == *param) && ('\0' != *param)) + param++; + + /*no argument followed by spaces*/ + if ('\0' == *param) + return -EINVAL; + + /*getting the first argument ie the number of channels*/ + if (sscanf(param, "%d ", &temp_int) != 1) { + hdd_err("Cannot get number of channels from input"); + return -EINVAL; + } + + if (temp_int < 0 || temp_int > MAX_CHANNEL) { + hdd_err("Invalid Number of channel received"); + return -EINVAL; + } + + hdd_debug("Number of channel to disable are: %d", temp_int); + + if (!temp_int) { + if (!wlan_hdd_restore_channels(hdd_ctx)) { + /* + * Free the cache channels only when the command is + * received with num channels as 0 + */ + wlan_hdd_free_cache_channels(hdd_ctx); + } + return 0; + } + + qdf_mutex_acquire(&hdd_ctx->cache_channel_lock); + + if (!hdd_ctx->original_channels) { + if (hdd_alloc_chan_cache(hdd_ctx, temp_int)) { + ret = -ENOMEM; + goto mem_alloc_failed; + } + } else if (hdd_ctx->original_channels->num_channels != temp_int) { + hdd_err("Invalid Number of channels"); + ret = -EINVAL; + is_command_repeated = true; + goto parse_failed; + } else { + is_command_repeated = true; + } + num_channels = temp_int; + for (j = 0; j < num_channels; j++) { + /* + * param pointing to the beginning of first space + * after number of channels + */ + param = strpbrk(param, " "); + /*no channel list after the number of channels argument*/ + if (NULL == param) { + hdd_err("Invalid No of channel provided in the list"); + ret = -EINVAL; + goto parse_failed; + } + + param++; + + /*removing empty space*/ + while ((SPACE_ASCII_VALUE == *param) && ('\0' != *param)) + param++; + + if ('\0' == *param) { + hdd_err("No channel is provided in the list"); + ret = -EINVAL; + goto parse_failed; + } + + if (sscanf(param, "%d ", &temp_int) != 1) { + hdd_err("Cannot read channel number"); + ret = -EINVAL; + goto parse_failed; + } + + if (!IS_CHANNEL_VALID(temp_int)) { + hdd_err("Invalid channel number received"); + ret = -EINVAL; + goto parse_failed; + } + + hdd_debug("channel[%d] = %d", j, temp_int); + parsed_channels[j] = temp_int; + } + + /*extra arguments check*/ + param = strpbrk(param, " "); + if (NULL != param) { + while ((SPACE_ASCII_VALUE == *param) && ('\0' != *param)) + param++; + + if ('\0' != *param) { + hdd_err("Invalid argument received"); + ret = -EINVAL; + goto parse_failed; + } + } + + /* + * If command is received first time, cache the channels to + * be disabled else compare the channels received in the + * command with the cached channels, if channel list matches + * return success otherewise return failure. + */ + if (!is_command_repeated) + for (j = 0; j < num_channels; j++) + hdd_ctx->original_channels-> + channel_info[j].channel_num = + parsed_channels[j]; + else { + for (i = 0; i < num_channels; i++) { + for (j = 0; j < num_channels; j++) + if (hdd_ctx->original_channels-> + channel_info[i].channel_num == + parsed_channels[j]) + break; + if (j == num_channels) { + ret = -EINVAL; + goto parse_failed; + } + } + ret = 0; + } +mem_alloc_failed: + + qdf_mutex_release(&hdd_ctx->cache_channel_lock); + EXIT(); + + return ret; + +parse_failed: + qdf_mutex_release(&hdd_ctx->cache_channel_lock); + if (!is_command_repeated) + wlan_hdd_free_cache_channels(hdd_ctx); + EXIT(); + + return ret; +} + +static int drv_cmd_set_disable_chan_list(hdd_adapter_t *adapter, + hdd_context_t *hdd_ctx, + uint8_t *command, + uint8_t command_len, + hdd_priv_data_t *priv_data) +{ + return hdd_parse_disable_chan_cmd(adapter, command); +} + /* * The following table contains all supported WLAN HDD * IOCTL driver commands and the handler for each of them. @@ -6968,6 +7211,7 @@ static const struct hdd_drv_cmd hdd_drv_cmds[] = { {"CHANNEL_SWITCH", drv_cmd_set_channel_switch, true}, {"SETANTENNAMODE", drv_cmd_set_antenna_mode, true}, {"GETANTENNAMODE", drv_cmd_get_antenna_mode, false}, + {"SET_DISABLE_CHANNEL_LIST", drv_cmd_set_disable_chan_list, true}, {"STOP", drv_cmd_dummy, false}, }; diff --git a/core/hdd/src/wlan_hdd_main.c b/core/hdd/src/wlan_hdd_main.c index dcf5c42dd48a..bcb928b57869 100644 --- a/core/hdd/src/wlan_hdd_main.c +++ b/core/hdd/src/wlan_hdd_main.c @@ -6417,6 +6417,7 @@ static void hdd_wlan_exit(hdd_context_t *hdd_ctx) qdf_spinlock_destroy(&hdd_ctx->hdd_adapter_lock); qdf_spinlock_destroy(&hdd_ctx->sta_update_info_lock); qdf_spinlock_destroy(&hdd_ctx->connection_status_lock); + qdf_mutex_destroy(&hdd_ctx->cache_channel_lock); /* * Close CDS @@ -10545,6 +10546,8 @@ int hdd_wlan_stop_modules(hdd_context_t *hdd_ctx, bool ftm_mode) hdd_err("CNSS power down failed put device into Low power mode:%d", ret); } + /* Free the cache channels of the command SET_DISABLE_CHANNEL_LIST */ + wlan_hdd_free_cache_channels(hdd_ctx); /* many adapter resources are not freed by design in SSR case */ if (!is_recover_stop) @@ -10740,6 +10743,10 @@ int hdd_wlan_startup(struct device *dev) if (ret) goto err_hdd_free_context; + ret = qdf_mutex_create(&hdd_ctx->cache_channel_lock); + if (QDF_IS_STATUS_ERROR(ret)) + goto err_hdd_free_context; + hdd_request_manager_init(); hdd_green_ap_init(hdd_ctx); diff --git a/core/hdd/src/wlan_hdd_softap_tx_rx.c b/core/hdd/src/wlan_hdd_softap_tx_rx.c index 390dc97aac93..44d19d64622a 100644 --- a/core/hdd/src/wlan_hdd_softap_tx_rx.c +++ b/core/hdd/src/wlan_hdd_softap_tx_rx.c @@ -1015,6 +1015,8 @@ QDF_STATUS hdd_softap_stop_bss(hdd_adapter_t *pAdapter) } } } + if (pAdapter->device_mode == QDF_SAP_MODE) + wlan_hdd_restore_channels(pHddCtx); /* Mark the indoor channel (passive) to enable */ if (pHddCtx->config->disable_indoor_channel) { diff --git a/core/hdd/src/wlan_hdd_wext.c b/core/hdd/src/wlan_hdd_wext.c index 8eebb5402f8c..352cec67d35e 100644 --- a/core/hdd/src/wlan_hdd_wext.c +++ b/core/hdd/src/wlan_hdd_wext.c @@ -3367,7 +3367,7 @@ int hdd_wlan_get_frag_threshold(hdd_adapter_t *pAdapter, * @channel: channel to be converted * @pfreq: where to store the frequency * - * Return: 1 on success, otherwise a negative errno + * Return: 0 on success, otherwise a negative errno */ int hdd_wlan_get_freq(uint32_t channel, uint32_t *pfreq) { @@ -3377,7 +3377,7 @@ int hdd_wlan_get_freq(uint32_t channel, uint32_t *pfreq) for (i = 0; i < FREQ_CHAN_MAP_TABLE_SIZE; i++) { if (channel == freq_chan_map[i].chan) { *pfreq = freq_chan_map[i].freq; - return 1; + return 0; } } } @@ -4865,7 +4865,7 @@ static int __iw_get_freq(struct net_device *dev, struct iw_request_info *info, return -EIO; } status = hdd_wlan_get_freq(channel, &freq); - if (true == status) { + if (0 == status) { /* Set Exponent parameter as 6 (MHZ) * in struct iw_freq iwlist & iwconfig * command shows frequency into proper |
