Commit | Line | Data |
---|---|---|
635d2b00 GKH |
1 | /* |
2 | * --------------------------------------------------------------------------- | |
3 | * FILE: firmware.c | |
4 | * | |
5 | * PURPOSE: | |
6 | * Implements the f/w related HIP core lib API. | |
7 | * It is part of the porting exercise in Linux. | |
8 | * | |
9 | * Also, it contains example code for reading the loader and f/w files | |
10 | * from the userspace and starting the SME in Linux. | |
11 | * | |
12 | * Copyright (C) 2005-2009 by Cambridge Silicon Radio Ltd. | |
13 | * | |
14 | * Refer to LICENSE.txt included with this source code for details on | |
15 | * the license terms. | |
16 | * | |
17 | * --------------------------------------------------------------------------- | |
18 | */ | |
19 | #include <linux/kmod.h> | |
20 | #include <linux/vmalloc.h> | |
21 | #include <linux/firmware.h> | |
22 | #include <asm/uaccess.h> | |
23 | #include "csr_wifi_hip_unifi.h" | |
24 | #include "csr_wifi_hip_unifi_udi.h" | |
25 | #include "unifiio.h" | |
26 | #include "unifi_priv.h" | |
27 | ||
28 | /* | |
29 | * --------------------------------------------------------------------------- | |
30 | * | |
31 | * F/W download. Part of the HIP core API | |
32 | * | |
33 | * --------------------------------------------------------------------------- | |
34 | */ | |
35 | ||
36 | ||
37 | /* | |
38 | * --------------------------------------------------------------------------- | |
39 | * unifi_fw_read_start | |
40 | * | |
41 | * Returns a structure to be passed in unifi_fw_read(). | |
42 | * This structure is an OS specific description of the f/w file. | |
43 | * In the linux implementation it is a buffer with the f/w and its' length. | |
44 | * The HIP driver calls this functions to request for the loader or | |
45 | * the firmware file. | |
46 | * The structure pointer can be freed when unifi_fw_read_stop() is called. | |
47 | * | |
48 | * Arguments: | |
49 | * ospriv Pointer to driver context. | |
50 | * is_fw Type of firmware to retrieve | |
51 | * info Versions information. Can be used to determine | |
52 | * the appropriate f/w file to load. | |
53 | * | |
54 | * Returns: | |
55 | * O on success, non-zero otherwise. | |
56 | * | |
57 | * --------------------------------------------------------------------------- | |
58 | */ | |
59 | void* | |
163eb0d8 | 60 | unifi_fw_read_start(void *ospriv, s8 is_fw, const card_info_t *info) |
635d2b00 GKH |
61 | { |
62 | unifi_priv_t *priv = (unifi_priv_t*)ospriv; | |
63 | CSR_UNUSED(info); | |
64 | ||
635d2b00 GKH |
65 | if (is_fw == UNIFI_FW_STA) { |
66 | /* F/w may have been released after a previous successful download. */ | |
67 | if (priv->fw_sta.dl_data == NULL) { | |
68 | unifi_trace(priv, UDBG2, "Attempt reload of sta f/w\n"); | |
69 | uf_request_firmware_files(priv, UNIFI_FW_STA); | |
70 | } | |
71 | /* Set up callback struct for readfunc() */ | |
72 | if (priv->fw_sta.dl_data != NULL) { | |
635d2b00 GKH |
73 | return &priv->fw_sta; |
74 | } | |
75 | ||
76 | } else { | |
77 | unifi_error(priv, "downloading firmware... unknown request: %d\n", is_fw); | |
78 | } | |
79 | ||
635d2b00 GKH |
80 | return NULL; |
81 | } /* unifi_fw_read_start() */ | |
82 | ||
83 | ||
84 | ||
85 | /* | |
86 | * --------------------------------------------------------------------------- | |
87 | * unifi_fw_read_stop | |
88 | * | |
89 | * Called when the HIP driver has finished using the loader or | |
90 | * the firmware file. | |
91 | * The firmware buffer may be released now. | |
92 | * | |
93 | * Arguments: | |
94 | * ospriv Pointer to driver context. | |
95 | * dlpriv The pointer returned by unifi_fw_read_start() | |
96 | * | |
97 | * --------------------------------------------------------------------------- | |
98 | */ | |
99 | void | |
100 | unifi_fw_read_stop(void *ospriv, void *dlpriv) | |
101 | { | |
102 | unifi_priv_t *priv = (unifi_priv_t*)ospriv; | |
103 | struct dlpriv *dl_struct = (struct dlpriv *)dlpriv; | |
635d2b00 GKH |
104 | |
105 | if (dl_struct != NULL) { | |
106 | if (dl_struct->dl_data != NULL) { | |
107 | unifi_trace(priv, UDBG2, "Release f/w buffer %p, %d bytes\n", | |
108 | dl_struct->dl_data, dl_struct->dl_len); | |
109 | } | |
110 | uf_release_firmware(priv, dl_struct); | |
111 | } | |
112 | ||
635d2b00 GKH |
113 | } /* unifi_fw_read_stop() */ |
114 | ||
115 | ||
116 | /* | |
117 | * --------------------------------------------------------------------------- | |
118 | * unifi_fw_open_buffer | |
119 | * | |
120 | * Returns a handle for a buffer dynamically allocated by the driver, | |
121 | * e.g. into which a firmware file may have been converted from another format | |
122 | * which is the case with some production test images. | |
123 | * | |
124 | * The handle may then be used by unifi_fw_read() to access the contents of | |
125 | * the buffer. | |
126 | * | |
127 | * Arguments: | |
128 | * ospriv Pointer to driver context. | |
129 | * fwbuf Buffer containing firmware image | |
130 | * len Length of buffer in bytes | |
131 | * | |
132 | * Returns | |
133 | * Handle for buffer, or NULL on error | |
134 | * --------------------------------------------------------------------------- | |
135 | */ | |
136 | void * | |
26a6b2e1 | 137 | unifi_fw_open_buffer(void *ospriv, void *fwbuf, u32 len) |
635d2b00 GKH |
138 | { |
139 | unifi_priv_t *priv = (unifi_priv_t*)ospriv; | |
635d2b00 GKH |
140 | |
141 | if (fwbuf == NULL) { | |
635d2b00 GKH |
142 | return NULL; |
143 | } | |
144 | priv->fw_conv.dl_data = fwbuf; | |
145 | priv->fw_conv.dl_len = len; | |
146 | priv->fw_conv.fw_desc = NULL; /* No OS f/w resource is associated */ | |
147 | ||
635d2b00 GKH |
148 | return &priv->fw_conv; |
149 | } | |
150 | ||
151 | /* | |
152 | * --------------------------------------------------------------------------- | |
153 | * unifi_fw_close_buffer | |
154 | * | |
155 | * Releases any handle for a buffer dynamically allocated by the driver, | |
156 | * e.g. into which a firmware file may have been converted from another format | |
157 | * which is the case with some production test images. | |
158 | * | |
159 | * | |
160 | * Arguments: | |
161 | * ospriv Pointer to driver context. | |
162 | * fwbuf Buffer containing firmware image | |
163 | * | |
164 | * Returns | |
165 | * Handle for buffer, or NULL on error | |
166 | * --------------------------------------------------------------------------- | |
167 | */ | |
168 | void unifi_fw_close_buffer(void *ospriv, void *fwbuf) | |
169 | { | |
170 | } | |
171 | ||
172 | /* | |
173 | * --------------------------------------------------------------------------- | |
174 | * unifi_fw_read | |
175 | * | |
176 | * The HIP driver calls this function to ask for a part of the loader or | |
177 | * the firmware file. | |
178 | * | |
179 | * Arguments: | |
180 | * ospriv Pointer to driver context. | |
181 | * arg The pointer returned by unifi_fw_read_start(). | |
182 | * offset The offset in the file to return from. | |
183 | * buf A buffer to store the requested data. | |
184 | * len The size of the buf and the size of the requested data. | |
185 | * | |
186 | * Returns | |
187 | * The number of bytes read from the firmware image, or -ve on error | |
188 | * --------------------------------------------------------------------------- | |
189 | */ | |
95e326c2 | 190 | s32 |
26a6b2e1 | 191 | unifi_fw_read(void *ospriv, void *arg, u32 offset, void *buf, u32 len) |
635d2b00 GKH |
192 | { |
193 | const struct dlpriv *dlpriv = arg; | |
194 | ||
195 | if (offset >= dlpriv->dl_len) { | |
196 | /* at end of file */ | |
197 | return 0; | |
198 | } | |
199 | ||
200 | if ((offset + len) > dlpriv->dl_len) { | |
201 | /* attempt to read past end of file */ | |
202 | return -1; | |
203 | } | |
204 | ||
205 | memcpy(buf, dlpriv->dl_data+offset, len); | |
206 | ||
207 | return len; | |
208 | ||
209 | } /* unifi_fw_read() */ | |
210 | ||
211 | ||
212 | ||
213 | ||
635d2b00 GKH |
214 | #define UNIFIHELPER_INIT_MODE_SMEUSER 2 |
215 | #define UNIFIHELPER_INIT_MODE_NATIVE 1 | |
216 | ||
217 | /* | |
218 | * --------------------------------------------------------------------------- | |
219 | * uf_run_unifihelper | |
220 | * | |
221 | * Ask userspace to send us firmware for download by running | |
222 | * '/usr/sbin/unififw'. | |
223 | * The same script starts the SME userspace application. | |
224 | * Derived from net_run_sbin_hotplug(). | |
225 | * | |
226 | * Arguments: | |
227 | * priv Pointer to OS private struct. | |
228 | * | |
229 | * Returns: | |
230 | * None. | |
231 | * --------------------------------------------------------------------------- | |
232 | */ | |
233 | int | |
234 | uf_run_unifihelper(unifi_priv_t *priv) | |
235 | { | |
635d2b00 GKH |
236 | #ifdef ANDROID_BUILD |
237 | char *prog = "/system/bin/unififw"; | |
238 | #else | |
239 | char *prog = "/usr/sbin/unififw"; | |
240 | #endif /* ANDROID_BUILD */ | |
241 | ||
242 | char *argv[6], *envp[4]; | |
243 | char inst_str[8]; | |
244 | char init_mode[8]; | |
245 | int i, r; | |
246 | ||
247 | #if (defined CSR_SME_USERSPACE) && (!defined CSR_SUPPORT_WEXT) | |
248 | unifi_trace(priv, UDBG1, "SME userspace build: run unifi_helper manually\n"); | |
249 | return 0; | |
250 | #endif | |
251 | ||
252 | unifi_trace(priv, UDBG1, "starting %s\n", prog); | |
253 | ||
254 | snprintf(inst_str, 8, "%d", priv->instance); | |
255 | #if (defined CSR_SME_USERSPACE) | |
256 | snprintf(init_mode, 8, "%d", UNIFIHELPER_INIT_MODE_SMEUSER); | |
257 | #else | |
258 | snprintf(init_mode, 8, "%d", UNIFIHELPER_INIT_MODE_NATIVE); | |
259 | #endif /* CSR_SME_USERSPACE */ | |
260 | ||
261 | i = 0; | |
262 | argv[i++] = prog; | |
263 | argv[i++] = inst_str; | |
264 | argv[i++] = init_mode; | |
265 | argv[i++] = 0; | |
266 | argv[i] = 0; | |
267 | /* Don't add more args without making argv bigger */ | |
268 | ||
269 | /* minimal command environment */ | |
270 | i = 0; | |
271 | envp[i++] = "HOME=/"; | |
272 | envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; | |
273 | envp[i] = 0; | |
274 | /* Don't add more without making envp bigger */ | |
275 | ||
276 | unifi_trace(priv, UDBG2, "running %s %s %s\n", argv[0], argv[1], argv[2]); | |
277 | ||
2b5d20db | 278 | r = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC); |
635d2b00 GKH |
279 | |
280 | return r; | |
635d2b00 GKH |
281 | } /* uf_run_unifihelper() */ |
282 | ||
95edd09e | 283 | #ifdef CSR_WIFI_SPLIT_PATCH |
5379b13d | 284 | static u8 is_ap_mode(unifi_priv_t *priv) |
95edd09e GKH |
285 | { |
286 | if (priv == NULL || priv->interfacePriv[0] == NULL) | |
287 | { | |
288 | return FALSE; | |
289 | } | |
635d2b00 | 290 | |
95edd09e GKH |
291 | /* Test for mode requiring AP patch */ |
292 | return(CSR_WIFI_HIP_IS_AP_FW(priv->interfacePriv[0]->interfaceMode)); | |
293 | } | |
294 | #endif | |
635d2b00 GKH |
295 | |
296 | /* | |
297 | * --------------------------------------------------------------------------- | |
298 | * uf_request_firmware_files | |
299 | * | |
300 | * Get the firmware files from userspace. | |
301 | * | |
302 | * Arguments: | |
303 | * priv Pointer to OS private struct. | |
304 | * is_fw type of firmware to load (UNIFI_FW_STA/LOADER) | |
305 | * | |
306 | * Returns: | |
307 | * None. | |
308 | * --------------------------------------------------------------------------- | |
309 | */ | |
310 | int uf_request_firmware_files(unifi_priv_t *priv, int is_fw) | |
311 | { | |
312 | /* uses the default method to get the firmware */ | |
313 | const struct firmware *fw_entry; | |
314 | int postfix; | |
315 | #define UNIFI_MAX_FW_PATH_LEN 32 | |
316 | char fw_name[UNIFI_MAX_FW_PATH_LEN]; | |
317 | int r; | |
318 | ||
319 | #if (defined CSR_SUPPORT_SME) && (defined CSR_SUPPORT_WEXT) | |
320 | if (priv->mib_data.length) { | |
321 | vfree(priv->mib_data.data); | |
322 | priv->mib_data.data = NULL; | |
323 | priv->mib_data.length = 0; | |
324 | } | |
325 | #endif /* CSR_SUPPORT_SME && CSR_SUPPORT_WEXT*/ | |
326 | ||
327 | postfix = priv->instance; | |
328 | ||
329 | if (is_fw == UNIFI_FW_STA) { | |
330 | /* Free kernel buffer and reload */ | |
331 | uf_release_firmware(priv, &priv->fw_sta); | |
95edd09e | 332 | #ifdef CSR_WIFI_SPLIT_PATCH |
635d2b00 | 333 | scnprintf(fw_name, UNIFI_MAX_FW_PATH_LEN, "unifi-sdio-%d/%s", |
95edd09e GKH |
334 | postfix, (is_ap_mode(priv) ? "ap.xbv" : "staonly.xbv") ); |
335 | #else | |
336 | scnprintf(fw_name, UNIFI_MAX_FW_PATH_LEN, "unifi-sdio-%d/%s", | |
337 | postfix, "sta.xbv" ); | |
338 | #endif | |
635d2b00 GKH |
339 | r = request_firmware(&fw_entry, fw_name, priv->unifi_device); |
340 | if (r == 0) { | |
341 | priv->fw_sta.dl_data = fw_entry->data; | |
342 | priv->fw_sta.dl_len = fw_entry->size; | |
343 | priv->fw_sta.fw_desc = (void *)fw_entry; | |
344 | } else { | |
345 | unifi_trace(priv, UDBG2, "Firmware file not available\n"); | |
346 | } | |
347 | } | |
348 | ||
349 | return 0; | |
350 | ||
351 | } /* uf_request_firmware_files() */ | |
352 | ||
353 | /* | |
354 | * --------------------------------------------------------------------------- | |
355 | * uf_release_firmware_files | |
356 | * | |
357 | * Release all buffers used to store firmware files | |
358 | * | |
359 | * Arguments: | |
360 | * priv Pointer to OS private struct. | |
361 | * | |
362 | * Returns: | |
363 | * None. | |
364 | * --------------------------------------------------------------------------- | |
365 | */ | |
366 | int uf_release_firmware_files(unifi_priv_t *priv) | |
367 | { | |
368 | uf_release_firmware(priv, &priv->fw_sta); | |
369 | ||
370 | return 0; | |
371 | } | |
372 | ||
373 | /* | |
374 | * --------------------------------------------------------------------------- | |
375 | * uf_release_firmware | |
376 | * | |
377 | * Release specific buffer used to store firmware | |
378 | * | |
379 | * Arguments: | |
380 | * priv Pointer to OS private struct. | |
381 | * to_free Pointer to specific buffer to release | |
382 | * | |
383 | * Returns: | |
384 | * None. | |
385 | * --------------------------------------------------------------------------- | |
386 | */ | |
387 | int uf_release_firmware(unifi_priv_t *priv, struct dlpriv *to_free) | |
388 | { | |
389 | if (to_free != NULL) { | |
08fb73c1 | 390 | release_firmware((const struct firmware *)to_free->fw_desc); |
635d2b00 GKH |
391 | to_free->fw_desc = NULL; |
392 | to_free->dl_data = NULL; | |
393 | to_free->dl_len = 0; | |
394 | } | |
395 | return 0; | |
396 | } |