nexmon – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | /* |
2 | * Copyright (c) 2013 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 | #include <linux/kernel.h> |
||
18 | #include <linux/slab.h> |
||
19 | #include <linux/device.h> |
||
20 | #include <linux/firmware.h> |
||
21 | #include <linux/module.h> |
||
22 | #include <linux/bcm47xx_nvram.h> |
||
23 | |||
24 | #include "debug.h" |
||
25 | #include "firmware.h" |
||
26 | |||
27 | #define BRCMF_FW_MAX_NVRAM_SIZE 64000 |
||
28 | #define BRCMF_FW_NVRAM_DEVPATH_LEN 19 /* devpath0=pcie/1/4/ */ |
||
29 | #define BRCMF_FW_NVRAM_PCIEDEV_LEN 10 /* pcie/1/4/ + \0 */ |
||
30 | |||
31 | char brcmf_firmware_path[BRCMF_FW_PATH_LEN]; |
||
32 | module_param_string(alternative_fw_path, brcmf_firmware_path, |
||
33 | BRCMF_FW_PATH_LEN, 0440); |
||
34 | |||
35 | enum nvram_parser_state { |
||
36 | IDLE, |
||
37 | KEY, |
||
38 | VALUE, |
||
39 | COMMENT, |
||
40 | END |
||
41 | }; |
||
42 | |||
43 | /** |
||
44 | * struct nvram_parser - internal info for parser. |
||
45 | * |
||
46 | * @state: current parser state. |
||
47 | * @data: input buffer being parsed. |
||
48 | * @nvram: output buffer with parse result. |
||
49 | * @nvram_len: lenght of parse result. |
||
50 | * @line: current line. |
||
51 | * @column: current column in line. |
||
52 | * @pos: byte offset in input buffer. |
||
53 | * @entry: start position of key,value entry. |
||
54 | * @multi_dev_v1: detect pcie multi device v1 (compressed). |
||
55 | * @multi_dev_v2: detect pcie multi device v2. |
||
56 | */ |
||
57 | struct nvram_parser { |
||
58 | enum nvram_parser_state state; |
||
59 | const u8 *data; |
||
60 | u8 *nvram; |
||
61 | u32 nvram_len; |
||
62 | u32 line; |
||
63 | u32 column; |
||
64 | u32 pos; |
||
65 | u32 entry; |
||
66 | bool multi_dev_v1; |
||
67 | bool multi_dev_v2; |
||
68 | }; |
||
69 | |||
70 | /** |
||
71 | * is_nvram_char() - check if char is a valid one for NVRAM entry |
||
72 | * |
||
73 | * It accepts all printable ASCII chars except for '#' which opens a comment. |
||
74 | * Please note that ' ' (space) while accepted is not a valid key name char. |
||
75 | */ |
||
76 | static bool is_nvram_char(char c) |
||
77 | { |
||
78 | /* comment marker excluded */ |
||
79 | if (c == '#') |
||
80 | return false; |
||
81 | |||
82 | /* key and value may have any other readable character */ |
||
83 | return (c >= 0x20 && c < 0x7f); |
||
84 | } |
||
85 | |||
86 | static bool is_whitespace(char c) |
||
87 | { |
||
88 | return (c == ' ' || c == '\r' || c == '\n' || c == '\t'); |
||
89 | } |
||
90 | |||
91 | static enum nvram_parser_state brcmf_nvram_handle_idle(struct nvram_parser *nvp) |
||
92 | { |
||
93 | char c; |
||
94 | |||
95 | c = nvp->data[nvp->pos]; |
||
96 | if (c == '\n') |
||
97 | return COMMENT; |
||
98 | if (is_whitespace(c)) |
||
99 | goto proceed; |
||
100 | if (c == '#') |
||
101 | return COMMENT; |
||
102 | if (is_nvram_char(c)) { |
||
103 | nvp->entry = nvp->pos; |
||
104 | return KEY; |
||
105 | } |
||
106 | brcmf_dbg(INFO, "warning: ln=%d:col=%d: ignoring invalid character\n", |
||
107 | nvp->line, nvp->column); |
||
108 | proceed: |
||
109 | nvp->column++; |
||
110 | nvp->pos++; |
||
111 | return IDLE; |
||
112 | } |
||
113 | |||
114 | static enum nvram_parser_state brcmf_nvram_handle_key(struct nvram_parser *nvp) |
||
115 | { |
||
116 | enum nvram_parser_state st = nvp->state; |
||
117 | char c; |
||
118 | |||
119 | c = nvp->data[nvp->pos]; |
||
120 | if (c == '=') { |
||
121 | /* ignore RAW1 by treating as comment */ |
||
122 | if (strncmp(&nvp->data[nvp->entry], "RAW1", 4) == 0) |
||
123 | st = COMMENT; |
||
124 | else |
||
125 | st = VALUE; |
||
126 | if (strncmp(&nvp->data[nvp->entry], "devpath", 7) == 0) |
||
127 | nvp->multi_dev_v1 = true; |
||
128 | if (strncmp(&nvp->data[nvp->entry], "pcie/", 5) == 0) |
||
129 | nvp->multi_dev_v2 = true; |
||
130 | } else if (!is_nvram_char(c) || c == ' ') { |
||
131 | brcmf_dbg(INFO, "warning: ln=%d:col=%d: '=' expected, skip invalid key entry\n", |
||
132 | nvp->line, nvp->column); |
||
133 | return COMMENT; |
||
134 | } |
||
135 | |||
136 | nvp->column++; |
||
137 | nvp->pos++; |
||
138 | return st; |
||
139 | } |
||
140 | |||
141 | static enum nvram_parser_state |
||
142 | brcmf_nvram_handle_value(struct nvram_parser *nvp) |
||
143 | { |
||
144 | char c; |
||
145 | char *skv; |
||
146 | char *ekv; |
||
147 | u32 cplen; |
||
148 | |||
149 | c = nvp->data[nvp->pos]; |
||
150 | if (!is_nvram_char(c)) { |
||
151 | /* key,value pair complete */ |
||
152 | ekv = (u8 *)&nvp->data[nvp->pos]; |
||
153 | skv = (u8 *)&nvp->data[nvp->entry]; |
||
154 | cplen = ekv - skv; |
||
155 | if (nvp->nvram_len + cplen + 1 >= BRCMF_FW_MAX_NVRAM_SIZE) |
||
156 | return END; |
||
157 | /* copy to output buffer */ |
||
158 | memcpy(&nvp->nvram[nvp->nvram_len], skv, cplen); |
||
159 | nvp->nvram_len += cplen; |
||
160 | nvp->nvram[nvp->nvram_len] = '\0'; |
||
161 | nvp->nvram_len++; |
||
162 | return IDLE; |
||
163 | } |
||
164 | nvp->pos++; |
||
165 | nvp->column++; |
||
166 | return VALUE; |
||
167 | } |
||
168 | |||
169 | static enum nvram_parser_state |
||
170 | brcmf_nvram_handle_comment(struct nvram_parser *nvp) |
||
171 | { |
||
172 | char *eoc, *sol; |
||
173 | |||
174 | sol = (char *)&nvp->data[nvp->pos]; |
||
175 | eoc = strchr(sol, '\n'); |
||
176 | if (!eoc) { |
||
177 | eoc = strchr(sol, '\0'); |
||
178 | if (!eoc) |
||
179 | return END; |
||
180 | } |
||
181 | |||
182 | /* eat all moving to next line */ |
||
183 | nvp->line++; |
||
184 | nvp->column = 1; |
||
185 | nvp->pos += (eoc - sol) + 1; |
||
186 | return IDLE; |
||
187 | } |
||
188 | |||
189 | static enum nvram_parser_state brcmf_nvram_handle_end(struct nvram_parser *nvp) |
||
190 | { |
||
191 | /* final state */ |
||
192 | return END; |
||
193 | } |
||
194 | |||
195 | static enum nvram_parser_state |
||
196 | (*nv_parser_states[])(struct nvram_parser *nvp) = { |
||
197 | brcmf_nvram_handle_idle, |
||
198 | brcmf_nvram_handle_key, |
||
199 | brcmf_nvram_handle_value, |
||
200 | brcmf_nvram_handle_comment, |
||
201 | brcmf_nvram_handle_end |
||
202 | }; |
||
203 | |||
204 | static int brcmf_init_nvram_parser(struct nvram_parser *nvp, |
||
205 | const u8 *data, size_t data_len) |
||
206 | { |
||
207 | size_t size; |
||
208 | |||
209 | memset(nvp, 0, sizeof(*nvp)); |
||
210 | nvp->data = data; |
||
211 | /* Limit size to MAX_NVRAM_SIZE, some files contain lot of comment */ |
||
212 | if (data_len > BRCMF_FW_MAX_NVRAM_SIZE) |
||
213 | size = BRCMF_FW_MAX_NVRAM_SIZE; |
||
214 | else |
||
215 | size = data_len; |
||
216 | /* Alloc for extra 0 byte + roundup by 4 + length field */ |
||
217 | size += 1 + 3 + sizeof(u32); |
||
218 | nvp->nvram = kzalloc(size, GFP_KERNEL); |
||
219 | if (!nvp->nvram) |
||
220 | return -ENOMEM; |
||
221 | |||
222 | nvp->line = 1; |
||
223 | nvp->column = 1; |
||
224 | return 0; |
||
225 | } |
||
226 | |||
227 | /* brcmf_fw_strip_multi_v1 :Some nvram files contain settings for multiple |
||
228 | * devices. Strip it down for one device, use domain_nr/bus_nr to determine |
||
229 | * which data is to be returned. v1 is the version where nvram is stored |
||
230 | * compressed and "devpath" maps to index for valid entries. |
||
231 | */ |
||
232 | static void brcmf_fw_strip_multi_v1(struct nvram_parser *nvp, u16 domain_nr, |
||
233 | u16 bus_nr) |
||
234 | { |
||
235 | /* Device path with a leading '=' key-value separator */ |
||
236 | char pci_path[] = "=pci/?/?"; |
||
237 | size_t pci_len; |
||
238 | char pcie_path[] = "=pcie/?/?"; |
||
239 | size_t pcie_len; |
||
240 | |||
241 | u32 i, j; |
||
242 | bool found; |
||
243 | u8 *nvram; |
||
244 | u8 id; |
||
245 | |||
246 | nvram = kzalloc(nvp->nvram_len + 1 + 3 + sizeof(u32), GFP_KERNEL); |
||
247 | if (!nvram) |
||
248 | goto fail; |
||
249 | |||
250 | /* min length: devpath0=pcie/1/4/ + 0:x=y */ |
||
251 | if (nvp->nvram_len < BRCMF_FW_NVRAM_DEVPATH_LEN + 6) |
||
252 | goto fail; |
||
253 | |||
254 | /* First search for the devpathX and see if it is the configuration |
||
255 | * for domain_nr/bus_nr. Search complete nvp |
||
256 | */ |
||
257 | snprintf(pci_path, sizeof(pci_path), "=pci/%d/%d", domain_nr, |
||
258 | bus_nr); |
||
259 | pci_len = strlen(pci_path); |
||
260 | snprintf(pcie_path, sizeof(pcie_path), "=pcie/%d/%d", domain_nr, |
||
261 | bus_nr); |
||
262 | pcie_len = strlen(pcie_path); |
||
263 | found = false; |
||
264 | i = 0; |
||
265 | while (i < nvp->nvram_len - BRCMF_FW_NVRAM_DEVPATH_LEN) { |
||
266 | /* Format: devpathX=pcie/Y/Z/ |
||
267 | * Y = domain_nr, Z = bus_nr, X = virtual ID |
||
268 | */ |
||
269 | if (strncmp(&nvp->nvram[i], "devpath", 7) == 0 && |
||
270 | (!strncmp(&nvp->nvram[i + 8], pci_path, pci_len) || |
||
271 | !strncmp(&nvp->nvram[i + 8], pcie_path, pcie_len))) { |
||
272 | id = nvp->nvram[i + 7] - '0'; |
||
273 | found = true; |
||
274 | break; |
||
275 | } |
||
276 | while (nvp->nvram[i] != 0) |
||
277 | i++; |
||
278 | i++; |
||
279 | } |
||
280 | if (!found) |
||
281 | goto fail; |
||
282 | |||
283 | /* Now copy all valid entries, release old nvram and assign new one */ |
||
284 | i = 0; |
||
285 | j = 0; |
||
286 | while (i < nvp->nvram_len) { |
||
287 | if ((nvp->nvram[i] - '0' == id) && (nvp->nvram[i + 1] == ':')) { |
||
288 | i += 2; |
||
289 | while (nvp->nvram[i] != 0) { |
||
290 | nvram[j] = nvp->nvram[i]; |
||
291 | i++; |
||
292 | j++; |
||
293 | } |
||
294 | nvram[j] = 0; |
||
295 | j++; |
||
296 | } |
||
297 | while (nvp->nvram[i] != 0) |
||
298 | i++; |
||
299 | i++; |
||
300 | } |
||
301 | kfree(nvp->nvram); |
||
302 | nvp->nvram = nvram; |
||
303 | nvp->nvram_len = j; |
||
304 | return; |
||
305 | |||
306 | fail: |
||
307 | kfree(nvram); |
||
308 | nvp->nvram_len = 0; |
||
309 | } |
||
310 | |||
311 | /* brcmf_fw_strip_multi_v2 :Some nvram files contain settings for multiple |
||
312 | * devices. Strip it down for one device, use domain_nr/bus_nr to determine |
||
313 | * which data is to be returned. v2 is the version where nvram is stored |
||
314 | * uncompressed, all relevant valid entries are identified by |
||
315 | * pcie/domain_nr/bus_nr: |
||
316 | */ |
||
317 | static void brcmf_fw_strip_multi_v2(struct nvram_parser *nvp, u16 domain_nr, |
||
318 | u16 bus_nr) |
||
319 | { |
||
320 | char prefix[BRCMF_FW_NVRAM_PCIEDEV_LEN]; |
||
321 | size_t len; |
||
322 | u32 i, j; |
||
323 | u8 *nvram; |
||
324 | |||
325 | nvram = kzalloc(nvp->nvram_len + 1 + 3 + sizeof(u32), GFP_KERNEL); |
||
326 | if (!nvram) |
||
327 | goto fail; |
||
328 | |||
329 | /* Copy all valid entries, release old nvram and assign new one. |
||
330 | * Valid entries are of type pcie/X/Y/ where X = domain_nr and |
||
331 | * Y = bus_nr. |
||
332 | */ |
||
333 | snprintf(prefix, sizeof(prefix), "pcie/%d/%d/", domain_nr, bus_nr); |
||
334 | len = strlen(prefix); |
||
335 | i = 0; |
||
336 | j = 0; |
||
337 | while (i < nvp->nvram_len - len) { |
||
338 | if (strncmp(&nvp->nvram[i], prefix, len) == 0) { |
||
339 | i += len; |
||
340 | while (nvp->nvram[i] != 0) { |
||
341 | nvram[j] = nvp->nvram[i]; |
||
342 | i++; |
||
343 | j++; |
||
344 | } |
||
345 | nvram[j] = 0; |
||
346 | j++; |
||
347 | } |
||
348 | while (nvp->nvram[i] != 0) |
||
349 | i++; |
||
350 | i++; |
||
351 | } |
||
352 | kfree(nvp->nvram); |
||
353 | nvp->nvram = nvram; |
||
354 | nvp->nvram_len = j; |
||
355 | return; |
||
356 | fail: |
||
357 | kfree(nvram); |
||
358 | nvp->nvram_len = 0; |
||
359 | } |
||
360 | |||
361 | /* brcmf_nvram_strip :Takes a buffer of "<var>=<value>\n" lines read from a fil |
||
362 | * and ending in a NUL. Removes carriage returns, empty lines, comment lines, |
||
363 | * and converts newlines to NULs. Shortens buffer as needed and pads with NULs. |
||
364 | * End of buffer is completed with token identifying length of buffer. |
||
365 | */ |
||
366 | static void *brcmf_fw_nvram_strip(const u8 *data, size_t data_len, |
||
367 | u32 *new_length, u16 domain_nr, u16 bus_nr) |
||
368 | { |
||
369 | struct nvram_parser nvp; |
||
370 | u32 pad; |
||
371 | u32 token; |
||
372 | __le32 token_le; |
||
373 | |||
374 | if (brcmf_init_nvram_parser(&nvp, data, data_len) < 0) |
||
375 | return NULL; |
||
376 | |||
377 | while (nvp.pos < data_len) { |
||
378 | nvp.state = nv_parser_states[nvp.state](&nvp); |
||
379 | if (nvp.state == END) |
||
380 | break; |
||
381 | } |
||
382 | if (nvp.multi_dev_v1) |
||
383 | brcmf_fw_strip_multi_v1(&nvp, domain_nr, bus_nr); |
||
384 | else if (nvp.multi_dev_v2) |
||
385 | brcmf_fw_strip_multi_v2(&nvp, domain_nr, bus_nr); |
||
386 | |||
387 | if (nvp.nvram_len == 0) { |
||
388 | kfree(nvp.nvram); |
||
389 | return NULL; |
||
390 | } |
||
391 | |||
392 | pad = nvp.nvram_len; |
||
393 | *new_length = roundup(nvp.nvram_len + 1, 4); |
||
394 | while (pad != *new_length) { |
||
395 | nvp.nvram[pad] = 0; |
||
396 | pad++; |
||
397 | } |
||
398 | |||
399 | token = *new_length / 4; |
||
400 | token = (~token << 16) | (token & 0x0000FFFF); |
||
401 | token_le = cpu_to_le32(token); |
||
402 | |||
403 | memcpy(&nvp.nvram[*new_length], &token_le, sizeof(token_le)); |
||
404 | *new_length += sizeof(token_le); |
||
405 | |||
406 | return nvp.nvram; |
||
407 | } |
||
408 | |||
409 | void brcmf_fw_nvram_free(void *nvram) |
||
410 | { |
||
411 | kfree(nvram); |
||
412 | } |
||
413 | |||
414 | struct brcmf_fw { |
||
415 | struct device *dev; |
||
416 | u16 flags; |
||
417 | const struct firmware *code; |
||
418 | const char *nvram_name; |
||
419 | u16 domain_nr; |
||
420 | u16 bus_nr; |
||
421 | void (*done)(struct device *dev, const struct firmware *fw, |
||
422 | void *nvram_image, u32 nvram_len); |
||
423 | }; |
||
424 | |||
425 | static void brcmf_fw_request_nvram_done(const struct firmware *fw, void *ctx) |
||
426 | { |
||
427 | struct brcmf_fw *fwctx = ctx; |
||
428 | u32 nvram_length = 0; |
||
429 | void *nvram = NULL; |
||
430 | u8 *data = NULL; |
||
431 | size_t data_len; |
||
432 | bool raw_nvram; |
||
433 | |||
434 | brcmf_dbg(TRACE, "enter: dev=%s\n", dev_name(fwctx->dev)); |
||
435 | if (fw && fw->data) { |
||
436 | data = (u8 *)fw->data; |
||
437 | data_len = fw->size; |
||
438 | raw_nvram = false; |
||
439 | } else { |
||
440 | data = bcm47xx_nvram_get_contents(&data_len); |
||
441 | if (!data && !(fwctx->flags & BRCMF_FW_REQ_NV_OPTIONAL)) |
||
442 | goto fail; |
||
443 | raw_nvram = true; |
||
444 | } |
||
445 | |||
446 | if (data) |
||
447 | nvram = brcmf_fw_nvram_strip(data, data_len, &nvram_length, |
||
448 | fwctx->domain_nr, fwctx->bus_nr); |
||
449 | |||
450 | if (raw_nvram) |
||
451 | bcm47xx_nvram_release_contents(data); |
||
452 | if (fw) |
||
453 | release_firmware(fw); |
||
454 | if (!nvram && !(fwctx->flags & BRCMF_FW_REQ_NV_OPTIONAL)) |
||
455 | goto fail; |
||
456 | |||
457 | fwctx->done(fwctx->dev, fwctx->code, nvram, nvram_length); |
||
458 | kfree(fwctx); |
||
459 | return; |
||
460 | |||
461 | fail: |
||
462 | brcmf_dbg(TRACE, "failed: dev=%s\n", dev_name(fwctx->dev)); |
||
463 | release_firmware(fwctx->code); |
||
464 | device_release_driver(fwctx->dev); |
||
465 | kfree(fwctx); |
||
466 | } |
||
467 | |||
468 | static void brcmf_fw_request_code_done(const struct firmware *fw, void *ctx) |
||
469 | { |
||
470 | struct brcmf_fw *fwctx = ctx; |
||
471 | int ret; |
||
472 | |||
473 | brcmf_dbg(TRACE, "enter: dev=%s\n", dev_name(fwctx->dev)); |
||
474 | if (!fw) |
||
475 | goto fail; |
||
476 | |||
477 | /* only requested code so done here */ |
||
478 | if (!(fwctx->flags & BRCMF_FW_REQUEST_NVRAM)) { |
||
479 | fwctx->done(fwctx->dev, fw, NULL, 0); |
||
480 | kfree(fwctx); |
||
481 | return; |
||
482 | } |
||
483 | fwctx->code = fw; |
||
484 | ret = request_firmware_nowait(THIS_MODULE, true, fwctx->nvram_name, |
||
485 | fwctx->dev, GFP_KERNEL, fwctx, |
||
486 | brcmf_fw_request_nvram_done); |
||
487 | |||
488 | if (!ret) |
||
489 | return; |
||
490 | |||
491 | brcmf_fw_request_nvram_done(NULL, fwctx); |
||
492 | return; |
||
493 | |||
494 | fail: |
||
495 | brcmf_dbg(TRACE, "failed: dev=%s\n", dev_name(fwctx->dev)); |
||
496 | device_release_driver(fwctx->dev); |
||
497 | kfree(fwctx); |
||
498 | } |
||
499 | |||
500 | int brcmf_fw_get_firmwares_pcie(struct device *dev, u16 flags, |
||
501 | const char *code, const char *nvram, |
||
502 | void (*fw_cb)(struct device *dev, |
||
503 | const struct firmware *fw, |
||
504 | void *nvram_image, u32 nvram_len), |
||
505 | u16 domain_nr, u16 bus_nr) |
||
506 | { |
||
507 | struct brcmf_fw *fwctx; |
||
508 | |||
509 | brcmf_dbg(TRACE, "enter: dev=%s\n", dev_name(dev)); |
||
510 | if (!fw_cb || !code) |
||
511 | return -EINVAL; |
||
512 | |||
513 | if ((flags & BRCMF_FW_REQUEST_NVRAM) && !nvram) |
||
514 | return -EINVAL; |
||
515 | |||
516 | fwctx = kzalloc(sizeof(*fwctx), GFP_KERNEL); |
||
517 | if (!fwctx) |
||
518 | return -ENOMEM; |
||
519 | |||
520 | fwctx->dev = dev; |
||
521 | fwctx->flags = flags; |
||
522 | fwctx->done = fw_cb; |
||
523 | if (flags & BRCMF_FW_REQUEST_NVRAM) |
||
524 | fwctx->nvram_name = nvram; |
||
525 | fwctx->domain_nr = domain_nr; |
||
526 | fwctx->bus_nr = bus_nr; |
||
527 | |||
528 | return request_firmware_nowait(THIS_MODULE, true, code, dev, |
||
529 | GFP_KERNEL, fwctx, |
||
530 | brcmf_fw_request_code_done); |
||
531 | } |
||
532 | |||
533 | int brcmf_fw_get_firmwares(struct device *dev, u16 flags, |
||
534 | const char *code, const char *nvram, |
||
535 | void (*fw_cb)(struct device *dev, |
||
536 | const struct firmware *fw, |
||
537 | void *nvram_image, u32 nvram_len)) |
||
538 | { |
||
539 | return brcmf_fw_get_firmwares_pcie(dev, flags, code, nvram, fw_cb, 0, |
||
540 | 0); |
||
541 | } |
||
542 |