diff options
author | Xinrui <xinrui.sun@broadcom.corp-partner.google.com> | 2020-06-14 08:59:44 +0800 |
---|---|---|
committer | Ahmed ElArabawy <arabawy@google.com> | 2020-07-15 17:25:59 -0700 |
commit | 9ab28903cb9589eb3f9cd4bee93f2ac5e27b9a56 (patch) | |
tree | 96c7ecf1b498109413ae914ef33da15fbdc756e7 | |
parent | 2a3e52550189588d6e03710a63ce9f38211d3075 (diff) | |
download | bcm43752-9ab28903cb9589eb3f9cd4bee93f2ac5e27b9a56.tar.gz |
Add ACS functions for softAP
Use freq list from hostapd to do channel select.
Finally send vendor event with selected channel info to hostapd.
Bug: 149885852
Test: ACS works on Hikey
Change-Id: I284314ccb1e031f55eaad47bb48ccef715b02b6e
Signed-off-by: Ahmed ElArabawy <arabawy@google.com>
-rw-r--r-- | include/brcm_nl80211.h | 73 | ||||
-rwxr-xr-x | wl_cfg80211.c | 125 | ||||
-rw-r--r-- | wl_cfg80211.h | 5 | ||||
-rwxr-xr-x | wl_cfgscan.c | 2 | ||||
-rw-r--r-- | wl_cfgvendor.c | 869 | ||||
-rw-r--r-- | wl_cfgvendor.h | 4 |
6 files changed, 1054 insertions, 24 deletions
diff --git a/include/brcm_nl80211.h b/include/brcm_nl80211.h index 29a4281..7b7f8fa 100644 --- a/include/brcm_nl80211.h +++ b/include/brcm_nl80211.h @@ -39,9 +39,80 @@ enum wl_vendor_subcmd { BRCM_VENDOR_SCMD_SET_MAC = 6, BRCM_VENDOR_SCMD_SET_CONNECT_PARAMS = 7, BRCM_VENDOR_SCMD_SET_START_AP_PARAMS = 8, - BRCM_VENDOR_SCMD_MAX = 9 + BRCM_VENDOR_SCMD_ACS = 9, + BRCM_VENDOR_SCMD_MAX = 10 }; +/* Added for HOSTAPD required ACS action */ +#ifdef WL_SUPPORT_AUTO_CHANNEL +#define APCS_MAX_RETRY 10 +#define APCS_DEFAULT_2G_CH 1 +#define APCS_DEFAULT_5G_CH 149 + +enum wl_vendor_attr_acs_offload { + BRCM_VENDOR_ATTR_ACS_CHANNEL_INVALID = 0, + BRCM_VENDOR_ATTR_ACS_PRIMARY_FREQ, + BRCM_VENDOR_ATTR_ACS_SECONDARY_FREQ, + BRCM_VENDOR_ATTR_ACS_VHT_SEG0_CENTER_CHANNEL, + BRCM_VENDOR_ATTR_ACS_VHT_SEG1_CENTER_CHANNEL, + + BRCM_VENDOR_ATTR_ACS_HW_MODE, + BRCM_VENDOR_ATTR_ACS_HT_ENABLED, + BRCM_VENDOR_ATTR_ACS_HT40_ENABLED, + BRCM_VENDOR_ATTR_ACS_VHT_ENABLED, + BRCM_VENDOR_ATTR_ACS_CHWIDTH, + BRCM_VENDOR_ATTR_ACS_CH_LIST, + BRCM_VENDOR_ATTR_ACS_FREQ_LIST, + + BRCM_VENDOR_ATTR_ACS_LAST +}; + +/* defined for hw_mode in hostapd.conf */ +enum hostapd_hw_mode { + HOSTAPD_MODE_IEEE80211B, + HOSTAPD_MODE_IEEE80211G, + HOSTAPD_MODE_IEEE80211A, + HOSTAPD_MODE_IEEE80211AD, + HOSTAPD_MODE_IEEE80211ANY, + NUM_HOSTAPD_MODES +}; + +typedef struct acs_selected_channels { + u32 pri_freq; /* save slelcted primary frequency */ + u32 sec_freq; /* save slelcted secondary frequency */ + u8 vht_seg0_center_ch; + u8 vht_seg1_center_ch; + u16 ch_width; + enum hostapd_hw_mode hw_mode; +} acs_selected_channels_t; + +typedef struct drv_acs_params { + enum hostapd_hw_mode hw_mode; + int band; + + int ht_enabled; + int ht40_enabled; + int vht_enabled; + int he_enabled; + + u16 ch_width; + + unsigned int ch_list_len; + const u8 *ch_list; + + const int *freq_list; +} drv_acs_params_t; + +typedef struct acs_delay_work { + struct delayed_work acs_delay_work; + int init_flag; + + struct net_device *ndev; + chanspec_t ch_chosen; + drv_acs_params_t parameter; +} acs_delay_work_t; +#endif /* WL_SUPPORT_AUTO_CHANNEL */ + struct bcm_nlmsg_hdr { uint cmd; /* common ioctl definition */ int len; /* expected return buffer length */ diff --git a/wl_cfg80211.c b/wl_cfg80211.c index 64a2456..ed9f64f 100755 --- a/wl_cfg80211.c +++ b/wl_cfg80211.c @@ -1018,7 +1018,7 @@ struct chan_info { #define CHAN2G(_channel, _freq, _flags) { \ .band = IEEE80211_BAND_2GHZ, \ .center_freq = (_freq), \ - .hw_value = CH_TO_CHSPC(WL_CHANSPEC_BAND_2G, _channel), \ + .hw_value = (_channel), \ .flags = (_flags), \ .max_antenna_gain = 0, \ .max_power = 30, \ @@ -1027,7 +1027,7 @@ struct chan_info { #define CHAN5G(_channel, _flags) { \ .band = IEEE80211_BAND_5GHZ, \ .center_freq = 5000 + (5 * (_channel)), \ - .hw_value = CH_TO_CHSPC(WL_CHANSPEC_BAND_5G, _channel), \ + .hw_value = (_channel), \ .flags = (_flags), \ .max_antenna_gain = 0, \ .max_power = 30, \ @@ -1037,7 +1037,7 @@ struct chan_info { #define CHAN6G(_channel, _flags) { \ .band = IEEE80211_BAND_5GHZ, \ .center_freq = 5940 + (5 * (_channel)), \ - .hw_value = CH_TO_CHSPC(WL_CHANSPEC_BAND_6G, _channel), \ + .hw_value = (_channel), \ .flags = (_flags), \ .max_antenna_gain = 0, \ .max_power = 30, \ @@ -19310,7 +19310,7 @@ static s32 wl_update_chan_param(struct net_device *dev, u32 cur_chan, return err; } -static int wl_construct_reginfo(struct bcm_cfg80211 *cfg, s32 bw_cap) +static int wl_construct_reginfo(struct bcm_cfg80211 *cfg, s32 bw_cap_2g, s32 bw_cap_5g) { struct net_device *dev = bcmcfg_to_prmry_ndev(cfg); struct ieee80211_channel *band_chan_arr = NULL; @@ -19375,7 +19375,7 @@ static int wl_construct_reginfo(struct bcm_cfg80211 *cfg, s32 bw_cap) (channel <= CH_MAX_2G_CHANNEL)) { band_chan_arr = __wl_2ghz_channels; array_size = ARRAYSIZE(__wl_2ghz_channels); - ht40_allowed = (bw_cap == WLC_N_BW_40ALL)? true : false; + ht40_allowed = WL_BW_CAP_40MHZ(bw_cap_2g); } else if ( #ifdef WL_6G_BAND /* Currently due to lack of kernel support both 6GHz and 5GHz @@ -19387,7 +19387,7 @@ static int wl_construct_reginfo(struct bcm_cfg80211 *cfg, s32 bw_cap) (CHSPEC_IS5G(chspec) && channel >= CH_MIN_5G_CHANNEL)) { band_chan_arr = __wl_5ghz_a_channels; array_size = ARRAYSIZE(__wl_5ghz_a_channels); - ht40_allowed = (bw_cap == WLC_N_BW_20ALL)? false : true; + ht40_allowed = WL_BW_CAP_40MHZ(bw_cap_5g); } else { WL_ERR(("Invalid channel Sepc. 0x%x.\n", chspec)); continue; @@ -19395,7 +19395,7 @@ static int wl_construct_reginfo(struct bcm_cfg80211 *cfg, s32 bw_cap) if (!ht40_allowed && CHSPEC_IS40(chspec)) continue; for (j = 0; j < array_size; j++) { - if (band_chan_arr[j].hw_value == chspec) { + if (band_chan_arr[j].hw_value == channel) { break; } } @@ -19408,7 +19408,7 @@ static int wl_construct_reginfo(struct bcm_cfg80211 *cfg, s32 bw_cap) band_chan_arr[index].center_freq = wl_channel_to_frequency(channel, CHSPEC_BAND(chspec)); #endif - band_chan_arr[index].hw_value = chspec; + band_chan_arr[index].hw_value = channel; band_chan_arr[index].beacon_found = false; band_chan_arr[index].flags &= ~IEEE80211_CHAN_DISABLED; @@ -19493,7 +19493,9 @@ static s32 __wl_update_wiphybands(struct bcm_cfg80211 *cfg, bool notify) s32 txbf_bfe_cap = 0; s32 txbf_bfr_cap = 0; #endif - s32 bw_cap = 0; + s32 txchain = 0; + s32 rxchain = 0; + s32 bw_cap_2g = 0, bw_cap_5g = 0; s32 cur_band = -1; struct ieee80211_supported_band *bands[IEEE80211_NUM_BANDS] = {NULL, }; @@ -19560,23 +19562,50 @@ static s32 __wl_update_wiphybands(struct bcm_cfg80211 *cfg, bool notify) } #endif + err = wldev_iovar_getint(dev, "txchain", &txchain); + if (unlikely(err)) { + WL_ERR(("error reading txchain (%d)\n", err)); + } else if (txchain == 0x03) { + txchain = 2; + } else { + txchain = 1; + } + err = wldev_iovar_getint(dev, "rxchain", &rxchain); + if (unlikely(err)) { + WL_ERR(("error reading rxchain (%d)\n", err)); + } else if (rxchain == 0x03) { + rxchain = 2; + } else { + rxchain = 1; + } + /* For nmode and vhtmode check bw cap */ if (nmode || #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)) vhtmode || #endif 0) { - err = wldev_iovar_getint(dev, "mimo_bw_cap", &bw_cap); + uint32 value; + + value = WLC_BAND_2G; + err = wldev_iovar_getint(dev, "bw_cap", &value); if (unlikely(err)) { - WL_ERR(("error get mimo_bw_cap (%d)\n", err)); + WL_ERR(("error get bw_cap 2g (%d)\n", err)); } + bw_cap_2g = dtoh32(value); + value = WLC_BAND_5G; + err = wldev_iovar_getint(dev, "bw_cap", &value); + if (unlikely(err)) { + WL_ERR(("error get bw_cap 5g (%d)\n", err)); + } + bw_cap_5g = dtoh32(value); } #ifdef WL_6G_BAND wl_is_6g_supported(cfg, bandlist, bandlist[0]); #endif /* WL_6G_BAND */ - err = wl_construct_reginfo(cfg, bw_cap); + err = wl_construct_reginfo(cfg, bw_cap_2g, bw_cap_5g); if (err) { WL_ERR(("wl_construct_reginfo() fails err=%d\n", err)); if (err != BCME_UNSUPPORTED) @@ -19586,6 +19615,9 @@ static s32 __wl_update_wiphybands(struct bcm_cfg80211 *cfg, bool notify) wiphy = bcmcfg_to_wiphy(cfg); nband = bandlist[0]; + wiphy->available_antennas_tx = txchain; + wiphy->available_antennas_rx = rxchain; + for (i = 1; i <= nband && i < ARRAYSIZE(bandlist); i++) { index = -1; @@ -19594,14 +19626,25 @@ static s32 __wl_update_wiphybands(struct bcm_cfg80211 *cfg, bool notify) bands[IEEE80211_BAND_5GHZ] = &__wl_band_5ghz_a; index = IEEE80211_BAND_5GHZ; - if (nmode && (bw_cap == WLC_N_BW_40ALL || bw_cap == WLC_N_BW_20IN2G_40IN5G)) - bands[index]->ht_cap.cap |= IEEE80211_HT_CAP_SGI_40; + memset(bands[index]->ht_cap.mcs.rx_mask, 0, IEEE80211_HT_MCS_MASK_LEN); + if (nmode && (WL_BW_CAP_40MHZ(bw_cap_5g))) { + bands[index]->ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40 | + IEEE80211_HT_CAP_SGI_40; + bands[index]->ht_cap.mcs.rx_mask[4] = 0x01; + bands[index]->ht_cap.mcs.rx_highest = cpu_to_le16(150 * rxchain); + } else { + bands[index]->ht_cap.mcs.rx_highest = cpu_to_le16(72 * rxchain); + } #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)) /* VHT capabilities. */ if (vhtmode) { /* Supported */ bands[index]->vht_cap.vht_supported = TRUE; + bands[index]->vht_cap.vht_mcs.tx_highest = + cpu_to_le16(433 * txstreams); /* Mbps */ + bands[index]->vht_cap.vht_mcs.rx_highest = + cpu_to_le16(433 * txstreams); /* Mbps */ for (j = 1; j <= VHT_CAP_MCS_MAP_NSS_MAX; j++) { /* TX stream rates. */ @@ -19624,11 +19667,13 @@ static s32 __wl_update_wiphybands(struct bcm_cfg80211 *cfg, bool notify) } /* Capabilities */ + bands[index]->vht_cap.cap |= IEEE80211_VHT_CAP_RX_ANTENNA_PATTERN + | IEEE80211_VHT_CAP_TX_ANTENNA_PATTERN; /* 80 MHz is mandatory */ bands[index]->vht_cap.cap |= IEEE80211_VHT_CAP_SHORT_GI_80; - if (WL_BW_CAP_160MHZ(bw_cap)) { + if (WL_BW_CAP_160MHZ(bw_cap_5g)) { bands[index]->vht_cap.cap |= IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ; bands[index]->vht_cap.cap |= @@ -19686,8 +19731,17 @@ static s32 __wl_update_wiphybands(struct bcm_cfg80211 *cfg, bool notify) bands[IEEE80211_BAND_2GHZ] = &__wl_band_2ghz; index = IEEE80211_BAND_2GHZ; - if (bw_cap == WLC_N_BW_40ALL) - bands[index]->ht_cap.cap |= IEEE80211_HT_CAP_SGI_40; + memset(bands[index]->ht_cap.mcs.rx_mask, 0, IEEE80211_HT_MCS_MASK_LEN); + if (nmode && (WL_BW_CAP_40MHZ(bw_cap_2g))) { + bands[index]->ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40 | + IEEE80211_HT_CAP_SGI_40; + bands[index]->ht_cap.mcs.rx_mask[4] = 0x01; + bands[index]->ht_cap.mcs.rx_highest = + cpu_to_le16(150 * rxchain); /* Mbps */ + } else { + bands[index]->ht_cap.mcs.rx_highest = + cpu_to_le16(72 * rxchain); /* Mbps */ + } } if ((index >= 0) && nmode) { @@ -19697,7 +19751,10 @@ static s32 __wl_update_wiphybands(struct bcm_cfg80211 *cfg, bool notify) bands[index]->ht_cap.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; bands[index]->ht_cap.ampdu_density = IEEE80211_HT_MPDU_DENSITY_16; /* An HT shall support all EQM rates for one spatial stream */ - bands[index]->ht_cap.mcs.rx_mask[0] = 0xff; + for (j = 0; j < rxchain; j++) { + bands[index]->ht_cap.mcs.rx_mask[j] = 0xff; + } + bands[index]->ht_cap.mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; } } @@ -22764,6 +22821,38 @@ static int wl_cfg80211_del_pmk(struct wiphy *wiphy, struct net_device *dev, } #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0) */ +#if defined(WL_SUPPORT_AUTO_CHANNEL) +int +wl_cfg80211_set_spect(struct net_device *dev, int spect) +{ + struct bcm_cfg80211 *cfg = wl_get_cfg(dev); + int wlc_down = 1; + int wlc_up = 1; + int err = BCME_OK; + + if (!wl_get_drv_status_all(cfg, CONNECTED)) { + err = wldev_ioctl_set(dev, WLC_DOWN, &wlc_down, sizeof(wlc_down)); + if (err) { + WL_ERR(("%s: WLC_DOWN failed: code: %d\n", __func__, err)); + return err; + } + + err = wldev_ioctl_set(dev, WLC_SET_SPECT_MANAGMENT, &spect, sizeof(spect)); + if (err) { + WL_ERR(("%s: error setting spect: code: %d\n", __func__, err)); + return err; + } + + err = wldev_ioctl_set(dev, WLC_UP, &wlc_up, sizeof(wlc_up)); + if (err) { + WL_ERR(("%s: WLC_UP failed: code: %d\n", __func__, err)); + return err; + } + } + return err; +} +#endif /* WL_SUPPORT_AUTO_CHANNEL */ + int wl_cfg80211_get_sta_channel(struct bcm_cfg80211 *cfg) { diff --git a/wl_cfg80211.h b/wl_cfg80211.h index 3934b3a..b0f0f3a 100644 --- a/wl_cfg80211.h +++ b/wl_cfg80211.h @@ -2544,6 +2544,9 @@ extern s32 wl_cfgvendor_send_as_rtt_legacy_event(struct wiphy *wiphy, #ifdef WL_CFG80211_P2P_DEV_IF extern void wl_cfg80211_del_p2p_wdev(struct net_device *dev); #endif /* WL_CFG80211_P2P_DEV_IF */ +#if defined(WL_SUPPORT_AUTO_CHANNEL) +extern int wl_cfg80211_set_spect(struct net_device *dev, int spect); +#endif /* WL_SUPPORT_AUTO_CHANNEL */ extern int wl_cfg80211_get_sta_channel(struct bcm_cfg80211 *cfg); #ifdef WL_CFG80211_SYNC_GON #define WL_DRV_STATUS_SENDING_AF_FRM_EXT(cfg) \ @@ -2722,6 +2725,8 @@ static inline s32 wl_rssi_offset(s32 rssi) #define wl_rssi_offset(x) x #endif extern int wl_channel_to_frequency(u32 chan, chanspec_band_t band); +extern chanspec_t +wl_channel_to_chanspec(struct wiphy *wiphy, struct net_device *dev, u32 channel, u32 bw_cap); extern int wl_cfg80211_config_rsnxe_ie(struct bcm_cfg80211 *cfg, struct net_device *dev, const u8 *parse, u32 len); diff --git a/wl_cfgscan.c b/wl_cfgscan.c index 341c3a5..1afa642 100755 --- a/wl_cfgscan.c +++ b/wl_cfgscan.c @@ -2188,7 +2188,7 @@ __wl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev, */ if (request && (scan_req_iftype(request) == NL80211_IFTYPE_AP)) { WL_DBG(("Scan Command on SoftAP Interface. Ignoring...\n")); - return 0; + return -EOPNOTSUPP; } if (request && request->n_ssids > WL_SCAN_PARAMS_SSID_MAX) { diff --git a/wl_cfgvendor.c b/wl_cfgvendor.c index b8a0d0e..71c07aa 100644 --- a/wl_cfgvendor.c +++ b/wl_cfgvendor.c @@ -8569,6 +8569,859 @@ exit: return ret; } +#if defined(WL_SUPPORT_AUTO_CHANNEL) +#define SEC_FREQ_HT40_OFFSET 20 +static acs_delay_work_t delay_work_acs = { .init_flag = 0 }; + +static int wl_cfgvendor_acs_parse_result(acs_selected_channels_t *pResult, + chanspec_t ch_chosen, drv_acs_params_t *pParameter) +{ + unsigned int chspec_band, chspec_ctl_freq, chspec_center_ch, chspec_bw, chspec_sb; + + if ((!pResult) || (!pParameter)) { + WL_ERR(("%s: parameter invalid\n", __FUNCTION__)); + return BCME_BADARG; + } else if (!wf_chspec_valid(ch_chosen)) { + WL_ERR(("%s: ch_chosen=0x%X invalid\n", + __FUNCTION__, ch_chosen)); + return BCME_BADARG; + } + + chspec_ctl_freq = wl_channel_to_frequency(wf_chspec_ctlchan(ch_chosen), + CHSPEC_BAND(ch_chosen)); + chspec_center_ch = CHSPEC_CHANNEL(ch_chosen); + chspec_band = CHSPEC_BAND(ch_chosen); + chspec_bw = CHSPEC_BW(ch_chosen); + chspec_sb = CHSPEC_CTL_SB(ch_chosen); + WL_TRACE(("%s: ctl_freq=%d, center_ch=%d, band=0x%X, bw=0x%X, sb=0x%X\n", + __FUNCTION__, + chspec_ctl_freq, chspec_center_ch, chspec_band, chspec_bw, chspec_sb)); + + memset(pResult, 0, sizeof(acs_selected_channels_t)); + + /* hw_mode */ + switch (chspec_band) { + case WL_CHANSPEC_BAND_2G: + pResult->hw_mode = HOSTAPD_MODE_IEEE80211G; + break; + case WL_CHANSPEC_BAND_5G: + default: + pResult->hw_mode = HOSTAPD_MODE_IEEE80211A; + break; + } + WL_TRACE(("%s: hw_mode=%d\n", __FUNCTION__, pResult->hw_mode)); + + /* ch_width and others */ + switch (chspec_bw) { + case WL_CHANSPEC_BW_40: + if (pParameter->ht40_enabled) { + pResult->ch_width = 40; + switch (chspec_sb) { + case WL_CHANSPEC_CTL_SB_U: + pResult->pri_freq = chspec_ctl_freq; + pResult->sec_freq = chspec_ctl_freq - SEC_FREQ_HT40_OFFSET; + break; + case WL_CHANSPEC_CTL_SB_L: + default: + pResult->pri_freq = chspec_ctl_freq; + pResult->sec_freq = chspec_ctl_freq + SEC_FREQ_HT40_OFFSET; + break; + } + WL_TRACE(("%s: HT40 ok\n", __FUNCTION__)); + } else { + pResult->ch_width = 20; + pResult->pri_freq = chspec_ctl_freq; + pResult->sec_freq = 0; + WL_TRACE(("%s: HT40 to HT20\n", __FUNCTION__)); + } + break; + case WL_CHANSPEC_BW_80: + if ((pParameter->vht_enabled) || (pParameter->he_enabled)) { + pResult->ch_width = 80; + pResult->vht_seg0_center_ch = chspec_center_ch; + pResult->vht_seg1_center_ch = 0; + switch (chspec_sb) { + case WL_CHANSPEC_CTL_SB_LL: + case WL_CHANSPEC_CTL_SB_LU: + pResult->pri_freq = chspec_ctl_freq; + pResult->sec_freq = chspec_ctl_freq + SEC_FREQ_HT40_OFFSET; + break; + case WL_CHANSPEC_CTL_SB_UL: + case WL_CHANSPEC_CTL_SB_UU: + default: + pResult->pri_freq = chspec_ctl_freq; + pResult->sec_freq = chspec_ctl_freq - SEC_FREQ_HT40_OFFSET; + break; + } + WL_TRACE(("%s: HT80 ok\n", __FUNCTION__)); + } else if (pParameter->ht40_enabled) { + pResult->ch_width = 40; + switch (chspec_sb) { + case WL_CHANSPEC_CTL_SB_LL: + case WL_CHANSPEC_CTL_SB_UL: + pResult->pri_freq = chspec_ctl_freq; + pResult->sec_freq = chspec_ctl_freq + SEC_FREQ_HT40_OFFSET; + break; + case WL_CHANSPEC_CTL_SB_LU: + case WL_CHANSPEC_CTL_SB_UU: + default: + pResult->pri_freq = chspec_ctl_freq; + pResult->sec_freq = chspec_ctl_freq - SEC_FREQ_HT40_OFFSET; + break; + } + WL_TRACE(("%s: HT80 to HT40\n", __FUNCTION__)); + } else { + pResult->ch_width = 20; + pResult->pri_freq = chspec_ctl_freq; + pResult->sec_freq = 0; + WL_TRACE(("%s: HT80 to HT20\n", __FUNCTION__)); + } + break; + case WL_CHANSPEC_BW_160: + case WL_CHANSPEC_BW_8080: + if ((pParameter->vht_enabled) || (pParameter->he_enabled)) { + pResult->ch_width = 160; + switch (chspec_sb) { + case WL_CHANSPEC_CTL_SB_LLL: + case WL_CHANSPEC_CTL_SB_LLU: + case WL_CHANSPEC_CTL_SB_LUL: + case WL_CHANSPEC_CTL_SB_LUU: + pResult->pri_freq = chspec_ctl_freq; + pResult->sec_freq = chspec_ctl_freq + SEC_FREQ_HT40_OFFSET; + pResult->vht_seg0_center_ch = chspec_center_ch; + pResult->vht_seg1_center_ch = chspec_center_ch + CH_80MHZ_APART; + break; + case WL_CHANSPEC_CTL_SB_ULL: + case WL_CHANSPEC_CTL_SB_ULU: + case WL_CHANSPEC_CTL_SB_UUL: + case WL_CHANSPEC_CTL_SB_UUU: + default: + pResult->pri_freq = chspec_ctl_freq; + pResult->sec_freq = chspec_ctl_freq - SEC_FREQ_HT40_OFFSET; + pResult->vht_seg0_center_ch = chspec_center_ch; + pResult->vht_seg1_center_ch = chspec_center_ch - CH_80MHZ_APART; + break; + } + WL_TRACE(("%s: HT160 ok\n", __FUNCTION__)); + } else if (pParameter->ht40_enabled) { + pResult->ch_width = 40; + switch (chspec_sb) { + case WL_CHANSPEC_CTL_SB_LLL: + case WL_CHANSPEC_CTL_SB_LUL: + case WL_CHANSPEC_CTL_SB_ULL: + case WL_CHANSPEC_CTL_SB_UUL: + pResult->pri_freq = chspec_ctl_freq; + pResult->sec_freq = chspec_ctl_freq + SEC_FREQ_HT40_OFFSET; + break; + case WL_CHANSPEC_CTL_SB_LLU: + case WL_CHANSPEC_CTL_SB_LUU: + case WL_CHANSPEC_CTL_SB_ULU: + case WL_CHANSPEC_CTL_SB_UUU: + default: + pResult->pri_freq = chspec_ctl_freq; + pResult->sec_freq = chspec_ctl_freq - SEC_FREQ_HT40_OFFSET; + break; + } + WL_TRACE(("%s: HT160 to HT40\n", __FUNCTION__)); + } else { + pResult->ch_width = 20; + pResult->pri_freq = chspec_ctl_freq; + pResult->sec_freq = 0; + WL_TRACE(("%s: HT160 to HT20\n", __FUNCTION__)); + printk("%s: HT160 to HT20\n", __FUNCTION__); + } + break; + case WL_CHANSPEC_BW_20: + default: + if ((pParameter->ht_enabled) || (TRUE)) { + pResult->ch_width = 20; + pResult->pri_freq = chspec_ctl_freq; + } + WL_TRACE(("%s: HT20 ok\n", __FUNCTION__)); + break; + } + + WL_TRACE(("%s: result: pri_freq=%d, sec_freq=%d, vht_seg0=%d, vht_seg1=%d," + " ch_width=%d, hw_mode=%d\n", __FUNCTION__, + pResult->pri_freq, pResult->sec_freq, + pResult->vht_seg0_center_ch, pResult->vht_seg1_center_ch, + pResult->ch_width, pResult->hw_mode)); + + return 0; +} + +static int wl_cfgvendor_acs_parse_parameter_save(int *pLen, uint32 *pList, chanspec_t chspec) +{ + int ret = 0; + int qty = 0; + int i; + + do { + if ((!pLen) || (!pList)) { + WL_ERR(("%s: parameter invalid\n", __FUNCTION__)); + ret = BCME_BADARG; + break; + } else { + qty = *pLen; + } + + if (!wf_chspec_valid(chspec)) { + WL_TRACE(("%s: chanspec=0x%X invalid\n", __FUNCTION__, chspec)); + ret = BCME_BADARG; + break; + } + + for (i = 0; i < qty; i++) { + if (pList[i] == chspec) { + break; + } + } + + if (i == qty) { + pList[qty++] = chspec; + *pLen = qty; + WL_TRACE(("%s: fill list[%d] = 0x%X\n", __FUNCTION__, qty, chspec)); + } else { + WL_TRACE(("%s: duplicate with [idx]=[%d]=0x%X\n", __FUNCTION__, i, chspec)); + break; + } + } while (0); + + return ret; +} + +static int wl_cfgvendor_acs_parse_parameter(int *pLen, uint32 *pList, unsigned int channel, + drv_acs_params_t *pParameter) +{ + unsigned int chspec_ctl_ch = 0x0; + unsigned int chspec_band, chspec_bw, chspec_sb; + chanspec_t chanspec = 0x0; + int qty = 0; + int ret = 0; + int i; + + do { + if ((!pLen) || (!pList) || (!pParameter)) { + WL_ERR(("%s: parameter invalid\n", __FUNCTION__)); + ret = BCME_BADARG; + break; + } + + if (WLC_BAND_2G == pParameter->band) { + if (CH_MAX_2G_CHANNEL < channel) { + WL_TRACE(("%s: wrong band, CTL_CH=%d, band=%d\n", + __FUNCTION__, channel, pParameter->band)); + ret = BCME_BADARG; + break; + } + } + + chspec_band = WL_CHANNEL_BAND(channel); + qty = *pLen; + + /* HT20 */ + chspec_bw = WL_CHANSPEC_BW_20; + if (((pParameter->ht_enabled) || (pParameter->ht40_enabled) || + (pParameter->vht_enabled) || (pParameter->he_enabled)) && + (20 <= pParameter->ch_width)) { + chspec_ctl_ch = channel; + chspec_sb = WL_CHANSPEC_CTL_SB_NONE; + chanspec = (chanspec_t)(chspec_ctl_ch | chspec_band | + chspec_bw | chspec_sb); + WL_TRACE(("%s: checking HT20 [%d] = 0x%X\n", __FUNCTION__, qty, chanspec)); + wl_cfgvendor_acs_parse_parameter_save(&qty, pList, chanspec); + } + + /* HT40 */ + chspec_bw = WL_CHANSPEC_BW_40; + if (((pParameter->ht40_enabled) || (pParameter->vht_enabled) || + (pParameter->he_enabled)) && (pParameter->ch_width >= 40)) { + for (i = -CH_20MHZ_APART; i <= CH_20MHZ_APART; i++) { + chspec_ctl_ch = channel + i; + /* L-sideband */ + chspec_sb = WL_CHANSPEC_CTL_SB_LOWER; + chanspec = (chanspec_t)(chspec_ctl_ch | chspec_band | + chspec_bw | chspec_sb); + WL_TRACE(("%s: checking HT40 U [%d] = 0x%X\n", + __FUNCTION__, qty, chanspec)); + wl_cfgvendor_acs_parse_parameter_save(&qty, pList, chanspec); + /* R-sideband */ + chspec_sb = WL_CHANSPEC_CTL_SB_UPPER; + chanspec = (chanspec_t)(chspec_ctl_ch | chspec_band | + chspec_bw | chspec_sb); + WL_TRACE(("%s: checking HT40 L [%d] = 0x%X\n", + __FUNCTION__, qty, chanspec)); + wl_cfgvendor_acs_parse_parameter_save(&qty, pList, chanspec); + } + } + + /* HT80 */ + chspec_bw = WL_CHANSPEC_BW_80; + if ((pParameter->vht_enabled || pParameter->he_enabled) && + (80 <= pParameter->ch_width)) { + for (i = -CH_40MHZ_APART; i <= CH_40MHZ_APART; i++) { + chspec_ctl_ch = channel + i; + /* L-L-sideband */ + chspec_sb = WL_CHANSPEC_CTL_SB_LL; + chanspec = (chanspec_t)(chspec_ctl_ch | chspec_band | + chspec_bw | chspec_sb); + WL_TRACE(("checking HT80 LL [%d] = 0x%X\n", qty, chanspec)); + wl_cfgvendor_acs_parse_parameter_save(&qty, pList, chanspec); + /* L-U-sideband */ + chspec_sb = WL_CHANSPEC_CTL_SB_LU; + chanspec = (chanspec_t)(chspec_ctl_ch | chspec_band | + chspec_bw | chspec_sb); + WL_TRACE(("checking HT80 LU [%d] = 0x%X\n", qty, chanspec)); + wl_cfgvendor_acs_parse_parameter_save(&qty, pList, chanspec); + /* U-L-sideband */ + chspec_sb = WL_CHANSPEC_CTL_SB_UL; + chanspec = (chanspec_t)(chspec_ctl_ch | chspec_band | + chspec_bw | chspec_sb); + WL_TRACE(("checking HT80 UL [%d] = 0x%X\n", qty, chanspec)); + wl_cfgvendor_acs_parse_parameter_save(&qty, pList, chanspec); + /* U-U-sideband */ + chspec_sb = WL_CHANSPEC_CTL_SB_UU; + chanspec = (chanspec_t)(chspec_ctl_ch | chspec_band | + chspec_bw | chspec_sb); + WL_TRACE(("checking HT80 UU [%d] = 0x%X\n", qty, chanspec)); + wl_cfgvendor_acs_parse_parameter_save(&qty, pList, chanspec); + } + } + + /* HT160 */ + if (pParameter->he_enabled && (160 <= pParameter->ch_width)) { + for (i = -CH_80MHZ_APART; i <= CH_80MHZ_APART; i++) { + chspec_ctl_ch = channel + i; + /* L-L-L-sideband */ + chspec_sb = WL_CHANSPEC_CTL_SB_LLL; + chanspec = (chanspec_t)(chspec_ctl_ch | chspec_band | + chspec_bw | chspec_sb); + WL_TRACE(("checking HT160 LLL [%d] = 0x%X\n", qty, chanspec)); + wl_cfgvendor_acs_parse_parameter_save(&qty, pList, chanspec); + /* L-L-U-sideband */ + chspec_sb = WL_CHANSPEC_CTL_SB_LLU; + chanspec = (chanspec_t)(chspec_ctl_ch | chspec_band | + chspec_bw | chspec_sb); + WL_TRACE(("checking HT160 LLU [%d] = 0x%X\n", qty, chanspec)); + wl_cfgvendor_acs_parse_parameter_save(&qty, pList, chanspec); + /* L-U-L-sideband */ + chspec_sb = WL_CHANSPEC_CTL_SB_LUL; + chanspec = (chanspec_t)(chspec_ctl_ch | chspec_band | + chspec_bw | chspec_sb); + WL_TRACE(("checking HT160 LUL [%d] = 0x%X\n", qty, chanspec)); + wl_cfgvendor_acs_parse_parameter_save(&qty, pList, chanspec); + /* L-U-U-sideband */ + chspec_sb = WL_CHANSPEC_CTL_SB_LUU; + chanspec = (chanspec_t)(chspec_ctl_ch | chspec_band | + chspec_bw | chspec_sb); + WL_TRACE(("checking HT160 LUU [%d] = 0x%X\n", qty, chanspec)); + wl_cfgvendor_acs_parse_parameter_save(&qty, pList, chanspec); + /* U-L-L-sideband */ + chspec_sb = WL_CHANSPEC_CTL_SB_ULL; + chanspec = (chanspec_t)(chspec_ctl_ch | chspec_band | + chspec_bw | chspec_sb); + WL_TRACE(("checking HT160 ULL [%d] = 0x%X\n", qty, chanspec)); + wl_cfgvendor_acs_parse_parameter_save(&qty, pList, chanspec); + /* U-L-U-sideband */ + chspec_sb = WL_CHANSPEC_CTL_SB_ULU; + chanspec = (chanspec_t)(chspec_ctl_ch | chspec_band | + chspec_bw | chspec_sb); + WL_TRACE(("checking HT160 ULU [%d] = 0x%X\n", qty, chanspec)); + wl_cfgvendor_acs_parse_parameter_save(&qty, pList, chanspec); + /* U-U-L-sideband */ + chspec_sb = WL_CHANSPEC_CTL_SB_UUL; + chanspec = (chanspec_t)(chspec_ctl_ch | chspec_band | + chspec_bw | chspec_sb); + WL_TRACE(("checking HT160 UUL [%d] = 0x%X\n", qty, chanspec)); + wl_cfgvendor_acs_parse_parameter_save(&qty, pList, chanspec); + /* U-U-U-sideband */ + chspec_sb = WL_CHANSPEC_CTL_SB_UUU; + chanspec = (chanspec_t)(chspec_ctl_ch | chspec_band | + chspec_bw | chspec_sb); + WL_TRACE(("checking HT160 UUU [%d] = 0x%X\n", qty, chanspec)); + wl_cfgvendor_acs_parse_parameter_save(&qty, pList, chanspec); + } + } + + *pLen = qty; + WL_TRACE(("%s: current quantity=%d\n", __FUNCTION__, qty)); + } while (0); + + return ret; +} + +static void wl_cfgvendor_acs_result_event(struct work_struct *work) +{ + acs_delay_work_t *delay_work = (acs_delay_work_t *)work; + struct net_device *ndev = NULL; + struct wiphy *wiphy = NULL; + chanspec_t ch_chosen; + drv_acs_params_t *pParameter; + gfp_t kflags; + struct sk_buff *skb = NULL; + acs_selected_channels_t result; + int len = 0; + int ret = 0; + + do { + if (!delay_work) { + WL_ERR(("%s: work parameter invalid\n", __FUNCTION__)); + ret = BCME_BADARG; + break; + } else { + ndev = delay_work->ndev; + ch_chosen = delay_work->ch_chosen; + pParameter = &delay_work->parameter; + } + + if ((!ndev) || (!(ndev->ieee80211_ptr)) || (!(ndev->ieee80211_ptr->wiphy))) { + WL_ERR(("%s: parameter invalid\n", __FUNCTION__)); + ret = BCME_BADARG; + break; + } + wiphy = ndev->ieee80211_ptr->wiphy; + + /* construct result */ + if (wl_cfgvendor_acs_parse_result(&result, ch_chosen, pParameter) < 0) { + WL_ERR(("%s: fail to conver the result\n", __FUNCTION__)); + ret = BCME_BADARG; + break; + } + + len = 200; + kflags = in_atomic()? GFP_ATOMIC : GFP_KERNEL; + WL_TRACE(("%s: idx=%d, wiphy->n_vendor_events=%d\n", + __FUNCTION__, BRCM_VENDOR_EVENT_ACS, wiphy->n_vendor_events)); + /* Alloc the SKB for vendor_event */ +#if (defined(CONFIG_ARCH_MSM) && defined(SUPPORT_WDEV_CFG80211_VENDOR_EVENT_ALLOC)) || \ + LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) || defined(USE_BACKPORT_4) + skb = cfg80211_vendor_event_alloc(wiphy, ndev_to_wdev(ndev), len, + BRCM_VENDOR_EVENT_ACS, kflags); +#else + skb = cfg80211_vendor_event_alloc(wiphy, len, BRCM_VENDOR_EVENT_ACS, kflags); +#endif /* CONFIG_ARCH_MSM SUPPORT_WDEV_CFG80211_VENDOR_EVENT_ALLOC */ + if (!skb) { + WL_ERR(("%s: Error, no memory for event\n", __FUNCTION__)); + ret = BCME_NOMEM; + break; + } + WL_TRACE(("%s: good to get skb=0x%p\n", __FUNCTION__, skb)); + + if ((nla_put_u16(skb, BRCM_VENDOR_ATTR_ACS_PRIMARY_FREQ, result.pri_freq) < 0) || + (nla_put_u16(skb, BRCM_VENDOR_ATTR_ACS_SECONDARY_FREQ, result.sec_freq) < 0) || + (nla_put_u8(skb, BRCM_VENDOR_ATTR_ACS_VHT_SEG0_CENTER_CHANNEL, + result.vht_seg0_center_ch) < 0) || + (nla_put_u8(skb, BRCM_VENDOR_ATTR_ACS_VHT_SEG1_CENTER_CHANNEL, + result.vht_seg1_center_ch) < 0) || + (nla_put_u16(skb, BRCM_VENDOR_ATTR_ACS_CHWIDTH, result.ch_width) < 0) || + (nla_put_u32(skb, BRCM_VENDOR_ATTR_ACS_HW_MODE, result.hw_mode) < 0)) { + WL_ERR(("%s: Error, fail to fill the result\n", __FUNCTION__)); + ret = BCME_BADARG; + break; + } + + WL_TRACE(("%s: send the event\n", __FUNCTION__)); + cfg80211_vendor_event(skb, kflags); + } while (0); + + if (ret < 0) { + if (skb) { + WL_ERR(("%s: free the event since fail with ret=%d\n", __FUNCTION__, ret)); + dev_kfree_skb_any(skb); + } + } + +} + +static int wl_cfgvendor_acs_do_apcs(struct net_device *dev, int band, chanspec_t *pCH, + int len, unsigned char *pBuffer, int qty, uint32 *pList) +{ + struct bcm_cfg80211 *cfg = wl_get_cfg(dev); + uint32 sta_band = WLC_BAND_2G; + int channel = 0; + int chosen = 0; + int retry = 0; + int ret = 0; + int spect = 0; + +#if defined(CONFIG_WLAN_BEYONDX) || defined(CONFIG_SEC_5GMODEL) + wl_cfg80211_register_dev_ril_bridge_event_notifier(); + if (band == WLC_BAND_2G) { + wl_cfg80211_send_msg_to_ril(); + + if (g_mhs_chan_for_cpcoex) { + chosen = CH20MHZ_CHSPEC(g_mhs_chan_for_cpcoex); + g_mhs_chan_for_cpcoex = 0; + goto done2; + } + } + wl_cfg80211_unregister_dev_ril_bridge_event_notifier(); +#endif /* CONFIG_WLAN_BEYONDX || defined(CONFIG_SEC_5GMODEL) */ + + /* If STA is connected, we can't do APCS + * and try to get a chanspec based on STA channel + */ + channel = wl_cfg80211_get_sta_channel(cfg); + if (channel) { + sta_band = WL_GET_BAND(channel); + switch (sta_band) { + case (WLC_BAND_5G): + if (WLC_BAND_2G == band) { + if ((!pList) || (!qty)) { + chosen = CH20MHZ_CHSPEC(APCS_DEFAULT_2G_CH); + } else { + chosen = pList[qty - 1]; + } + } else { + chosen = CH20MHZ_CHSPEC(channel); + } + break; + case (WLC_BAND_2G): + if (WLC_BAND_5G == band) { + if ((!pList) || (!qty)) { + chosen = APCS_DEFAULT_5G_CH; + } else { + chosen = pList[qty - 1]; + } + } else { + chosen = CH20MHZ_CHSPEC(channel); + } + break; + default: + /* Intentional fall through to use same sta channel for softap */ + break; + } + goto done2; + } + + ret = wldev_ioctl_get(dev, WLC_GET_SPECT_MANAGMENT, &spect, sizeof(spect)); + if (ret) { + WL_ERR(("%s: ***Error, error getting the spect, ret=%d\n", __FUNCTION__, ret)); + ret = BCME_BADARG; + goto done; + } + + if (spect > 0) { + ret = wl_cfg80211_set_spect(dev, 0); + if (ret < 0) { + WL_ERR(("%s: Error, fail setting spect, ret=%d\n", __FUNCTION__, ret)); + ret = BCME_BADARG; + goto done; + } + } + + if ((pBuffer) && (0 < len)) { + ret = wldev_ioctl_set(dev, WLC_START_CHANNEL_SEL, (void *)pBuffer, len); + WL_TRACE(("%s: trigger autochannel with ret=%d\n", __FUNCTION__, ret)); + } else { + ret = BCME_BADARG; + WL_ERR(("%s: Error, no parameter to go, ret=%d\n", __FUNCTION__, ret)); + } + + if (ret < 0) { + channel = 0; + ret = BCME_BADARG; + goto done; + } + + /* Wait for auto channel selection, max 3000 ms */ + if ((band == WLC_BAND_2G) || (band == WLC_BAND_5G)) { + OSL_SLEEP(500); + } else { + /* Full channel scan at the minimum takes 1.2secs + * even with parallel scan. max wait time: 3500ms + */ + OSL_SLEEP(1000); + } + + retry = APCS_MAX_RETRY; + while (retry--) { + ret = wldev_ioctl_get(dev, WLC_GET_CHANNEL_SEL, &chosen, sizeof(chosen)); + if (ret < 0) { + chosen = 0; + } else { + chosen = dtoh32(chosen); + } + WL_TRACE(("%s: round=%d, ret=%d, chosen=0x%X\n", + __FUNCTION__, APCS_MAX_RETRY-retry, ret, chosen)); + + if (chosen) { + int chosen_band; + int apcs_band; + + channel = wf_chspec_ctlchan((chanspec_t)chosen); + + apcs_band = (band == WLC_BAND_AUTO) ? WLC_BAND_ALL : band; + chosen_band = (channel <= CH_MAX_2G_CHANNEL) ? WLC_BAND_2G : WLC_BAND_5G; + if ((apcs_band == chosen_band) || (apcs_band == WLC_BAND_ALL)) { + WL_INFORM(("%s: * good, selected chosen=0x%X, channel = %d\n", + __FUNCTION__, chosen, channel)); + /* Get max cap */ + chosen = wl_channel_to_chanspec(cfg->wdev->wiphy, dev, channel, + WL_CHANSPEC_BW_8080); + if (chosen) { + break; + } + } + } + OSL_SLEEP(300); + } + +done: + WL_TRACE(("%s: retry=%d, ret=%d, chosen=0x%X\n", __FUNCTION__, retry, ret, chosen)); + + if (!chosen) { + /* On failure, fallback to a default channel */ + if (WLC_BAND_5G == band) { + if ((!pList) || (!qty)) { + chosen = CH20MHZ_CHSPEC(APCS_DEFAULT_5G_CH); + } else { + chosen = pList[qty - 1]; + } + } else { + if ((!pList) || (!qty)) { + chosen = CH20MHZ_CHSPEC(APCS_DEFAULT_2G_CH); + } else { + chosen = pList[qty - 1]; + } + } + WL_ERR(("%s: use default channel\n", __FUNCTION__)); + if (ret < 0) { + /* set it 0 when use default channel */ + ret = 0; + } + } + +done2: + if (spect > 0) { + if ((ret = wl_cfg80211_set_spect(dev, spect) < 0)) { + WL_ERR(("%s: error while setting spect\n", __FUNCTION__)); + } + } + + *pCH = chosen; + + return ret; +} + +static int wl_cfgvendor_acs_freq_to_channel(uint32 freq) +{ + int channel = 0; + + /* Ref 802.11 17.3.8.3.2 and Annex J */ + if (freq == 2484) { + channel = 14; + } else if (freq < 2484) { + channel = (freq - 2407) / 5; + } else if (freq >= 4910 && freq <= 4980) { + channel = (freq - 4000) / 5; + } else if (freq < 5940) { + channel = (freq - 5000) / 5; +#ifdef WL_6G_BAND + /* DMG band lower limit */ + } else if (freq <= 45000) { + /* Ref 802.11ax D4.1 27.3.22.2 */ + channel = (freq - 5940) / 5; + } else if (freq >= 58320 && freq <= 70200) { + channel = (freq - 56160) / 2160; +#endif /* WL_6G_BAND */ + } else { + WL_ERR(("Invalid frequency \n")); + return INVCHANSPEC; + } + + return channel; +} + +static int +wl_cfgvendor_acs(struct wiphy *wiphy, + struct wireless_dev *wdev, const void *data, int len) +{ + int ret = 0; + struct bcm_cfg80211 *cfg = wiphy_priv(wiphy); + struct net_device *net = wdev_to_ndev(wdev); + + int qty = 0, total = 0; + int i = 0; + unsigned char *pElem_chan = NULL; + unsigned int *pElem_freq = NULL; + unsigned int channel = 0; + /* original HOSTAPD parameters */ + struct nlattr *tb[BRCM_VENDOR_ATTR_ACS_LAST + 1]; + drv_acs_params_t *parameter = NULL; + const struct nlattr *pChanList = NULL, *pFreqList = NULL; + uint32 chan_list_len = 0, freq_list_len = 0; + + /* converted list */ + wl_uint32_list_t *pReq = NULL, *pReq_Ext = NULL; + uint32 *pList = NULL; + int req_len = 0, req_ext_len = 0; + chanspec_t ch_chosen = 0x0; + + if (!delay_work_acs.init_flag) { + delay_work_acs.ndev = net; + delay_work_acs.ch_chosen = 0; + INIT_DELAYED_WORK(&delay_work_acs.acs_delay_work, wl_cfgvendor_acs_result_event); + delay_work_acs.init_flag = 1; + } + + do { + /* get orignal HOSTAPD paramters */ + if (nla_parse(tb, BRCM_VENDOR_ATTR_ACS_LAST, (struct nlattr *)data, + len, NULL, NULL) || + (!tb[BRCM_VENDOR_ATTR_ACS_HW_MODE])) { + WL_ERR(("%s: ***Error, parse fail\n", __FUNCTION__)); + ret = BCME_BADARG; + break; + } + + parameter = &delay_work_acs.parameter; + memset(parameter, 0, sizeof(drv_acs_params_t)); + if (tb[BRCM_VENDOR_ATTR_ACS_HW_MODE]) { + parameter->hw_mode = nla_get_u8(tb[BRCM_VENDOR_ATTR_ACS_HW_MODE]); + WL_TRACE(("%s: hw_mode=%d\n", __FUNCTION__, parameter->hw_mode)); + } + if (tb[BRCM_VENDOR_ATTR_ACS_HT_ENABLED]) { + parameter->ht_enabled = nla_get_u8(tb[BRCM_VENDOR_ATTR_ACS_HT_ENABLED]); + WL_TRACE(("%s: ht_enabled=%d\n", __FUNCTION__, parameter->ht_enabled)); + } + if (tb[BRCM_VENDOR_ATTR_ACS_HT40_ENABLED]) { + parameter->ht40_enabled = nla_get_u8(tb[BRCM_VENDOR_ATTR_ACS_HT40_ENABLED]); + WL_TRACE(("%s: ht40_enabled=%d\n", __FUNCTION__, parameter->ht40_enabled)); + } + if (tb[BRCM_VENDOR_ATTR_ACS_VHT_ENABLED]) { + parameter->vht_enabled = nla_get_u8(tb[BRCM_VENDOR_ATTR_ACS_VHT_ENABLED]); + WL_TRACE(("%s: vht_enabled=%d\n", __FUNCTION__, parameter->vht_enabled)); + } + parameter->he_enabled = parameter->vht_enabled; + if (tb[BRCM_VENDOR_ATTR_ACS_CHWIDTH]) { + parameter->ch_width = nla_get_u8(tb[BRCM_VENDOR_ATTR_ACS_CHWIDTH]); + WL_TRACE(("%s: ch_width=%d\n", __FUNCTION__, parameter->ch_width)); + } + if (tb[BRCM_VENDOR_ATTR_ACS_CH_LIST]) { + pChanList = tb[BRCM_VENDOR_ATTR_ACS_CH_LIST]; + chan_list_len = nla_len(tb[BRCM_VENDOR_ATTR_ACS_CH_LIST]); + WL_TRACE(("%s: chan_list_len=%d\n", __FUNCTION__, chan_list_len)); + } + if (tb[BRCM_VENDOR_ATTR_ACS_FREQ_LIST]) { + pFreqList = tb[BRCM_VENDOR_ATTR_ACS_FREQ_LIST]; + freq_list_len = nla_len(tb[BRCM_VENDOR_ATTR_ACS_FREQ_LIST]) / sizeof(int); + WL_TRACE(("%s: freq_list_len=%d\n", __FUNCTION__, freq_list_len)); + } + + switch (parameter->hw_mode) { + case HOSTAPD_MODE_IEEE80211B: + parameter->band = WLC_BAND_2G; + break; + case HOSTAPD_MODE_IEEE80211G: + parameter->band = WLC_BAND_2G; + break; + case HOSTAPD_MODE_IEEE80211A: + parameter->band = WLC_BAND_5G; + break; + case HOSTAPD_MODE_IEEE80211ANY: + parameter->band = WLC_BAND_AUTO; + break; + case HOSTAPD_MODE_IEEE80211AD: + /* 802.11ad 60G is 'dead' and not supported */ + default: + parameter->band = WLC_BAND_INVALID; + break; + } + WL_TRACE(("%s: parameter->hw_mode=%d, parameter->band=%d\n", + __FUNCTION__, parameter->hw_mode, parameter->band)); + if (WLC_BAND_INVALID == parameter->band) { + ret = BCME_BADARG; + WL_ERR(("%s: *Error, hw_mode=%d based band invalid\n", + __FUNCTION__, parameter->hw_mode)); + break; + } + + /* count memory requirement */ + qty = chan_list_len + freq_list_len; + total = sizeof(uint32) * qty * + (/* extra structure 'count' item */ + (1 + 1) + + /* maximum expand quantity of each channel: 20MHZ * 1, 40MHz * 2, + * 80MHz * 4, 160MHz * 8 + */ + (1 + 2 + 4 + 8)); + WL_TRACE(("%s: qty=%d+%d=%d, total=%d\n", + __FUNCTION__, chan_list_len, freq_list_len, qty, total)); + + if (total <= 0) { + ret = BCME_BADARG; + WL_ERR(("%s: *Error, total number (%d) is invalid\n", + __FUNCTION__, total)); + break; + } + + pReq = MALLOC(cfg->osh, total); + if (!pReq) { + WL_ERR(("%s: *Error, no memory for %d bytes\n", __FUNCTION__, total)); + ret = BCME_NOMEM; + break; + } else { + memset(pReq, 0, total); + pReq->count = req_len = 0; + pList = pReq->element; + } + pReq_Ext = (wl_uint32_list_t *)(pReq->count + (uint32 *)pReq->element); + pReq_Ext->count = req_ext_len = 0; + pList = pReq_Ext->element; + + /* process 'ch_list' for select list*/ + pElem_chan = (unsigned char *)nla_data(pChanList); + for (i = 0; i < chan_list_len; i++) { + channel = pElem_chan[i]; + wl_cfgvendor_acs_parse_parameter(&req_ext_len, pList, channel, parameter); + } + WL_TRACE(("%s: list_len=%d after ch_list\n", __FUNCTION__, req_len)); + + /* process 'freq_list' */ + pElem_freq = (unsigned int *)nla_data(pFreqList); + for (i = 0; i < freq_list_len; i++) { + channel = wl_cfgvendor_acs_freq_to_channel(pElem_freq[i]); + WL_TRACE(("%s: list[%d]=%d => ctl_ch=%d\n", __FUNCTION__, i, + pElem_freq[i], channel)); + wl_cfgvendor_acs_parse_parameter(&req_ext_len, pList, channel, parameter); + } + WL_TRACE(("%s: list_len=%d after freq_list\n", __FUNCTION__, req_len)); + + /* update request memory */ + pReq_Ext->count = (((~req_ext_len) << 16) & 0xffff0000) + | (((req_ext_len) << 0) & 0x0000ffff); + WL_TRACE(("%s: set pReq->count=0x%X, with req_len=%d(0x%X)\n", + __FUNCTION__, pReq->count, req_len, req_len)); + + ret = wl_cfgvendor_acs_do_apcs(net, parameter->band, &ch_chosen, total, + (unsigned char *)pReq, req_ext_len, pList); + WL_TRACE(("%s: do acs ret=%d, ch_chosen=%d(0x%X)\n", + __FUNCTION__, ret, ch_chosen, ch_chosen)); + if (ret >= 0) { + delay_work_acs.ndev = net; + delay_work_acs.ch_chosen = ch_chosen; + if (delayed_work_pending(&delay_work_acs.acs_delay_work)) { + cancel_delayed_work(&delay_work_acs.acs_delay_work); + } + WL_TRACE(("%s: schedule the acs result event send work\n", __FUNCTION__)); + schedule_delayed_work(&delay_work_acs.acs_delay_work, + msecs_to_jiffies((const unsigned int)500)); + ret = 0; + } + + /* free and clean up */ + if (NULL != pReq) { + WL_TRACE(("%s: free the pReq=0x%p with total=%d\n", + __FUNCTION__, pReq, total)); + MFREE(cfg->osh, pReq, total); + } + } while (0); + return ret; +} + +#endif /* WL_SUPPORT_AUTO_CHANNEL */ + #ifdef WL_P2P_RAND static int wl_cfgvendor_set_p2p_rand_mac(struct wiphy *wiphy, @@ -9399,9 +10252,18 @@ static const struct wiphy_vendor_command wl_vendor_cmds [] = { }, .flags = WIPHY_VENDOR_CMD_NEED_WDEV, .doit = wl_cfgvendor_tx_power_scenario - } + }, #endif /* WL_SAR_TX_POWER */ - +#if defined(WL_SUPPORT_AUTO_CHANNEL) + { + { + .vendor_id = OUI_BRCM, + .subcmd = BRCM_VENDOR_SCMD_ACS + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = wl_cfgvendor_acs + }, +#endif /* WL_SUPPORT_AUTO_CHANNEL */ }; static const struct nl80211_vendor_cmd_info wl_vendor_events [] = { @@ -9446,7 +10308,8 @@ static const struct nl80211_vendor_cmd_info wl_vendor_events [] = { { OUI_BRCM, BRCM_VENDOR_EVENT_CU}, { OUI_BRCM, BRCM_VENDOR_EVENT_WIPS}, { OUI_GOOGLE, NAN_ASYNC_RESPONSE_DISABLED}, - { OUI_BRCM, BRCM_VENDOR_EVENT_RCC_INFO} + { OUI_BRCM, BRCM_VENDOR_EVENT_RCC_INFO}, + { OUI_BRCM, BRCM_VENDOR_EVENT_ACS} }; int wl_cfgvendor_attach(struct wiphy *wiphy, dhd_pub_t *dhd) diff --git a/wl_cfgvendor.h b/wl_cfgvendor.h index 412bbda..55a0654 100644 --- a/wl_cfgvendor.h +++ b/wl_cfgvendor.h @@ -575,7 +575,9 @@ typedef enum wl_vendor_event { BRCM_VENDOR_EVENT_CU = 38, BRCM_VENDOR_EVENT_WIPS = 39, NAN_ASYNC_RESPONSE_DISABLED = 40, - BRCM_VENDOR_EVENT_RCC_INFO = 41 + BRCM_VENDOR_EVENT_RCC_INFO = 41, + BRCM_VENDOR_EVENT_ACS = 42, + BRCM_VENDOR_EVENT_LAST } wl_vendor_event_t; enum andr_wifi_attr { |