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