1 /* SPDX-License-Identifier: GPL-2.0 */
2 /*
3 * Linux roam cache
4 *
5 * Copyright (C) 1999-2019, Broadcom.
6 *
7 * Unless you and Broadcom execute a separate written software license
8 * agreement governing use of this software, this software is licensed to you
9 * under the terms of the GNU General Public License version 2 (the "GPL"),
10 * available at http://www.broadcom.com/licenses/GPLv2.php, with the
11 * following added to such license:
12 *
13 * As a special exception, the copyright holders of this software give you
14 * permission to link this software with independent modules, and to copy and
15 * distribute the resulting executable under terms of your choice, provided that
16 * you also meet, for each linked independent module, the terms and conditions of
17 * the license of that module. An independent module is a module which is not
18 * derived from this software. The special exception does not apply to any
19 * modifications of the software.
20 *
21 * Notwithstanding the above, under no circumstances may you combine this
22 * software in any way with any other Broadcom software provided under a license
23 * other than the GPL, without Broadcom's express prior written consent.
24 *
25 *
26 * <<Broadcom-WL-IPTag/Open:>>
27 *
28 * $Id: wl_roam.c 798173 2019-01-07 09:23:21Z $
29 */
30
31 #include <typedefs.h>
32 #include <osl.h>
33 #include <bcmwifi_channels.h>
34 #include <wlioctl.h>
35 #include <bcmutils.h>
36 #ifdef WL_CFG80211
37 #include <wl_cfg80211.h>
38 #endif // endif
39 #include <wldev_common.h>
40 #include <bcmstdlib_s.h>
41
42 #ifdef ESCAN_CHANNEL_CACHE
43 #define MAX_ROAM_CACHE 200
44 #define MAX_SSID_BUFSIZE 36
45
46 #define ROAMSCAN_MODE_NORMAL 0
47 #define ROAMSCAN_MODE_WES 1
48
49 typedef struct {
50 chanspec_t chanspec;
51 int ssid_len;
52 char ssid[MAX_SSID_BUFSIZE];
53 } roam_channel_cache;
54
55 static int n_roam_cache = 0;
56 static int roam_band = WLC_BAND_AUTO;
57 static roam_channel_cache roam_cache[MAX_ROAM_CACHE];
58 static uint band2G, band5G, band_bw;
59
60 #ifdef ROAM_CHANNEL_CACHE
init_roam_cache(struct bcm_cfg80211 * cfg,int ioctl_ver)61 int init_roam_cache(struct bcm_cfg80211 *cfg, int ioctl_ver)
62 {
63 int err;
64 struct net_device *dev = bcmcfg_to_prmry_ndev(cfg);
65 s32 mode;
66
67 /* Check support in firmware */
68 err = wldev_iovar_getint(dev, "roamscan_mode", &mode);
69 if (err && (err == BCME_UNSUPPORTED)) {
70 /* If firmware doesn't support, return error. Else proceed */
71 WL_ERR(("roamscan_mode iovar failed. %d\n", err));
72 return err;
73 }
74
75 #ifdef D11AC_IOTYPES
76 if (ioctl_ver == 1) {
77 /* legacy chanspec */
78 band2G = WL_LCHANSPEC_BAND_2G;
79 band5G = WL_LCHANSPEC_BAND_5G;
80 band_bw = WL_LCHANSPEC_BW_20 | WL_LCHANSPEC_CTL_SB_NONE;
81 } else {
82 band2G = WL_CHANSPEC_BAND_2G;
83 band5G = WL_CHANSPEC_BAND_5G;
84 band_bw = WL_CHANSPEC_BW_20;
85 }
86 #else
87 band2G = WL_CHANSPEC_BAND_2G;
88 band5G = WL_CHANSPEC_BAND_5G;
89 band_bw = WL_CHANSPEC_BW_20 | WL_CHANSPEC_CTL_SB_NONE;
90 #endif /* D11AC_IOTYPES */
91
92 n_roam_cache = 0;
93 roam_band = WLC_BAND_AUTO;
94
95 return 0;
96 }
97 #endif /* ROAM_CHANNEL_CACHE */
98
99 #ifdef ESCAN_CHANNEL_CACHE
set_roam_band(int band)100 void set_roam_band(int band)
101 {
102 roam_band = band;
103 }
104
reset_roam_cache(struct bcm_cfg80211 * cfg)105 void reset_roam_cache(struct bcm_cfg80211 *cfg)
106 {
107 if (!cfg->rcc_enabled) {
108 return;
109 }
110
111 n_roam_cache = 0;
112 }
113
add_roam_cache(struct bcm_cfg80211 * cfg,wl_bss_info_t * bi)114 void add_roam_cache(struct bcm_cfg80211 *cfg, wl_bss_info_t *bi)
115 {
116 int i;
117 uint8 channel;
118 char chanbuf[CHANSPEC_STR_LEN];
119
120 if (!cfg->rcc_enabled) {
121 return;
122 }
123
124 if (n_roam_cache >= MAX_ROAM_CACHE)
125 return;
126
127 for (i = 0; i < n_roam_cache; i++) {
128 if ((roam_cache[i].ssid_len == bi->SSID_len) &&
129 (roam_cache[i].chanspec == bi->chanspec) &&
130 (memcmp(roam_cache[i].ssid, bi->SSID, bi->SSID_len) == 0)) {
131 /* identical one found, just return */
132 return;
133 }
134 }
135
136 roam_cache[n_roam_cache].ssid_len = bi->SSID_len;
137 channel = wf_chspec_ctlchan(bi->chanspec);
138 WL_DBG(("CHSPEC = %s, CTL %d\n", wf_chspec_ntoa_ex(bi->chanspec, chanbuf), channel));
139 roam_cache[n_roam_cache].chanspec =
140 (channel <= CH_MAX_2G_CHANNEL ? band2G : band5G) | band_bw | channel;
141 (void)memcpy_s(roam_cache[n_roam_cache].ssid, bi->SSID_len, bi->SSID, bi->SSID_len);
142
143 n_roam_cache++;
144 }
145
is_duplicated_channel(const chanspec_t * channels,int n_channels,chanspec_t new)146 static bool is_duplicated_channel(const chanspec_t *channels, int n_channels, chanspec_t new)
147 {
148 int i;
149
150 for (i = 0; i < n_channels; i++) {
151 if (channels[i] == new)
152 return TRUE;
153 }
154
155 return FALSE;
156 }
157
get_roam_channel_list(int target_chan,chanspec_t * channels,int n_channels,const wlc_ssid_t * ssid,int ioctl_ver)158 int get_roam_channel_list(int target_chan,
159 chanspec_t *channels, int n_channels, const wlc_ssid_t *ssid, int ioctl_ver)
160 {
161 int i, n = 1;
162 char chanbuf[CHANSPEC_STR_LEN];
163
164 /* first index is filled with the given target channel */
165 if (target_chan) {
166 channels[0] = (target_chan & WL_CHANSPEC_CHAN_MASK) |
167 (target_chan <= CH_MAX_2G_CHANNEL ? band2G : band5G) | band_bw;
168 } else {
169 /* If target channel is not provided, set the index to 0 */
170 n = 0;
171 }
172
173 WL_DBG((" %s: %03d 0x%04X\n", __FUNCTION__, target_chan, channels[0]));
174
175 for (i = 0; i < n_roam_cache; i++) {
176 chanspec_t ch = roam_cache[i].chanspec;
177 bool is_2G = ioctl_ver == 1 ? LCHSPEC_IS2G(ch) : CHSPEC_IS2G(ch);
178 bool is_5G = ioctl_ver == 1 ? LCHSPEC_IS5G(ch) : CHSPEC_IS5G(ch);
179 bool band_match = ((roam_band == WLC_BAND_AUTO) ||
180 ((roam_band == WLC_BAND_2G) && is_2G) ||
181 ((roam_band == WLC_BAND_5G) && is_5G));
182
183 ch = CHSPEC_CHANNEL(ch) | (is_2G ? band2G : band5G) | band_bw;
184 if ((roam_cache[i].ssid_len == ssid->SSID_len) &&
185 band_match && !is_duplicated_channel(channels, n, ch) &&
186 (memcmp(roam_cache[i].ssid, ssid->SSID, ssid->SSID_len) == 0)) {
187 /* match found, add it */
188 WL_DBG(("%s: Chanspec = %s\n", __FUNCTION__,
189 wf_chspec_ntoa_ex(ch, chanbuf)));
190 channels[n++] = ch;
191 if (n >= n_channels) {
192 WL_ERR(("Too many roam scan channels\n"));
193 return n;
194 }
195 }
196 }
197
198 return n;
199 }
200 #endif /* ESCAN_CHANNEL_CACHE */
201
202 #ifdef ROAM_CHANNEL_CACHE
print_roam_cache(struct bcm_cfg80211 * cfg)203 void print_roam_cache(struct bcm_cfg80211 *cfg)
204 {
205 int i;
206
207 if (!cfg->rcc_enabled) {
208 return;
209 }
210
211 WL_DBG((" %d cache\n", n_roam_cache));
212
213 for (i = 0; i < n_roam_cache; i++) {
214 roam_cache[i].ssid[roam_cache[i].ssid_len] = 0;
215 WL_DBG(("0x%02X %02d %s\n", roam_cache[i].chanspec,
216 roam_cache[i].ssid_len, roam_cache[i].ssid));
217 }
218 }
219
add_roamcache_channel(wl_roam_channel_list_t * channels,chanspec_t ch)220 static void add_roamcache_channel(wl_roam_channel_list_t *channels, chanspec_t ch)
221 {
222 int i;
223
224 if (channels->n >= MAX_ROAM_CHANNEL) /* buffer full */
225 return;
226
227 for (i = 0; i < channels->n; i++) {
228 if (channels->channels[i] == ch) /* already in the list */
229 return;
230 }
231
232 channels->channels[i] = ch;
233 channels->n++;
234
235 WL_DBG((" RCC: %02d 0x%04X\n",
236 ch & WL_CHANSPEC_CHAN_MASK, ch));
237 }
238
update_roam_cache(struct bcm_cfg80211 * cfg,int ioctl_ver)239 void update_roam_cache(struct bcm_cfg80211 *cfg, int ioctl_ver)
240 {
241 int error, i, prev_channels;
242 wl_roam_channel_list_t channel_list;
243 char iobuf[WLC_IOCTL_SMLEN];
244 struct net_device *dev = bcmcfg_to_prmry_ndev(cfg);
245 wlc_ssid_t ssid;
246
247 if (!cfg->rcc_enabled) {
248 return;
249 }
250
251 if (!wl_get_drv_status(cfg, CONNECTED, dev)) {
252 WL_DBG(("Not associated\n"));
253 return;
254 }
255
256 /* need to read out the current cache list
257 as the firmware may change dynamically
258 */
259 error = wldev_iovar_getbuf(dev, "roamscan_channels", 0, 0,
260 (void *)&channel_list, sizeof(channel_list), NULL);
261 if (error) {
262 WL_ERR(("Failed to get roamscan channels, error = %d\n", error));
263 return;
264 }
265
266 error = wldev_get_ssid(dev, &ssid);
267 if (error) {
268 WL_ERR(("Failed to get SSID, err=%d\n", error));
269 return;
270 }
271
272 prev_channels = channel_list.n;
273 for (i = 0; i < n_roam_cache; i++) {
274 chanspec_t ch = roam_cache[i].chanspec;
275 bool is_2G = ioctl_ver == 1 ? LCHSPEC_IS2G(ch) : CHSPEC_IS2G(ch);
276 bool is_5G = ioctl_ver == 1 ? LCHSPEC_IS5G(ch) : CHSPEC_IS5G(ch);
277 bool band_match = ((roam_band == WLC_BAND_AUTO) ||
278 ((roam_band == WLC_BAND_2G) && is_2G) ||
279 ((roam_band == WLC_BAND_5G) && is_5G));
280
281 if ((roam_cache[i].ssid_len == ssid.SSID_len) &&
282 band_match && (memcmp(roam_cache[i].ssid, ssid.SSID, ssid.SSID_len) == 0)) {
283 /* match found, add it */
284 ch = CHSPEC_CHANNEL(ch) | (is_2G ? band2G : band5G) | band_bw;
285 add_roamcache_channel(&channel_list, ch);
286 }
287 }
288 if (prev_channels != channel_list.n) {
289 /* channel list updated */
290 error = wldev_iovar_setbuf(dev, "roamscan_channels", &channel_list,
291 sizeof(channel_list), iobuf, sizeof(iobuf), NULL);
292 if (error) {
293 WL_ERR(("Failed to update roamscan channels, error = %d\n", error));
294 }
295 }
296
297 WL_DBG(("%d AP, %d cache item(s), err=%d\n", n_roam_cache, channel_list.n, error));
298 }
299
wl_update_roamscan_cache_by_band(struct net_device * dev,int band)300 void wl_update_roamscan_cache_by_band(struct net_device *dev, int band)
301 {
302 int i, error, ioctl_ver, wes_mode;
303 wl_roam_channel_list_t chanlist_before, chanlist_after;
304 char iobuf[WLC_IOCTL_SMLEN];
305
306 roam_band = band;
307
308 error = wldev_iovar_getint(dev, "roamscan_mode", &wes_mode);
309 if (error) {
310 WL_ERR(("Failed to get roamscan mode, error = %d\n", error));
311 return;
312 }
313
314 ioctl_ver = wl_cfg80211_get_ioctl_version();
315 /* in case of WES mode, update channel list by band based on the cache in DHD */
316 if (wes_mode) {
317 int n = 0;
318 chanlist_before.n = n_roam_cache;
319
320 for (n = 0; n < n_roam_cache; n++) {
321 chanspec_t ch = roam_cache[n].chanspec;
322 bool is_2G = ioctl_ver == 1 ? LCHSPEC_IS2G(ch) : CHSPEC_IS2G(ch);
323 chanlist_before.channels[n] = CHSPEC_CHANNEL(ch) |
324 (is_2G ? band2G : band5G) | band_bw;
325 }
326 } else {
327 if (band == WLC_BAND_AUTO) {
328 return;
329 }
330 error = wldev_iovar_getbuf(dev, "roamscan_channels", 0, 0,
331 (void *)&chanlist_before, sizeof(wl_roam_channel_list_t), NULL);
332 if (error) {
333 WL_ERR(("Failed to get roamscan channels, error = %d\n", error));
334 return;
335 }
336 }
337 chanlist_after.n = 0;
338 /* filtering by the given band */
339 for (i = 0; i < chanlist_before.n; i++) {
340 chanspec_t chspec = chanlist_before.channels[i];
341 bool is_2G = ioctl_ver == 1 ? LCHSPEC_IS2G(chspec) : CHSPEC_IS2G(chspec);
342 bool is_5G = ioctl_ver == 1 ? LCHSPEC_IS5G(chspec) : CHSPEC_IS5G(chspec);
343 bool band_match = ((band == WLC_BAND_AUTO) ||
344 ((band == WLC_BAND_2G) && is_2G) ||
345 ((band == WLC_BAND_5G) && is_5G));
346 if (band_match) {
347 chanlist_after.channels[chanlist_after.n++] = chspec;
348 }
349 }
350
351 if (wes_mode) {
352 /* need to set ROAMSCAN_MODE_NORMAL to update roamscan_channels,
353 * otherwise, it won't be updated
354 */
355 wldev_iovar_setint(dev, "roamscan_mode", ROAMSCAN_MODE_NORMAL);
356
357 error = wldev_iovar_setbuf(dev, "roamscan_channels", &chanlist_after,
358 sizeof(wl_roam_channel_list_t), iobuf, sizeof(iobuf), NULL);
359 if (error) {
360 WL_ERR(("Failed to update roamscan channels, error = %d\n", error));
361 }
362 wldev_iovar_setint(dev, "roamscan_mode", ROAMSCAN_MODE_WES);
363 } else {
364 if (chanlist_before.n == chanlist_after.n) {
365 return;
366 }
367 error = wldev_iovar_setbuf(dev, "roamscan_channels", &chanlist_after,
368 sizeof(wl_roam_channel_list_t), iobuf, sizeof(iobuf), NULL);
369 if (error) {
370 WL_ERR(("Failed to update roamscan channels, error = %d\n", error));
371 }
372 }
373 }
374 #endif /* ROAM_CHANNEL_CACHE */
375 #endif /* ESCAN_CHANNEL_CACHE */
376