375 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			375 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
| /*
 | |
|  * 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 */
 | 
