Commit | Line | Data |
---|---|---|
5b435de0 AS |
1 | /* |
2 | * Copyright (c) 2010 Broadcom Corporation | |
3 | * | |
4 | * Permission to use, copy, modify, and/or distribute this software for any | |
5 | * purpose with or without fee is hereby granted, provided that the above | |
6 | * copyright notice and this permission notice appear in all copies. | |
7 | * | |
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY | |
11 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION | |
13 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | |
14 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
15 | */ | |
16 | ||
02f77195 JP |
17 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
18 | ||
5b435de0 AS |
19 | #include <linux/init.h> |
20 | #include <linux/kernel.h> | |
21 | #include <linux/kthread.h> | |
22 | #include <linux/slab.h> | |
23 | #include <linux/skbuff.h> | |
24 | #include <linux/netdevice.h> | |
25 | #include <linux/etherdevice.h> | |
26 | #include <linux/mmc/sdio_func.h> | |
27 | #include <linux/random.h> | |
28 | #include <linux/spinlock.h> | |
29 | #include <linux/ethtool.h> | |
30 | #include <linux/fcntl.h> | |
31 | #include <linux/fs.h> | |
32 | #include <linux/uaccess.h> | |
33 | #include <linux/hardirq.h> | |
34 | #include <linux/mutex.h> | |
35 | #include <linux/wait.h> | |
b7a57e76 | 36 | #include <linux/module.h> |
5b435de0 AS |
37 | #include <net/cfg80211.h> |
38 | #include <net/rtnetlink.h> | |
39 | #include <defs.h> | |
40 | #include <brcmu_utils.h> | |
41 | #include <brcmu_wifi.h> | |
42 | ||
43 | #include "dhd.h" | |
44 | #include "dhd_bus.h" | |
45 | #include "dhd_proto.h" | |
46 | #include "dhd_dbg.h" | |
47 | #include "wl_cfg80211.h" | |
5b435de0 AS |
48 | |
49 | MODULE_AUTHOR("Broadcom Corporation"); | |
50 | MODULE_DESCRIPTION("Broadcom 802.11n wireless LAN fullmac driver."); | |
51 | MODULE_SUPPORTED_DEVICE("Broadcom 802.11n WLAN fullmac cards"); | |
52 | MODULE_LICENSE("Dual BSD/GPL"); | |
53 | ||
54 | ||
55 | /* Interface control information */ | |
56 | struct brcmf_if { | |
d08b6a37 | 57 | struct brcmf_pub *drvr; /* back pointer to brcmf_pub */ |
5b435de0 AS |
58 | /* OS/stack specifics */ |
59 | struct net_device *ndev; | |
60 | struct net_device_stats stats; | |
61 | int idx; /* iface idx in dongle */ | |
5b435de0 AS |
62 | u8 mac_addr[ETH_ALEN]; /* assigned MAC address */ |
63 | }; | |
64 | ||
5b435de0 | 65 | /* Error bits */ |
4f96bf19 | 66 | int brcmf_msg_level = BRCMF_ERROR_VAL; |
5b435de0 AS |
67 | module_param(brcmf_msg_level, int, 0); |
68 | ||
d08b6a37 | 69 | int brcmf_ifname2idx(struct brcmf_pub *drvr, char *name) |
5b435de0 AS |
70 | { |
71 | int i = BRCMF_MAX_IFS; | |
72 | struct brcmf_if *ifp; | |
73 | ||
74 | if (name == NULL || *name == '\0') | |
75 | return 0; | |
76 | ||
77 | while (--i > 0) { | |
d08b6a37 | 78 | ifp = drvr->iflist[i]; |
5b435de0 AS |
79 | if (ifp && !strncmp(ifp->ndev->name, name, IFNAMSIZ)) |
80 | break; | |
81 | } | |
82 | ||
83 | brcmf_dbg(TRACE, "return idx %d for \"%s\"\n", i, name); | |
84 | ||
85 | return i; /* default - the primary interface */ | |
86 | } | |
87 | ||
88 | char *brcmf_ifname(struct brcmf_pub *drvr, int ifidx) | |
89 | { | |
5b435de0 AS |
90 | if (ifidx < 0 || ifidx >= BRCMF_MAX_IFS) { |
91 | brcmf_dbg(ERROR, "ifidx %d out of range\n", ifidx); | |
92 | return "<if_bad>"; | |
93 | } | |
94 | ||
d08b6a37 | 95 | if (drvr->iflist[ifidx] == NULL) { |
5b435de0 AS |
96 | brcmf_dbg(ERROR, "null i/f %d\n", ifidx); |
97 | return "<if_null>"; | |
98 | } | |
99 | ||
d08b6a37 FL |
100 | if (drvr->iflist[ifidx]->ndev) |
101 | return drvr->iflist[ifidx]->ndev->name; | |
5b435de0 AS |
102 | |
103 | return "<if_none>"; | |
104 | } | |
105 | ||
106 | static void _brcmf_set_multicast_list(struct work_struct *work) | |
107 | { | |
108 | struct net_device *ndev; | |
109 | struct netdev_hw_addr *ha; | |
b5036243 | 110 | u32 dcmd_value, cnt; |
5b435de0 | 111 | __le32 cnt_le; |
b5036243 | 112 | __le32 dcmd_le_value; |
5b435de0 AS |
113 | |
114 | struct brcmf_dcmd dcmd; | |
115 | char *buf, *bufp; | |
116 | uint buflen; | |
117 | int ret; | |
118 | ||
d08b6a37 | 119 | struct brcmf_pub *drvr = container_of(work, struct brcmf_pub, |
5b435de0 AS |
120 | multicast_work); |
121 | ||
d08b6a37 | 122 | ndev = drvr->iflist[0]->ndev; |
5b435de0 AS |
123 | cnt = netdev_mc_count(ndev); |
124 | ||
125 | /* Determine initial value of allmulti flag */ | |
b5036243 | 126 | dcmd_value = (ndev->flags & IFF_ALLMULTI) ? true : false; |
5b435de0 AS |
127 | |
128 | /* Send down the multicast list first. */ | |
129 | ||
130 | buflen = sizeof("mcast_list") + sizeof(cnt) + (cnt * ETH_ALEN); | |
131 | bufp = buf = kmalloc(buflen, GFP_ATOMIC); | |
132 | if (!bufp) | |
133 | return; | |
134 | ||
135 | strcpy(bufp, "mcast_list"); | |
136 | bufp += strlen("mcast_list") + 1; | |
137 | ||
138 | cnt_le = cpu_to_le32(cnt); | |
139 | memcpy(bufp, &cnt_le, sizeof(cnt)); | |
140 | bufp += sizeof(cnt_le); | |
141 | ||
142 | netdev_for_each_mc_addr(ha, ndev) { | |
143 | if (!cnt) | |
144 | break; | |
145 | memcpy(bufp, ha->addr, ETH_ALEN); | |
146 | bufp += ETH_ALEN; | |
147 | cnt--; | |
148 | } | |
149 | ||
150 | memset(&dcmd, 0, sizeof(dcmd)); | |
151 | dcmd.cmd = BRCMF_C_SET_VAR; | |
152 | dcmd.buf = buf; | |
153 | dcmd.len = buflen; | |
154 | dcmd.set = true; | |
155 | ||
d08b6a37 | 156 | ret = brcmf_proto_dcmd(drvr, 0, &dcmd, dcmd.len); |
5b435de0 AS |
157 | if (ret < 0) { |
158 | brcmf_dbg(ERROR, "%s: set mcast_list failed, cnt %d\n", | |
d08b6a37 | 159 | brcmf_ifname(drvr, 0), cnt); |
b5036243 | 160 | dcmd_value = cnt ? true : dcmd_value; |
5b435de0 AS |
161 | } |
162 | ||
163 | kfree(buf); | |
164 | ||
165 | /* Now send the allmulti setting. This is based on the setting in the | |
166 | * net_device flags, but might be modified above to be turned on if we | |
167 | * were trying to set some addresses and dongle rejected it... | |
168 | */ | |
169 | ||
b5036243 | 170 | buflen = sizeof("allmulti") + sizeof(dcmd_value); |
5b435de0 AS |
171 | buf = kmalloc(buflen, GFP_ATOMIC); |
172 | if (!buf) | |
173 | return; | |
174 | ||
b5036243 | 175 | dcmd_le_value = cpu_to_le32(dcmd_value); |
5b435de0 | 176 | |
53a2277d | 177 | if (!brcmf_c_mkiovar |
b5036243 AS |
178 | ("allmulti", (void *)&dcmd_le_value, |
179 | sizeof(dcmd_le_value), buf, buflen)) { | |
5b435de0 | 180 | brcmf_dbg(ERROR, "%s: mkiovar failed for allmulti, datalen %d buflen %u\n", |
d08b6a37 | 181 | brcmf_ifname(drvr, 0), |
b5036243 | 182 | (int)sizeof(dcmd_value), buflen); |
5b435de0 AS |
183 | kfree(buf); |
184 | return; | |
185 | } | |
186 | ||
187 | memset(&dcmd, 0, sizeof(dcmd)); | |
188 | dcmd.cmd = BRCMF_C_SET_VAR; | |
189 | dcmd.buf = buf; | |
190 | dcmd.len = buflen; | |
191 | dcmd.set = true; | |
192 | ||
d08b6a37 | 193 | ret = brcmf_proto_dcmd(drvr, 0, &dcmd, dcmd.len); |
5b435de0 AS |
194 | if (ret < 0) { |
195 | brcmf_dbg(ERROR, "%s: set allmulti %d failed\n", | |
d08b6a37 | 196 | brcmf_ifname(drvr, 0), |
b5036243 | 197 | le32_to_cpu(dcmd_le_value)); |
5b435de0 AS |
198 | } |
199 | ||
200 | kfree(buf); | |
201 | ||
202 | /* Finally, pick up the PROMISC flag as well, like the NIC | |
203 | driver does */ | |
204 | ||
b5036243 AS |
205 | dcmd_value = (ndev->flags & IFF_PROMISC) ? true : false; |
206 | dcmd_le_value = cpu_to_le32(dcmd_value); | |
5b435de0 AS |
207 | |
208 | memset(&dcmd, 0, sizeof(dcmd)); | |
209 | dcmd.cmd = BRCMF_C_SET_PROMISC; | |
b5036243 AS |
210 | dcmd.buf = &dcmd_le_value; |
211 | dcmd.len = sizeof(dcmd_le_value); | |
5b435de0 AS |
212 | dcmd.set = true; |
213 | ||
d08b6a37 | 214 | ret = brcmf_proto_dcmd(drvr, 0, &dcmd, dcmd.len); |
5b435de0 AS |
215 | if (ret < 0) { |
216 | brcmf_dbg(ERROR, "%s: set promisc %d failed\n", | |
d08b6a37 | 217 | brcmf_ifname(drvr, 0), |
b5036243 | 218 | le32_to_cpu(dcmd_le_value)); |
5b435de0 AS |
219 | } |
220 | } | |
221 | ||
222 | static void | |
223 | _brcmf_set_mac_address(struct work_struct *work) | |
224 | { | |
225 | char buf[32]; | |
226 | struct brcmf_dcmd dcmd; | |
227 | int ret; | |
228 | ||
d08b6a37 | 229 | struct brcmf_pub *drvr = container_of(work, struct brcmf_pub, |
5b435de0 AS |
230 | setmacaddr_work); |
231 | ||
232 | brcmf_dbg(TRACE, "enter\n"); | |
d08b6a37 | 233 | if (!brcmf_c_mkiovar("cur_etheraddr", (char *)drvr->macvalue, |
5b435de0 AS |
234 | ETH_ALEN, buf, 32)) { |
235 | brcmf_dbg(ERROR, "%s: mkiovar failed for cur_etheraddr\n", | |
d08b6a37 | 236 | brcmf_ifname(drvr, 0)); |
5b435de0 AS |
237 | return; |
238 | } | |
239 | memset(&dcmd, 0, sizeof(dcmd)); | |
240 | dcmd.cmd = BRCMF_C_SET_VAR; | |
241 | dcmd.buf = buf; | |
242 | dcmd.len = 32; | |
243 | dcmd.set = true; | |
244 | ||
d08b6a37 | 245 | ret = brcmf_proto_dcmd(drvr, 0, &dcmd, dcmd.len); |
5b435de0 AS |
246 | if (ret < 0) |
247 | brcmf_dbg(ERROR, "%s: set cur_etheraddr failed\n", | |
d08b6a37 | 248 | brcmf_ifname(drvr, 0)); |
5b435de0 | 249 | else |
d08b6a37 FL |
250 | memcpy(drvr->iflist[0]->ndev->dev_addr, |
251 | drvr->macvalue, ETH_ALEN); | |
5b435de0 AS |
252 | |
253 | return; | |
254 | } | |
255 | ||
256 | static int brcmf_netdev_set_mac_address(struct net_device *ndev, void *addr) | |
257 | { | |
e1b83586 | 258 | struct brcmf_if *ifp = netdev_priv(ndev); |
d08b6a37 | 259 | struct brcmf_pub *drvr = ifp->drvr; |
5b435de0 | 260 | struct sockaddr *sa = (struct sockaddr *)addr; |
5b435de0 | 261 | |
d08b6a37 FL |
262 | memcpy(&drvr->macvalue, sa->sa_data, ETH_ALEN); |
263 | schedule_work(&drvr->setmacaddr_work); | |
5b435de0 AS |
264 | return 0; |
265 | } | |
266 | ||
267 | static void brcmf_netdev_set_multicast_list(struct net_device *ndev) | |
268 | { | |
e1b83586 | 269 | struct brcmf_if *ifp = netdev_priv(ndev); |
d08b6a37 | 270 | struct brcmf_pub *drvr = ifp->drvr; |
5b435de0 | 271 | |
d08b6a37 | 272 | schedule_work(&drvr->multicast_work); |
5b435de0 AS |
273 | } |
274 | ||
275 | int brcmf_sendpkt(struct brcmf_pub *drvr, int ifidx, struct sk_buff *pktbuf) | |
276 | { | |
5b435de0 | 277 | /* Reject if down */ |
3fb1d8d2 | 278 | if (!drvr->bus_if->drvr_up || (drvr->bus_if->state == BRCMF_BUS_DOWN)) |
5b435de0 AS |
279 | return -ENODEV; |
280 | ||
281 | /* Update multicast statistic */ | |
282 | if (pktbuf->len >= ETH_ALEN) { | |
283 | u8 *pktdata = (u8 *) (pktbuf->data); | |
284 | struct ethhdr *eh = (struct ethhdr *)pktdata; | |
285 | ||
286 | if (is_multicast_ether_addr(eh->h_dest)) | |
287 | drvr->tx_multicast++; | |
288 | if (ntohs(eh->h_proto) == ETH_P_PAE) | |
d08b6a37 | 289 | atomic_inc(&drvr->pend_8021x_cnt); |
5b435de0 AS |
290 | } |
291 | ||
292 | /* If the protocol uses a data header, apply it */ | |
293 | brcmf_proto_hdrpush(drvr, ifidx, pktbuf); | |
294 | ||
295 | /* Use bus module to send data frame */ | |
b9692d17 | 296 | return drvr->bus_if->brcmf_bus_txdata(drvr->dev, pktbuf); |
5b435de0 AS |
297 | } |
298 | ||
299 | static int brcmf_netdev_start_xmit(struct sk_buff *skb, struct net_device *ndev) | |
300 | { | |
301 | int ret; | |
e1b83586 | 302 | struct brcmf_if *ifp = netdev_priv(ndev); |
d08b6a37 | 303 | struct brcmf_pub *drvr = ifp->drvr; |
5b435de0 AS |
304 | |
305 | brcmf_dbg(TRACE, "Enter\n"); | |
306 | ||
307 | /* Reject if down */ | |
3fb1d8d2 | 308 | if (!drvr->bus_if->drvr_up || |
d08b6a37 | 309 | (drvr->bus_if->state == BRCMF_BUS_DOWN)) { |
3fb1d8d2 FL |
310 | brcmf_dbg(ERROR, "xmit rejected drvup=%d state=%d\n", |
311 | drvr->bus_if->drvr_up, | |
d08b6a37 | 312 | drvr->bus_if->state); |
5b435de0 AS |
313 | netif_stop_queue(ndev); |
314 | return -ENODEV; | |
315 | } | |
316 | ||
d08b6a37 | 317 | if (!drvr->iflist[ifp->idx]) { |
e1b83586 | 318 | brcmf_dbg(ERROR, "bad ifidx %d\n", ifp->idx); |
5b435de0 AS |
319 | netif_stop_queue(ndev); |
320 | return -ENODEV; | |
321 | } | |
322 | ||
323 | /* Make sure there's enough room for any header */ | |
d08b6a37 | 324 | if (skb_headroom(skb) < drvr->hdrlen) { |
5b435de0 AS |
325 | struct sk_buff *skb2; |
326 | ||
327 | brcmf_dbg(INFO, "%s: insufficient headroom\n", | |
d08b6a37 | 328 | brcmf_ifname(drvr, ifp->idx)); |
9c1a043a | 329 | drvr->bus_if->tx_realloc++; |
d08b6a37 | 330 | skb2 = skb_realloc_headroom(skb, drvr->hdrlen); |
5b435de0 AS |
331 | dev_kfree_skb(skb); |
332 | skb = skb2; | |
333 | if (skb == NULL) { | |
334 | brcmf_dbg(ERROR, "%s: skb_realloc_headroom failed\n", | |
d08b6a37 | 335 | brcmf_ifname(drvr, ifp->idx)); |
5b435de0 AS |
336 | ret = -ENOMEM; |
337 | goto done; | |
338 | } | |
339 | } | |
340 | ||
d08b6a37 | 341 | ret = brcmf_sendpkt(drvr, ifp->idx, skb); |
5b435de0 AS |
342 | |
343 | done: | |
344 | if (ret) | |
719f2733 | 345 | drvr->bus_if->dstats.tx_dropped++; |
5b435de0 | 346 | else |
719f2733 | 347 | drvr->bus_if->dstats.tx_packets++; |
5b435de0 AS |
348 | |
349 | /* Return ok: we always eat the packet */ | |
350 | return 0; | |
351 | } | |
352 | ||
2b459056 | 353 | void brcmf_txflowcontrol(struct device *dev, int ifidx, bool state) |
5b435de0 AS |
354 | { |
355 | struct net_device *ndev; | |
2b459056 FL |
356 | struct brcmf_bus *bus_if = dev_get_drvdata(dev); |
357 | struct brcmf_pub *drvr = bus_if->drvr; | |
5b435de0 AS |
358 | |
359 | brcmf_dbg(TRACE, "Enter\n"); | |
360 | ||
d08b6a37 | 361 | ndev = drvr->iflist[ifidx]->ndev; |
5b435de0 AS |
362 | if (state == ON) |
363 | netif_stop_queue(ndev); | |
364 | else | |
365 | netif_wake_queue(ndev); | |
366 | } | |
367 | ||
d08b6a37 | 368 | static int brcmf_host_event(struct brcmf_pub *drvr, int *ifidx, |
5b435de0 AS |
369 | void *pktdata, struct brcmf_event_msg *event, |
370 | void **data) | |
371 | { | |
372 | int bcmerror = 0; | |
373 | ||
d08b6a37 | 374 | bcmerror = brcmf_c_host_event(drvr, ifidx, pktdata, event, data); |
5b435de0 AS |
375 | if (bcmerror != 0) |
376 | return bcmerror; | |
377 | ||
d08b6a37 FL |
378 | if (drvr->iflist[*ifidx]->ndev) |
379 | brcmf_cfg80211_event(drvr->iflist[*ifidx]->ndev, | |
5b435de0 AS |
380 | event, *data); |
381 | ||
382 | return bcmerror; | |
383 | } | |
384 | ||
228bb43d | 385 | void brcmf_rx_frame(struct device *dev, int ifidx, |
0b45bf74 | 386 | struct sk_buff_head *skb_list) |
5b435de0 | 387 | { |
5b435de0 AS |
388 | unsigned char *eth; |
389 | uint len; | |
390 | void *data; | |
0b45bf74 | 391 | struct sk_buff *skb, *pnext; |
5b435de0 AS |
392 | struct brcmf_if *ifp; |
393 | struct brcmf_event_msg event; | |
228bb43d FL |
394 | struct brcmf_bus *bus_if = dev_get_drvdata(dev); |
395 | struct brcmf_pub *drvr = bus_if->drvr; | |
5b435de0 AS |
396 | |
397 | brcmf_dbg(TRACE, "Enter\n"); | |
398 | ||
0b45bf74 AS |
399 | skb_queue_walk_safe(skb_list, skb, pnext) { |
400 | skb_unlink(skb, skb_list); | |
5b435de0 AS |
401 | |
402 | /* Get the protocol, maintain skb around eth_type_trans() | |
403 | * The main reason for this hack is for the limitation of | |
404 | * Linux 2.4 where 'eth_type_trans' uses the | |
405 | * 'net->hard_header_len' | |
406 | * to perform skb_pull inside vs ETH_HLEN. Since to avoid | |
407 | * coping of the packet coming from the network stack to add | |
408 | * BDC, Hardware header etc, during network interface | |
409 | * registration | |
410 | * we set the 'net->hard_header_len' to ETH_HLEN + extra space | |
411 | * required | |
412 | * for BDC, Hardware header etc. and not just the ETH_HLEN | |
413 | */ | |
414 | eth = skb->data; | |
415 | len = skb->len; | |
416 | ||
d08b6a37 | 417 | ifp = drvr->iflist[ifidx]; |
5b435de0 | 418 | if (ifp == NULL) |
d08b6a37 | 419 | ifp = drvr->iflist[0]; |
5b435de0 | 420 | |
0c094c77 FL |
421 | if (!ifp || !ifp->ndev || |
422 | ifp->ndev->reg_state != NETREG_REGISTERED) { | |
423 | brcmu_pkt_buf_free_skb(skb); | |
424 | continue; | |
425 | } | |
426 | ||
5b435de0 AS |
427 | skb->dev = ifp->ndev; |
428 | skb->protocol = eth_type_trans(skb, skb->dev); | |
429 | ||
430 | if (skb->pkt_type == PACKET_MULTICAST) | |
719f2733 | 431 | bus_if->dstats.multicast++; |
5b435de0 AS |
432 | |
433 | skb->data = eth; | |
434 | skb->len = len; | |
435 | ||
436 | /* Strip header, count, deliver upward */ | |
437 | skb_pull(skb, ETH_HLEN); | |
438 | ||
439 | /* Process special event packets and then discard them */ | |
440 | if (ntohs(skb->protocol) == ETH_P_LINK_CTL) | |
d08b6a37 | 441 | brcmf_host_event(drvr, &ifidx, |
5b435de0 AS |
442 | skb_mac_header(skb), |
443 | &event, &data); | |
444 | ||
d08b6a37 FL |
445 | if (drvr->iflist[ifidx]) { |
446 | ifp = drvr->iflist[ifidx]; | |
5b435de0 | 447 | ifp->ndev->last_rx = jiffies; |
d1a5b6fb | 448 | } |
5b435de0 | 449 | |
719f2733 FL |
450 | bus_if->dstats.rx_bytes += skb->len; |
451 | bus_if->dstats.rx_packets++; /* Local count */ | |
5b435de0 AS |
452 | |
453 | if (in_interrupt()) | |
454 | netif_rx(skb); | |
455 | else | |
456 | /* If the receive is not processed inside an ISR, | |
457 | * the softirqd must be woken explicitly to service | |
458 | * the NET_RX_SOFTIRQ. In 2.6 kernels, this is handled | |
459 | * by netif_rx_ni(), but in earlier kernels, we need | |
460 | * to do it manually. | |
461 | */ | |
462 | netif_rx_ni(skb); | |
463 | } | |
464 | } | |
465 | ||
c995788f | 466 | void brcmf_txcomplete(struct device *dev, struct sk_buff *txp, bool success) |
5b435de0 AS |
467 | { |
468 | uint ifidx; | |
5b435de0 AS |
469 | struct ethhdr *eh; |
470 | u16 type; | |
c995788f FL |
471 | struct brcmf_bus *bus_if = dev_get_drvdata(dev); |
472 | struct brcmf_pub *drvr = bus_if->drvr; | |
5b435de0 | 473 | |
d5625ee6 | 474 | brcmf_proto_hdrpull(dev, &ifidx, txp); |
5b435de0 AS |
475 | |
476 | eh = (struct ethhdr *)(txp->data); | |
477 | type = ntohs(eh->h_proto); | |
478 | ||
479 | if (type == ETH_P_PAE) | |
d08b6a37 | 480 | atomic_dec(&drvr->pend_8021x_cnt); |
5b435de0 AS |
481 | |
482 | } | |
483 | ||
484 | static struct net_device_stats *brcmf_netdev_get_stats(struct net_device *ndev) | |
485 | { | |
e1b83586 | 486 | struct brcmf_if *ifp = netdev_priv(ndev); |
719f2733 | 487 | struct brcmf_bus *bus_if = ifp->drvr->bus_if; |
5b435de0 AS |
488 | |
489 | brcmf_dbg(TRACE, "Enter\n"); | |
490 | ||
5b435de0 | 491 | /* Copy dongle stats to net device stats */ |
719f2733 FL |
492 | ifp->stats.rx_packets = bus_if->dstats.rx_packets; |
493 | ifp->stats.tx_packets = bus_if->dstats.tx_packets; | |
494 | ifp->stats.rx_bytes = bus_if->dstats.rx_bytes; | |
495 | ifp->stats.tx_bytes = bus_if->dstats.tx_bytes; | |
496 | ifp->stats.rx_errors = bus_if->dstats.rx_errors; | |
497 | ifp->stats.tx_errors = bus_if->dstats.tx_errors; | |
498 | ifp->stats.rx_dropped = bus_if->dstats.rx_dropped; | |
499 | ifp->stats.tx_dropped = bus_if->dstats.tx_dropped; | |
500 | ifp->stats.multicast = bus_if->dstats.multicast; | |
5b435de0 AS |
501 | |
502 | return &ifp->stats; | |
503 | } | |
504 | ||
505 | /* Retrieve current toe component enables, which are kept | |
506 | as a bitmap in toe_ol iovar */ | |
d08b6a37 | 507 | static int brcmf_toe_get(struct brcmf_pub *drvr, int ifidx, u32 *toe_ol) |
5b435de0 AS |
508 | { |
509 | struct brcmf_dcmd dcmd; | |
1062904c | 510 | __le32 toe_le; |
5b435de0 AS |
511 | char buf[32]; |
512 | int ret; | |
513 | ||
514 | memset(&dcmd, 0, sizeof(dcmd)); | |
515 | ||
516 | dcmd.cmd = BRCMF_C_GET_VAR; | |
517 | dcmd.buf = buf; | |
518 | dcmd.len = (uint) sizeof(buf); | |
519 | dcmd.set = false; | |
520 | ||
521 | strcpy(buf, "toe_ol"); | |
d08b6a37 | 522 | ret = brcmf_proto_dcmd(drvr, ifidx, &dcmd, dcmd.len); |
5b435de0 AS |
523 | if (ret < 0) { |
524 | /* Check for older dongle image that doesn't support toe_ol */ | |
525 | if (ret == -EIO) { | |
526 | brcmf_dbg(ERROR, "%s: toe not supported by device\n", | |
d08b6a37 | 527 | brcmf_ifname(drvr, ifidx)); |
5b435de0 AS |
528 | return -EOPNOTSUPP; |
529 | } | |
530 | ||
531 | brcmf_dbg(INFO, "%s: could not get toe_ol: ret=%d\n", | |
d08b6a37 | 532 | brcmf_ifname(drvr, ifidx), ret); |
5b435de0 AS |
533 | return ret; |
534 | } | |
535 | ||
1062904c AS |
536 | memcpy(&toe_le, buf, sizeof(u32)); |
537 | *toe_ol = le32_to_cpu(toe_le); | |
5b435de0 AS |
538 | return 0; |
539 | } | |
540 | ||
541 | /* Set current toe component enables in toe_ol iovar, | |
542 | and set toe global enable iovar */ | |
d08b6a37 | 543 | static int brcmf_toe_set(struct brcmf_pub *drvr, int ifidx, u32 toe_ol) |
5b435de0 AS |
544 | { |
545 | struct brcmf_dcmd dcmd; | |
546 | char buf[32]; | |
1062904c AS |
547 | int ret; |
548 | __le32 toe_le = cpu_to_le32(toe_ol); | |
5b435de0 AS |
549 | |
550 | memset(&dcmd, 0, sizeof(dcmd)); | |
551 | ||
552 | dcmd.cmd = BRCMF_C_SET_VAR; | |
553 | dcmd.buf = buf; | |
554 | dcmd.len = (uint) sizeof(buf); | |
555 | dcmd.set = true; | |
556 | ||
557 | /* Set toe_ol as requested */ | |
5b435de0 | 558 | strcpy(buf, "toe_ol"); |
1062904c | 559 | memcpy(&buf[sizeof("toe_ol")], &toe_le, sizeof(u32)); |
5b435de0 | 560 | |
d08b6a37 | 561 | ret = brcmf_proto_dcmd(drvr, ifidx, &dcmd, dcmd.len); |
5b435de0 AS |
562 | if (ret < 0) { |
563 | brcmf_dbg(ERROR, "%s: could not set toe_ol: ret=%d\n", | |
d08b6a37 | 564 | brcmf_ifname(drvr, ifidx), ret); |
5b435de0 AS |
565 | return ret; |
566 | } | |
567 | ||
568 | /* Enable toe globally only if any components are enabled. */ | |
1062904c | 569 | toe_le = cpu_to_le32(toe_ol != 0); |
5b435de0 AS |
570 | |
571 | strcpy(buf, "toe"); | |
1062904c | 572 | memcpy(&buf[sizeof("toe")], &toe_le, sizeof(u32)); |
5b435de0 | 573 | |
d08b6a37 | 574 | ret = brcmf_proto_dcmd(drvr, ifidx, &dcmd, dcmd.len); |
5b435de0 AS |
575 | if (ret < 0) { |
576 | brcmf_dbg(ERROR, "%s: could not set toe: ret=%d\n", | |
d08b6a37 | 577 | brcmf_ifname(drvr, ifidx), ret); |
5b435de0 AS |
578 | return ret; |
579 | } | |
580 | ||
581 | return 0; | |
582 | } | |
583 | ||
584 | static void brcmf_ethtool_get_drvinfo(struct net_device *ndev, | |
585 | struct ethtool_drvinfo *info) | |
586 | { | |
e1b83586 | 587 | struct brcmf_if *ifp = netdev_priv(ndev); |
d08b6a37 | 588 | struct brcmf_pub *drvr = ifp->drvr; |
5b435de0 AS |
589 | |
590 | sprintf(info->driver, KBUILD_MODNAME); | |
d08b6a37 FL |
591 | sprintf(info->version, "%lu", drvr->drv_version); |
592 | sprintf(info->bus_info, "%s", dev_name(drvr->dev)); | |
5b435de0 AS |
593 | } |
594 | ||
3eb1fa7e SH |
595 | static const struct ethtool_ops brcmf_ethtool_ops = { |
596 | .get_drvinfo = brcmf_ethtool_get_drvinfo, | |
5b435de0 AS |
597 | }; |
598 | ||
d08b6a37 | 599 | static int brcmf_ethtool(struct brcmf_pub *drvr, void __user *uaddr) |
5b435de0 AS |
600 | { |
601 | struct ethtool_drvinfo info; | |
602 | char drvname[sizeof(info.driver)]; | |
603 | u32 cmd; | |
604 | struct ethtool_value edata; | |
605 | u32 toe_cmpnt, csum_dir; | |
606 | int ret; | |
607 | ||
608 | brcmf_dbg(TRACE, "Enter\n"); | |
609 | ||
610 | /* all ethtool calls start with a cmd word */ | |
611 | if (copy_from_user(&cmd, uaddr, sizeof(u32))) | |
612 | return -EFAULT; | |
613 | ||
614 | switch (cmd) { | |
615 | case ETHTOOL_GDRVINFO: | |
616 | /* Copy out any request driver name */ | |
617 | if (copy_from_user(&info, uaddr, sizeof(info))) | |
618 | return -EFAULT; | |
619 | strncpy(drvname, info.driver, sizeof(info.driver)); | |
620 | drvname[sizeof(info.driver) - 1] = '\0'; | |
621 | ||
622 | /* clear struct for return */ | |
623 | memset(&info, 0, sizeof(info)); | |
624 | info.cmd = cmd; | |
625 | ||
626 | /* if requested, identify ourselves */ | |
627 | if (strcmp(drvname, "?dhd") == 0) { | |
628 | sprintf(info.driver, "dhd"); | |
629 | strcpy(info.version, BRCMF_VERSION_STR); | |
630 | } | |
631 | ||
632 | /* otherwise, require dongle to be up */ | |
3fb1d8d2 | 633 | else if (!drvr->bus_if->drvr_up) { |
5b435de0 AS |
634 | brcmf_dbg(ERROR, "dongle is not up\n"); |
635 | return -ENODEV; | |
636 | } | |
637 | ||
638 | /* finally, report dongle driver type */ | |
d08b6a37 | 639 | else if (drvr->iswl) |
5b435de0 AS |
640 | sprintf(info.driver, "wl"); |
641 | else | |
642 | sprintf(info.driver, "xx"); | |
643 | ||
d08b6a37 | 644 | sprintf(info.version, "%lu", drvr->drv_version); |
5b435de0 AS |
645 | if (copy_to_user(uaddr, &info, sizeof(info))) |
646 | return -EFAULT; | |
647 | brcmf_dbg(CTL, "given %*s, returning %s\n", | |
648 | (int)sizeof(drvname), drvname, info.driver); | |
649 | break; | |
650 | ||
651 | /* Get toe offload components from dongle */ | |
652 | case ETHTOOL_GRXCSUM: | |
653 | case ETHTOOL_GTXCSUM: | |
d08b6a37 | 654 | ret = brcmf_toe_get(drvr, 0, &toe_cmpnt); |
5b435de0 AS |
655 | if (ret < 0) |
656 | return ret; | |
657 | ||
658 | csum_dir = | |
659 | (cmd == ETHTOOL_GTXCSUM) ? TOE_TX_CSUM_OL : TOE_RX_CSUM_OL; | |
660 | ||
661 | edata.cmd = cmd; | |
662 | edata.data = (toe_cmpnt & csum_dir) ? 1 : 0; | |
663 | ||
664 | if (copy_to_user(uaddr, &edata, sizeof(edata))) | |
665 | return -EFAULT; | |
666 | break; | |
667 | ||
668 | /* Set toe offload components in dongle */ | |
669 | case ETHTOOL_SRXCSUM: | |
670 | case ETHTOOL_STXCSUM: | |
671 | if (copy_from_user(&edata, uaddr, sizeof(edata))) | |
672 | return -EFAULT; | |
673 | ||
674 | /* Read the current settings, update and write back */ | |
d08b6a37 | 675 | ret = brcmf_toe_get(drvr, 0, &toe_cmpnt); |
5b435de0 AS |
676 | if (ret < 0) |
677 | return ret; | |
678 | ||
679 | csum_dir = | |
680 | (cmd == ETHTOOL_STXCSUM) ? TOE_TX_CSUM_OL : TOE_RX_CSUM_OL; | |
681 | ||
682 | if (edata.data != 0) | |
683 | toe_cmpnt |= csum_dir; | |
684 | else | |
685 | toe_cmpnt &= ~csum_dir; | |
686 | ||
d08b6a37 | 687 | ret = brcmf_toe_set(drvr, 0, toe_cmpnt); |
5b435de0 AS |
688 | if (ret < 0) |
689 | return ret; | |
690 | ||
691 | /* If setting TX checksum mode, tell Linux the new mode */ | |
692 | if (cmd == ETHTOOL_STXCSUM) { | |
693 | if (edata.data) | |
d08b6a37 | 694 | drvr->iflist[0]->ndev->features |= |
5b435de0 AS |
695 | NETIF_F_IP_CSUM; |
696 | else | |
d08b6a37 | 697 | drvr->iflist[0]->ndev->features &= |
5b435de0 AS |
698 | ~NETIF_F_IP_CSUM; |
699 | } | |
700 | ||
701 | break; | |
702 | ||
703 | default: | |
704 | return -EOPNOTSUPP; | |
705 | } | |
706 | ||
707 | return 0; | |
708 | } | |
709 | ||
710 | static int brcmf_netdev_ioctl_entry(struct net_device *ndev, struct ifreq *ifr, | |
711 | int cmd) | |
712 | { | |
e1b83586 | 713 | struct brcmf_if *ifp = netdev_priv(ndev); |
d08b6a37 | 714 | struct brcmf_pub *drvr = ifp->drvr; |
5b435de0 | 715 | |
e1b83586 | 716 | brcmf_dbg(TRACE, "ifidx %d, cmd 0x%04x\n", ifp->idx, cmd); |
5b435de0 | 717 | |
d08b6a37 | 718 | if (!drvr->iflist[ifp->idx]) |
5b435de0 AS |
719 | return -1; |
720 | ||
721 | if (cmd == SIOCETHTOOL) | |
d08b6a37 | 722 | return brcmf_ethtool(drvr, ifr->ifr_data); |
5b435de0 AS |
723 | |
724 | return -EOPNOTSUPP; | |
725 | } | |
726 | ||
727 | /* called only from within this driver. Sends a command to the dongle. */ | |
728 | s32 brcmf_exec_dcmd(struct net_device *ndev, u32 cmd, void *arg, u32 len) | |
729 | { | |
730 | struct brcmf_dcmd dcmd; | |
731 | s32 err = 0; | |
732 | int buflen = 0; | |
733 | bool is_set_key_cmd; | |
e1b83586 | 734 | struct brcmf_if *ifp = netdev_priv(ndev); |
d08b6a37 | 735 | struct brcmf_pub *drvr = ifp->drvr; |
5b435de0 AS |
736 | |
737 | memset(&dcmd, 0, sizeof(dcmd)); | |
738 | dcmd.cmd = cmd; | |
739 | dcmd.buf = arg; | |
740 | dcmd.len = len; | |
741 | ||
5b435de0 AS |
742 | if (dcmd.buf != NULL) |
743 | buflen = min_t(uint, dcmd.len, BRCMF_DCMD_MAXLEN); | |
744 | ||
745 | /* send to dongle (must be up, and wl) */ | |
d08b6a37 | 746 | if ((drvr->bus_if->state != BRCMF_BUS_DATA)) { |
5b435de0 AS |
747 | brcmf_dbg(ERROR, "DONGLE_DOWN\n"); |
748 | err = -EIO; | |
749 | goto done; | |
750 | } | |
751 | ||
d08b6a37 | 752 | if (!drvr->iswl) { |
5b435de0 AS |
753 | err = -EIO; |
754 | goto done; | |
755 | } | |
756 | ||
757 | /* | |
758 | * Intercept BRCMF_C_SET_KEY CMD - serialize M4 send and | |
759 | * set key CMD to prevent M4 encryption. | |
760 | */ | |
761 | is_set_key_cmd = ((dcmd.cmd == BRCMF_C_SET_KEY) || | |
762 | ((dcmd.cmd == BRCMF_C_SET_VAR) && | |
763 | !(strncmp("wsec_key", dcmd.buf, 9))) || | |
764 | ((dcmd.cmd == BRCMF_C_SET_VAR) && | |
765 | !(strncmp("bsscfg:wsec_key", dcmd.buf, 15)))); | |
766 | if (is_set_key_cmd) | |
767 | brcmf_netdev_wait_pend8021x(ndev); | |
768 | ||
d08b6a37 | 769 | err = brcmf_proto_dcmd(drvr, ifp->idx, &dcmd, buflen); |
5b435de0 AS |
770 | |
771 | done: | |
772 | if (err > 0) | |
773 | err = 0; | |
774 | ||
775 | return err; | |
776 | } | |
777 | ||
778 | static int brcmf_netdev_stop(struct net_device *ndev) | |
779 | { | |
e1b83586 | 780 | struct brcmf_if *ifp = netdev_priv(ndev); |
d08b6a37 | 781 | struct brcmf_pub *drvr = ifp->drvr; |
5b435de0 AS |
782 | |
783 | brcmf_dbg(TRACE, "Enter\n"); | |
784 | brcmf_cfg80211_down(drvr->config); | |
3fb1d8d2 | 785 | if (drvr->bus_if->drvr_up == 0) |
5b435de0 AS |
786 | return 0; |
787 | ||
788 | /* Set state and stop OS transmissions */ | |
57adc1fc | 789 | drvr->bus_if->drvr_up = false; |
5b435de0 AS |
790 | netif_stop_queue(ndev); |
791 | ||
792 | return 0; | |
793 | } | |
794 | ||
795 | static int brcmf_netdev_open(struct net_device *ndev) | |
796 | { | |
e1b83586 | 797 | struct brcmf_if *ifp = netdev_priv(ndev); |
d08b6a37 | 798 | struct brcmf_pub *drvr = ifp->drvr; |
5b435de0 | 799 | u32 toe_ol; |
5b435de0 AS |
800 | s32 ret = 0; |
801 | ||
e1b83586 | 802 | brcmf_dbg(TRACE, "ifidx %d\n", ifp->idx); |
5b435de0 | 803 | |
e1b83586 | 804 | if (ifp->idx == 0) { /* do it only for primary eth0 */ |
5b435de0 | 805 | /* try to bring up bus */ |
ed683c98 | 806 | ret = brcmf_bus_start(drvr->dev); |
5b435de0 AS |
807 | if (ret != 0) { |
808 | brcmf_dbg(ERROR, "failed with code %d\n", ret); | |
809 | return -1; | |
810 | } | |
d08b6a37 | 811 | atomic_set(&drvr->pend_8021x_cnt, 0); |
5b435de0 | 812 | |
d08b6a37 | 813 | memcpy(ndev->dev_addr, drvr->mac, ETH_ALEN); |
5b435de0 AS |
814 | |
815 | /* Get current TOE mode from dongle */ | |
d08b6a37 | 816 | if (brcmf_toe_get(drvr, ifp->idx, &toe_ol) >= 0 |
5b435de0 | 817 | && (toe_ol & TOE_TX_CSUM_OL) != 0) |
d08b6a37 | 818 | drvr->iflist[ifp->idx]->ndev->features |= |
5b435de0 AS |
819 | NETIF_F_IP_CSUM; |
820 | else | |
d08b6a37 | 821 | drvr->iflist[ifp->idx]->ndev->features &= |
5b435de0 AS |
822 | ~NETIF_F_IP_CSUM; |
823 | } | |
824 | /* Allow transmit calls */ | |
825 | netif_start_queue(ndev); | |
57adc1fc | 826 | drvr->bus_if->drvr_up = true; |
d08b6a37 | 827 | if (brcmf_cfg80211_up(drvr->config)) { |
5b435de0 AS |
828 | brcmf_dbg(ERROR, "failed to bring up cfg80211\n"); |
829 | return -1; | |
830 | } | |
831 | ||
832 | return ret; | |
833 | } | |
834 | ||
dfded557 FL |
835 | static const struct net_device_ops brcmf_netdev_ops_pri = { |
836 | .ndo_open = brcmf_netdev_open, | |
837 | .ndo_stop = brcmf_netdev_stop, | |
838 | .ndo_get_stats = brcmf_netdev_get_stats, | |
839 | .ndo_do_ioctl = brcmf_netdev_ioctl_entry, | |
840 | .ndo_start_xmit = brcmf_netdev_start_xmit, | |
841 | .ndo_set_mac_address = brcmf_netdev_set_mac_address, | |
842 | .ndo_set_rx_mode = brcmf_netdev_set_multicast_list | |
843 | }; | |
844 | ||
5b435de0 | 845 | int |
55a63bcc | 846 | brcmf_add_if(struct device *dev, int ifidx, char *name, u8 *mac_addr) |
5b435de0 AS |
847 | { |
848 | struct brcmf_if *ifp; | |
e1b83586 | 849 | struct net_device *ndev; |
55a63bcc FL |
850 | struct brcmf_bus *bus_if = dev_get_drvdata(dev); |
851 | struct brcmf_pub *drvr = bus_if->drvr; | |
5b435de0 | 852 | |
15d45b6f | 853 | brcmf_dbg(TRACE, "idx %d\n", ifidx); |
5b435de0 | 854 | |
d08b6a37 | 855 | ifp = drvr->iflist[ifidx]; |
e1b83586 FL |
856 | /* |
857 | * Delete the existing interface before overwriting it | |
858 | * in case we missed the BRCMF_E_IF_DEL event. | |
859 | */ | |
860 | if (ifp) { | |
861 | brcmf_dbg(ERROR, "ERROR: netdev:%s already exists, try free & unregister\n", | |
862 | ifp->ndev->name); | |
863 | netif_stop_queue(ifp->ndev); | |
864 | unregister_netdev(ifp->ndev); | |
865 | free_netdev(ifp->ndev); | |
d08b6a37 | 866 | drvr->iflist[ifidx] = NULL; |
15d45b6f | 867 | } |
5b435de0 | 868 | |
15d45b6f | 869 | /* Allocate netdev, including space for private structure */ |
e1b83586 FL |
870 | ndev = alloc_netdev(sizeof(struct brcmf_if), name, ether_setup); |
871 | if (!ndev) { | |
15d45b6f | 872 | brcmf_dbg(ERROR, "OOM - alloc_netdev\n"); |
e1b83586 | 873 | return -ENOMEM; |
15d45b6f | 874 | } |
5b435de0 | 875 | |
e1b83586 FL |
876 | ifp = netdev_priv(ndev); |
877 | ifp->ndev = ndev; | |
d08b6a37 FL |
878 | ifp->drvr = drvr; |
879 | drvr->iflist[ifidx] = ifp; | |
e1b83586 | 880 | ifp->idx = ifidx; |
15d45b6f FL |
881 | if (mac_addr != NULL) |
882 | memcpy(&ifp->mac_addr, mac_addr, ETH_ALEN); | |
5b435de0 | 883 | |
d08b6a37 | 884 | if (brcmf_net_attach(drvr, ifp->idx)) { |
15d45b6f FL |
885 | brcmf_dbg(ERROR, "brcmf_net_attach failed"); |
886 | free_netdev(ifp->ndev); | |
d08b6a37 | 887 | drvr->iflist[ifidx] = NULL; |
e1b83586 | 888 | return -EOPNOTSUPP; |
15d45b6f | 889 | } |
5b435de0 | 890 | |
15d45b6f FL |
891 | brcmf_dbg(TRACE, " ==== pid:%x, net_device for if:%s created ===\n", |
892 | current->pid, ifp->ndev->name); | |
5b435de0 AS |
893 | |
894 | return 0; | |
895 | } | |
896 | ||
d08b6a37 | 897 | void brcmf_del_if(struct brcmf_pub *drvr, int ifidx) |
5b435de0 AS |
898 | { |
899 | struct brcmf_if *ifp; | |
900 | ||
901 | brcmf_dbg(TRACE, "idx %d\n", ifidx); | |
902 | ||
d08b6a37 | 903 | ifp = drvr->iflist[ifidx]; |
5b435de0 AS |
904 | if (!ifp) { |
905 | brcmf_dbg(ERROR, "Null interface\n"); | |
906 | return; | |
907 | } | |
dfded557 FL |
908 | if (ifp->ndev) { |
909 | if (ifidx == 0) { | |
910 | if (ifp->ndev->netdev_ops == &brcmf_netdev_ops_pri) { | |
911 | rtnl_lock(); | |
912 | brcmf_netdev_stop(ifp->ndev); | |
913 | rtnl_unlock(); | |
914 | } | |
915 | } else { | |
916 | netif_stop_queue(ifp->ndev); | |
917 | } | |
918 | ||
5b435de0 | 919 | unregister_netdev(ifp->ndev); |
d08b6a37 | 920 | drvr->iflist[ifidx] = NULL; |
dfded557 | 921 | if (ifidx == 0) |
d08b6a37 | 922 | brcmf_cfg80211_detach(drvr->config); |
dfded557 | 923 | free_netdev(ifp->ndev); |
5b435de0 AS |
924 | } |
925 | } | |
926 | ||
2447ffb0 | 927 | int brcmf_attach(uint bus_hdrlen, struct device *dev) |
5b435de0 | 928 | { |
d08b6a37 | 929 | struct brcmf_pub *drvr = NULL; |
712ac5b3 | 930 | int ret = 0; |
5b435de0 AS |
931 | |
932 | brcmf_dbg(TRACE, "Enter\n"); | |
933 | ||
5b435de0 | 934 | /* Allocate primary brcmf_info */ |
d08b6a37 FL |
935 | drvr = kzalloc(sizeof(struct brcmf_pub), GFP_ATOMIC); |
936 | if (!drvr) | |
712ac5b3 | 937 | return -ENOMEM; |
5b435de0 | 938 | |
d08b6a37 | 939 | mutex_init(&drvr->proto_block); |
5b435de0 AS |
940 | |
941 | /* Link to bus module */ | |
d08b6a37 FL |
942 | drvr->hdrlen = bus_hdrlen; |
943 | drvr->bus_if = dev_get_drvdata(dev); | |
55a63bcc | 944 | drvr->bus_if->drvr = drvr; |
d08b6a37 | 945 | drvr->dev = dev; |
5b435de0 AS |
946 | |
947 | /* Attach and link in the protocol */ | |
712ac5b3 FL |
948 | ret = brcmf_proto_attach(drvr); |
949 | if (ret != 0) { | |
5b435de0 AS |
950 | brcmf_dbg(ERROR, "brcmf_prot_attach failed\n"); |
951 | goto fail; | |
952 | } | |
953 | ||
d08b6a37 FL |
954 | INIT_WORK(&drvr->setmacaddr_work, _brcmf_set_mac_address); |
955 | INIT_WORK(&drvr->multicast_work, _brcmf_set_multicast_list); | |
5b435de0 | 956 | |
712ac5b3 | 957 | return ret; |
5b435de0 AS |
958 | |
959 | fail: | |
712ac5b3 | 960 | brcmf_detach(dev); |
5b435de0 | 961 | |
712ac5b3 | 962 | return ret; |
5b435de0 AS |
963 | } |
964 | ||
ed683c98 | 965 | int brcmf_bus_start(struct device *dev) |
5b435de0 AS |
966 | { |
967 | int ret = -1; | |
5b435de0 AS |
968 | /* Room for "event_msgs" + '\0' + bitvec */ |
969 | char iovbuf[BRCMF_EVENTING_MASK_LEN + 12]; | |
ed683c98 FL |
970 | struct brcmf_bus *bus_if = dev_get_drvdata(dev); |
971 | struct brcmf_pub *drvr = bus_if->drvr; | |
5b435de0 AS |
972 | |
973 | brcmf_dbg(TRACE, "\n"); | |
974 | ||
975 | /* Bring up the bus */ | |
99a0b8ff | 976 | ret = bus_if->brcmf_bus_init(dev); |
5b435de0 AS |
977 | if (ret != 0) { |
978 | brcmf_dbg(ERROR, "brcmf_sdbrcm_bus_init failed %d\n", ret); | |
979 | return ret; | |
980 | } | |
981 | ||
982 | /* If bus is not ready, can't come up */ | |
ed683c98 | 983 | if (bus_if->state != BRCMF_BUS_DATA) { |
5b435de0 AS |
984 | brcmf_dbg(ERROR, "failed bus is not ready\n"); |
985 | return -ENODEV; | |
986 | } | |
987 | ||
53a2277d | 988 | brcmf_c_mkiovar("event_msgs", drvr->eventmask, BRCMF_EVENTING_MASK_LEN, |
5b435de0 AS |
989 | iovbuf, sizeof(iovbuf)); |
990 | brcmf_proto_cdc_query_dcmd(drvr, 0, BRCMF_C_GET_VAR, iovbuf, | |
991 | sizeof(iovbuf)); | |
992 | memcpy(drvr->eventmask, iovbuf, BRCMF_EVENTING_MASK_LEN); | |
993 | ||
994 | setbit(drvr->eventmask, BRCMF_E_SET_SSID); | |
995 | setbit(drvr->eventmask, BRCMF_E_PRUNE); | |
996 | setbit(drvr->eventmask, BRCMF_E_AUTH); | |
997 | setbit(drvr->eventmask, BRCMF_E_REASSOC); | |
998 | setbit(drvr->eventmask, BRCMF_E_REASSOC_IND); | |
999 | setbit(drvr->eventmask, BRCMF_E_DEAUTH_IND); | |
1000 | setbit(drvr->eventmask, BRCMF_E_DISASSOC_IND); | |
1001 | setbit(drvr->eventmask, BRCMF_E_DISASSOC); | |
1002 | setbit(drvr->eventmask, BRCMF_E_JOIN); | |
1003 | setbit(drvr->eventmask, BRCMF_E_ASSOC_IND); | |
1004 | setbit(drvr->eventmask, BRCMF_E_PSK_SUP); | |
1005 | setbit(drvr->eventmask, BRCMF_E_LINK); | |
1006 | setbit(drvr->eventmask, BRCMF_E_NDIS_LINK); | |
1007 | setbit(drvr->eventmask, BRCMF_E_MIC_ERROR); | |
1008 | setbit(drvr->eventmask, BRCMF_E_PMKID_CACHE); | |
1009 | setbit(drvr->eventmask, BRCMF_E_TXFAIL); | |
1010 | setbit(drvr->eventmask, BRCMF_E_JOIN_START); | |
1011 | setbit(drvr->eventmask, BRCMF_E_SCAN_COMPLETE); | |
1012 | ||
1013 | /* enable dongle roaming event */ | |
1014 | ||
1015 | drvr->pktfilter_count = 1; | |
1016 | /* Setup filter to allow only unicast */ | |
1017 | drvr->pktfilter[0] = "100 0 0 0 0x01 0x00"; | |
1018 | ||
1019 | /* Bus is ready, do any protocol initialization */ | |
d08b6a37 | 1020 | ret = brcmf_proto_init(drvr); |
5b435de0 AS |
1021 | if (ret < 0) |
1022 | return ret; | |
1023 | ||
1024 | return 0; | |
1025 | } | |
1026 | ||
5b435de0 AS |
1027 | int brcmf_net_attach(struct brcmf_pub *drvr, int ifidx) |
1028 | { | |
5b435de0 AS |
1029 | struct net_device *ndev; |
1030 | u8 temp_addr[ETH_ALEN] = { | |
1031 | 0x00, 0x90, 0x4c, 0x11, 0x22, 0x33}; | |
1032 | ||
1033 | brcmf_dbg(TRACE, "ifidx %d\n", ifidx); | |
1034 | ||
d08b6a37 | 1035 | ndev = drvr->iflist[ifidx]->ndev; |
5b435de0 AS |
1036 | ndev->netdev_ops = &brcmf_netdev_ops_pri; |
1037 | ||
1038 | /* | |
1039 | * We have to use the primary MAC for virtual interfaces | |
1040 | */ | |
1041 | if (ifidx != 0) { | |
1042 | /* for virtual interfaces use the primary MAC */ | |
d08b6a37 | 1043 | memcpy(temp_addr, drvr->mac, ETH_ALEN); |
5b435de0 AS |
1044 | |
1045 | } | |
1046 | ||
1047 | if (ifidx == 1) { | |
1048 | brcmf_dbg(TRACE, "ACCESS POINT MAC:\n"); | |
1049 | /* ACCESSPOINT INTERFACE CASE */ | |
1050 | temp_addr[0] |= 0X02; /* set bit 2 , | |
1051 | - Locally Administered address */ | |
1052 | ||
1053 | } | |
d08b6a37 | 1054 | ndev->hard_header_len = ETH_HLEN + drvr->hdrlen; |
5b435de0 AS |
1055 | ndev->ethtool_ops = &brcmf_ethtool_ops; |
1056 | ||
d08b6a37 FL |
1057 | drvr->rxsz = ndev->mtu + ndev->hard_header_len + |
1058 | drvr->hdrlen; | |
5b435de0 AS |
1059 | |
1060 | memcpy(ndev->dev_addr, temp_addr, ETH_ALEN); | |
1061 | ||
15d45b6f FL |
1062 | /* attach to cfg80211 for primary interface */ |
1063 | if (!ifidx) { | |
c0a7962a | 1064 | drvr->config = brcmf_cfg80211_attach(ndev, drvr->dev, drvr); |
15d45b6f FL |
1065 | if (drvr->config == NULL) { |
1066 | brcmf_dbg(ERROR, "wl_cfg80211_attach failed\n"); | |
1067 | goto fail; | |
1068 | } | |
1069 | } | |
1070 | ||
5b435de0 AS |
1071 | if (register_netdev(ndev) != 0) { |
1072 | brcmf_dbg(ERROR, "couldn't register the net device\n"); | |
1073 | goto fail; | |
1074 | } | |
1075 | ||
1076 | brcmf_dbg(INFO, "%s: Broadcom Dongle Host Driver\n", ndev->name); | |
1077 | ||
1078 | return 0; | |
1079 | ||
1080 | fail: | |
1081 | ndev->netdev_ops = NULL; | |
1082 | return -EBADE; | |
1083 | } | |
1084 | ||
1085 | static void brcmf_bus_detach(struct brcmf_pub *drvr) | |
1086 | { | |
5b435de0 AS |
1087 | brcmf_dbg(TRACE, "Enter\n"); |
1088 | ||
1089 | if (drvr) { | |
d08b6a37 FL |
1090 | /* Stop the protocol module */ |
1091 | brcmf_proto_stop(drvr); | |
5b435de0 | 1092 | |
d08b6a37 | 1093 | /* Stop the bus module */ |
a9ffda88 | 1094 | drvr->bus_if->brcmf_bus_stop(drvr->dev); |
5b435de0 AS |
1095 | } |
1096 | } | |
1097 | ||
5f947ad9 | 1098 | void brcmf_detach(struct device *dev) |
5b435de0 | 1099 | { |
5f947ad9 FL |
1100 | int i; |
1101 | struct brcmf_bus *bus_if = dev_get_drvdata(dev); | |
1102 | struct brcmf_pub *drvr = bus_if->drvr; | |
5b435de0 AS |
1103 | |
1104 | brcmf_dbg(TRACE, "Enter\n"); | |
1105 | ||
5b435de0 | 1106 | |
5f947ad9 FL |
1107 | /* make sure primary interface removed last */ |
1108 | for (i = BRCMF_MAX_IFS-1; i > -1; i--) | |
1109 | if (drvr->iflist[i]) | |
1110 | brcmf_del_if(drvr, i); | |
5b435de0 | 1111 | |
5f947ad9 | 1112 | brcmf_bus_detach(drvr); |
5b435de0 | 1113 | |
89fdb468 FL |
1114 | if (drvr->prot) { |
1115 | cancel_work_sync(&drvr->setmacaddr_work); | |
1116 | cancel_work_sync(&drvr->multicast_work); | |
5f947ad9 | 1117 | brcmf_proto_detach(drvr); |
89fdb468 | 1118 | } |
5b435de0 | 1119 | |
5f947ad9 FL |
1120 | bus_if->drvr = NULL; |
1121 | kfree(drvr); | |
5b435de0 AS |
1122 | } |
1123 | ||
d08b6a37 | 1124 | static int brcmf_get_pend_8021x_cnt(struct brcmf_pub *drvr) |
5b435de0 | 1125 | { |
d08b6a37 | 1126 | return atomic_read(&drvr->pend_8021x_cnt); |
5b435de0 AS |
1127 | } |
1128 | ||
1129 | #define MAX_WAIT_FOR_8021X_TX 10 | |
1130 | ||
1131 | int brcmf_netdev_wait_pend8021x(struct net_device *ndev) | |
1132 | { | |
e1b83586 | 1133 | struct brcmf_if *ifp = netdev_priv(ndev); |
d08b6a37 | 1134 | struct brcmf_pub *drvr = ifp->drvr; |
5b435de0 AS |
1135 | int timeout = 10 * HZ / 1000; |
1136 | int ntimes = MAX_WAIT_FOR_8021X_TX; | |
d08b6a37 | 1137 | int pend = brcmf_get_pend_8021x_cnt(drvr); |
5b435de0 AS |
1138 | |
1139 | while (ntimes && pend) { | |
1140 | if (pend) { | |
1141 | set_current_state(TASK_INTERRUPTIBLE); | |
1142 | schedule_timeout(timeout); | |
1143 | set_current_state(TASK_RUNNING); | |
1144 | ntimes--; | |
1145 | } | |
d08b6a37 | 1146 | pend = brcmf_get_pend_8021x_cnt(drvr); |
5b435de0 AS |
1147 | } |
1148 | return pend; | |
1149 | } | |
1150 | ||
8ae74654 | 1151 | #ifdef DEBUG |
af534958 | 1152 | int brcmf_write_to_file(struct brcmf_pub *drvr, const u8 *buf, int size) |
5b435de0 AS |
1153 | { |
1154 | int ret = 0; | |
1155 | struct file *fp; | |
1156 | mm_segment_t old_fs; | |
1157 | loff_t pos = 0; | |
1158 | ||
1159 | /* change to KERNEL_DS address limit */ | |
1160 | old_fs = get_fs(); | |
1161 | set_fs(KERNEL_DS); | |
1162 | ||
1163 | /* open file to write */ | |
1164 | fp = filp_open("/tmp/mem_dump", O_WRONLY | O_CREAT, 0640); | |
1165 | if (!fp) { | |
1166 | brcmf_dbg(ERROR, "open file error\n"); | |
1167 | ret = -1; | |
1168 | goto exit; | |
1169 | } | |
1170 | ||
1171 | /* Write buf to file */ | |
af534958 | 1172 | fp->f_op->write(fp, (char __user *)buf, size, &pos); |
5b435de0 AS |
1173 | |
1174 | exit: | |
1175 | /* free buf before return */ | |
1176 | kfree(buf); | |
1177 | /* close file before return */ | |
1178 | if (fp) | |
1179 | filp_close(fp, current->files); | |
1180 | /* restore previous address limit */ | |
1181 | set_fs(old_fs); | |
1182 | ||
1183 | return ret; | |
1184 | } | |
8ae74654 | 1185 | #endif /* DEBUG */ |
f3d7cdc3 AS |
1186 | |
1187 | static int __init brcmfmac_init(void) | |
1188 | { | |
1189 | int ret = 0; | |
1190 | ||
1191 | #ifdef CONFIG_BRCMFMAC_SDIO | |
1192 | ret = brcmf_sdio_init(); | |
1193 | if (ret) | |
1194 | goto fail; | |
1195 | #endif | |
1196 | ||
1197 | fail: | |
1198 | return ret; | |
1199 | } | |
1200 | ||
1201 | static void __exit brcmfmac_exit(void) | |
1202 | { | |
1203 | #ifdef CONFIG_BRCMFMAC_SDIO | |
1204 | brcmf_sdio_exit(); | |
1205 | #endif | |
1206 | } | |
1207 | ||
1208 | module_init(brcmfmac_init); | |
1209 | module_exit(brcmfmac_exit); |