1 /*
2 * Linux roam cache
3 *
4 * Copyright (C) 2020, Broadcom.
5 *
6 * Unless you and Broadcom execute a separate written software license
7 * agreement governing use of this software, this software is licensed to you
8 * under the terms of the GNU General Public License version 2 (the "GPL"),
9 * available at http://www.broadcom.com/licenses/GPLv2.php, with the
10 * following added to such license:
11 *
12 * As a special exception, the copyright holders of this software give you
13 * permission to link this software with independent modules, and to copy and
14 * distribute the resulting executable under terms of your choice, provided that
15 * you also meet, for each linked independent module, the terms and conditions of
16 * the license of that module. An independent module is a module which is not
17 * derived from this software. The special exception does not apply to any
18 * modifications of the software.
19 *
20 *
21 * <<Broadcom-WL-IPTag/Dual:>>
22 */
23
24 #include <typedefs.h>
25 #include <osl.h>
26 #include <bcmwifi_channels.h>
27 #include <wlioctl.h>
28 #include <bcmutils.h>
29 #ifdef WL_CFG80211
30 #include <wl_cfg80211.h>
31 #endif
32 #include <wldev_common.h>
33 #if defined(__linux__)
34 #include <bcmstdlib_s.h>
35 #endif /* defined(__linux__) */
36
37 #ifdef ESCAN_CHANNEL_CACHE
38 #define MAX_ROAM_CACHE 200
39 #define MAX_SSID_BUFSIZE 36
40
41 typedef struct {
42 chanspec_t chanspec;
43 int ssid_len;
44 char ssid[MAX_SSID_BUFSIZE];
45 } roam_channel_cache;
46
47 static int n_roam_cache = 0;
48 static int roam_band = WLC_BAND_AUTO;
49 static roam_channel_cache roam_cache[MAX_ROAM_CACHE];
50 static uint band_bw;
51
add_roamcache_channel(wl_roam_channel_list_t * channels,chanspec_t ch)52 static void add_roamcache_channel(wl_roam_channel_list_t *channels, chanspec_t ch)
53 {
54 int i;
55
56 if (channels->n >= MAX_ROAM_CHANNEL) /* buffer full */
57 return;
58
59 for (i = 0; i < channels->n; i++) {
60 if (channels->channels[i] == ch) /* already in the list */
61 return;
62 }
63
64 channels->channels[i] = ch;
65 channels->n++;
66
67 WL_DBG((" RCC: %02d 0x%04X\n",
68 ch & WL_CHANSPEC_CHAN_MASK, ch));
69 }
70
update_roam_cache(struct bcm_cfg80211 * cfg,int ioctl_ver)71 void update_roam_cache(struct bcm_cfg80211 *cfg, int ioctl_ver)
72 {
73 int error, i, prev_channels;
74 wl_roam_channel_list_t channel_list;
75 char iobuf[WLC_IOCTL_SMLEN];
76 struct net_device *dev = bcmcfg_to_prmry_ndev(cfg);
77 wlc_ssid_t ssid;
78
79 if (!cfg->rcc_enabled) {
80 return;
81 }
82
83 #ifdef WES_SUPPORT
84 if (cfg->roamscan_mode == ROAMSCAN_MODE_WES) {
85 /* no update when ROAMSCAN_MODE_WES */
86 return;
87 }
88 #endif /* WES_SUPPORT */
89
90 if (!wl_get_drv_status(cfg, CONNECTED, dev)) {
91 WL_DBG(("Not associated\n"));
92 return;
93 }
94
95 /* need to read out the current cache list
96 as the firmware may change dynamically
97 */
98 error = wldev_iovar_getbuf(dev, "roamscan_channels", 0, 0,
99 (void *)&channel_list, sizeof(channel_list), NULL);
100 if (error) {
101 WL_ERR(("Failed to get roamscan channels, error = %d\n", error));
102 return;
103 }
104
105 error = wldev_get_ssid(dev, &ssid);
106 if (error) {
107 WL_ERR(("Failed to get SSID, err=%d\n", error));
108 return;
109 }
110
111 prev_channels = channel_list.n;
112 for (i = 0; i < n_roam_cache; i++) {
113 chanspec_t ch = roam_cache[i].chanspec;
114 bool band_match = ((roam_band == WLC_BAND_AUTO) ||
115 #ifdef WL_6G_BAND
116 ((roam_band == WLC_BAND_6G) && (CHSPEC_IS6G(ch))) ||
117 #endif /* WL_6G_BAND */
118 ((roam_band == WLC_BAND_2G) && (CHSPEC_IS2G(ch))) ||
119 ((roam_band == WLC_BAND_5G) && (CHSPEC_IS5G(ch))));
120
121 if ((roam_cache[i].ssid_len == ssid.SSID_len) &&
122 band_match && (memcmp(roam_cache[i].ssid, ssid.SSID, ssid.SSID_len) == 0)) {
123 /* match found, add it */
124 ch = wf_chspec_ctlchan(ch) | CHSPEC_BAND(ch) | band_bw;
125 add_roamcache_channel(&channel_list, ch);
126 }
127 }
128 if (prev_channels != channel_list.n) {
129 /* channel list updated */
130 error = wldev_iovar_setbuf(dev, "roamscan_channels", &channel_list,
131 sizeof(channel_list), iobuf, sizeof(iobuf), NULL);
132 if (error) {
133 WL_ERR(("Failed to update roamscan channels, error = %d\n", error));
134 }
135 }
136
137 WL_DBG(("%d AP, %d cache item(s), err=%d\n", n_roam_cache, channel_list.n, error));
138 }
139
set_roam_band(int band)140 void set_roam_band(int band)
141 {
142 roam_band = band;
143 }
144
reset_roam_cache(struct bcm_cfg80211 * cfg)145 void reset_roam_cache(struct bcm_cfg80211 *cfg)
146 {
147 if (!cfg->rcc_enabled) {
148 return;
149 }
150
151 #ifdef WES_SUPPORT
152 if (cfg->roamscan_mode == ROAMSCAN_MODE_WES)
153 return;
154 #endif /* WES_SUPPORT */
155
156 n_roam_cache = 0;
157 }
158
159 static void
add_roam_cache_list(uint8 * SSID,uint32 SSID_len,chanspec_t chanspec)160 add_roam_cache_list(uint8 *SSID, uint32 SSID_len, chanspec_t chanspec)
161 {
162 int i;
163 uint8 channel;
164 char chanbuf[CHANSPEC_STR_LEN];
165
166 if (n_roam_cache >= MAX_ROAM_CACHE) {
167 return;
168 }
169
170 for (i = 0; i < n_roam_cache; i++) {
171 if ((roam_cache[i].ssid_len == SSID_len) &&
172 (roam_cache[i].chanspec == chanspec) &&
173 (memcmp(roam_cache[i].ssid, SSID, SSID_len) == 0)) {
174 /* identical one found, just return */
175 return;
176 }
177 }
178
179 roam_cache[n_roam_cache].ssid_len = SSID_len;
180 channel = wf_chspec_ctlchan(chanspec);
181 WL_DBG(("CHSPEC = %s, CTL %d SSID %s\n",
182 wf_chspec_ntoa_ex(chanspec, chanbuf), channel, SSID));
183 roam_cache[n_roam_cache].chanspec = CHSPEC_BAND(chanspec) | band_bw | channel;
184 (void)memcpy_s(roam_cache[n_roam_cache].ssid, SSID_len, SSID, SSID_len);
185
186 n_roam_cache++;
187 }
188
189 void
add_roam_cache(struct bcm_cfg80211 * cfg,wl_bss_info_t * bi)190 add_roam_cache(struct bcm_cfg80211 *cfg, wl_bss_info_t *bi)
191 {
192 if (!cfg->rcc_enabled) {
193 return;
194 }
195
196 #ifdef WES_SUPPORT
197 if (cfg->roamscan_mode == ROAMSCAN_MODE_WES) {
198 return;
199 }
200 #endif /* WES_SUPPORT */
201
202 add_roam_cache_list(bi->SSID, bi->SSID_len, bi->chanspec);
203 }
204
is_duplicated_channel(const chanspec_t * channels,int n_channels,chanspec_t new)205 static bool is_duplicated_channel(const chanspec_t *channels, int n_channels, chanspec_t new)
206 {
207 int i;
208
209 for (i = 0; i < n_channels; i++) {
210 if (channels[i] == new)
211 return TRUE;
212 }
213
214 return FALSE;
215 }
216
get_roam_channel_list(struct bcm_cfg80211 * cfg,chanspec_t target_chan,chanspec_t * channels,int n_channels,const wlc_ssid_t * ssid,int ioctl_ver)217 int get_roam_channel_list(struct bcm_cfg80211 *cfg, chanspec_t target_chan,
218 chanspec_t *channels, int n_channels, const wlc_ssid_t *ssid, int ioctl_ver)
219 {
220 int i, n = 0;
221 char chanbuf[CHANSPEC_STR_LEN];
222
223 /* first index is filled with the given target channel */
224 if ((target_chan != INVCHANSPEC) && (target_chan != 0)) {
225 channels[0] = target_chan;
226 n++;
227 }
228
229 WL_DBG((" %s: 0x%04X\n", __FUNCTION__, channels[0]));
230
231 #ifdef WES_SUPPORT
232 if (cfg->roamscan_mode == ROAMSCAN_MODE_WES) {
233 for (i = 0; i < n_roam_cache; i++) {
234 chanspec_t ch = roam_cache[i].chanspec;
235 bool band_match = ((roam_band == WLC_BAND_AUTO) ||
236 #ifdef WL_6G_BAND
237 ((roam_band == WLC_BAND_6G) && (CHSPEC_IS6G(ch))) ||
238 #endif /* WL_6G_BAND */
239 ((roam_band == WLC_BAND_2G) && (CHSPEC_IS2G(ch))) ||
240 ((roam_band == WLC_BAND_5G) && (CHSPEC_IS5G(ch))));
241
242 ch = wf_chspec_ctlchan(ch) | CHSPEC_BAND(ch) | band_bw;
243
244 if (band_match && !is_duplicated_channel(channels, n, ch)) {
245 WL_DBG(("%s: Chanspec = %s\n", __FUNCTION__,
246 wf_chspec_ntoa_ex(ch, chanbuf)));
247 channels[n++] = ch;
248 if (n >= n_channels) {
249 WL_ERR(("Too many roam scan channels\n"));
250 return n;
251 }
252 }
253 }
254
255 return n;
256 }
257 #endif /* WES_SUPPORT */
258
259 for (i = 0; i < n_roam_cache; i++) {
260 chanspec_t ch = roam_cache[i].chanspec;
261 bool band_match = ((roam_band == WLC_BAND_AUTO) ||
262 #ifdef WL_6G_BAND
263 ((roam_band == WLC_BAND_6G) && (CHSPEC_IS6G(ch))) ||
264 #endif /* WL_6G_BAND */
265 ((roam_band == WLC_BAND_2G) && (CHSPEC_IS2G(ch))) ||
266 ((roam_band == WLC_BAND_5G) && (CHSPEC_IS5G(ch))));
267
268 ch = wf_chspec_ctlchan(ch) | CHSPEC_BAND(ch) | band_bw;
269 if ((roam_cache[i].ssid_len == ssid->SSID_len) &&
270 band_match && !is_duplicated_channel(channels, n, ch) &&
271 (memcmp(roam_cache[i].ssid, ssid->SSID, ssid->SSID_len) == 0)) {
272 /* match found, add it */
273 WL_DBG(("%s: Chanspec = %s\n", __FUNCTION__,
274 wf_chspec_ntoa_ex(ch, chanbuf)));
275 channels[n++] = ch;
276 if (n >= n_channels) {
277 WL_ERR(("Too many roam scan channels\n"));
278 return n;
279 }
280 }
281 }
282
283 return n;
284 }
285
286 #ifdef WES_SUPPORT
get_roamscan_mode(struct net_device * dev,int * mode)287 int get_roamscan_mode(struct net_device *dev, int *mode)
288 {
289 struct bcm_cfg80211 *cfg = wl_get_cfg(dev);
290 *mode = cfg->roamscan_mode;
291
292 return 0;
293 }
294
set_roamscan_mode(struct net_device * dev,int mode)295 int set_roamscan_mode(struct net_device *dev, int mode)
296 {
297 struct bcm_cfg80211 *cfg = wl_get_cfg(dev);
298 int error = 0;
299 cfg->roamscan_mode = mode;
300 n_roam_cache = 0;
301
302 error = wldev_iovar_setint(dev, "roamscan_mode", mode);
303 if (error) {
304 WL_ERR(("Failed to set roamscan mode to %d, error = %d\n", mode, error));
305 }
306
307 return error;
308 }
309
310 int
get_roamscan_chanspec_list(struct net_device * dev,chanspec_t * chanspecs)311 get_roamscan_chanspec_list(struct net_device *dev, chanspec_t *chanspecs)
312 {
313 int i = 0;
314 int error = BCME_OK;
315 wl_roam_channel_list_t channel_list;
316
317 /* Get Current RCC List */
318 error = wldev_iovar_getbuf(dev, "roamscan_channels", 0, 0,
319 (void *)&channel_list, sizeof(channel_list), NULL);
320 if (error) {
321 WL_ERR(("Failed to get roamscan channels, err = %d\n", error));
322 return error;
323 }
324 if (channel_list.n > MAX_ROAM_CHANNEL) {
325 WL_ERR(("Invalid roamscan channels count(%d)\n", channel_list.n));
326 return BCME_ERROR;
327 }
328
329 for (i = 0; i < channel_list.n; i++) {
330 chanspecs[i] = channel_list.channels[i];
331 WL_DBG(("%02d: chanspec %04x\n", i, chanspecs[i]));
332 }
333
334 return i;
335 }
336
337 int
set_roamscan_chanspec_list(struct net_device * dev,uint nchan,chanspec_t * chanspecs)338 set_roamscan_chanspec_list(struct net_device *dev, uint nchan, chanspec_t *chanspecs)
339 {
340 int i;
341 int error;
342 wl_roam_channel_list_t channel_list;
343 char iobuf[WLC_IOCTL_SMLEN];
344 struct bcm_cfg80211 *cfg = wl_get_cfg(dev);
345 cfg->roamscan_mode = ROAMSCAN_MODE_WES;
346
347 if (nchan > MAX_ROAM_CHANNEL) {
348 nchan = MAX_ROAM_CHANNEL;
349 }
350
351 for (i = 0; i < nchan; i++) {
352 roam_cache[i].chanspec = chanspecs[i];
353 channel_list.channels[i] = chanspecs[i];
354
355 WL_DBG(("%02d/%d: chan: 0x%04x\n", i, nchan, chanspecs[i]));
356 }
357
358 n_roam_cache = nchan;
359 channel_list.n = nchan;
360
361 /* need to set ROAMSCAN_MODE_NORMAL to update roamscan_channels,
362 * otherwise, it won't be updated
363 */
364 error = wldev_iovar_setint(dev, "roamscan_mode", ROAMSCAN_MODE_NORMAL);
365 if (error) {
366 WL_ERR(("Failed to set roamscan mode to %d, error = %d\n",
367 ROAMSCAN_MODE_NORMAL, error));
368 return error;
369 }
370 error = wldev_iovar_setbuf(dev, "roamscan_channels", &channel_list,
371 sizeof(channel_list), iobuf, sizeof(iobuf), NULL);
372 if (error) {
373 WL_ERR(("Failed to set roamscan channels, error = %d\n", error));
374 return error;
375 }
376 error = wldev_iovar_setint(dev, "roamscan_mode", ROAMSCAN_MODE_WES);
377 if (error) {
378 WL_ERR(("Failed to set roamscan mode to %d, error = %d\n",
379 ROAMSCAN_MODE_WES, error));
380 }
381
382 return error;
383 }
384
385 int
add_roamscan_chanspec_list(struct net_device * dev,uint nchan,chanspec_t * chanspecs)386 add_roamscan_chanspec_list(struct net_device *dev, uint nchan, chanspec_t *chanspecs)
387 {
388 int i, error = BCME_OK;
389 struct bcm_cfg80211 *cfg = wl_get_cfg(dev);
390 wlc_ssid_t ssid;
391
392 if (!cfg->rcc_enabled) {
393 return BCME_ERROR;
394 }
395
396 if (cfg->roamscan_mode == ROAMSCAN_MODE_WES) {
397 WL_ERR(("Failed to add roamscan channels, WES mode %d\n",
398 cfg->roamscan_mode));
399 return BCME_ERROR;
400 }
401
402 if (nchan > MAX_ROAM_CHANNEL) {
403 WL_ERR(("Failed Over MAX channel list(%d)\n", nchan));
404 return BCME_BADARG;
405 }
406
407 error = wldev_get_ssid(dev, &ssid);
408 if (error) {
409 WL_ERR(("Failed to get SSID, err=%d\n", error));
410 return error;
411 }
412
413 WL_DBG(("Add Roam scan channel count %d\n", nchan));
414
415 for (i = 0; i < nchan; i++) {
416 if (chanspecs[i] == 0) {
417 continue;
418 }
419 add_roam_cache_list(ssid.SSID, ssid.SSID_len, chanspecs[i]);
420 WL_DBG(("channel[%d] - 0x%04x SSID %s\n", i, chanspecs[i], ssid.SSID));
421 }
422
423 update_roam_cache(cfg, ioctl_version);
424
425 return error;
426 }
427 #endif /* WES_SUPPORT */
428
429 #ifdef ROAM_CHANNEL_CACHE
init_roam_cache(struct bcm_cfg80211 * cfg,int ioctl_ver)430 int init_roam_cache(struct bcm_cfg80211 *cfg, int ioctl_ver)
431 {
432 int err;
433 struct net_device *dev = bcmcfg_to_prmry_ndev(cfg);
434 s32 mode;
435
436 /* Check support in firmware */
437 err = wldev_iovar_getint(dev, "roamscan_mode", &mode);
438 if (err && (err == BCME_UNSUPPORTED)) {
439 /* If firmware doesn't support, return error. Else proceed */
440 WL_ERR(("roamscan_mode iovar failed. %d\n", err));
441 return err;
442 }
443
444 #ifdef D11AC_IOTYPES
445 band_bw = WL_CHANSPEC_BW_20;
446 #else
447 band_bw = WL_CHANSPEC_BW_20 | WL_CHANSPEC_CTL_SB_NONE;
448 #endif /* D11AC_IOTYPES */
449
450 n_roam_cache = 0;
451 roam_band = WLC_BAND_AUTO;
452 cfg->roamscan_mode = ROAMSCAN_MODE_NORMAL;
453
454 return 0;
455 }
456
print_roam_cache(struct bcm_cfg80211 * cfg)457 void print_roam_cache(struct bcm_cfg80211 *cfg)
458 {
459 int i;
460
461 if (!cfg->rcc_enabled) {
462 return;
463 }
464
465 WL_DBG((" %d cache\n", n_roam_cache));
466
467 for (i = 0; i < n_roam_cache; i++) {
468 roam_cache[i].ssid[roam_cache[i].ssid_len] = 0;
469 WL_DBG(("0x%02X %02d %s\n", roam_cache[i].chanspec,
470 roam_cache[i].ssid_len, roam_cache[i].ssid));
471 }
472 }
473
wl_update_roamscan_cache_by_band(struct net_device * dev,int band)474 void wl_update_roamscan_cache_by_band(struct net_device *dev, int band)
475 {
476 int i, error, roamscan_mode;
477 wl_roam_channel_list_t chanlist_before, chanlist_after;
478 char iobuf[WLC_IOCTL_SMLEN];
479
480 roam_band = band;
481
482 error = wldev_iovar_getint(dev, "roamscan_mode", &roamscan_mode);
483 if (error) {
484 WL_ERR(("Failed to get roamscan mode, error = %d\n", error));
485 return;
486 }
487
488 /* in case of WES mode, update channel list by band based on the cache in DHD */
489 if (roamscan_mode) {
490 int n = 0;
491 chanlist_before.n = n_roam_cache;
492
493 for (n = 0; n < n_roam_cache; n++) {
494 chanspec_t ch = roam_cache[n].chanspec;
495 chanlist_before.channels[n] = wf_chspec_ctlchan(ch) |
496 CHSPEC_BAND(ch) | band_bw;
497 }
498 } else {
499 if (band == WLC_BAND_AUTO) {
500 return;
501 }
502 error = wldev_iovar_getbuf(dev, "roamscan_channels", 0, 0,
503 (void *)&chanlist_before, sizeof(wl_roam_channel_list_t), NULL);
504 if (error) {
505 WL_ERR(("Failed to get roamscan channels, error = %d\n", error));
506 return;
507 }
508 }
509 chanlist_after.n = 0;
510 /* filtering by the given band */
511 for (i = 0; i < chanlist_before.n; i++) {
512 chanspec_t chspec = chanlist_before.channels[i];
513 bool band_match = ((band == WLC_BAND_AUTO) ||
514 #ifdef WL_6G_BAND
515 ((band == WLC_BAND_6G) && (CHSPEC_IS6G(chspec))) ||
516 #endif /* WL_6G_BAND */
517 ((band == WLC_BAND_2G) && (CHSPEC_IS2G(chspec))) ||
518 ((band == WLC_BAND_5G) && (CHSPEC_IS5G(chspec))));
519 if (band_match) {
520 chanlist_after.channels[chanlist_after.n++] = chspec;
521 }
522 }
523
524 if (roamscan_mode) {
525 /* need to set ROAMSCAN_MODE_NORMAL to update roamscan_channels,
526 * otherwise, it won't be updated
527 */
528 wldev_iovar_setint(dev, "roamscan_mode", ROAMSCAN_MODE_NORMAL);
529
530 error = wldev_iovar_setbuf(dev, "roamscan_channels", &chanlist_after,
531 sizeof(wl_roam_channel_list_t), iobuf, sizeof(iobuf), NULL);
532 if (error) {
533 WL_ERR(("Failed to update roamscan channels, error = %d\n", error));
534 }
535 wldev_iovar_setint(dev, "roamscan_mode", ROAMSCAN_MODE_WES);
536 } else {
537 if (chanlist_before.n == chanlist_after.n) {
538 return;
539 }
540 error = wldev_iovar_setbuf(dev, "roamscan_channels", &chanlist_after,
541 sizeof(wl_roam_channel_list_t), iobuf, sizeof(iobuf), NULL);
542 if (error) {
543 WL_ERR(("Failed to update roamscan channels, error = %d\n", error));
544 }
545 }
546 }
547 #endif /* ROAM_CHANNEL_CACHE */
548 #endif /* ESCAN_CHANNEL_CACHE */
549