1 | // SPDX-License-Identifier: ISC |
2 | /* |
3 | * Copyright (c) 2014-2017 Qualcomm Atheros, Inc. |
4 | */ |
5 | |
6 | #include "testmode.h" |
7 | |
8 | #include <net/netlink.h> |
9 | #include <linux/firmware.h> |
10 | |
11 | #include "debug.h" |
12 | #include "wmi.h" |
13 | #include "hif.h" |
14 | #include "hw.h" |
15 | #include "core.h" |
16 | |
17 | #include "testmode_i.h" |
18 | |
19 | static const struct nla_policy ath10k_tm_policy[ATH10K_TM_ATTR_MAX + 1] = { |
20 | [ATH10K_TM_ATTR_CMD] = { .type = NLA_U32 }, |
21 | [ATH10K_TM_ATTR_DATA] = { .type = NLA_BINARY, |
22 | .len = ATH10K_TM_DATA_MAX_LEN }, |
23 | [ATH10K_TM_ATTR_WMI_CMDID] = { .type = NLA_U32 }, |
24 | [ATH10K_TM_ATTR_VERSION_MAJOR] = { .type = NLA_U32 }, |
25 | [ATH10K_TM_ATTR_VERSION_MINOR] = { .type = NLA_U32 }, |
26 | }; |
27 | |
28 | /* Returns true if callee consumes the skb and the skb should be discarded. |
29 | * Returns false if skb is not used. Does not sleep. |
30 | */ |
31 | bool ath10k_tm_event_wmi(struct ath10k *ar, u32 cmd_id, struct sk_buff *skb) |
32 | { |
33 | struct sk_buff *nl_skb; |
34 | bool consumed; |
35 | int ret; |
36 | |
37 | ath10k_dbg(ar, ATH10K_DBG_TESTMODE, |
38 | "testmode event wmi cmd_id %d skb %pK skb->len %d\n" , |
39 | cmd_id, skb, skb->len); |
40 | |
41 | ath10k_dbg_dump(ar, mask: ATH10K_DBG_TESTMODE, NULL, prefix: "" , buf: skb->data, len: skb->len); |
42 | |
43 | spin_lock_bh(lock: &ar->data_lock); |
44 | |
45 | if (!ar->testmode.utf_monitor) { |
46 | consumed = false; |
47 | goto out; |
48 | } |
49 | |
50 | /* Only testmode.c should be handling events from utf firmware, |
51 | * otherwise all sort of problems will arise as mac80211 operations |
52 | * are not initialised. |
53 | */ |
54 | consumed = true; |
55 | |
56 | nl_skb = cfg80211_testmode_alloc_event_skb(wiphy: ar->hw->wiphy, |
57 | approxlen: 2 * sizeof(u32) + skb->len, |
58 | GFP_ATOMIC); |
59 | if (!nl_skb) { |
60 | ath10k_warn(ar, |
61 | fmt: "failed to allocate skb for testmode wmi event\n" ); |
62 | goto out; |
63 | } |
64 | |
65 | ret = nla_put_u32(skb: nl_skb, attrtype: ATH10K_TM_ATTR_CMD, value: ATH10K_TM_CMD_WMI); |
66 | if (ret) { |
67 | ath10k_warn(ar, |
68 | fmt: "failed to put testmode wmi event cmd attribute: %d\n" , |
69 | ret); |
70 | kfree_skb(skb: nl_skb); |
71 | goto out; |
72 | } |
73 | |
74 | ret = nla_put_u32(skb: nl_skb, attrtype: ATH10K_TM_ATTR_WMI_CMDID, value: cmd_id); |
75 | if (ret) { |
76 | ath10k_warn(ar, |
77 | fmt: "failed to put testmode wmi event cmd_id: %d\n" , |
78 | ret); |
79 | kfree_skb(skb: nl_skb); |
80 | goto out; |
81 | } |
82 | |
83 | ret = nla_put(skb: nl_skb, attrtype: ATH10K_TM_ATTR_DATA, attrlen: skb->len, data: skb->data); |
84 | if (ret) { |
85 | ath10k_warn(ar, |
86 | fmt: "failed to copy skb to testmode wmi event: %d\n" , |
87 | ret); |
88 | kfree_skb(skb: nl_skb); |
89 | goto out; |
90 | } |
91 | |
92 | cfg80211_testmode_event(skb: nl_skb, GFP_ATOMIC); |
93 | |
94 | out: |
95 | spin_unlock_bh(lock: &ar->data_lock); |
96 | |
97 | return consumed; |
98 | } |
99 | |
100 | static int ath10k_tm_cmd_get_version(struct ath10k *ar, struct nlattr *tb[]) |
101 | { |
102 | struct sk_buff *skb; |
103 | int ret; |
104 | |
105 | ath10k_dbg(ar, ATH10K_DBG_TESTMODE, |
106 | "testmode cmd get version_major %d version_minor %d\n" , |
107 | ATH10K_TESTMODE_VERSION_MAJOR, |
108 | ATH10K_TESTMODE_VERSION_MINOR); |
109 | |
110 | skb = cfg80211_testmode_alloc_reply_skb(wiphy: ar->hw->wiphy, |
111 | approxlen: nla_total_size(payload: sizeof(u32))); |
112 | if (!skb) |
113 | return -ENOMEM; |
114 | |
115 | ret = nla_put_u32(skb, attrtype: ATH10K_TM_ATTR_VERSION_MAJOR, |
116 | ATH10K_TESTMODE_VERSION_MAJOR); |
117 | if (ret) { |
118 | kfree_skb(skb); |
119 | return ret; |
120 | } |
121 | |
122 | ret = nla_put_u32(skb, attrtype: ATH10K_TM_ATTR_VERSION_MINOR, |
123 | ATH10K_TESTMODE_VERSION_MINOR); |
124 | if (ret) { |
125 | kfree_skb(skb); |
126 | return ret; |
127 | } |
128 | |
129 | ret = nla_put_u32(skb, attrtype: ATH10K_TM_ATTR_WMI_OP_VERSION, |
130 | value: ar->normal_mode_fw.fw_file.wmi_op_version); |
131 | if (ret) { |
132 | kfree_skb(skb); |
133 | return ret; |
134 | } |
135 | |
136 | return cfg80211_testmode_reply(skb); |
137 | } |
138 | |
139 | static int ath10k_tm_fetch_utf_firmware_api_1(struct ath10k *ar, |
140 | struct ath10k_fw_file *fw_file) |
141 | { |
142 | char filename[100]; |
143 | int ret; |
144 | |
145 | snprintf(buf: filename, size: sizeof(filename), fmt: "%s/%s" , |
146 | ar->hw_params.fw.dir, ATH10K_FW_UTF_FILE); |
147 | |
148 | /* load utf firmware image */ |
149 | ret = firmware_request_nowarn(fw: &fw_file->firmware, name: filename, device: ar->dev); |
150 | ath10k_dbg(ar, ATH10K_DBG_TESTMODE, "testmode fw request '%s': %d\n" , |
151 | filename, ret); |
152 | |
153 | if (ret) { |
154 | ath10k_warn(ar, fmt: "failed to retrieve utf firmware '%s': %d\n" , |
155 | filename, ret); |
156 | return ret; |
157 | } |
158 | |
159 | /* We didn't find FW UTF API 1 ("utf.bin") does not advertise |
160 | * firmware features. Do an ugly hack where we force the firmware |
161 | * features to match with 10.1 branch so that wmi.c will use the |
162 | * correct WMI interface. |
163 | */ |
164 | |
165 | fw_file->wmi_op_version = ATH10K_FW_WMI_OP_VERSION_10_1; |
166 | fw_file->htt_op_version = ATH10K_FW_HTT_OP_VERSION_10_1; |
167 | fw_file->firmware_data = fw_file->firmware->data; |
168 | fw_file->firmware_len = fw_file->firmware->size; |
169 | |
170 | return 0; |
171 | } |
172 | |
173 | static int ath10k_tm_fetch_firmware(struct ath10k *ar) |
174 | { |
175 | struct ath10k_fw_components *utf_mode_fw; |
176 | int ret; |
177 | char fw_name[100]; |
178 | int fw_api2 = 2; |
179 | |
180 | switch (ar->hif.bus) { |
181 | case ATH10K_BUS_SDIO: |
182 | case ATH10K_BUS_USB: |
183 | scnprintf(buf: fw_name, size: sizeof(fw_name), fmt: "%s-%s-%d.bin" , |
184 | ATH10K_FW_UTF_FILE_BASE, ath10k_bus_str(bus: ar->hif.bus), |
185 | fw_api2); |
186 | break; |
187 | default: |
188 | scnprintf(buf: fw_name, size: sizeof(fw_name), fmt: "%s-%d.bin" , |
189 | ATH10K_FW_UTF_FILE_BASE, fw_api2); |
190 | break; |
191 | } |
192 | |
193 | ret = ath10k_core_fetch_firmware_api_n(ar, name: fw_name, |
194 | fw_file: &ar->testmode.utf_mode_fw.fw_file); |
195 | if (ret == 0) { |
196 | ath10k_dbg(ar, ATH10K_DBG_TESTMODE, "testmode using fw utf api 2" ); |
197 | goto out; |
198 | } |
199 | |
200 | ret = ath10k_tm_fetch_utf_firmware_api_1(ar, fw_file: &ar->testmode.utf_mode_fw.fw_file); |
201 | if (ret) { |
202 | ath10k_err(ar, fmt: "failed to fetch utf firmware binary: %d" , ret); |
203 | return ret; |
204 | } |
205 | |
206 | ath10k_dbg(ar, ATH10K_DBG_TESTMODE, "testmode using utf api 1" ); |
207 | |
208 | out: |
209 | utf_mode_fw = &ar->testmode.utf_mode_fw; |
210 | |
211 | /* Use the same board data file as the normal firmware uses (but |
212 | * it's still "owned" by normal_mode_fw so we shouldn't free it. |
213 | */ |
214 | utf_mode_fw->board_data = ar->normal_mode_fw.board_data; |
215 | utf_mode_fw->board_len = ar->normal_mode_fw.board_len; |
216 | |
217 | if (!utf_mode_fw->fw_file.otp_data) { |
218 | ath10k_info(ar, fmt: "utf.bin didn't contain otp binary, taking it from the normal mode firmware" ); |
219 | utf_mode_fw->fw_file.otp_data = ar->normal_mode_fw.fw_file.otp_data; |
220 | utf_mode_fw->fw_file.otp_len = ar->normal_mode_fw.fw_file.otp_len; |
221 | } |
222 | |
223 | return 0; |
224 | } |
225 | |
226 | static int ath10k_tm_cmd_utf_start(struct ath10k *ar, struct nlattr *tb[]) |
227 | { |
228 | const char *ver; |
229 | int ret; |
230 | |
231 | ath10k_dbg(ar, ATH10K_DBG_TESTMODE, "testmode cmd utf start\n" ); |
232 | |
233 | mutex_lock(&ar->conf_mutex); |
234 | |
235 | if (ar->state == ATH10K_STATE_UTF) { |
236 | ret = -EALREADY; |
237 | goto err; |
238 | } |
239 | |
240 | /* start utf only when the driver is not in use */ |
241 | if (ar->state != ATH10K_STATE_OFF) { |
242 | ret = -EBUSY; |
243 | goto err; |
244 | } |
245 | |
246 | if (WARN_ON(ar->testmode.utf_mode_fw.fw_file.firmware != NULL)) { |
247 | /* utf image is already downloaded, it shouldn't be */ |
248 | ret = -EEXIST; |
249 | goto err; |
250 | } |
251 | |
252 | ret = ath10k_tm_fetch_firmware(ar); |
253 | if (ret) { |
254 | ath10k_err(ar, fmt: "failed to fetch UTF firmware: %d" , ret); |
255 | goto err; |
256 | } |
257 | |
258 | if (ar->testmode.utf_mode_fw.fw_file.codeswap_data && |
259 | ar->testmode.utf_mode_fw.fw_file.codeswap_len) { |
260 | ret = ath10k_swap_code_seg_init(ar, |
261 | fw_file: &ar->testmode.utf_mode_fw.fw_file); |
262 | if (ret) { |
263 | ath10k_warn(ar, |
264 | fmt: "failed to init utf code swap segment: %d\n" , |
265 | ret); |
266 | goto err_release_utf_mode_fw; |
267 | } |
268 | } |
269 | |
270 | spin_lock_bh(lock: &ar->data_lock); |
271 | ar->testmode.utf_monitor = true; |
272 | spin_unlock_bh(lock: &ar->data_lock); |
273 | |
274 | ath10k_dbg(ar, ATH10K_DBG_TESTMODE, "testmode wmi version %d\n" , |
275 | ar->testmode.utf_mode_fw.fw_file.wmi_op_version); |
276 | |
277 | ret = ath10k_hif_power_up(ar, fw_mode: ATH10K_FIRMWARE_MODE_UTF); |
278 | if (ret) { |
279 | ath10k_err(ar, fmt: "failed to power up hif (testmode): %d\n" , ret); |
280 | ar->state = ATH10K_STATE_OFF; |
281 | goto err_release_utf_mode_fw; |
282 | } |
283 | |
284 | ret = ath10k_core_start(ar, mode: ATH10K_FIRMWARE_MODE_UTF, |
285 | fw_components: &ar->testmode.utf_mode_fw); |
286 | if (ret) { |
287 | ath10k_err(ar, fmt: "failed to start core (testmode): %d\n" , ret); |
288 | ar->state = ATH10K_STATE_OFF; |
289 | goto err_power_down; |
290 | } |
291 | |
292 | ar->state = ATH10K_STATE_UTF; |
293 | |
294 | if (strlen(ar->testmode.utf_mode_fw.fw_file.fw_version) > 0) |
295 | ver = ar->testmode.utf_mode_fw.fw_file.fw_version; |
296 | else |
297 | ver = "API 1" ; |
298 | |
299 | ath10k_info(ar, fmt: "UTF firmware %s started\n" , ver); |
300 | |
301 | mutex_unlock(lock: &ar->conf_mutex); |
302 | |
303 | return 0; |
304 | |
305 | err_power_down: |
306 | ath10k_hif_power_down(ar); |
307 | |
308 | err_release_utf_mode_fw: |
309 | if (ar->testmode.utf_mode_fw.fw_file.codeswap_data && |
310 | ar->testmode.utf_mode_fw.fw_file.codeswap_len) |
311 | ath10k_swap_code_seg_release(ar, |
312 | fw_file: &ar->testmode.utf_mode_fw.fw_file); |
313 | |
314 | release_firmware(fw: ar->testmode.utf_mode_fw.fw_file.firmware); |
315 | ar->testmode.utf_mode_fw.fw_file.firmware = NULL; |
316 | |
317 | err: |
318 | mutex_unlock(lock: &ar->conf_mutex); |
319 | |
320 | return ret; |
321 | } |
322 | |
323 | static void __ath10k_tm_cmd_utf_stop(struct ath10k *ar) |
324 | { |
325 | lockdep_assert_held(&ar->conf_mutex); |
326 | |
327 | ath10k_core_stop(ar); |
328 | ath10k_hif_power_down(ar); |
329 | |
330 | spin_lock_bh(lock: &ar->data_lock); |
331 | |
332 | ar->testmode.utf_monitor = false; |
333 | |
334 | spin_unlock_bh(lock: &ar->data_lock); |
335 | |
336 | if (ar->testmode.utf_mode_fw.fw_file.codeswap_data && |
337 | ar->testmode.utf_mode_fw.fw_file.codeswap_len) |
338 | ath10k_swap_code_seg_release(ar, |
339 | fw_file: &ar->testmode.utf_mode_fw.fw_file); |
340 | |
341 | release_firmware(fw: ar->testmode.utf_mode_fw.fw_file.firmware); |
342 | ar->testmode.utf_mode_fw.fw_file.firmware = NULL; |
343 | |
344 | ar->state = ATH10K_STATE_OFF; |
345 | } |
346 | |
347 | static int ath10k_tm_cmd_utf_stop(struct ath10k *ar, struct nlattr *tb[]) |
348 | { |
349 | int ret; |
350 | |
351 | ath10k_dbg(ar, ATH10K_DBG_TESTMODE, "testmode cmd utf stop\n" ); |
352 | |
353 | mutex_lock(&ar->conf_mutex); |
354 | |
355 | if (ar->state != ATH10K_STATE_UTF) { |
356 | ret = -ENETDOWN; |
357 | goto out; |
358 | } |
359 | |
360 | __ath10k_tm_cmd_utf_stop(ar); |
361 | |
362 | ret = 0; |
363 | |
364 | ath10k_info(ar, fmt: "UTF firmware stopped\n" ); |
365 | |
366 | out: |
367 | mutex_unlock(lock: &ar->conf_mutex); |
368 | return ret; |
369 | } |
370 | |
371 | static int ath10k_tm_cmd_wmi(struct ath10k *ar, struct nlattr *tb[]) |
372 | { |
373 | struct sk_buff *skb; |
374 | int ret, buf_len; |
375 | u32 cmd_id; |
376 | void *buf; |
377 | |
378 | mutex_lock(&ar->conf_mutex); |
379 | |
380 | if (ar->state != ATH10K_STATE_UTF) { |
381 | ret = -ENETDOWN; |
382 | goto out; |
383 | } |
384 | |
385 | if (!tb[ATH10K_TM_ATTR_DATA]) { |
386 | ret = -EINVAL; |
387 | goto out; |
388 | } |
389 | |
390 | if (!tb[ATH10K_TM_ATTR_WMI_CMDID]) { |
391 | ret = -EINVAL; |
392 | goto out; |
393 | } |
394 | |
395 | buf = nla_data(nla: tb[ATH10K_TM_ATTR_DATA]); |
396 | buf_len = nla_len(nla: tb[ATH10K_TM_ATTR_DATA]); |
397 | cmd_id = nla_get_u32(nla: tb[ATH10K_TM_ATTR_WMI_CMDID]); |
398 | |
399 | ath10k_dbg(ar, ATH10K_DBG_TESTMODE, |
400 | "testmode cmd wmi cmd_id %d buf %pK buf_len %d\n" , |
401 | cmd_id, buf, buf_len); |
402 | |
403 | ath10k_dbg_dump(ar, mask: ATH10K_DBG_TESTMODE, NULL, prefix: "" , buf, len: buf_len); |
404 | |
405 | skb = ath10k_wmi_alloc_skb(ar, len: buf_len); |
406 | if (!skb) { |
407 | ret = -ENOMEM; |
408 | goto out; |
409 | } |
410 | |
411 | memcpy(skb->data, buf, buf_len); |
412 | |
413 | ret = ath10k_wmi_cmd_send(ar, skb, cmd_id); |
414 | if (ret) { |
415 | ath10k_warn(ar, fmt: "failed to transmit wmi command (testmode): %d\n" , |
416 | ret); |
417 | goto out; |
418 | } |
419 | |
420 | ret = 0; |
421 | |
422 | out: |
423 | mutex_unlock(lock: &ar->conf_mutex); |
424 | return ret; |
425 | } |
426 | |
427 | int ath10k_tm_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif, |
428 | void *data, int len) |
429 | { |
430 | struct ath10k *ar = hw->priv; |
431 | struct nlattr *tb[ATH10K_TM_ATTR_MAX + 1]; |
432 | int ret; |
433 | |
434 | ret = nla_parse_deprecated(tb, maxtype: ATH10K_TM_ATTR_MAX, head: data, len, |
435 | policy: ath10k_tm_policy, NULL); |
436 | if (ret) |
437 | return ret; |
438 | |
439 | if (!tb[ATH10K_TM_ATTR_CMD]) |
440 | return -EINVAL; |
441 | |
442 | switch (nla_get_u32(nla: tb[ATH10K_TM_ATTR_CMD])) { |
443 | case ATH10K_TM_CMD_GET_VERSION: |
444 | return ath10k_tm_cmd_get_version(ar, tb); |
445 | case ATH10K_TM_CMD_UTF_START: |
446 | return ath10k_tm_cmd_utf_start(ar, tb); |
447 | case ATH10K_TM_CMD_UTF_STOP: |
448 | return ath10k_tm_cmd_utf_stop(ar, tb); |
449 | case ATH10K_TM_CMD_WMI: |
450 | return ath10k_tm_cmd_wmi(ar, tb); |
451 | default: |
452 | return -EOPNOTSUPP; |
453 | } |
454 | } |
455 | |
456 | void ath10k_testmode_destroy(struct ath10k *ar) |
457 | { |
458 | mutex_lock(&ar->conf_mutex); |
459 | |
460 | if (ar->state != ATH10K_STATE_UTF) { |
461 | /* utf firmware is not running, nothing to do */ |
462 | goto out; |
463 | } |
464 | |
465 | __ath10k_tm_cmd_utf_stop(ar); |
466 | |
467 | out: |
468 | mutex_unlock(lock: &ar->conf_mutex); |
469 | } |
470 | |