]>
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; |
1bb1f384 | 799 | struct brcmf_bus *bus_if = drvr->bus_if; |
5b435de0 | 800 | u32 toe_ol; |
5b435de0 AS |
801 | s32 ret = 0; |
802 | ||
e1b83586 | 803 | brcmf_dbg(TRACE, "ifidx %d\n", ifp->idx); |
5b435de0 | 804 | |
e1b83586 | 805 | if (ifp->idx == 0) { /* do it only for primary eth0 */ |
1bb1f384 AS |
806 | /* If bus is not ready, can't continue */ |
807 | if (bus_if->state != BRCMF_BUS_DATA) { | |
808 | brcmf_dbg(ERROR, "failed bus is not ready\n"); | |
809 | return -EAGAIN; | |
5b435de0 | 810 | } |
1bb1f384 | 811 | |
d08b6a37 | 812 | atomic_set(&drvr->pend_8021x_cnt, 0); |
5b435de0 | 813 | |
d08b6a37 | 814 | memcpy(ndev->dev_addr, drvr->mac, ETH_ALEN); |
5b435de0 AS |
815 | |
816 | /* Get current TOE mode from dongle */ | |
d08b6a37 | 817 | if (brcmf_toe_get(drvr, ifp->idx, &toe_ol) >= 0 |
5b435de0 | 818 | && (toe_ol & TOE_TX_CSUM_OL) != 0) |
d08b6a37 | 819 | drvr->iflist[ifp->idx]->ndev->features |= |
5b435de0 AS |
820 | NETIF_F_IP_CSUM; |
821 | else | |
d08b6a37 | 822 | drvr->iflist[ifp->idx]->ndev->features &= |
5b435de0 AS |
823 | ~NETIF_F_IP_CSUM; |
824 | } | |
825 | /* Allow transmit calls */ | |
826 | netif_start_queue(ndev); | |
57adc1fc | 827 | drvr->bus_if->drvr_up = true; |
d08b6a37 | 828 | if (brcmf_cfg80211_up(drvr->config)) { |
5b435de0 AS |
829 | brcmf_dbg(ERROR, "failed to bring up cfg80211\n"); |
830 | return -1; | |
831 | } | |
832 | ||
833 | return ret; | |
834 | } | |
835 | ||
dfded557 FL |
836 | static const struct net_device_ops brcmf_netdev_ops_pri = { |
837 | .ndo_open = brcmf_netdev_open, | |
838 | .ndo_stop = brcmf_netdev_stop, | |
839 | .ndo_get_stats = brcmf_netdev_get_stats, | |
840 | .ndo_do_ioctl = brcmf_netdev_ioctl_entry, | |
841 | .ndo_start_xmit = brcmf_netdev_start_xmit, | |
842 | .ndo_set_mac_address = brcmf_netdev_set_mac_address, | |
843 | .ndo_set_rx_mode = brcmf_netdev_set_multicast_list | |
844 | }; | |
845 | ||
5b435de0 | 846 | int |
55a63bcc | 847 | brcmf_add_if(struct device *dev, int ifidx, char *name, u8 *mac_addr) |
5b435de0 AS |
848 | { |
849 | struct brcmf_if *ifp; | |
e1b83586 | 850 | struct net_device *ndev; |
55a63bcc FL |
851 | struct brcmf_bus *bus_if = dev_get_drvdata(dev); |
852 | struct brcmf_pub *drvr = bus_if->drvr; | |
5b435de0 | 853 | |
15d45b6f | 854 | brcmf_dbg(TRACE, "idx %d\n", ifidx); |
5b435de0 | 855 | |
d08b6a37 | 856 | ifp = drvr->iflist[ifidx]; |
e1b83586 FL |
857 | /* |
858 | * Delete the existing interface before overwriting it | |
859 | * in case we missed the BRCMF_E_IF_DEL event. | |
860 | */ | |
861 | if (ifp) { | |
862 | brcmf_dbg(ERROR, "ERROR: netdev:%s already exists, try free & unregister\n", | |
863 | ifp->ndev->name); | |
864 | netif_stop_queue(ifp->ndev); | |
865 | unregister_netdev(ifp->ndev); | |
866 | free_netdev(ifp->ndev); | |
d08b6a37 | 867 | drvr->iflist[ifidx] = NULL; |
15d45b6f | 868 | } |
5b435de0 | 869 | |
15d45b6f | 870 | /* Allocate netdev, including space for private structure */ |
e1b83586 FL |
871 | ndev = alloc_netdev(sizeof(struct brcmf_if), name, ether_setup); |
872 | if (!ndev) { | |
15d45b6f | 873 | brcmf_dbg(ERROR, "OOM - alloc_netdev\n"); |
e1b83586 | 874 | return -ENOMEM; |
15d45b6f | 875 | } |
5b435de0 | 876 | |
e1b83586 FL |
877 | ifp = netdev_priv(ndev); |
878 | ifp->ndev = ndev; | |
d08b6a37 FL |
879 | ifp->drvr = drvr; |
880 | drvr->iflist[ifidx] = ifp; | |
e1b83586 | 881 | ifp->idx = ifidx; |
15d45b6f FL |
882 | if (mac_addr != NULL) |
883 | memcpy(&ifp->mac_addr, mac_addr, ETH_ALEN); | |
5b435de0 | 884 | |
d08b6a37 | 885 | if (brcmf_net_attach(drvr, ifp->idx)) { |
15d45b6f FL |
886 | brcmf_dbg(ERROR, "brcmf_net_attach failed"); |
887 | free_netdev(ifp->ndev); | |
d08b6a37 | 888 | drvr->iflist[ifidx] = NULL; |
e1b83586 | 889 | return -EOPNOTSUPP; |
15d45b6f | 890 | } |
5b435de0 | 891 | |
15d45b6f FL |
892 | brcmf_dbg(TRACE, " ==== pid:%x, net_device for if:%s created ===\n", |
893 | current->pid, ifp->ndev->name); | |
5b435de0 AS |
894 | |
895 | return 0; | |
896 | } | |
897 | ||
d08b6a37 | 898 | void brcmf_del_if(struct brcmf_pub *drvr, int ifidx) |
5b435de0 AS |
899 | { |
900 | struct brcmf_if *ifp; | |
901 | ||
902 | brcmf_dbg(TRACE, "idx %d\n", ifidx); | |
903 | ||
d08b6a37 | 904 | ifp = drvr->iflist[ifidx]; |
5b435de0 AS |
905 | if (!ifp) { |
906 | brcmf_dbg(ERROR, "Null interface\n"); | |
907 | return; | |
908 | } | |
dfded557 FL |
909 | if (ifp->ndev) { |
910 | if (ifidx == 0) { | |
911 | if (ifp->ndev->netdev_ops == &brcmf_netdev_ops_pri) { | |
912 | rtnl_lock(); | |
913 | brcmf_netdev_stop(ifp->ndev); | |
914 | rtnl_unlock(); | |
915 | } | |
916 | } else { | |
917 | netif_stop_queue(ifp->ndev); | |
918 | } | |
919 | ||
5b435de0 | 920 | unregister_netdev(ifp->ndev); |
d08b6a37 | 921 | drvr->iflist[ifidx] = NULL; |
dfded557 | 922 | if (ifidx == 0) |
d08b6a37 | 923 | brcmf_cfg80211_detach(drvr->config); |
dfded557 | 924 | free_netdev(ifp->ndev); |
5b435de0 AS |
925 | } |
926 | } | |
927 | ||
2447ffb0 | 928 | int brcmf_attach(uint bus_hdrlen, struct device *dev) |
5b435de0 | 929 | { |
d08b6a37 | 930 | struct brcmf_pub *drvr = NULL; |
712ac5b3 | 931 | int ret = 0; |
5b435de0 AS |
932 | |
933 | brcmf_dbg(TRACE, "Enter\n"); | |
934 | ||
5b435de0 | 935 | /* Allocate primary brcmf_info */ |
d08b6a37 FL |
936 | drvr = kzalloc(sizeof(struct brcmf_pub), GFP_ATOMIC); |
937 | if (!drvr) | |
712ac5b3 | 938 | return -ENOMEM; |
5b435de0 | 939 | |
d08b6a37 | 940 | mutex_init(&drvr->proto_block); |
5b435de0 AS |
941 | |
942 | /* Link to bus module */ | |
d08b6a37 FL |
943 | drvr->hdrlen = bus_hdrlen; |
944 | drvr->bus_if = dev_get_drvdata(dev); | |
55a63bcc | 945 | drvr->bus_if->drvr = drvr; |
d08b6a37 | 946 | drvr->dev = dev; |
5b435de0 AS |
947 | |
948 | /* Attach and link in the protocol */ | |
712ac5b3 FL |
949 | ret = brcmf_proto_attach(drvr); |
950 | if (ret != 0) { | |
5b435de0 AS |
951 | brcmf_dbg(ERROR, "brcmf_prot_attach failed\n"); |
952 | goto fail; | |
953 | } | |
954 | ||
d08b6a37 FL |
955 | INIT_WORK(&drvr->setmacaddr_work, _brcmf_set_mac_address); |
956 | INIT_WORK(&drvr->multicast_work, _brcmf_set_multicast_list); | |
5b435de0 | 957 | |
712ac5b3 | 958 | return ret; |
5b435de0 AS |
959 | |
960 | fail: | |
712ac5b3 | 961 | brcmf_detach(dev); |
5b435de0 | 962 | |
712ac5b3 | 963 | return ret; |
5b435de0 AS |
964 | } |
965 | ||
ed683c98 | 966 | int brcmf_bus_start(struct device *dev) |
5b435de0 AS |
967 | { |
968 | int ret = -1; | |
5b435de0 AS |
969 | /* Room for "event_msgs" + '\0' + bitvec */ |
970 | char iovbuf[BRCMF_EVENTING_MASK_LEN + 12]; | |
ed683c98 FL |
971 | struct brcmf_bus *bus_if = dev_get_drvdata(dev); |
972 | struct brcmf_pub *drvr = bus_if->drvr; | |
5b435de0 AS |
973 | |
974 | brcmf_dbg(TRACE, "\n"); | |
975 | ||
976 | /* Bring up the bus */ | |
99a0b8ff | 977 | ret = bus_if->brcmf_bus_init(dev); |
5b435de0 AS |
978 | if (ret != 0) { |
979 | brcmf_dbg(ERROR, "brcmf_sdbrcm_bus_init failed %d\n", ret); | |
980 | return ret; | |
981 | } | |
982 | ||
53a2277d | 983 | brcmf_c_mkiovar("event_msgs", drvr->eventmask, BRCMF_EVENTING_MASK_LEN, |
5b435de0 AS |
984 | iovbuf, sizeof(iovbuf)); |
985 | brcmf_proto_cdc_query_dcmd(drvr, 0, BRCMF_C_GET_VAR, iovbuf, | |
986 | sizeof(iovbuf)); | |
987 | memcpy(drvr->eventmask, iovbuf, BRCMF_EVENTING_MASK_LEN); | |
988 | ||
989 | setbit(drvr->eventmask, BRCMF_E_SET_SSID); | |
990 | setbit(drvr->eventmask, BRCMF_E_PRUNE); | |
991 | setbit(drvr->eventmask, BRCMF_E_AUTH); | |
992 | setbit(drvr->eventmask, BRCMF_E_REASSOC); | |
993 | setbit(drvr->eventmask, BRCMF_E_REASSOC_IND); | |
994 | setbit(drvr->eventmask, BRCMF_E_DEAUTH_IND); | |
995 | setbit(drvr->eventmask, BRCMF_E_DISASSOC_IND); | |
996 | setbit(drvr->eventmask, BRCMF_E_DISASSOC); | |
997 | setbit(drvr->eventmask, BRCMF_E_JOIN); | |
998 | setbit(drvr->eventmask, BRCMF_E_ASSOC_IND); | |
999 | setbit(drvr->eventmask, BRCMF_E_PSK_SUP); | |
1000 | setbit(drvr->eventmask, BRCMF_E_LINK); | |
1001 | setbit(drvr->eventmask, BRCMF_E_NDIS_LINK); | |
1002 | setbit(drvr->eventmask, BRCMF_E_MIC_ERROR); | |
1003 | setbit(drvr->eventmask, BRCMF_E_PMKID_CACHE); | |
1004 | setbit(drvr->eventmask, BRCMF_E_TXFAIL); | |
1005 | setbit(drvr->eventmask, BRCMF_E_JOIN_START); | |
1006 | setbit(drvr->eventmask, BRCMF_E_SCAN_COMPLETE); | |
1007 | ||
1008 | /* enable dongle roaming event */ | |
1009 | ||
1010 | drvr->pktfilter_count = 1; | |
1011 | /* Setup filter to allow only unicast */ | |
1012 | drvr->pktfilter[0] = "100 0 0 0 0x01 0x00"; | |
1013 | ||
1014 | /* Bus is ready, do any protocol initialization */ | |
d08b6a37 | 1015 | ret = brcmf_proto_init(drvr); |
5b435de0 AS |
1016 | if (ret < 0) |
1017 | return ret; | |
1018 | ||
1bb1f384 AS |
1019 | /* signal bus ready */ |
1020 | bus_if->state = BRCMF_BUS_DATA; | |
5b435de0 AS |
1021 | return 0; |
1022 | } | |
1023 | ||
5b435de0 AS |
1024 | int brcmf_net_attach(struct brcmf_pub *drvr, int ifidx) |
1025 | { | |
5b435de0 AS |
1026 | struct net_device *ndev; |
1027 | u8 temp_addr[ETH_ALEN] = { | |
1028 | 0x00, 0x90, 0x4c, 0x11, 0x22, 0x33}; | |
1029 | ||
1030 | brcmf_dbg(TRACE, "ifidx %d\n", ifidx); | |
1031 | ||
d08b6a37 | 1032 | ndev = drvr->iflist[ifidx]->ndev; |
5b435de0 AS |
1033 | ndev->netdev_ops = &brcmf_netdev_ops_pri; |
1034 | ||
1035 | /* | |
1036 | * We have to use the primary MAC for virtual interfaces | |
1037 | */ | |
1038 | if (ifidx != 0) { | |
1039 | /* for virtual interfaces use the primary MAC */ | |
d08b6a37 | 1040 | memcpy(temp_addr, drvr->mac, ETH_ALEN); |
5b435de0 AS |
1041 | |
1042 | } | |
1043 | ||
1044 | if (ifidx == 1) { | |
1045 | brcmf_dbg(TRACE, "ACCESS POINT MAC:\n"); | |
1046 | /* ACCESSPOINT INTERFACE CASE */ | |
1047 | temp_addr[0] |= 0X02; /* set bit 2 , | |
1048 | - Locally Administered address */ | |
1049 | ||
1050 | } | |
d08b6a37 | 1051 | ndev->hard_header_len = ETH_HLEN + drvr->hdrlen; |
5b435de0 AS |
1052 | ndev->ethtool_ops = &brcmf_ethtool_ops; |
1053 | ||
d08b6a37 FL |
1054 | drvr->rxsz = ndev->mtu + ndev->hard_header_len + |
1055 | drvr->hdrlen; | |
5b435de0 AS |
1056 | |
1057 | memcpy(ndev->dev_addr, temp_addr, ETH_ALEN); | |
1058 | ||
15d45b6f FL |
1059 | /* attach to cfg80211 for primary interface */ |
1060 | if (!ifidx) { | |
c0a7962a | 1061 | drvr->config = brcmf_cfg80211_attach(ndev, drvr->dev, drvr); |
15d45b6f FL |
1062 | if (drvr->config == NULL) { |
1063 | brcmf_dbg(ERROR, "wl_cfg80211_attach failed\n"); | |
1064 | goto fail; | |
1065 | } | |
1066 | } | |
1067 | ||
5b435de0 AS |
1068 | if (register_netdev(ndev) != 0) { |
1069 | brcmf_dbg(ERROR, "couldn't register the net device\n"); | |
1070 | goto fail; | |
1071 | } | |
1072 | ||
1073 | brcmf_dbg(INFO, "%s: Broadcom Dongle Host Driver\n", ndev->name); | |
1074 | ||
1075 | return 0; | |
1076 | ||
1077 | fail: | |
1078 | ndev->netdev_ops = NULL; | |
1079 | return -EBADE; | |
1080 | } | |
1081 | ||
1082 | static void brcmf_bus_detach(struct brcmf_pub *drvr) | |
1083 | { | |
5b435de0 AS |
1084 | brcmf_dbg(TRACE, "Enter\n"); |
1085 | ||
1086 | if (drvr) { | |
d08b6a37 FL |
1087 | /* Stop the protocol module */ |
1088 | brcmf_proto_stop(drvr); | |
5b435de0 | 1089 | |
d08b6a37 | 1090 | /* Stop the bus module */ |
a9ffda88 | 1091 | drvr->bus_if->brcmf_bus_stop(drvr->dev); |
5b435de0 AS |
1092 | } |
1093 | } | |
1094 | ||
5f947ad9 | 1095 | void brcmf_detach(struct device *dev) |
5b435de0 | 1096 | { |
5f947ad9 FL |
1097 | int i; |
1098 | struct brcmf_bus *bus_if = dev_get_drvdata(dev); | |
1099 | struct brcmf_pub *drvr = bus_if->drvr; | |
5b435de0 AS |
1100 | |
1101 | brcmf_dbg(TRACE, "Enter\n"); | |
1102 | ||
5b435de0 | 1103 | |
5f947ad9 FL |
1104 | /* make sure primary interface removed last */ |
1105 | for (i = BRCMF_MAX_IFS-1; i > -1; i--) | |
1106 | if (drvr->iflist[i]) | |
1107 | brcmf_del_if(drvr, i); | |
5b435de0 | 1108 | |
5f947ad9 | 1109 | brcmf_bus_detach(drvr); |
5b435de0 | 1110 | |
89fdb468 FL |
1111 | if (drvr->prot) { |
1112 | cancel_work_sync(&drvr->setmacaddr_work); | |
1113 | cancel_work_sync(&drvr->multicast_work); | |
5f947ad9 | 1114 | brcmf_proto_detach(drvr); |
89fdb468 | 1115 | } |
5b435de0 | 1116 | |
5f947ad9 FL |
1117 | bus_if->drvr = NULL; |
1118 | kfree(drvr); | |
5b435de0 AS |
1119 | } |
1120 | ||
d08b6a37 | 1121 | static int brcmf_get_pend_8021x_cnt(struct brcmf_pub *drvr) |
5b435de0 | 1122 | { |
d08b6a37 | 1123 | return atomic_read(&drvr->pend_8021x_cnt); |
5b435de0 AS |
1124 | } |
1125 | ||
1126 | #define MAX_WAIT_FOR_8021X_TX 10 | |
1127 | ||
1128 | int brcmf_netdev_wait_pend8021x(struct net_device *ndev) | |
1129 | { | |
e1b83586 | 1130 | struct brcmf_if *ifp = netdev_priv(ndev); |
d08b6a37 | 1131 | struct brcmf_pub *drvr = ifp->drvr; |
5b435de0 AS |
1132 | int timeout = 10 * HZ / 1000; |
1133 | int ntimes = MAX_WAIT_FOR_8021X_TX; | |
d08b6a37 | 1134 | int pend = brcmf_get_pend_8021x_cnt(drvr); |
5b435de0 AS |
1135 | |
1136 | while (ntimes && pend) { | |
1137 | if (pend) { | |
1138 | set_current_state(TASK_INTERRUPTIBLE); | |
1139 | schedule_timeout(timeout); | |
1140 | set_current_state(TASK_RUNNING); | |
1141 | ntimes--; | |
1142 | } | |
d08b6a37 | 1143 | pend = brcmf_get_pend_8021x_cnt(drvr); |
5b435de0 AS |
1144 | } |
1145 | return pend; | |
1146 | } | |
1147 | ||
8ae74654 | 1148 | #ifdef DEBUG |
af534958 | 1149 | int brcmf_write_to_file(struct brcmf_pub *drvr, const u8 *buf, int size) |
5b435de0 AS |
1150 | { |
1151 | int ret = 0; | |
1152 | struct file *fp; | |
1153 | mm_segment_t old_fs; | |
1154 | loff_t pos = 0; | |
1155 | ||
1156 | /* change to KERNEL_DS address limit */ | |
1157 | old_fs = get_fs(); | |
1158 | set_fs(KERNEL_DS); | |
1159 | ||
1160 | /* open file to write */ | |
1161 | fp = filp_open("/tmp/mem_dump", O_WRONLY | O_CREAT, 0640); | |
1162 | if (!fp) { | |
1163 | brcmf_dbg(ERROR, "open file error\n"); | |
1164 | ret = -1; | |
1165 | goto exit; | |
1166 | } | |
1167 | ||
1168 | /* Write buf to file */ | |
af534958 | 1169 | fp->f_op->write(fp, (char __user *)buf, size, &pos); |
5b435de0 AS |
1170 | |
1171 | exit: | |
1172 | /* free buf before return */ | |
1173 | kfree(buf); | |
1174 | /* close file before return */ | |
1175 | if (fp) | |
1176 | filp_close(fp, current->files); | |
1177 | /* restore previous address limit */ | |
1178 | set_fs(old_fs); | |
1179 | ||
1180 | return ret; | |
1181 | } | |
8ae74654 | 1182 | #endif /* DEBUG */ |
f3d7cdc3 | 1183 | |
e64a4b70 | 1184 | static void brcmf_driver_init(struct work_struct *work) |
f3d7cdc3 | 1185 | { |
f3d7cdc3 | 1186 | #ifdef CONFIG_BRCMFMAC_SDIO |
549040ab | 1187 | brcmf_sdio_init(); |
f3d7cdc3 | 1188 | #endif |
71bb244b | 1189 | #ifdef CONFIG_BRCMFMAC_USB |
549040ab | 1190 | brcmf_usb_init(); |
71bb244b | 1191 | #endif |
e64a4b70 AS |
1192 | } |
1193 | static DECLARE_WORK(brcmf_driver_work, brcmf_driver_init); | |
1194 | ||
1195 | static int __init brcmfmac_module_init(void) | |
1196 | { | |
1197 | if (!schedule_work(&brcmf_driver_work)) | |
1198 | return -EBUSY; | |
1199 | ||
549040ab | 1200 | return 0; |
f3d7cdc3 AS |
1201 | } |
1202 | ||
e64a4b70 | 1203 | static void __exit brcmfmac_module_exit(void) |
f3d7cdc3 | 1204 | { |
e64a4b70 AS |
1205 | cancel_work_sync(&brcmf_driver_work); |
1206 | ||
f3d7cdc3 AS |
1207 | #ifdef CONFIG_BRCMFMAC_SDIO |
1208 | brcmf_sdio_exit(); | |
1209 | #endif | |
71bb244b AS |
1210 | #ifdef CONFIG_BRCMFMAC_USB |
1211 | brcmf_usb_exit(); | |
1212 | #endif | |
f3d7cdc3 AS |
1213 | } |
1214 | ||
e64a4b70 AS |
1215 | module_init(brcmfmac_module_init); |
1216 | module_exit(brcmfmac_module_exit); |