]>
Commit | Line | Data |
---|---|---|
a74d036f HM |
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> | |
c1416e77 | 19 | #include <linux/device.h> |
a74d036f | 20 | #include <linux/firmware.h> |
c1b20532 | 21 | #include <linux/module.h> |
4e70f214 | 22 | #include <linux/bcm47xx_nvram.h> |
a74d036f | 23 | |
a8e8ed34 | 24 | #include "debug.h" |
dabedab9 | 25 | #include "firmware.h" |
a74d036f | 26 | |
c4365534 HM |
27 | #define BRCMF_FW_MAX_NVRAM_SIZE 64000 |
28 | #define BRCMF_FW_NVRAM_DEVPATH_LEN 19 /* devpath0=pcie/1/4/ */ | |
ae8c2366 | 29 | #define BRCMF_FW_NVRAM_PCIEDEV_LEN 10 /* pcie/1/4/ + \0 */ |
c4365534 | 30 | |
c1b20532 DK |
31 | char brcmf_firmware_path[BRCMF_FW_PATH_LEN]; |
32 | module_param_string(firmware_path, brcmf_firmware_path, | |
33 | BRCMF_FW_PATH_LEN, 0440); | |
34 | ||
3e99b08a AS |
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. | |
11f09d4b | 47 | * @data: input buffer being parsed. |
3e99b08a AS |
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. | |
c4365534 HM |
54 | * @multi_dev_v1: detect pcie multi device v1 (compressed). |
55 | * @multi_dev_v2: detect pcie multi device v2. | |
3e99b08a AS |
56 | */ |
57 | struct nvram_parser { | |
58 | enum nvram_parser_state state; | |
11f09d4b | 59 | const u8 *data; |
3e99b08a AS |
60 | u8 *nvram; |
61 | u32 nvram_len; | |
62 | u32 line; | |
63 | u32 column; | |
64 | u32 pos; | |
65 | u32 entry; | |
c4365534 HM |
66 | bool multi_dev_v1; |
67 | bool multi_dev_v2; | |
3e99b08a AS |
68 | }; |
69 | ||
fc23e81e RM |
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 | */ | |
3e99b08a AS |
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 */ | |
fc23e81e | 83 | return (c >= 0x20 && c < 0x7f); |
3e99b08a AS |
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 | ||
11f09d4b | 95 | c = nvp->data[nvp->pos]; |
3e99b08a AS |
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 | ||
11f09d4b | 119 | c = nvp->data[nvp->pos]; |
3e99b08a | 120 | if (c == '=') { |
4165fe9a | 121 | /* ignore RAW1 by treating as comment */ |
11f09d4b | 122 | if (strncmp(&nvp->data[nvp->entry], "RAW1", 4) == 0) |
4165fe9a AS |
123 | st = COMMENT; |
124 | else | |
125 | st = VALUE; | |
11f09d4b | 126 | if (strncmp(&nvp->data[nvp->entry], "devpath", 7) == 0) |
c4365534 | 127 | nvp->multi_dev_v1 = true; |
11f09d4b | 128 | if (strncmp(&nvp->data[nvp->entry], "pcie/", 5) == 0) |
c4365534 | 129 | nvp->multi_dev_v2 = true; |
fc23e81e | 130 | } else if (!is_nvram_char(c) || c == ' ') { |
3e99b08a AS |
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 | ||
11f09d4b | 149 | c = nvp->data[nvp->pos]; |
3e99b08a AS |
150 | if (!is_nvram_char(c)) { |
151 | /* key,value pair complete */ | |
11f09d4b RM |
152 | ekv = (u8 *)&nvp->data[nvp->pos]; |
153 | skv = (u8 *)&nvp->data[nvp->entry]; | |
3e99b08a | 154 | cplen = ekv - skv; |
c4365534 HM |
155 | if (nvp->nvram_len + cplen + 1 >= BRCMF_FW_MAX_NVRAM_SIZE) |
156 | return END; | |
3e99b08a AS |
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 | { | |
279b4cb7 | 172 | char *eoc, *sol; |
3e99b08a | 173 | |
11f09d4b | 174 | sol = (char *)&nvp->data[nvp->pos]; |
279b4cb7 RM |
175 | eoc = strchr(sol, '\n'); |
176 | if (!eoc) { | |
177 | eoc = strchr(sol, '\0'); | |
178 | if (!eoc) | |
179 | return END; | |
180 | } | |
3e99b08a AS |
181 | |
182 | /* eat all moving to next line */ | |
183 | nvp->line++; | |
184 | nvp->column = 1; | |
279b4cb7 | 185 | nvp->pos += (eoc - sol) + 1; |
3e99b08a AS |
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, | |
11f09d4b | 205 | const u8 *data, size_t data_len) |
3e99b08a | 206 | { |
c4365534 HM |
207 | size_t size; |
208 | ||
3e99b08a | 209 | memset(nvp, 0, sizeof(*nvp)); |
11f09d4b | 210 | nvp->data = data; |
c4365534 | 211 | /* Limit size to MAX_NVRAM_SIZE, some files contain lot of comment */ |
11f09d4b | 212 | if (data_len > BRCMF_FW_MAX_NVRAM_SIZE) |
c4365534 HM |
213 | size = BRCMF_FW_MAX_NVRAM_SIZE; |
214 | else | |
11f09d4b | 215 | size = data_len; |
3e99b08a | 216 | /* Alloc for extra 0 byte + roundup by 4 + length field */ |
c4365534 HM |
217 | size += 1 + 3 + sizeof(u32); |
218 | nvp->nvram = kzalloc(size, GFP_KERNEL); | |
3e99b08a AS |
219 | if (!nvp->nvram) |
220 | return -ENOMEM; | |
221 | ||
222 | nvp->line = 1; | |
223 | nvp->column = 1; | |
224 | return 0; | |
225 | } | |
226 | ||
c4365534 HM |
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 | { | |
5d08408b | 235 | /* Device path with a leading '=' key-value separator */ |
f33d5915 RM |
236 | char pci_path[] = "=pci/?/?"; |
237 | size_t pci_len; | |
5d08408b RM |
238 | char pcie_path[] = "=pcie/?/?"; |
239 | size_t pcie_len; | |
240 | ||
c4365534 HM |
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 | */ | |
f33d5915 RM |
257 | snprintf(pci_path, sizeof(pci_path), "=pci/%d/%d", domain_nr, |
258 | bus_nr); | |
259 | pci_len = strlen(pci_path); | |
5d08408b RM |
260 | snprintf(pcie_path, sizeof(pcie_path), "=pcie/%d/%d", domain_nr, |
261 | bus_nr); | |
262 | pcie_len = strlen(pcie_path); | |
c4365534 HM |
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 | */ | |
f33d5915 RM |
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))) { | |
5d08408b RM |
272 | id = nvp->nvram[i + 7] - '0'; |
273 | found = true; | |
274 | break; | |
c4365534 HM |
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 | { | |
ae8c2366 RM |
320 | char prefix[BRCMF_FW_NVRAM_PCIEDEV_LEN]; |
321 | size_t len; | |
c4365534 HM |
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 | */ | |
ae8c2366 RM |
333 | snprintf(prefix, sizeof(prefix), "pcie/%d/%d/", domain_nr, bus_nr); |
334 | len = strlen(prefix); | |
c4365534 HM |
335 | i = 0; |
336 | j = 0; | |
ae8c2366 RM |
337 | while (i < nvp->nvram_len - len) { |
338 | if (strncmp(&nvp->nvram[i], prefix, len) == 0) { | |
339 | i += len; | |
c4365534 HM |
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 | ||
3e99b08a | 361 | /* brcmf_nvram_strip :Takes a buffer of "<var>=<value>\n" lines read from a fil |
a74d036f HM |
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 | */ | |
11f09d4b RM |
366 | static void *brcmf_fw_nvram_strip(const u8 *data, size_t data_len, |
367 | u32 *new_length, u16 domain_nr, u16 bus_nr) | |
a74d036f | 368 | { |
3e99b08a AS |
369 | struct nvram_parser nvp; |
370 | u32 pad; | |
a74d036f HM |
371 | u32 token; |
372 | __le32 token_le; | |
373 | ||
11f09d4b | 374 | if (brcmf_init_nvram_parser(&nvp, data, data_len) < 0) |
a74d036f HM |
375 | return NULL; |
376 | ||
11f09d4b | 377 | while (nvp.pos < data_len) { |
3e99b08a AS |
378 | nvp.state = nv_parser_states[nvp.state](&nvp); |
379 | if (nvp.state == END) | |
a74d036f | 380 | break; |
a74d036f | 381 | } |
c4365534 HM |
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 | ||
3e99b08a AS |
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++; | |
a74d036f HM |
397 | } |
398 | ||
399 | token = *new_length / 4; | |
400 | token = (~token << 16) | (token & 0x0000FFFF); | |
401 | token_le = cpu_to_le32(token); | |
402 | ||
3e99b08a | 403 | memcpy(&nvp.nvram[*new_length], &token_le, sizeof(token_le)); |
a74d036f HM |
404 | *new_length += sizeof(token_le); |
405 | ||
3e99b08a | 406 | return nvp.nvram; |
a74d036f HM |
407 | } |
408 | ||
dabedab9 | 409 | void brcmf_fw_nvram_free(void *nvram) |
a74d036f HM |
410 | { |
411 | kfree(nvram); | |
412 | } | |
413 | ||
c1416e77 AS |
414 | struct brcmf_fw { |
415 | struct device *dev; | |
416 | u16 flags; | |
417 | const struct firmware *code; | |
418 | const char *nvram_name; | |
c4365534 HM |
419 | u16 domain_nr; |
420 | u16 bus_nr; | |
c1416e77 AS |
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; | |
4e70f214 HM |
430 | u8 *data = NULL; |
431 | size_t data_len; | |
432 | bool raw_nvram; | |
c1416e77 AS |
433 | |
434 | brcmf_dbg(TRACE, "enter: dev=%s\n", dev_name(fwctx->dev)); | |
4e70f214 HM |
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 | } | |
c1416e77 | 445 | |
4e70f214 HM |
446 | if (data) |
447 | nvram = brcmf_fw_nvram_strip(data, data_len, &nvram_length, | |
c4365534 | 448 | fwctx->domain_nr, fwctx->bus_nr); |
4e70f214 HM |
449 | |
450 | if (raw_nvram) | |
451 | bcm47xx_nvram_release_contents(data); | |
452 | if (fw) | |
c1416e77 | 453 | release_firmware(fw); |
4e70f214 HM |
454 | if (!nvram && !(fwctx->flags & BRCMF_FW_REQ_NV_OPTIONAL)) |
455 | goto fail; | |
c1416e77 AS |
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)); | |
ac96ce83 | 463 | release_firmware(fwctx->code); |
c1416e77 AS |
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 | ||
4e70f214 HM |
491 | brcmf_fw_request_nvram_done(NULL, fwctx); |
492 | return; | |
c1416e77 | 493 | |
c1416e77 AS |
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 | ||
c4365534 HM |
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) | |
c1416e77 AS |
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; | |
c4365534 HM |
525 | fwctx->domain_nr = domain_nr; |
526 | fwctx->bus_nr = bus_nr; | |
c1416e77 AS |
527 | |
528 | return request_firmware_nowait(THIS_MODULE, true, code, dev, | |
529 | GFP_KERNEL, fwctx, | |
530 | brcmf_fw_request_code_done); | |
531 | } | |
c4365534 HM |
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 |