]>
Commit | Line | Data |
---|---|---|
635d2b00 GKH |
1 | /* |
2 | * *************************************************************************** | |
3 | * FILE: putest.c | |
4 | * | |
5 | * PURPOSE: putest related functions. | |
6 | * | |
7 | * Copyright (C) 2008-2009 by Cambridge Silicon Radio Ltd. | |
8 | * | |
9 | * Refer to LICENSE.txt included with this source code for details on | |
10 | * the license terms. | |
11 | * | |
12 | * *************************************************************************** | |
13 | */ | |
14 | ||
15 | #include <linux/vmalloc.h> | |
16 | #include <linux/firmware.h> | |
17 | ||
18 | #include "unifi_priv.h" | |
19 | #include "csr_wifi_hip_chiphelper.h" | |
20 | ||
21 | #define UNIFI_PROC_BOTH 3 | |
22 | ||
23 | ||
24 | int unifi_putest_cmd52_read(unifi_priv_t *priv, unsigned char *arg) | |
25 | { | |
26 | struct unifi_putest_cmd52 cmd52_params; | |
7e6f5794 | 27 | u8 *arg_pos; |
635d2b00 GKH |
28 | unsigned int cmd_param_size; |
29 | int r; | |
30 | CsrResult csrResult; | |
31 | unsigned char ret_buffer[32]; | |
7e6f5794 GKH |
32 | u8 *ret_buffer_pos; |
33 | u8 retries; | |
635d2b00 | 34 | |
7e6f5794 | 35 | arg_pos = (u8*)(((unifi_putest_command_t*)arg) + 1); |
635d2b00 GKH |
36 | if (get_user(cmd_param_size, (int*)arg_pos)) { |
37 | unifi_error(priv, | |
38 | "unifi_putest_cmd52_read: Failed to get the argument\n"); | |
39 | return -EFAULT; | |
40 | } | |
41 | ||
42 | if (cmd_param_size != sizeof(struct unifi_putest_cmd52)) { | |
43 | unifi_error(priv, | |
44 | "unifi_putest_cmd52_read: cmd52 struct mismatch\n"); | |
45 | return -EINVAL; | |
46 | } | |
47 | ||
48 | arg_pos += sizeof(unsigned int); | |
49 | if (copy_from_user(&cmd52_params, | |
50 | (void*)arg_pos, | |
51 | sizeof(struct unifi_putest_cmd52))) { | |
52 | unifi_error(priv, | |
53 | "unifi_putest_cmd52_read: Failed to get the cmd52 params\n"); | |
54 | return -EFAULT; | |
55 | } | |
56 | ||
57 | unifi_trace(priv, UDBG2, "cmd52r: func=%d addr=0x%x ", | |
58 | cmd52_params.funcnum, cmd52_params.addr); | |
59 | ||
60 | retries = 3; | |
61 | CsrSdioClaim(priv->sdio); | |
62 | do { | |
63 | if (cmd52_params.funcnum == 0) { | |
64 | csrResult = CsrSdioF0Read8(priv->sdio, cmd52_params.addr, &cmd52_params.data); | |
65 | } else { | |
66 | csrResult = CsrSdioRead8(priv->sdio, cmd52_params.addr, &cmd52_params.data); | |
67 | } | |
68 | } while (--retries && ((csrResult == CSR_SDIO_RESULT_CRC_ERROR) || (csrResult == CSR_SDIO_RESULT_TIMEOUT))); | |
69 | CsrSdioRelease(priv->sdio); | |
70 | ||
71 | if (csrResult != CSR_RESULT_SUCCESS) { | |
72 | unifi_error(priv, | |
73 | "\nunifi_putest_cmd52_read: Read8() failed (csrResult=0x%x)\n", csrResult); | |
74 | return -EFAULT; | |
75 | } | |
76 | unifi_trace(priv, UDBG2, "data=%d\n", cmd52_params.data); | |
77 | ||
78 | /* Copy the info to the out buffer */ | |
79 | *(unifi_putest_command_t*)ret_buffer = UNIFI_PUTEST_CMD52_READ; | |
7e6f5794 | 80 | ret_buffer_pos = (u8*)(((unifi_putest_command_t*)ret_buffer) + 1); |
635d2b00 GKH |
81 | *(unsigned int*)ret_buffer_pos = sizeof(struct unifi_putest_cmd52); |
82 | ret_buffer_pos += sizeof(unsigned int); | |
83 | memcpy(ret_buffer_pos, &cmd52_params, sizeof(struct unifi_putest_cmd52)); | |
84 | ret_buffer_pos += sizeof(struct unifi_putest_cmd52); | |
85 | ||
86 | r = copy_to_user((void*)arg, | |
87 | ret_buffer, | |
88 | ret_buffer_pos - ret_buffer); | |
89 | if (r) { | |
90 | unifi_error(priv, | |
91 | "unifi_putest_cmd52_read: Failed to return the data\n"); | |
92 | return -EFAULT; | |
93 | } | |
94 | ||
95 | return 0; | |
96 | } | |
97 | ||
98 | ||
99 | int unifi_putest_cmd52_write(unifi_priv_t *priv, unsigned char *arg) | |
100 | { | |
101 | struct unifi_putest_cmd52 cmd52_params; | |
7e6f5794 | 102 | u8 *arg_pos; |
635d2b00 GKH |
103 | unsigned int cmd_param_size; |
104 | CsrResult csrResult; | |
7e6f5794 | 105 | u8 retries; |
635d2b00 | 106 | |
7e6f5794 | 107 | arg_pos = (u8*)(((unifi_putest_command_t*)arg) + 1); |
635d2b00 GKH |
108 | if (get_user(cmd_param_size, (int*)arg_pos)) { |
109 | unifi_error(priv, | |
110 | "unifi_putest_cmd52_write: Failed to get the argument\n"); | |
111 | return -EFAULT; | |
112 | } | |
113 | ||
114 | if (cmd_param_size != sizeof(struct unifi_putest_cmd52)) { | |
115 | unifi_error(priv, | |
116 | "unifi_putest_cmd52_write: cmd52 struct mismatch\n"); | |
117 | return -EINVAL; | |
118 | } | |
119 | ||
120 | arg_pos += sizeof(unsigned int); | |
121 | if (copy_from_user(&cmd52_params, | |
122 | (void*)(arg_pos), | |
123 | sizeof(struct unifi_putest_cmd52))) { | |
124 | unifi_error(priv, | |
125 | "unifi_putest_cmd52_write: Failed to get the cmd52 params\n"); | |
126 | return -EFAULT; | |
127 | } | |
128 | ||
129 | unifi_trace(priv, UDBG2, "cmd52w: func=%d addr=0x%x data=%d\n", | |
130 | cmd52_params.funcnum, cmd52_params.addr, cmd52_params.data); | |
131 | ||
132 | retries = 3; | |
133 | CsrSdioClaim(priv->sdio); | |
134 | do { | |
135 | if (cmd52_params.funcnum == 0) { | |
136 | csrResult = CsrSdioF0Write8(priv->sdio, cmd52_params.addr, cmd52_params.data); | |
137 | } else { | |
138 | csrResult = CsrSdioWrite8(priv->sdio, cmd52_params.addr, cmd52_params.data); | |
139 | } | |
140 | } while (--retries && ((csrResult == CSR_SDIO_RESULT_CRC_ERROR) || (csrResult == CSR_SDIO_RESULT_TIMEOUT))); | |
141 | CsrSdioRelease(priv->sdio); | |
142 | ||
143 | if (csrResult != CSR_RESULT_SUCCESS) { | |
144 | unifi_error(priv, | |
145 | "unifi_putest_cmd52_write: Write8() failed (csrResult=0x%x)\n", csrResult); | |
146 | return -EFAULT; | |
147 | } | |
148 | ||
149 | return 0; | |
150 | } | |
151 | ||
152 | int unifi_putest_gp_read16(unifi_priv_t *priv, unsigned char *arg) | |
153 | { | |
154 | struct unifi_putest_gp_rw16 gp_r16_params; | |
7e6f5794 | 155 | u8 *arg_pos; |
635d2b00 GKH |
156 | unsigned int cmd_param_size; |
157 | int r; | |
158 | CsrResult csrResult; | |
159 | unsigned char ret_buffer[32]; | |
7e6f5794 | 160 | u8 *ret_buffer_pos; |
635d2b00 | 161 | |
7e6f5794 | 162 | arg_pos = (u8*)(((unifi_putest_command_t*)arg) + 1); |
635d2b00 GKH |
163 | if (get_user(cmd_param_size, (int*)arg_pos)) { |
164 | unifi_error(priv, | |
165 | "unifi_putest_gp_read16: Failed to get the argument\n"); | |
166 | return -EFAULT; | |
167 | } | |
168 | ||
169 | if (cmd_param_size != sizeof(struct unifi_putest_gp_rw16)) { | |
170 | unifi_error(priv, | |
171 | "unifi_putest_gp_read16: struct mismatch\n"); | |
172 | return -EINVAL; | |
173 | } | |
174 | ||
175 | arg_pos += sizeof(unsigned int); | |
176 | if (copy_from_user(&gp_r16_params, | |
177 | (void*)arg_pos, | |
178 | sizeof(struct unifi_putest_gp_rw16))) { | |
179 | unifi_error(priv, | |
180 | "unifi_putest_gp_read16: Failed to get the params\n"); | |
181 | return -EFAULT; | |
182 | } | |
95edd09e | 183 | CsrSdioClaim(priv->sdio); |
635d2b00 | 184 | csrResult = unifi_card_read16(priv->card, gp_r16_params.addr, &gp_r16_params.data); |
95edd09e | 185 | CsrSdioRelease(priv->sdio); |
635d2b00 GKH |
186 | if (csrResult != CSR_RESULT_SUCCESS) { |
187 | unifi_error(priv, | |
188 | "unifi_putest_gp_read16: unifi_card_read16() GP=0x%x failed (csrResult=0x%x)\n", gp_r16_params.addr, csrResult); | |
189 | return -EFAULT; | |
190 | } | |
191 | ||
192 | unifi_trace(priv, UDBG2, "gp_r16: GP=0x%08x, data=0x%04x\n", gp_r16_params.addr, gp_r16_params.data); | |
193 | ||
194 | /* Copy the info to the out buffer */ | |
195 | *(unifi_putest_command_t*)ret_buffer = UNIFI_PUTEST_GP_READ16; | |
7e6f5794 | 196 | ret_buffer_pos = (u8*)(((unifi_putest_command_t*)ret_buffer) + 1); |
635d2b00 GKH |
197 | *(unsigned int*)ret_buffer_pos = sizeof(struct unifi_putest_gp_rw16); |
198 | ret_buffer_pos += sizeof(unsigned int); | |
199 | memcpy(ret_buffer_pos, &gp_r16_params, sizeof(struct unifi_putest_gp_rw16)); | |
200 | ret_buffer_pos += sizeof(struct unifi_putest_gp_rw16); | |
201 | ||
202 | r = copy_to_user((void*)arg, | |
203 | ret_buffer, | |
204 | ret_buffer_pos - ret_buffer); | |
205 | if (r) { | |
206 | unifi_error(priv, | |
207 | "unifi_putest_gp_read16: Failed to return the data\n"); | |
208 | return -EFAULT; | |
209 | } | |
210 | ||
211 | return 0; | |
212 | } | |
213 | ||
214 | int unifi_putest_gp_write16(unifi_priv_t *priv, unsigned char *arg) | |
215 | { | |
216 | struct unifi_putest_gp_rw16 gp_w16_params; | |
7e6f5794 | 217 | u8 *arg_pos; |
635d2b00 GKH |
218 | unsigned int cmd_param_size; |
219 | CsrResult csrResult; | |
220 | ||
7e6f5794 | 221 | arg_pos = (u8*)(((unifi_putest_command_t*)arg) + 1); |
635d2b00 GKH |
222 | if (get_user(cmd_param_size, (int*)arg_pos)) { |
223 | unifi_error(priv, | |
224 | "unifi_putest_gp_write16: Failed to get the argument\n"); | |
225 | return -EFAULT; | |
226 | } | |
227 | ||
228 | if (cmd_param_size != sizeof(struct unifi_putest_gp_rw16)) { | |
229 | unifi_error(priv, | |
230 | "unifi_putest_gp_write16: struct mismatch\n"); | |
231 | return -EINVAL; | |
232 | } | |
233 | ||
234 | arg_pos += sizeof(unsigned int); | |
235 | if (copy_from_user(&gp_w16_params, | |
236 | (void*)(arg_pos), | |
237 | sizeof(struct unifi_putest_gp_rw16))) { | |
238 | unifi_error(priv, | |
239 | "unifi_putest_gp_write16: Failed to get the params\n"); | |
240 | return -EFAULT; | |
241 | } | |
242 | ||
243 | unifi_trace(priv, UDBG2, "gp_w16: GP=0x%08x, data=0x%04x\n", gp_w16_params.addr, gp_w16_params.data); | |
95edd09e | 244 | CsrSdioClaim(priv->sdio); |
635d2b00 | 245 | csrResult = unifi_card_write16(priv->card, gp_w16_params.addr, gp_w16_params.data); |
95edd09e | 246 | CsrSdioRelease(priv->sdio); |
635d2b00 GKH |
247 | if (csrResult != CSR_RESULT_SUCCESS) { |
248 | unifi_error(priv, | |
249 | "unifi_putest_gp_write16: unifi_card_write16() GP=%x failed (csrResult=0x%x)\n", gp_w16_params.addr, csrResult); | |
250 | return -EFAULT; | |
251 | } | |
252 | ||
253 | return 0; | |
254 | } | |
255 | ||
256 | int unifi_putest_set_sdio_clock(unifi_priv_t *priv, unsigned char *arg) | |
257 | { | |
258 | int sdio_clock_speed; | |
259 | CsrResult csrResult; | |
260 | ||
261 | if (get_user(sdio_clock_speed, (int*)(((unifi_putest_command_t*)arg) + 1))) { | |
262 | unifi_error(priv, | |
263 | "unifi_putest_set_sdio_clock: Failed to get the argument\n"); | |
264 | return -EFAULT; | |
265 | } | |
266 | ||
267 | unifi_trace(priv, UDBG2, "set sdio clock: %d KHz\n", sdio_clock_speed); | |
268 | ||
269 | CsrSdioClaim(priv->sdio); | |
95edd09e | 270 | csrResult = CsrSdioMaxBusClockFrequencySet(priv->sdio, sdio_clock_speed * 1000); |
635d2b00 GKH |
271 | CsrSdioRelease(priv->sdio); |
272 | if (csrResult != CSR_RESULT_SUCCESS) { | |
273 | unifi_error(priv, | |
274 | "unifi_putest_set_sdio_clock: Set clock failed (csrResult=0x%x)\n", csrResult); | |
275 | return -EFAULT; | |
276 | } | |
277 | ||
278 | return 0; | |
279 | } | |
280 | ||
281 | ||
282 | int unifi_putest_start(unifi_priv_t *priv, unsigned char *arg) | |
283 | { | |
284 | int r; | |
285 | CsrResult csrResult; | |
286 | int already_in_test = priv->ptest_mode; | |
287 | ||
288 | /* Ensure that sme_sys_suspend() doesn't power down the chip because: | |
289 | * 1) Power is needed anyway for ptest. | |
290 | * 2) The app code uses the START ioctl as a reset, so it gets called | |
291 | * multiple times. If the app stops the XAPs, but the power_down/up | |
292 | * sequence doesn't actually power down the chip, there can be problems | |
293 | * resetting, because part of the power_up sequence disables function 1 | |
294 | */ | |
295 | priv->ptest_mode = 1; | |
296 | ||
297 | /* Suspend the SME and UniFi */ | |
298 | if (priv->sme_cli) { | |
299 | r = sme_sys_suspend(priv); | |
300 | if (r) { | |
301 | unifi_error(priv, | |
302 | "unifi_putest_start: failed to suspend UniFi\n"); | |
303 | return r; | |
304 | } | |
305 | } | |
306 | ||
307 | /* Application may have stopped the XAPs, but they are needed for reset */ | |
308 | if (already_in_test) { | |
95edd09e | 309 | CsrSdioClaim(priv->sdio); |
635d2b00 | 310 | csrResult = unifi_start_processors(priv->card); |
95edd09e | 311 | CsrSdioRelease(priv->sdio); |
635d2b00 GKH |
312 | if (csrResult != CSR_RESULT_SUCCESS) { |
313 | unifi_error(priv, "Failed to start XAPs. Hard reset required.\n"); | |
314 | } | |
315 | } else { | |
316 | /* Ensure chip is powered for the case where there's no unifi_helper */ | |
317 | CsrSdioClaim(priv->sdio); | |
318 | csrResult = CsrSdioPowerOn(priv->sdio); | |
319 | CsrSdioRelease(priv->sdio); | |
320 | if (csrResult != CSR_RESULT_SUCCESS) { | |
321 | unifi_error(priv, "CsrSdioPowerOn csrResult = %d\n", csrResult); | |
322 | } | |
323 | } | |
95edd09e | 324 | CsrSdioClaim(priv->sdio); |
635d2b00 | 325 | csrResult = unifi_init(priv->card); |
95edd09e | 326 | CsrSdioRelease(priv->sdio); |
635d2b00 GKH |
327 | if (csrResult != CSR_RESULT_SUCCESS) { |
328 | unifi_error(priv, | |
329 | "unifi_putest_start: failed to init UniFi\n"); | |
330 | return CsrHipResultToStatus(csrResult); | |
331 | } | |
332 | ||
333 | return 0; | |
334 | } | |
335 | ||
336 | ||
337 | int unifi_putest_stop(unifi_priv_t *priv, unsigned char *arg) | |
338 | { | |
339 | int r = 0; | |
340 | CsrResult csrResult; | |
341 | ||
342 | /* Application may have stopped the XAPs, but they are needed for reset */ | |
95edd09e | 343 | CsrSdioClaim(priv->sdio); |
635d2b00 | 344 | csrResult = unifi_start_processors(priv->card); |
95edd09e | 345 | CsrSdioRelease(priv->sdio); |
635d2b00 GKH |
346 | if (csrResult != CSR_RESULT_SUCCESS) { |
347 | unifi_error(priv, "Failed to start XAPs. Hard reset required.\n"); | |
348 | } | |
349 | ||
350 | /* PUTEST_STOP is also used to resume the XAPs after SME coredump. | |
351 | * Don't power off the chip, leave that to the normal wifi-off which is | |
352 | * about to carry on. No need to resume the SME either, as it wasn't suspended. | |
353 | */ | |
354 | if (priv->coredump_mode) { | |
355 | priv->coredump_mode = 0; | |
356 | return 0; | |
357 | } | |
358 | ||
359 | /* At this point function 1 is enabled and the XAPs are running, so it is | |
360 | * safe to let the card power down. Power is restored later, asynchronously, | |
361 | * during the wifi_on requested by the SME. | |
362 | */ | |
363 | CsrSdioClaim(priv->sdio); | |
364 | CsrSdioPowerOff(priv->sdio); | |
365 | CsrSdioRelease(priv->sdio); | |
366 | ||
367 | /* Resume the SME and UniFi */ | |
368 | if (priv->sme_cli) { | |
369 | r = sme_sys_resume(priv); | |
370 | if (r) { | |
371 | unifi_error(priv, | |
372 | "unifi_putest_stop: failed to resume SME\n"); | |
373 | } | |
374 | } | |
375 | priv->ptest_mode = 0; | |
376 | ||
377 | return r; | |
378 | } | |
379 | ||
380 | ||
381 | int unifi_putest_dl_fw(unifi_priv_t *priv, unsigned char *arg) | |
382 | { | |
383 | #define UF_PUTEST_MAX_FW_FILE_NAME 16 | |
384 | #define UNIFI_MAX_FW_PATH_LEN 32 | |
385 | unsigned int fw_name_length; | |
386 | unsigned char fw_name[UF_PUTEST_MAX_FW_FILE_NAME+1]; | |
387 | unsigned char *name_buffer; | |
388 | int postfix; | |
389 | char fw_path[UNIFI_MAX_FW_PATH_LEN]; | |
390 | const struct firmware *fw_entry; | |
391 | struct dlpriv temp_fw_sta; | |
392 | int r; | |
393 | CsrResult csrResult; | |
394 | ||
395 | /* Get the f/w file name length */ | |
396 | if (get_user(fw_name_length, (unsigned int*)(((unifi_putest_command_t*)arg) + 1))) { | |
397 | unifi_error(priv, | |
398 | "unifi_putest_dl_fw: Failed to get the length argument\n"); | |
399 | return -EFAULT; | |
400 | } | |
401 | ||
402 | unifi_trace(priv, UDBG2, "unifi_putest_dl_fw: file name size = %d\n", fw_name_length); | |
403 | ||
404 | /* Sanity check for the f/w file name length */ | |
405 | if (fw_name_length > UF_PUTEST_MAX_FW_FILE_NAME) { | |
406 | unifi_error(priv, | |
407 | "unifi_putest_dl_fw: F/W file name is too long\n"); | |
408 | return -EINVAL; | |
409 | } | |
410 | ||
411 | /* Get the f/w file name */ | |
412 | name_buffer = ((unsigned char*)arg) + sizeof(unifi_putest_command_t) + sizeof(unsigned int); | |
413 | if (copy_from_user(fw_name, (void*)name_buffer, fw_name_length)) { | |
414 | unifi_error(priv, "unifi_putest_dl_fw: Failed to get the file name\n"); | |
415 | return -EFAULT; | |
416 | } | |
417 | fw_name[fw_name_length] = '\0'; | |
418 | unifi_trace(priv, UDBG2, "unifi_putest_dl_fw: file = %s\n", fw_name); | |
419 | ||
420 | /* Keep the existing f/w to a temp, we need to restore it later */ | |
421 | temp_fw_sta = priv->fw_sta; | |
422 | ||
423 | /* Get the putest f/w */ | |
424 | postfix = priv->instance; | |
425 | scnprintf(fw_path, UNIFI_MAX_FW_PATH_LEN, "unifi-sdio-%d/%s", | |
426 | postfix, fw_name); | |
427 | r = request_firmware(&fw_entry, fw_path, priv->unifi_device); | |
428 | if (r == 0) { | |
429 | priv->fw_sta.fw_desc = (void *)fw_entry; | |
430 | priv->fw_sta.dl_data = fw_entry->data; | |
431 | priv->fw_sta.dl_len = fw_entry->size; | |
432 | } else { | |
433 | unifi_error(priv, "Firmware file not available\n"); | |
434 | return -EINVAL; | |
435 | } | |
436 | ||
437 | /* Application may have stopped the XAPs, but they are needed for reset */ | |
95edd09e | 438 | CsrSdioClaim(priv->sdio); |
635d2b00 | 439 | csrResult = unifi_start_processors(priv->card); |
95edd09e | 440 | CsrSdioRelease(priv->sdio); |
635d2b00 GKH |
441 | if (csrResult != CSR_RESULT_SUCCESS) { |
442 | unifi_error(priv, "Failed to start XAPs. Hard reset required.\n"); | |
443 | } | |
444 | ||
445 | /* Download the f/w. On UF6xxx this will cause the f/w file to convert | |
446 | * into patch format and download via the ROM boot loader | |
447 | */ | |
95edd09e | 448 | CsrSdioClaim(priv->sdio); |
635d2b00 | 449 | csrResult = unifi_download(priv->card, 0x0c00); |
95edd09e | 450 | CsrSdioRelease(priv->sdio); |
635d2b00 GKH |
451 | if (csrResult != CSR_RESULT_SUCCESS) { |
452 | unifi_error(priv, | |
453 | "unifi_putest_dl_fw: failed to download the f/w\n"); | |
454 | goto free_fw; | |
455 | } | |
456 | ||
457 | /* Free the putest f/w... */ | |
458 | free_fw: | |
459 | uf_release_firmware(priv, &priv->fw_sta); | |
460 | /* ... and restore the original f/w */ | |
461 | priv->fw_sta = temp_fw_sta; | |
462 | ||
463 | return CsrHipResultToStatus(csrResult); | |
464 | } | |
465 | ||
466 | ||
467 | int unifi_putest_dl_fw_buff(unifi_priv_t *priv, unsigned char *arg) | |
468 | { | |
469 | unsigned int fw_length; | |
470 | unsigned char *fw_buf = NULL; | |
471 | unsigned char *fw_user_ptr; | |
472 | struct dlpriv temp_fw_sta; | |
473 | CsrResult csrResult; | |
474 | ||
475 | /* Get the f/w buffer length */ | |
476 | if (get_user(fw_length, (unsigned int*)(((unifi_putest_command_t*)arg) + 1))) { | |
477 | unifi_error(priv, | |
478 | "unifi_putest_dl_fw_buff: Failed to get the length arg\n"); | |
479 | return -EFAULT; | |
480 | } | |
481 | ||
482 | unifi_trace(priv, UDBG2, "unifi_putest_dl_fw_buff: size = %d\n", fw_length); | |
483 | ||
484 | /* Sanity check for the buffer length */ | |
485 | if (fw_length == 0 || fw_length > 0xfffffff) { | |
486 | unifi_error(priv, | |
487 | "unifi_putest_dl_fw_buff: buffer length bad %u\n", fw_length); | |
488 | return -EINVAL; | |
489 | } | |
490 | ||
491 | /* Buffer for kernel copy of the f/w image */ | |
786eeeb3 | 492 | fw_buf = kmalloc(fw_length, GFP_KERNEL); |
635d2b00 GKH |
493 | if (!fw_buf) { |
494 | unifi_error(priv, "unifi_putest_dl_fw_buff: malloc fail\n"); | |
495 | return -ENOMEM; | |
496 | } | |
497 | ||
498 | /* Get the f/w image */ | |
499 | fw_user_ptr = ((unsigned char*)arg) + sizeof(unifi_putest_command_t) + sizeof(unsigned int); | |
500 | if (copy_from_user(fw_buf, (void*)fw_user_ptr, fw_length)) { | |
501 | unifi_error(priv, "unifi_putest_dl_fw_buff: Failed to get the buffer\n"); | |
55a27055 | 502 | kfree(fw_buf); |
635d2b00 GKH |
503 | return -EFAULT; |
504 | } | |
505 | ||
506 | /* Save the existing f/w to a temp, we need to restore it later */ | |
507 | temp_fw_sta = priv->fw_sta; | |
508 | ||
509 | /* Setting fw_desc NULL indicates to the core that no f/w file was loaded | |
510 | * via the kernel request_firmware() mechanism. This indicates to the core | |
511 | * that it shouldn't call release_firmware() after the download is done. | |
512 | */ | |
513 | priv->fw_sta.fw_desc = NULL; /* No OS f/w resource */ | |
514 | priv->fw_sta.dl_data = fw_buf; | |
515 | priv->fw_sta.dl_len = fw_length; | |
516 | ||
517 | /* Application may have stopped the XAPs, but they are needed for reset */ | |
95edd09e | 518 | CsrSdioClaim(priv->sdio); |
635d2b00 | 519 | csrResult = unifi_start_processors(priv->card); |
95edd09e | 520 | CsrSdioRelease(priv->sdio); |
635d2b00 GKH |
521 | if (csrResult != CSR_RESULT_SUCCESS) { |
522 | unifi_error(priv, "Failed to start XAPs. Hard reset required.\n"); | |
523 | } | |
524 | ||
525 | /* Download the f/w. On UF6xxx this will cause the f/w file to convert | |
526 | * into patch format and download via the ROM boot loader | |
527 | */ | |
95edd09e | 528 | CsrSdioClaim(priv->sdio); |
635d2b00 | 529 | csrResult = unifi_download(priv->card, 0x0c00); |
95edd09e | 530 | CsrSdioRelease(priv->sdio); |
635d2b00 GKH |
531 | if (csrResult != CSR_RESULT_SUCCESS) { |
532 | unifi_error(priv, | |
533 | "unifi_putest_dl_fw_buff: failed to download the f/w\n"); | |
534 | goto free_fw; | |
535 | } | |
536 | ||
537 | free_fw: | |
538 | /* Finished with the putest f/w, so restore the station f/w */ | |
539 | priv->fw_sta = temp_fw_sta; | |
55a27055 | 540 | kfree(fw_buf); |
635d2b00 GKH |
541 | |
542 | return CsrHipResultToStatus(csrResult); | |
543 | } | |
544 | ||
545 | ||
546 | int unifi_putest_coredump_prepare(unifi_priv_t *priv, unsigned char *arg) | |
547 | { | |
8c87f69a | 548 | u16 data_u16; |
95e326c2 | 549 | s32 i; |
635d2b00 GKH |
550 | CsrResult r; |
551 | ||
552 | unifi_info(priv, "Preparing for SDIO coredump\n"); | |
553 | #if defined (CSR_WIFI_HIP_DEBUG_OFFLINE) | |
554 | unifi_debug_buf_dump(); | |
555 | #endif | |
556 | ||
557 | /* Sanity check that userspace hasn't called a PUTEST_START, because that | |
558 | * would have reset UniFi, potentially power cycling it and losing context | |
559 | */ | |
560 | if (priv->ptest_mode) { | |
561 | unifi_error(priv, "PUTEST_START shouldn't be used before a coredump\n"); | |
562 | } | |
563 | ||
564 | /* Flag that the userspace has requested coredump. Even if this preparation | |
565 | * fails, the SME will call PUTEST_STOP to tidy up. | |
566 | */ | |
567 | priv->coredump_mode = 1; | |
568 | ||
569 | for (i = 0; i < 3; i++) { | |
570 | CsrSdioClaim(priv->sdio); | |
571 | r = CsrSdioRead16(priv->sdio, CHIP_HELPER_UNIFI_GBL_CHIP_VERSION*2, &data_u16); | |
572 | CsrSdioRelease(priv->sdio); | |
573 | if (r != CSR_RESULT_SUCCESS) { | |
574 | unifi_info(priv, "Failed to read chip version! Try %d\n", i); | |
575 | ||
576 | /* First try, re-enable function which may have been disabled by f/w panic */ | |
577 | if (i == 0) { | |
578 | unifi_info(priv, "Try function enable\n"); | |
579 | CsrSdioClaim(priv->sdio); | |
580 | r = CsrSdioFunctionEnable(priv->sdio); | |
581 | CsrSdioRelease(priv->sdio); | |
582 | if (r != CSR_RESULT_SUCCESS) { | |
583 | unifi_error(priv, "CsrSdioFunctionEnable failed %d\n", r); | |
584 | } | |
585 | continue; | |
586 | } | |
587 | ||
588 | /* Subsequent tries, reset */ | |
589 | ||
590 | /* Set clock speed low */ | |
591 | CsrSdioClaim(priv->sdio); | |
592 | r = CsrSdioMaxBusClockFrequencySet(priv->sdio, UNIFI_SDIO_CLOCK_SAFE_HZ); | |
593 | CsrSdioRelease(priv->sdio); | |
594 | if (r != CSR_RESULT_SUCCESS) { | |
595 | unifi_error(priv, "CsrSdioMaxBusClockFrequencySet() failed %d\n", r); | |
596 | } | |
597 | ||
598 | /* Card software reset */ | |
95edd09e | 599 | CsrSdioClaim(priv->sdio); |
635d2b00 | 600 | r = unifi_card_hard_reset(priv->card); |
95edd09e | 601 | CsrSdioRelease(priv->sdio); |
635d2b00 GKH |
602 | if (r != CSR_RESULT_SUCCESS) { |
603 | unifi_error(priv, "unifi_card_hard_reset() failed %d\n", r); | |
604 | } | |
605 | } else { | |
606 | unifi_info(priv, "Read chip version of 0x%04x\n", data_u16); | |
607 | break; | |
608 | } | |
609 | } | |
610 | ||
611 | if (r != CSR_RESULT_SUCCESS) { | |
612 | unifi_error(priv, "Failed to prepare chip\n"); | |
613 | return -EIO; | |
614 | } | |
615 | ||
616 | /* Stop the XAPs for coredump. The PUTEST_STOP must be called, e.g. at | |
617 | * Raw SDIO deinit, to resume them. | |
618 | */ | |
95edd09e | 619 | CsrSdioClaim(priv->sdio); |
635d2b00 | 620 | r = unifi_card_stop_processor(priv->card, UNIFI_PROC_BOTH); |
95edd09e | 621 | CsrSdioRelease(priv->sdio); |
635d2b00 GKH |
622 | if (r != CSR_RESULT_SUCCESS) { |
623 | unifi_error(priv, "Failed to stop processors\n"); | |
624 | } | |
625 | ||
626 | return 0; | |
627 | } | |
628 | ||
629 | int unifi_putest_cmd52_block_read(unifi_priv_t *priv, unsigned char *arg) | |
630 | { | |
631 | struct unifi_putest_block_cmd52_r block_cmd52; | |
7e6f5794 | 632 | u8 *arg_pos; |
635d2b00 GKH |
633 | unsigned int cmd_param_size; |
634 | CsrResult r; | |
7e6f5794 | 635 | u8 *block_local_buffer; |
635d2b00 | 636 | |
7e6f5794 | 637 | arg_pos = (u8*)(((unifi_putest_command_t*)arg) + 1); |
635d2b00 GKH |
638 | if (get_user(cmd_param_size, (int*)arg_pos)) { |
639 | unifi_error(priv, | |
640 | "cmd52r_block: Failed to get the argument\n"); | |
641 | return -EFAULT; | |
642 | } | |
643 | ||
644 | if (cmd_param_size != sizeof(struct unifi_putest_block_cmd52_r)) { | |
645 | unifi_error(priv, | |
646 | "cmd52r_block: cmd52 struct mismatch\n"); | |
647 | return -EINVAL; | |
648 | } | |
649 | ||
650 | arg_pos += sizeof(unsigned int); | |
651 | if (copy_from_user(&block_cmd52, | |
652 | (void*)arg_pos, | |
653 | sizeof(struct unifi_putest_block_cmd52_r))) { | |
654 | unifi_error(priv, | |
655 | "cmd52r_block: Failed to get the cmd52 params\n"); | |
656 | return -EFAULT; | |
657 | } | |
658 | ||
659 | unifi_trace(priv, UDBG2, "cmd52r_block: func=%d addr=0x%x len=0x%x ", | |
660 | block_cmd52.funcnum, block_cmd52.addr, block_cmd52.length); | |
661 | ||
662 | block_local_buffer = vmalloc(block_cmd52.length); | |
663 | if (block_local_buffer == NULL) { | |
664 | unifi_error(priv, "cmd52r_block: Failed to allocate buffer\n"); | |
665 | return -ENOMEM; | |
666 | } | |
667 | ||
95edd09e | 668 | CsrSdioClaim(priv->sdio); |
635d2b00 | 669 | r = unifi_card_readn(priv->card, block_cmd52.addr, block_local_buffer, block_cmd52.length); |
95edd09e | 670 | CsrSdioRelease(priv->sdio); |
635d2b00 GKH |
671 | if (r != CSR_RESULT_SUCCESS) { |
672 | unifi_error(priv, "cmd52r_block: unifi_readn failed\n"); | |
673 | return -EIO; | |
674 | } | |
675 | ||
676 | if (copy_to_user((void*)block_cmd52.data, | |
677 | block_local_buffer, | |
678 | block_cmd52.length)) { | |
679 | unifi_error(priv, | |
680 | "cmd52r_block: Failed to return the data\n"); | |
681 | return -EFAULT; | |
682 | } | |
683 | ||
684 | return 0; | |
685 | } |