| /* |
| * Cellular channel avoidance implementation |
| * |
| * Copyright (C) 2021, Broadcom. |
| * |
| * Unless you and Broadcom execute a separate written software license |
| * agreement governing use of this software, this software is licensed to you |
| * under the terms of the GNU General Public License version 2 (the "GPL"), |
| * available at http://www.broadcom.com/licenses/GPLv2.php, with the |
| * following added to such license: |
| * |
| * As a special exception, the copyright holders of this software give you |
| * permission to link this software with independent modules, and to copy and |
| * distribute the resulting executable under terms of your choice, provided that |
| * you also meet, for each linked independent module, the terms and conditions of |
| * the license of that module. An independent module is a module which is not |
| * derived from this software. The special exception does not apply to any |
| * modifications of the software. |
| * |
| * |
| * <<Broadcom-WL-IPTag/Dual:>> |
| */ |
| |
| #include <typedefs.h> |
| #include <linuxver.h> |
| #include <linux/kernel.h> |
| |
| #include <bcmutils.h> |
| #include <bcmstdlib_s.h> |
| #include <bcmwifi_channels.h> |
| #include <bcmendian.h> |
| #include <ethernet.h> |
| #ifdef WL_WPS_SYNC |
| #include <eapol.h> |
| #endif /* WL_WPS_SYNC */ |
| #include <802.11.h> |
| #include <bcmiov.h> |
| #include <linux/if_arp.h> |
| #include <asm/uaccess.h> |
| |
| #include <ethernet.h> |
| #include <linux/kernel.h> |
| #include <linux/kthread.h> |
| #include <linux/netdevice.h> |
| #include <linux/sched.h> |
| #include <linux/etherdevice.h> |
| #include <linux/wireless.h> |
| #include <linux/ieee80211.h> |
| #include <linux/wait.h> |
| #include <net/cfg80211.h> |
| #include <net/rtnetlink.h> |
| |
| #include <wlioctl.h> |
| #include <bcmevent.h> |
| #include <wldev_common.h> |
| #include <wl_cfg80211.h> |
| #include <wl_cfgp2p.h> |
| #include <wl_cfgscan.h> |
| #include <wl_cfgvif.h> |
| #include <bcmdevs.h> |
| #include <bcmdevs_legacy.h> |
| #include <linux/list_sort.h> |
| #include <wl_cfgvendor.h> |
| #include <wl_cfg_cellavoid.h> |
| |
| #define INVALID_CHSPEC_BW (0xFFFF) |
| |
| #define CELLAVOID_DEFAULT_TXCAP 127u |
| #define CELLAVOID_MAX_CH 128u |
| #define WL_CELLAVOID_INFORM(args) WL_ERR(args) |
| |
| #define CELLAVOID_CSA_CNT 50u |
| |
| #define CSA_DELAYWORK_CSA_INTERVAL (CELLAVOID_CSA_CNT * 100u) |
| #define CSA_DELAYWORK_FAIL_INTERVAL 1000u |
| #define CSA_DELAYWORK_BUSY_INTERVAL 200u |
| #define CSA_DELAYWORK_FIRST_INTERVAL 0u |
| |
| #define CSA_MAX_RETRY_CNT 20u |
| |
| typedef enum cellavoid_ch_state { |
| CELLAVOID_STATE_CH_UNSAFE = 0, |
| CELLAVOID_STATE_CH_SAFE = 1 |
| } cellavoid_ch_state_t; |
| |
| typedef struct wl_cellavoid_chan_param { |
| int band; |
| int center_channel; |
| int8 pwr_cap; |
| chanspec_bw_t chspec_bw; |
| chanspec_band_t chspec_band; |
| } wl_cellavoid_chan_param_t; |
| |
| /* struct for channel info list |
| * allocated on the country code change and |
| * moved to the safe list (availale channel list) |
| * If framework gives unsafe channel list, |
| * then corresponding channel infos go to the unsafe list(cellular chan list) |
| */ |
| typedef struct wl_cellavoid_chan_info { |
| struct list_head list; |
| chanspec_t chanspec; |
| int8 pwr_cap; |
| } wl_cellavoid_chan_info_t; |
| |
| typedef struct wl_cellavoid_csa_info { |
| struct list_head list; |
| struct net_device *ndev; |
| chanspec_t chanspec; |
| } wl_cellavoid_csa_info_t; |
| |
| typedef struct wl_cellavoid_req_band { |
| struct net_device *ndev; |
| uint32 req_band; |
| } wl_cellavoid_req_band_t; |
| |
| /* This struct is used in cfg vendor function setting function */ |
| typedef struct wl_cellavoid_param { |
| wl_cellavoid_chan_param_t *chan_param; |
| u8 chan_cnt; |
| u32 mandatory; |
| } wl_cellavoid_param_t; |
| |
| typedef struct wl_cellavoid_info { |
| osl_t *osh; |
| struct mutex sync; |
| wl_cellavoid_param_t params; |
| u32 mandatory_flag; |
| u16 cell_chan_info_cnt; |
| struct list_head cell_chan_info_list; |
| struct list_head avail_chan_info_list; |
| wl_cellavoid_req_band_t req_band[MAX_AP_INTERFACE]; |
| bool csa_progress; |
| u32 csa_info_cnt; |
| struct list_head csa_info_list; |
| u32 csa_reschedule_cnt; |
| } wl_cellavoid_info_t; |
| |
| static int wl_cellavoid_verify_avail_chan_list(struct bcm_cfg80211 *cfg, |
| wl_cellavoid_info_t *cellavoid_info); |
| static void wl_cellavoid_clear_cell_chan_list(wl_cellavoid_info_t *cellavoid_info); |
| static void wl_cellavoid_free_avail_chan_list(wl_cellavoid_info_t *cellavoid_info); |
| static int wl_cellavoid_alloc_avail_chan_list(struct wiphy *wiphy, |
| wl_cellavoid_info_t *cellavoid_info); |
| static int wl_cellavoid_restore_txpwrcap(struct bcm_cfg80211 *cfg, |
| wl_cellavoid_info_t *cellavoid_info); |
| static void wl_cellavoid_do_csa_work(struct work_struct *work); |
| static void wl_cellavoid_free_csa_info_list(wl_cellavoid_info_t *cellavoid_info); |
| static int wl_cellavoid_set_cell_channels(struct bcm_cfg80211 *cfg, wl_cellavoid_param_t *param); |
| |
| /* Initialize context */ |
| int |
| wl_cellavoid_init(struct bcm_cfg80211 *cfg) |
| { |
| wl_cellavoid_info_t *cellavoid_info; |
| int ret = BCME_OK; |
| |
| WL_INFORM(("%s: Enter\n", __FUNCTION__)); |
| cellavoid_info = (wl_cellavoid_info_t *) |
| MALLOCZ(cfg->osh, sizeof(*cellavoid_info)); |
| if (cellavoid_info == NULL) { |
| WL_ERR(("failed to create cellavoid_info\n")); |
| return BCME_NOMEM; |
| } |
| |
| cellavoid_info->params.chan_param = |
| (wl_cellavoid_chan_param_t *)MALLOCZ(cfg->osh, |
| CELLAVOID_MAX_CH * sizeof(*(cellavoid_info->params.chan_param))); |
| if (cellavoid_info->params.chan_param == NULL) { |
| MFREE(cfg->osh, cellavoid_info, sizeof(*cellavoid_info)); |
| WL_ERR(("failed to create cellavoid_info params\n")); |
| return BCME_NOMEM; |
| } |
| |
| INIT_LIST_HEAD(&cellavoid_info->cell_chan_info_list); |
| INIT_LIST_HEAD(&cellavoid_info->avail_chan_info_list); |
| INIT_LIST_HEAD(&cellavoid_info->csa_info_list); |
| |
| INIT_DELAYED_WORK(&cfg->csa_delayed_work, wl_cellavoid_do_csa_work); |
| mutex_init(&cellavoid_info->sync); |
| |
| cellavoid_info->osh = cfg->osh; |
| cfg->cellavoid_info = cellavoid_info; |
| |
| return ret; |
| } |
| |
| /* deinitialize context */ |
| void |
| wl_cellavoid_deinit(struct bcm_cfg80211 *cfg) |
| { |
| wl_cellavoid_info_t *cellavoid_info = cfg->cellavoid_info; |
| |
| WL_INFORM(("%s: Enter\n", __FUNCTION__)); |
| if (!cellavoid_info) { |
| return; |
| } |
| |
| cancel_delayed_work(&cfg->csa_delayed_work); |
| |
| mutex_lock(&cellavoid_info->sync); |
| wl_cellavoid_free_csa_info_list(cellavoid_info); |
| wl_cellavoid_clear_cell_chan_list(cellavoid_info); |
| wl_cellavoid_free_avail_chan_list(cellavoid_info); |
| MFREE(cfg->osh, cellavoid_info->params.chan_param, |
| CELLAVOID_MAX_CH * sizeof(*(cellavoid_info->params.chan_param))); |
| mutex_unlock(&cellavoid_info->sync); |
| MFREE(cfg->osh, cellavoid_info, sizeof(*cellavoid_info)); |
| |
| cellavoid_info = NULL; |
| } |
| |
| void |
| wl_cellavoid_sync_lock(struct bcm_cfg80211 *cfg) |
| { |
| |
| wl_cellavoid_info_t *cellavoid_info = cfg->cellavoid_info; |
| |
| if (!cellavoid_info) { |
| return; |
| } |
| |
| mutex_lock(&cellavoid_info->sync); |
| } |
| |
| void |
| wl_cellavoid_sync_unlock(struct bcm_cfg80211 *cfg) |
| { |
| wl_cellavoid_info_t *cellavoid_info = cfg->cellavoid_info; |
| |
| if (!cellavoid_info) { |
| return; |
| } |
| |
| mutex_unlock(&cellavoid_info->sync); |
| } |
| |
| /* Called in wl_update_wiphybands function |
| * 1) If there's item in unsafe channel list (cellular channel list), then |
| * move it to safe channel list(available channel list) |
| * 2) Then, free all the channel info items |
| * 3) Trigger IOVAR to remove txpwrcap in FW (wl_cellavoid_restore_txpwrcap) |
| * 4) Allocate new channel items for the new country code |
| * 5) Recreate cellular channel list from saved params |
| */ |
| int |
| wl_cellavoid_reinit(struct bcm_cfg80211 *cfg) |
| { |
| wl_cellavoid_info_t *cellavoid_info = cfg->cellavoid_info; |
| int ret = BCME_ERROR; |
| |
| WL_INFORM(("%s: Enter\n", __FUNCTION__)); |
| if (!cellavoid_info) { |
| return ret; |
| } |
| |
| cancel_delayed_work_sync(&cfg->csa_delayed_work); |
| |
| mutex_lock(&cellavoid_info->sync); |
| |
| wl_cellavoid_free_csa_info_list(cellavoid_info); |
| wl_cellavoid_clear_cell_chan_list(cellavoid_info); |
| wl_cellavoid_free_avail_chan_list(cellavoid_info); |
| |
| ret = wl_cellavoid_restore_txpwrcap(cfg, cellavoid_info); |
| if (ret != BCME_OK) { |
| goto fail; |
| } |
| ret = wl_cellavoid_alloc_avail_chan_list(bcmcfg_to_wiphy(cfg), cellavoid_info); |
| if (ret != BCME_OK) { |
| goto fail; |
| } |
| |
| /* Recreate cellular channel list from saved params */ |
| ret = wl_cellavoid_set_cell_channels(cfg, &cellavoid_info->params); |
| if (ret != BCME_OK) { |
| goto fail; |
| } |
| |
| mutex_unlock(&cellavoid_info->sync); |
| return ret; |
| |
| fail: |
| wl_cellavoid_free_avail_chan_list(cellavoid_info); |
| |
| mutex_unlock(&cellavoid_info->sync); |
| return ret; |
| } |
| |
| static void |
| wl_cellavoid_free_csa_info_list(wl_cellavoid_info_t *cellavoid_info) |
| { |
| wl_cellavoid_csa_info_t *csa_info, *next; |
| |
| GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); |
| list_for_each_entry_safe(csa_info, next, &cellavoid_info->csa_info_list, list) { |
| GCC_DIAGNOSTIC_POP(); |
| list_del(&csa_info->list); |
| MFREE(cellavoid_info->osh, csa_info, sizeof(*csa_info)); |
| } |
| |
| cellavoid_info->csa_info_cnt = 0; |
| cellavoid_info->csa_progress = FALSE; |
| |
| } |
| |
| void |
| wl_cellavoid_free_csa_info(void *cai, struct net_device *ndev) |
| { |
| wl_cellavoid_csa_info_t *csa_info, *next; |
| wl_cellavoid_info_t *cellavoid_info = cai; |
| |
| GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); |
| list_for_each_entry_safe(csa_info, next, &cellavoid_info->csa_info_list, list) { |
| GCC_DIAGNOSTIC_POP(); |
| if (csa_info->ndev == ndev) { |
| list_del(&csa_info->list); |
| cellavoid_info->csa_info_cnt--; |
| if (cellavoid_info->csa_info_cnt == 0) { |
| cellavoid_info->csa_progress = FALSE; |
| cellavoid_info->csa_reschedule_cnt = 0; |
| } |
| MFREE(cellavoid_info->osh, csa_info, sizeof(*csa_info)); |
| break; |
| } |
| } |
| } |
| |
| static int |
| wl_cellavoid_add_csa_info(wl_cellavoid_info_t *cellavoid_info, |
| struct net_device *ndev, chanspec_t chanspec) |
| { |
| wl_cellavoid_csa_info_t *csa_info = NULL; |
| |
| /* Allocate csa info */ |
| csa_info = (wl_cellavoid_csa_info_t *) |
| MALLOCZ(cellavoid_info->osh, sizeof(wl_cellavoid_csa_info_t)); |
| if (!csa_info) { |
| WL_ERR(("failed to allocate chan info\n")); |
| return -ENOMEM; |
| } |
| |
| csa_info->ndev = ndev; |
| csa_info->chanspec = chanspec; |
| |
| list_add_tail(&csa_info->list, &cellavoid_info->csa_info_list); |
| cellavoid_info->csa_info_cnt++; |
| |
| return BCME_OK; |
| } |
| |
| void |
| wl_cellavoid_set_csa_done(void *cai) |
| { |
| wl_cellavoid_info_t *cellavoid_info = cai; |
| |
| if (cellavoid_info == NULL) { |
| return; |
| } |
| |
| mutex_lock(&cellavoid_info->sync); |
| |
| if (list_empty(&cellavoid_info->csa_info_list)) { |
| cellavoid_info->csa_progress = FALSE; |
| } |
| |
| mutex_unlock(&cellavoid_info->sync); |
| } |
| |
| static void |
| wl_cellavoid_do_csa_work(struct work_struct *work) |
| { |
| struct bcm_cfg80211 *cfg; |
| wl_cellavoid_info_t *cellavoid_info; |
| wl_cellavoid_csa_info_t *csa_info = NULL; |
| struct delayed_work *dw = to_delayed_work(work); |
| wl_chan_switch_t csa_arg; |
| uint delay = CSA_DELAYWORK_BUSY_INTERVAL; |
| char smbuf[WLC_IOCTL_SMLEN]; |
| struct net_info *iter, *next; |
| bool found = FALSE; |
| int err; |
| |
| GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); |
| cfg = container_of(dw, struct bcm_cfg80211, csa_delayed_work); |
| GCC_DIAGNOSTIC_POP(); |
| |
| cellavoid_info = cfg->cellavoid_info; |
| |
| mutex_lock(&cellavoid_info->sync); |
| |
| if (!list_empty(&cellavoid_info->csa_info_list)) { |
| csa_info = list_entry(cellavoid_info->csa_info_list.next, |
| wl_cellavoid_csa_info_t, list); |
| |
| /* Need to check ndev is still valid */ |
| GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); |
| for_each_ndev(cfg, iter, next) { |
| GCC_DIAGNOSTIC_POP(); |
| if ((iter->ndev) && |
| (iter->ndev->ieee80211_ptr->iftype == NL80211_IFTYPE_AP) && |
| wl_get_drv_status(cfg, CONNECTED, iter->ndev) && |
| iter->ndev == csa_info->ndev) { |
| found = TRUE; |
| } |
| } |
| |
| if (found == FALSE) { |
| wl_cellavoid_free_csa_info(cellavoid_info, csa_info->ndev); |
| delay = CSA_DELAYWORK_BUSY_INTERVAL; |
| goto exit; |
| } |
| |
| if (wl_get_drv_status_all(cfg, SCANNING) || |
| wl_get_drv_status_all(cfg, CONNECTING)) { |
| WL_INFORM_MEM(("scanning/connecting, " |
| "so delay for a while, target chspec %x\n", |
| csa_info->chanspec)); |
| goto reschedule; |
| } |
| |
| bzero(&csa_arg, sizeof(csa_arg)); |
| csa_arg.mode = DOT11_CSA_MODE_ADVISORY; |
| csa_arg.count = CELLAVOID_CSA_CNT; |
| csa_arg.reg = 0; |
| csa_arg.chspec = wl_chspec_host_to_driver(csa_info->chanspec); |
| |
| /* TBD, ndev valid check, limit on reschedule count */ |
| WL_INFORM_MEM(("CSA, target chspec %x\n", csa_info->chanspec)); |
| |
| err = wldev_iovar_setbuf(csa_info->ndev, "csa", &csa_arg, sizeof(csa_arg), |
| smbuf, sizeof(smbuf), NULL); |
| if (err == BCME_BUSY) { |
| WL_INFORM_MEM(("device is busy, so delay for a while, target chspec %x\n", |
| csa_info->chanspec)); |
| goto reschedule; |
| } else { |
| wl_cellavoid_free_csa_info(cellavoid_info, csa_info->ndev); |
| if (err < 0) { |
| WL_ERR(("csa failed, target chanspec %x\n", csa_info->chanspec)); |
| delay = CSA_DELAYWORK_FAIL_INTERVAL; |
| } else { |
| delay = CSA_DELAYWORK_CSA_INTERVAL; |
| } |
| |
| } |
| } |
| |
| exit: |
| if (list_empty(&cellavoid_info->csa_info_list)) { |
| mutex_unlock(&cellavoid_info->sync); |
| return; |
| } |
| |
| reschedule: |
| if (cellavoid_info->csa_reschedule_cnt < CSA_MAX_RETRY_CNT) { |
| cellavoid_info->csa_reschedule_cnt++; |
| schedule_delayed_work(&cfg->csa_delayed_work, |
| msecs_to_jiffies((const unsigned int)delay)); |
| } else { |
| WL_ERR(("Hit CSA retry limit\n")); |
| wl_cellavoid_free_csa_info_list(cellavoid_info); |
| } |
| mutex_unlock(&cellavoid_info->sync); |
| } |
| |
| /* Move channel items from unsafe channel list (cellular channel list) to |
| * safe channel list(available channel list) |
| */ |
| static void |
| wl_cellavoid_clear_cell_chan_list(wl_cellavoid_info_t *cellavoid_info) |
| { |
| wl_cellavoid_chan_info_t *chan_info, *next; |
| |
| WL_INFORM(("%s: Enter\n", __FUNCTION__)); |
| GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); |
| list_for_each_entry_safe(chan_info, next, &cellavoid_info->cell_chan_info_list, list) { |
| GCC_DIAGNOSTIC_POP(); |
| list_del(&chan_info->list); |
| /* Restore channel info to the value of safe channel */ |
| chan_info->pwr_cap = CELLAVOID_DEFAULT_TXCAP; |
| list_add(&chan_info->list, &cellavoid_info->avail_chan_info_list); |
| } |
| cellavoid_info->cell_chan_info_cnt = 0; |
| cellavoid_info->mandatory_flag = 0; |
| } |
| |
| /* Free all the channel items in the safe channel list(available channel list) |
| * Called on country code change |
| */ |
| static void |
| wl_cellavoid_free_avail_chan_list(wl_cellavoid_info_t *cellavoid_info) |
| { |
| wl_cellavoid_chan_info_t *chan_info, *next; |
| |
| WL_INFORM(("%s: Enter\n", __FUNCTION__)); |
| GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); |
| list_for_each_entry_safe(chan_info, next, &cellavoid_info->avail_chan_info_list, list) { |
| GCC_DIAGNOSTIC_POP(); |
| list_del(&chan_info->list); |
| MFREE(cellavoid_info->osh, chan_info, sizeof(*chan_info)); |
| } |
| } |
| |
| /* Used when framework sets unsafe channel |
| * If the chanspec from the framework matches with items |
| * in the safe channel list(available channel list) |
| * Then, detach the item from the list, and returns the pointer for the channel item |
| */ |
| static wl_cellavoid_chan_info_t * |
| wl_cellavoid_get_chan_info_from_avail_chan_list(wl_cellavoid_info_t *cellavoid_info, |
| chanspec_t chanspec) |
| { |
| wl_cellavoid_chan_info_t *chan_info, *next; |
| wl_cellavoid_chan_info_t *ret = NULL; |
| |
| GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); |
| list_for_each_entry_safe(chan_info, next, &cellavoid_info->avail_chan_info_list, list) { |
| GCC_DIAGNOSTIC_POP(); |
| if (chan_info->chanspec == chanspec) { |
| list_del(&chan_info->list); |
| WL_INFORM(("%s: removed in list, chanspec: %x\n", |
| __FUNCTION__, chanspec)); |
| ret = chan_info; |
| break; |
| } |
| } |
| |
| return ret; |
| } |
| |
| /* Check the chanspec is whether safe or unsafe */ |
| static cellavoid_ch_state_t |
| wl_cellavoid_get_chan_info(wl_cellavoid_info_t *cellavoid_info, chanspec_t chanspec) |
| { |
| wl_cellavoid_chan_info_t *chan_info, *next; |
| |
| GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); |
| list_for_each_entry_safe(chan_info, next, &cellavoid_info->cell_chan_info_list, list) { |
| GCC_DIAGNOSTIC_POP(); |
| if (chan_info->chanspec == chanspec) { |
| return CELLAVOID_STATE_CH_UNSAFE; |
| } |
| } |
| |
| return CELLAVOID_STATE_CH_SAFE; |
| } |
| |
| bool |
| wl_cellavoid_is_safe(void *cai, chanspec_t chanspec) |
| { |
| wl_cellavoid_info_t *cellavoid_info = cai; |
| |
| if (wl_cellavoid_get_chan_info(cellavoid_info, chanspec) == CELLAVOID_STATE_CH_UNSAFE) { |
| return FALSE; |
| } else { |
| return TRUE; |
| } |
| } |
| |
| bool |
| wl_cellavoid_mandatory_isset(void *cai, enum nl80211_iftype type) |
| { |
| bool mandatory = FALSE; |
| wl_cellavoid_info_t *cellavoid_info = cai; |
| |
| switch (type) { |
| case NL80211_IFTYPE_P2P_GO: |
| case NL80211_IFTYPE_P2P_DEVICE: |
| if (cellavoid_info->mandatory_flag & WL_CELL_AVOID_WIFI_DIRECT) { |
| mandatory = TRUE; |
| } |
| break; |
| case NL80211_IFTYPE_AP: |
| if (cellavoid_info->mandatory_flag & WL_CELL_AVOID_SOFTAP) { |
| mandatory = TRUE; |
| } |
| break; |
| |
| default: |
| break; |
| } |
| return mandatory; |
| } |
| |
| wifi_interface_mode |
| wl_cellavoid_mandatory_to_usable_channel_filter(void *cai) |
| { |
| wifi_interface_mode mode = 0; |
| wl_cellavoid_info_t *cellavoid_info = cai; |
| |
| if (cellavoid_info->mandatory_flag & WL_CELL_AVOID_WIFI_DIRECT) { |
| mode |= ((1U << WIFI_INTERFACE_P2P_GO) | (1U << WIFI_INTERFACE_P2P_CLIENT)); |
| } |
| if (cellavoid_info->mandatory_flag & WL_CELL_AVOID_SOFTAP) { |
| mode |= (1U << WIFI_INTERFACE_SOFTAP); |
| } |
| if (cellavoid_info->mandatory_flag & WL_CELL_AVOID_NAN) { |
| mode |= (1U << WIFI_INTERFACE_NAN); |
| } |
| |
| return mode; |
| } |
| |
| /* Check the chanspec is whether safe or unsafe */ |
| bool |
| wl_cellavoid_operation_allowed(void *cai, chanspec_t chanspec, |
| enum nl80211_iftype type) |
| { |
| bool allowed = TRUE; |
| wl_cellavoid_info_t *cellavoid_info = cai; |
| |
| if (wl_cellavoid_get_chan_info(cellavoid_info, chanspec) == CELLAVOID_STATE_CH_UNSAFE && |
| wl_cellavoid_mandatory_isset(cellavoid_info, type)) { |
| allowed = FALSE; |
| } |
| |
| return allowed; |
| } |
| |
| /* Used when moving channel item to the unsafe channel list (cellular channel list) */ |
| static void |
| wl_cellavoid_move_chan_info_to_cell_chan_list(wl_cellavoid_info_t *cellavoid_info, |
| wl_cellavoid_chan_info_t * chan_info) |
| { |
| list_add(&chan_info->list, &cellavoid_info->cell_chan_info_list); |
| cellavoid_info->cell_chan_info_cnt++; |
| } |
| |
| /* Used when moving channel item to the safe channel list (avail channel list) */ |
| static void |
| wl_cellavoid_move_chan_info_to_avail_chan_list(wl_cellavoid_info_t *cellavoid_info, |
| wl_cellavoid_chan_info_t * chan_info) |
| { |
| list_add(&chan_info->list, &cellavoid_info->avail_chan_info_list); |
| } |
| |
| /* Used when sorting the channel list |
| * wider bw, large channel number comes first and |
| * narrower bw, small channel number comes later after sorting |
| */ |
| static int |
| wl_cellavoid_chan_info_compare(void *priv, struct list_head *a, struct list_head *b) |
| { |
| uint8 i1_chan, i2_chan; |
| uint16 i1_bw, i2_bw; |
| |
| wl_cellavoid_chan_info_t *info1 = CONTAINEROF(a, wl_cellavoid_chan_info_t, list); |
| wl_cellavoid_chan_info_t *info2 = CONTAINEROF(b, wl_cellavoid_chan_info_t, list); |
| |
| i1_chan = wf_chspec_ctlchan(info1->chanspec); |
| i2_chan = wf_chspec_ctlchan(info2->chanspec); |
| i1_bw = CHSPEC_BW(info1->chanspec); |
| i2_bw = CHSPEC_BW(info2->chanspec); |
| |
| /* Wider BW comes first */ |
| if (i1_bw > i2_bw) { |
| return -1; |
| } else if (i1_bw < i2_bw) { |
| return 1; |
| } else { |
| if (i1_chan > i2_chan) { |
| return -1; |
| } else if (i1_chan < i2_chan) { |
| return 1; |
| } else { |
| return 0; |
| } |
| } |
| } |
| |
| /* Used when sorting the channel list, sort both unsafe channel list (cellular channel list) and |
| * safe channel list (avail channel list) |
| */ |
| static void |
| wl_cellavoid_sort_chan_info_list(wl_cellavoid_info_t *cellavoid_info) |
| { |
| /* Sorting ascending */ |
| list_sort(NULL, &cellavoid_info->cell_chan_info_list, wl_cellavoid_chan_info_compare); |
| list_sort(NULL, &cellavoid_info->avail_chan_info_list, wl_cellavoid_chan_info_compare); |
| } |
| |
| #ifdef WL_CELLULAR_CHAN_AVOID_DUMP |
| /* Dump function, shows chanspec/pwrcap item both in the unsafe channel list (cellular channel list) |
| * and safe channel list (avail channel list) |
| */ |
| static void |
| wl_cellavoid_dump_chan_info_list(wl_cellavoid_info_t *cellavoid_info) |
| { |
| wl_cellavoid_chan_info_t *chan_info, *next; |
| char chanspec_str[CHANSPEC_STR_LEN]; |
| |
| GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); |
| list_for_each_entry_safe(chan_info, next, &cellavoid_info->cell_chan_info_list, list) { |
| GCC_DIAGNOSTIC_POP(); |
| wf_chspec_ntoa(chan_info->chanspec, chanspec_str); |
| WL_MEM(("Cellular : chanspec %s(%x), pwrcap %d\n", |
| chanspec_str, chan_info->chanspec, chan_info->pwr_cap)); |
| } |
| |
| GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); |
| list_for_each_entry_safe(chan_info, next, &cellavoid_info->avail_chan_info_list, list) { |
| GCC_DIAGNOSTIC_POP(); |
| wf_chspec_ntoa(chan_info->chanspec, chanspec_str); |
| WL_MEM(("Avail : chanspec %s(%x), pwrcap %d\n", |
| chanspec_str, chan_info->chanspec, chan_info->pwr_cap)); |
| } |
| |
| } |
| #endif /* WL_CELLULAR_CHAN_AVOID_DUMP */ |
| |
| /* Allocate channel item(chanspec + pwrcap), called upon country code change */ |
| static wl_cellavoid_chan_info_t * |
| wl_cellavoid_alloc_chan_info(wl_cellavoid_info_t *cellavoid_info, chanspec_t chanspec) |
| { |
| wl_cellavoid_chan_info_t *chan_info = NULL; |
| |
| /* Allocate availabe channel info */ |
| chan_info = (wl_cellavoid_chan_info_t *) |
| MALLOCZ(cellavoid_info->osh, sizeof(wl_cellavoid_chan_info_t)); |
| if (!chan_info) { |
| WL_ERR(("failed to allocate chan info\n")); |
| return NULL; |
| } |
| |
| chan_info->chanspec = chanspec; |
| chan_info->pwr_cap = CELLAVOID_DEFAULT_TXCAP; |
| |
| return chan_info; |
| } |
| |
| /* Allocate channel item(chanspec + pwrcap) per band (2g or 5g) */ |
| static int |
| wl_cellavoid_alloc_avail_chan_list_band(wl_cellavoid_info_t *cellavoid_info, |
| struct ieee80211_supported_band *sband) |
| { |
| struct ieee80211_channel *channel; |
| wl_cellavoid_chan_info_t *chan_info = NULL; |
| int i, j; |
| uint16 bandwidth[] = {WL_CHANSPEC_BW_40, WL_CHANSPEC_BW_80}; |
| uint8 ctlchan; |
| chanspec_band_t band; |
| chanspec_t chanspec = INVCHANSPEC; |
| |
| for (i = 0; i < sband->n_channels; i++) { |
| channel = &sband->channels[i]; |
| /* If channel from Kernel wiphy is disabled state or DFS channel, drop */ |
| if (channel->flags & IEEE80211_CHAN_DISABLED || |
| IS_RADAR_CHAN(channel->flags)) { |
| WL_MEM(("chanspec %x is not allowed\n", channel->hw_value)); |
| continue; |
| } |
| |
| /* Allocate channel item (chanspec + pwrcap) */ |
| chan_info = wl_cellavoid_alloc_chan_info(cellavoid_info, channel->hw_value); |
| if (chan_info == NULL) { |
| goto free_list; |
| } |
| |
| /* Move allocated channel item(20Mhz) |
| * to the safe channel list (avail channel list) |
| */ |
| wl_cellavoid_move_chan_info_to_avail_chan_list(cellavoid_info, chan_info); |
| |
| if (sband->band == NL80211_BAND_5GHZ) { |
| /* If 5ghz, also create channel item for 40/80 chanspec */ |
| ctlchan = wf_chspec_ctlchan(chan_info->chanspec); |
| band = CHSPEC_BAND(chan_info->chanspec); |
| ASSERT(band == WL_CHANSPEC_BAND_5G); |
| for (j = 0; j < (sizeof(bandwidth) / sizeof(uint16)); j++) { |
| #ifdef WL_6G_320_SUPPORT |
| chanspec = wf_create_chspec_from_primary(ctlchan, |
| bandwidth[j], band, 0); |
| #else |
| chanspec = wf_create_chspec_from_primary(ctlchan, |
| bandwidth[j], band); |
| #endif /* WL_6G_320_SUPPORT */ |
| if (chanspec == INVCHANSPEC) { |
| WL_ERR(("invalid chanspec ctlchan %d, band %d " |
| "bandwidth %d\n", ctlchan, band, bandwidth[j])); |
| goto free_list; |
| } |
| /* Allocate channel item (chanspec + pwrcap) for 40/80 chanspec */ |
| chan_info = wl_cellavoid_alloc_chan_info(cellavoid_info, chanspec); |
| if (chan_info == NULL) { |
| goto free_list; |
| } |
| |
| /* Add 40/80 chanspec item to the available channel list */ |
| wl_cellavoid_move_chan_info_to_avail_chan_list(cellavoid_info, |
| chan_info); |
| } |
| } |
| } |
| return BCME_OK; |
| |
| free_list: |
| /* If there's an error, free all the items in the safe channel list (avail channel list) */ |
| wl_cellavoid_free_avail_chan_list(cellavoid_info); |
| return BCME_NOMEM; |
| } |
| |
| /* This function is used verifying channel items |
| * created by wl_cellavoid_alloc_avail_chan_list are valid |
| * by comparing the channel item to chan_info_list from FW |
| * If it does not match with chan_info_list, drop |
| * CH165 only support 20MHz BW, so 165/40, 165/80 chanspecs created by |
| * wl_cellavoid_alloc_avail_chan_list_band are dropped by this function |
| */ |
| static int |
| wl_cellavoid_verify_avail_chan_list(struct bcm_cfg80211 *cfg, wl_cellavoid_info_t *cellavoid_info) |
| { |
| wl_cellavoid_chan_info_t *chan_info, *next; |
| u16 list_count; |
| void *dngl_chan_list; |
| bool legacy_chan_info = FALSE; |
| bool found; |
| int i, err; |
| chanspec_t chanspec = 0; |
| char chanspec_str[CHANSPEC_STR_LEN]; |
| |
| /* Get chan_info_list or chanspec from FW */ |
| #define LOCAL_BUF_LEN 4096 |
| dngl_chan_list = MALLOCZ(cfg->osh, LOCAL_BUF_LEN); |
| if (dngl_chan_list == NULL) { |
| WL_ERR(("failed to allocate local buf\n")); |
| return BCME_NOMEM; |
| } |
| |
| err = wldev_iovar_getbuf_bsscfg(bcmcfg_to_prmry_ndev(cfg), "chan_info_list", NULL, |
| 0, dngl_chan_list, LOCAL_BUF_LEN, 0, &cfg->ioctl_buf_sync); |
| if (err == BCME_UNSUPPORTED) { |
| WL_INFORM(("get chan_info_list, UNSUPPORTED\n")); |
| err = wldev_iovar_getbuf_bsscfg(bcmcfg_to_prmry_ndev(cfg), "chanspecs", NULL, |
| 0, dngl_chan_list, LOCAL_BUF_LEN, 0, &cfg->ioctl_buf_sync); |
| if (err != BCME_OK) { |
| WL_ERR(("get chanspecs err(%d)\n", err)); |
| MFREE(cfg->osh, dngl_chan_list, LOCAL_BUF_LEN); |
| return err; |
| } |
| /* Update indicating legacy chan info usage */ |
| legacy_chan_info = TRUE; |
| } else if (err != BCME_OK) { |
| WL_ERR(("get chan_info_list err(%d)\n", err)); |
| MFREE(cfg->osh, dngl_chan_list, LOCAL_BUF_LEN); |
| return err; |
| } |
| |
| list_count = legacy_chan_info ? ((wl_uint32_list_t *)dngl_chan_list)->count : |
| ((wl_chanspec_list_v1_t *)dngl_chan_list)->count; |
| |
| /* Comparing the channel item to chan_info_list from FW |
| * If the chanspec in channel item is not supported by FW, |
| * delete it from the safe channel list (avail channel list) |
| */ |
| GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); |
| list_for_each_entry_safe(chan_info, next, &cellavoid_info->avail_chan_info_list, list) { |
| GCC_DIAGNOSTIC_POP(); |
| found = FALSE; |
| for (i = 0; i < dtoh32(list_count); i++) { |
| if (legacy_chan_info) { |
| chanspec = (chanspec_t) |
| dtoh32(((wl_uint32_list_t *)dngl_chan_list)->element[i]); |
| } else { |
| chanspec = (chanspec_t)dtoh32 |
| (((wl_chanspec_list_v1_t *)dngl_chan_list)->chspecs[i].chanspec); |
| } |
| |
| if (chan_info->chanspec == chanspec) { |
| found = TRUE; |
| break; |
| } |
| } |
| |
| if (found == FALSE) { |
| list_del(&chan_info->list); |
| wf_chspec_ntoa(chan_info->chanspec, chanspec_str); |
| WL_INFORM_MEM(("chanspec %s(%x) is removed from avail list\n", |
| chanspec_str, chan_info->chanspec)); |
| MFREE(cfg->osh, chan_info, sizeof(*chan_info)); |
| } |
| } |
| MFREE(cfg->osh, dngl_chan_list, LOCAL_BUF_LEN); |
| #undef LOCAL_BUF_LEN |
| |
| return BCME_OK; |
| } |
| |
| /* Allocate channel item(chanspec + pwrcap) from both band (2g and 5g) |
| * Channel information comes from wiphy in Kernel |
| */ |
| static int |
| wl_cellavoid_alloc_avail_chan_list(struct wiphy *wiphy, wl_cellavoid_info_t *cellavoid_info) |
| { |
| struct ieee80211_supported_band *sband; |
| int ret; |
| |
| sband = wiphy->bands[IEEE80211_BAND_2GHZ]; |
| if (!sband || !sband->n_channels) { |
| WL_ERR(("No 2ghz channel exists\n")); |
| return BCME_ERROR; |
| } |
| |
| ret = wl_cellavoid_alloc_avail_chan_list_band(cellavoid_info, sband); |
| if (ret) { |
| return ret; |
| } |
| |
| sband = wiphy->bands[IEEE80211_BAND_5GHZ]; |
| if (!sband || !sband->n_channels) { |
| WL_ERR(("No 5ghz channel exists\n")); |
| return BCME_OK; |
| } |
| |
| ret = wl_cellavoid_alloc_avail_chan_list_band(cellavoid_info, sband); |
| if (ret) { |
| return ret; |
| } |
| |
| /* Verify channel list from the IOVAR chanspecs */ |
| return wl_cellavoid_verify_avail_chan_list(wiphy_priv(wiphy), cellavoid_info); |
| } |
| |
| /* Used to remove existing pwrcap in the FW */ |
| static int |
| wl_cellavoid_restore_txpwrcap(struct bcm_cfg80211 *cfg, wl_cellavoid_info_t *cellavoid_info) |
| { |
| int ret; |
| int hdr_size, payload_size, total_size; |
| bcm_iov_buf_t *iov_buf = NULL; |
| wl_cell_avoid_ch_info_v1_t *subcmd; |
| |
| hdr_size = OFFSETOF(bcm_iov_buf_t, data); |
| payload_size = sizeof(*subcmd); |
| total_size = hdr_size + payload_size; |
| |
| iov_buf = (bcm_iov_buf_t *)MALLOCZ(cfg->osh, total_size); |
| if (iov_buf == NULL) { |
| WL_ERR(("memory alloc failure size %d\n", total_size)); |
| return BCME_NOMEM; |
| } |
| |
| iov_buf->version = htod16(WL_CELL_AVOID_IOV_VERSION_1); |
| iov_buf->id = htod16(WL_CELL_AVOID_SUBCMD_CH_INFO); |
| iov_buf->len = htod16(payload_size); |
| |
| subcmd = (wl_cell_avoid_ch_info_v1_t *)iov_buf->data; |
| subcmd->version = htod16(WL_CELL_AVOID_SUB_IOV_VERSION_1); |
| subcmd->length = htod16(payload_size); |
| /* This will remove cellular channel info in FW and FW will reset txpwrcap */ |
| subcmd->flags = htod16(WL_CELL_AVOID_REMOVE_CH_INFO); |
| |
| ret = wldev_iovar_setbuf(bcmcfg_to_prmry_ndev(cfg), "cellavoid", (char *)iov_buf, |
| total_size, cfg->ioctl_buf, WLC_IOCTL_SMLEN, NULL); |
| if (ret != BCME_OK) { |
| WL_ERR(("fail to restore txpwrcap ret : %d\n", ret)); |
| } |
| |
| MFREE(cfg->osh, iov_buf, total_size); |
| |
| return ret; |
| } |
| |
| /* Used to deliver chanspec + pwrcap to the FW */ |
| static int |
| wl_cellavoid_apply_txpwrcap(struct bcm_cfg80211 *cfg, wl_cellavoid_info_t *cellavoid_info) |
| { |
| int ret; |
| int hdr_size, payload_size, total_size; |
| bcm_iov_buf_t *iov_buf = NULL; |
| wl_cell_avoid_ch_info_v1_t *subcmd; |
| wl_cellavoid_chan_info_t *chan_info, *next; |
| int i = 0; |
| |
| /* If there isn't any unsafe channel(cellular channel), |
| * then remove cellular channel + pwrcap info in FW |
| */ |
| if (cellavoid_info->cell_chan_info_cnt == 0) { |
| return wl_cellavoid_restore_txpwrcap(cfg, cellavoid_info); |
| } |
| |
| hdr_size = OFFSETOF(bcm_iov_buf_t, data); |
| payload_size = sizeof(*subcmd) + |
| cellavoid_info->cell_chan_info_cnt * sizeof(wl_cell_pwrcap_chanspec_v1_t); |
| total_size = hdr_size + payload_size; |
| |
| iov_buf = (bcm_iov_buf_t *)MALLOCZ(cfg->osh, total_size); |
| if (iov_buf == NULL) { |
| WL_ERR(("memory alloc failure size %d\n", total_size)); |
| return -ENOMEM; |
| } |
| |
| iov_buf->version = htod16(WL_CELL_AVOID_IOV_VERSION_1); |
| iov_buf->id = htod16(WL_CELL_AVOID_SUBCMD_CH_INFO); |
| iov_buf->len = htod16(payload_size); |
| |
| subcmd = (wl_cell_avoid_ch_info_v1_t *)iov_buf->data; |
| subcmd->version = htod16(WL_CELL_AVOID_SUB_IOV_VERSION_1); |
| subcmd->length = htod16(payload_size); |
| subcmd->flags = htod16(cellavoid_info->mandatory_flag); |
| subcmd->cnt = htod16(cellavoid_info->cell_chan_info_cnt); |
| |
| GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); |
| list_for_each_entry_safe(chan_info, next, &cellavoid_info->cell_chan_info_list, list) { |
| GCC_DIAGNOSTIC_POP(); |
| subcmd->list[i].chanspec = htod16(chan_info->chanspec); |
| subcmd->list[i].pwrcap = chan_info->pwr_cap; |
| i++; |
| } |
| ASSERT(cellavoid_info->cell_chan_info_cnt == i); |
| |
| ret = wldev_iovar_setbuf(bcmcfg_to_prmry_ndev(cfg), "cellavoid", (char *)iov_buf, |
| total_size, cfg->ioctl_buf, WLC_IOCTL_MEDLEN, NULL); |
| if (ret != BCME_OK) { |
| WL_ERR(("fail to set txpwrcap ret : %d\n", ret)); |
| } |
| |
| return ret; |
| } |
| |
| int |
| wl_cellavoid_set_requested_freq_bands(struct net_device *ndev, |
| void *cai, u32 *pElem_freq, u32 freq_list_len) |
| { |
| int i, j; |
| chanspec_t chanspec; |
| wl_cellavoid_info_t *cellavoid_info = cai; |
| |
| for (i = 0; i < MAX_AP_INTERFACE; i++) { |
| if (cellavoid_info->req_band[i].ndev == NULL) { |
| break; |
| } |
| } |
| |
| if (i == MAX_AP_INTERFACE) { |
| for (i = 0; i < MAX_AP_INTERFACE; i++) { |
| WL_ERR(("No empty slot, name %s, req_band %x in slot %d\n", |
| cellavoid_info->req_band[i].ndev->name, |
| cellavoid_info->req_band[i].req_band, i)); |
| cellavoid_info->req_band[i].ndev = NULL; |
| cellavoid_info->req_band[i].req_band = WLC_BAND_INVALID; |
| } |
| |
| /* Reset i = 0 */ |
| i = 0; |
| } |
| |
| cellavoid_info->req_band[i].ndev = ndev; |
| cellavoid_info->req_band[i].req_band = 0; |
| |
| for (j = 0; j < freq_list_len; j++) { |
| chanspec = wl_freq_to_chanspec(pElem_freq[j]); |
| /* mark all the bands found */ |
| cellavoid_info->req_band[i].req_band |= |
| CHSPEC_TO_WLC_BAND(CHSPEC_BAND(chanspec)); |
| } |
| |
| WL_INFORM_MEM(("name %s, req_band %x is in slot %d\n", |
| cellavoid_info->req_band[i].ndev->name, cellavoid_info->req_band[i].req_band, i)); |
| |
| return BCME_OK; |
| } |
| |
| void |
| wl_cellavoid_clear_requested_freq_bands(struct net_device *ndev, void *cai) |
| { |
| int i; |
| wl_cellavoid_info_t *cellavoid_info = cai; |
| |
| for (i = 0; i < MAX_AP_INTERFACE; i++) { |
| if (cellavoid_info->req_band[i].ndev == ndev) { |
| break; |
| } |
| } |
| |
| if (i == MAX_AP_INTERFACE) { |
| for (i = 0; i < MAX_AP_INTERFACE; i++) { |
| WL_ERR(("No empty slot, id %d, name %s, req_band %x\n", |
| i, cellavoid_info->req_band[i].ndev->name, |
| cellavoid_info->req_band[i].req_band)); |
| } |
| return; |
| } |
| |
| WL_INFORM_MEM(("name %s, req_band %x in slot %d cleared\n", |
| cellavoid_info->req_band[i].ndev->name, cellavoid_info->req_band[i].req_band, i)); |
| |
| cellavoid_info->req_band[i].ndev = NULL; |
| cellavoid_info->req_band[i].req_band = WLC_BAND_INVALID; |
| } |
| |
| static int |
| wl_cellavoid_find_requested_freq_bands(struct net_device *ndev, wl_cellavoid_info_t *cellavoid_info) |
| { |
| int i; |
| |
| for (i = 0; i < MAX_AP_INTERFACE; i++) { |
| if (cellavoid_info->req_band[i].ndev == ndev) { |
| break; |
| } |
| } |
| |
| if (i == MAX_AP_INTERFACE) { |
| for (i = 0; i < MAX_AP_INTERFACE; i++) { |
| WL_ERR(("can not find valid slot, id %d, name %s, req_band %x\n", |
| i, cellavoid_info->req_band[i].ndev->name, |
| cellavoid_info->req_band[i].req_band)); |
| |
| } |
| return WLC_BAND_INVALID; |
| } |
| |
| return cellavoid_info->req_band[i].req_band; |
| } |
| |
| static wl_cellavoid_chan_info_t * |
| wl_cellavoid_find_chinfo_sameband(wl_cellavoid_info_t *cellavoid_info, |
| chanspec_t chanspec) |
| { |
| wl_cellavoid_chan_info_t *chan_info, *next; |
| wl_cellavoid_chan_info_t *ret = NULL; |
| |
| /* Find channel info with same band in available channel list(safe channel) first */ |
| GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); |
| list_for_each_entry_safe(chan_info, next, &cellavoid_info->avail_chan_info_list, list) { |
| GCC_DIAGNOSTIC_POP(); |
| if (CHSPEC_TO_WLC_BAND(chan_info->chanspec) == |
| CHSPEC_TO_WLC_BAND(chanspec)) { |
| ret = chan_info; |
| WL_INFORM_MEM(("chanspec %x found in avail list\n", chan_info->chanspec)); |
| goto exit; |
| } |
| } |
| |
| /* If it's not found and mandatory flag is set return null */ |
| if (cellavoid_info->mandatory_flag & WL_CELL_AVOID_SOFTAP) { |
| WL_INFORM_MEM(("No chanspec in avail list and mandatory flag set\n")); |
| goto exit; |
| } |
| |
| /* If it's not found and mandatory flag is zeo, |
| * Find the current chanspec from cellular channel list(unsafe list) |
| * This is to reduce the number of channel switch trial |
| */ |
| GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); |
| list_for_each_entry_safe(chan_info, next, &cellavoid_info->cell_chan_info_list, list) { |
| GCC_DIAGNOSTIC_POP(); |
| if (chan_info->chanspec == chanspec) { |
| ret = chan_info; |
| WL_INFORM_MEM(("chanspec %x found in cellular list\n", |
| chan_info->chanspec)); |
| goto exit; |
| } |
| } |
| |
| exit: |
| if (ret == NULL) { |
| WL_INFORM_MEM(("No chanspec in avail list/cellular list\n")); |
| } |
| |
| return ret; |
| } |
| |
| static wl_cellavoid_chan_info_t * |
| wl_cellavoid_find_chinfo_fromchspec(wl_cellavoid_info_t *cellavoid_info, |
| chanspec_t chanspec) |
| { |
| wl_cellavoid_chan_info_t *chan_info, *next; |
| wl_cellavoid_chan_info_t *ret = NULL; |
| |
| /* Find in available channel list(safe channel) first */ |
| GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); |
| list_for_each_entry_safe(chan_info, next, &cellavoid_info->avail_chan_info_list, list) { |
| GCC_DIAGNOSTIC_POP(); |
| /* List is always sorted to come wide bw comes first, |
| * so the first one is the widest one |
| */ |
| if (wf_chspec_ctlchan(chan_info->chanspec) == wf_chspec_ctlchan(chanspec)) { |
| ret = chan_info; |
| WL_INFORM_MEM(("chanspec %x found in avail list\n", chan_info->chanspec)); |
| goto exit; |
| } |
| } |
| |
| /* If it's not found and mandatory flag is set return null */ |
| if (cellavoid_info->mandatory_flag & WL_CELL_AVOID_SOFTAP) { |
| WL_INFORM_MEM(("No chanspec in avail list and mandatory flag set\n")); |
| goto exit; |
| } |
| |
| /* If it's not found and mandatory flag is zeo, |
| * pick up the chanspec from cellular channel list(unsafe list) |
| */ |
| GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); |
| list_for_each_entry_safe(chan_info, next, &cellavoid_info->cell_chan_info_list, list) { |
| GCC_DIAGNOSTIC_POP(); |
| /* List is always sorted to come wide bw comes first, |
| * so the first one is the widest one |
| */ |
| if (wf_chspec_ctlchan(chan_info->chanspec) == wf_chspec_ctlchan(chanspec)) { |
| ret = chan_info; |
| WL_INFORM_MEM(("chanspec %x found in cellular list\n", |
| chan_info->chanspec)); |
| goto exit; |
| } |
| } |
| |
| exit: |
| if (ret == NULL) { |
| wl_cellavoid_chan_info_t *chan_info, *next; |
| char chanspec_str[CHANSPEC_STR_LEN]; |
| |
| GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); |
| list_for_each_entry_safe(chan_info, next, &cellavoid_info->cell_chan_info_list, |
| list) { |
| GCC_DIAGNOSTIC_POP(); |
| wf_chspec_ntoa(chan_info->chanspec, chanspec_str); |
| WL_INFORM_MEM(("Cellular : chanspec %s(%x), pwrcap %d\n", |
| chanspec_str, chan_info->chanspec, chan_info->pwr_cap)); |
| } |
| |
| GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); |
| list_for_each_entry_safe(chan_info, next, &cellavoid_info->avail_chan_info_list, |
| list) { |
| GCC_DIAGNOSTIC_POP(); |
| wf_chspec_ntoa(chan_info->chanspec, chanspec_str); |
| WL_INFORM_MEM(("Avail : chanspec %s(%x), pwrcap %d\n", |
| chanspec_str, chan_info->chanspec, chan_info->pwr_cap)); |
| } |
| WL_INFORM_MEM(("No chanspec in avail list/cellular list\n")); |
| } |
| |
| return ret; |
| } |
| |
| static wl_cellavoid_chan_info_t * |
| wl_cellavoid_find_chinfo_fromband(wl_cellavoid_info_t *cellavoid_info, int band) |
| { |
| wl_cellavoid_chan_info_t *chan_info, *next; |
| wl_cellavoid_chan_info_t *ret = NULL; |
| |
| /* Find in available channel list(safe channel) first */ |
| GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); |
| list_for_each_entry_safe(chan_info, next, &cellavoid_info->avail_chan_info_list, list) { |
| GCC_DIAGNOSTIC_POP(); |
| if (CHSPEC_TO_WLC_BAND(chan_info->chanspec) == band) { |
| ret = chan_info; |
| WL_INFORM_MEM(("chanspec %x found in avail list\n", chan_info->chanspec)); |
| goto exit; |
| } |
| } |
| |
| /* If it's not found and mandatory flag is set return null */ |
| if (cellavoid_info->mandatory_flag & WL_CELL_AVOID_SOFTAP) { |
| WL_INFORM_MEM(("No chanspec in avail list and mandatory flag set\n")); |
| goto exit; |
| } |
| |
| /* If it's not found and mandatory flag is zeo, |
| * pick up the chanspec from cellular channel list(unsafe list) |
| */ |
| GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); |
| list_for_each_entry_safe(chan_info, next, &cellavoid_info->cell_chan_info_list, list) { |
| GCC_DIAGNOSTIC_POP(); |
| if (CHSPEC_TO_WLC_BAND(chan_info->chanspec) == band) { |
| ret = chan_info; |
| WL_INFORM_MEM(("chanspec %x found in cellular list\n", |
| chan_info->chanspec)); |
| goto exit; |
| } |
| } |
| |
| exit: |
| if (ret == NULL) { |
| WL_INFORM_MEM(("No chanspec in avail list/cellular list\n")); |
| } |
| |
| return ret; |
| } |
| |
| chanspec_t |
| wl_cellavoid_find_chspec_fromband(void *cai, int band) |
| { |
| wl_cellavoid_chan_info_t* chan_info; |
| chanspec_t chanspec; |
| wl_cellavoid_info_t *cellavoid_info = cai; |
| |
| chan_info = wl_cellavoid_find_chinfo_fromband(cellavoid_info, band); |
| if (chan_info == NULL) { |
| chanspec = INVCHANSPEC; |
| } else { |
| chanspec = chan_info->chanspec; |
| } |
| |
| return chanspec; |
| } |
| chanspec_t |
| wl_cellavoid_find_widechspec_fromchspec(void *cai, chanspec_t chanspec) |
| { |
| wl_cellavoid_chan_info_t* chan_info; |
| chanspec_t wide_chanspec; |
| wl_cellavoid_info_t *cellavoid_info = cai; |
| |
| chan_info = wl_cellavoid_find_chinfo_fromchspec(cellavoid_info, chanspec); |
| if (chan_info == NULL) { |
| wide_chanspec = INVCHANSPEC; |
| } else { |
| wide_chanspec = chan_info->chanspec; |
| } |
| |
| return wide_chanspec; |
| } |
| |
| static wl_cellavoid_chan_info_t * |
| wl_cellavoid_find_ap_chan_info(struct bcm_cfg80211 *cfg, chanspec_t ap_chspec, |
| chanspec_t sta_chspec, int csa_target_band) |
| { |
| int ap_band, sta_band = WLC_BAND_INVALID; |
| wl_cellavoid_chan_info_t *chan_info = NULL; |
| |
| WL_INFORM_MEM(("AP chspec %x, STA chspec %x, CSA target %x\n", |
| ap_chspec, sta_chspec, csa_target_band)); |
| |
| if (csa_target_band == WLC_BAND_INVALID) { |
| return NULL; |
| } |
| |
| /* This will be checked later */ |
| if (csa_target_band & WLC_BAND_6G) { |
| csa_target_band &= ~WLC_BAND_6G; |
| } |
| |
| ap_band = CHSPEC_TO_WLC_BAND(ap_chspec); |
| if (sta_chspec) { |
| sta_band = CHSPEC_TO_WLC_BAND(sta_chspec); |
| } |
| |
| /* Same band CSA first */ |
| if (csa_target_band & ap_band) { |
| if (sta_band == ap_band) { |
| /* SCC in this core */ |
| WL_INFORM_MEM(("STA in the same core, band %d\n", sta_band)); |
| chan_info = wl_cellavoid_find_chinfo_fromchspec(cfg->cellavoid_info, |
| sta_chspec); |
| } else { |
| /* No STA in this core */ |
| WL_INFORM_MEM(("No STA in the same core, band %d\n", ap_band)); |
| chan_info = wl_cellavoid_find_chinfo_sameband(cfg->cellavoid_info, |
| ap_chspec); |
| } |
| csa_target_band &= ~ap_band; |
| } |
| |
| /* If there's no target to CSA, try in different core */ |
| if (chan_info == NULL && csa_target_band != 0) { |
| if (csa_target_band == sta_band) { |
| /* STA in the another core, so check STA chanspec is available to use |
| * Skip DFS case |
| */ |
| WL_INFORM_MEM(("STA in the another core. band %d\n", csa_target_band)); |
| if (!is_chanspec_dfs(cfg, sta_chspec)) { |
| chan_info = wl_cellavoid_find_chinfo_fromchspec(cfg->cellavoid_info, |
| sta_chspec); |
| } |
| } else { |
| /* No STA in another core */ |
| WL_INFORM_MEM(("No STA in the another core, band %d\n", csa_target_band)); |
| chan_info = wl_cellavoid_find_chinfo_fromband(cfg->cellavoid_info, |
| csa_target_band); |
| } |
| } |
| |
| if (chan_info) { |
| WL_INFORM_MEM(("Found chan info %x\n", chan_info->chanspec)); |
| } |
| |
| return chan_info; |
| } |
| |
| /* After making the safe channel/unsafe channel list, |
| * perform actions needs to be done (AP->CSA and others) |
| * Then, deliver chanspec + pwrcap information to the FW |
| * by calling wl_cellavoid_apply_txpwrcap |
| */ |
| static int |
| wl_cellavoid_handle_apsta_concurrency(struct bcm_cfg80211 *cfg) |
| { |
| wl_cellavoid_info_t *cellavoid_info = cfg->cellavoid_info; |
| wl_ap_oper_data_t ap_oper_data = {0}; |
| cellavoid_ch_state_t ch_state; |
| int i, ap_band = WLC_BAND_INVALID; |
| int req_band, csa_target_band; |
| uint32 sta_cnt; |
| chanspec_t sta_chanspec; |
| wl_cellavoid_chan_info_t *csa_chan_info = NULL; |
| char chanspec_str1[CHANSPEC_STR_LEN], chanspec_str2[CHANSPEC_STR_LEN]; |
| int ret = BCME_OK; |
| |
| sta_cnt = wl_cfgvif_get_iftype_count(cfg, WL_IF_TYPE_STA); |
| if (sta_cnt == MAX_STA_INTERFACE) { |
| /* This is STA+STA case */ |
| return BCME_OK; |
| } |
| |
| /* Check whether AP and STA is already operational */ |
| wl_get_ap_chanspecs(cfg, &ap_oper_data); |
| sta_chanspec = wl_cfg80211_get_sta_chanspec(cfg); |
| |
| /* If there's any AP interface */ |
| if (ap_oper_data.count > 0) { |
| for (i = 0; i < ap_oper_data.count; i++) { |
| ch_state = wl_cellavoid_get_chan_info(cellavoid_info, |
| ap_oper_data.iface[i].chspec); |
| |
| /* If AP is on the safe channel, skip this AP */ |
| if (ch_state == CELLAVOID_STATE_CH_SAFE) { |
| continue; |
| } |
| |
| /* AP channel is unsafe channel(cellular channel) */ |
| /* Get AP band */ |
| ap_band = CHSPEC_TO_WLC_BAND(ap_oper_data.iface[i].chspec); |
| if (ap_oper_data.count == MAX_AP_INTERFACE) { |
| /* 2AP case, CSA in the same wlc */ |
| WL_INFORM_MEM(("AP/AP, CSA only in the same band, AP chanspec %x\n", |
| ap_oper_data.iface[i].chspec)); |
| csa_target_band = ap_band; |
| if (csa_target_band == WLC_BAND_5G) { |
| csa_target_band |= WLC_BAND_6G; |
| } |
| } else { |
| /* 1 AP case, AP bsscfg can move to any wlc */ |
| WL_INFORM_MEM(("AP, CSA to any band, AP chanspec %x\n", |
| ap_oper_data.iface[i].chspec)); |
| csa_target_band = WLC_BAND_2G | WLC_BAND_5G | WLC_BAND_6G; |
| } |
| |
| /* Take ACS band info and |
| * filtering csa_target_band with requested ACS band |
| */ |
| req_band = |
| wl_cellavoid_find_requested_freq_bands(ap_oper_data.iface[i].ndev, |
| cellavoid_info); |
| |
| if (req_band != WLC_BAND_INVALID) { |
| /* If ACS band info is valid, apply it as the filter */ |
| WL_INFORM_MEM(("csa_target_band %x, reqband %x\n", |
| csa_target_band, req_band)); |
| csa_target_band &= req_band; |
| } |
| |
| /* Handle STA concurrency scenario and get chan into to switch channel */ |
| csa_chan_info = wl_cellavoid_find_ap_chan_info(cfg, |
| ap_oper_data.iface[i].chspec, sta_chanspec, csa_target_band); |
| |
| /* If channel exists, schedule channel swith for this AP */ |
| if (csa_chan_info) { |
| /* Schedule CSA only when the target chanspec is different |
| * from cur chanspec |
| */ |
| if (ap_oper_data.iface[i].chspec != csa_chan_info->chanspec) { |
| wf_chspec_ntoa(ap_oper_data.iface[i].chspec, chanspec_str1); |
| wf_chspec_ntoa(csa_chan_info->chanspec, chanspec_str2); |
| WL_INFORM_MEM(("add csa item, chanspec org %s(%x) -> " |
| "target %s(%x)\n", chanspec_str1, |
| ap_oper_data.iface[i].chspec, chanspec_str2, |
| csa_chan_info->chanspec)); |
| ret = wl_cellavoid_add_csa_info(cellavoid_info, |
| ap_oper_data.iface[i].ndev, |
| csa_chan_info->chanspec); |
| if (ret != BCME_OK) { |
| WL_ERR(("add csa info failed\n")); |
| break; |
| } |
| } |
| } else { |
| WL_INFORM_MEM(("AP %s is not allowed to work, cur chanspec %x\n", |
| ap_oper_data.iface[i].ndev->name, |
| ap_oper_data.iface[i].chspec)); |
| } |
| } |
| } |
| |
| if (ret != BCME_OK) { |
| wl_cellavoid_free_csa_info_list(cellavoid_info); |
| } else { |
| if (!list_empty(&cellavoid_info->csa_info_list)) { |
| WL_INFORM_MEM(("scheduling csa work, item count %d\n", |
| cellavoid_info->csa_info_cnt)); |
| cellavoid_info->csa_progress = TRUE; |
| cellavoid_info->csa_reschedule_cnt = 0; |
| schedule_delayed_work(&cfg->csa_delayed_work, |
| msecs_to_jiffies((const unsigned int)CSA_DELAYWORK_FIRST_INTERVAL)); |
| } |
| } |
| |
| return ret; |
| } |
| |
| /* Used by cfg vendor interface to verify the param is correct and update bw/chanspec band */ |
| static int |
| wl_cellavoid_validate_param(struct bcm_cfg80211 *cfg, wl_cellavoid_param_t *param) |
| { |
| int ret = BCME_OK; |
| int i, param_ch; |
| chanspec_band_t param_band; |
| chanspec_bw_t bw; |
| |
| for (i = 0; i < param->chan_cnt; i++) { |
| bw = INVALID_CHSPEC_BW; |
| |
| /* Not supported DFS band */ |
| if (wl_cfgscan_is_dfs_set(param->chan_param[i].band)) { |
| WL_ERR(("Not supported DFS band\n")); |
| ret = -EINVAL; |
| goto exit; |
| } |
| |
| param_ch = param->chan_param[i].center_channel; |
| param_band = (param->chan_param[i].band == WIFI_BAND_BG) ? |
| WL_CHANSPEC_BAND_2G : WL_CHANSPEC_BAND_5G; |
| |
| if (param_band == WL_CHANSPEC_BAND_2G) { |
| if (wf_valid_20MHz_chan(param_ch, param_band)) { |
| bw = WL_CHANSPEC_BW_20; |
| } |
| } else { |
| if (wf_valid_40MHz_center_chan(param_ch, param_band)) { |
| bw = WL_CHANSPEC_BW_40; |
| } else if (wf_valid_80MHz_center_chan(param_ch, param_band)) { |
| bw = WL_CHANSPEC_BW_80; |
| } else if (wf_valid_160MHz_center_chan(param_ch, param_band)) { |
| bw = WL_CHANSPEC_BW_160; |
| } else if (wf_valid_20MHz_chan(param_ch, param_band)) { |
| bw = WL_CHANSPEC_BW_20; |
| } |
| } |
| |
| if (bw == INVALID_CHSPEC_BW) { |
| WL_ERR(("Not supported band %d, channel %d, pwrcap %d\n", |
| param->chan_param[i].band, param->chan_param[i].center_channel, |
| param->chan_param[i].pwr_cap)); |
| ret = -EINVAL; |
| goto exit; |
| } |
| |
| param->chan_param[i].chspec_bw = bw; |
| param->chan_param[i].chspec_band = param_band; |
| } |
| |
| exit: |
| return ret; |
| } |
| |
| static int |
| wl_cellavoid_save_input_params(struct bcm_cfg80211 *cfg, wl_cellavoid_param_t *param) |
| { |
| wl_cellavoid_info_t *cellavoid_info = cfg->cellavoid_info; |
| |
| if (!cellavoid_info || !param) { |
| return -EPERM; |
| } |
| |
| if (cellavoid_info->csa_progress) { |
| return -EBUSY; |
| } |
| |
| /* Backup the input parameter, |
| * this will be used to make safe/unsafe channel list upon the country code change |
| */ |
| cellavoid_info->params.chan_cnt = param->chan_cnt; |
| cellavoid_info->params.mandatory = param->mandatory; |
| |
| (void)memcpy_s(cellavoid_info->params.chan_param, |
| CELLAVOID_MAX_CH * sizeof(*(cellavoid_info->params.chan_param)), |
| param->chan_param, param->chan_cnt * sizeof(*(param->chan_param))); |
| |
| return BCME_OK; |
| } |
| |
| static int |
| wl_cellavoid_set_cell_channels(struct bcm_cfg80211 *cfg, wl_cellavoid_param_t *param) |
| { |
| wl_cellavoid_info_t *cellavoid_info = cfg->cellavoid_info; |
| int ret = BCME_OK, i, j, cnt; |
| int param_ch; |
| chanspec_band_t param_band; |
| chanspec_bw_t param_bw; |
| wl_cellavoid_chan_info_t *chan_info; |
| chanspec_t chspecs[WF_NUM_SIDEBANDS_160MHZ]; |
| |
| WL_INFORM(("%s: Enter\n", __FUNCTION__)); |
| if (!cellavoid_info || !param) { |
| return -EPERM; |
| } |
| |
| /* CSA triggered by cellular channel is on-going */ |
| if (cellavoid_info->csa_progress) { |
| return -EBUSY; |
| } |
| |
| /* Clear unsafe channel list, move back unsafe channel list to safe channel list */ |
| wl_cellavoid_clear_cell_chan_list(cellavoid_info); |
| |
| /* Set mandatory flag */ |
| cellavoid_info->mandatory_flag = param->mandatory; |
| |
| /* Check channel params and set unsafe channel list */ |
| for (i = 0; i < param->chan_cnt; i++) { |
| param_ch = param->chan_param[i].center_channel; |
| param_band = param->chan_param[i].chspec_band; |
| param_bw = param->chan_param[i].chspec_bw; |
| |
| if (wf_valid_160MHz_center_chan(param_ch, param_band)) { |
| WL_INFORM_MEM(("160MHz channel is not supported ch : %d band %x\n", |
| param_ch, param_band)); |
| continue; |
| } |
| |
| bzero(chspecs, sizeof(chspecs)); |
| |
| ret = wl_get_all_sideband_chanspecs(param_ch, param_band, param_bw, |
| chspecs, &cnt); |
| if (ret != BCME_OK || cnt == 0) { |
| WL_ERR(("channel is not supported ch : %d band %d\n", |
| param_ch, param_band)); |
| continue; |
| } |
| |
| for (j = 0; j < cnt; j++) { |
| /* Find chanspecs in the safe channel list(avail channel list) |
| * If the chanspec exists, detach the channel item |
| * from the safe channel list(avail channel list) |
| */ |
| chan_info = wl_cellavoid_get_chan_info_from_avail_chan_list(cellavoid_info, |
| chspecs[j]); |
| if (chan_info == NULL) { |
| WL_MEM(("no chan info for chanspec %x\n", chspecs[j])); |
| continue; |
| } |
| |
| /* If the chanspec exists, set txpwr cap for this chanspec */ |
| chan_info->pwr_cap = param->chan_param[i].pwr_cap; |
| |
| /* Move this chan info to the unsafe channel list(cellular channel list */ |
| wl_cellavoid_move_chan_info_to_cell_chan_list(cellavoid_info, chan_info); |
| } |
| } |
| |
| /* Sort the safe/unsafe channel list for dump */ |
| wl_cellavoid_sort_chan_info_list(cellavoid_info); |
| |
| #ifdef WL_CELLULAR_CHAN_AVOID_DUMP |
| wl_cellavoid_dump_chan_info_list(cellavoid_info); |
| #endif /* WL_CELLULAR_CHAN_AVOID_DUMP */ |
| |
| /* Perform actions needs to be done (AP->CSA) |
| */ |
| ret = wl_cellavoid_handle_apsta_concurrency(cfg); |
| if (ret != BCME_OK) { |
| WL_ERR(("returned fail %d\n", ret)); |
| goto fail; |
| } |
| |
| /* Deliver chanspec + pwrcap information to the FW */ |
| ret = wl_cellavoid_apply_txpwrcap(cfg, cellavoid_info); |
| |
| return ret; |
| |
| fail: |
| wl_cellavoid_clear_cell_chan_list(cellavoid_info); |
| |
| return ret; |
| } |
| |
| static int |
| wl_cellavoid_update_param(struct bcm_cfg80211 *cfg, wl_cellavoid_param_t *param) |
| { |
| int err = BCME_OK; |
| |
| wl_cellavoid_sync_lock(cfg); |
| |
| err = wl_cellavoid_save_input_params(cfg, param); |
| if (err == BCME_OK) { |
| err = wl_cellavoid_set_cell_channels(cfg, param); |
| if (err) { |
| WL_INFORM_MEM(("Failed to set cellular channel list, err:%d\n", err)); |
| } |
| } else { |
| WL_INFORM_MEM(("Failed to save input params, err:%d\n", err)); |
| } |
| |
| wl_cellavoid_sync_unlock(cfg); |
| |
| return err; |
| } |
| |
| /* cfg vendor interface function */ |
| int |
| wl_cfgvendor_cellavoid_set_cell_channels(struct wiphy *wiphy, |
| struct wireless_dev *wdev, const void *data, int len) |
| { |
| int err = BCME_OK, rem, rem1, rem2, type; |
| wl_cellavoid_param_t param; |
| wl_cellavoid_chan_param_t* cur_chan_param = NULL; |
| const struct nlattr *iter, *iter1, *iter2; |
| struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); |
| |
| BCM_REFERENCE(wdev); |
| |
| bzero(¶m, sizeof(param)); |
| if (len <= 0) { |
| WL_ERR(("Length of the nlattr is not valid len : %d\n", len)); |
| err = -EINVAL; |
| goto exit; |
| } |
| nla_for_each_attr(iter, data, len, rem) { |
| type = nla_type(iter); |
| switch (type) { |
| case CELLAVOID_ATTRIBUTE_CNT: |
| param.chan_cnt = nla_get_u8(iter); |
| if (param.chan_cnt > CELLAVOID_MAX_CH) { |
| err = -EINVAL; |
| goto exit; |
| } |
| param.chan_param = (wl_cellavoid_chan_param_t *)MALLOCZ(cfg->osh, |
| sizeof(wl_cellavoid_chan_param_t) * param.chan_cnt); |
| if (param.chan_param == NULL) { |
| WL_ERR(("failed to allocate target param for (%d)\n", |
| param.chan_cnt)); |
| err = -ENOMEM; |
| goto exit; |
| } |
| |
| break; |
| case CELLAVOID_ATTRIBUTE_MANDATORY: |
| param.mandatory = nla_get_u32(iter); |
| break; |
| case CELLAVOID_ATTRIBUTE_CONFIG: |
| if (param.chan_param == NULL) { |
| WL_ERR(("chan_param is NULL (%d)\n", param.chan_cnt)); |
| err = -ENOMEM; |
| goto exit; |
| } |
| cur_chan_param = param.chan_param; |
| nla_for_each_nested(iter1, iter, rem1) { |
| if ((uint8 *)cur_chan_param >= ((uint8 *)param.chan_param + |
| sizeof(wl_cellavoid_chan_param_t) * param.chan_cnt)) { |
| WL_ERR(("increased addr is over its max size\n")); |
| err = -EINVAL; |
| goto exit; |
| } |
| nla_for_each_nested(iter2, iter1, rem2) { |
| type = nla_type(iter2); |
| switch (type) { |
| case CELLAVOID_ATTRIBUTE_BAND: |
| cur_chan_param->band = nla_get_u32(iter2); |
| break; |
| case CELLAVOID_ATTRIBUTE_CHANNEL: |
| cur_chan_param->center_channel = |
| nla_get_u32(iter2); |
| break; |
| case CELLAVOID_ATTRIBUTE_PWRCAP: |
| cur_chan_param->pwr_cap = |
| nla_get_u32(iter2); |
| break; |
| } |
| } |
| cur_chan_param++; |
| } |
| break; |
| } |
| } |
| |
| WL_INFORM_MEM(("CELLAVOID PARAM - CNT:%d MANDATORY:%d\n", |
| param.chan_cnt, param.mandatory)); |
| |
| err = wl_cellavoid_validate_param(cfg, ¶m); |
| if (err) { |
| err = -EINVAL; |
| goto exit; |
| } |
| |
| err = wl_cellavoid_update_param(cfg, ¶m); |
| |
| exit: |
| /* free the config param table */ |
| if (param.chan_param) { |
| MFREE(cfg->osh, param.chan_param, |
| sizeof(wl_cellavoid_chan_param_t) * param.chan_cnt); |
| } |
| return err; |
| } |