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