| /* |
| * Linux roam cache |
| * |
| * Copyright (C) 1999-2019, 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. |
| * |
| * Notwithstanding the above, under no circumstances may you combine this |
| * software in any way with any other Broadcom software provided under a license |
| * other than the GPL, without Broadcom's express prior written consent. |
| * |
| * |
| * <<Broadcom-WL-IPTag/Open:>> |
| * |
| * $Id: wl_roam.c 798173 2019-01-07 09:23:21Z $ |
| */ |
| |
| #include <typedefs.h> |
| #include <osl.h> |
| #include <bcmwifi_channels.h> |
| #include <wlioctl.h> |
| #include <bcmutils.h> |
| #ifdef WL_CFG80211 |
| #include <wl_cfg80211.h> |
| #endif // endif |
| #include <wldev_common.h> |
| #include <bcmstdlib_s.h> |
| |
| #ifdef ESCAN_CHANNEL_CACHE |
| #define MAX_ROAM_CACHE 200 |
| #define MAX_SSID_BUFSIZE 36 |
| |
| #define ROAMSCAN_MODE_NORMAL 0 |
| #define ROAMSCAN_MODE_WES 1 |
| |
| typedef struct { |
| chanspec_t chanspec; |
| int ssid_len; |
| char ssid[MAX_SSID_BUFSIZE]; |
| } roam_channel_cache; |
| |
| static int n_roam_cache = 0; |
| static int roam_band = WLC_BAND_AUTO; |
| static roam_channel_cache roam_cache[MAX_ROAM_CACHE]; |
| static uint band2G, band5G, band_bw; |
| |
| #ifdef ROAM_CHANNEL_CACHE |
| int init_roam_cache(struct bcm_cfg80211 *cfg, int ioctl_ver) |
| { |
| int err; |
| struct net_device *dev = bcmcfg_to_prmry_ndev(cfg); |
| s32 mode; |
| |
| /* Check support in firmware */ |
| err = wldev_iovar_getint(dev, "roamscan_mode", &mode); |
| if (err && (err == BCME_UNSUPPORTED)) { |
| /* If firmware doesn't support, return error. Else proceed */ |
| WL_ERR(("roamscan_mode iovar failed. %d\n", err)); |
| return err; |
| } |
| |
| #ifdef D11AC_IOTYPES |
| if (ioctl_ver == 1) { |
| /* legacy chanspec */ |
| band2G = WL_LCHANSPEC_BAND_2G; |
| band5G = WL_LCHANSPEC_BAND_5G; |
| band_bw = WL_LCHANSPEC_BW_20 | WL_LCHANSPEC_CTL_SB_NONE; |
| } else { |
| band2G = WL_CHANSPEC_BAND_2G; |
| band5G = WL_CHANSPEC_BAND_5G; |
| band_bw = WL_CHANSPEC_BW_20; |
| } |
| #else |
| band2G = WL_CHANSPEC_BAND_2G; |
| band5G = WL_CHANSPEC_BAND_5G; |
| band_bw = WL_CHANSPEC_BW_20 | WL_CHANSPEC_CTL_SB_NONE; |
| #endif /* D11AC_IOTYPES */ |
| |
| n_roam_cache = 0; |
| roam_band = WLC_BAND_AUTO; |
| |
| return 0; |
| } |
| #endif /* ROAM_CHANNEL_CACHE */ |
| |
| #ifdef ESCAN_CHANNEL_CACHE |
| void set_roam_band(int band) |
| { |
| roam_band = band; |
| } |
| |
| void reset_roam_cache(struct bcm_cfg80211 *cfg) |
| { |
| if (!cfg->rcc_enabled) { |
| return; |
| } |
| |
| n_roam_cache = 0; |
| } |
| |
| void add_roam_cache(struct bcm_cfg80211 *cfg, wl_bss_info_t *bi) |
| { |
| int i; |
| uint8 channel; |
| char chanbuf[CHANSPEC_STR_LEN]; |
| |
| if (!cfg->rcc_enabled) { |
| return; |
| } |
| |
| if (n_roam_cache >= MAX_ROAM_CACHE) |
| return; |
| |
| for (i = 0; i < n_roam_cache; i++) { |
| if ((roam_cache[i].ssid_len == bi->SSID_len) && |
| (roam_cache[i].chanspec == bi->chanspec) && |
| (memcmp(roam_cache[i].ssid, bi->SSID, bi->SSID_len) == 0)) { |
| /* identical one found, just return */ |
| return; |
| } |
| } |
| |
| roam_cache[n_roam_cache].ssid_len = bi->SSID_len; |
| channel = wf_chspec_ctlchan(bi->chanspec); |
| WL_DBG(("CHSPEC = %s, CTL %d\n", wf_chspec_ntoa_ex(bi->chanspec, chanbuf), channel)); |
| roam_cache[n_roam_cache].chanspec = |
| (channel <= CH_MAX_2G_CHANNEL ? band2G : band5G) | band_bw | channel; |
| (void)memcpy_s(roam_cache[n_roam_cache].ssid, bi->SSID_len, bi->SSID, bi->SSID_len); |
| |
| n_roam_cache++; |
| } |
| |
| static bool is_duplicated_channel(const chanspec_t *channels, int n_channels, chanspec_t new) |
| { |
| int i; |
| |
| for (i = 0; i < n_channels; i++) { |
| if (channels[i] == new) |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| int get_roam_channel_list(int target_chan, |
| chanspec_t *channels, int n_channels, const wlc_ssid_t *ssid, int ioctl_ver) |
| { |
| int i, n = 1; |
| char chanbuf[CHANSPEC_STR_LEN]; |
| |
| /* first index is filled with the given target channel */ |
| if (target_chan) { |
| channels[0] = (target_chan & WL_CHANSPEC_CHAN_MASK) | |
| (target_chan <= CH_MAX_2G_CHANNEL ? band2G : band5G) | band_bw; |
| } else { |
| /* If target channel is not provided, set the index to 0 */ |
| n = 0; |
| } |
| |
| WL_DBG((" %s: %03d 0x%04X\n", __FUNCTION__, target_chan, channels[0])); |
| |
| for (i = 0; i < n_roam_cache; i++) { |
| chanspec_t ch = roam_cache[i].chanspec; |
| bool is_2G = ioctl_ver == 1 ? LCHSPEC_IS2G(ch) : CHSPEC_IS2G(ch); |
| bool is_5G = ioctl_ver == 1 ? LCHSPEC_IS5G(ch) : CHSPEC_IS5G(ch); |
| bool band_match = ((roam_band == WLC_BAND_AUTO) || |
| ((roam_band == WLC_BAND_2G) && is_2G) || |
| ((roam_band == WLC_BAND_5G) && is_5G)); |
| |
| ch = CHSPEC_CHANNEL(ch) | (is_2G ? band2G : band5G) | band_bw; |
| if ((roam_cache[i].ssid_len == ssid->SSID_len) && |
| band_match && !is_duplicated_channel(channels, n, ch) && |
| (memcmp(roam_cache[i].ssid, ssid->SSID, ssid->SSID_len) == 0)) { |
| /* match found, add it */ |
| WL_DBG(("%s: Chanspec = %s\n", __FUNCTION__, |
| wf_chspec_ntoa_ex(ch, chanbuf))); |
| channels[n++] = ch; |
| if (n >= n_channels) { |
| WL_ERR(("Too many roam scan channels\n")); |
| return n; |
| } |
| } |
| } |
| |
| return n; |
| } |
| #endif /* ESCAN_CHANNEL_CACHE */ |
| |
| #ifdef ROAM_CHANNEL_CACHE |
| void print_roam_cache(struct bcm_cfg80211 *cfg) |
| { |
| int i; |
| |
| if (!cfg->rcc_enabled) { |
| return; |
| } |
| |
| WL_DBG((" %d cache\n", n_roam_cache)); |
| |
| for (i = 0; i < n_roam_cache; i++) { |
| roam_cache[i].ssid[roam_cache[i].ssid_len] = 0; |
| WL_DBG(("0x%02X %02d %s\n", roam_cache[i].chanspec, |
| roam_cache[i].ssid_len, roam_cache[i].ssid)); |
| } |
| } |
| |
| static void add_roamcache_channel(wl_roam_channel_list_t *channels, chanspec_t ch) |
| { |
| int i; |
| |
| if (channels->n >= MAX_ROAM_CHANNEL) /* buffer full */ |
| return; |
| |
| for (i = 0; i < channels->n; i++) { |
| if (channels->channels[i] == ch) /* already in the list */ |
| return; |
| } |
| |
| channels->channels[i] = ch; |
| channels->n++; |
| |
| WL_DBG((" RCC: %02d 0x%04X\n", |
| ch & WL_CHANSPEC_CHAN_MASK, ch)); |
| } |
| |
| void update_roam_cache(struct bcm_cfg80211 *cfg, int ioctl_ver) |
| { |
| int error, i, prev_channels; |
| wl_roam_channel_list_t channel_list; |
| char iobuf[WLC_IOCTL_SMLEN]; |
| struct net_device *dev = bcmcfg_to_prmry_ndev(cfg); |
| wlc_ssid_t ssid; |
| |
| if (!cfg->rcc_enabled) { |
| return; |
| } |
| |
| if (!wl_get_drv_status(cfg, CONNECTED, dev)) { |
| WL_DBG(("Not associated\n")); |
| return; |
| } |
| |
| /* need to read out the current cache list |
| as the firmware may change dynamically |
| */ |
| error = wldev_iovar_getbuf(dev, "roamscan_channels", 0, 0, |
| (void *)&channel_list, sizeof(channel_list), NULL); |
| if (error) { |
| WL_ERR(("Failed to get roamscan channels, error = %d\n", error)); |
| return; |
| } |
| |
| error = wldev_get_ssid(dev, &ssid); |
| if (error) { |
| WL_ERR(("Failed to get SSID, err=%d\n", error)); |
| return; |
| } |
| |
| prev_channels = channel_list.n; |
| for (i = 0; i < n_roam_cache; i++) { |
| chanspec_t ch = roam_cache[i].chanspec; |
| bool is_2G = ioctl_ver == 1 ? LCHSPEC_IS2G(ch) : CHSPEC_IS2G(ch); |
| bool is_5G = ioctl_ver == 1 ? LCHSPEC_IS5G(ch) : CHSPEC_IS5G(ch); |
| bool band_match = ((roam_band == WLC_BAND_AUTO) || |
| ((roam_band == WLC_BAND_2G) && is_2G) || |
| ((roam_band == WLC_BAND_5G) && is_5G)); |
| |
| if ((roam_cache[i].ssid_len == ssid.SSID_len) && |
| band_match && (memcmp(roam_cache[i].ssid, ssid.SSID, ssid.SSID_len) == 0)) { |
| /* match found, add it */ |
| ch = CHSPEC_CHANNEL(ch) | (is_2G ? band2G : band5G) | band_bw; |
| add_roamcache_channel(&channel_list, ch); |
| } |
| } |
| if (prev_channels != channel_list.n) { |
| /* channel list updated */ |
| error = wldev_iovar_setbuf(dev, "roamscan_channels", &channel_list, |
| sizeof(channel_list), iobuf, sizeof(iobuf), NULL); |
| if (error) { |
| WL_ERR(("Failed to update roamscan channels, error = %d\n", error)); |
| } |
| } |
| |
| WL_DBG(("%d AP, %d cache item(s), err=%d\n", n_roam_cache, channel_list.n, error)); |
| } |
| |
| void wl_update_roamscan_cache_by_band(struct net_device *dev, int band) |
| { |
| int i, error, ioctl_ver, wes_mode; |
| wl_roam_channel_list_t chanlist_before, chanlist_after; |
| char iobuf[WLC_IOCTL_SMLEN]; |
| |
| roam_band = band; |
| |
| error = wldev_iovar_getint(dev, "roamscan_mode", &wes_mode); |
| if (error) { |
| WL_ERR(("Failed to get roamscan mode, error = %d\n", error)); |
| return; |
| } |
| |
| ioctl_ver = wl_cfg80211_get_ioctl_version(); |
| /* in case of WES mode, update channel list by band based on the cache in DHD */ |
| if (wes_mode) { |
| int n = 0; |
| chanlist_before.n = n_roam_cache; |
| |
| for (n = 0; n < n_roam_cache; n++) { |
| chanspec_t ch = roam_cache[n].chanspec; |
| bool is_2G = ioctl_ver == 1 ? LCHSPEC_IS2G(ch) : CHSPEC_IS2G(ch); |
| chanlist_before.channels[n] = CHSPEC_CHANNEL(ch) | |
| (is_2G ? band2G : band5G) | band_bw; |
| } |
| } else { |
| if (band == WLC_BAND_AUTO) { |
| return; |
| } |
| error = wldev_iovar_getbuf(dev, "roamscan_channels", 0, 0, |
| (void *)&chanlist_before, sizeof(wl_roam_channel_list_t), NULL); |
| if (error) { |
| WL_ERR(("Failed to get roamscan channels, error = %d\n", error)); |
| return; |
| } |
| } |
| chanlist_after.n = 0; |
| /* filtering by the given band */ |
| for (i = 0; i < chanlist_before.n; i++) { |
| chanspec_t chspec = chanlist_before.channels[i]; |
| bool is_2G = ioctl_ver == 1 ? LCHSPEC_IS2G(chspec) : CHSPEC_IS2G(chspec); |
| bool is_5G = ioctl_ver == 1 ? LCHSPEC_IS5G(chspec) : CHSPEC_IS5G(chspec); |
| bool band_match = ((band == WLC_BAND_AUTO) || |
| ((band == WLC_BAND_2G) && is_2G) || |
| ((band == WLC_BAND_5G) && is_5G)); |
| if (band_match) { |
| chanlist_after.channels[chanlist_after.n++] = chspec; |
| } |
| } |
| |
| if (wes_mode) { |
| /* need to set ROAMSCAN_MODE_NORMAL to update roamscan_channels, |
| * otherwise, it won't be updated |
| */ |
| wldev_iovar_setint(dev, "roamscan_mode", ROAMSCAN_MODE_NORMAL); |
| |
| error = wldev_iovar_setbuf(dev, "roamscan_channels", &chanlist_after, |
| sizeof(wl_roam_channel_list_t), iobuf, sizeof(iobuf), NULL); |
| if (error) { |
| WL_ERR(("Failed to update roamscan channels, error = %d\n", error)); |
| } |
| wldev_iovar_setint(dev, "roamscan_mode", ROAMSCAN_MODE_WES); |
| } else { |
| if (chanlist_before.n == chanlist_after.n) { |
| return; |
| } |
| error = wldev_iovar_setbuf(dev, "roamscan_channels", &chanlist_after, |
| sizeof(wl_roam_channel_list_t), iobuf, sizeof(iobuf), NULL); |
| if (error) { |
| WL_ERR(("Failed to update roamscan channels, error = %d\n", error)); |
| } |
| } |
| } |
| #endif /* ROAM_CHANNEL_CACHE */ |
| #endif /* ESCAN_CHANNEL_CACHE */ |