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