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 | */ | |
02f77195 | 16 | |
5b435de0 AS |
17 | #include <linux/kernel.h> |
18 | #include <linux/string.h> | |
5b435de0 | 19 | #include <linux/netdevice.h> |
5b435de0 AS |
20 | #include <brcmu_wifi.h> |
21 | #include <brcmu_utils.h> | |
22 | #include "dhd.h" | |
23 | #include "dhd_bus.h" | |
24 | #include "dhd_proto.h" | |
25 | #include "dhd_dbg.h" | |
0af29bf7 | 26 | #include "fwil.h" |
e5483576 | 27 | #include "tracepoint.h" |
5b435de0 | 28 | |
0af29bf7 | 29 | #define PKTFILTER_BUF_SIZE 128 |
5b435de0 | 30 | #define BRCMF_ARPOL_MODE 0xb /* agent|snoop|peer_autoreply */ |
0af29bf7 HM |
31 | #define BRCMF_DEFAULT_BCN_TIMEOUT 3 |
32 | #define BRCMF_DEFAULT_SCAN_CHANNEL_TIME 40 | |
33 | #define BRCMF_DEFAULT_SCAN_UNASSOC_TIME 40 | |
34 | #define BRCMF_DEFAULT_PACKET_FILTER "100 0 0 0 0x01 0x00" | |
5b435de0 | 35 | |
8ae74654 | 36 | #ifdef DEBUG |
5b435de0 AS |
37 | static const char brcmf_version[] = |
38 | "Dongle Host Driver, version " BRCMF_VERSION_STR "\nCompiled on " | |
39 | __DATE__ " at " __TIME__; | |
40 | #else | |
41 | static const char brcmf_version[] = | |
42 | "Dongle Host Driver, version " BRCMF_VERSION_STR; | |
43 | #endif | |
44 | ||
53a2277d | 45 | |
b63487ed | 46 | bool brcmf_c_prec_enq(struct device *dev, struct pktq *q, |
5b435de0 AS |
47 | struct sk_buff *pkt, int prec) |
48 | { | |
49 | struct sk_buff *p; | |
50 | int eprec = -1; /* precedence to evict from */ | |
51 | bool discard_oldest; | |
b63487ed FL |
52 | struct brcmf_bus *bus_if = dev_get_drvdata(dev); |
53 | struct brcmf_pub *drvr = bus_if->drvr; | |
5b435de0 AS |
54 | |
55 | /* Fast case, precedence queue is not full and we are also not | |
56 | * exceeding total queue length | |
57 | */ | |
58 | if (!pktq_pfull(q, prec) && !pktq_full(q)) { | |
59 | brcmu_pktq_penq(q, prec, pkt); | |
60 | return true; | |
61 | } | |
62 | ||
63 | /* Determine precedence from which to evict packet, if any */ | |
64 | if (pktq_pfull(q, prec)) | |
65 | eprec = prec; | |
66 | else if (pktq_full(q)) { | |
67 | p = brcmu_pktq_peek_tail(q, &eprec); | |
68 | if (eprec > prec) | |
69 | return false; | |
70 | } | |
71 | ||
72 | /* Evict if needed */ | |
73 | if (eprec >= 0) { | |
74 | /* Detect queueing to unconfigured precedence */ | |
75 | discard_oldest = ac_bitmap_tst(drvr->wme_dp, eprec); | |
76 | if (eprec == prec && !discard_oldest) | |
77 | return false; /* refuse newer (incoming) packet */ | |
78 | /* Evict packet according to discard policy */ | |
79 | p = discard_oldest ? brcmu_pktq_pdeq(q, eprec) : | |
80 | brcmu_pktq_pdeq_tail(q, eprec); | |
81 | if (p == NULL) | |
5e8149f5 | 82 | brcmf_err("brcmu_pktq_penq() failed, oldest %d\n", |
5b435de0 AS |
83 | discard_oldest); |
84 | ||
85 | brcmu_pkt_buf_free_skb(p); | |
86 | } | |
87 | ||
88 | /* Enqueue */ | |
89 | p = brcmu_pktq_penq(q, prec, pkt); | |
90 | if (p == NULL) | |
5e8149f5 | 91 | brcmf_err("brcmu_pktq_penq() failed\n"); |
5b435de0 AS |
92 | |
93 | return p != NULL; | |
94 | } | |
95 | ||
5b435de0 AS |
96 | /* Convert user's input in hex pattern to byte-size mask */ |
97 | static int brcmf_c_pattern_atoh(char *src, char *dst) | |
98 | { | |
99 | int i; | |
100 | if (strncmp(src, "0x", 2) != 0 && strncmp(src, "0X", 2) != 0) { | |
5e8149f5 | 101 | brcmf_err("Mask invalid format. Needs to start with 0x\n"); |
5b435de0 AS |
102 | return -EINVAL; |
103 | } | |
104 | src = src + 2; /* Skip past 0x */ | |
105 | if (strlen(src) % 2 != 0) { | |
5e8149f5 | 106 | brcmf_err("Mask invalid format. Length must be even.\n"); |
5b435de0 AS |
107 | return -EINVAL; |
108 | } | |
109 | for (i = 0; *src != '\0'; i++) { | |
110 | unsigned long res; | |
111 | char num[3]; | |
112 | strncpy(num, src, 2); | |
113 | num[2] = '\0'; | |
114 | if (kstrtoul(num, 16, &res)) | |
115 | return -EINVAL; | |
116 | dst[i] = (u8)res; | |
117 | src += 2; | |
118 | } | |
119 | return i; | |
120 | } | |
121 | ||
0af29bf7 HM |
122 | static void |
123 | brcmf_c_pktfilter_offload_enable(struct brcmf_if *ifp, char *arg, int enable, | |
124 | int master_mode) | |
5b435de0 AS |
125 | { |
126 | unsigned long res; | |
0af29bf7 | 127 | char *argv; |
5b435de0 | 128 | char *arg_save = NULL, *arg_org = NULL; |
0af29bf7 | 129 | s32 err; |
f7264adb | 130 | struct brcmf_pkt_filter_enable_le enable_parm; |
5b435de0 | 131 | |
0af29bf7 | 132 | arg_save = kstrdup(arg, GFP_ATOMIC); |
5b435de0 AS |
133 | if (!arg_save) |
134 | goto fail; | |
135 | ||
136 | arg_org = arg_save; | |
5b435de0 | 137 | |
0af29bf7 | 138 | argv = strsep(&arg_save, " "); |
5b435de0 | 139 | |
0af29bf7 | 140 | if (argv == NULL) { |
5e8149f5 | 141 | brcmf_err("No args provided\n"); |
5b435de0 AS |
142 | goto fail; |
143 | } | |
144 | ||
5b435de0 AS |
145 | /* Parse packet filter id. */ |
146 | enable_parm.id = 0; | |
0af29bf7 | 147 | if (!kstrtoul(argv, 0, &res)) |
f7264adb | 148 | enable_parm.id = cpu_to_le32((u32)res); |
5b435de0 | 149 | |
0af29bf7 | 150 | /* Enable/disable the specified filter. */ |
f7264adb | 151 | enable_parm.enable = cpu_to_le32(enable); |
5b435de0 | 152 | |
0af29bf7 HM |
153 | err = brcmf_fil_iovar_data_set(ifp, "pkt_filter_enable", &enable_parm, |
154 | sizeof(enable_parm)); | |
155 | if (err) | |
5e8149f5 | 156 | brcmf_err("Set pkt_filter_enable error (%d)\n", err); |
5b435de0 | 157 | |
0af29bf7 HM |
158 | /* Control the master mode */ |
159 | err = brcmf_fil_iovar_int_set(ifp, "pkt_filter_mode", master_mode); | |
160 | if (err) | |
5e8149f5 | 161 | brcmf_err("Set pkt_filter_mode error (%d)\n", err); |
5b435de0 AS |
162 | |
163 | fail: | |
164 | kfree(arg_org); | |
165 | } | |
166 | ||
0af29bf7 | 167 | static void brcmf_c_pktfilter_offload_set(struct brcmf_if *ifp, char *arg) |
5b435de0 | 168 | { |
0af29bf7 | 169 | struct brcmf_pkt_filter_le *pkt_filter; |
5b435de0 AS |
170 | unsigned long res; |
171 | int buf_len; | |
0af29bf7 | 172 | s32 err; |
5b435de0 AS |
173 | u32 mask_size; |
174 | u32 pattern_size; | |
175 | char *argv[8], *buf = NULL; | |
176 | int i = 0; | |
177 | char *arg_save = NULL, *arg_org = NULL; | |
178 | ||
179 | arg_save = kstrdup(arg, GFP_ATOMIC); | |
180 | if (!arg_save) | |
181 | goto fail; | |
182 | ||
183 | arg_org = arg_save; | |
184 | ||
185 | buf = kmalloc(PKTFILTER_BUF_SIZE, GFP_ATOMIC); | |
186 | if (!buf) | |
187 | goto fail; | |
188 | ||
189 | argv[i] = strsep(&arg_save, " "); | |
0af29bf7 HM |
190 | while (argv[i]) { |
191 | i++; | |
192 | if (i >= 8) { | |
5e8149f5 | 193 | brcmf_err("Too many parameters\n"); |
0af29bf7 HM |
194 | goto fail; |
195 | } | |
5b435de0 | 196 | argv[i] = strsep(&arg_save, " "); |
0af29bf7 | 197 | } |
5b435de0 | 198 | |
0af29bf7 | 199 | if (i != 6) { |
5e8149f5 | 200 | brcmf_err("Not enough args provided %d\n", i); |
5b435de0 AS |
201 | goto fail; |
202 | } | |
203 | ||
0af29bf7 | 204 | pkt_filter = (struct brcmf_pkt_filter_le *)buf; |
5b435de0 AS |
205 | |
206 | /* Parse packet filter id. */ | |
0af29bf7 HM |
207 | pkt_filter->id = 0; |
208 | if (!kstrtoul(argv[0], 0, &res)) | |
209 | pkt_filter->id = cpu_to_le32((u32)res); | |
5b435de0 AS |
210 | |
211 | /* Parse filter polarity. */ | |
0af29bf7 HM |
212 | pkt_filter->negate_match = 0; |
213 | if (!kstrtoul(argv[1], 0, &res)) | |
214 | pkt_filter->negate_match = cpu_to_le32((u32)res); | |
5b435de0 AS |
215 | |
216 | /* Parse filter type. */ | |
0af29bf7 HM |
217 | pkt_filter->type = 0; |
218 | if (!kstrtoul(argv[2], 0, &res)) | |
219 | pkt_filter->type = cpu_to_le32((u32)res); | |
5b435de0 AS |
220 | |
221 | /* Parse pattern filter offset. */ | |
0af29bf7 HM |
222 | pkt_filter->u.pattern.offset = 0; |
223 | if (!kstrtoul(argv[3], 0, &res)) | |
224 | pkt_filter->u.pattern.offset = cpu_to_le32((u32)res); | |
5b435de0 AS |
225 | |
226 | /* Parse pattern filter mask. */ | |
0af29bf7 HM |
227 | mask_size = brcmf_c_pattern_atoh(argv[4], |
228 | (char *)pkt_filter->u.pattern.mask_and_pattern); | |
5b435de0 AS |
229 | |
230 | /* Parse pattern filter pattern. */ | |
0af29bf7 HM |
231 | pattern_size = brcmf_c_pattern_atoh(argv[5], |
232 | (char *)&pkt_filter->u.pattern.mask_and_pattern[mask_size]); | |
5b435de0 AS |
233 | |
234 | if (mask_size != pattern_size) { | |
5e8149f5 | 235 | brcmf_err("Mask and pattern not the same size\n"); |
5b435de0 AS |
236 | goto fail; |
237 | } | |
238 | ||
0af29bf7 | 239 | pkt_filter->u.pattern.size_bytes = cpu_to_le32(mask_size); |
c697be5a HM |
240 | buf_len = offsetof(struct brcmf_pkt_filter_le, |
241 | u.pattern.mask_and_pattern); | |
0af29bf7 | 242 | buf_len += mask_size + pattern_size; |
5b435de0 | 243 | |
0af29bf7 HM |
244 | err = brcmf_fil_iovar_data_set(ifp, "pkt_filter_add", pkt_filter, |
245 | buf_len); | |
246 | if (err) | |
5e8149f5 | 247 | brcmf_err("Set pkt_filter_add error (%d)\n", err); |
5b435de0 AS |
248 | |
249 | fail: | |
250 | kfree(arg_org); | |
251 | ||
252 | kfree(buf); | |
253 | } | |
254 | ||
0af29bf7 | 255 | int brcmf_c_preinit_dcmds(struct brcmf_if *ifp) |
5b435de0 | 256 | { |
0af29bf7 HM |
257 | s8 eventmask[BRCMF_EVENTING_MASK_LEN]; |
258 | u8 buf[BRCMF_DCMD_SMLEN]; | |
259 | char *ptr; | |
260 | s32 err; | |
135e4c61 FL |
261 | struct brcmf_bus_dcmd *cmdlst; |
262 | struct list_head *cur, *q; | |
5b435de0 | 263 | |
0af29bf7 HM |
264 | /* retreive mac address */ |
265 | err = brcmf_fil_iovar_data_get(ifp, "cur_etheraddr", ifp->mac_addr, | |
266 | sizeof(ifp->mac_addr)); | |
267 | if (err < 0) { | |
5e8149f5 | 268 | brcmf_err("Retreiving cur_etheraddr failed, %d\n", |
0af29bf7 HM |
269 | err); |
270 | goto done; | |
5b435de0 | 271 | } |
0af29bf7 | 272 | memcpy(ifp->drvr->mac, ifp->mac_addr, sizeof(ifp->drvr->mac)); |
5b435de0 AS |
273 | |
274 | /* query for 'ver' to get version info from firmware */ | |
275 | memset(buf, 0, sizeof(buf)); | |
0af29bf7 HM |
276 | strcpy(buf, "ver"); |
277 | err = brcmf_fil_iovar_data_get(ifp, "ver", buf, sizeof(buf)); | |
278 | if (err < 0) { | |
5e8149f5 | 279 | brcmf_err("Retreiving version information failed, %d\n", |
0af29bf7 HM |
280 | err); |
281 | goto done; | |
282 | } | |
283 | ptr = (char *)buf; | |
5b435de0 AS |
284 | strsep(&ptr, "\n"); |
285 | /* Print fw version info */ | |
5e8149f5 | 286 | brcmf_err("Firmware version = %s\n", buf); |
5b435de0 | 287 | |
0af29bf7 HM |
288 | /* |
289 | * Setup timeout if Beacons are lost and roam is off to report | |
290 | * link down | |
291 | */ | |
292 | err = brcmf_fil_iovar_int_set(ifp, "bcn_timeout", | |
293 | BRCMF_DEFAULT_BCN_TIMEOUT); | |
294 | if (err) { | |
5e8149f5 | 295 | brcmf_err("bcn_timeout error (%d)\n", err); |
0af29bf7 HM |
296 | goto done; |
297 | } | |
5b435de0 AS |
298 | |
299 | /* Enable/Disable build-in roaming to allowed ext supplicant to take | |
0af29bf7 HM |
300 | * of romaing |
301 | */ | |
302 | err = brcmf_fil_iovar_int_set(ifp, "roam_off", 1); | |
303 | if (err) { | |
5e8149f5 | 304 | brcmf_err("roam_off error (%d)\n", err); |
0af29bf7 HM |
305 | goto done; |
306 | } | |
307 | ||
308 | /* Setup event_msgs, enable E_IF */ | |
309 | err = brcmf_fil_iovar_data_get(ifp, "event_msgs", eventmask, | |
310 | BRCMF_EVENTING_MASK_LEN); | |
311 | if (err) { | |
5e8149f5 | 312 | brcmf_err("Get event_msgs error (%d)\n", err); |
0af29bf7 HM |
313 | goto done; |
314 | } | |
315 | setbit(eventmask, BRCMF_E_IF); | |
316 | err = brcmf_fil_iovar_data_set(ifp, "event_msgs", eventmask, | |
317 | BRCMF_EVENTING_MASK_LEN); | |
318 | if (err) { | |
5e8149f5 | 319 | brcmf_err("Set event_msgs error (%d)\n", err); |
0af29bf7 HM |
320 | goto done; |
321 | } | |
322 | ||
323 | /* Setup default scan channel time */ | |
324 | err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_SCAN_CHANNEL_TIME, | |
325 | BRCMF_DEFAULT_SCAN_CHANNEL_TIME); | |
326 | if (err) { | |
5e8149f5 | 327 | brcmf_err("BRCMF_C_SET_SCAN_CHANNEL_TIME error (%d)\n", |
0af29bf7 HM |
328 | err); |
329 | goto done; | |
330 | } | |
331 | ||
332 | /* Setup default scan unassoc time */ | |
333 | err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_SCAN_UNASSOC_TIME, | |
334 | BRCMF_DEFAULT_SCAN_UNASSOC_TIME); | |
335 | if (err) { | |
5e8149f5 | 336 | brcmf_err("BRCMF_C_SET_SCAN_UNASSOC_TIME error (%d)\n", |
0af29bf7 HM |
337 | err); |
338 | goto done; | |
339 | } | |
340 | ||
341 | /* Try to set and enable ARP offload feature, this may fail */ | |
342 | err = brcmf_fil_iovar_int_set(ifp, "arp_ol", BRCMF_ARPOL_MODE); | |
343 | if (err) { | |
344 | brcmf_dbg(TRACE, "failed to set ARP offload mode to 0x%x, err = %d\n", | |
345 | BRCMF_ARPOL_MODE, err); | |
346 | err = 0; | |
347 | } else { | |
348 | err = brcmf_fil_iovar_int_set(ifp, "arpoe", 1); | |
349 | if (err) { | |
350 | brcmf_dbg(TRACE, "failed to enable ARP offload err = %d\n", | |
351 | err); | |
352 | err = 0; | |
353 | } else | |
354 | brcmf_dbg(TRACE, "successfully enabled ARP offload to 0x%x\n", | |
355 | BRCMF_ARPOL_MODE); | |
5b435de0 AS |
356 | } |
357 | ||
0af29bf7 HM |
358 | /* Setup packet filter */ |
359 | brcmf_c_pktfilter_offload_set(ifp, BRCMF_DEFAULT_PACKET_FILTER); | |
360 | brcmf_c_pktfilter_offload_enable(ifp, BRCMF_DEFAULT_PACKET_FILTER, | |
361 | 0, true); | |
362 | ||
135e4c61 | 363 | /* set bus specific command if there is any */ |
0af29bf7 | 364 | list_for_each_safe(cur, q, &ifp->drvr->bus_if->dcmd_list) { |
135e4c61 FL |
365 | cmdlst = list_entry(cur, struct brcmf_bus_dcmd, list); |
366 | if (cmdlst->name && cmdlst->param && cmdlst->param_len) { | |
0af29bf7 HM |
367 | brcmf_fil_iovar_data_set(ifp, cmdlst->name, |
368 | cmdlst->param, | |
369 | cmdlst->param_len); | |
135e4c61 FL |
370 | } |
371 | list_del(cur); | |
372 | kfree(cmdlst); | |
373 | } | |
0af29bf7 HM |
374 | done: |
375 | return err; | |
5b435de0 | 376 | } |
e5483576 AS |
377 | |
378 | #ifdef CONFIG_BRCM_TRACING | |
379 | void __brcmf_err(const char *func, const char *fmt, ...) | |
380 | { | |
381 | struct va_format vaf = { | |
382 | .fmt = fmt, | |
383 | }; | |
384 | va_list args; | |
385 | ||
386 | va_start(args, fmt); | |
387 | vaf.va = &args; | |
388 | pr_err("%s: %pV", func, &vaf); | |
389 | trace_brcmf_err(func, &vaf); | |
390 | va_end(args); | |
391 | } | |
392 | #endif | |
393 | #if defined(CONFIG_BRCM_TRACING) || defined(CONFIG_BRCMDBG) | |
394 | void __brcmf_dbg(u32 level, const char *func, const char *fmt, ...) | |
395 | { | |
396 | struct va_format vaf = { | |
397 | .fmt = fmt, | |
398 | }; | |
399 | va_list args; | |
400 | ||
401 | va_start(args, fmt); | |
402 | vaf.va = &args; | |
403 | if (brcmf_msg_level & level) | |
404 | pr_debug("%s %pV", func, &vaf); | |
405 | trace_brcmf_dbg(level, func, &vaf); | |
406 | va_end(args); | |
407 | } | |
408 | #endif |