1 | // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause |
2 | /* |
3 | * Copyright (C) 2018-2024 Intel Corporation |
4 | */ |
5 | #include <linux/firmware.h> |
6 | #include "iwl-drv.h" |
7 | #include "iwl-trans.h" |
8 | #include "iwl-dbg-tlv.h" |
9 | #include "fw/dbg.h" |
10 | #include "fw/runtime.h" |
11 | |
12 | /** |
13 | * enum iwl_dbg_tlv_type - debug TLV types |
14 | * @IWL_DBG_TLV_TYPE_DEBUG_INFO: debug info TLV |
15 | * @IWL_DBG_TLV_TYPE_BUF_ALLOC: buffer allocation TLV |
16 | * @IWL_DBG_TLV_TYPE_HCMD: host command TLV |
17 | * @IWL_DBG_TLV_TYPE_REGION: region TLV |
18 | * @IWL_DBG_TLV_TYPE_TRIGGER: trigger TLV |
19 | * @IWL_DBG_TLV_TYPE_CONF_SET: conf set TLV |
20 | * @IWL_DBG_TLV_TYPE_NUM: number of debug TLVs |
21 | */ |
22 | enum iwl_dbg_tlv_type { |
23 | IWL_DBG_TLV_TYPE_DEBUG_INFO = |
24 | IWL_UCODE_TLV_TYPE_DEBUG_INFO - IWL_UCODE_TLV_DEBUG_BASE, |
25 | IWL_DBG_TLV_TYPE_BUF_ALLOC, |
26 | IWL_DBG_TLV_TYPE_HCMD, |
27 | IWL_DBG_TLV_TYPE_REGION, |
28 | IWL_DBG_TLV_TYPE_TRIGGER, |
29 | IWL_DBG_TLV_TYPE_CONF_SET, |
30 | IWL_DBG_TLV_TYPE_NUM, |
31 | }; |
32 | |
33 | /** |
34 | * struct iwl_dbg_tlv_ver_data - debug TLV version struct |
35 | * @min_ver: min version supported |
36 | * @max_ver: max version supported |
37 | */ |
38 | struct iwl_dbg_tlv_ver_data { |
39 | int min_ver; |
40 | int max_ver; |
41 | }; |
42 | |
43 | /** |
44 | * struct iwl_dbg_tlv_timer_node - timer node struct |
45 | * @list: list of &struct iwl_dbg_tlv_timer_node |
46 | * @timer: timer |
47 | * @fwrt: &struct iwl_fw_runtime |
48 | * @tlv: TLV attach to the timer node |
49 | */ |
50 | struct iwl_dbg_tlv_timer_node { |
51 | struct list_head list; |
52 | struct timer_list timer; |
53 | struct iwl_fw_runtime *fwrt; |
54 | struct iwl_ucode_tlv *tlv; |
55 | }; |
56 | |
57 | static const struct iwl_dbg_tlv_ver_data |
58 | dbg_ver_table[IWL_DBG_TLV_TYPE_NUM] = { |
59 | [IWL_DBG_TLV_TYPE_DEBUG_INFO] = {.min_ver = 1, .max_ver = 1,}, |
60 | [IWL_DBG_TLV_TYPE_BUF_ALLOC] = {.min_ver = 1, .max_ver = 1,}, |
61 | [IWL_DBG_TLV_TYPE_HCMD] = {.min_ver = 1, .max_ver = 1,}, |
62 | [IWL_DBG_TLV_TYPE_REGION] = {.min_ver = 1, .max_ver = 3,}, |
63 | [IWL_DBG_TLV_TYPE_TRIGGER] = {.min_ver = 1, .max_ver = 1,}, |
64 | [IWL_DBG_TLV_TYPE_CONF_SET] = {.min_ver = 1, .max_ver = 1,}, |
65 | }; |
66 | |
67 | /* add a new TLV node, returning it so it can be modified */ |
68 | static struct iwl_ucode_tlv *iwl_dbg_tlv_add(const struct iwl_ucode_tlv *tlv, |
69 | struct list_head *list) |
70 | { |
71 | u32 len = le32_to_cpu(tlv->length); |
72 | struct iwl_dbg_tlv_node *node; |
73 | |
74 | node = kzalloc(struct_size(node, tlv.data, len), GFP_KERNEL); |
75 | if (!node) |
76 | return NULL; |
77 | |
78 | memcpy(&node->tlv, tlv, sizeof(node->tlv)); |
79 | memcpy(node->tlv.data, tlv->data, len); |
80 | list_add_tail(new: &node->list, head: list); |
81 | |
82 | return &node->tlv; |
83 | } |
84 | |
85 | static bool iwl_dbg_tlv_ver_support(const struct iwl_ucode_tlv *tlv) |
86 | { |
87 | const struct iwl_fw_ini_header *hdr = (const void *)&tlv->data[0]; |
88 | u32 type = le32_to_cpu(tlv->type); |
89 | u32 tlv_idx = type - IWL_UCODE_TLV_DEBUG_BASE; |
90 | u32 ver = le32_to_cpu(hdr->version); |
91 | |
92 | if (ver < dbg_ver_table[tlv_idx].min_ver || |
93 | ver > dbg_ver_table[tlv_idx].max_ver) |
94 | return false; |
95 | |
96 | return true; |
97 | } |
98 | |
99 | static int iwl_dbg_tlv_alloc_debug_info(struct iwl_trans *trans, |
100 | const struct iwl_ucode_tlv *tlv) |
101 | { |
102 | const struct iwl_fw_ini_debug_info_tlv *debug_info = (const void *)tlv->data; |
103 | |
104 | if (le32_to_cpu(tlv->length) != sizeof(*debug_info)) |
105 | return -EINVAL; |
106 | |
107 | /* we use this as a string, ensure input was NUL terminated */ |
108 | if (strnlen(p: debug_info->debug_cfg_name, |
109 | maxlen: sizeof(debug_info->debug_cfg_name)) == |
110 | sizeof(debug_info->debug_cfg_name)) |
111 | return -EINVAL; |
112 | |
113 | IWL_DEBUG_FW(trans, "WRT: Loading debug cfg: %s\n" , |
114 | debug_info->debug_cfg_name); |
115 | |
116 | if (!iwl_dbg_tlv_add(tlv, list: &trans->dbg.debug_info_tlv_list)) |
117 | return -ENOMEM; |
118 | return 0; |
119 | } |
120 | |
121 | static int iwl_dbg_tlv_alloc_buf_alloc(struct iwl_trans *trans, |
122 | const struct iwl_ucode_tlv *tlv) |
123 | { |
124 | const struct iwl_fw_ini_allocation_tlv *alloc = (const void *)tlv->data; |
125 | u32 buf_location; |
126 | u32 alloc_id; |
127 | |
128 | if (le32_to_cpu(tlv->length) != sizeof(*alloc)) |
129 | return -EINVAL; |
130 | |
131 | buf_location = le32_to_cpu(alloc->buf_location); |
132 | alloc_id = le32_to_cpu(alloc->alloc_id); |
133 | |
134 | if (buf_location == IWL_FW_INI_LOCATION_INVALID || |
135 | buf_location >= IWL_FW_INI_LOCATION_NUM) |
136 | goto err; |
137 | |
138 | if (alloc_id == IWL_FW_INI_ALLOCATION_INVALID || |
139 | alloc_id >= IWL_FW_INI_ALLOCATION_NUM) |
140 | goto err; |
141 | |
142 | if (buf_location == IWL_FW_INI_LOCATION_NPK_PATH && |
143 | alloc_id != IWL_FW_INI_ALLOCATION_ID_DBGC1) |
144 | goto err; |
145 | |
146 | if (buf_location == IWL_FW_INI_LOCATION_SRAM_PATH && |
147 | alloc_id != IWL_FW_INI_ALLOCATION_ID_DBGC1) |
148 | goto err; |
149 | |
150 | if (buf_location == IWL_FW_INI_LOCATION_DRAM_PATH && |
151 | alloc->req_size == 0) { |
152 | IWL_ERR(trans, "WRT: Invalid DRAM buffer allocation requested size (0)\n" ); |
153 | return -EINVAL; |
154 | } |
155 | |
156 | trans->dbg.fw_mon_cfg[alloc_id] = *alloc; |
157 | |
158 | return 0; |
159 | err: |
160 | IWL_ERR(trans, |
161 | "WRT: Invalid allocation id %u and/or location id %u for allocation TLV\n" , |
162 | alloc_id, buf_location); |
163 | return -EINVAL; |
164 | } |
165 | |
166 | static int iwl_dbg_tlv_alloc_hcmd(struct iwl_trans *trans, |
167 | const struct iwl_ucode_tlv *tlv) |
168 | { |
169 | const struct iwl_fw_ini_hcmd_tlv *hcmd = (const void *)tlv->data; |
170 | u32 tp = le32_to_cpu(hcmd->time_point); |
171 | |
172 | if (le32_to_cpu(tlv->length) <= sizeof(*hcmd)) |
173 | return -EINVAL; |
174 | |
175 | /* Host commands can not be sent in early time point since the FW |
176 | * is not ready |
177 | */ |
178 | if (tp == IWL_FW_INI_TIME_POINT_INVALID || |
179 | tp >= IWL_FW_INI_TIME_POINT_NUM || |
180 | tp == IWL_FW_INI_TIME_POINT_EARLY) { |
181 | IWL_ERR(trans, |
182 | "WRT: Invalid time point %u for host command TLV\n" , |
183 | tp); |
184 | return -EINVAL; |
185 | } |
186 | |
187 | if (!iwl_dbg_tlv_add(tlv, list: &trans->dbg.time_point[tp].hcmd_list)) |
188 | return -ENOMEM; |
189 | return 0; |
190 | } |
191 | |
192 | static int iwl_dbg_tlv_alloc_region(struct iwl_trans *trans, |
193 | const struct iwl_ucode_tlv *tlv) |
194 | { |
195 | const struct iwl_fw_ini_region_tlv *reg = (const void *)tlv->data; |
196 | struct iwl_ucode_tlv **active_reg; |
197 | u32 id = le32_to_cpu(reg->id); |
198 | u8 type = reg->type; |
199 | u32 tlv_len = sizeof(*tlv) + le32_to_cpu(tlv->length); |
200 | |
201 | /* |
202 | * The higher part of the ID from version 2 is debug policy. |
203 | * The id will be only lsb 16 bits, so mask it out. |
204 | */ |
205 | if (le32_to_cpu(reg->hdr.version) >= 2) |
206 | id &= IWL_FW_INI_REGION_ID_MASK; |
207 | |
208 | if (le32_to_cpu(tlv->length) < sizeof(*reg)) |
209 | return -EINVAL; |
210 | |
211 | /* for safe use of a string from FW, limit it to IWL_FW_INI_MAX_NAME */ |
212 | IWL_DEBUG_FW(trans, "WRT: parsing region: %.*s\n" , |
213 | IWL_FW_INI_MAX_NAME, reg->name); |
214 | |
215 | if (id >= IWL_FW_INI_MAX_REGION_ID) { |
216 | IWL_ERR(trans, "WRT: Invalid region id %u\n" , id); |
217 | return -EINVAL; |
218 | } |
219 | |
220 | if (type <= IWL_FW_INI_REGION_INVALID || |
221 | type >= IWL_FW_INI_REGION_NUM) { |
222 | IWL_ERR(trans, "WRT: Invalid region type %u\n" , type); |
223 | return -EINVAL; |
224 | } |
225 | |
226 | if (type == IWL_FW_INI_REGION_PCI_IOSF_CONFIG && |
227 | !trans->ops->read_config32) { |
228 | IWL_ERR(trans, "WRT: Unsupported region type %u\n" , type); |
229 | return -EOPNOTSUPP; |
230 | } |
231 | |
232 | if (type == IWL_FW_INI_REGION_INTERNAL_BUFFER) { |
233 | trans->dbg.imr_data.sram_addr = |
234 | le32_to_cpu(reg->internal_buffer.base_addr); |
235 | trans->dbg.imr_data.sram_size = |
236 | le32_to_cpu(reg->internal_buffer.size); |
237 | } |
238 | |
239 | |
240 | active_reg = &trans->dbg.active_regions[id]; |
241 | if (*active_reg) { |
242 | IWL_WARN(trans, "WRT: Overriding region id %u\n" , id); |
243 | |
244 | kfree(objp: *active_reg); |
245 | } |
246 | |
247 | *active_reg = kmemdup(p: tlv, size: tlv_len, GFP_KERNEL); |
248 | if (!*active_reg) |
249 | return -ENOMEM; |
250 | |
251 | IWL_DEBUG_FW(trans, "WRT: Enabling region id %u type %u\n" , id, type); |
252 | |
253 | return 0; |
254 | } |
255 | |
256 | static int iwl_dbg_tlv_alloc_trigger(struct iwl_trans *trans, |
257 | const struct iwl_ucode_tlv *tlv) |
258 | { |
259 | const struct iwl_fw_ini_trigger_tlv *trig = (const void *)tlv->data; |
260 | u32 tp = le32_to_cpu(trig->time_point); |
261 | u32 rf = le32_to_cpu(trig->reset_fw); |
262 | struct iwl_ucode_tlv *new_tlv; |
263 | |
264 | if (le32_to_cpu(tlv->length) < sizeof(*trig)) |
265 | return -EINVAL; |
266 | |
267 | if (tp <= IWL_FW_INI_TIME_POINT_INVALID || |
268 | tp >= IWL_FW_INI_TIME_POINT_NUM) { |
269 | IWL_ERR(trans, |
270 | "WRT: Invalid time point %u for trigger TLV\n" , |
271 | tp); |
272 | return -EINVAL; |
273 | } |
274 | |
275 | IWL_DEBUG_FW(trans, |
276 | "WRT: time point %u for trigger TLV with reset_fw %u\n" , |
277 | tp, rf); |
278 | trans->dbg.last_tp_resetfw = 0xFF; |
279 | |
280 | new_tlv = iwl_dbg_tlv_add(tlv, list: &trans->dbg.time_point[tp].trig_list); |
281 | if (!new_tlv) |
282 | return -ENOMEM; |
283 | |
284 | if (!le32_to_cpu(trig->occurrences)) { |
285 | struct iwl_fw_ini_trigger_tlv *new_trig = (void *)new_tlv->data; |
286 | |
287 | new_trig->occurrences = cpu_to_le32(-1); |
288 | } |
289 | |
290 | return 0; |
291 | } |
292 | |
293 | static int iwl_dbg_tlv_config_set(struct iwl_trans *trans, |
294 | const struct iwl_ucode_tlv *tlv) |
295 | { |
296 | const struct iwl_fw_ini_conf_set_tlv *conf_set = (const void *)tlv->data; |
297 | u32 tp = le32_to_cpu(conf_set->time_point); |
298 | u32 type = le32_to_cpu(conf_set->set_type); |
299 | |
300 | if (tp <= IWL_FW_INI_TIME_POINT_INVALID || |
301 | tp >= IWL_FW_INI_TIME_POINT_NUM) { |
302 | IWL_DEBUG_FW(trans, |
303 | "WRT: Invalid time point %u for config set TLV\n" , tp); |
304 | return -EINVAL; |
305 | } |
306 | |
307 | if (type <= IWL_FW_INI_CONFIG_SET_TYPE_INVALID || |
308 | type >= IWL_FW_INI_CONFIG_SET_TYPE_MAX_NUM) { |
309 | IWL_DEBUG_FW(trans, |
310 | "WRT: Invalid config set type %u for config set TLV\n" , type); |
311 | return -EINVAL; |
312 | } |
313 | |
314 | if (!iwl_dbg_tlv_add(tlv, list: &trans->dbg.time_point[tp].config_list)) |
315 | return -ENOMEM; |
316 | return 0; |
317 | } |
318 | |
319 | static int (*dbg_tlv_alloc[])(struct iwl_trans *trans, |
320 | const struct iwl_ucode_tlv *tlv) = { |
321 | [IWL_DBG_TLV_TYPE_DEBUG_INFO] = iwl_dbg_tlv_alloc_debug_info, |
322 | [IWL_DBG_TLV_TYPE_BUF_ALLOC] = iwl_dbg_tlv_alloc_buf_alloc, |
323 | [IWL_DBG_TLV_TYPE_HCMD] = iwl_dbg_tlv_alloc_hcmd, |
324 | [IWL_DBG_TLV_TYPE_REGION] = iwl_dbg_tlv_alloc_region, |
325 | [IWL_DBG_TLV_TYPE_TRIGGER] = iwl_dbg_tlv_alloc_trigger, |
326 | [IWL_DBG_TLV_TYPE_CONF_SET] = iwl_dbg_tlv_config_set, |
327 | }; |
328 | |
329 | void iwl_dbg_tlv_alloc(struct iwl_trans *trans, const struct iwl_ucode_tlv *tlv, |
330 | bool ext) |
331 | { |
332 | enum iwl_ini_cfg_state *cfg_state = ext ? |
333 | &trans->dbg.external_ini_cfg : &trans->dbg.internal_ini_cfg; |
334 | const struct iwl_fw_ini_header *hdr = (const void *)&tlv->data[0]; |
335 | u32 type; |
336 | u32 tlv_idx; |
337 | u32 domain; |
338 | int ret; |
339 | |
340 | if (le32_to_cpu(tlv->length) < sizeof(*hdr)) |
341 | return; |
342 | |
343 | type = le32_to_cpu(tlv->type); |
344 | tlv_idx = type - IWL_UCODE_TLV_DEBUG_BASE; |
345 | domain = le32_to_cpu(hdr->domain); |
346 | |
347 | if (domain != IWL_FW_INI_DOMAIN_ALWAYS_ON && |
348 | !(domain & trans->dbg.domains_bitmap)) { |
349 | IWL_DEBUG_FW(trans, |
350 | "WRT: Skipping TLV with disabled domain 0x%0x (0x%0x)\n" , |
351 | domain, trans->dbg.domains_bitmap); |
352 | return; |
353 | } |
354 | |
355 | if (tlv_idx >= ARRAY_SIZE(dbg_tlv_alloc) || !dbg_tlv_alloc[tlv_idx]) { |
356 | IWL_ERR(trans, "WRT: Unsupported TLV type 0x%x\n" , type); |
357 | goto out_err; |
358 | } |
359 | |
360 | if (!iwl_dbg_tlv_ver_support(tlv)) { |
361 | IWL_ERR(trans, "WRT: Unsupported TLV 0x%x version %u\n" , type, |
362 | le32_to_cpu(hdr->version)); |
363 | goto out_err; |
364 | } |
365 | |
366 | ret = dbg_tlv_alloc[tlv_idx](trans, tlv); |
367 | if (ret) { |
368 | IWL_WARN(trans, |
369 | "WRT: Failed to allocate TLV 0x%x, ret %d, (ext=%d)\n" , |
370 | type, ret, ext); |
371 | goto out_err; |
372 | } |
373 | |
374 | if (*cfg_state == IWL_INI_CFG_STATE_NOT_LOADED) |
375 | *cfg_state = IWL_INI_CFG_STATE_LOADED; |
376 | |
377 | return; |
378 | |
379 | out_err: |
380 | *cfg_state = IWL_INI_CFG_STATE_CORRUPTED; |
381 | } |
382 | |
383 | void iwl_dbg_tlv_del_timers(struct iwl_trans *trans) |
384 | { |
385 | struct list_head *timer_list = &trans->dbg.periodic_trig_list; |
386 | struct iwl_dbg_tlv_timer_node *node, *tmp; |
387 | |
388 | list_for_each_entry_safe(node, tmp, timer_list, list) { |
389 | timer_shutdown_sync(timer: &node->timer); |
390 | list_del(entry: &node->list); |
391 | kfree(objp: node); |
392 | } |
393 | } |
394 | IWL_EXPORT_SYMBOL(iwl_dbg_tlv_del_timers); |
395 | |
396 | static void iwl_dbg_tlv_fragments_free(struct iwl_trans *trans, |
397 | enum iwl_fw_ini_allocation_id alloc_id) |
398 | { |
399 | struct iwl_fw_mon *fw_mon; |
400 | int i; |
401 | |
402 | if (alloc_id <= IWL_FW_INI_ALLOCATION_INVALID || |
403 | alloc_id >= IWL_FW_INI_ALLOCATION_NUM) |
404 | return; |
405 | |
406 | fw_mon = &trans->dbg.fw_mon_ini[alloc_id]; |
407 | |
408 | for (i = 0; i < fw_mon->num_frags; i++) { |
409 | struct iwl_dram_data *frag = &fw_mon->frags[i]; |
410 | |
411 | dma_free_coherent(dev: trans->dev, size: frag->size, cpu_addr: frag->block, |
412 | dma_handle: frag->physical); |
413 | |
414 | frag->physical = 0; |
415 | frag->block = NULL; |
416 | frag->size = 0; |
417 | } |
418 | |
419 | kfree(objp: fw_mon->frags); |
420 | fw_mon->frags = NULL; |
421 | fw_mon->num_frags = 0; |
422 | } |
423 | |
424 | void iwl_dbg_tlv_free(struct iwl_trans *trans) |
425 | { |
426 | struct iwl_dbg_tlv_node *tlv_node, *tlv_node_tmp; |
427 | int i; |
428 | |
429 | iwl_dbg_tlv_del_timers(trans); |
430 | |
431 | for (i = 0; i < ARRAY_SIZE(trans->dbg.active_regions); i++) { |
432 | struct iwl_ucode_tlv **active_reg = |
433 | &trans->dbg.active_regions[i]; |
434 | |
435 | kfree(objp: *active_reg); |
436 | *active_reg = NULL; |
437 | } |
438 | |
439 | list_for_each_entry_safe(tlv_node, tlv_node_tmp, |
440 | &trans->dbg.debug_info_tlv_list, list) { |
441 | list_del(entry: &tlv_node->list); |
442 | kfree(objp: tlv_node); |
443 | } |
444 | |
445 | for (i = 0; i < ARRAY_SIZE(trans->dbg.time_point); i++) { |
446 | struct iwl_dbg_tlv_time_point_data *tp = |
447 | &trans->dbg.time_point[i]; |
448 | |
449 | list_for_each_entry_safe(tlv_node, tlv_node_tmp, &tp->trig_list, |
450 | list) { |
451 | list_del(entry: &tlv_node->list); |
452 | kfree(objp: tlv_node); |
453 | } |
454 | |
455 | list_for_each_entry_safe(tlv_node, tlv_node_tmp, &tp->hcmd_list, |
456 | list) { |
457 | list_del(entry: &tlv_node->list); |
458 | kfree(objp: tlv_node); |
459 | } |
460 | |
461 | list_for_each_entry_safe(tlv_node, tlv_node_tmp, |
462 | &tp->active_trig_list, list) { |
463 | list_del(entry: &tlv_node->list); |
464 | kfree(objp: tlv_node); |
465 | } |
466 | |
467 | list_for_each_entry_safe(tlv_node, tlv_node_tmp, |
468 | &tp->config_list, list) { |
469 | list_del(entry: &tlv_node->list); |
470 | kfree(objp: tlv_node); |
471 | } |
472 | |
473 | } |
474 | |
475 | for (i = 0; i < ARRAY_SIZE(trans->dbg.fw_mon_ini); i++) |
476 | iwl_dbg_tlv_fragments_free(trans, alloc_id: i); |
477 | } |
478 | |
479 | static int iwl_dbg_tlv_parse_bin(struct iwl_trans *trans, const u8 *data, |
480 | size_t len) |
481 | { |
482 | const struct iwl_ucode_tlv *tlv; |
483 | u32 tlv_len; |
484 | |
485 | while (len >= sizeof(*tlv)) { |
486 | len -= sizeof(*tlv); |
487 | tlv = (const void *)data; |
488 | |
489 | tlv_len = le32_to_cpu(tlv->length); |
490 | |
491 | if (len < tlv_len) { |
492 | IWL_ERR(trans, "invalid TLV len: %zd/%u\n" , |
493 | len, tlv_len); |
494 | return -EINVAL; |
495 | } |
496 | len -= ALIGN(tlv_len, 4); |
497 | data += sizeof(*tlv) + ALIGN(tlv_len, 4); |
498 | |
499 | iwl_dbg_tlv_alloc(trans, tlv, ext: true); |
500 | } |
501 | |
502 | return 0; |
503 | } |
504 | |
505 | void iwl_dbg_tlv_load_bin(struct device *dev, struct iwl_trans *trans) |
506 | { |
507 | const struct firmware *fw; |
508 | const char *yoyo_bin = "iwl-debug-yoyo.bin" ; |
509 | int res; |
510 | |
511 | if (!iwlwifi_mod_params.enable_ini || |
512 | trans->trans_cfg->device_family <= IWL_DEVICE_FAMILY_8000) |
513 | return; |
514 | |
515 | res = firmware_request_nowarn(fw: &fw, name: yoyo_bin, device: dev); |
516 | IWL_DEBUG_FW(trans, "%s %s\n" , res ? "didn't load" : "loaded" , yoyo_bin); |
517 | |
518 | if (res) |
519 | return; |
520 | |
521 | trans->dbg.yoyo_bin_loaded = true; |
522 | |
523 | iwl_dbg_tlv_parse_bin(trans, data: fw->data, len: fw->size); |
524 | |
525 | release_firmware(fw); |
526 | } |
527 | |
528 | void iwl_dbg_tlv_init(struct iwl_trans *trans) |
529 | { |
530 | int i; |
531 | |
532 | INIT_LIST_HEAD(list: &trans->dbg.debug_info_tlv_list); |
533 | INIT_LIST_HEAD(list: &trans->dbg.periodic_trig_list); |
534 | |
535 | for (i = 0; i < ARRAY_SIZE(trans->dbg.time_point); i++) { |
536 | struct iwl_dbg_tlv_time_point_data *tp = |
537 | &trans->dbg.time_point[i]; |
538 | |
539 | INIT_LIST_HEAD(list: &tp->trig_list); |
540 | INIT_LIST_HEAD(list: &tp->hcmd_list); |
541 | INIT_LIST_HEAD(list: &tp->active_trig_list); |
542 | INIT_LIST_HEAD(list: &tp->config_list); |
543 | } |
544 | } |
545 | |
546 | static int iwl_dbg_tlv_alloc_fragment(struct iwl_fw_runtime *fwrt, |
547 | struct iwl_dram_data *frag, u32 pages) |
548 | { |
549 | void *block = NULL; |
550 | dma_addr_t physical; |
551 | |
552 | if (!frag || frag->size || !pages) |
553 | return -EIO; |
554 | |
555 | /* |
556 | * We try to allocate as many pages as we can, starting with |
557 | * the requested amount and going down until we can allocate |
558 | * something. Because of DIV_ROUND_UP(), pages will never go |
559 | * down to 0 and stop the loop, so stop when pages reaches 1, |
560 | * which is too small anyway. |
561 | */ |
562 | while (pages > 1) { |
563 | block = dma_alloc_coherent(dev: fwrt->dev, size: pages * PAGE_SIZE, |
564 | dma_handle: &physical, |
565 | GFP_KERNEL | __GFP_NOWARN); |
566 | if (block) |
567 | break; |
568 | |
569 | IWL_WARN(fwrt, "WRT: Failed to allocate fragment size %lu\n" , |
570 | pages * PAGE_SIZE); |
571 | |
572 | pages = DIV_ROUND_UP(pages, 2); |
573 | } |
574 | |
575 | if (!block) |
576 | return -ENOMEM; |
577 | |
578 | frag->physical = physical; |
579 | frag->block = block; |
580 | frag->size = pages * PAGE_SIZE; |
581 | |
582 | return pages; |
583 | } |
584 | |
585 | static int iwl_dbg_tlv_alloc_fragments(struct iwl_fw_runtime *fwrt, |
586 | enum iwl_fw_ini_allocation_id alloc_id) |
587 | { |
588 | struct iwl_fw_mon *fw_mon; |
589 | struct iwl_fw_ini_allocation_tlv *fw_mon_cfg; |
590 | u32 num_frags, remain_pages, frag_pages; |
591 | int i; |
592 | |
593 | if (alloc_id < IWL_FW_INI_ALLOCATION_INVALID || |
594 | alloc_id >= IWL_FW_INI_ALLOCATION_NUM) |
595 | return -EIO; |
596 | |
597 | fw_mon_cfg = &fwrt->trans->dbg.fw_mon_cfg[alloc_id]; |
598 | fw_mon = &fwrt->trans->dbg.fw_mon_ini[alloc_id]; |
599 | |
600 | if (fw_mon->num_frags) { |
601 | for (i = 0; i < fw_mon->num_frags; i++) |
602 | memset(fw_mon->frags[i].block, 0, |
603 | fw_mon->frags[i].size); |
604 | return 0; |
605 | } |
606 | |
607 | if (fw_mon_cfg->buf_location != |
608 | cpu_to_le32(IWL_FW_INI_LOCATION_DRAM_PATH)) |
609 | return 0; |
610 | |
611 | num_frags = le32_to_cpu(fw_mon_cfg->max_frags_num); |
612 | if (fwrt->trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210) { |
613 | if (alloc_id != IWL_FW_INI_ALLOCATION_ID_DBGC1) |
614 | return -EIO; |
615 | num_frags = 1; |
616 | } else if (fwrt->trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_BZ && |
617 | alloc_id > IWL_FW_INI_ALLOCATION_ID_DBGC3) { |
618 | return -EIO; |
619 | } |
620 | |
621 | remain_pages = DIV_ROUND_UP(le32_to_cpu(fw_mon_cfg->req_size), |
622 | PAGE_SIZE); |
623 | num_frags = min_t(u32, num_frags, BUF_ALLOC_MAX_NUM_FRAGS); |
624 | num_frags = min_t(u32, num_frags, remain_pages); |
625 | frag_pages = DIV_ROUND_UP(remain_pages, num_frags); |
626 | |
627 | fw_mon->frags = kcalloc(n: num_frags, size: sizeof(*fw_mon->frags), GFP_KERNEL); |
628 | if (!fw_mon->frags) |
629 | return -ENOMEM; |
630 | |
631 | for (i = 0; i < num_frags; i++) { |
632 | int pages = min_t(u32, frag_pages, remain_pages); |
633 | |
634 | IWL_DEBUG_FW(fwrt, |
635 | "WRT: Allocating DRAM buffer (alloc_id=%u, fragment=%u, size=0x%lx)\n" , |
636 | alloc_id, i, pages * PAGE_SIZE); |
637 | |
638 | pages = iwl_dbg_tlv_alloc_fragment(fwrt, frag: &fw_mon->frags[i], |
639 | pages); |
640 | if (pages < 0) { |
641 | u32 alloc_size = le32_to_cpu(fw_mon_cfg->req_size) - |
642 | (remain_pages * PAGE_SIZE); |
643 | |
644 | if (alloc_size < le32_to_cpu(fw_mon_cfg->min_size)) { |
645 | iwl_dbg_tlv_fragments_free(trans: fwrt->trans, |
646 | alloc_id); |
647 | return pages; |
648 | } |
649 | break; |
650 | } |
651 | |
652 | remain_pages -= pages; |
653 | fw_mon->num_frags++; |
654 | } |
655 | |
656 | return 0; |
657 | } |
658 | |
659 | static int iwl_dbg_tlv_apply_buffer(struct iwl_fw_runtime *fwrt, |
660 | enum iwl_fw_ini_allocation_id alloc_id) |
661 | { |
662 | struct iwl_fw_mon *fw_mon; |
663 | u32 remain_frags, num_commands; |
664 | int i, fw_mon_idx = 0; |
665 | |
666 | if (!fw_has_capa(capabilities: &fwrt->fw->ucode_capa, |
667 | capa: IWL_UCODE_TLV_CAPA_DBG_BUF_ALLOC_CMD_SUPP)) |
668 | return 0; |
669 | |
670 | if (alloc_id < IWL_FW_INI_ALLOCATION_INVALID || |
671 | alloc_id >= IWL_FW_INI_ALLOCATION_NUM) |
672 | return -EIO; |
673 | |
674 | if (le32_to_cpu(fwrt->trans->dbg.fw_mon_cfg[alloc_id].buf_location) != |
675 | IWL_FW_INI_LOCATION_DRAM_PATH) |
676 | return 0; |
677 | |
678 | fw_mon = &fwrt->trans->dbg.fw_mon_ini[alloc_id]; |
679 | |
680 | /* the first fragment of DBGC1 is given to the FW via register |
681 | * or context info |
682 | */ |
683 | if (alloc_id == IWL_FW_INI_ALLOCATION_ID_DBGC1) |
684 | fw_mon_idx++; |
685 | |
686 | remain_frags = fw_mon->num_frags - fw_mon_idx; |
687 | if (!remain_frags) |
688 | return 0; |
689 | |
690 | num_commands = DIV_ROUND_UP(remain_frags, BUF_ALLOC_MAX_NUM_FRAGS); |
691 | |
692 | IWL_DEBUG_FW(fwrt, "WRT: Applying DRAM destination (alloc_id=%u)\n" , |
693 | alloc_id); |
694 | |
695 | for (i = 0; i < num_commands; i++) { |
696 | u32 num_frags = min_t(u32, remain_frags, |
697 | BUF_ALLOC_MAX_NUM_FRAGS); |
698 | struct iwl_buf_alloc_cmd data = { |
699 | .alloc_id = cpu_to_le32(alloc_id), |
700 | .num_frags = cpu_to_le32(num_frags), |
701 | .buf_location = |
702 | cpu_to_le32(IWL_FW_INI_LOCATION_DRAM_PATH), |
703 | }; |
704 | struct iwl_host_cmd hcmd = { |
705 | .id = WIDE_ID(DEBUG_GROUP, BUFFER_ALLOCATION), |
706 | .data[0] = &data, |
707 | .len[0] = sizeof(data), |
708 | .flags = CMD_SEND_IN_RFKILL, |
709 | }; |
710 | int ret, j; |
711 | |
712 | for (j = 0; j < num_frags; j++) { |
713 | struct iwl_buf_alloc_frag *frag = &data.frags[j]; |
714 | struct iwl_dram_data *fw_mon_frag = |
715 | &fw_mon->frags[fw_mon_idx++]; |
716 | |
717 | frag->addr = cpu_to_le64(fw_mon_frag->physical); |
718 | frag->size = cpu_to_le32(fw_mon_frag->size); |
719 | } |
720 | ret = iwl_trans_send_cmd(trans: fwrt->trans, cmd: &hcmd); |
721 | if (ret) |
722 | return ret; |
723 | |
724 | remain_frags -= num_frags; |
725 | } |
726 | |
727 | return 0; |
728 | } |
729 | |
730 | static void iwl_dbg_tlv_apply_buffers(struct iwl_fw_runtime *fwrt) |
731 | { |
732 | int ret, i; |
733 | |
734 | if (fw_has_capa(capabilities: &fwrt->fw->ucode_capa, |
735 | capa: IWL_UCODE_TLV_CAPA_DRAM_FRAG_SUPPORT)) |
736 | return; |
737 | |
738 | for (i = 0; i < IWL_FW_INI_ALLOCATION_NUM; i++) { |
739 | ret = iwl_dbg_tlv_apply_buffer(fwrt, alloc_id: i); |
740 | if (ret) |
741 | IWL_WARN(fwrt, |
742 | "WRT: Failed to apply DRAM buffer for allocation id %d, ret=%d\n" , |
743 | i, ret); |
744 | } |
745 | } |
746 | |
747 | static int iwl_dbg_tlv_update_dram(struct iwl_fw_runtime *fwrt, |
748 | enum iwl_fw_ini_allocation_id alloc_id, |
749 | struct iwl_dram_info *dram_info) |
750 | { |
751 | struct iwl_fw_mon *fw_mon; |
752 | u32 remain_frags, num_frags; |
753 | int j, fw_mon_idx = 0; |
754 | struct iwl_buf_alloc_cmd *data; |
755 | |
756 | if (le32_to_cpu(fwrt->trans->dbg.fw_mon_cfg[alloc_id].buf_location) != |
757 | IWL_FW_INI_LOCATION_DRAM_PATH) { |
758 | IWL_DEBUG_FW(fwrt, "WRT: alloc_id %u location is not in DRAM_PATH\n" , |
759 | alloc_id); |
760 | return -1; |
761 | } |
762 | |
763 | fw_mon = &fwrt->trans->dbg.fw_mon_ini[alloc_id]; |
764 | |
765 | /* the first fragment of DBGC1 is given to the FW via register |
766 | * or context info |
767 | */ |
768 | if (alloc_id == IWL_FW_INI_ALLOCATION_ID_DBGC1) |
769 | fw_mon_idx++; |
770 | |
771 | remain_frags = fw_mon->num_frags - fw_mon_idx; |
772 | if (!remain_frags) |
773 | return -1; |
774 | |
775 | num_frags = min_t(u32, remain_frags, BUF_ALLOC_MAX_NUM_FRAGS); |
776 | data = &dram_info->dram_frags[alloc_id - 1]; |
777 | data->alloc_id = cpu_to_le32(alloc_id); |
778 | data->num_frags = cpu_to_le32(num_frags); |
779 | data->buf_location = cpu_to_le32(IWL_FW_INI_LOCATION_DRAM_PATH); |
780 | |
781 | IWL_DEBUG_FW(fwrt, "WRT: DRAM buffer details alloc_id=%u, num_frags=%u\n" , |
782 | cpu_to_le32(alloc_id), cpu_to_le32(num_frags)); |
783 | |
784 | for (j = 0; j < num_frags; j++) { |
785 | struct iwl_buf_alloc_frag *frag = &data->frags[j]; |
786 | struct iwl_dram_data *fw_mon_frag = &fw_mon->frags[fw_mon_idx++]; |
787 | |
788 | frag->addr = cpu_to_le64(fw_mon_frag->physical); |
789 | frag->size = cpu_to_le32(fw_mon_frag->size); |
790 | IWL_DEBUG_FW(fwrt, "WRT: DRAM fragment details\n" ); |
791 | IWL_DEBUG_FW(fwrt, "frag=%u, addr=0x%016llx, size=0x%x)\n" , |
792 | j, cpu_to_le64(fw_mon_frag->physical), |
793 | cpu_to_le32(fw_mon_frag->size)); |
794 | } |
795 | return 0; |
796 | } |
797 | |
798 | static void iwl_dbg_tlv_update_drams(struct iwl_fw_runtime *fwrt) |
799 | { |
800 | int ret, i; |
801 | bool dram_alloc = false; |
802 | struct iwl_dram_data *frags = |
803 | &fwrt->trans->dbg.fw_mon_ini[IWL_FW_INI_ALLOCATION_ID_DBGC1].frags[0]; |
804 | struct iwl_dram_info *dram_info; |
805 | |
806 | if (!frags || !frags->block) |
807 | return; |
808 | |
809 | dram_info = frags->block; |
810 | |
811 | if (!fw_has_capa(capabilities: &fwrt->fw->ucode_capa, |
812 | capa: IWL_UCODE_TLV_CAPA_DRAM_FRAG_SUPPORT)) |
813 | return; |
814 | |
815 | memset(dram_info, 0, sizeof(*dram_info)); |
816 | |
817 | for (i = IWL_FW_INI_ALLOCATION_ID_DBGC1; |
818 | i < IWL_FW_INI_ALLOCATION_NUM; i++) { |
819 | if (fwrt->trans->dbg.fw_mon_cfg[i].buf_location == |
820 | IWL_FW_INI_LOCATION_INVALID) |
821 | continue; |
822 | |
823 | ret = iwl_dbg_tlv_update_dram(fwrt, alloc_id: i, dram_info); |
824 | if (!ret) |
825 | dram_alloc = true; |
826 | else |
827 | IWL_INFO(fwrt, |
828 | "WRT: Failed to set DRAM buffer for alloc id %d, ret=%d\n" , |
829 | i, ret); |
830 | } |
831 | |
832 | if (dram_alloc) { |
833 | dram_info->first_word = cpu_to_le32(DRAM_INFO_FIRST_MAGIC_WORD); |
834 | dram_info->second_word = cpu_to_le32(DRAM_INFO_SECOND_MAGIC_WORD); |
835 | } |
836 | } |
837 | |
838 | static void iwl_dbg_tlv_send_hcmds(struct iwl_fw_runtime *fwrt, |
839 | struct list_head *hcmd_list) |
840 | { |
841 | struct iwl_dbg_tlv_node *node; |
842 | |
843 | list_for_each_entry(node, hcmd_list, list) { |
844 | struct iwl_fw_ini_hcmd_tlv *hcmd = (void *)node->tlv.data; |
845 | struct iwl_fw_ini_hcmd *hcmd_data = &hcmd->hcmd; |
846 | u16 hcmd_len = le32_to_cpu(node->tlv.length) - sizeof(*hcmd); |
847 | struct iwl_host_cmd cmd = { |
848 | .id = WIDE_ID(hcmd_data->group, hcmd_data->id), |
849 | .len = { hcmd_len, }, |
850 | .data = { hcmd_data->data, }, |
851 | }; |
852 | |
853 | iwl_trans_send_cmd(trans: fwrt->trans, cmd: &cmd); |
854 | } |
855 | } |
856 | |
857 | static void iwl_dbg_tlv_apply_config(struct iwl_fw_runtime *fwrt, |
858 | struct list_head *conf_list) |
859 | { |
860 | struct iwl_dbg_tlv_node *node; |
861 | |
862 | list_for_each_entry(node, conf_list, list) { |
863 | struct iwl_fw_ini_conf_set_tlv *config_list = (void *)node->tlv.data; |
864 | u32 count, address, value; |
865 | u32 len = (le32_to_cpu(node->tlv.length) - sizeof(*config_list)) / 8; |
866 | u32 type = le32_to_cpu(config_list->set_type); |
867 | u32 offset = le32_to_cpu(config_list->addr_offset); |
868 | |
869 | switch (type) { |
870 | case IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_PERIPHERY_MAC: { |
871 | if (!iwl_trans_grab_nic_access(fwrt->trans)) { |
872 | IWL_DEBUG_FW(fwrt, "WRT: failed to get nic access\n" ); |
873 | IWL_DEBUG_FW(fwrt, "WRT: skipping MAC PERIPHERY config\n" ); |
874 | continue; |
875 | } |
876 | IWL_DEBUG_FW(fwrt, "WRT: MAC PERIPHERY config len: len %u\n" , len); |
877 | for (count = 0; count < len; count++) { |
878 | address = le32_to_cpu(config_list->addr_val[count].address); |
879 | value = le32_to_cpu(config_list->addr_val[count].value); |
880 | iwl_trans_write_prph(trans: fwrt->trans, ofs: address + offset, val: value); |
881 | } |
882 | iwl_trans_release_nic_access(trans: fwrt->trans); |
883 | break; |
884 | } |
885 | case IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_MEMORY: { |
886 | for (count = 0; count < len; count++) { |
887 | address = le32_to_cpu(config_list->addr_val[count].address); |
888 | value = le32_to_cpu(config_list->addr_val[count].value); |
889 | iwl_trans_write_mem32(trans: fwrt->trans, addr: address + offset, val: value); |
890 | IWL_DEBUG_FW(fwrt, "WRT: DEV_MEM: count %u, add: %u val: %u\n" , |
891 | count, address, value); |
892 | } |
893 | break; |
894 | } |
895 | case IWL_FW_INI_CONFIG_SET_TYPE_CSR: { |
896 | for (count = 0; count < len; count++) { |
897 | address = le32_to_cpu(config_list->addr_val[count].address); |
898 | value = le32_to_cpu(config_list->addr_val[count].value); |
899 | iwl_write32(trans: fwrt->trans, ofs: address + offset, val: value); |
900 | IWL_DEBUG_FW(fwrt, "WRT: CSR: count %u, add: %u val: %u\n" , |
901 | count, address, value); |
902 | } |
903 | break; |
904 | } |
905 | case IWL_FW_INI_CONFIG_SET_TYPE_DBGC_DRAM_ADDR: { |
906 | struct iwl_dbgc1_info dram_info = {}; |
907 | struct iwl_dram_data *frags = &fwrt->trans->dbg.fw_mon_ini[1].frags[0]; |
908 | __le64 dram_base_addr; |
909 | __le32 dram_size; |
910 | u64 dram_addr; |
911 | u32 ret; |
912 | |
913 | if (!frags) |
914 | break; |
915 | |
916 | dram_base_addr = cpu_to_le64(frags->physical); |
917 | dram_size = cpu_to_le32(frags->size); |
918 | dram_addr = le64_to_cpu(dram_base_addr); |
919 | |
920 | IWL_DEBUG_FW(fwrt, "WRT: dram_base_addr 0x%016llx, dram_size 0x%x\n" , |
921 | dram_base_addr, dram_size); |
922 | IWL_DEBUG_FW(fwrt, "WRT: config_list->addr_offset: %u\n" , |
923 | le32_to_cpu(config_list->addr_offset)); |
924 | for (count = 0; count < len; count++) { |
925 | address = le32_to_cpu(config_list->addr_val[count].address); |
926 | dram_info.dbgc1_add_lsb = |
927 | cpu_to_le32((dram_addr & 0x00000000FFFFFFFFULL) + 0x400); |
928 | dram_info.dbgc1_add_msb = |
929 | cpu_to_le32((dram_addr & 0xFFFFFFFF00000000ULL) >> 32); |
930 | dram_info.dbgc1_size = cpu_to_le32(le32_to_cpu(dram_size) - 0x400); |
931 | ret = iwl_trans_write_mem(trans: fwrt->trans, |
932 | addr: address + offset, buf: &dram_info, dwords: 4); |
933 | if (ret) { |
934 | IWL_ERR(fwrt, "Failed to write dram_info to HW_SMEM\n" ); |
935 | break; |
936 | } |
937 | } |
938 | break; |
939 | } |
940 | case IWL_FW_INI_CONFIG_SET_TYPE_PERIPH_SCRATCH_HWM: { |
941 | u32 debug_token_config = |
942 | le32_to_cpu(config_list->addr_val[0].value); |
943 | |
944 | IWL_DEBUG_FW(fwrt, "WRT: Setting HWM debug token config: %u\n" , |
945 | debug_token_config); |
946 | fwrt->trans->dbg.ucode_preset = debug_token_config; |
947 | break; |
948 | } |
949 | default: |
950 | break; |
951 | } |
952 | } |
953 | } |
954 | |
955 | static void iwl_dbg_tlv_periodic_trig_handler(struct timer_list *t) |
956 | { |
957 | struct iwl_dbg_tlv_timer_node *timer_node = |
958 | from_timer(timer_node, t, timer); |
959 | struct iwl_fwrt_dump_data dump_data = { |
960 | .trig = (void *)timer_node->tlv->data, |
961 | }; |
962 | int ret; |
963 | |
964 | ret = iwl_fw_dbg_ini_collect(fwrt: timer_node->fwrt, dump_data: &dump_data, sync: false); |
965 | if (!ret || ret == -EBUSY) { |
966 | u32 occur = le32_to_cpu(dump_data.trig->occurrences); |
967 | u32 collect_interval = le32_to_cpu(dump_data.trig->data[0]); |
968 | |
969 | if (!occur) |
970 | return; |
971 | |
972 | mod_timer(timer: t, expires: jiffies + msecs_to_jiffies(m: collect_interval)); |
973 | } |
974 | } |
975 | |
976 | static void iwl_dbg_tlv_set_periodic_trigs(struct iwl_fw_runtime *fwrt) |
977 | { |
978 | struct iwl_dbg_tlv_node *node; |
979 | struct list_head *trig_list = |
980 | &fwrt->trans->dbg.time_point[IWL_FW_INI_TIME_POINT_PERIODIC].active_trig_list; |
981 | |
982 | list_for_each_entry(node, trig_list, list) { |
983 | struct iwl_fw_ini_trigger_tlv *trig = (void *)node->tlv.data; |
984 | struct iwl_dbg_tlv_timer_node *timer_node; |
985 | u32 occur = le32_to_cpu(trig->occurrences), collect_interval; |
986 | u32 min_interval = 100; |
987 | |
988 | if (!occur) |
989 | continue; |
990 | |
991 | /* make sure there is at least one dword of data for the |
992 | * interval value |
993 | */ |
994 | if (le32_to_cpu(node->tlv.length) < |
995 | sizeof(*trig) + sizeof(__le32)) { |
996 | IWL_ERR(fwrt, |
997 | "WRT: Invalid periodic trigger data was not given\n" ); |
998 | continue; |
999 | } |
1000 | |
1001 | if (le32_to_cpu(trig->data[0]) < min_interval) { |
1002 | IWL_WARN(fwrt, |
1003 | "WRT: Override min interval from %u to %u msec\n" , |
1004 | le32_to_cpu(trig->data[0]), min_interval); |
1005 | trig->data[0] = cpu_to_le32(min_interval); |
1006 | } |
1007 | |
1008 | collect_interval = le32_to_cpu(trig->data[0]); |
1009 | |
1010 | timer_node = kzalloc(size: sizeof(*timer_node), GFP_KERNEL); |
1011 | if (!timer_node) { |
1012 | IWL_ERR(fwrt, |
1013 | "WRT: Failed to allocate periodic trigger\n" ); |
1014 | continue; |
1015 | } |
1016 | |
1017 | timer_node->fwrt = fwrt; |
1018 | timer_node->tlv = &node->tlv; |
1019 | timer_setup(&timer_node->timer, |
1020 | iwl_dbg_tlv_periodic_trig_handler, 0); |
1021 | |
1022 | list_add_tail(new: &timer_node->list, |
1023 | head: &fwrt->trans->dbg.periodic_trig_list); |
1024 | |
1025 | IWL_DEBUG_FW(fwrt, "WRT: Enabling periodic trigger\n" ); |
1026 | |
1027 | mod_timer(timer: &timer_node->timer, |
1028 | expires: jiffies + msecs_to_jiffies(m: collect_interval)); |
1029 | } |
1030 | } |
1031 | |
1032 | static bool is_trig_data_contained(const struct iwl_ucode_tlv *new, |
1033 | const struct iwl_ucode_tlv *old) |
1034 | { |
1035 | const struct iwl_fw_ini_trigger_tlv *new_trig = (const void *)new->data; |
1036 | const struct iwl_fw_ini_trigger_tlv *old_trig = (const void *)old->data; |
1037 | const __le32 *new_data = new_trig->data, *old_data = old_trig->data; |
1038 | u32 new_dwords_num = iwl_tlv_array_len(new, new_trig, data); |
1039 | u32 old_dwords_num = iwl_tlv_array_len(old, old_trig, data); |
1040 | int i, j; |
1041 | |
1042 | for (i = 0; i < new_dwords_num; i++) { |
1043 | bool match = false; |
1044 | |
1045 | for (j = 0; j < old_dwords_num; j++) { |
1046 | if (new_data[i] == old_data[j]) { |
1047 | match = true; |
1048 | break; |
1049 | } |
1050 | } |
1051 | if (!match) |
1052 | return false; |
1053 | } |
1054 | |
1055 | return true; |
1056 | } |
1057 | |
1058 | static int iwl_dbg_tlv_override_trig_node(struct iwl_fw_runtime *fwrt, |
1059 | struct iwl_ucode_tlv *trig_tlv, |
1060 | struct iwl_dbg_tlv_node *node) |
1061 | { |
1062 | struct iwl_ucode_tlv *node_tlv = &node->tlv; |
1063 | struct iwl_fw_ini_trigger_tlv *node_trig = (void *)node_tlv->data; |
1064 | struct iwl_fw_ini_trigger_tlv *trig = (void *)trig_tlv->data; |
1065 | u32 policy = le32_to_cpu(trig->apply_policy); |
1066 | u32 size = le32_to_cpu(trig_tlv->length); |
1067 | u32 trig_data_len = size - sizeof(*trig); |
1068 | u32 offset = 0; |
1069 | |
1070 | if (!(policy & IWL_FW_INI_APPLY_POLICY_OVERRIDE_DATA)) { |
1071 | u32 data_len = le32_to_cpu(node_tlv->length) - |
1072 | sizeof(*node_trig); |
1073 | |
1074 | IWL_DEBUG_FW(fwrt, |
1075 | "WRT: Appending trigger data (time point %u)\n" , |
1076 | le32_to_cpu(trig->time_point)); |
1077 | |
1078 | offset += data_len; |
1079 | size += data_len; |
1080 | } else { |
1081 | IWL_DEBUG_FW(fwrt, |
1082 | "WRT: Overriding trigger data (time point %u)\n" , |
1083 | le32_to_cpu(trig->time_point)); |
1084 | } |
1085 | |
1086 | if (size != le32_to_cpu(node_tlv->length)) { |
1087 | struct list_head *prev = node->list.prev; |
1088 | struct iwl_dbg_tlv_node *tmp; |
1089 | |
1090 | list_del(entry: &node->list); |
1091 | |
1092 | tmp = krealloc(objp: node, new_size: sizeof(*node) + size, GFP_KERNEL); |
1093 | if (!tmp) { |
1094 | IWL_WARN(fwrt, |
1095 | "WRT: No memory to override trigger (time point %u)\n" , |
1096 | le32_to_cpu(trig->time_point)); |
1097 | |
1098 | list_add(new: &node->list, head: prev); |
1099 | |
1100 | return -ENOMEM; |
1101 | } |
1102 | |
1103 | list_add(new: &tmp->list, head: prev); |
1104 | node_tlv = &tmp->tlv; |
1105 | node_trig = (void *)node_tlv->data; |
1106 | } |
1107 | |
1108 | memcpy((u8 *)node_trig->data + offset, trig->data, trig_data_len); |
1109 | node_tlv->length = cpu_to_le32(size); |
1110 | |
1111 | if (policy & IWL_FW_INI_APPLY_POLICY_OVERRIDE_CFG) { |
1112 | IWL_DEBUG_FW(fwrt, |
1113 | "WRT: Overriding trigger configuration (time point %u)\n" , |
1114 | le32_to_cpu(trig->time_point)); |
1115 | |
1116 | /* the first 11 dwords are configuration related */ |
1117 | memcpy(node_trig, trig, sizeof(__le32) * 11); |
1118 | } |
1119 | |
1120 | if (policy & IWL_FW_INI_APPLY_POLICY_OVERRIDE_REGIONS) { |
1121 | IWL_DEBUG_FW(fwrt, |
1122 | "WRT: Overriding trigger regions (time point %u)\n" , |
1123 | le32_to_cpu(trig->time_point)); |
1124 | |
1125 | node_trig->regions_mask = trig->regions_mask; |
1126 | } else { |
1127 | IWL_DEBUG_FW(fwrt, |
1128 | "WRT: Appending trigger regions (time point %u)\n" , |
1129 | le32_to_cpu(trig->time_point)); |
1130 | |
1131 | node_trig->regions_mask |= trig->regions_mask; |
1132 | } |
1133 | |
1134 | return 0; |
1135 | } |
1136 | |
1137 | static int |
1138 | iwl_dbg_tlv_add_active_trigger(struct iwl_fw_runtime *fwrt, |
1139 | struct list_head *trig_list, |
1140 | struct iwl_ucode_tlv *trig_tlv) |
1141 | { |
1142 | struct iwl_fw_ini_trigger_tlv *trig = (void *)trig_tlv->data; |
1143 | struct iwl_dbg_tlv_node *node, *match = NULL; |
1144 | u32 policy = le32_to_cpu(trig->apply_policy); |
1145 | |
1146 | list_for_each_entry(node, trig_list, list) { |
1147 | if (!(policy & IWL_FW_INI_APPLY_POLICY_MATCH_TIME_POINT)) |
1148 | break; |
1149 | |
1150 | if (!(policy & IWL_FW_INI_APPLY_POLICY_MATCH_DATA) || |
1151 | is_trig_data_contained(new: trig_tlv, old: &node->tlv)) { |
1152 | match = node; |
1153 | break; |
1154 | } |
1155 | } |
1156 | |
1157 | if (!match) { |
1158 | IWL_DEBUG_FW(fwrt, "WRT: Enabling trigger (time point %u)\n" , |
1159 | le32_to_cpu(trig->time_point)); |
1160 | if (!iwl_dbg_tlv_add(tlv: trig_tlv, list: trig_list)) |
1161 | return -ENOMEM; |
1162 | return 0; |
1163 | } |
1164 | |
1165 | return iwl_dbg_tlv_override_trig_node(fwrt, trig_tlv, node: match); |
1166 | } |
1167 | |
1168 | static void |
1169 | iwl_dbg_tlv_gen_active_trig_list(struct iwl_fw_runtime *fwrt, |
1170 | struct iwl_dbg_tlv_time_point_data *tp) |
1171 | { |
1172 | struct iwl_dbg_tlv_node *node; |
1173 | struct list_head *trig_list = &tp->trig_list; |
1174 | struct list_head *active_trig_list = &tp->active_trig_list; |
1175 | |
1176 | list_for_each_entry(node, trig_list, list) { |
1177 | struct iwl_ucode_tlv *tlv = &node->tlv; |
1178 | |
1179 | iwl_dbg_tlv_add_active_trigger(fwrt, trig_list: active_trig_list, trig_tlv: tlv); |
1180 | } |
1181 | } |
1182 | |
1183 | static bool iwl_dbg_tlv_check_fw_pkt(struct iwl_fw_runtime *fwrt, |
1184 | struct iwl_fwrt_dump_data *dump_data, |
1185 | union iwl_dbg_tlv_tp_data *tp_data, |
1186 | u32 trig_data) |
1187 | { |
1188 | struct iwl_rx_packet *pkt = tp_data->fw_pkt; |
1189 | struct iwl_cmd_header *wanted_hdr = (void *)&trig_data; |
1190 | |
1191 | if (pkt && (pkt->hdr.cmd == wanted_hdr->cmd && |
1192 | pkt->hdr.group_id == wanted_hdr->group_id)) { |
1193 | struct iwl_rx_packet *fw_pkt = |
1194 | kmemdup(p: pkt, |
1195 | size: sizeof(*pkt) + iwl_rx_packet_payload_len(pkt), |
1196 | GFP_ATOMIC); |
1197 | |
1198 | if (!fw_pkt) |
1199 | return false; |
1200 | |
1201 | dump_data->fw_pkt = fw_pkt; |
1202 | |
1203 | return true; |
1204 | } |
1205 | |
1206 | return false; |
1207 | } |
1208 | |
1209 | static int |
1210 | iwl_dbg_tlv_tp_trigger(struct iwl_fw_runtime *fwrt, bool sync, |
1211 | struct list_head *active_trig_list, |
1212 | union iwl_dbg_tlv_tp_data *tp_data, |
1213 | bool (*data_check)(struct iwl_fw_runtime *fwrt, |
1214 | struct iwl_fwrt_dump_data *dump_data, |
1215 | union iwl_dbg_tlv_tp_data *tp_data, |
1216 | u32 trig_data)) |
1217 | { |
1218 | struct iwl_dbg_tlv_node *node; |
1219 | |
1220 | list_for_each_entry(node, active_trig_list, list) { |
1221 | struct iwl_fwrt_dump_data dump_data = { |
1222 | .trig = (void *)node->tlv.data, |
1223 | }; |
1224 | u32 num_data = iwl_tlv_array_len(&node->tlv, dump_data.trig, |
1225 | data); |
1226 | int ret, i; |
1227 | u32 tp = le32_to_cpu(dump_data.trig->time_point); |
1228 | |
1229 | |
1230 | if (!num_data) { |
1231 | ret = iwl_fw_dbg_ini_collect(fwrt, dump_data: &dump_data, sync); |
1232 | if (ret) |
1233 | return ret; |
1234 | } |
1235 | |
1236 | for (i = 0; i < num_data; i++) { |
1237 | if (!data_check || |
1238 | data_check(fwrt, &dump_data, tp_data, |
1239 | le32_to_cpu(dump_data.trig->data[i]))) { |
1240 | ret = iwl_fw_dbg_ini_collect(fwrt, dump_data: &dump_data, sync); |
1241 | if (ret) |
1242 | return ret; |
1243 | |
1244 | break; |
1245 | } |
1246 | } |
1247 | |
1248 | fwrt->trans->dbg.restart_required = false; |
1249 | IWL_DEBUG_FW(fwrt, "WRT: tp %d, reset_fw %d\n" , |
1250 | tp, dump_data.trig->reset_fw); |
1251 | IWL_DEBUG_FW(fwrt, |
1252 | "WRT: restart_required %d, last_tp_resetfw %d\n" , |
1253 | fwrt->trans->dbg.restart_required, |
1254 | fwrt->trans->dbg.last_tp_resetfw); |
1255 | |
1256 | if (fwrt->trans->trans_cfg->device_family == |
1257 | IWL_DEVICE_FAMILY_9000) { |
1258 | fwrt->trans->dbg.restart_required = true; |
1259 | } else if (tp == IWL_FW_INI_TIME_POINT_FW_ASSERT && |
1260 | fwrt->trans->dbg.last_tp_resetfw == |
1261 | IWL_FW_INI_RESET_FW_MODE_STOP_FW_ONLY) { |
1262 | fwrt->trans->dbg.restart_required = false; |
1263 | fwrt->trans->dbg.last_tp_resetfw = 0xFF; |
1264 | IWL_DEBUG_FW(fwrt, "WRT: FW_ASSERT due to reset_fw_mode-no restart\n" ); |
1265 | } else if (le32_to_cpu(dump_data.trig->reset_fw) == |
1266 | IWL_FW_INI_RESET_FW_MODE_STOP_AND_RELOAD_FW) { |
1267 | IWL_DEBUG_FW(fwrt, "WRT: stop and reload firmware\n" ); |
1268 | fwrt->trans->dbg.restart_required = true; |
1269 | } else if (le32_to_cpu(dump_data.trig->reset_fw) == |
1270 | IWL_FW_INI_RESET_FW_MODE_STOP_FW_ONLY) { |
1271 | IWL_DEBUG_FW(fwrt, |
1272 | "WRT: stop only and no reload firmware\n" ); |
1273 | fwrt->trans->dbg.restart_required = false; |
1274 | fwrt->trans->dbg.last_tp_resetfw = |
1275 | le32_to_cpu(dump_data.trig->reset_fw); |
1276 | } else if (le32_to_cpu(dump_data.trig->reset_fw) == |
1277 | IWL_FW_INI_RESET_FW_MODE_NOTHING) { |
1278 | IWL_DEBUG_FW(fwrt, |
1279 | "WRT: nothing need to be done after debug collection\n" ); |
1280 | } else { |
1281 | IWL_ERR(fwrt, "WRT: wrong resetfw %d\n" , |
1282 | le32_to_cpu(dump_data.trig->reset_fw)); |
1283 | } |
1284 | } |
1285 | return 0; |
1286 | } |
1287 | |
1288 | void iwl_dbg_tlv_init_cfg(struct iwl_fw_runtime *fwrt) |
1289 | { |
1290 | enum iwl_fw_ini_buffer_location *ini_dest = &fwrt->trans->dbg.ini_dest; |
1291 | int ret, i; |
1292 | u32 failed_alloc = 0; |
1293 | |
1294 | if (*ini_dest == IWL_FW_INI_LOCATION_INVALID) { |
1295 | IWL_DEBUG_FW(fwrt, |
1296 | "WRT: Generating active triggers list, domain 0x%x\n" , |
1297 | fwrt->trans->dbg.domains_bitmap); |
1298 | |
1299 | for (i = 0; i < ARRAY_SIZE(fwrt->trans->dbg.time_point); i++) { |
1300 | struct iwl_dbg_tlv_time_point_data *tp = |
1301 | &fwrt->trans->dbg.time_point[i]; |
1302 | |
1303 | iwl_dbg_tlv_gen_active_trig_list(fwrt, tp); |
1304 | } |
1305 | } else if (*ini_dest != IWL_FW_INI_LOCATION_DRAM_PATH) { |
1306 | /* For DRAM, go through the loop below to clear all the buffers |
1307 | * properly on restart, otherwise garbage may be left there and |
1308 | * leak into new debug dumps. |
1309 | */ |
1310 | return; |
1311 | } |
1312 | |
1313 | *ini_dest = IWL_FW_INI_LOCATION_INVALID; |
1314 | for (i = 0; i < IWL_FW_INI_ALLOCATION_NUM; i++) { |
1315 | struct iwl_fw_ini_allocation_tlv *fw_mon_cfg = |
1316 | &fwrt->trans->dbg.fw_mon_cfg[i]; |
1317 | u32 dest = le32_to_cpu(fw_mon_cfg->buf_location); |
1318 | |
1319 | if (dest == IWL_FW_INI_LOCATION_INVALID) { |
1320 | failed_alloc |= BIT(i); |
1321 | continue; |
1322 | } |
1323 | |
1324 | if (*ini_dest == IWL_FW_INI_LOCATION_INVALID) |
1325 | *ini_dest = dest; |
1326 | |
1327 | if (dest != *ini_dest) |
1328 | continue; |
1329 | |
1330 | ret = iwl_dbg_tlv_alloc_fragments(fwrt, alloc_id: i); |
1331 | |
1332 | if (ret) { |
1333 | IWL_WARN(fwrt, |
1334 | "WRT: Failed to allocate DRAM buffer for allocation id %d, ret=%d\n" , |
1335 | i, ret); |
1336 | failed_alloc |= BIT(i); |
1337 | } |
1338 | } |
1339 | |
1340 | if (!failed_alloc) |
1341 | return; |
1342 | |
1343 | for (i = 0; i < ARRAY_SIZE(fwrt->trans->dbg.active_regions) && failed_alloc; i++) { |
1344 | struct iwl_fw_ini_region_tlv *reg; |
1345 | struct iwl_ucode_tlv **active_reg = |
1346 | &fwrt->trans->dbg.active_regions[i]; |
1347 | u32 reg_type; |
1348 | |
1349 | if (!*active_reg) { |
1350 | fwrt->trans->dbg.unsupported_region_msk |= BIT(i); |
1351 | continue; |
1352 | } |
1353 | |
1354 | reg = (void *)(*active_reg)->data; |
1355 | reg_type = reg->type; |
1356 | |
1357 | if (reg_type != IWL_FW_INI_REGION_DRAM_BUFFER || |
1358 | !(BIT(le32_to_cpu(reg->dram_alloc_id)) & failed_alloc)) |
1359 | continue; |
1360 | |
1361 | IWL_DEBUG_FW(fwrt, |
1362 | "WRT: removing allocation id %d from region id %d\n" , |
1363 | le32_to_cpu(reg->dram_alloc_id), i); |
1364 | |
1365 | failed_alloc &= ~BIT(le32_to_cpu(reg->dram_alloc_id)); |
1366 | fwrt->trans->dbg.unsupported_region_msk |= BIT(i); |
1367 | |
1368 | kfree(objp: *active_reg); |
1369 | *active_reg = NULL; |
1370 | } |
1371 | } |
1372 | |
1373 | void _iwl_dbg_tlv_time_point(struct iwl_fw_runtime *fwrt, |
1374 | enum iwl_fw_ini_time_point tp_id, |
1375 | union iwl_dbg_tlv_tp_data *tp_data, |
1376 | bool sync) |
1377 | { |
1378 | struct list_head *hcmd_list, *trig_list, *conf_list; |
1379 | |
1380 | if (!iwl_trans_dbg_ini_valid(trans: fwrt->trans) || |
1381 | tp_id == IWL_FW_INI_TIME_POINT_INVALID || |
1382 | tp_id >= IWL_FW_INI_TIME_POINT_NUM) |
1383 | return; |
1384 | |
1385 | hcmd_list = &fwrt->trans->dbg.time_point[tp_id].hcmd_list; |
1386 | trig_list = &fwrt->trans->dbg.time_point[tp_id].active_trig_list; |
1387 | conf_list = &fwrt->trans->dbg.time_point[tp_id].config_list; |
1388 | |
1389 | switch (tp_id) { |
1390 | case IWL_FW_INI_TIME_POINT_EARLY: |
1391 | iwl_dbg_tlv_init_cfg(fwrt); |
1392 | iwl_dbg_tlv_apply_config(fwrt, conf_list); |
1393 | iwl_dbg_tlv_update_drams(fwrt); |
1394 | iwl_dbg_tlv_tp_trigger(fwrt, sync, active_trig_list: trig_list, tp_data, NULL); |
1395 | break; |
1396 | case IWL_FW_INI_TIME_POINT_AFTER_ALIVE: |
1397 | iwl_dbg_tlv_apply_buffers(fwrt); |
1398 | iwl_dbg_tlv_send_hcmds(fwrt, hcmd_list); |
1399 | iwl_dbg_tlv_apply_config(fwrt, conf_list); |
1400 | iwl_dbg_tlv_tp_trigger(fwrt, sync, active_trig_list: trig_list, tp_data, NULL); |
1401 | break; |
1402 | case IWL_FW_INI_TIME_POINT_PERIODIC: |
1403 | iwl_dbg_tlv_set_periodic_trigs(fwrt); |
1404 | iwl_dbg_tlv_send_hcmds(fwrt, hcmd_list); |
1405 | break; |
1406 | case IWL_FW_INI_TIME_POINT_FW_RSP_OR_NOTIF: |
1407 | case IWL_FW_INI_TIME_POINT_MISSED_BEACONS: |
1408 | case IWL_FW_INI_TIME_POINT_FW_DHC_NOTIFICATION: |
1409 | iwl_dbg_tlv_send_hcmds(fwrt, hcmd_list); |
1410 | iwl_dbg_tlv_apply_config(fwrt, conf_list); |
1411 | iwl_dbg_tlv_tp_trigger(fwrt, sync, active_trig_list: trig_list, tp_data, |
1412 | data_check: iwl_dbg_tlv_check_fw_pkt); |
1413 | break; |
1414 | default: |
1415 | iwl_dbg_tlv_send_hcmds(fwrt, hcmd_list); |
1416 | iwl_dbg_tlv_apply_config(fwrt, conf_list); |
1417 | iwl_dbg_tlv_tp_trigger(fwrt, sync, active_trig_list: trig_list, tp_data, NULL); |
1418 | break; |
1419 | } |
1420 | } |
1421 | IWL_EXPORT_SYMBOL(_iwl_dbg_tlv_time_point); |
1422 | |