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