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