1 /*
2 * Linux cfg80211 driver - Dongle Host Driver (DHD) related
3 *
4 * Portions of this code are copyright (c) 2021 Cypress Semiconductor Corporation
5 *
6 * Copyright (C) 1999-2017, Broadcom Corporation
7 *
8 * Unless you and Broadcom execute a separate written software license
9 * agreement governing use of this software, this software is licensed to you
10 * under the terms of the GNU General Public License version 2 (the "GPL"),
11 * available at http://www.broadcom.com/licenses/GPLv2.php, with the
12 * following added to such license:
13 *
14 * As a special exception, the copyright holders of this software give you
15 * permission to link this software with independent modules, and to copy and
16 * distribute the resulting executable under terms of your choice, provided that
17 * you also meet, for each linked independent module, the terms and conditions of
18 * the license of that module. An independent module is a module which is not
19 * derived from this software. The special exception does not apply to any
20 * modifications of the software.
21 *
22 * Notwithstanding the above, under no circumstances may you combine this
23 * software in any way with any other Broadcom software provided under a license
24 * other than the GPL, without Broadcom's express prior written consent.
25 *
26 *
27 * <<Broadcom-WL-IPTag/Open:>>
28 *
29 * $Id: dhd_cfg80211.c 696903 2017-04-28 19:48:01Z $
30 */
31
32 #include <linux/vmalloc.h>
33 #include <net/rtnetlink.h>
34
35 #include <bcmutils.h>
36 #include <wldev_common.h>
37 #include <wl_cfg80211.h>
38 #include <dhd_cfg80211.h>
39
40 #ifdef PKT_FILTER_SUPPORT
41 #include <dngl_stats.h>
42 #include <dhd.h>
43 #endif // endif
44
45 #ifdef PKT_FILTER_SUPPORT
46 extern uint dhd_pkt_filter_enable;
47 extern uint dhd_master_mode;
48 extern void dhd_pktfilter_offload_enable(dhd_pub_t * dhd, char *arg, int enable, int master_mode);
49 #endif // endif
50
51 static int dhd_dongle_up = FALSE;
52
53 #include <dngl_stats.h>
54 #include <dhd.h>
55 #include <dhdioctl.h>
56 #include <wlioctl.h>
57 #include <brcm_nl80211.h>
58 #include <dhd_cfg80211.h>
59
60 static s32 wl_dongle_up(struct net_device *ndev);
61 static s32 wl_dongle_down(struct net_device *ndev);
62 #ifndef OEM_ANDROID
63 static s32 wl_dongle_power(struct net_device *ndev, u32 power_mode);
64 #ifdef BCMSDIO /* glomming is a sdio specific feature */
65 static s32 wl_dongle_glom(struct net_device *ndev, s32 glom, u32 dongle_align);
66 #endif // endif
67 static s32 wl_dongle_scantime(struct net_device *ndev, s32 scan_assoc_time, s32 scan_unassoc_time);
68 static s32 wl_dongle_offload(struct net_device *ndev, s32 arpoe, s32 arp_ol);
69 static s32 wl_pattern_atoh(s8 *src, s8 *dst);
70 static s32 wl_dongle_filter(struct net_device *ndev, u32 filter_mode);
71 #endif /* OEM_ANDROID */
72
73 /**
74 * Function implementations
75 */
76
dhd_cfg80211_init(struct bcm_cfg80211 * cfg)77 s32 dhd_cfg80211_init(struct bcm_cfg80211 *cfg)
78 {
79 dhd_dongle_up = FALSE;
80 return 0;
81 }
82
dhd_cfg80211_deinit(struct bcm_cfg80211 * cfg)83 s32 dhd_cfg80211_deinit(struct bcm_cfg80211 *cfg)
84 {
85 dhd_dongle_up = FALSE;
86 return 0;
87 }
88
dhd_cfg80211_down(struct bcm_cfg80211 * cfg)89 s32 dhd_cfg80211_down(struct bcm_cfg80211 *cfg)
90 {
91 struct net_device *ndev;
92 s32 err = 0;
93 dhd_pub_t *dhd = (dhd_pub_t *)(cfg->pub);
94
95 WL_TRACE(("In\n"));
96 if ((!dhd_dongle_up) || (!dhd->up)) {
97 WL_INFORM_MEM(("Dongle is already down\n"));
98 err = 0;
99 goto done;
100 }
101 ndev = bcmcfg_to_prmry_ndev(cfg);
102 wl_dongle_down(ndev);
103 done:
104 dhd_dongle_up = FALSE;
105 return err;
106 }
107
dhd_cfg80211_set_p2p_info(struct bcm_cfg80211 * cfg,int val)108 s32 dhd_cfg80211_set_p2p_info(struct bcm_cfg80211 *cfg, int val)
109 {
110 dhd_pub_t *dhd = (dhd_pub_t *)(cfg->pub);
111 dhd->op_mode |= val;
112 WL_ERR(("Set : op_mode=0x%04x\n", dhd->op_mode));
113 #ifdef ARP_OFFLOAD_SUPPORT
114 if (dhd->arp_version == 1) {
115 /* IF P2P is enabled, disable arpoe */
116 dhd_arp_offload_set(dhd, 0);
117 dhd_arp_offload_enable(dhd, false);
118 }
119 #endif /* ARP_OFFLOAD_SUPPORT */
120
121 return 0;
122 }
123
dhd_cfg80211_clean_p2p_info(struct bcm_cfg80211 * cfg)124 s32 dhd_cfg80211_clean_p2p_info(struct bcm_cfg80211 *cfg)
125 {
126 dhd_pub_t *dhd = (dhd_pub_t *)(cfg->pub);
127 dhd->op_mode &= ~(DHD_FLAG_P2P_GC_MODE | DHD_FLAG_P2P_GO_MODE);
128 WL_ERR(("Clean : op_mode=0x%04x\n", dhd->op_mode));
129
130 #ifdef ARP_OFFLOAD_SUPPORT
131 if (dhd->arp_version == 1) {
132 /* IF P2P is disabled, enable arpoe back for STA mode. */
133 dhd_arp_offload_set(dhd, dhd_arp_mode);
134 dhd_arp_offload_enable(dhd, true);
135 }
136 #endif /* ARP_OFFLOAD_SUPPORT */
137
138 return 0;
139 }
140
141 #ifdef WL_STATIC_IF
142 int32
wl_cfg80211_update_iflist_info(struct bcm_cfg80211 * cfg,struct net_device * ndev,int ifidx,uint8 * addr,int bssidx,char * name,int if_state)143 wl_cfg80211_update_iflist_info(struct bcm_cfg80211 *cfg, struct net_device *ndev,
144 int ifidx, uint8 *addr, int bssidx, char *name, int if_state)
145 {
146 return dhd_update_iflist_info(cfg->pub, ndev, ifidx, addr, bssidx, name, if_state);
147 }
148 #endif /* WL_STATIC_IF */
149
wl_cfg80211_allocate_if(struct bcm_cfg80211 * cfg,int ifidx,const char * name,uint8 * mac,uint8 bssidx,const char * dngl_name)150 struct net_device* wl_cfg80211_allocate_if(struct bcm_cfg80211 *cfg, int ifidx, const char *name,
151 uint8 *mac, uint8 bssidx, const char *dngl_name)
152 {
153 return dhd_allocate_if(cfg->pub, ifidx, name, mac, bssidx, FALSE, dngl_name);
154 }
155
wl_cfg80211_register_if(struct bcm_cfg80211 * cfg,int ifidx,struct net_device * ndev,bool rtnl_lock_reqd)156 int wl_cfg80211_register_if(struct bcm_cfg80211 *cfg,
157 int ifidx, struct net_device* ndev, bool rtnl_lock_reqd)
158 {
159 return dhd_register_if(cfg->pub, ifidx, rtnl_lock_reqd);
160 }
161
wl_cfg80211_remove_if(struct bcm_cfg80211 * cfg,int ifidx,struct net_device * ndev,bool rtnl_lock_reqd)162 int wl_cfg80211_remove_if(struct bcm_cfg80211 *cfg,
163 int ifidx, struct net_device* ndev, bool rtnl_lock_reqd)
164 {
165 #ifdef DHD_PCIE_RUNTIMEPM
166 dhdpcie_runtime_bus_wake(cfg->pub, CAN_SLEEP(), __builtin_return_address(0));
167 #endif /* DHD_PCIE_RUNTIMEPM */
168 return dhd_remove_if(cfg->pub, ifidx, rtnl_lock_reqd);
169 }
170
wl_cfg80211_cleanup_if(struct net_device * net)171 void wl_cfg80211_cleanup_if(struct net_device *net)
172 {
173 struct bcm_cfg80211 *cfg = wl_get_cfg(net);
174 #ifdef DHD_PCIE_RUNTIMEPM
175 dhdpcie_runtime_bus_wake(cfg->pub, CAN_SLEEP(), __builtin_return_address(0));
176 #else
177 BCM_REFERENCE(cfg);
178 #endif /* DHD_PCIE_RUNTIMEPM */
179 dhd_cleanup_if(net);
180 }
181
dhd_cfg80211_netdev_free(struct net_device * ndev)182 struct net_device * dhd_cfg80211_netdev_free(struct net_device *ndev)
183 {
184 struct bcm_cfg80211 *cfg;
185
186 if (ndev) {
187 cfg = wl_get_cfg(ndev);
188 if (ndev->ieee80211_ptr) {
189 MFREE(cfg->osh, ndev->ieee80211_ptr, sizeof(struct wireless_dev));
190 ndev->ieee80211_ptr = NULL;
191 }
192 free_netdev(ndev);
193 return NULL;
194 }
195
196 return ndev;
197 }
198
dhd_netdev_free(struct net_device * ndev)199 void dhd_netdev_free(struct net_device *ndev)
200 {
201 #ifdef WL_CFG80211
202 ndev = dhd_cfg80211_netdev_free(ndev);
203 #endif // endif
204 if (ndev)
205 free_netdev(ndev);
206 }
207
208 static s32
wl_dongle_up(struct net_device * ndev)209 wl_dongle_up(struct net_device *ndev)
210 {
211 s32 err = 0;
212 u32 local_up = 0;
213
214 err = wldev_ioctl_set(ndev, WLC_UP, &local_up, sizeof(local_up));
215 if (unlikely(err)) {
216 WL_ERR(("WLC_UP error (%d)\n", err));
217 }
218 return err;
219 }
220
221 static s32
wl_dongle_down(struct net_device * ndev)222 wl_dongle_down(struct net_device *ndev)
223 {
224 s32 err = 0;
225 u32 local_down = 0;
226
227 err = wldev_ioctl_set(ndev, WLC_DOWN, &local_down, sizeof(local_down));
228 if (unlikely(err)) {
229 WL_ERR(("WLC_DOWN error (%d)\n", err));
230 }
231 return err;
232 }
233
234 #ifndef OEM_ANDROID
wl_dongle_power(struct net_device * ndev,u32 power_mode)235 static s32 wl_dongle_power(struct net_device *ndev, u32 power_mode)
236 {
237 s32 err = 0;
238
239 WL_TRACE(("In\n"));
240 err = wldev_ioctl_set(ndev, WLC_SET_PM, &power_mode, sizeof(power_mode));
241 if (unlikely(err)) {
242 WL_ERR(("WLC_SET_PM error (%d)\n", err));
243 }
244 return err;
245 }
246
247 #ifdef BCMSDIO
248 static s32
wl_dongle_glom(struct net_device * ndev,s32 glom,u32 dongle_align)249 wl_dongle_glom(struct net_device *ndev, s32 glom, u32 dongle_align)
250 {
251 s32 err = 0;
252
253 /* Match Host and Dongle rx alignment */
254 err = wldev_iovar_setint(ndev, "bus:txglomalign", dongle_align);
255 if (unlikely(err)) {
256 WL_ERR(("txglomalign error (%d)\n", err));
257 goto dongle_glom_out;
258 }
259 /* disable glom option per default */
260 if (glom != DEFAULT_GLOM_VALUE) {
261 err = wldev_iovar_setint(ndev, "bus:txglom", glom);
262 if (unlikely(err)) {
263 WL_ERR(("txglom error (%d)\n", err));
264 goto dongle_glom_out;
265 }
266 }
267 dongle_glom_out:
268 return err;
269 }
270
271 #endif /* BCMSDIO */
272 #endif /* OEM_ANDROID */
273
274 s32
wl_dongle_roam(struct net_device * ndev,u32 roamvar,u32 bcn_timeout)275 wl_dongle_roam(struct net_device *ndev, u32 roamvar, u32 bcn_timeout)
276 {
277 s32 err = 0;
278
279 /* Setup timeout if Beacons are lost and roam is off to report link down */
280 if (roamvar) {
281 err = wldev_iovar_setint(ndev, "bcn_timeout", bcn_timeout);
282 if (unlikely(err)) {
283 WL_ERR(("bcn_timeout error (%d)\n", err));
284 goto dongle_rom_out;
285 }
286 }
287 /* Enable/Disable built-in roaming to allow supplicant to take care of roaming */
288 err = wldev_iovar_setint(ndev, "roam_off", roamvar);
289 if (unlikely(err)) {
290 WL_ERR(("roam_off error (%d)\n", err));
291 goto dongle_rom_out;
292 }
293 dongle_rom_out:
294 return err;
295 }
296
297 #ifndef OEM_ANDROID
298 static s32
wl_dongle_scantime(struct net_device * ndev,s32 scan_assoc_time,s32 scan_unassoc_time)299 wl_dongle_scantime(struct net_device *ndev, s32 scan_assoc_time,
300 s32 scan_unassoc_time)
301 {
302 s32 err = 0;
303
304 err = wldev_ioctl_set(ndev, WLC_SET_SCAN_CHANNEL_TIME, &scan_assoc_time,
305 sizeof(scan_assoc_time));
306 if (err) {
307 if (err == -EOPNOTSUPP) {
308 WL_INFORM(("Scan assoc time is not supported\n"));
309 } else {
310 WL_ERR(("Scan assoc time error (%d)\n", err));
311 }
312 goto dongle_scantime_out;
313 }
314 err = wldev_ioctl_set(ndev, WLC_SET_SCAN_UNASSOC_TIME, &scan_unassoc_time,
315 sizeof(scan_unassoc_time));
316 if (err) {
317 if (err == -EOPNOTSUPP) {
318 WL_INFORM(("Scan unassoc time is not supported\n"));
319 } else {
320 WL_ERR(("Scan unassoc time error (%d)\n", err));
321 }
322 goto dongle_scantime_out;
323 }
324
325 dongle_scantime_out:
326 return err;
327 }
328
329 static s32
wl_dongle_offload(struct net_device * ndev,s32 arpoe,s32 arp_ol)330 wl_dongle_offload(struct net_device *ndev, s32 arpoe, s32 arp_ol)
331 {
332 s8 iovbuf[WLC_IOCTL_SMLEN];
333 s32 err = 0;
334 s32 len;
335
336 /* Set ARP offload */
337 len = bcm_mkiovar("arpoe", (char *)&arpoe, sizeof(arpoe), iovbuf, sizeof(iovbuf));
338 if (!len) {
339 WL_ERR(("%s: bcm_mkiovar failed:%d\n", __FUNCTION__, len));
340 return BCME_BADARG;
341 }
342 err = wldev_ioctl_set(ndev, WLC_SET_VAR, iovbuf, len);
343 if (err) {
344 if (err == -EOPNOTSUPP)
345 WL_INFORM(("arpoe is not supported\n"));
346 else
347 WL_ERR(("arpoe error (%d)\n", err));
348
349 goto dongle_offload_out;
350 }
351 len = bcm_mkiovar("arp_ol", (char *)&arp_ol, sizeof(arp_ol), iovbuf, sizeof(iovbuf));
352 if (!len) {
353 WL_ERR(("%s: bcm_mkiovar failed:%d\n", __FUNCTION__, len));
354 return BCME_BADARG;
355 }
356 err = wldev_ioctl_set(ndev, WLC_SET_VAR, iovbuf, len);
357 if (err) {
358 if (err == -EOPNOTSUPP)
359 WL_INFORM(("arp_ol is not supported\n"));
360 else
361 WL_ERR(("arp_ol error (%d)\n", err));
362
363 goto dongle_offload_out;
364 }
365
366 dongle_offload_out:
367 return err;
368 }
369
wl_pattern_atoh(s8 * src,s8 * dst)370 static s32 wl_pattern_atoh(s8 *src, s8 *dst)
371 {
372 int i;
373 if (strncmp(src, "0x", 2) != 0 && strncmp(src, "0X", 2) != 0) {
374 WL_ERR(("Mask invalid format. Needs to start with 0x\n"));
375 return -1;
376 }
377 src = src + 2; /* Skip past 0x */
378 if (strlen(src) % 2 != 0) {
379 WL_ERR(("Mask invalid format. Needs to be of even length\n"));
380 return -1;
381 }
382 for (i = 0; *src != '\0'; i++) {
383 char num[3];
384 strncpy(num, src, 2);
385 num[2] = '\0';
386 dst[i] = (u8) simple_strtoul(num, NULL, 16);
387 src += 2;
388 }
389 return i;
390 }
391
wl_dongle_filter(struct net_device * ndev,u32 filter_mode)392 static s32 wl_dongle_filter(struct net_device *ndev, u32 filter_mode)
393 {
394 const s8 *str;
395 struct wl_pkt_filter pkt_filter;
396 struct wl_pkt_filter *pkt_filterp;
397 s32 buf_len;
398 s32 str_len;
399 u32 mask_size;
400 u32 pattern_size;
401 s8 buf[64] = {0};
402 s32 err = 0;
403
404 /* add a default packet filter pattern */
405 str = "pkt_filter_add";
406 str_len = strlen(str);
407 strncpy(buf, str, sizeof(buf) - 1);
408 buf[ sizeof(buf) - 1 ] = '\0';
409 buf_len = str_len + 1;
410
411 pkt_filterp = (struct wl_pkt_filter *)(buf + str_len + 1);
412
413 /* Parse packet filter id. */
414 pkt_filter.id = htod32(100);
415
416 /* Parse filter polarity. */
417 pkt_filter.negate_match = htod32(0);
418
419 /* Parse filter type. */
420 pkt_filter.type = htod32(0);
421
422 /* Parse pattern filter offset. */
423 pkt_filter.u.pattern.offset = htod32(0);
424
425 /* Parse pattern filter mask. */
426 mask_size = htod32(wl_pattern_atoh("0xff",
427 (char *)pkt_filterp->u.pattern.
428 mask_and_pattern));
429
430 /* Parse pattern filter pattern. */
431 pattern_size = htod32(wl_pattern_atoh("0x00",
432 (char *)&pkt_filterp->u.pattern.mask_and_pattern[mask_size]));
433
434 if (mask_size != pattern_size) {
435 WL_ERR(("Mask and pattern not the same size\n"));
436 err = -EINVAL;
437 goto dongle_filter_out;
438 }
439
440 pkt_filter.u.pattern.size_bytes = mask_size;
441 buf_len += WL_PKT_FILTER_FIXED_LEN;
442 buf_len += (WL_PKT_FILTER_PATTERN_FIXED_LEN + 2 * mask_size);
443
444 /* Keep-alive attributes are set in local
445 * variable (keep_alive_pkt), and
446 * then memcpy'ed into buffer (keep_alive_pktp) since there is no
447 * guarantee that the buffer is properly aligned.
448 */
449 memcpy((char *)pkt_filterp, &pkt_filter,
450 WL_PKT_FILTER_FIXED_LEN + WL_PKT_FILTER_PATTERN_FIXED_LEN);
451
452 err = wldev_ioctl_set(ndev, WLC_SET_VAR, buf, buf_len);
453 if (err) {
454 if (err == -EOPNOTSUPP) {
455 WL_INFORM(("filter not supported\n"));
456 } else {
457 WL_ERR(("filter (%d)\n", err));
458 }
459 goto dongle_filter_out;
460 }
461
462 /* set mode to allow pattern */
463 err = wldev_iovar_setint(ndev, "pkt_filter_mode", filter_mode);
464 if (err) {
465 if (err == -EOPNOTSUPP) {
466 WL_INFORM(("filter_mode not supported\n"));
467 } else {
468 WL_ERR(("filter_mode (%d)\n", err));
469 }
470 goto dongle_filter_out;
471 }
472
473 dongle_filter_out:
474 return err;
475 }
476 #endif /* OEM_ANDROID */
477
dhd_config_dongle(struct bcm_cfg80211 * cfg)478 s32 dhd_config_dongle(struct bcm_cfg80211 *cfg)
479 {
480 #ifndef DHD_SDALIGN
481 #define DHD_SDALIGN 32
482 #endif // endif
483 struct net_device *ndev;
484 s32 err = 0;
485 #if !defined(OEM_ANDROID) && defined(BCMSDIO)
486 s32 glom = CUSTOM_GLOM_SETTING;
487 #endif // endif
488
489 WL_TRACE(("In\n"));
490 if (dhd_dongle_up) {
491 WL_ERR(("Dongle is already up\n"));
492 return err;
493 }
494
495 ndev = bcmcfg_to_prmry_ndev(cfg);
496
497 err = wl_dongle_up(ndev);
498 if (unlikely(err)) {
499 WL_ERR(("wl_dongle_up failed\n"));
500 goto default_conf_out;
501 }
502 #ifndef OEM_ANDROID
503 err = wl_dongle_power(ndev, PM_FAST);
504 if (unlikely(err)) {
505 WL_ERR(("wl_dongle_power failed\n"));
506 goto default_conf_out;
507 }
508 #ifdef BCMSDIO
509 if (glom != DEFAULT_GLOM_VALUE) {
510 err = wl_dongle_glom(ndev, glom, DHD_SDALIGN);
511 } else {
512 err = wl_dongle_glom(ndev, DEFAULT_GLOM_VALUE, DHD_SDALIGN);
513 }
514 if (unlikely(err)) {
515 WL_ERR(("wl_dongle_glom failed\n"));
516 goto default_conf_out;
517 }
518 #endif /* BCMSDIO */
519 err = wl_dongle_roam(ndev, (cfg->roam_on ? 0 : 1), 3);
520 if (unlikely(err)) {
521 WL_ERR(("wl_dongle_roam failed\n"));
522 goto default_conf_out;
523 }
524 wl_dongle_scantime(ndev, 40, 80);
525 wl_dongle_offload(ndev, 1, 0xf);
526 wl_dongle_filter(ndev, 1);
527 #endif /* OEM_ANDROID */
528 dhd_dongle_up = true;
529
530 default_conf_out:
531
532 return err;
533
534 }
535
dhd_cfgvendor_priv_string_handler(struct bcm_cfg80211 * cfg,struct wireless_dev * wdev,const struct bcm_nlmsg_hdr * nlioc,void * buf)536 int dhd_cfgvendor_priv_string_handler(struct bcm_cfg80211 *cfg, struct wireless_dev *wdev,
537 const struct bcm_nlmsg_hdr *nlioc, void *buf)
538 {
539 struct net_device *ndev = NULL;
540 dhd_pub_t *dhd;
541 dhd_ioctl_t ioc = { 0, NULL, 0, 0, 0, 0, 0};
542 int ret = 0;
543 int8 index;
544
545 WL_TRACE(("entry: cmd = %d\n", nlioc->cmd));
546
547 dhd = cfg->pub;
548 DHD_OS_WAKE_LOCK(dhd);
549
550 ndev = wdev_to_wlc_ndev(wdev, cfg);
551 index = dhd_net2idx(dhd->info, ndev);
552 if (index == DHD_BAD_IF) {
553 WL_ERR(("Bad ifidx from wdev:%p\n", wdev));
554 ret = BCME_ERROR;
555 goto done;
556 }
557
558 ioc.cmd = nlioc->cmd;
559 ioc.len = nlioc->len;
560 ioc.set = nlioc->set;
561 ioc.driver = nlioc->magic;
562 ioc.buf = buf;
563 ret = dhd_ioctl_process(dhd, index, &ioc, buf);
564 if (ret) {
565 WL_TRACE(("dhd_ioctl_process return err %d\n", ret));
566 ret = OSL_ERROR(ret);
567 goto done;
568 }
569
570 done:
571 DHD_OS_WAKE_UNLOCK(dhd);
572 return ret;
573 }
574