blob: 65c948e28bcabcd0254086a345dd3943f7abc4f4 [file] [log] [blame]
Arend van Spriel5b435de2011-10-05 13:19:03 +02001/*
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
17/*******************************************************************************
18 * Communicates with the dongle by using dcmd codes.
19 * For certain dcmd codes, the dongle interprets string data from the host.
20 ******************************************************************************/
21
22#include <linux/types.h>
23#include <linux/netdevice.h>
24#include <linux/sched.h>
25#include <defs.h>
26
27#include <brcmu_utils.h>
28#include <brcmu_wifi.h>
29
30#include "dhd.h"
31#include "dhd_proto.h"
32#include "dhd_bus.h"
33#include "dhd_dbg.h"
34
35struct brcmf_proto_cdc_dcmd {
36 __le32 cmd; /* dongle command value */
37 __le32 len; /* lower 16: output buflen;
38 * upper 16: input buflen (excludes header) */
39 __le32 flags; /* flag defns given below */
40 __le32 status; /* status code returned from the device */
41};
42
43/* Max valid buffer size that can be sent to the dongle */
44#define CDC_MAX_MSG_SIZE (ETH_FRAME_LEN+ETH_FCS_LEN)
45
46/* CDC flag definitions */
47#define CDC_DCMD_ERROR 0x01 /* 1=cmd failed */
48#define CDC_DCMD_SET 0x02 /* 0=get, 1=set cmd */
49#define CDC_DCMD_IF_MASK 0xF000 /* I/F index */
50#define CDC_DCMD_IF_SHIFT 12
51#define CDC_DCMD_ID_MASK 0xFFFF0000 /* id an cmd pairing */
52#define CDC_DCMD_ID_SHIFT 16 /* ID Mask shift bits */
53#define CDC_DCMD_ID(flags) \
54 (((flags) & CDC_DCMD_ID_MASK) >> CDC_DCMD_ID_SHIFT)
55
56/*
57 * BDC header - Broadcom specific extension of CDC.
58 * Used on data packets to convey priority across USB.
59 */
60#define BDC_HEADER_LEN 4
Franky Line40aed02011-12-08 15:06:38 -080061#define BDC_PROTO_VER 2 /* Protocol version */
Arend van Spriel5b435de2011-10-05 13:19:03 +020062#define BDC_FLAG_VER_MASK 0xf0 /* Protocol version mask */
63#define BDC_FLAG_VER_SHIFT 4 /* Protocol version shift */
64#define BDC_FLAG_SUM_GOOD 0x04 /* Good RX checksums */
65#define BDC_FLAG_SUM_NEEDED 0x08 /* Dongle needs to do TX checksums */
66#define BDC_PRIORITY_MASK 0x7
67#define BDC_FLAG2_IF_MASK 0x0f /* packet rx interface in APSTA */
68#define BDC_FLAG2_IF_SHIFT 0
69
70#define BDC_GET_IF_IDX(hdr) \
71 ((int)((((hdr)->flags2) & BDC_FLAG2_IF_MASK) >> BDC_FLAG2_IF_SHIFT))
72#define BDC_SET_IF_IDX(hdr, idx) \
73 ((hdr)->flags2 = (((hdr)->flags2 & ~BDC_FLAG2_IF_MASK) | \
74 ((idx) << BDC_FLAG2_IF_SHIFT)))
75
76struct brcmf_proto_bdc_header {
77 u8 flags;
78 u8 priority; /* 802.1d Priority, 4:7 flow control info for usb */
79 u8 flags2;
Franky Line40aed02011-12-08 15:06:38 -080080 u8 data_offset;
Arend van Spriel5b435de2011-10-05 13:19:03 +020081};
82
83
84#define RETRIES 2 /* # of retries to retrieve matching dcmd response */
Franky Lin6e3c7122011-12-16 18:37:11 -080085#define BUS_HEADER_LEN (16+64) /* Must be atleast SDPCM_RESERVE
Arend van Spriel5b435de2011-10-05 13:19:03 +020086 * (amount of header tha might be added)
87 * plus any space that might be needed
Franky Lin6e3c7122011-12-16 18:37:11 -080088 * for bus alignment padding.
Arend van Spriel5b435de2011-10-05 13:19:03 +020089 */
Franky Lin6e3c7122011-12-16 18:37:11 -080090#define ROUND_UP_MARGIN 2048 /* Biggest bus block size possible for
Arend van Spriel5b435de2011-10-05 13:19:03 +020091 * round off at the end of buffer
Franky Lin6e3c7122011-12-16 18:37:11 -080092 * Currently is SDIO
Arend van Spriel5b435de2011-10-05 13:19:03 +020093 */
94
95struct brcmf_proto {
96 u16 reqid;
97 u8 pending;
98 u32 lastcmd;
99 u8 bus_header[BUS_HEADER_LEN];
100 struct brcmf_proto_cdc_dcmd msg;
101 unsigned char buf[BRCMF_DCMD_MAXLEN + ROUND_UP_MARGIN];
102};
103
104static int brcmf_proto_cdc_msg(struct brcmf_pub *drvr)
105{
106 struct brcmf_proto *prot = drvr->prot;
107 int len = le32_to_cpu(prot->msg.len) +
108 sizeof(struct brcmf_proto_cdc_dcmd);
109
110 brcmf_dbg(TRACE, "Enter\n");
111
112 /* NOTE : cdc->msg.len holds the desired length of the buffer to be
113 * returned. Only up to CDC_MAX_MSG_SIZE of this buffer area
114 * is actually sent to the dongle
115 */
116 if (len > CDC_MAX_MSG_SIZE)
117 len = CDC_MAX_MSG_SIZE;
118
119 /* Send request */
Franky Lin47a1ce72011-11-22 17:21:55 -0800120 return brcmf_sdbrcm_bus_txctl(drvr->dev, (unsigned char *)&prot->msg,
Arend van Spriel5b435de2011-10-05 13:19:03 +0200121 len);
122}
123
124static int brcmf_proto_cdc_cmplt(struct brcmf_pub *drvr, u32 id, u32 len)
125{
126 int ret;
127 struct brcmf_proto *prot = drvr->prot;
128
129 brcmf_dbg(TRACE, "Enter\n");
130
131 do {
Franky Lin532cdd32011-11-22 17:21:54 -0800132 ret = brcmf_sdbrcm_bus_rxctl(drvr->dev,
Arend van Spriel5b435de2011-10-05 13:19:03 +0200133 (unsigned char *)&prot->msg,
134 len + sizeof(struct brcmf_proto_cdc_dcmd));
135 if (ret < 0)
136 break;
137 } while (CDC_DCMD_ID(le32_to_cpu(prot->msg.flags)) != id);
138
139 return ret;
140}
141
142int
143brcmf_proto_cdc_query_dcmd(struct brcmf_pub *drvr, int ifidx, uint cmd,
144 void *buf, uint len)
145{
146 struct brcmf_proto *prot = drvr->prot;
147 struct brcmf_proto_cdc_dcmd *msg = &prot->msg;
148 void *info;
149 int ret = 0, retries = 0;
150 u32 id, flags;
151
152 brcmf_dbg(TRACE, "Enter\n");
153 brcmf_dbg(CTL, "cmd %d len %d\n", cmd, len);
154
155 /* Respond "bcmerror" and "bcmerrorstr" with local cache */
156 if (cmd == BRCMF_C_GET_VAR && buf) {
157 if (!strcmp((char *)buf, "bcmerrorstr")) {
158 strncpy((char *)buf, "bcm_error",
159 BCME_STRLEN);
160 goto done;
161 } else if (!strcmp((char *)buf, "bcmerror")) {
162 *(int *)buf = drvr->dongle_error;
163 goto done;
164 }
165 }
166
167 memset(msg, 0, sizeof(struct brcmf_proto_cdc_dcmd));
168
169 msg->cmd = cpu_to_le32(cmd);
170 msg->len = cpu_to_le32(len);
171 flags = (++prot->reqid << CDC_DCMD_ID_SHIFT);
172 flags = (flags & ~CDC_DCMD_IF_MASK) |
173 (ifidx << CDC_DCMD_IF_SHIFT);
174 msg->flags = cpu_to_le32(flags);
175
176 if (buf)
177 memcpy(prot->buf, buf, len);
178
179 ret = brcmf_proto_cdc_msg(drvr);
180 if (ret < 0) {
181 brcmf_dbg(ERROR, "brcmf_proto_cdc_msg failed w/status %d\n",
182 ret);
183 goto done;
184 }
185
186retry:
187 /* wait for interrupt and get first fragment */
188 ret = brcmf_proto_cdc_cmplt(drvr, prot->reqid, len);
189 if (ret < 0)
190 goto done;
191
192 flags = le32_to_cpu(msg->flags);
193 id = (flags & CDC_DCMD_ID_MASK) >> CDC_DCMD_ID_SHIFT;
194
195 if ((id < prot->reqid) && (++retries < RETRIES))
196 goto retry;
197 if (id != prot->reqid) {
198 brcmf_dbg(ERROR, "%s: unexpected request id %d (expected %d)\n",
199 brcmf_ifname(drvr, ifidx), id, prot->reqid);
200 ret = -EINVAL;
201 goto done;
202 }
203
204 /* Check info buffer */
205 info = (void *)&msg[1];
206
207 /* Copy info buffer */
208 if (buf) {
209 if (ret < (int)len)
210 len = ret;
211 memcpy(buf, info, len);
212 }
213
214 /* Check the ERROR flag */
215 if (flags & CDC_DCMD_ERROR) {
216 ret = le32_to_cpu(msg->status);
217 /* Cache error from dongle */
218 drvr->dongle_error = ret;
219 }
220
221done:
222 return ret;
223}
224
225int brcmf_proto_cdc_set_dcmd(struct brcmf_pub *drvr, int ifidx, uint cmd,
226 void *buf, uint len)
227{
228 struct brcmf_proto *prot = drvr->prot;
229 struct brcmf_proto_cdc_dcmd *msg = &prot->msg;
230 int ret = 0;
231 u32 flags, id;
232
233 brcmf_dbg(TRACE, "Enter\n");
234 brcmf_dbg(CTL, "cmd %d len %d\n", cmd, len);
235
236 memset(msg, 0, sizeof(struct brcmf_proto_cdc_dcmd));
237
238 msg->cmd = cpu_to_le32(cmd);
239 msg->len = cpu_to_le32(len);
240 flags = (++prot->reqid << CDC_DCMD_ID_SHIFT) | CDC_DCMD_SET;
241 flags = (flags & ~CDC_DCMD_IF_MASK) |
242 (ifidx << CDC_DCMD_IF_SHIFT);
243 msg->flags = cpu_to_le32(flags);
244
245 if (buf)
246 memcpy(prot->buf, buf, len);
247
248 ret = brcmf_proto_cdc_msg(drvr);
249 if (ret < 0)
250 goto done;
251
252 ret = brcmf_proto_cdc_cmplt(drvr, prot->reqid, len);
253 if (ret < 0)
254 goto done;
255
256 flags = le32_to_cpu(msg->flags);
257 id = (flags & CDC_DCMD_ID_MASK) >> CDC_DCMD_ID_SHIFT;
258
259 if (id != prot->reqid) {
260 brcmf_dbg(ERROR, "%s: unexpected request id %d (expected %d)\n",
261 brcmf_ifname(drvr, ifidx), id, prot->reqid);
262 ret = -EINVAL;
263 goto done;
264 }
265
266 /* Check the ERROR flag */
267 if (flags & CDC_DCMD_ERROR) {
268 ret = le32_to_cpu(msg->status);
269 /* Cache error from dongle */
270 drvr->dongle_error = ret;
271 }
272
273done:
274 return ret;
275}
276
277int
278brcmf_proto_dcmd(struct brcmf_pub *drvr, int ifidx, struct brcmf_dcmd *dcmd,
279 int len)
280{
281 struct brcmf_proto *prot = drvr->prot;
282 int ret = -1;
283
Franky Lin8d169aa2011-11-22 17:21:52 -0800284 if (drvr->bus_if->state == BRCMF_BUS_DOWN) {
Arend van Spriel5b435de2011-10-05 13:19:03 +0200285 brcmf_dbg(ERROR, "bus is down. we have nothing to do.\n");
286 return ret;
287 }
Franky Lin1ae00422011-12-16 18:36:52 -0800288 mutex_lock(&drvr->proto_block);
Arend van Spriel5b435de2011-10-05 13:19:03 +0200289
290 brcmf_dbg(TRACE, "Enter\n");
291
292 if (len > BRCMF_DCMD_MAXLEN)
293 goto done;
294
295 if (prot->pending == true) {
296 brcmf_dbg(TRACE, "CDC packet is pending!!!! cmd=0x%x (%lu) lastcmd=0x%x (%lu)\n",
297 dcmd->cmd, (unsigned long)dcmd->cmd, prot->lastcmd,
298 (unsigned long)prot->lastcmd);
299 if (dcmd->cmd == BRCMF_C_SET_VAR ||
300 dcmd->cmd == BRCMF_C_GET_VAR)
301 brcmf_dbg(TRACE, "iovar cmd=%s\n", (char *)dcmd->buf);
302
303 goto done;
304 }
305
306 prot->pending = true;
307 prot->lastcmd = dcmd->cmd;
308 if (dcmd->set)
309 ret = brcmf_proto_cdc_set_dcmd(drvr, ifidx, dcmd->cmd,
310 dcmd->buf, len);
311 else {
312 ret = brcmf_proto_cdc_query_dcmd(drvr, ifidx, dcmd->cmd,
313 dcmd->buf, len);
314 if (ret > 0)
315 dcmd->used = ret -
316 sizeof(struct brcmf_proto_cdc_dcmd);
317 }
318
319 if (ret >= 0)
320 ret = 0;
321 else {
322 struct brcmf_proto_cdc_dcmd *msg = &prot->msg;
323 /* len == needed when set/query fails from dongle */
324 dcmd->needed = le32_to_cpu(msg->len);
325 }
326
327 /* Intercept the wme_dp dongle cmd here */
328 if (!ret && dcmd->cmd == BRCMF_C_SET_VAR &&
329 !strcmp(dcmd->buf, "wme_dp")) {
330 int slen;
331 __le32 val = 0;
332
333 slen = strlen("wme_dp") + 1;
334 if (len >= (int)(slen + sizeof(int)))
335 memcpy(&val, (char *)dcmd->buf + slen, sizeof(int));
336 drvr->wme_dp = (u8) le32_to_cpu(val);
337 }
338
339 prot->pending = false;
340
341done:
Franky Lin1ae00422011-12-16 18:36:52 -0800342 mutex_unlock(&drvr->proto_block);
Arend van Spriel5b435de2011-10-05 13:19:03 +0200343
344 return ret;
345}
346
347static bool pkt_sum_needed(struct sk_buff *skb)
348{
349 return skb->ip_summed == CHECKSUM_PARTIAL;
350}
351
352static void pkt_set_sum_good(struct sk_buff *skb, bool x)
353{
354 skb->ip_summed = (x ? CHECKSUM_UNNECESSARY : CHECKSUM_NONE);
355}
356
357void brcmf_proto_hdrpush(struct brcmf_pub *drvr, int ifidx,
358 struct sk_buff *pktbuf)
359{
360 struct brcmf_proto_bdc_header *h;
361
362 brcmf_dbg(TRACE, "Enter\n");
363
364 /* Push BDC header used to convey priority for buses that don't */
365
366 skb_push(pktbuf, BDC_HEADER_LEN);
367
368 h = (struct brcmf_proto_bdc_header *)(pktbuf->data);
369
370 h->flags = (BDC_PROTO_VER << BDC_FLAG_VER_SHIFT);
371 if (pkt_sum_needed(pktbuf))
372 h->flags |= BDC_FLAG_SUM_NEEDED;
373
374 h->priority = (pktbuf->priority & BDC_PRIORITY_MASK);
375 h->flags2 = 0;
Franky Line40aed02011-12-08 15:06:38 -0800376 h->data_offset = 0;
Arend van Spriel5b435de2011-10-05 13:19:03 +0200377 BDC_SET_IF_IDX(h, ifidx);
378}
379
Franky Lind5625ee2011-12-16 18:37:01 -0800380int brcmf_proto_hdrpull(struct device *dev, int *ifidx,
Arend van Spriel5b435de2011-10-05 13:19:03 +0200381 struct sk_buff *pktbuf)
382{
383 struct brcmf_proto_bdc_header *h;
Franky Lind5625ee2011-12-16 18:37:01 -0800384 struct brcmf_bus *bus_if = dev_get_drvdata(dev);
385 struct brcmf_pub *drvr = bus_if->drvr;
Arend van Spriel5b435de2011-10-05 13:19:03 +0200386
387 brcmf_dbg(TRACE, "Enter\n");
388
389 /* Pop BDC header used to convey priority for buses that don't */
390
391 if (pktbuf->len < BDC_HEADER_LEN) {
392 brcmf_dbg(ERROR, "rx data too short (%d < %d)\n",
393 pktbuf->len, BDC_HEADER_LEN);
394 return -EBADE;
395 }
396
397 h = (struct brcmf_proto_bdc_header *)(pktbuf->data);
398
399 *ifidx = BDC_GET_IF_IDX(h);
400 if (*ifidx >= BRCMF_MAX_IFS) {
401 brcmf_dbg(ERROR, "rx data ifnum out of range (%d)\n", *ifidx);
402 return -EBADE;
403 }
404
405 if (((h->flags & BDC_FLAG_VER_MASK) >> BDC_FLAG_VER_SHIFT) !=
406 BDC_PROTO_VER) {
407 brcmf_dbg(ERROR, "%s: non-BDC packet received, flags 0x%x\n",
408 brcmf_ifname(drvr, *ifidx), h->flags);
409 return -EBADE;
410 }
411
412 if (h->flags & BDC_FLAG_SUM_GOOD) {
413 brcmf_dbg(INFO, "%s: BDC packet received with good rx-csum, flags 0x%x\n",
414 brcmf_ifname(drvr, *ifidx), h->flags);
415 pkt_set_sum_good(pktbuf, true);
416 }
417
418 pktbuf->priority = h->priority & BDC_PRIORITY_MASK;
419
420 skb_pull(pktbuf, BDC_HEADER_LEN);
421
422 return 0;
423}
424
425int brcmf_proto_attach(struct brcmf_pub *drvr)
426{
427 struct brcmf_proto *cdc;
428
429 cdc = kzalloc(sizeof(struct brcmf_proto), GFP_ATOMIC);
430 if (!cdc)
431 goto fail;
432
433 /* ensure that the msg buf directly follows the cdc msg struct */
434 if ((unsigned long)(&cdc->msg + 1) != (unsigned long)cdc->buf) {
435 brcmf_dbg(ERROR, "struct brcmf_proto is not correctly defined\n");
436 goto fail;
437 }
438
439 drvr->prot = cdc;
440 drvr->hdrlen += BDC_HEADER_LEN;
Franky Linb01a6b32011-12-16 18:37:03 -0800441 drvr->bus_if->maxctl = BRCMF_DCMD_MAXLEN +
Arend van Spriel5b435de2011-10-05 13:19:03 +0200442 sizeof(struct brcmf_proto_cdc_dcmd) + ROUND_UP_MARGIN;
443 return 0;
444
445fail:
446 kfree(cdc);
447 return -ENOMEM;
448}
449
450/* ~NOTE~ What if another thread is waiting on the semaphore? Holding it? */
451void brcmf_proto_detach(struct brcmf_pub *drvr)
452{
453 kfree(drvr->prot);
454 drvr->prot = NULL;
455}
456
Arend van Spriel5b435de2011-10-05 13:19:03 +0200457int brcmf_proto_init(struct brcmf_pub *drvr)
458{
459 int ret = 0;
460 char buf[128];
461
462 brcmf_dbg(TRACE, "Enter\n");
463
Franky Lin1ae00422011-12-16 18:36:52 -0800464 mutex_lock(&drvr->proto_block);
Arend van Spriel5b435de2011-10-05 13:19:03 +0200465
466 /* Get the device MAC address */
467 strcpy(buf, "cur_etheraddr");
468 ret = brcmf_proto_cdc_query_dcmd(drvr, 0, BRCMF_C_GET_VAR,
469 buf, sizeof(buf));
470 if (ret < 0) {
Franky Lin1ae00422011-12-16 18:36:52 -0800471 mutex_unlock(&drvr->proto_block);
Arend van Spriel5b435de2011-10-05 13:19:03 +0200472 return ret;
473 }
474 memcpy(drvr->mac, buf, ETH_ALEN);
475
Franky Lin1ae00422011-12-16 18:36:52 -0800476 mutex_unlock(&drvr->proto_block);
Arend van Spriel5b435de2011-10-05 13:19:03 +0200477
478 ret = brcmf_c_preinit_dcmds(drvr);
479
480 /* Always assumes wl for now */
481 drvr->iswl = true;
482
483 return ret;
484}
485
486void brcmf_proto_stop(struct brcmf_pub *drvr)
487{
488 /* Nothing to do for CDC */
489}