summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXinrui <xinrui.sun@broadcom.corp-partner.google.com>2020-06-14 08:59:44 +0800
committerAhmed ElArabawy <arabawy@google.com>2020-07-15 17:25:59 -0700
commit9ab28903cb9589eb3f9cd4bee93f2ac5e27b9a56 (patch)
tree96c7ecf1b498109413ae914ef33da15fbdc756e7
parent2a3e52550189588d6e03710a63ce9f38211d3075 (diff)
downloadbcm43752-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.h73
-rwxr-xr-xwl_cfg80211.c125
-rw-r--r--wl_cfg80211.h5
-rwxr-xr-xwl_cfgscan.c2
-rw-r--r--wl_cfgvendor.c869
-rw-r--r--wl_cfgvendor.h4
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 {