1*4882a593Smuzhiyun // SPDX-License-Identifier: ISC
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * Copyright (c) 2013 Broadcom Corporation
4*4882a593Smuzhiyun */
5*4882a593Smuzhiyun
6*4882a593Smuzhiyun #include <linux/efi.h>
7*4882a593Smuzhiyun #include <linux/kernel.h>
8*4882a593Smuzhiyun #include <linux/slab.h>
9*4882a593Smuzhiyun #include <linux/device.h>
10*4882a593Smuzhiyun #include <linux/firmware.h>
11*4882a593Smuzhiyun #include <linux/module.h>
12*4882a593Smuzhiyun #include <linux/bcm47xx_nvram.h>
13*4882a593Smuzhiyun
14*4882a593Smuzhiyun #include "debug.h"
15*4882a593Smuzhiyun #include "firmware.h"
16*4882a593Smuzhiyun #include "core.h"
17*4882a593Smuzhiyun #include "common.h"
18*4882a593Smuzhiyun #include "chip.h"
19*4882a593Smuzhiyun
20*4882a593Smuzhiyun #define BRCMF_FW_MAX_NVRAM_SIZE 64000
21*4882a593Smuzhiyun #define BRCMF_FW_NVRAM_DEVPATH_LEN 19 /* devpath0=pcie/1/4/ */
22*4882a593Smuzhiyun #define BRCMF_FW_NVRAM_PCIEDEV_LEN 10 /* pcie/1/4/ + \0 */
23*4882a593Smuzhiyun #define BRCMF_FW_DEFAULT_BOARDREV "boardrev=0xff"
24*4882a593Smuzhiyun
25*4882a593Smuzhiyun enum nvram_parser_state {
26*4882a593Smuzhiyun IDLE,
27*4882a593Smuzhiyun KEY,
28*4882a593Smuzhiyun VALUE,
29*4882a593Smuzhiyun COMMENT,
30*4882a593Smuzhiyun END
31*4882a593Smuzhiyun };
32*4882a593Smuzhiyun
33*4882a593Smuzhiyun /**
34*4882a593Smuzhiyun * struct nvram_parser - internal info for parser.
35*4882a593Smuzhiyun *
36*4882a593Smuzhiyun * @state: current parser state.
37*4882a593Smuzhiyun * @data: input buffer being parsed.
38*4882a593Smuzhiyun * @nvram: output buffer with parse result.
39*4882a593Smuzhiyun * @nvram_len: length of parse result.
40*4882a593Smuzhiyun * @line: current line.
41*4882a593Smuzhiyun * @column: current column in line.
42*4882a593Smuzhiyun * @pos: byte offset in input buffer.
43*4882a593Smuzhiyun * @entry: start position of key,value entry.
44*4882a593Smuzhiyun * @multi_dev_v1: detect pcie multi device v1 (compressed).
45*4882a593Smuzhiyun * @multi_dev_v2: detect pcie multi device v2.
46*4882a593Smuzhiyun * @boardrev_found: nvram contains boardrev information.
47*4882a593Smuzhiyun */
48*4882a593Smuzhiyun struct nvram_parser {
49*4882a593Smuzhiyun enum nvram_parser_state state;
50*4882a593Smuzhiyun const u8 *data;
51*4882a593Smuzhiyun u8 *nvram;
52*4882a593Smuzhiyun u32 nvram_len;
53*4882a593Smuzhiyun u32 line;
54*4882a593Smuzhiyun u32 column;
55*4882a593Smuzhiyun u32 pos;
56*4882a593Smuzhiyun u32 entry;
57*4882a593Smuzhiyun bool multi_dev_v1;
58*4882a593Smuzhiyun bool multi_dev_v2;
59*4882a593Smuzhiyun bool boardrev_found;
60*4882a593Smuzhiyun };
61*4882a593Smuzhiyun
62*4882a593Smuzhiyun /*
63*4882a593Smuzhiyun * is_nvram_char() - check if char is a valid one for NVRAM entry
64*4882a593Smuzhiyun *
65*4882a593Smuzhiyun * It accepts all printable ASCII chars except for '#' which opens a comment.
66*4882a593Smuzhiyun * Please note that ' ' (space) while accepted is not a valid key name char.
67*4882a593Smuzhiyun */
is_nvram_char(char c)68*4882a593Smuzhiyun static bool is_nvram_char(char c)
69*4882a593Smuzhiyun {
70*4882a593Smuzhiyun /* comment marker excluded */
71*4882a593Smuzhiyun if (c == '#')
72*4882a593Smuzhiyun return false;
73*4882a593Smuzhiyun
74*4882a593Smuzhiyun /* key and value may have any other readable character */
75*4882a593Smuzhiyun return (c >= 0x20 && c < 0x7f);
76*4882a593Smuzhiyun }
77*4882a593Smuzhiyun
is_whitespace(char c)78*4882a593Smuzhiyun static bool is_whitespace(char c)
79*4882a593Smuzhiyun {
80*4882a593Smuzhiyun return (c == ' ' || c == '\r' || c == '\n' || c == '\t');
81*4882a593Smuzhiyun }
82*4882a593Smuzhiyun
brcmf_nvram_handle_idle(struct nvram_parser * nvp)83*4882a593Smuzhiyun static enum nvram_parser_state brcmf_nvram_handle_idle(struct nvram_parser *nvp)
84*4882a593Smuzhiyun {
85*4882a593Smuzhiyun char c;
86*4882a593Smuzhiyun
87*4882a593Smuzhiyun c = nvp->data[nvp->pos];
88*4882a593Smuzhiyun if (c == '\n')
89*4882a593Smuzhiyun return COMMENT;
90*4882a593Smuzhiyun if (is_whitespace(c) || c == '\0')
91*4882a593Smuzhiyun goto proceed;
92*4882a593Smuzhiyun if (c == '#')
93*4882a593Smuzhiyun return COMMENT;
94*4882a593Smuzhiyun if (is_nvram_char(c)) {
95*4882a593Smuzhiyun nvp->entry = nvp->pos;
96*4882a593Smuzhiyun return KEY;
97*4882a593Smuzhiyun }
98*4882a593Smuzhiyun brcmf_dbg(INFO, "warning: ln=%d:col=%d: ignoring invalid character\n",
99*4882a593Smuzhiyun nvp->line, nvp->column);
100*4882a593Smuzhiyun proceed:
101*4882a593Smuzhiyun nvp->column++;
102*4882a593Smuzhiyun nvp->pos++;
103*4882a593Smuzhiyun return IDLE;
104*4882a593Smuzhiyun }
105*4882a593Smuzhiyun
brcmf_nvram_handle_key(struct nvram_parser * nvp)106*4882a593Smuzhiyun static enum nvram_parser_state brcmf_nvram_handle_key(struct nvram_parser *nvp)
107*4882a593Smuzhiyun {
108*4882a593Smuzhiyun enum nvram_parser_state st = nvp->state;
109*4882a593Smuzhiyun char c;
110*4882a593Smuzhiyun
111*4882a593Smuzhiyun c = nvp->data[nvp->pos];
112*4882a593Smuzhiyun if (c == '=') {
113*4882a593Smuzhiyun /* ignore RAW1 by treating as comment */
114*4882a593Smuzhiyun if (strncmp(&nvp->data[nvp->entry], "RAW1", 4) == 0)
115*4882a593Smuzhiyun st = COMMENT;
116*4882a593Smuzhiyun else
117*4882a593Smuzhiyun st = VALUE;
118*4882a593Smuzhiyun if (strncmp(&nvp->data[nvp->entry], "devpath", 7) == 0)
119*4882a593Smuzhiyun nvp->multi_dev_v1 = true;
120*4882a593Smuzhiyun if (strncmp(&nvp->data[nvp->entry], "pcie/", 5) == 0)
121*4882a593Smuzhiyun nvp->multi_dev_v2 = true;
122*4882a593Smuzhiyun if (strncmp(&nvp->data[nvp->entry], "boardrev", 8) == 0)
123*4882a593Smuzhiyun nvp->boardrev_found = true;
124*4882a593Smuzhiyun } else if (!is_nvram_char(c) || c == ' ') {
125*4882a593Smuzhiyun brcmf_dbg(INFO, "warning: ln=%d:col=%d: '=' expected, skip invalid key entry\n",
126*4882a593Smuzhiyun nvp->line, nvp->column);
127*4882a593Smuzhiyun return COMMENT;
128*4882a593Smuzhiyun }
129*4882a593Smuzhiyun
130*4882a593Smuzhiyun nvp->column++;
131*4882a593Smuzhiyun nvp->pos++;
132*4882a593Smuzhiyun return st;
133*4882a593Smuzhiyun }
134*4882a593Smuzhiyun
135*4882a593Smuzhiyun static enum nvram_parser_state
brcmf_nvram_handle_value(struct nvram_parser * nvp)136*4882a593Smuzhiyun brcmf_nvram_handle_value(struct nvram_parser *nvp)
137*4882a593Smuzhiyun {
138*4882a593Smuzhiyun char c;
139*4882a593Smuzhiyun char *skv;
140*4882a593Smuzhiyun char *ekv;
141*4882a593Smuzhiyun u32 cplen;
142*4882a593Smuzhiyun
143*4882a593Smuzhiyun c = nvp->data[nvp->pos];
144*4882a593Smuzhiyun if (!is_nvram_char(c)) {
145*4882a593Smuzhiyun /* key,value pair complete */
146*4882a593Smuzhiyun ekv = (u8 *)&nvp->data[nvp->pos];
147*4882a593Smuzhiyun skv = (u8 *)&nvp->data[nvp->entry];
148*4882a593Smuzhiyun cplen = ekv - skv;
149*4882a593Smuzhiyun if (nvp->nvram_len + cplen + 1 >= BRCMF_FW_MAX_NVRAM_SIZE)
150*4882a593Smuzhiyun return END;
151*4882a593Smuzhiyun /* copy to output buffer */
152*4882a593Smuzhiyun memcpy(&nvp->nvram[nvp->nvram_len], skv, cplen);
153*4882a593Smuzhiyun nvp->nvram_len += cplen;
154*4882a593Smuzhiyun nvp->nvram[nvp->nvram_len] = '\0';
155*4882a593Smuzhiyun nvp->nvram_len++;
156*4882a593Smuzhiyun return IDLE;
157*4882a593Smuzhiyun }
158*4882a593Smuzhiyun nvp->pos++;
159*4882a593Smuzhiyun nvp->column++;
160*4882a593Smuzhiyun return VALUE;
161*4882a593Smuzhiyun }
162*4882a593Smuzhiyun
163*4882a593Smuzhiyun static enum nvram_parser_state
brcmf_nvram_handle_comment(struct nvram_parser * nvp)164*4882a593Smuzhiyun brcmf_nvram_handle_comment(struct nvram_parser *nvp)
165*4882a593Smuzhiyun {
166*4882a593Smuzhiyun char *eoc, *sol;
167*4882a593Smuzhiyun
168*4882a593Smuzhiyun sol = (char *)&nvp->data[nvp->pos];
169*4882a593Smuzhiyun eoc = strchr(sol, '\n');
170*4882a593Smuzhiyun if (!eoc) {
171*4882a593Smuzhiyun eoc = strchr(sol, '\0');
172*4882a593Smuzhiyun if (!eoc)
173*4882a593Smuzhiyun return END;
174*4882a593Smuzhiyun }
175*4882a593Smuzhiyun
176*4882a593Smuzhiyun /* eat all moving to next line */
177*4882a593Smuzhiyun nvp->line++;
178*4882a593Smuzhiyun nvp->column = 1;
179*4882a593Smuzhiyun nvp->pos += (eoc - sol) + 1;
180*4882a593Smuzhiyun return IDLE;
181*4882a593Smuzhiyun }
182*4882a593Smuzhiyun
brcmf_nvram_handle_end(struct nvram_parser * nvp)183*4882a593Smuzhiyun static enum nvram_parser_state brcmf_nvram_handle_end(struct nvram_parser *nvp)
184*4882a593Smuzhiyun {
185*4882a593Smuzhiyun /* final state */
186*4882a593Smuzhiyun return END;
187*4882a593Smuzhiyun }
188*4882a593Smuzhiyun
189*4882a593Smuzhiyun static enum nvram_parser_state
190*4882a593Smuzhiyun (*nv_parser_states[])(struct nvram_parser *nvp) = {
191*4882a593Smuzhiyun brcmf_nvram_handle_idle,
192*4882a593Smuzhiyun brcmf_nvram_handle_key,
193*4882a593Smuzhiyun brcmf_nvram_handle_value,
194*4882a593Smuzhiyun brcmf_nvram_handle_comment,
195*4882a593Smuzhiyun brcmf_nvram_handle_end
196*4882a593Smuzhiyun };
197*4882a593Smuzhiyun
brcmf_init_nvram_parser(struct nvram_parser * nvp,const u8 * data,size_t data_len)198*4882a593Smuzhiyun static int brcmf_init_nvram_parser(struct nvram_parser *nvp,
199*4882a593Smuzhiyun const u8 *data, size_t data_len)
200*4882a593Smuzhiyun {
201*4882a593Smuzhiyun size_t size;
202*4882a593Smuzhiyun
203*4882a593Smuzhiyun memset(nvp, 0, sizeof(*nvp));
204*4882a593Smuzhiyun nvp->data = data;
205*4882a593Smuzhiyun /* Limit size to MAX_NVRAM_SIZE, some files contain lot of comment */
206*4882a593Smuzhiyun if (data_len > BRCMF_FW_MAX_NVRAM_SIZE)
207*4882a593Smuzhiyun size = BRCMF_FW_MAX_NVRAM_SIZE;
208*4882a593Smuzhiyun else
209*4882a593Smuzhiyun size = data_len;
210*4882a593Smuzhiyun /* Add space for properties we may add */
211*4882a593Smuzhiyun size += strlen(BRCMF_FW_DEFAULT_BOARDREV) + 1;
212*4882a593Smuzhiyun /* Alloc for extra 0 byte + roundup by 4 + length field */
213*4882a593Smuzhiyun size += 1 + 3 + sizeof(u32);
214*4882a593Smuzhiyun nvp->nvram = kzalloc(size, GFP_KERNEL);
215*4882a593Smuzhiyun if (!nvp->nvram)
216*4882a593Smuzhiyun return -ENOMEM;
217*4882a593Smuzhiyun
218*4882a593Smuzhiyun nvp->line = 1;
219*4882a593Smuzhiyun nvp->column = 1;
220*4882a593Smuzhiyun return 0;
221*4882a593Smuzhiyun }
222*4882a593Smuzhiyun
223*4882a593Smuzhiyun /* brcmf_fw_strip_multi_v1 :Some nvram files contain settings for multiple
224*4882a593Smuzhiyun * devices. Strip it down for one device, use domain_nr/bus_nr to determine
225*4882a593Smuzhiyun * which data is to be returned. v1 is the version where nvram is stored
226*4882a593Smuzhiyun * compressed and "devpath" maps to index for valid entries.
227*4882a593Smuzhiyun */
brcmf_fw_strip_multi_v1(struct nvram_parser * nvp,u16 domain_nr,u16 bus_nr)228*4882a593Smuzhiyun static void brcmf_fw_strip_multi_v1(struct nvram_parser *nvp, u16 domain_nr,
229*4882a593Smuzhiyun u16 bus_nr)
230*4882a593Smuzhiyun {
231*4882a593Smuzhiyun /* Device path with a leading '=' key-value separator */
232*4882a593Smuzhiyun char pci_path[] = "=pci/?/?";
233*4882a593Smuzhiyun size_t pci_len;
234*4882a593Smuzhiyun char pcie_path[] = "=pcie/?/?";
235*4882a593Smuzhiyun size_t pcie_len;
236*4882a593Smuzhiyun
237*4882a593Smuzhiyun u32 i, j;
238*4882a593Smuzhiyun bool found;
239*4882a593Smuzhiyun u8 *nvram;
240*4882a593Smuzhiyun u8 id;
241*4882a593Smuzhiyun
242*4882a593Smuzhiyun nvram = kzalloc(nvp->nvram_len + 1 + 3 + sizeof(u32), GFP_KERNEL);
243*4882a593Smuzhiyun if (!nvram)
244*4882a593Smuzhiyun goto fail;
245*4882a593Smuzhiyun
246*4882a593Smuzhiyun /* min length: devpath0=pcie/1/4/ + 0:x=y */
247*4882a593Smuzhiyun if (nvp->nvram_len < BRCMF_FW_NVRAM_DEVPATH_LEN + 6)
248*4882a593Smuzhiyun goto fail;
249*4882a593Smuzhiyun
250*4882a593Smuzhiyun /* First search for the devpathX and see if it is the configuration
251*4882a593Smuzhiyun * for domain_nr/bus_nr. Search complete nvp
252*4882a593Smuzhiyun */
253*4882a593Smuzhiyun snprintf(pci_path, sizeof(pci_path), "=pci/%d/%d", domain_nr,
254*4882a593Smuzhiyun bus_nr);
255*4882a593Smuzhiyun pci_len = strlen(pci_path);
256*4882a593Smuzhiyun snprintf(pcie_path, sizeof(pcie_path), "=pcie/%d/%d", domain_nr,
257*4882a593Smuzhiyun bus_nr);
258*4882a593Smuzhiyun pcie_len = strlen(pcie_path);
259*4882a593Smuzhiyun found = false;
260*4882a593Smuzhiyun i = 0;
261*4882a593Smuzhiyun while (i < nvp->nvram_len - BRCMF_FW_NVRAM_DEVPATH_LEN) {
262*4882a593Smuzhiyun /* Format: devpathX=pcie/Y/Z/
263*4882a593Smuzhiyun * Y = domain_nr, Z = bus_nr, X = virtual ID
264*4882a593Smuzhiyun */
265*4882a593Smuzhiyun if (strncmp(&nvp->nvram[i], "devpath", 7) == 0 &&
266*4882a593Smuzhiyun (!strncmp(&nvp->nvram[i + 8], pci_path, pci_len) ||
267*4882a593Smuzhiyun !strncmp(&nvp->nvram[i + 8], pcie_path, pcie_len))) {
268*4882a593Smuzhiyun id = nvp->nvram[i + 7] - '0';
269*4882a593Smuzhiyun found = true;
270*4882a593Smuzhiyun break;
271*4882a593Smuzhiyun }
272*4882a593Smuzhiyun while (nvp->nvram[i] != 0)
273*4882a593Smuzhiyun i++;
274*4882a593Smuzhiyun i++;
275*4882a593Smuzhiyun }
276*4882a593Smuzhiyun if (!found)
277*4882a593Smuzhiyun goto fail;
278*4882a593Smuzhiyun
279*4882a593Smuzhiyun /* Now copy all valid entries, release old nvram and assign new one */
280*4882a593Smuzhiyun i = 0;
281*4882a593Smuzhiyun j = 0;
282*4882a593Smuzhiyun while (i < nvp->nvram_len) {
283*4882a593Smuzhiyun if ((nvp->nvram[i] - '0' == id) && (nvp->nvram[i + 1] == ':')) {
284*4882a593Smuzhiyun i += 2;
285*4882a593Smuzhiyun if (strncmp(&nvp->nvram[i], "boardrev", 8) == 0)
286*4882a593Smuzhiyun nvp->boardrev_found = true;
287*4882a593Smuzhiyun while (nvp->nvram[i] != 0) {
288*4882a593Smuzhiyun nvram[j] = nvp->nvram[i];
289*4882a593Smuzhiyun i++;
290*4882a593Smuzhiyun j++;
291*4882a593Smuzhiyun }
292*4882a593Smuzhiyun nvram[j] = 0;
293*4882a593Smuzhiyun j++;
294*4882a593Smuzhiyun }
295*4882a593Smuzhiyun while (nvp->nvram[i] != 0)
296*4882a593Smuzhiyun i++;
297*4882a593Smuzhiyun i++;
298*4882a593Smuzhiyun }
299*4882a593Smuzhiyun kfree(nvp->nvram);
300*4882a593Smuzhiyun nvp->nvram = nvram;
301*4882a593Smuzhiyun nvp->nvram_len = j;
302*4882a593Smuzhiyun return;
303*4882a593Smuzhiyun
304*4882a593Smuzhiyun fail:
305*4882a593Smuzhiyun kfree(nvram);
306*4882a593Smuzhiyun nvp->nvram_len = 0;
307*4882a593Smuzhiyun }
308*4882a593Smuzhiyun
309*4882a593Smuzhiyun /* brcmf_fw_strip_multi_v2 :Some nvram files contain settings for multiple
310*4882a593Smuzhiyun * devices. Strip it down for one device, use domain_nr/bus_nr to determine
311*4882a593Smuzhiyun * which data is to be returned. v2 is the version where nvram is stored
312*4882a593Smuzhiyun * uncompressed, all relevant valid entries are identified by
313*4882a593Smuzhiyun * pcie/domain_nr/bus_nr:
314*4882a593Smuzhiyun */
brcmf_fw_strip_multi_v2(struct nvram_parser * nvp,u16 domain_nr,u16 bus_nr)315*4882a593Smuzhiyun static void brcmf_fw_strip_multi_v2(struct nvram_parser *nvp, u16 domain_nr,
316*4882a593Smuzhiyun u16 bus_nr)
317*4882a593Smuzhiyun {
318*4882a593Smuzhiyun char prefix[BRCMF_FW_NVRAM_PCIEDEV_LEN];
319*4882a593Smuzhiyun size_t len;
320*4882a593Smuzhiyun u32 i, j;
321*4882a593Smuzhiyun u8 *nvram;
322*4882a593Smuzhiyun
323*4882a593Smuzhiyun nvram = kzalloc(nvp->nvram_len + 1 + 3 + sizeof(u32), GFP_KERNEL);
324*4882a593Smuzhiyun if (!nvram)
325*4882a593Smuzhiyun goto fail;
326*4882a593Smuzhiyun
327*4882a593Smuzhiyun /* Copy all valid entries, release old nvram and assign new one.
328*4882a593Smuzhiyun * Valid entries are of type pcie/X/Y/ where X = domain_nr and
329*4882a593Smuzhiyun * Y = bus_nr.
330*4882a593Smuzhiyun */
331*4882a593Smuzhiyun snprintf(prefix, sizeof(prefix), "pcie/%d/%d/", domain_nr, bus_nr);
332*4882a593Smuzhiyun len = strlen(prefix);
333*4882a593Smuzhiyun i = 0;
334*4882a593Smuzhiyun j = 0;
335*4882a593Smuzhiyun while (i < nvp->nvram_len - len) {
336*4882a593Smuzhiyun if (strncmp(&nvp->nvram[i], prefix, len) == 0) {
337*4882a593Smuzhiyun i += len;
338*4882a593Smuzhiyun if (strncmp(&nvp->nvram[i], "boardrev", 8) == 0)
339*4882a593Smuzhiyun nvp->boardrev_found = true;
340*4882a593Smuzhiyun while (nvp->nvram[i] != 0) {
341*4882a593Smuzhiyun nvram[j] = nvp->nvram[i];
342*4882a593Smuzhiyun i++;
343*4882a593Smuzhiyun j++;
344*4882a593Smuzhiyun }
345*4882a593Smuzhiyun nvram[j] = 0;
346*4882a593Smuzhiyun j++;
347*4882a593Smuzhiyun }
348*4882a593Smuzhiyun while (nvp->nvram[i] != 0)
349*4882a593Smuzhiyun i++;
350*4882a593Smuzhiyun i++;
351*4882a593Smuzhiyun }
352*4882a593Smuzhiyun kfree(nvp->nvram);
353*4882a593Smuzhiyun nvp->nvram = nvram;
354*4882a593Smuzhiyun nvp->nvram_len = j;
355*4882a593Smuzhiyun return;
356*4882a593Smuzhiyun fail:
357*4882a593Smuzhiyun kfree(nvram);
358*4882a593Smuzhiyun nvp->nvram_len = 0;
359*4882a593Smuzhiyun }
360*4882a593Smuzhiyun
brcmf_fw_add_defaults(struct nvram_parser * nvp)361*4882a593Smuzhiyun static void brcmf_fw_add_defaults(struct nvram_parser *nvp)
362*4882a593Smuzhiyun {
363*4882a593Smuzhiyun if (nvp->boardrev_found)
364*4882a593Smuzhiyun return;
365*4882a593Smuzhiyun
366*4882a593Smuzhiyun memcpy(&nvp->nvram[nvp->nvram_len], &BRCMF_FW_DEFAULT_BOARDREV,
367*4882a593Smuzhiyun strlen(BRCMF_FW_DEFAULT_BOARDREV));
368*4882a593Smuzhiyun nvp->nvram_len += strlen(BRCMF_FW_DEFAULT_BOARDREV);
369*4882a593Smuzhiyun nvp->nvram[nvp->nvram_len] = '\0';
370*4882a593Smuzhiyun nvp->nvram_len++;
371*4882a593Smuzhiyun }
372*4882a593Smuzhiyun
373*4882a593Smuzhiyun /* brcmf_nvram_strip :Takes a buffer of "<var>=<value>\n" lines read from a fil
374*4882a593Smuzhiyun * and ending in a NUL. Removes carriage returns, empty lines, comment lines,
375*4882a593Smuzhiyun * and converts newlines to NULs. Shortens buffer as needed and pads with NULs.
376*4882a593Smuzhiyun * End of buffer is completed with token identifying length of buffer.
377*4882a593Smuzhiyun */
brcmf_fw_nvram_strip(const u8 * data,size_t data_len,u32 * new_length,u16 domain_nr,u16 bus_nr)378*4882a593Smuzhiyun static void *brcmf_fw_nvram_strip(const u8 *data, size_t data_len,
379*4882a593Smuzhiyun u32 *new_length, u16 domain_nr, u16 bus_nr)
380*4882a593Smuzhiyun {
381*4882a593Smuzhiyun struct nvram_parser nvp;
382*4882a593Smuzhiyun u32 pad;
383*4882a593Smuzhiyun u32 token;
384*4882a593Smuzhiyun __le32 token_le;
385*4882a593Smuzhiyun
386*4882a593Smuzhiyun if (brcmf_init_nvram_parser(&nvp, data, data_len) < 0)
387*4882a593Smuzhiyun return NULL;
388*4882a593Smuzhiyun
389*4882a593Smuzhiyun while (nvp.pos < data_len) {
390*4882a593Smuzhiyun nvp.state = nv_parser_states[nvp.state](&nvp);
391*4882a593Smuzhiyun if (nvp.state == END)
392*4882a593Smuzhiyun break;
393*4882a593Smuzhiyun }
394*4882a593Smuzhiyun if (nvp.multi_dev_v1) {
395*4882a593Smuzhiyun nvp.boardrev_found = false;
396*4882a593Smuzhiyun brcmf_fw_strip_multi_v1(&nvp, domain_nr, bus_nr);
397*4882a593Smuzhiyun } else if (nvp.multi_dev_v2) {
398*4882a593Smuzhiyun nvp.boardrev_found = false;
399*4882a593Smuzhiyun brcmf_fw_strip_multi_v2(&nvp, domain_nr, bus_nr);
400*4882a593Smuzhiyun }
401*4882a593Smuzhiyun
402*4882a593Smuzhiyun if (nvp.nvram_len == 0) {
403*4882a593Smuzhiyun kfree(nvp.nvram);
404*4882a593Smuzhiyun return NULL;
405*4882a593Smuzhiyun }
406*4882a593Smuzhiyun
407*4882a593Smuzhiyun brcmf_fw_add_defaults(&nvp);
408*4882a593Smuzhiyun
409*4882a593Smuzhiyun pad = nvp.nvram_len;
410*4882a593Smuzhiyun *new_length = roundup(nvp.nvram_len + 1, 4);
411*4882a593Smuzhiyun while (pad != *new_length) {
412*4882a593Smuzhiyun nvp.nvram[pad] = 0;
413*4882a593Smuzhiyun pad++;
414*4882a593Smuzhiyun }
415*4882a593Smuzhiyun
416*4882a593Smuzhiyun token = *new_length / 4;
417*4882a593Smuzhiyun token = (~token << 16) | (token & 0x0000FFFF);
418*4882a593Smuzhiyun token_le = cpu_to_le32(token);
419*4882a593Smuzhiyun
420*4882a593Smuzhiyun memcpy(&nvp.nvram[*new_length], &token_le, sizeof(token_le));
421*4882a593Smuzhiyun *new_length += sizeof(token_le);
422*4882a593Smuzhiyun
423*4882a593Smuzhiyun return nvp.nvram;
424*4882a593Smuzhiyun }
425*4882a593Smuzhiyun
brcmf_fw_nvram_free(void * nvram)426*4882a593Smuzhiyun void brcmf_fw_nvram_free(void *nvram)
427*4882a593Smuzhiyun {
428*4882a593Smuzhiyun kfree(nvram);
429*4882a593Smuzhiyun }
430*4882a593Smuzhiyun
431*4882a593Smuzhiyun struct brcmf_fw {
432*4882a593Smuzhiyun struct device *dev;
433*4882a593Smuzhiyun struct brcmf_fw_request *req;
434*4882a593Smuzhiyun u32 curpos;
435*4882a593Smuzhiyun void (*done)(struct device *dev, int err, struct brcmf_fw_request *req);
436*4882a593Smuzhiyun };
437*4882a593Smuzhiyun
438*4882a593Smuzhiyun static void brcmf_fw_request_done(const struct firmware *fw, void *ctx);
439*4882a593Smuzhiyun
440*4882a593Smuzhiyun #ifdef CONFIG_EFI
441*4882a593Smuzhiyun /* In some cases the EFI-var stored nvram contains "ccode=ALL" or "ccode=XV"
442*4882a593Smuzhiyun * to specify "worldwide" compatible settings, but these 2 ccode-s do not work
443*4882a593Smuzhiyun * properly. "ccode=ALL" causes channels 12 and 13 to not be available,
444*4882a593Smuzhiyun * "ccode=XV" causes all 5GHz channels to not be available. So we replace both
445*4882a593Smuzhiyun * with "ccode=X2" which allows channels 12+13 and 5Ghz channels in
446*4882a593Smuzhiyun * no-Initiate-Radiation mode. This means that we will never send on these
447*4882a593Smuzhiyun * channels without first having received valid wifi traffic on the channel.
448*4882a593Smuzhiyun */
brcmf_fw_fix_efi_nvram_ccode(char * data,unsigned long data_len)449*4882a593Smuzhiyun static void brcmf_fw_fix_efi_nvram_ccode(char *data, unsigned long data_len)
450*4882a593Smuzhiyun {
451*4882a593Smuzhiyun char *ccode;
452*4882a593Smuzhiyun
453*4882a593Smuzhiyun ccode = strnstr((char *)data, "ccode=ALL", data_len);
454*4882a593Smuzhiyun if (!ccode)
455*4882a593Smuzhiyun ccode = strnstr((char *)data, "ccode=XV\r", data_len);
456*4882a593Smuzhiyun if (!ccode)
457*4882a593Smuzhiyun return;
458*4882a593Smuzhiyun
459*4882a593Smuzhiyun ccode[6] = 'X';
460*4882a593Smuzhiyun ccode[7] = '2';
461*4882a593Smuzhiyun ccode[8] = '\r';
462*4882a593Smuzhiyun }
463*4882a593Smuzhiyun
brcmf_fw_nvram_from_efi(size_t * data_len_ret)464*4882a593Smuzhiyun static u8 *brcmf_fw_nvram_from_efi(size_t *data_len_ret)
465*4882a593Smuzhiyun {
466*4882a593Smuzhiyun const u16 name[] = { 'n', 'v', 'r', 'a', 'm', 0 };
467*4882a593Smuzhiyun struct efivar_entry *nvram_efivar;
468*4882a593Smuzhiyun unsigned long data_len = 0;
469*4882a593Smuzhiyun u8 *data = NULL;
470*4882a593Smuzhiyun int err;
471*4882a593Smuzhiyun
472*4882a593Smuzhiyun nvram_efivar = kzalloc(sizeof(*nvram_efivar), GFP_KERNEL);
473*4882a593Smuzhiyun if (!nvram_efivar)
474*4882a593Smuzhiyun return NULL;
475*4882a593Smuzhiyun
476*4882a593Smuzhiyun memcpy(&nvram_efivar->var.VariableName, name, sizeof(name));
477*4882a593Smuzhiyun nvram_efivar->var.VendorGuid = EFI_GUID(0x74b00bd9, 0x805a, 0x4d61,
478*4882a593Smuzhiyun 0xb5, 0x1f, 0x43, 0x26,
479*4882a593Smuzhiyun 0x81, 0x23, 0xd1, 0x13);
480*4882a593Smuzhiyun
481*4882a593Smuzhiyun err = efivar_entry_size(nvram_efivar, &data_len);
482*4882a593Smuzhiyun if (err)
483*4882a593Smuzhiyun goto fail;
484*4882a593Smuzhiyun
485*4882a593Smuzhiyun data = kmalloc(data_len, GFP_KERNEL);
486*4882a593Smuzhiyun if (!data)
487*4882a593Smuzhiyun goto fail;
488*4882a593Smuzhiyun
489*4882a593Smuzhiyun err = efivar_entry_get(nvram_efivar, NULL, &data_len, data);
490*4882a593Smuzhiyun if (err)
491*4882a593Smuzhiyun goto fail;
492*4882a593Smuzhiyun
493*4882a593Smuzhiyun brcmf_fw_fix_efi_nvram_ccode(data, data_len);
494*4882a593Smuzhiyun brcmf_info("Using nvram EFI variable\n");
495*4882a593Smuzhiyun
496*4882a593Smuzhiyun kfree(nvram_efivar);
497*4882a593Smuzhiyun *data_len_ret = data_len;
498*4882a593Smuzhiyun return data;
499*4882a593Smuzhiyun
500*4882a593Smuzhiyun fail:
501*4882a593Smuzhiyun kfree(data);
502*4882a593Smuzhiyun kfree(nvram_efivar);
503*4882a593Smuzhiyun return NULL;
504*4882a593Smuzhiyun }
505*4882a593Smuzhiyun #else
brcmf_fw_nvram_from_efi(size_t * data_len)506*4882a593Smuzhiyun static inline u8 *brcmf_fw_nvram_from_efi(size_t *data_len) { return NULL; }
507*4882a593Smuzhiyun #endif
508*4882a593Smuzhiyun
brcmf_fw_free_request(struct brcmf_fw_request * req)509*4882a593Smuzhiyun static void brcmf_fw_free_request(struct brcmf_fw_request *req)
510*4882a593Smuzhiyun {
511*4882a593Smuzhiyun struct brcmf_fw_item *item;
512*4882a593Smuzhiyun int i;
513*4882a593Smuzhiyun
514*4882a593Smuzhiyun for (i = 0, item = &req->items[0]; i < req->n_items; i++, item++) {
515*4882a593Smuzhiyun if (item->type == BRCMF_FW_TYPE_BINARY)
516*4882a593Smuzhiyun release_firmware(item->binary);
517*4882a593Smuzhiyun else if (item->type == BRCMF_FW_TYPE_NVRAM)
518*4882a593Smuzhiyun brcmf_fw_nvram_free(item->nv_data.data);
519*4882a593Smuzhiyun }
520*4882a593Smuzhiyun kfree(req);
521*4882a593Smuzhiyun }
522*4882a593Smuzhiyun
brcmf_fw_request_nvram_done(const struct firmware * fw,void * ctx)523*4882a593Smuzhiyun static int brcmf_fw_request_nvram_done(const struct firmware *fw, void *ctx)
524*4882a593Smuzhiyun {
525*4882a593Smuzhiyun struct brcmf_fw *fwctx = ctx;
526*4882a593Smuzhiyun struct brcmf_fw_item *cur;
527*4882a593Smuzhiyun bool free_bcm47xx_nvram = false;
528*4882a593Smuzhiyun bool kfree_nvram = false;
529*4882a593Smuzhiyun u32 nvram_length = 0;
530*4882a593Smuzhiyun void *nvram = NULL;
531*4882a593Smuzhiyun u8 *data = NULL;
532*4882a593Smuzhiyun size_t data_len;
533*4882a593Smuzhiyun
534*4882a593Smuzhiyun brcmf_dbg(TRACE, "enter: dev=%s\n", dev_name(fwctx->dev));
535*4882a593Smuzhiyun
536*4882a593Smuzhiyun cur = &fwctx->req->items[fwctx->curpos];
537*4882a593Smuzhiyun
538*4882a593Smuzhiyun if (fw && fw->data) {
539*4882a593Smuzhiyun data = (u8 *)fw->data;
540*4882a593Smuzhiyun data_len = fw->size;
541*4882a593Smuzhiyun } else {
542*4882a593Smuzhiyun if ((data = bcm47xx_nvram_get_contents(&data_len)))
543*4882a593Smuzhiyun free_bcm47xx_nvram = true;
544*4882a593Smuzhiyun else if ((data = brcmf_fw_nvram_from_efi(&data_len)))
545*4882a593Smuzhiyun kfree_nvram = true;
546*4882a593Smuzhiyun else if (!(cur->flags & BRCMF_FW_REQF_OPTIONAL))
547*4882a593Smuzhiyun goto fail;
548*4882a593Smuzhiyun }
549*4882a593Smuzhiyun
550*4882a593Smuzhiyun if (data)
551*4882a593Smuzhiyun nvram = brcmf_fw_nvram_strip(data, data_len, &nvram_length,
552*4882a593Smuzhiyun fwctx->req->domain_nr,
553*4882a593Smuzhiyun fwctx->req->bus_nr);
554*4882a593Smuzhiyun
555*4882a593Smuzhiyun if (free_bcm47xx_nvram)
556*4882a593Smuzhiyun bcm47xx_nvram_release_contents(data);
557*4882a593Smuzhiyun if (kfree_nvram)
558*4882a593Smuzhiyun kfree(data);
559*4882a593Smuzhiyun
560*4882a593Smuzhiyun release_firmware(fw);
561*4882a593Smuzhiyun if (!nvram && !(cur->flags & BRCMF_FW_REQF_OPTIONAL))
562*4882a593Smuzhiyun goto fail;
563*4882a593Smuzhiyun
564*4882a593Smuzhiyun brcmf_dbg(TRACE, "nvram %p len %d\n", nvram, nvram_length);
565*4882a593Smuzhiyun cur->nv_data.data = nvram;
566*4882a593Smuzhiyun cur->nv_data.len = nvram_length;
567*4882a593Smuzhiyun return 0;
568*4882a593Smuzhiyun
569*4882a593Smuzhiyun fail:
570*4882a593Smuzhiyun return -ENOENT;
571*4882a593Smuzhiyun }
572*4882a593Smuzhiyun
brcmf_fw_complete_request(const struct firmware * fw,struct brcmf_fw * fwctx)573*4882a593Smuzhiyun static int brcmf_fw_complete_request(const struct firmware *fw,
574*4882a593Smuzhiyun struct brcmf_fw *fwctx)
575*4882a593Smuzhiyun {
576*4882a593Smuzhiyun struct brcmf_fw_item *cur = &fwctx->req->items[fwctx->curpos];
577*4882a593Smuzhiyun int ret = 0;
578*4882a593Smuzhiyun
579*4882a593Smuzhiyun brcmf_dbg(TRACE, "firmware %s %sfound\n", cur->path, fw ? "" : "not ");
580*4882a593Smuzhiyun
581*4882a593Smuzhiyun switch (cur->type) {
582*4882a593Smuzhiyun case BRCMF_FW_TYPE_NVRAM:
583*4882a593Smuzhiyun ret = brcmf_fw_request_nvram_done(fw, fwctx);
584*4882a593Smuzhiyun break;
585*4882a593Smuzhiyun case BRCMF_FW_TYPE_BINARY:
586*4882a593Smuzhiyun if (fw)
587*4882a593Smuzhiyun cur->binary = fw;
588*4882a593Smuzhiyun else
589*4882a593Smuzhiyun ret = -ENOENT;
590*4882a593Smuzhiyun break;
591*4882a593Smuzhiyun default:
592*4882a593Smuzhiyun /* something fishy here so bail out early */
593*4882a593Smuzhiyun brcmf_err("unknown fw type: %d\n", cur->type);
594*4882a593Smuzhiyun release_firmware(fw);
595*4882a593Smuzhiyun ret = -EINVAL;
596*4882a593Smuzhiyun }
597*4882a593Smuzhiyun
598*4882a593Smuzhiyun return (cur->flags & BRCMF_FW_REQF_OPTIONAL) ? 0 : ret;
599*4882a593Smuzhiyun }
600*4882a593Smuzhiyun
brcmf_fw_request_firmware(const struct firmware ** fw,struct brcmf_fw * fwctx)601*4882a593Smuzhiyun static int brcmf_fw_request_firmware(const struct firmware **fw,
602*4882a593Smuzhiyun struct brcmf_fw *fwctx)
603*4882a593Smuzhiyun {
604*4882a593Smuzhiyun struct brcmf_fw_item *cur = &fwctx->req->items[fwctx->curpos];
605*4882a593Smuzhiyun int ret;
606*4882a593Smuzhiyun
607*4882a593Smuzhiyun /* nvram files are board-specific, first try a board-specific path */
608*4882a593Smuzhiyun if (cur->type == BRCMF_FW_TYPE_NVRAM && fwctx->req->board_type) {
609*4882a593Smuzhiyun char alt_path[BRCMF_FW_NAME_LEN];
610*4882a593Smuzhiyun
611*4882a593Smuzhiyun strlcpy(alt_path, cur->path, BRCMF_FW_NAME_LEN);
612*4882a593Smuzhiyun /* strip .txt at the end */
613*4882a593Smuzhiyun alt_path[strlen(alt_path) - 4] = 0;
614*4882a593Smuzhiyun strlcat(alt_path, ".", BRCMF_FW_NAME_LEN);
615*4882a593Smuzhiyun strlcat(alt_path, fwctx->req->board_type, BRCMF_FW_NAME_LEN);
616*4882a593Smuzhiyun strlcat(alt_path, ".txt", BRCMF_FW_NAME_LEN);
617*4882a593Smuzhiyun
618*4882a593Smuzhiyun ret = request_firmware(fw, alt_path, fwctx->dev);
619*4882a593Smuzhiyun if (ret == 0)
620*4882a593Smuzhiyun return ret;
621*4882a593Smuzhiyun }
622*4882a593Smuzhiyun
623*4882a593Smuzhiyun return request_firmware(fw, cur->path, fwctx->dev);
624*4882a593Smuzhiyun }
625*4882a593Smuzhiyun
brcmf_fw_request_done(const struct firmware * fw,void * ctx)626*4882a593Smuzhiyun static void brcmf_fw_request_done(const struct firmware *fw, void *ctx)
627*4882a593Smuzhiyun {
628*4882a593Smuzhiyun struct brcmf_fw *fwctx = ctx;
629*4882a593Smuzhiyun int ret;
630*4882a593Smuzhiyun
631*4882a593Smuzhiyun ret = brcmf_fw_complete_request(fw, fwctx);
632*4882a593Smuzhiyun
633*4882a593Smuzhiyun while (ret == 0 && ++fwctx->curpos < fwctx->req->n_items) {
634*4882a593Smuzhiyun brcmf_fw_request_firmware(&fw, fwctx);
635*4882a593Smuzhiyun ret = brcmf_fw_complete_request(fw, ctx);
636*4882a593Smuzhiyun }
637*4882a593Smuzhiyun
638*4882a593Smuzhiyun if (ret) {
639*4882a593Smuzhiyun brcmf_fw_free_request(fwctx->req);
640*4882a593Smuzhiyun fwctx->req = NULL;
641*4882a593Smuzhiyun }
642*4882a593Smuzhiyun fwctx->done(fwctx->dev, ret, fwctx->req);
643*4882a593Smuzhiyun kfree(fwctx);
644*4882a593Smuzhiyun }
645*4882a593Smuzhiyun
brcmf_fw_request_is_valid(struct brcmf_fw_request * req)646*4882a593Smuzhiyun static bool brcmf_fw_request_is_valid(struct brcmf_fw_request *req)
647*4882a593Smuzhiyun {
648*4882a593Smuzhiyun struct brcmf_fw_item *item;
649*4882a593Smuzhiyun int i;
650*4882a593Smuzhiyun
651*4882a593Smuzhiyun if (!req->n_items)
652*4882a593Smuzhiyun return false;
653*4882a593Smuzhiyun
654*4882a593Smuzhiyun for (i = 0, item = &req->items[0]; i < req->n_items; i++, item++) {
655*4882a593Smuzhiyun if (!item->path)
656*4882a593Smuzhiyun return false;
657*4882a593Smuzhiyun }
658*4882a593Smuzhiyun return true;
659*4882a593Smuzhiyun }
660*4882a593Smuzhiyun
brcmf_fw_get_firmwares(struct device * dev,struct brcmf_fw_request * req,void (* fw_cb)(struct device * dev,int err,struct brcmf_fw_request * req))661*4882a593Smuzhiyun int brcmf_fw_get_firmwares(struct device *dev, struct brcmf_fw_request *req,
662*4882a593Smuzhiyun void (*fw_cb)(struct device *dev, int err,
663*4882a593Smuzhiyun struct brcmf_fw_request *req))
664*4882a593Smuzhiyun {
665*4882a593Smuzhiyun struct brcmf_fw_item *first = &req->items[0];
666*4882a593Smuzhiyun struct brcmf_fw *fwctx;
667*4882a593Smuzhiyun int ret;
668*4882a593Smuzhiyun
669*4882a593Smuzhiyun brcmf_dbg(TRACE, "enter: dev=%s\n", dev_name(dev));
670*4882a593Smuzhiyun if (!fw_cb)
671*4882a593Smuzhiyun return -EINVAL;
672*4882a593Smuzhiyun
673*4882a593Smuzhiyun if (!brcmf_fw_request_is_valid(req))
674*4882a593Smuzhiyun return -EINVAL;
675*4882a593Smuzhiyun
676*4882a593Smuzhiyun fwctx = kzalloc(sizeof(*fwctx), GFP_KERNEL);
677*4882a593Smuzhiyun if (!fwctx)
678*4882a593Smuzhiyun return -ENOMEM;
679*4882a593Smuzhiyun
680*4882a593Smuzhiyun fwctx->dev = dev;
681*4882a593Smuzhiyun fwctx->req = req;
682*4882a593Smuzhiyun fwctx->done = fw_cb;
683*4882a593Smuzhiyun
684*4882a593Smuzhiyun ret = request_firmware_nowait(THIS_MODULE, true, first->path,
685*4882a593Smuzhiyun fwctx->dev, GFP_KERNEL, fwctx,
686*4882a593Smuzhiyun brcmf_fw_request_done);
687*4882a593Smuzhiyun if (ret < 0)
688*4882a593Smuzhiyun brcmf_fw_request_done(NULL, fwctx);
689*4882a593Smuzhiyun
690*4882a593Smuzhiyun return 0;
691*4882a593Smuzhiyun }
692*4882a593Smuzhiyun
693*4882a593Smuzhiyun struct brcmf_fw_request *
brcmf_fw_alloc_request(u32 chip,u32 chiprev,const struct brcmf_firmware_mapping mapping_table[],u32 table_size,struct brcmf_fw_name * fwnames,u32 n_fwnames)694*4882a593Smuzhiyun brcmf_fw_alloc_request(u32 chip, u32 chiprev,
695*4882a593Smuzhiyun const struct brcmf_firmware_mapping mapping_table[],
696*4882a593Smuzhiyun u32 table_size, struct brcmf_fw_name *fwnames,
697*4882a593Smuzhiyun u32 n_fwnames)
698*4882a593Smuzhiyun {
699*4882a593Smuzhiyun struct brcmf_fw_request *fwreq;
700*4882a593Smuzhiyun char chipname[12];
701*4882a593Smuzhiyun const char *mp_path;
702*4882a593Smuzhiyun size_t mp_path_len;
703*4882a593Smuzhiyun u32 i, j;
704*4882a593Smuzhiyun char end = '\0';
705*4882a593Smuzhiyun
706*4882a593Smuzhiyun for (i = 0; i < table_size; i++) {
707*4882a593Smuzhiyun if (mapping_table[i].chipid == chip &&
708*4882a593Smuzhiyun mapping_table[i].revmask & BIT(chiprev))
709*4882a593Smuzhiyun break;
710*4882a593Smuzhiyun }
711*4882a593Smuzhiyun
712*4882a593Smuzhiyun brcmf_chip_name(chip, chiprev, chipname, sizeof(chipname));
713*4882a593Smuzhiyun
714*4882a593Smuzhiyun if (i == table_size) {
715*4882a593Smuzhiyun brcmf_err("Unknown chip %s\n", chipname);
716*4882a593Smuzhiyun return NULL;
717*4882a593Smuzhiyun }
718*4882a593Smuzhiyun
719*4882a593Smuzhiyun fwreq = kzalloc(struct_size(fwreq, items, n_fwnames), GFP_KERNEL);
720*4882a593Smuzhiyun if (!fwreq)
721*4882a593Smuzhiyun return NULL;
722*4882a593Smuzhiyun
723*4882a593Smuzhiyun brcmf_info("using %s for chip %s\n",
724*4882a593Smuzhiyun mapping_table[i].fw_base, chipname);
725*4882a593Smuzhiyun
726*4882a593Smuzhiyun mp_path = brcmf_mp_global.firmware_path;
727*4882a593Smuzhiyun mp_path_len = strnlen(mp_path, BRCMF_FW_ALTPATH_LEN);
728*4882a593Smuzhiyun if (mp_path_len)
729*4882a593Smuzhiyun end = mp_path[mp_path_len - 1];
730*4882a593Smuzhiyun
731*4882a593Smuzhiyun fwreq->n_items = n_fwnames;
732*4882a593Smuzhiyun
733*4882a593Smuzhiyun for (j = 0; j < n_fwnames; j++) {
734*4882a593Smuzhiyun fwreq->items[j].path = fwnames[j].path;
735*4882a593Smuzhiyun fwnames[j].path[0] = '\0';
736*4882a593Smuzhiyun /* check if firmware path is provided by module parameter */
737*4882a593Smuzhiyun if (brcmf_mp_global.firmware_path[0] != '\0') {
738*4882a593Smuzhiyun strlcpy(fwnames[j].path, mp_path,
739*4882a593Smuzhiyun BRCMF_FW_NAME_LEN);
740*4882a593Smuzhiyun
741*4882a593Smuzhiyun if (end != '/') {
742*4882a593Smuzhiyun strlcat(fwnames[j].path, "/",
743*4882a593Smuzhiyun BRCMF_FW_NAME_LEN);
744*4882a593Smuzhiyun }
745*4882a593Smuzhiyun }
746*4882a593Smuzhiyun strlcat(fwnames[j].path, mapping_table[i].fw_base,
747*4882a593Smuzhiyun BRCMF_FW_NAME_LEN);
748*4882a593Smuzhiyun strlcat(fwnames[j].path, fwnames[j].extension,
749*4882a593Smuzhiyun BRCMF_FW_NAME_LEN);
750*4882a593Smuzhiyun fwreq->items[j].path = fwnames[j].path;
751*4882a593Smuzhiyun }
752*4882a593Smuzhiyun
753*4882a593Smuzhiyun return fwreq;
754*4882a593Smuzhiyun }
755