1 /* SPDX-License-Identifier: GPL-2.0 */
2 /*
3 * Common function shared by Linux WEXT, cfg80211 and p2p drivers
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: wldev_common.c 786015 2018-10-24 08:21:53Z $
29 */
30
31 #include <osl.h>
32 #include <linux/kernel.h>
33 #include <linux/kthread.h>
34 #include <linux/netdevice.h>
35
36 #include <wldev_common.h>
37 #include <bcmutils.h>
38 #ifdef WL_CFG80211
39 #include <wl_cfg80211.h>
40 #include <wl_cfgscan.h>
41 #endif /* WL_CFG80211 */
42 #include <dhd_config.h>
43
44 #define htod32(i) (i)
45 #define htod16(i) (i)
46 #define dtoh32(i) (i)
47 #define dtoh16(i) (i)
48 #define htodchanspec(i) (i)
49 #define dtohchanspec(i) (i)
50
51 #define WLDEV_ERROR_MSG(x, args...) \
52 do { \
53 printk(KERN_INFO DHD_LOG_PREFIXS "WLDEV-ERROR) " x, ## args); \
54 } while (0)
55 #define WLDEV_ERROR(x) WLDEV_ERROR_MSG x
56
57 #define WLDEV_INFO_MSG(x, args...) \
58 do { \
59 printk(KERN_INFO DHD_LOG_PREFIXS "WLDEV-INFO) " x, ## args); \
60 } while (0)
61 #define WLDEV_INFO(x) WLDEV_INFO_MSG x
62
63 extern int dhd_ioctl_entry_local(struct net_device *net, wl_ioctl_t *ioc, int cmd);
64
wldev_ioctl(struct net_device * dev,u32 cmd,void * arg,u32 len,u32 set)65 s32 wldev_ioctl(
66 struct net_device *dev, u32 cmd, void *arg, u32 len, u32 set)
67 {
68 s32 ret = 0;
69 struct wl_ioctl ioc;
70
71 memset(&ioc, 0, sizeof(ioc));
72 ioc.cmd = cmd;
73 ioc.buf = arg;
74 ioc.len = len;
75 ioc.set = set;
76 ret = dhd_ioctl_entry_local(dev, (wl_ioctl_t *)&ioc, cmd);
77
78 return ret;
79 }
80
81 /*
82 SET commands :
83 cast buffer to non-const and call the GET function
84 */
85
wldev_ioctl_set(struct net_device * dev,u32 cmd,const void * arg,u32 len)86 s32 wldev_ioctl_set(
87 struct net_device *dev, u32 cmd, const void *arg, u32 len)
88 {
89
90 #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
91 #pragma GCC diagnostic push
92 #pragma GCC diagnostic ignored "-Wcast-qual"
93 #endif // endif
94 return wldev_ioctl(dev, cmd, (void *)arg, len, 1);
95 #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
96 #pragma GCC diagnostic pop
97 #endif // endif
98
99 }
100
wldev_ioctl_get(struct net_device * dev,u32 cmd,void * arg,u32 len)101 s32 wldev_ioctl_get(
102 struct net_device *dev, u32 cmd, void *arg, u32 len)
103 {
104 return wldev_ioctl(dev, cmd, (void *)arg, len, 0);
105 }
106
107 /* Format a iovar buffer, not bsscfg indexed. The bsscfg index will be
108 * taken care of in dhd_ioctl_entry. Internal use only, not exposed to
109 * wl_iw, wl_cfg80211 and wl_cfgp2p
110 */
wldev_mkiovar(const s8 * iovar_name,const s8 * param,s32 paramlen,s8 * iovar_buf,u32 buflen)111 static s32 wldev_mkiovar(
112 const s8 *iovar_name, const s8 *param, s32 paramlen,
113 s8 *iovar_buf, u32 buflen)
114 {
115 s32 iolen = 0;
116
117 iolen = bcm_mkiovar(iovar_name, param, paramlen, iovar_buf, buflen);
118 return iolen;
119 }
120
wldev_iovar_getbuf(struct net_device * dev,s8 * iovar_name,const void * param,s32 paramlen,void * buf,s32 buflen,struct mutex * buf_sync)121 s32 wldev_iovar_getbuf(
122 struct net_device *dev, s8 *iovar_name,
123 const void *param, s32 paramlen, void *buf, s32 buflen, struct mutex* buf_sync)
124 {
125 s32 ret = 0;
126 if (buf_sync) {
127 mutex_lock(buf_sync);
128 }
129
130 if (buf && (buflen > 0)) {
131 /* initialize the response buffer */
132 memset(buf, 0, buflen);
133 } else {
134 ret = BCME_BADARG;
135 goto exit;
136 }
137
138 ret = wldev_mkiovar(iovar_name, param, paramlen, buf, buflen);
139
140 if (!ret) {
141 ret = BCME_BUFTOOSHORT;
142 goto exit;
143 }
144 ret = wldev_ioctl_get(dev, WLC_GET_VAR, buf, buflen);
145 exit:
146 if (buf_sync)
147 mutex_unlock(buf_sync);
148 return ret;
149 }
150
wldev_iovar_setbuf(struct net_device * dev,s8 * iovar_name,const void * param,s32 paramlen,void * buf,s32 buflen,struct mutex * buf_sync)151 s32 wldev_iovar_setbuf(
152 struct net_device *dev, s8 *iovar_name,
153 const void *param, s32 paramlen, void *buf, s32 buflen, struct mutex* buf_sync)
154 {
155 s32 ret = 0;
156 s32 iovar_len;
157 if (buf_sync) {
158 mutex_lock(buf_sync);
159 }
160 iovar_len = wldev_mkiovar(iovar_name, param, paramlen, buf, buflen);
161 if (iovar_len > 0)
162 ret = wldev_ioctl_set(dev, WLC_SET_VAR, buf, iovar_len);
163 else
164 ret = BCME_BUFTOOSHORT;
165
166 if (buf_sync)
167 mutex_unlock(buf_sync);
168 return ret;
169 }
170
wldev_iovar_setint(struct net_device * dev,s8 * iovar,s32 val)171 s32 wldev_iovar_setint(
172 struct net_device *dev, s8 *iovar, s32 val)
173 {
174 s8 iovar_buf[WLC_IOCTL_SMLEN];
175
176 val = htod32(val);
177 memset(iovar_buf, 0, sizeof(iovar_buf));
178 return wldev_iovar_setbuf(dev, iovar, &val, sizeof(val), iovar_buf,
179 sizeof(iovar_buf), NULL);
180 }
181
wldev_iovar_getint(struct net_device * dev,s8 * iovar,s32 * pval)182 s32 wldev_iovar_getint(
183 struct net_device *dev, s8 *iovar, s32 *pval)
184 {
185 s8 iovar_buf[WLC_IOCTL_SMLEN];
186 s32 err;
187
188 memset(iovar_buf, 0, sizeof(iovar_buf));
189 err = wldev_iovar_getbuf(dev, iovar, pval, sizeof(*pval), iovar_buf,
190 sizeof(iovar_buf), NULL);
191 if (err == 0)
192 {
193 memcpy(pval, iovar_buf, sizeof(*pval));
194 *pval = dtoh32(*pval);
195 }
196 return err;
197 }
198
199 /** Format a bsscfg indexed iovar buffer. The bsscfg index will be
200 * taken care of in dhd_ioctl_entry. Internal use only, not exposed to
201 * wl_iw, wl_cfg80211 and wl_cfgp2p
202 */
wldev_mkiovar_bsscfg(const s8 * iovar_name,const s8 * param,s32 paramlen,s8 * iovar_buf,s32 buflen,s32 bssidx)203 s32 wldev_mkiovar_bsscfg(
204 const s8 *iovar_name, const s8 *param, s32 paramlen,
205 s8 *iovar_buf, s32 buflen, s32 bssidx)
206 {
207 const s8 *prefix = "bsscfg:";
208 s8 *p;
209 u32 prefixlen;
210 u32 namelen;
211 u32 iolen;
212
213 /* initialize buffer */
214 if (!iovar_buf || buflen <= 0)
215 return BCME_BADARG;
216 memset(iovar_buf, 0, buflen);
217
218 if (bssidx == 0) {
219 return wldev_mkiovar(iovar_name, param, paramlen,
220 iovar_buf, buflen);
221 }
222
223 prefixlen = (u32) strlen(prefix); /* lengh of bsscfg prefix */
224 namelen = (u32) strlen(iovar_name) + 1; /* lengh of iovar name + null */
225 iolen = prefixlen + namelen + sizeof(u32) + paramlen;
226
227 if (iolen > (u32)buflen) {
228 WLDEV_ERROR(("%s: buffer is too short\n", __FUNCTION__));
229 return BCME_BUFTOOSHORT;
230 }
231
232 p = (s8 *)iovar_buf;
233
234 /* copy prefix, no null */
235 memcpy(p, prefix, prefixlen);
236 p += prefixlen;
237
238 /* copy iovar name including null */
239 memcpy(p, iovar_name, namelen);
240 p += namelen;
241
242 /* bss config index as first param */
243 bssidx = htod32(bssidx);
244 memcpy(p, &bssidx, sizeof(u32));
245 p += sizeof(u32);
246
247 /* parameter buffer follows */
248 if (paramlen)
249 memcpy(p, param, paramlen);
250
251 return iolen;
252
253 }
254
wldev_iovar_getbuf_bsscfg(struct net_device * dev,s8 * iovar_name,void * param,s32 paramlen,void * buf,s32 buflen,s32 bsscfg_idx,struct mutex * buf_sync)255 s32 wldev_iovar_getbuf_bsscfg(
256 struct net_device *dev, s8 *iovar_name,
257 void *param, s32 paramlen, void *buf, s32 buflen, s32 bsscfg_idx, struct mutex* buf_sync)
258 {
259 s32 ret = 0;
260 if (buf_sync) {
261 mutex_lock(buf_sync);
262 }
263
264 wldev_mkiovar_bsscfg(iovar_name, param, paramlen, buf, buflen, bsscfg_idx);
265 ret = wldev_ioctl_get(dev, WLC_GET_VAR, buf, buflen);
266 if (buf_sync) {
267 mutex_unlock(buf_sync);
268 }
269 return ret;
270
271 }
272
wldev_iovar_setbuf_bsscfg(struct net_device * dev,const s8 * iovar_name,const void * param,s32 paramlen,void * buf,s32 buflen,s32 bsscfg_idx,struct mutex * buf_sync)273 s32 wldev_iovar_setbuf_bsscfg(
274 struct net_device *dev, const s8 *iovar_name,
275 const void *param, s32 paramlen,
276 void *buf, s32 buflen, s32 bsscfg_idx, struct mutex* buf_sync)
277 {
278 s32 ret = 0;
279 s32 iovar_len;
280 if (buf_sync) {
281 mutex_lock(buf_sync);
282 }
283 iovar_len = wldev_mkiovar_bsscfg(iovar_name, param, paramlen, buf, buflen, bsscfg_idx);
284 if (iovar_len > 0)
285 ret = wldev_ioctl_set(dev, WLC_SET_VAR, buf, iovar_len);
286 else {
287 ret = BCME_BUFTOOSHORT;
288 }
289
290 if (buf_sync) {
291 mutex_unlock(buf_sync);
292 }
293 return ret;
294 }
295
wldev_iovar_setint_bsscfg(struct net_device * dev,s8 * iovar,s32 val,s32 bssidx)296 s32 wldev_iovar_setint_bsscfg(
297 struct net_device *dev, s8 *iovar, s32 val, s32 bssidx)
298 {
299 s8 iovar_buf[WLC_IOCTL_SMLEN];
300
301 val = htod32(val);
302 memset(iovar_buf, 0, sizeof(iovar_buf));
303 return wldev_iovar_setbuf_bsscfg(dev, iovar, &val, sizeof(val), iovar_buf,
304 sizeof(iovar_buf), bssidx, NULL);
305 }
306
wldev_iovar_getint_bsscfg(struct net_device * dev,s8 * iovar,s32 * pval,s32 bssidx)307 s32 wldev_iovar_getint_bsscfg(
308 struct net_device *dev, s8 *iovar, s32 *pval, s32 bssidx)
309 {
310 s8 iovar_buf[WLC_IOCTL_SMLEN];
311 s32 err;
312
313 memset(iovar_buf, 0, sizeof(iovar_buf));
314 err = wldev_iovar_getbuf_bsscfg(dev, iovar, pval, sizeof(*pval), iovar_buf,
315 sizeof(iovar_buf), bssidx, NULL);
316 if (err == 0)
317 {
318 memcpy(pval, iovar_buf, sizeof(*pval));
319 *pval = dtoh32(*pval);
320 }
321 return err;
322 }
323
wldev_get_link_speed(struct net_device * dev,int * plink_speed)324 int wldev_get_link_speed(
325 struct net_device *dev, int *plink_speed)
326 {
327 int error;
328
329 if (!plink_speed)
330 return -ENOMEM;
331 *plink_speed = 0;
332 error = wldev_ioctl_get(dev, WLC_GET_RATE, plink_speed, sizeof(int));
333 if (unlikely(error))
334 return error;
335
336 /* Convert internal 500Kbps to Kbps */
337 *plink_speed *= 500;
338 return error;
339 }
340
wldev_get_rssi(struct net_device * dev,scb_val_t * scb_val)341 int wldev_get_rssi(
342 struct net_device *dev, scb_val_t *scb_val)
343 {
344 int error;
345
346 if (!scb_val)
347 return -ENOMEM;
348 memset(scb_val, 0, sizeof(scb_val_t));
349 error = wldev_ioctl_get(dev, WLC_GET_RSSI, scb_val, sizeof(scb_val_t));
350 if (unlikely(error))
351 return error;
352
353 return error;
354 }
355
wldev_get_ssid(struct net_device * dev,wlc_ssid_t * pssid)356 int wldev_get_ssid(
357 struct net_device *dev, wlc_ssid_t *pssid)
358 {
359 int error;
360
361 if (!pssid)
362 return -ENOMEM;
363 memset(pssid, 0, sizeof(wlc_ssid_t));
364 error = wldev_ioctl_get(dev, WLC_GET_SSID, pssid, sizeof(wlc_ssid_t));
365 if (unlikely(error))
366 return error;
367 pssid->SSID_len = dtoh32(pssid->SSID_len);
368 return error;
369 }
370
wldev_get_band(struct net_device * dev,uint * pband)371 int wldev_get_band(
372 struct net_device *dev, uint *pband)
373 {
374 int error;
375
376 *pband = 0;
377 error = wldev_ioctl_get(dev, WLC_GET_BAND, pband, sizeof(uint));
378 return error;
379 }
380
wldev_set_band(struct net_device * dev,uint band)381 int wldev_set_band(
382 struct net_device *dev, uint band)
383 {
384 int error = -1;
385
386 if ((band == WLC_BAND_AUTO) || (band == WLC_BAND_5G) || (band == WLC_BAND_2G)) {
387 error = wldev_ioctl_set(dev, WLC_SET_BAND, &band, sizeof(band));
388 if (!error)
389 dhd_bus_band_set(dev, band);
390 }
391 return error;
392 }
wldev_get_datarate(struct net_device * dev,int * datarate)393 int wldev_get_datarate(struct net_device *dev, int *datarate)
394 {
395 int error = 0;
396
397 error = wldev_ioctl_get(dev, WLC_GET_RATE, datarate, sizeof(int));
398 if (error) {
399 return -1;
400 } else {
401 *datarate = dtoh32(*datarate);
402 }
403
404 return error;
405 }
406
407 #ifdef WL_CFG80211
408 extern chanspec_t
409 wl_chspec_driver_to_host(chanspec_t chanspec);
410 #define WL_EXTRA_BUF_MAX 2048
wldev_get_mode(struct net_device * dev,uint8 * cap,uint8 caplen)411 int wldev_get_mode(
412 struct net_device *dev, uint8 *cap, uint8 caplen)
413 {
414 int error = 0;
415 int chanspec = 0;
416 uint16 band = 0;
417 uint16 bandwidth = 0;
418 wl_bss_info_t *bss = NULL;
419 char* buf = NULL;
420
421 buf = kzalloc(WL_EXTRA_BUF_MAX, GFP_KERNEL);
422 if (!buf) {
423 WLDEV_ERROR(("%s:ENOMEM\n", __FUNCTION__));
424 return -ENOMEM;
425 }
426
427 *(u32*) buf = htod32(WL_EXTRA_BUF_MAX);
428 error = wldev_ioctl_get(dev, WLC_GET_BSS_INFO, (void*)buf, WL_EXTRA_BUF_MAX);
429 if (error) {
430 WLDEV_ERROR(("%s:failed:%d\n", __FUNCTION__, error));
431 kfree(buf);
432 buf = NULL;
433 return error;
434 }
435 bss = (wl_bss_info_t*)(buf + 4);
436 chanspec = wl_chspec_driver_to_host(bss->chanspec);
437
438 band = chanspec & WL_CHANSPEC_BAND_MASK;
439 bandwidth = chanspec & WL_CHANSPEC_BW_MASK;
440
441 if (band == WL_CHANSPEC_BAND_2G) {
442 if (bss->n_cap)
443 strncpy(cap, "n", caplen);
444 else
445 strncpy(cap, "bg", caplen);
446 } else if (band == WL_CHANSPEC_BAND_5G) {
447 if (bandwidth == WL_CHANSPEC_BW_80)
448 strncpy(cap, "ac", caplen);
449 else if ((bandwidth == WL_CHANSPEC_BW_40) || (bandwidth == WL_CHANSPEC_BW_20)) {
450 if ((bss->nbss_cap & 0xf00) && (bss->n_cap))
451 strncpy(cap, "n|ac", caplen);
452 else if (bss->n_cap)
453 strncpy(cap, "n", caplen);
454 else if (bss->vht_cap)
455 strncpy(cap, "ac", caplen);
456 else
457 strncpy(cap, "a", caplen);
458 } else {
459 WLDEV_ERROR(("%s:Mode get failed\n", __FUNCTION__));
460 error = BCME_ERROR;
461 }
462
463 }
464 kfree(buf);
465 buf = NULL;
466 return error;
467 }
468 #endif
469
wldev_set_country(struct net_device * dev,char * country_code,bool notify,bool user_enforced,int revinfo)470 int wldev_set_country(
471 struct net_device *dev, char *country_code, bool notify, bool user_enforced, int revinfo)
472 {
473 int error = -1;
474 wl_country_t cspec = {{0}, 0, {0}};
475 scb_val_t scbval;
476 #ifdef WL_CFG80211
477 struct wireless_dev *wdev = ndev_to_wdev(dev);
478 struct wiphy *wiphy = wdev->wiphy;
479 struct bcm_cfg80211 *cfg = wiphy_priv(wiphy);
480 #endif /* WL_CFG80211 */
481
482 if (!country_code)
483 return error;
484
485 bzero(&scbval, sizeof(scb_val_t));
486 error = wldev_iovar_getbuf(dev, "country", NULL, 0, &cspec, sizeof(cspec), NULL);
487 if (error < 0) {
488 WLDEV_ERROR(("%s: get country failed = %d\n", __FUNCTION__, error));
489 return error;
490 }
491
492 if ((error < 0) ||
493 dhd_force_country_change(dev) ||
494 (strncmp(country_code, cspec.ccode, WLC_CNTRY_BUF_SZ) != 0)) {
495
496 #ifdef WL_CFG80211
497 if ((user_enforced) && (wl_get_drv_status(cfg, CONNECTED, dev)))
498 #else
499 if (user_enforced)
500 #endif /* WL_CFG80211 */
501 {
502 bzero(&scbval, sizeof(scb_val_t));
503 error = wldev_ioctl_set(dev, WLC_DISASSOC,
504 &scbval, sizeof(scb_val_t));
505 if (error < 0) {
506 WLDEV_ERROR(("%s: set country failed due to Disassoc error %d\n",
507 __FUNCTION__, error));
508 return error;
509 }
510 }
511
512 #ifdef WL_CFG80211
513 wl_cfg80211_scan_abort(cfg);
514 #endif
515
516 cspec.rev = revinfo;
517 strlcpy(cspec.country_abbrev, country_code, WLC_CNTRY_BUF_SZ);
518 strlcpy(cspec.ccode, country_code, WLC_CNTRY_BUF_SZ);
519 error = dhd_conf_map_country_list(dhd_get_pub(dev), &cspec);
520 if (error)
521 dhd_get_customized_country_code(dev, (char *)&cspec.country_abbrev, &cspec);
522 error = dhd_conf_set_country(dhd_get_pub(dev), &cspec);
523 if (error < 0) {
524 WLDEV_ERROR(("%s: set country for %s as %s rev %d failed\n",
525 __FUNCTION__, country_code, cspec.ccode, cspec.rev));
526 return error;
527 }
528 dhd_conf_fix_country(dhd_get_pub(dev));
529 dhd_conf_get_country(dhd_get_pub(dev), &cspec);
530 dhd_bus_country_set(dev, &cspec, notify);
531 printf("%s: set country for %s as %s rev %d\n",
532 __FUNCTION__, country_code, cspec.ccode, cspec.rev);
533 }
534 return 0;
535 }
536