diff options
Diffstat (limited to 'drivers/net/wireless/brcm80211/brcmfmac/firmware.c')
-rw-r--r-- | drivers/net/wireless/brcm80211/brcmfmac/firmware.c | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/drivers/net/wireless/brcm80211/brcmfmac/firmware.c b/drivers/net/wireless/brcm80211/brcmfmac/firmware.c new file mode 100644 index 000000000000..7b7d237c1ddb --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/firmware.c | |||
@@ -0,0 +1,332 @@ | |||
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 | |||
22 | #include "dhd_dbg.h" | ||
23 | #include "firmware.h" | ||
24 | |||
25 | enum nvram_parser_state { | ||
26 | IDLE, | ||
27 | KEY, | ||
28 | VALUE, | ||
29 | COMMENT, | ||
30 | END | ||
31 | }; | ||
32 | |||
33 | /** | ||
34 | * struct nvram_parser - internal info for parser. | ||
35 | * | ||
36 | * @state: current parser state. | ||
37 | * @fwnv: input buffer being parsed. | ||
38 | * @nvram: output buffer with parse result. | ||
39 | * @nvram_len: lenght of parse result. | ||
40 | * @line: current line. | ||
41 | * @column: current column in line. | ||
42 | * @pos: byte offset in input buffer. | ||
43 | * @entry: start position of key,value entry. | ||
44 | */ | ||
45 | struct nvram_parser { | ||
46 | enum nvram_parser_state state; | ||
47 | const struct firmware *fwnv; | ||
48 | u8 *nvram; | ||
49 | u32 nvram_len; | ||
50 | u32 line; | ||
51 | u32 column; | ||
52 | u32 pos; | ||
53 | u32 entry; | ||
54 | }; | ||
55 | |||
56 | static bool is_nvram_char(char c) | ||
57 | { | ||
58 | /* comment marker excluded */ | ||
59 | if (c == '#') | ||
60 | return false; | ||
61 | |||
62 | /* key and value may have any other readable character */ | ||
63 | return (c > 0x20 && c < 0x7f); | ||
64 | } | ||
65 | |||
66 | static bool is_whitespace(char c) | ||
67 | { | ||
68 | return (c == ' ' || c == '\r' || c == '\n' || c == '\t'); | ||
69 | } | ||
70 | |||
71 | static enum nvram_parser_state brcmf_nvram_handle_idle(struct nvram_parser *nvp) | ||
72 | { | ||
73 | char c; | ||
74 | |||
75 | c = nvp->fwnv->data[nvp->pos]; | ||
76 | if (c == '\n') | ||
77 | return COMMENT; | ||
78 | if (is_whitespace(c)) | ||
79 | goto proceed; | ||
80 | if (c == '#') | ||
81 | return COMMENT; | ||
82 | if (is_nvram_char(c)) { | ||
83 | nvp->entry = nvp->pos; | ||
84 | return KEY; | ||
85 | } | ||
86 | brcmf_dbg(INFO, "warning: ln=%d:col=%d: ignoring invalid character\n", | ||
87 | nvp->line, nvp->column); | ||
88 | proceed: | ||
89 | nvp->column++; | ||
90 | nvp->pos++; | ||
91 | return IDLE; | ||
92 | } | ||
93 | |||
94 | static enum nvram_parser_state brcmf_nvram_handle_key(struct nvram_parser *nvp) | ||
95 | { | ||
96 | enum nvram_parser_state st = nvp->state; | ||
97 | char c; | ||
98 | |||
99 | c = nvp->fwnv->data[nvp->pos]; | ||
100 | if (c == '=') { | ||
101 | st = VALUE; | ||
102 | } else if (!is_nvram_char(c)) { | ||
103 | brcmf_dbg(INFO, "warning: ln=%d:col=%d: '=' expected, skip invalid key entry\n", | ||
104 | nvp->line, nvp->column); | ||
105 | return COMMENT; | ||
106 | } | ||
107 | |||
108 | nvp->column++; | ||
109 | nvp->pos++; | ||
110 | return st; | ||
111 | } | ||
112 | |||
113 | static enum nvram_parser_state | ||
114 | brcmf_nvram_handle_value(struct nvram_parser *nvp) | ||
115 | { | ||
116 | char c; | ||
117 | char *skv; | ||
118 | char *ekv; | ||
119 | u32 cplen; | ||
120 | |||
121 | c = nvp->fwnv->data[nvp->pos]; | ||
122 | if (!is_nvram_char(c)) { | ||
123 | /* key,value pair complete */ | ||
124 | ekv = (u8 *)&nvp->fwnv->data[nvp->pos]; | ||
125 | skv = (u8 *)&nvp->fwnv->data[nvp->entry]; | ||
126 | cplen = ekv - skv; | ||
127 | /* copy to output buffer */ | ||
128 | memcpy(&nvp->nvram[nvp->nvram_len], skv, cplen); | ||
129 | nvp->nvram_len += cplen; | ||
130 | nvp->nvram[nvp->nvram_len] = '\0'; | ||
131 | nvp->nvram_len++; | ||
132 | return IDLE; | ||
133 | } | ||
134 | nvp->pos++; | ||
135 | nvp->column++; | ||
136 | return VALUE; | ||
137 | } | ||
138 | |||
139 | static enum nvram_parser_state | ||
140 | brcmf_nvram_handle_comment(struct nvram_parser *nvp) | ||
141 | { | ||
142 | char *eol, *sol; | ||
143 | |||
144 | sol = (char *)&nvp->fwnv->data[nvp->pos]; | ||
145 | eol = strchr(sol, '\n'); | ||
146 | if (eol == NULL) | ||
147 | return END; | ||
148 | |||
149 | /* eat all moving to next line */ | ||
150 | nvp->line++; | ||
151 | nvp->column = 1; | ||
152 | nvp->pos += (eol - sol) + 1; | ||
153 | return IDLE; | ||
154 | } | ||
155 | |||
156 | static enum nvram_parser_state brcmf_nvram_handle_end(struct nvram_parser *nvp) | ||
157 | { | ||
158 | /* final state */ | ||
159 | return END; | ||
160 | } | ||
161 | |||
162 | static enum nvram_parser_state | ||
163 | (*nv_parser_states[])(struct nvram_parser *nvp) = { | ||
164 | brcmf_nvram_handle_idle, | ||
165 | brcmf_nvram_handle_key, | ||
166 | brcmf_nvram_handle_value, | ||
167 | brcmf_nvram_handle_comment, | ||
168 | brcmf_nvram_handle_end | ||
169 | }; | ||
170 | |||
171 | static int brcmf_init_nvram_parser(struct nvram_parser *nvp, | ||
172 | const struct firmware *nv) | ||
173 | { | ||
174 | memset(nvp, 0, sizeof(*nvp)); | ||
175 | nvp->fwnv = nv; | ||
176 | /* Alloc for extra 0 byte + roundup by 4 + length field */ | ||
177 | nvp->nvram = kzalloc(nv->size + 1 + 3 + sizeof(u32), GFP_KERNEL); | ||
178 | if (!nvp->nvram) | ||
179 | return -ENOMEM; | ||
180 | |||
181 | nvp->line = 1; | ||
182 | nvp->column = 1; | ||
183 | return 0; | ||
184 | } | ||
185 | |||
186 | /* brcmf_nvram_strip :Takes a buffer of "<var>=<value>\n" lines read from a fil | ||
187 | * and ending in a NUL. Removes carriage returns, empty lines, comment lines, | ||
188 | * and converts newlines to NULs. Shortens buffer as needed and pads with NULs. | ||
189 | * End of buffer is completed with token identifying length of buffer. | ||
190 | */ | ||
191 | static void *brcmf_fw_nvram_strip(const struct firmware *nv, u32 *new_length) | ||
192 | { | ||
193 | struct nvram_parser nvp; | ||
194 | u32 pad; | ||
195 | u32 token; | ||
196 | __le32 token_le; | ||
197 | |||
198 | if (brcmf_init_nvram_parser(&nvp, nv) < 0) | ||
199 | return NULL; | ||
200 | |||
201 | while (nvp.pos < nv->size) { | ||
202 | nvp.state = nv_parser_states[nvp.state](&nvp); | ||
203 | if (nvp.state == END) | ||
204 | break; | ||
205 | } | ||
206 | pad = nvp.nvram_len; | ||
207 | *new_length = roundup(nvp.nvram_len + 1, 4); | ||
208 | while (pad != *new_length) { | ||
209 | nvp.nvram[pad] = 0; | ||
210 | pad++; | ||
211 | } | ||
212 | |||
213 | token = *new_length / 4; | ||
214 | token = (~token << 16) | (token & 0x0000FFFF); | ||
215 | token_le = cpu_to_le32(token); | ||
216 | |||
217 | memcpy(&nvp.nvram[*new_length], &token_le, sizeof(token_le)); | ||
218 | *new_length += sizeof(token_le); | ||
219 | |||
220 | return nvp.nvram; | ||
221 | } | ||
222 | |||
223 | void brcmf_fw_nvram_free(void *nvram) | ||
224 | { | ||
225 | kfree(nvram); | ||
226 | } | ||
227 | |||
228 | struct brcmf_fw { | ||
229 | struct device *dev; | ||
230 | u16 flags; | ||
231 | const struct firmware *code; | ||
232 | const char *nvram_name; | ||
233 | void (*done)(struct device *dev, const struct firmware *fw, | ||
234 | void *nvram_image, u32 nvram_len); | ||
235 | }; | ||
236 | |||
237 | static void brcmf_fw_request_nvram_done(const struct firmware *fw, void *ctx) | ||
238 | { | ||
239 | struct brcmf_fw *fwctx = ctx; | ||
240 | u32 nvram_length = 0; | ||
241 | void *nvram = NULL; | ||
242 | |||
243 | brcmf_dbg(TRACE, "enter: dev=%s\n", dev_name(fwctx->dev)); | ||
244 | if (!fw && !(fwctx->flags & BRCMF_FW_REQ_NV_OPTIONAL)) | ||
245 | goto fail; | ||
246 | |||
247 | if (fw) { | ||
248 | nvram = brcmf_fw_nvram_strip(fw, &nvram_length); | ||
249 | release_firmware(fw); | ||
250 | if (!nvram && !(fwctx->flags & BRCMF_FW_REQ_NV_OPTIONAL)) | ||
251 | goto fail; | ||
252 | } | ||
253 | |||
254 | fwctx->done(fwctx->dev, fwctx->code, nvram, nvram_length); | ||
255 | kfree(fwctx); | ||
256 | return; | ||
257 | |||
258 | fail: | ||
259 | brcmf_dbg(TRACE, "failed: dev=%s\n", dev_name(fwctx->dev)); | ||
260 | if (fwctx->code) | ||
261 | release_firmware(fwctx->code); | ||
262 | device_release_driver(fwctx->dev); | ||
263 | kfree(fwctx); | ||
264 | } | ||
265 | |||
266 | static void brcmf_fw_request_code_done(const struct firmware *fw, void *ctx) | ||
267 | { | ||
268 | struct brcmf_fw *fwctx = ctx; | ||
269 | int ret; | ||
270 | |||
271 | brcmf_dbg(TRACE, "enter: dev=%s\n", dev_name(fwctx->dev)); | ||
272 | if (!fw) | ||
273 | goto fail; | ||
274 | |||
275 | /* only requested code so done here */ | ||
276 | if (!(fwctx->flags & BRCMF_FW_REQUEST_NVRAM)) { | ||
277 | fwctx->done(fwctx->dev, fw, NULL, 0); | ||
278 | kfree(fwctx); | ||
279 | return; | ||
280 | } | ||
281 | fwctx->code = fw; | ||
282 | ret = request_firmware_nowait(THIS_MODULE, true, fwctx->nvram_name, | ||
283 | fwctx->dev, GFP_KERNEL, fwctx, | ||
284 | brcmf_fw_request_nvram_done); | ||
285 | |||
286 | if (!ret) | ||
287 | return; | ||
288 | |||
289 | /* when nvram is optional call .done() callback here */ | ||
290 | if (fwctx->flags & BRCMF_FW_REQ_NV_OPTIONAL) { | ||
291 | fwctx->done(fwctx->dev, fw, NULL, 0); | ||
292 | kfree(fwctx); | ||
293 | return; | ||
294 | } | ||
295 | |||
296 | /* failed nvram request */ | ||
297 | release_firmware(fw); | ||
298 | fail: | ||
299 | brcmf_dbg(TRACE, "failed: dev=%s\n", dev_name(fwctx->dev)); | ||
300 | device_release_driver(fwctx->dev); | ||
301 | kfree(fwctx); | ||
302 | } | ||
303 | |||
304 | int brcmf_fw_get_firmwares(struct device *dev, u16 flags, | ||
305 | const char *code, const char *nvram, | ||
306 | void (*fw_cb)(struct device *dev, | ||
307 | const struct firmware *fw, | ||
308 | void *nvram_image, u32 nvram_len)) | ||
309 | { | ||
310 | struct brcmf_fw *fwctx; | ||
311 | |||
312 | brcmf_dbg(TRACE, "enter: dev=%s\n", dev_name(dev)); | ||
313 | if (!fw_cb || !code) | ||
314 | return -EINVAL; | ||
315 | |||
316 | if ((flags & BRCMF_FW_REQUEST_NVRAM) && !nvram) | ||
317 | return -EINVAL; | ||
318 | |||
319 | fwctx = kzalloc(sizeof(*fwctx), GFP_KERNEL); | ||
320 | if (!fwctx) | ||
321 | return -ENOMEM; | ||
322 | |||
323 | fwctx->dev = dev; | ||
324 | fwctx->flags = flags; | ||
325 | fwctx->done = fw_cb; | ||
326 | if (flags & BRCMF_FW_REQUEST_NVRAM) | ||
327 | fwctx->nvram_name = nvram; | ||
328 | |||
329 | return request_firmware_nowait(THIS_MODULE, true, code, dev, | ||
330 | GFP_KERNEL, fwctx, | ||
331 | brcmf_fw_request_code_done); | ||
332 | } | ||