1 | // SPDX-License-Identifier: BSD-3-Clause-Clear |
2 | /* |
3 | * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. |
4 | * Copyright (c) 2021-2023 Qualcomm Innovation Center, Inc. All rights reserved. |
5 | */ |
6 | |
7 | #include <linux/vmalloc.h> |
8 | |
9 | #include "debugfs.h" |
10 | |
11 | #include "core.h" |
12 | #include "debug.h" |
13 | #include "wmi.h" |
14 | #include "hal_rx.h" |
15 | #include "dp_tx.h" |
16 | #include "debugfs_htt_stats.h" |
17 | #include "peer.h" |
18 | #include "hif.h" |
19 | |
20 | static const char *htt_bp_umac_ring[HTT_SW_UMAC_RING_IDX_MAX] = { |
21 | "REO2SW1_RING" , |
22 | "REO2SW2_RING" , |
23 | "REO2SW3_RING" , |
24 | "REO2SW4_RING" , |
25 | "WBM2REO_LINK_RING" , |
26 | "REO2TCL_RING" , |
27 | "REO2FW_RING" , |
28 | "RELEASE_RING" , |
29 | "PPE_RELEASE_RING" , |
30 | "TCL2TQM_RING" , |
31 | "TQM_RELEASE_RING" , |
32 | "REO_RELEASE_RING" , |
33 | "WBM2SW0_RELEASE_RING" , |
34 | "WBM2SW1_RELEASE_RING" , |
35 | "WBM2SW2_RELEASE_RING" , |
36 | "WBM2SW3_RELEASE_RING" , |
37 | "REO_CMD_RING" , |
38 | "REO_STATUS_RING" , |
39 | }; |
40 | |
41 | static const char *htt_bp_lmac_ring[HTT_SW_LMAC_RING_IDX_MAX] = { |
42 | "FW2RXDMA_BUF_RING" , |
43 | "FW2RXDMA_STATUS_RING" , |
44 | "FW2RXDMA_LINK_RING" , |
45 | "SW2RXDMA_BUF_RING" , |
46 | "WBM2RXDMA_LINK_RING" , |
47 | "RXDMA2FW_RING" , |
48 | "RXDMA2SW_RING" , |
49 | "RXDMA2RELEASE_RING" , |
50 | "RXDMA2REO_RING" , |
51 | "MONITOR_STATUS_RING" , |
52 | "MONITOR_BUF_RING" , |
53 | "MONITOR_DESC_RING" , |
54 | "MONITOR_DEST_RING" , |
55 | }; |
56 | |
57 | void ath11k_debugfs_add_dbring_entry(struct ath11k *ar, |
58 | enum wmi_direct_buffer_module id, |
59 | enum ath11k_dbg_dbr_event event, |
60 | struct hal_srng *srng) |
61 | { |
62 | struct ath11k_debug_dbr *dbr_debug; |
63 | struct ath11k_dbg_dbr_data *dbr_data; |
64 | struct ath11k_dbg_dbr_entry *entry; |
65 | |
66 | if (id >= WMI_DIRECT_BUF_MAX || event >= ATH11K_DBG_DBR_EVENT_MAX) |
67 | return; |
68 | |
69 | dbr_debug = ar->debug.dbr_debug[id]; |
70 | if (!dbr_debug) |
71 | return; |
72 | |
73 | if (!dbr_debug->dbr_debug_enabled) |
74 | return; |
75 | |
76 | dbr_data = &dbr_debug->dbr_dbg_data; |
77 | |
78 | spin_lock_bh(lock: &dbr_data->lock); |
79 | |
80 | if (dbr_data->entries) { |
81 | entry = &dbr_data->entries[dbr_data->dbr_debug_idx]; |
82 | entry->hp = srng->u.src_ring.hp; |
83 | entry->tp = *srng->u.src_ring.tp_addr; |
84 | entry->timestamp = jiffies; |
85 | entry->event = event; |
86 | |
87 | dbr_data->dbr_debug_idx++; |
88 | if (dbr_data->dbr_debug_idx == |
89 | dbr_data->num_ring_debug_entries) |
90 | dbr_data->dbr_debug_idx = 0; |
91 | } |
92 | |
93 | spin_unlock_bh(lock: &dbr_data->lock); |
94 | } |
95 | |
96 | static void ath11k_debugfs_fw_stats_reset(struct ath11k *ar) |
97 | { |
98 | spin_lock_bh(lock: &ar->data_lock); |
99 | ar->fw_stats_done = false; |
100 | ath11k_fw_stats_pdevs_free(head: &ar->fw_stats.pdevs); |
101 | ath11k_fw_stats_vdevs_free(head: &ar->fw_stats.vdevs); |
102 | spin_unlock_bh(lock: &ar->data_lock); |
103 | } |
104 | |
105 | void ath11k_debugfs_fw_stats_process(struct ath11k *ar, struct ath11k_fw_stats *stats) |
106 | { |
107 | struct ath11k_base *ab = ar->ab; |
108 | struct ath11k_pdev *pdev; |
109 | bool is_end; |
110 | static unsigned int num_vdev, num_bcn; |
111 | size_t total_vdevs_started = 0; |
112 | int i; |
113 | |
114 | /* WMI_REQUEST_PDEV_STAT request has been already processed */ |
115 | |
116 | if (stats->stats_id == WMI_REQUEST_RSSI_PER_CHAIN_STAT) { |
117 | ar->fw_stats_done = true; |
118 | return; |
119 | } |
120 | |
121 | if (stats->stats_id == WMI_REQUEST_VDEV_STAT) { |
122 | if (list_empty(head: &stats->vdevs)) { |
123 | ath11k_warn(ab, fmt: "empty vdev stats" ); |
124 | return; |
125 | } |
126 | /* FW sends all the active VDEV stats irrespective of PDEV, |
127 | * hence limit until the count of all VDEVs started |
128 | */ |
129 | for (i = 0; i < ab->num_radios; i++) { |
130 | pdev = rcu_dereference(ab->pdevs_active[i]); |
131 | if (pdev && pdev->ar) |
132 | total_vdevs_started += ar->num_started_vdevs; |
133 | } |
134 | |
135 | is_end = ((++num_vdev) == total_vdevs_started); |
136 | |
137 | list_splice_tail_init(list: &stats->vdevs, |
138 | head: &ar->fw_stats.vdevs); |
139 | |
140 | if (is_end) { |
141 | ar->fw_stats_done = true; |
142 | num_vdev = 0; |
143 | } |
144 | return; |
145 | } |
146 | |
147 | if (stats->stats_id == WMI_REQUEST_BCN_STAT) { |
148 | if (list_empty(head: &stats->bcn)) { |
149 | ath11k_warn(ab, fmt: "empty bcn stats" ); |
150 | return; |
151 | } |
152 | /* Mark end until we reached the count of all started VDEVs |
153 | * within the PDEV |
154 | */ |
155 | is_end = ((++num_bcn) == ar->num_started_vdevs); |
156 | |
157 | list_splice_tail_init(list: &stats->bcn, |
158 | head: &ar->fw_stats.bcn); |
159 | |
160 | if (is_end) { |
161 | ar->fw_stats_done = true; |
162 | num_bcn = 0; |
163 | } |
164 | } |
165 | } |
166 | |
167 | static int ath11k_debugfs_fw_stats_request(struct ath11k *ar, |
168 | struct stats_request_params *req_param) |
169 | { |
170 | struct ath11k_base *ab = ar->ab; |
171 | unsigned long timeout, time_left; |
172 | int ret; |
173 | |
174 | lockdep_assert_held(&ar->conf_mutex); |
175 | |
176 | /* FW stats can get split when exceeding the stats data buffer limit. |
177 | * In that case, since there is no end marking for the back-to-back |
178 | * received 'update stats' event, we keep a 3 seconds timeout in case, |
179 | * fw_stats_done is not marked yet |
180 | */ |
181 | timeout = jiffies + msecs_to_jiffies(m: 3 * 1000); |
182 | |
183 | ath11k_debugfs_fw_stats_reset(ar); |
184 | |
185 | reinit_completion(x: &ar->fw_stats_complete); |
186 | |
187 | ret = ath11k_wmi_send_stats_request_cmd(ar, param: req_param); |
188 | |
189 | if (ret) { |
190 | ath11k_warn(ab, fmt: "could not request fw stats (%d)\n" , |
191 | ret); |
192 | return ret; |
193 | } |
194 | |
195 | time_left = wait_for_completion_timeout(x: &ar->fw_stats_complete, timeout: 1 * HZ); |
196 | |
197 | if (!time_left) |
198 | return -ETIMEDOUT; |
199 | |
200 | for (;;) { |
201 | if (time_after(jiffies, timeout)) |
202 | break; |
203 | |
204 | spin_lock_bh(lock: &ar->data_lock); |
205 | if (ar->fw_stats_done) { |
206 | spin_unlock_bh(lock: &ar->data_lock); |
207 | break; |
208 | } |
209 | spin_unlock_bh(lock: &ar->data_lock); |
210 | } |
211 | return 0; |
212 | } |
213 | |
214 | int ath11k_debugfs_get_fw_stats(struct ath11k *ar, u32 pdev_id, |
215 | u32 vdev_id, u32 stats_id) |
216 | { |
217 | struct ath11k_base *ab = ar->ab; |
218 | struct stats_request_params req_param; |
219 | int ret; |
220 | |
221 | mutex_lock(&ar->conf_mutex); |
222 | |
223 | if (ar->state != ATH11K_STATE_ON) { |
224 | ret = -ENETDOWN; |
225 | goto err_unlock; |
226 | } |
227 | |
228 | req_param.pdev_id = pdev_id; |
229 | req_param.vdev_id = vdev_id; |
230 | req_param.stats_id = stats_id; |
231 | |
232 | ret = ath11k_debugfs_fw_stats_request(ar, req_param: &req_param); |
233 | if (ret) |
234 | ath11k_warn(ab, fmt: "failed to request fw stats: %d\n" , ret); |
235 | |
236 | ath11k_dbg(ab, ATH11K_DBG_WMI, |
237 | "debug get fw stat pdev id %d vdev id %d stats id 0x%x\n" , |
238 | pdev_id, vdev_id, stats_id); |
239 | |
240 | err_unlock: |
241 | mutex_unlock(lock: &ar->conf_mutex); |
242 | |
243 | return ret; |
244 | } |
245 | |
246 | static int ath11k_open_pdev_stats(struct inode *inode, struct file *file) |
247 | { |
248 | struct ath11k *ar = inode->i_private; |
249 | struct ath11k_base *ab = ar->ab; |
250 | struct stats_request_params req_param; |
251 | void *buf = NULL; |
252 | int ret; |
253 | |
254 | mutex_lock(&ar->conf_mutex); |
255 | |
256 | if (ar->state != ATH11K_STATE_ON) { |
257 | ret = -ENETDOWN; |
258 | goto err_unlock; |
259 | } |
260 | |
261 | buf = vmalloc(ATH11K_FW_STATS_BUF_SIZE); |
262 | if (!buf) { |
263 | ret = -ENOMEM; |
264 | goto err_unlock; |
265 | } |
266 | |
267 | req_param.pdev_id = ar->pdev->pdev_id; |
268 | req_param.vdev_id = 0; |
269 | req_param.stats_id = WMI_REQUEST_PDEV_STAT; |
270 | |
271 | ret = ath11k_debugfs_fw_stats_request(ar, req_param: &req_param); |
272 | if (ret) { |
273 | ath11k_warn(ab, fmt: "failed to request fw pdev stats: %d\n" , ret); |
274 | goto err_free; |
275 | } |
276 | |
277 | ath11k_wmi_fw_stats_fill(ar, fw_stats: &ar->fw_stats, stats_id: req_param.stats_id, buf); |
278 | |
279 | file->private_data = buf; |
280 | |
281 | mutex_unlock(lock: &ar->conf_mutex); |
282 | return 0; |
283 | |
284 | err_free: |
285 | vfree(addr: buf); |
286 | |
287 | err_unlock: |
288 | mutex_unlock(lock: &ar->conf_mutex); |
289 | return ret; |
290 | } |
291 | |
292 | static int ath11k_release_pdev_stats(struct inode *inode, struct file *file) |
293 | { |
294 | vfree(addr: file->private_data); |
295 | |
296 | return 0; |
297 | } |
298 | |
299 | static ssize_t ath11k_read_pdev_stats(struct file *file, |
300 | char __user *user_buf, |
301 | size_t count, loff_t *ppos) |
302 | { |
303 | const char *buf = file->private_data; |
304 | size_t len = strlen(buf); |
305 | |
306 | return simple_read_from_buffer(to: user_buf, count, ppos, from: buf, available: len); |
307 | } |
308 | |
309 | static const struct file_operations fops_pdev_stats = { |
310 | .open = ath11k_open_pdev_stats, |
311 | .release = ath11k_release_pdev_stats, |
312 | .read = ath11k_read_pdev_stats, |
313 | .owner = THIS_MODULE, |
314 | .llseek = default_llseek, |
315 | }; |
316 | |
317 | static int ath11k_open_vdev_stats(struct inode *inode, struct file *file) |
318 | { |
319 | struct ath11k *ar = inode->i_private; |
320 | struct stats_request_params req_param; |
321 | void *buf = NULL; |
322 | int ret; |
323 | |
324 | mutex_lock(&ar->conf_mutex); |
325 | |
326 | if (ar->state != ATH11K_STATE_ON) { |
327 | ret = -ENETDOWN; |
328 | goto err_unlock; |
329 | } |
330 | |
331 | buf = vmalloc(ATH11K_FW_STATS_BUF_SIZE); |
332 | if (!buf) { |
333 | ret = -ENOMEM; |
334 | goto err_unlock; |
335 | } |
336 | |
337 | req_param.pdev_id = ar->pdev->pdev_id; |
338 | /* VDEV stats is always sent for all active VDEVs from FW */ |
339 | req_param.vdev_id = 0; |
340 | req_param.stats_id = WMI_REQUEST_VDEV_STAT; |
341 | |
342 | ret = ath11k_debugfs_fw_stats_request(ar, req_param: &req_param); |
343 | if (ret) { |
344 | ath11k_warn(ab: ar->ab, fmt: "failed to request fw vdev stats: %d\n" , ret); |
345 | goto err_free; |
346 | } |
347 | |
348 | ath11k_wmi_fw_stats_fill(ar, fw_stats: &ar->fw_stats, stats_id: req_param.stats_id, buf); |
349 | |
350 | file->private_data = buf; |
351 | |
352 | mutex_unlock(lock: &ar->conf_mutex); |
353 | return 0; |
354 | |
355 | err_free: |
356 | vfree(addr: buf); |
357 | |
358 | err_unlock: |
359 | mutex_unlock(lock: &ar->conf_mutex); |
360 | return ret; |
361 | } |
362 | |
363 | static int ath11k_release_vdev_stats(struct inode *inode, struct file *file) |
364 | { |
365 | vfree(addr: file->private_data); |
366 | |
367 | return 0; |
368 | } |
369 | |
370 | static ssize_t ath11k_read_vdev_stats(struct file *file, |
371 | char __user *user_buf, |
372 | size_t count, loff_t *ppos) |
373 | { |
374 | const char *buf = file->private_data; |
375 | size_t len = strlen(buf); |
376 | |
377 | return simple_read_from_buffer(to: user_buf, count, ppos, from: buf, available: len); |
378 | } |
379 | |
380 | static const struct file_operations fops_vdev_stats = { |
381 | .open = ath11k_open_vdev_stats, |
382 | .release = ath11k_release_vdev_stats, |
383 | .read = ath11k_read_vdev_stats, |
384 | .owner = THIS_MODULE, |
385 | .llseek = default_llseek, |
386 | }; |
387 | |
388 | static int ath11k_open_bcn_stats(struct inode *inode, struct file *file) |
389 | { |
390 | struct ath11k *ar = inode->i_private; |
391 | struct ath11k_vif *arvif; |
392 | struct stats_request_params req_param; |
393 | void *buf = NULL; |
394 | int ret; |
395 | |
396 | mutex_lock(&ar->conf_mutex); |
397 | |
398 | if (ar->state != ATH11K_STATE_ON) { |
399 | ret = -ENETDOWN; |
400 | goto err_unlock; |
401 | } |
402 | |
403 | buf = vmalloc(ATH11K_FW_STATS_BUF_SIZE); |
404 | if (!buf) { |
405 | ret = -ENOMEM; |
406 | goto err_unlock; |
407 | } |
408 | |
409 | req_param.stats_id = WMI_REQUEST_BCN_STAT; |
410 | req_param.pdev_id = ar->pdev->pdev_id; |
411 | |
412 | /* loop all active VDEVs for bcn stats */ |
413 | list_for_each_entry(arvif, &ar->arvifs, list) { |
414 | if (!arvif->is_up) |
415 | continue; |
416 | |
417 | req_param.vdev_id = arvif->vdev_id; |
418 | ret = ath11k_debugfs_fw_stats_request(ar, req_param: &req_param); |
419 | if (ret) { |
420 | ath11k_warn(ab: ar->ab, fmt: "failed to request fw bcn stats: %d\n" , ret); |
421 | goto err_free; |
422 | } |
423 | } |
424 | |
425 | ath11k_wmi_fw_stats_fill(ar, fw_stats: &ar->fw_stats, stats_id: req_param.stats_id, buf); |
426 | |
427 | /* since beacon stats request is looped for all active VDEVs, saved fw |
428 | * stats is not freed for each request until done for all active VDEVs |
429 | */ |
430 | spin_lock_bh(lock: &ar->data_lock); |
431 | ath11k_fw_stats_bcn_free(head: &ar->fw_stats.bcn); |
432 | spin_unlock_bh(lock: &ar->data_lock); |
433 | |
434 | file->private_data = buf; |
435 | |
436 | mutex_unlock(lock: &ar->conf_mutex); |
437 | return 0; |
438 | |
439 | err_free: |
440 | vfree(addr: buf); |
441 | |
442 | err_unlock: |
443 | mutex_unlock(lock: &ar->conf_mutex); |
444 | return ret; |
445 | } |
446 | |
447 | static int ath11k_release_bcn_stats(struct inode *inode, struct file *file) |
448 | { |
449 | vfree(addr: file->private_data); |
450 | |
451 | return 0; |
452 | } |
453 | |
454 | static ssize_t ath11k_read_bcn_stats(struct file *file, |
455 | char __user *user_buf, |
456 | size_t count, loff_t *ppos) |
457 | { |
458 | const char *buf = file->private_data; |
459 | size_t len = strlen(buf); |
460 | |
461 | return simple_read_from_buffer(to: user_buf, count, ppos, from: buf, available: len); |
462 | } |
463 | |
464 | static const struct file_operations fops_bcn_stats = { |
465 | .open = ath11k_open_bcn_stats, |
466 | .release = ath11k_release_bcn_stats, |
467 | .read = ath11k_read_bcn_stats, |
468 | .owner = THIS_MODULE, |
469 | .llseek = default_llseek, |
470 | }; |
471 | |
472 | static ssize_t ath11k_read_simulate_fw_crash(struct file *file, |
473 | char __user *user_buf, |
474 | size_t count, loff_t *ppos) |
475 | { |
476 | const char buf[] = |
477 | "To simulate firmware crash write one of the keywords to this file:\n" |
478 | "`assert` - this will send WMI_FORCE_FW_HANG_CMDID to firmware to cause assert.\n" |
479 | "`hw-restart` - this will simply queue hw restart without fw/hw actually crashing.\n" ; |
480 | |
481 | return simple_read_from_buffer(to: user_buf, count, ppos, from: buf, strlen(buf)); |
482 | } |
483 | |
484 | /* Simulate firmware crash: |
485 | * 'soft': Call wmi command causing firmware hang. This firmware hang is |
486 | * recoverable by warm firmware reset. |
487 | * 'hard': Force firmware crash by setting any vdev parameter for not allowed |
488 | * vdev id. This is hard firmware crash because it is recoverable only by cold |
489 | * firmware reset. |
490 | */ |
491 | static ssize_t ath11k_write_simulate_fw_crash(struct file *file, |
492 | const char __user *user_buf, |
493 | size_t count, loff_t *ppos) |
494 | { |
495 | struct ath11k_base *ab = file->private_data; |
496 | struct ath11k_pdev *pdev; |
497 | struct ath11k *ar = ab->pdevs[0].ar; |
498 | char buf[32] = {0}; |
499 | ssize_t rc; |
500 | int i, ret, radioup = 0; |
501 | |
502 | for (i = 0; i < ab->num_radios; i++) { |
503 | pdev = &ab->pdevs[i]; |
504 | ar = pdev->ar; |
505 | if (ar && ar->state == ATH11K_STATE_ON) { |
506 | radioup = 1; |
507 | break; |
508 | } |
509 | } |
510 | /* filter partial writes and invalid commands */ |
511 | if (*ppos != 0 || count >= sizeof(buf) || count == 0) |
512 | return -EINVAL; |
513 | |
514 | rc = simple_write_to_buffer(to: buf, available: sizeof(buf) - 1, ppos, from: user_buf, count); |
515 | if (rc < 0) |
516 | return rc; |
517 | |
518 | /* drop the possible '\n' from the end */ |
519 | if (buf[*ppos - 1] == '\n') |
520 | buf[*ppos - 1] = '\0'; |
521 | |
522 | if (radioup == 0) { |
523 | ret = -ENETDOWN; |
524 | goto exit; |
525 | } |
526 | |
527 | if (!strcmp(buf, "assert" )) { |
528 | ath11k_info(ab, fmt: "simulating firmware assert crash\n" ); |
529 | ret = ath11k_wmi_force_fw_hang_cmd(ar, |
530 | ATH11K_WMI_FW_HANG_ASSERT_TYPE, |
531 | ATH11K_WMI_FW_HANG_DELAY); |
532 | } else if (!strcmp(buf, "hw-restart" )) { |
533 | ath11k_info(ab, fmt: "user requested hw restart\n" ); |
534 | queue_work(wq: ab->workqueue_aux, work: &ab->reset_work); |
535 | ret = 0; |
536 | } else { |
537 | ret = -EINVAL; |
538 | goto exit; |
539 | } |
540 | |
541 | if (ret) { |
542 | ath11k_warn(ab, fmt: "failed to simulate firmware crash: %d\n" , ret); |
543 | goto exit; |
544 | } |
545 | |
546 | ret = count; |
547 | |
548 | exit: |
549 | return ret; |
550 | } |
551 | |
552 | static const struct file_operations fops_simulate_fw_crash = { |
553 | .read = ath11k_read_simulate_fw_crash, |
554 | .write = ath11k_write_simulate_fw_crash, |
555 | .open = simple_open, |
556 | .owner = THIS_MODULE, |
557 | .llseek = default_llseek, |
558 | }; |
559 | |
560 | static ssize_t ath11k_write_enable_extd_tx_stats(struct file *file, |
561 | const char __user *ubuf, |
562 | size_t count, loff_t *ppos) |
563 | { |
564 | struct ath11k *ar = file->private_data; |
565 | u32 filter; |
566 | int ret; |
567 | |
568 | if (kstrtouint_from_user(s: ubuf, count, base: 0, res: &filter)) |
569 | return -EINVAL; |
570 | |
571 | mutex_lock(&ar->conf_mutex); |
572 | |
573 | if (ar->state != ATH11K_STATE_ON) { |
574 | ret = -ENETDOWN; |
575 | goto out; |
576 | } |
577 | |
578 | if (filter == ar->debug.extd_tx_stats) { |
579 | ret = count; |
580 | goto out; |
581 | } |
582 | |
583 | ar->debug.extd_tx_stats = filter; |
584 | ret = count; |
585 | |
586 | out: |
587 | mutex_unlock(lock: &ar->conf_mutex); |
588 | return ret; |
589 | } |
590 | |
591 | static ssize_t ath11k_read_enable_extd_tx_stats(struct file *file, |
592 | char __user *ubuf, |
593 | size_t count, loff_t *ppos) |
594 | |
595 | { |
596 | char buf[32] = {0}; |
597 | struct ath11k *ar = file->private_data; |
598 | int len = 0; |
599 | |
600 | mutex_lock(&ar->conf_mutex); |
601 | len = scnprintf(buf, size: sizeof(buf) - len, fmt: "%08x\n" , |
602 | ar->debug.extd_tx_stats); |
603 | mutex_unlock(lock: &ar->conf_mutex); |
604 | |
605 | return simple_read_from_buffer(to: ubuf, count, ppos, from: buf, available: len); |
606 | } |
607 | |
608 | static const struct file_operations fops_extd_tx_stats = { |
609 | .read = ath11k_read_enable_extd_tx_stats, |
610 | .write = ath11k_write_enable_extd_tx_stats, |
611 | .open = simple_open |
612 | }; |
613 | |
614 | static ssize_t ath11k_write_extd_rx_stats(struct file *file, |
615 | const char __user *ubuf, |
616 | size_t count, loff_t *ppos) |
617 | { |
618 | struct ath11k *ar = file->private_data; |
619 | struct ath11k_base *ab = ar->ab; |
620 | struct htt_rx_ring_tlv_filter tlv_filter = {0}; |
621 | u32 enable, rx_filter = 0, ring_id; |
622 | int i; |
623 | int ret; |
624 | |
625 | if (kstrtouint_from_user(s: ubuf, count, base: 0, res: &enable)) |
626 | return -EINVAL; |
627 | |
628 | mutex_lock(&ar->conf_mutex); |
629 | |
630 | if (ar->state != ATH11K_STATE_ON) { |
631 | ret = -ENETDOWN; |
632 | goto exit; |
633 | } |
634 | |
635 | if (enable > 1) { |
636 | ret = -EINVAL; |
637 | goto exit; |
638 | } |
639 | |
640 | if (enable == ar->debug.extd_rx_stats) { |
641 | ret = count; |
642 | goto exit; |
643 | } |
644 | |
645 | if (test_bit(ATH11K_FLAG_MONITOR_STARTED, &ar->monitor_flags)) { |
646 | ar->debug.extd_rx_stats = enable; |
647 | ret = count; |
648 | goto exit; |
649 | } |
650 | |
651 | if (enable) { |
652 | rx_filter = HTT_RX_FILTER_TLV_FLAGS_MPDU_START; |
653 | rx_filter |= HTT_RX_FILTER_TLV_FLAGS_PPDU_START; |
654 | rx_filter |= HTT_RX_FILTER_TLV_FLAGS_PPDU_END; |
655 | rx_filter |= HTT_RX_FILTER_TLV_FLAGS_PPDU_END_USER_STATS; |
656 | rx_filter |= HTT_RX_FILTER_TLV_FLAGS_PPDU_END_USER_STATS_EXT; |
657 | rx_filter |= HTT_RX_FILTER_TLV_FLAGS_PPDU_END_STATUS_DONE; |
658 | |
659 | tlv_filter.rx_filter = rx_filter; |
660 | tlv_filter.pkt_filter_flags0 = HTT_RX_FP_MGMT_FILTER_FLAGS0; |
661 | tlv_filter.pkt_filter_flags1 = HTT_RX_FP_MGMT_FILTER_FLAGS1; |
662 | tlv_filter.pkt_filter_flags2 = HTT_RX_FP_CTRL_FILTER_FLASG2; |
663 | tlv_filter.pkt_filter_flags3 = HTT_RX_FP_CTRL_FILTER_FLASG3 | |
664 | HTT_RX_FP_DATA_FILTER_FLASG3; |
665 | } else { |
666 | tlv_filter = ath11k_mac_mon_status_filter_default; |
667 | } |
668 | |
669 | ar->debug.rx_filter = tlv_filter.rx_filter; |
670 | |
671 | for (i = 0; i < ab->hw_params.num_rxmda_per_pdev; i++) { |
672 | ring_id = ar->dp.rx_mon_status_refill_ring[i].refill_buf_ring.ring_id; |
673 | ret = ath11k_dp_tx_htt_rx_filter_setup(ab: ar->ab, ring_id, mac_id: ar->dp.mac_id, |
674 | ring_type: HAL_RXDMA_MONITOR_STATUS, |
675 | DP_RX_BUFFER_SIZE, tlv_filter: &tlv_filter); |
676 | |
677 | if (ret) { |
678 | ath11k_warn(ab: ar->ab, fmt: "failed to set rx filter for monitor status ring\n" ); |
679 | goto exit; |
680 | } |
681 | } |
682 | |
683 | ar->debug.extd_rx_stats = enable; |
684 | ret = count; |
685 | exit: |
686 | mutex_unlock(lock: &ar->conf_mutex); |
687 | return ret; |
688 | } |
689 | |
690 | static ssize_t ath11k_read_extd_rx_stats(struct file *file, |
691 | char __user *ubuf, |
692 | size_t count, loff_t *ppos) |
693 | { |
694 | struct ath11k *ar = file->private_data; |
695 | char buf[32]; |
696 | int len = 0; |
697 | |
698 | mutex_lock(&ar->conf_mutex); |
699 | len = scnprintf(buf, size: sizeof(buf) - len, fmt: "%d\n" , |
700 | ar->debug.extd_rx_stats); |
701 | mutex_unlock(lock: &ar->conf_mutex); |
702 | |
703 | return simple_read_from_buffer(to: ubuf, count, ppos, from: buf, available: len); |
704 | } |
705 | |
706 | static const struct file_operations fops_extd_rx_stats = { |
707 | .read = ath11k_read_extd_rx_stats, |
708 | .write = ath11k_write_extd_rx_stats, |
709 | .open = simple_open, |
710 | }; |
711 | |
712 | static int ath11k_fill_bp_stats(struct ath11k_base *ab, |
713 | struct ath11k_bp_stats *bp_stats, |
714 | char *buf, int len, int size) |
715 | { |
716 | lockdep_assert_held(&ab->base_lock); |
717 | |
718 | len += scnprintf(buf: buf + len, size: size - len, fmt: "count: %u\n" , |
719 | bp_stats->count); |
720 | len += scnprintf(buf: buf + len, size: size - len, fmt: "hp: %u\n" , |
721 | bp_stats->hp); |
722 | len += scnprintf(buf: buf + len, size: size - len, fmt: "tp: %u\n" , |
723 | bp_stats->tp); |
724 | len += scnprintf(buf: buf + len, size: size - len, fmt: "seen before: %ums\n\n" , |
725 | jiffies_to_msecs(j: jiffies - bp_stats->jiffies)); |
726 | return len; |
727 | } |
728 | |
729 | static ssize_t ath11k_debugfs_dump_soc_ring_bp_stats(struct ath11k_base *ab, |
730 | char *buf, int size) |
731 | { |
732 | struct ath11k_bp_stats *bp_stats; |
733 | bool stats_rxd = false; |
734 | u8 i, pdev_idx; |
735 | int len = 0; |
736 | |
737 | len += scnprintf(buf: buf + len, size: size - len, fmt: "\nBackpressure Stats\n" ); |
738 | len += scnprintf(buf: buf + len, size: size - len, fmt: "==================\n" ); |
739 | |
740 | spin_lock_bh(lock: &ab->base_lock); |
741 | for (i = 0; i < HTT_SW_UMAC_RING_IDX_MAX; i++) { |
742 | bp_stats = &ab->soc_stats.bp_stats.umac_ring_bp_stats[i]; |
743 | |
744 | if (!bp_stats->count) |
745 | continue; |
746 | |
747 | len += scnprintf(buf: buf + len, size: size - len, fmt: "Ring: %s\n" , |
748 | htt_bp_umac_ring[i]); |
749 | len = ath11k_fill_bp_stats(ab, bp_stats, buf, len, size); |
750 | stats_rxd = true; |
751 | } |
752 | |
753 | for (i = 0; i < HTT_SW_LMAC_RING_IDX_MAX; i++) { |
754 | for (pdev_idx = 0; pdev_idx < MAX_RADIOS; pdev_idx++) { |
755 | bp_stats = |
756 | &ab->soc_stats.bp_stats.lmac_ring_bp_stats[i][pdev_idx]; |
757 | |
758 | if (!bp_stats->count) |
759 | continue; |
760 | |
761 | len += scnprintf(buf: buf + len, size: size - len, fmt: "Ring: %s\n" , |
762 | htt_bp_lmac_ring[i]); |
763 | len += scnprintf(buf: buf + len, size: size - len, fmt: "pdev: %d\n" , |
764 | pdev_idx); |
765 | len = ath11k_fill_bp_stats(ab, bp_stats, buf, len, size); |
766 | stats_rxd = true; |
767 | } |
768 | } |
769 | spin_unlock_bh(lock: &ab->base_lock); |
770 | |
771 | if (!stats_rxd) |
772 | len += scnprintf(buf: buf + len, size: size - len, |
773 | fmt: "No Ring Backpressure stats received\n\n" ); |
774 | |
775 | return len; |
776 | } |
777 | |
778 | static ssize_t ath11k_debugfs_dump_soc_dp_stats(struct file *file, |
779 | char __user *user_buf, |
780 | size_t count, loff_t *ppos) |
781 | { |
782 | struct ath11k_base *ab = file->private_data; |
783 | struct ath11k_soc_dp_stats *soc_stats = &ab->soc_stats; |
784 | int len = 0, i, retval; |
785 | const int size = 4096; |
786 | static const char *rxdma_err[HAL_REO_ENTR_RING_RXDMA_ECODE_MAX] = { |
787 | "Overflow" , "MPDU len" , "FCS" , "Decrypt" , "TKIP MIC" , |
788 | "Unencrypt" , "MSDU len" , "MSDU limit" , "WiFi parse" , |
789 | "AMSDU parse" , "SA timeout" , "DA timeout" , |
790 | "Flow timeout" , "Flush req" }; |
791 | static const char *reo_err[HAL_REO_DEST_RING_ERROR_CODE_MAX] = { |
792 | "Desc addr zero" , "Desc inval" , "AMPDU in non BA" , |
793 | "Non BA dup" , "BA dup" , "Frame 2k jump" , "BAR 2k jump" , |
794 | "Frame OOR" , "BAR OOR" , "No BA session" , |
795 | "Frame SN equal SSN" , "PN check fail" , "2k err" , |
796 | "PN err" , "Desc blocked" }; |
797 | |
798 | char *buf; |
799 | |
800 | buf = kzalloc(size, GFP_KERNEL); |
801 | if (!buf) |
802 | return -ENOMEM; |
803 | |
804 | len += scnprintf(buf: buf + len, size: size - len, fmt: "SOC RX STATS:\n\n" ); |
805 | len += scnprintf(buf: buf + len, size: size - len, fmt: "err ring pkts: %u\n" , |
806 | soc_stats->err_ring_pkts); |
807 | len += scnprintf(buf: buf + len, size: size - len, fmt: "Invalid RBM: %u\n\n" , |
808 | soc_stats->invalid_rbm); |
809 | len += scnprintf(buf: buf + len, size: size - len, fmt: "RXDMA errors:\n" ); |
810 | for (i = 0; i < HAL_REO_ENTR_RING_RXDMA_ECODE_MAX; i++) |
811 | len += scnprintf(buf: buf + len, size: size - len, fmt: "%s: %u\n" , |
812 | rxdma_err[i], soc_stats->rxdma_error[i]); |
813 | |
814 | len += scnprintf(buf: buf + len, size: size - len, fmt: "\nREO errors:\n" ); |
815 | for (i = 0; i < HAL_REO_DEST_RING_ERROR_CODE_MAX; i++) |
816 | len += scnprintf(buf: buf + len, size: size - len, fmt: "%s: %u\n" , |
817 | reo_err[i], soc_stats->reo_error[i]); |
818 | |
819 | len += scnprintf(buf: buf + len, size: size - len, fmt: "\nHAL REO errors:\n" ); |
820 | len += scnprintf(buf: buf + len, size: size - len, |
821 | fmt: "ring0: %u\nring1: %u\nring2: %u\nring3: %u\n" , |
822 | soc_stats->hal_reo_error[0], |
823 | soc_stats->hal_reo_error[1], |
824 | soc_stats->hal_reo_error[2], |
825 | soc_stats->hal_reo_error[3]); |
826 | |
827 | len += scnprintf(buf: buf + len, size: size - len, fmt: "\nSOC TX STATS:\n" ); |
828 | len += scnprintf(buf: buf + len, size: size - len, fmt: "\nTCL Ring Full Failures:\n" ); |
829 | |
830 | for (i = 0; i < ab->hw_params.max_tx_ring; i++) |
831 | len += scnprintf(buf: buf + len, size: size - len, fmt: "ring%d: %u\n" , |
832 | i, soc_stats->tx_err.desc_na[i]); |
833 | |
834 | len += scnprintf(buf: buf + len, size: size - len, |
835 | fmt: "\nMisc Transmit Failures: %d\n" , |
836 | atomic_read(v: &soc_stats->tx_err.misc_fail)); |
837 | |
838 | len += ath11k_debugfs_dump_soc_ring_bp_stats(ab, buf: buf + len, size: size - len); |
839 | |
840 | if (len > size) |
841 | len = size; |
842 | retval = simple_read_from_buffer(to: user_buf, count, ppos, from: buf, available: len); |
843 | kfree(objp: buf); |
844 | |
845 | return retval; |
846 | } |
847 | |
848 | static const struct file_operations fops_soc_dp_stats = { |
849 | .read = ath11k_debugfs_dump_soc_dp_stats, |
850 | .open = simple_open, |
851 | .owner = THIS_MODULE, |
852 | .llseek = default_llseek, |
853 | }; |
854 | |
855 | static ssize_t ath11k_write_fw_dbglog(struct file *file, |
856 | const char __user *user_buf, |
857 | size_t count, loff_t *ppos) |
858 | { |
859 | struct ath11k *ar = file->private_data; |
860 | char buf[128] = {0}; |
861 | struct ath11k_fw_dbglog dbglog; |
862 | unsigned int param, mod_id_index, is_end; |
863 | u64 value; |
864 | int ret, num; |
865 | |
866 | ret = simple_write_to_buffer(to: buf, available: sizeof(buf) - 1, ppos, |
867 | from: user_buf, count); |
868 | if (ret <= 0) |
869 | return ret; |
870 | |
871 | num = sscanf(buf, "%u %llx %u %u" , ¶m, &value, &mod_id_index, &is_end); |
872 | |
873 | if (num < 2) |
874 | return -EINVAL; |
875 | |
876 | mutex_lock(&ar->conf_mutex); |
877 | if (param == WMI_DEBUG_LOG_PARAM_MOD_ENABLE_BITMAP || |
878 | param == WMI_DEBUG_LOG_PARAM_WOW_MOD_ENABLE_BITMAP) { |
879 | if (num != 4 || mod_id_index > (MAX_MODULE_ID_BITMAP_WORDS - 1)) { |
880 | ret = -EINVAL; |
881 | goto out; |
882 | } |
883 | ar->debug.module_id_bitmap[mod_id_index] = upper_32_bits(value); |
884 | if (!is_end) { |
885 | ret = count; |
886 | goto out; |
887 | } |
888 | } else { |
889 | if (num != 2) { |
890 | ret = -EINVAL; |
891 | goto out; |
892 | } |
893 | } |
894 | |
895 | dbglog.param = param; |
896 | dbglog.value = lower_32_bits(value); |
897 | ret = ath11k_wmi_fw_dbglog_cfg(ar, module_id_bitmap: ar->debug.module_id_bitmap, dbglog: &dbglog); |
898 | if (ret) { |
899 | ath11k_warn(ab: ar->ab, fmt: "fw dbglog config failed from debugfs: %d\n" , |
900 | ret); |
901 | goto out; |
902 | } |
903 | |
904 | ret = count; |
905 | |
906 | out: |
907 | mutex_unlock(lock: &ar->conf_mutex); |
908 | return ret; |
909 | } |
910 | |
911 | static const struct file_operations fops_fw_dbglog = { |
912 | .write = ath11k_write_fw_dbglog, |
913 | .open = simple_open, |
914 | .owner = THIS_MODULE, |
915 | .llseek = default_llseek, |
916 | }; |
917 | |
918 | static int ath11k_open_sram_dump(struct inode *inode, struct file *file) |
919 | { |
920 | struct ath11k_base *ab = inode->i_private; |
921 | u8 *buf; |
922 | u32 start, end; |
923 | int ret; |
924 | |
925 | start = ab->hw_params.sram_dump.start; |
926 | end = ab->hw_params.sram_dump.end; |
927 | |
928 | buf = vmalloc(size: end - start + 1); |
929 | if (!buf) |
930 | return -ENOMEM; |
931 | |
932 | ret = ath11k_hif_read(ab, buf, start, end); |
933 | if (ret) { |
934 | ath11k_warn(ab, fmt: "failed to dump sram: %d\n" , ret); |
935 | vfree(addr: buf); |
936 | return ret; |
937 | } |
938 | |
939 | file->private_data = buf; |
940 | return 0; |
941 | } |
942 | |
943 | static ssize_t ath11k_read_sram_dump(struct file *file, |
944 | char __user *user_buf, |
945 | size_t count, loff_t *ppos) |
946 | { |
947 | struct ath11k_base *ab = file->f_inode->i_private; |
948 | const char *buf = file->private_data; |
949 | int len; |
950 | u32 start, end; |
951 | |
952 | start = ab->hw_params.sram_dump.start; |
953 | end = ab->hw_params.sram_dump.end; |
954 | len = end - start + 1; |
955 | |
956 | return simple_read_from_buffer(to: user_buf, count, ppos, from: buf, available: len); |
957 | } |
958 | |
959 | static int ath11k_release_sram_dump(struct inode *inode, struct file *file) |
960 | { |
961 | vfree(addr: file->private_data); |
962 | file->private_data = NULL; |
963 | |
964 | return 0; |
965 | } |
966 | |
967 | static const struct file_operations fops_sram_dump = { |
968 | .open = ath11k_open_sram_dump, |
969 | .read = ath11k_read_sram_dump, |
970 | .release = ath11k_release_sram_dump, |
971 | .owner = THIS_MODULE, |
972 | .llseek = default_llseek, |
973 | }; |
974 | |
975 | int ath11k_debugfs_pdev_create(struct ath11k_base *ab) |
976 | { |
977 | if (test_bit(ATH11K_FLAG_REGISTERED, &ab->dev_flags)) |
978 | return 0; |
979 | |
980 | debugfs_create_file(name: "simulate_fw_crash" , mode: 0600, parent: ab->debugfs_soc, data: ab, |
981 | fops: &fops_simulate_fw_crash); |
982 | |
983 | debugfs_create_file(name: "soc_dp_stats" , mode: 0600, parent: ab->debugfs_soc, data: ab, |
984 | fops: &fops_soc_dp_stats); |
985 | |
986 | if (ab->hw_params.sram_dump.start != 0) |
987 | debugfs_create_file(name: "sram" , mode: 0400, parent: ab->debugfs_soc, data: ab, |
988 | fops: &fops_sram_dump); |
989 | |
990 | return 0; |
991 | } |
992 | |
993 | void ath11k_debugfs_pdev_destroy(struct ath11k_base *ab) |
994 | { |
995 | debugfs_remove_recursive(dentry: ab->debugfs_soc); |
996 | ab->debugfs_soc = NULL; |
997 | } |
998 | |
999 | int ath11k_debugfs_soc_create(struct ath11k_base *ab) |
1000 | { |
1001 | struct dentry *root; |
1002 | bool dput_needed; |
1003 | char name[64]; |
1004 | int ret; |
1005 | |
1006 | root = debugfs_lookup(name: "ath11k" , NULL); |
1007 | if (!root) { |
1008 | root = debugfs_create_dir(name: "ath11k" , NULL); |
1009 | if (IS_ERR_OR_NULL(ptr: root)) |
1010 | return PTR_ERR(ptr: root); |
1011 | |
1012 | dput_needed = false; |
1013 | } else { |
1014 | /* a dentry from lookup() needs dput() after we don't use it */ |
1015 | dput_needed = true; |
1016 | } |
1017 | |
1018 | scnprintf(buf: name, size: sizeof(name), fmt: "%s-%s" , ath11k_bus_str(bus: ab->hif.bus), |
1019 | dev_name(dev: ab->dev)); |
1020 | |
1021 | ab->debugfs_soc = debugfs_create_dir(name, parent: root); |
1022 | if (IS_ERR_OR_NULL(ptr: ab->debugfs_soc)) { |
1023 | ret = PTR_ERR(ptr: ab->debugfs_soc); |
1024 | goto out; |
1025 | } |
1026 | |
1027 | ret = 0; |
1028 | |
1029 | out: |
1030 | if (dput_needed) |
1031 | dput(root); |
1032 | |
1033 | return ret; |
1034 | } |
1035 | |
1036 | void ath11k_debugfs_soc_destroy(struct ath11k_base *ab) |
1037 | { |
1038 | debugfs_remove_recursive(dentry: ab->debugfs_soc); |
1039 | ab->debugfs_soc = NULL; |
1040 | |
1041 | /* We are not removing ath11k directory on purpose, even if it |
1042 | * would be empty. This simplifies the directory handling and it's |
1043 | * a minor cosmetic issue to leave an empty ath11k directory to |
1044 | * debugfs. |
1045 | */ |
1046 | } |
1047 | EXPORT_SYMBOL(ath11k_debugfs_soc_destroy); |
1048 | |
1049 | void ath11k_debugfs_fw_stats_init(struct ath11k *ar) |
1050 | { |
1051 | struct dentry *fwstats_dir = debugfs_create_dir(name: "fw_stats" , |
1052 | parent: ar->debug.debugfs_pdev); |
1053 | |
1054 | ar->fw_stats.debugfs_fwstats = fwstats_dir; |
1055 | |
1056 | /* all stats debugfs files created are under "fw_stats" directory |
1057 | * created per PDEV |
1058 | */ |
1059 | debugfs_create_file(name: "pdev_stats" , mode: 0600, parent: fwstats_dir, data: ar, |
1060 | fops: &fops_pdev_stats); |
1061 | debugfs_create_file(name: "vdev_stats" , mode: 0600, parent: fwstats_dir, data: ar, |
1062 | fops: &fops_vdev_stats); |
1063 | debugfs_create_file(name: "beacon_stats" , mode: 0600, parent: fwstats_dir, data: ar, |
1064 | fops: &fops_bcn_stats); |
1065 | } |
1066 | |
1067 | static ssize_t ath11k_write_pktlog_filter(struct file *file, |
1068 | const char __user *ubuf, |
1069 | size_t count, loff_t *ppos) |
1070 | { |
1071 | struct ath11k *ar = file->private_data; |
1072 | struct ath11k_base *ab = ar->ab; |
1073 | struct htt_rx_ring_tlv_filter tlv_filter = {0}; |
1074 | u32 rx_filter = 0, ring_id, filter, mode; |
1075 | u8 buf[128] = {0}; |
1076 | int i, ret, rx_buf_sz = 0; |
1077 | ssize_t rc; |
1078 | |
1079 | mutex_lock(&ar->conf_mutex); |
1080 | if (ar->state != ATH11K_STATE_ON) { |
1081 | ret = -ENETDOWN; |
1082 | goto out; |
1083 | } |
1084 | |
1085 | rc = simple_write_to_buffer(to: buf, available: sizeof(buf) - 1, ppos, from: ubuf, count); |
1086 | if (rc < 0) { |
1087 | ret = rc; |
1088 | goto out; |
1089 | } |
1090 | buf[rc] = '\0'; |
1091 | |
1092 | ret = sscanf(buf, "0x%x %u" , &filter, &mode); |
1093 | if (ret != 2) { |
1094 | ret = -EINVAL; |
1095 | goto out; |
1096 | } |
1097 | |
1098 | if (filter) { |
1099 | ret = ath11k_wmi_pdev_pktlog_enable(ar, pktlog_filter: filter); |
1100 | if (ret) { |
1101 | ath11k_warn(ab: ar->ab, |
1102 | fmt: "failed to enable pktlog filter %x: %d\n" , |
1103 | ar->debug.pktlog_filter, ret); |
1104 | goto out; |
1105 | } |
1106 | } else { |
1107 | ret = ath11k_wmi_pdev_pktlog_disable(ar); |
1108 | if (ret) { |
1109 | ath11k_warn(ab: ar->ab, fmt: "failed to disable pktlog: %d\n" , ret); |
1110 | goto out; |
1111 | } |
1112 | } |
1113 | |
1114 | /* Clear rx filter set for monitor mode and rx status */ |
1115 | for (i = 0; i < ab->hw_params.num_rxmda_per_pdev; i++) { |
1116 | ring_id = ar->dp.rx_mon_status_refill_ring[i].refill_buf_ring.ring_id; |
1117 | ret = ath11k_dp_tx_htt_rx_filter_setup(ab: ar->ab, ring_id, mac_id: ar->dp.mac_id, |
1118 | ring_type: HAL_RXDMA_MONITOR_STATUS, |
1119 | rx_buf_size: rx_buf_sz, tlv_filter: &tlv_filter); |
1120 | if (ret) { |
1121 | ath11k_warn(ab: ar->ab, fmt: "failed to set rx filter for monitor status ring\n" ); |
1122 | goto out; |
1123 | } |
1124 | } |
1125 | #define HTT_RX_FILTER_TLV_LITE_MODE \ |
1126 | (HTT_RX_FILTER_TLV_FLAGS_PPDU_START | \ |
1127 | HTT_RX_FILTER_TLV_FLAGS_PPDU_END | \ |
1128 | HTT_RX_FILTER_TLV_FLAGS_PPDU_END_USER_STATS | \ |
1129 | HTT_RX_FILTER_TLV_FLAGS_PPDU_END_USER_STATS_EXT | \ |
1130 | HTT_RX_FILTER_TLV_FLAGS_PPDU_END_STATUS_DONE | \ |
1131 | HTT_RX_FILTER_TLV_FLAGS_MPDU_START) |
1132 | |
1133 | if (mode == ATH11K_PKTLOG_MODE_FULL) { |
1134 | rx_filter = HTT_RX_FILTER_TLV_LITE_MODE | |
1135 | HTT_RX_FILTER_TLV_FLAGS_MSDU_START | |
1136 | HTT_RX_FILTER_TLV_FLAGS_MSDU_END | |
1137 | HTT_RX_FILTER_TLV_FLAGS_MPDU_END | |
1138 | HTT_RX_FILTER_TLV_FLAGS_PACKET_HEADER | |
1139 | HTT_RX_FILTER_TLV_FLAGS_ATTENTION; |
1140 | rx_buf_sz = DP_RX_BUFFER_SIZE; |
1141 | } else if (mode == ATH11K_PKTLOG_MODE_LITE) { |
1142 | ret = ath11k_dp_tx_htt_h2t_ppdu_stats_req(ar, |
1143 | HTT_PPDU_STATS_TAG_PKTLOG); |
1144 | if (ret) { |
1145 | ath11k_err(ab: ar->ab, fmt: "failed to enable pktlog lite: %d\n" , ret); |
1146 | goto out; |
1147 | } |
1148 | |
1149 | rx_filter = HTT_RX_FILTER_TLV_LITE_MODE; |
1150 | rx_buf_sz = DP_RX_BUFFER_SIZE_LITE; |
1151 | } else { |
1152 | rx_buf_sz = DP_RX_BUFFER_SIZE; |
1153 | tlv_filter = ath11k_mac_mon_status_filter_default; |
1154 | rx_filter = tlv_filter.rx_filter; |
1155 | |
1156 | ret = ath11k_dp_tx_htt_h2t_ppdu_stats_req(ar, |
1157 | HTT_PPDU_STATS_TAG_DEFAULT); |
1158 | if (ret) { |
1159 | ath11k_err(ab: ar->ab, fmt: "failed to send htt ppdu stats req: %d\n" , |
1160 | ret); |
1161 | goto out; |
1162 | } |
1163 | } |
1164 | |
1165 | tlv_filter.rx_filter = rx_filter; |
1166 | if (rx_filter) { |
1167 | tlv_filter.pkt_filter_flags0 = HTT_RX_FP_MGMT_FILTER_FLAGS0; |
1168 | tlv_filter.pkt_filter_flags1 = HTT_RX_FP_MGMT_FILTER_FLAGS1; |
1169 | tlv_filter.pkt_filter_flags2 = HTT_RX_FP_CTRL_FILTER_FLASG2; |
1170 | tlv_filter.pkt_filter_flags3 = HTT_RX_FP_CTRL_FILTER_FLASG3 | |
1171 | HTT_RX_FP_DATA_FILTER_FLASG3; |
1172 | } |
1173 | |
1174 | for (i = 0; i < ab->hw_params.num_rxmda_per_pdev; i++) { |
1175 | ring_id = ar->dp.rx_mon_status_refill_ring[i].refill_buf_ring.ring_id; |
1176 | ret = ath11k_dp_tx_htt_rx_filter_setup(ab, ring_id, |
1177 | mac_id: ar->dp.mac_id + i, |
1178 | ring_type: HAL_RXDMA_MONITOR_STATUS, |
1179 | rx_buf_size: rx_buf_sz, tlv_filter: &tlv_filter); |
1180 | |
1181 | if (ret) { |
1182 | ath11k_warn(ab, fmt: "failed to set rx filter for monitor status ring\n" ); |
1183 | goto out; |
1184 | } |
1185 | } |
1186 | |
1187 | ath11k_info(ab, fmt: "pktlog mode %s\n" , |
1188 | ((mode == ATH11K_PKTLOG_MODE_FULL) ? "full" : "lite" )); |
1189 | |
1190 | ar->debug.pktlog_filter = filter; |
1191 | ar->debug.pktlog_mode = mode; |
1192 | ret = count; |
1193 | |
1194 | out: |
1195 | mutex_unlock(lock: &ar->conf_mutex); |
1196 | return ret; |
1197 | } |
1198 | |
1199 | static ssize_t ath11k_read_pktlog_filter(struct file *file, |
1200 | char __user *ubuf, |
1201 | size_t count, loff_t *ppos) |
1202 | |
1203 | { |
1204 | char buf[32] = {0}; |
1205 | struct ath11k *ar = file->private_data; |
1206 | int len = 0; |
1207 | |
1208 | mutex_lock(&ar->conf_mutex); |
1209 | len = scnprintf(buf, size: sizeof(buf) - len, fmt: "%08x %08x\n" , |
1210 | ar->debug.pktlog_filter, |
1211 | ar->debug.pktlog_mode); |
1212 | mutex_unlock(lock: &ar->conf_mutex); |
1213 | |
1214 | return simple_read_from_buffer(to: ubuf, count, ppos, from: buf, available: len); |
1215 | } |
1216 | |
1217 | static const struct file_operations fops_pktlog_filter = { |
1218 | .read = ath11k_read_pktlog_filter, |
1219 | .write = ath11k_write_pktlog_filter, |
1220 | .open = simple_open |
1221 | }; |
1222 | |
1223 | static ssize_t ath11k_write_simulate_radar(struct file *file, |
1224 | const char __user *user_buf, |
1225 | size_t count, loff_t *ppos) |
1226 | { |
1227 | struct ath11k *ar = file->private_data; |
1228 | int ret; |
1229 | |
1230 | ret = ath11k_wmi_simulate_radar(ar); |
1231 | if (ret) |
1232 | return ret; |
1233 | |
1234 | return count; |
1235 | } |
1236 | |
1237 | static const struct file_operations fops_simulate_radar = { |
1238 | .write = ath11k_write_simulate_radar, |
1239 | .open = simple_open |
1240 | }; |
1241 | |
1242 | static ssize_t ath11k_debug_dump_dbr_entries(struct file *file, |
1243 | char __user *user_buf, |
1244 | size_t count, loff_t *ppos) |
1245 | { |
1246 | struct ath11k_dbg_dbr_data *dbr_dbg_data = file->private_data; |
1247 | static const char * const event_id_to_string[] = {"empty" , "Rx" , "Replenish" }; |
1248 | int size = ATH11K_DEBUG_DBR_ENTRIES_MAX * 100; |
1249 | char *buf; |
1250 | int i, ret; |
1251 | int len = 0; |
1252 | |
1253 | buf = kzalloc(size, GFP_KERNEL); |
1254 | if (!buf) |
1255 | return -ENOMEM; |
1256 | |
1257 | len += scnprintf(buf: buf + len, size: size - len, |
1258 | fmt: "-----------------------------------------\n" ); |
1259 | len += scnprintf(buf: buf + len, size: size - len, |
1260 | fmt: "| idx | hp | tp | timestamp | event |\n" ); |
1261 | len += scnprintf(buf: buf + len, size: size - len, |
1262 | fmt: "-----------------------------------------\n" ); |
1263 | |
1264 | spin_lock_bh(lock: &dbr_dbg_data->lock); |
1265 | |
1266 | for (i = 0; i < dbr_dbg_data->num_ring_debug_entries; i++) { |
1267 | len += scnprintf(buf: buf + len, size: size - len, |
1268 | fmt: "|%4u|%8u|%8u|%11llu|%8s|\n" , i, |
1269 | dbr_dbg_data->entries[i].hp, |
1270 | dbr_dbg_data->entries[i].tp, |
1271 | dbr_dbg_data->entries[i].timestamp, |
1272 | event_id_to_string[dbr_dbg_data->entries[i].event]); |
1273 | } |
1274 | |
1275 | spin_unlock_bh(lock: &dbr_dbg_data->lock); |
1276 | |
1277 | ret = simple_read_from_buffer(to: user_buf, count, ppos, from: buf, available: len); |
1278 | kfree(objp: buf); |
1279 | |
1280 | return ret; |
1281 | } |
1282 | |
1283 | static const struct file_operations fops_debug_dump_dbr_entries = { |
1284 | .read = ath11k_debug_dump_dbr_entries, |
1285 | .open = simple_open, |
1286 | .owner = THIS_MODULE, |
1287 | .llseek = default_llseek, |
1288 | }; |
1289 | |
1290 | static void ath11k_debugfs_dbr_dbg_destroy(struct ath11k *ar, int dbr_id) |
1291 | { |
1292 | struct ath11k_debug_dbr *dbr_debug; |
1293 | struct ath11k_dbg_dbr_data *dbr_dbg_data; |
1294 | |
1295 | if (!ar->debug.dbr_debug[dbr_id]) |
1296 | return; |
1297 | |
1298 | dbr_debug = ar->debug.dbr_debug[dbr_id]; |
1299 | dbr_dbg_data = &dbr_debug->dbr_dbg_data; |
1300 | |
1301 | debugfs_remove_recursive(dentry: dbr_debug->dbr_debugfs); |
1302 | kfree(objp: dbr_dbg_data->entries); |
1303 | kfree(objp: dbr_debug); |
1304 | ar->debug.dbr_debug[dbr_id] = NULL; |
1305 | } |
1306 | |
1307 | static int ath11k_debugfs_dbr_dbg_init(struct ath11k *ar, int dbr_id) |
1308 | { |
1309 | struct ath11k_debug_dbr *dbr_debug; |
1310 | struct ath11k_dbg_dbr_data *dbr_dbg_data; |
1311 | static const char * const dbr_id_to_str[] = {"spectral" , "CFR" }; |
1312 | |
1313 | if (ar->debug.dbr_debug[dbr_id]) |
1314 | return 0; |
1315 | |
1316 | ar->debug.dbr_debug[dbr_id] = kzalloc(size: sizeof(*dbr_debug), |
1317 | GFP_KERNEL); |
1318 | |
1319 | if (!ar->debug.dbr_debug[dbr_id]) |
1320 | return -ENOMEM; |
1321 | |
1322 | dbr_debug = ar->debug.dbr_debug[dbr_id]; |
1323 | dbr_dbg_data = &dbr_debug->dbr_dbg_data; |
1324 | |
1325 | if (dbr_debug->dbr_debugfs) |
1326 | return 0; |
1327 | |
1328 | dbr_debug->dbr_debugfs = debugfs_create_dir(name: dbr_id_to_str[dbr_id], |
1329 | parent: ar->debug.debugfs_pdev); |
1330 | if (IS_ERR_OR_NULL(ptr: dbr_debug->dbr_debugfs)) { |
1331 | if (IS_ERR(ptr: dbr_debug->dbr_debugfs)) |
1332 | return PTR_ERR(ptr: dbr_debug->dbr_debugfs); |
1333 | return -ENOMEM; |
1334 | } |
1335 | |
1336 | dbr_debug->dbr_debug_enabled = true; |
1337 | dbr_dbg_data->num_ring_debug_entries = ATH11K_DEBUG_DBR_ENTRIES_MAX; |
1338 | dbr_dbg_data->dbr_debug_idx = 0; |
1339 | dbr_dbg_data->entries = kcalloc(ATH11K_DEBUG_DBR_ENTRIES_MAX, |
1340 | size: sizeof(struct ath11k_dbg_dbr_entry), |
1341 | GFP_KERNEL); |
1342 | if (!dbr_dbg_data->entries) |
1343 | return -ENOMEM; |
1344 | |
1345 | spin_lock_init(&dbr_dbg_data->lock); |
1346 | |
1347 | debugfs_create_file(name: "dump_dbr_debug" , mode: 0444, parent: dbr_debug->dbr_debugfs, |
1348 | data: dbr_dbg_data, fops: &fops_debug_dump_dbr_entries); |
1349 | |
1350 | return 0; |
1351 | } |
1352 | |
1353 | static ssize_t ath11k_debugfs_write_enable_dbr_dbg(struct file *file, |
1354 | const char __user *ubuf, |
1355 | size_t count, loff_t *ppos) |
1356 | { |
1357 | struct ath11k *ar = file->private_data; |
1358 | char buf[32] = {0}; |
1359 | u32 dbr_id, enable; |
1360 | int ret; |
1361 | |
1362 | mutex_lock(&ar->conf_mutex); |
1363 | |
1364 | if (ar->state != ATH11K_STATE_ON) { |
1365 | ret = -ENETDOWN; |
1366 | goto out; |
1367 | } |
1368 | |
1369 | ret = simple_write_to_buffer(to: buf, available: sizeof(buf) - 1, ppos, from: ubuf, count); |
1370 | if (ret < 0) |
1371 | goto out; |
1372 | |
1373 | buf[ret] = '\0'; |
1374 | ret = sscanf(buf, "%u %u" , &dbr_id, &enable); |
1375 | if (ret != 2 || dbr_id > 1 || enable > 1) { |
1376 | ret = -EINVAL; |
1377 | ath11k_warn(ab: ar->ab, fmt: "usage: echo <dbr_id> <val> dbr_id:0-Spectral 1-CFR val:0-disable 1-enable\n" ); |
1378 | goto out; |
1379 | } |
1380 | |
1381 | if (enable) { |
1382 | ret = ath11k_debugfs_dbr_dbg_init(ar, dbr_id); |
1383 | if (ret) { |
1384 | ath11k_warn(ab: ar->ab, fmt: "db ring module debugfs init failed: %d\n" , |
1385 | ret); |
1386 | goto out; |
1387 | } |
1388 | } else { |
1389 | ath11k_debugfs_dbr_dbg_destroy(ar, dbr_id); |
1390 | } |
1391 | |
1392 | ret = count; |
1393 | out: |
1394 | mutex_unlock(lock: &ar->conf_mutex); |
1395 | return ret; |
1396 | } |
1397 | |
1398 | static const struct file_operations fops_dbr_debug = { |
1399 | .write = ath11k_debugfs_write_enable_dbr_dbg, |
1400 | .open = simple_open, |
1401 | .owner = THIS_MODULE, |
1402 | .llseek = default_llseek, |
1403 | }; |
1404 | |
1405 | static ssize_t ath11k_write_ps_timekeeper_enable(struct file *file, |
1406 | const char __user *user_buf, |
1407 | size_t count, loff_t *ppos) |
1408 | { |
1409 | struct ath11k *ar = file->private_data; |
1410 | ssize_t ret; |
1411 | u8 ps_timekeeper_enable; |
1412 | |
1413 | if (kstrtou8_from_user(s: user_buf, count, base: 0, res: &ps_timekeeper_enable)) |
1414 | return -EINVAL; |
1415 | |
1416 | mutex_lock(&ar->conf_mutex); |
1417 | |
1418 | if (ar->state != ATH11K_STATE_ON) { |
1419 | ret = -ENETDOWN; |
1420 | goto exit; |
1421 | } |
1422 | |
1423 | if (!ar->ps_state_enable) { |
1424 | ret = -EINVAL; |
1425 | goto exit; |
1426 | } |
1427 | |
1428 | ar->ps_timekeeper_enable = !!ps_timekeeper_enable; |
1429 | ret = count; |
1430 | exit: |
1431 | mutex_unlock(lock: &ar->conf_mutex); |
1432 | |
1433 | return ret; |
1434 | } |
1435 | |
1436 | static ssize_t ath11k_read_ps_timekeeper_enable(struct file *file, |
1437 | char __user *user_buf, |
1438 | size_t count, loff_t *ppos) |
1439 | { |
1440 | struct ath11k *ar = file->private_data; |
1441 | char buf[32]; |
1442 | int len; |
1443 | |
1444 | mutex_lock(&ar->conf_mutex); |
1445 | len = scnprintf(buf, size: sizeof(buf), fmt: "%d\n" , ar->ps_timekeeper_enable); |
1446 | mutex_unlock(lock: &ar->conf_mutex); |
1447 | |
1448 | return simple_read_from_buffer(to: user_buf, count, ppos, from: buf, available: len); |
1449 | } |
1450 | |
1451 | static const struct file_operations fops_ps_timekeeper_enable = { |
1452 | .read = ath11k_read_ps_timekeeper_enable, |
1453 | .write = ath11k_write_ps_timekeeper_enable, |
1454 | .open = simple_open, |
1455 | .owner = THIS_MODULE, |
1456 | .llseek = default_llseek, |
1457 | }; |
1458 | |
1459 | static void ath11k_reset_peer_ps_duration(void *data, |
1460 | struct ieee80211_sta *sta) |
1461 | { |
1462 | struct ath11k *ar = data; |
1463 | struct ath11k_sta *arsta = ath11k_sta_to_arsta(sta); |
1464 | |
1465 | spin_lock_bh(lock: &ar->data_lock); |
1466 | arsta->ps_total_duration = 0; |
1467 | spin_unlock_bh(lock: &ar->data_lock); |
1468 | } |
1469 | |
1470 | static ssize_t ath11k_write_reset_ps_duration(struct file *file, |
1471 | const char __user *user_buf, |
1472 | size_t count, loff_t *ppos) |
1473 | { |
1474 | struct ath11k *ar = file->private_data; |
1475 | int ret; |
1476 | u8 reset_ps_duration; |
1477 | |
1478 | if (kstrtou8_from_user(s: user_buf, count, base: 0, res: &reset_ps_duration)) |
1479 | return -EINVAL; |
1480 | |
1481 | mutex_lock(&ar->conf_mutex); |
1482 | |
1483 | if (ar->state != ATH11K_STATE_ON) { |
1484 | ret = -ENETDOWN; |
1485 | goto exit; |
1486 | } |
1487 | |
1488 | if (!ar->ps_state_enable) { |
1489 | ret = -EINVAL; |
1490 | goto exit; |
1491 | } |
1492 | |
1493 | ieee80211_iterate_stations_atomic(hw: ar->hw, |
1494 | iterator: ath11k_reset_peer_ps_duration, |
1495 | data: ar); |
1496 | |
1497 | ret = count; |
1498 | exit: |
1499 | mutex_unlock(lock: &ar->conf_mutex); |
1500 | return ret; |
1501 | } |
1502 | |
1503 | static const struct file_operations fops_reset_ps_duration = { |
1504 | .write = ath11k_write_reset_ps_duration, |
1505 | .open = simple_open, |
1506 | .owner = THIS_MODULE, |
1507 | .llseek = default_llseek, |
1508 | }; |
1509 | |
1510 | static void ath11k_peer_ps_state_disable(void *data, |
1511 | struct ieee80211_sta *sta) |
1512 | { |
1513 | struct ath11k *ar = data; |
1514 | struct ath11k_sta *arsta = ath11k_sta_to_arsta(sta); |
1515 | |
1516 | spin_lock_bh(lock: &ar->data_lock); |
1517 | arsta->peer_ps_state = WMI_PEER_PS_STATE_DISABLED; |
1518 | arsta->ps_start_time = 0; |
1519 | arsta->ps_total_duration = 0; |
1520 | spin_unlock_bh(lock: &ar->data_lock); |
1521 | } |
1522 | |
1523 | static ssize_t ath11k_write_ps_state_enable(struct file *file, |
1524 | const char __user *user_buf, |
1525 | size_t count, loff_t *ppos) |
1526 | { |
1527 | struct ath11k *ar = file->private_data; |
1528 | struct ath11k_pdev *pdev = ar->pdev; |
1529 | int ret; |
1530 | u32 param; |
1531 | u8 ps_state_enable; |
1532 | |
1533 | if (kstrtou8_from_user(s: user_buf, count, base: 0, res: &ps_state_enable)) |
1534 | return -EINVAL; |
1535 | |
1536 | mutex_lock(&ar->conf_mutex); |
1537 | |
1538 | ps_state_enable = !!ps_state_enable; |
1539 | |
1540 | if (ar->ps_state_enable == ps_state_enable) { |
1541 | ret = count; |
1542 | goto exit; |
1543 | } |
1544 | |
1545 | param = WMI_PDEV_PEER_STA_PS_STATECHG_ENABLE; |
1546 | ret = ath11k_wmi_pdev_set_param(ar, param_id: param, param_value: ps_state_enable, pdev_id: pdev->pdev_id); |
1547 | if (ret) { |
1548 | ath11k_warn(ab: ar->ab, fmt: "failed to enable ps_state_enable: %d\n" , |
1549 | ret); |
1550 | goto exit; |
1551 | } |
1552 | ar->ps_state_enable = ps_state_enable; |
1553 | |
1554 | if (!ar->ps_state_enable) { |
1555 | ar->ps_timekeeper_enable = false; |
1556 | ieee80211_iterate_stations_atomic(hw: ar->hw, |
1557 | iterator: ath11k_peer_ps_state_disable, |
1558 | data: ar); |
1559 | } |
1560 | |
1561 | ret = count; |
1562 | |
1563 | exit: |
1564 | mutex_unlock(lock: &ar->conf_mutex); |
1565 | |
1566 | return ret; |
1567 | } |
1568 | |
1569 | static ssize_t ath11k_read_ps_state_enable(struct file *file, |
1570 | char __user *user_buf, |
1571 | size_t count, loff_t *ppos) |
1572 | { |
1573 | struct ath11k *ar = file->private_data; |
1574 | char buf[32]; |
1575 | int len; |
1576 | |
1577 | mutex_lock(&ar->conf_mutex); |
1578 | len = scnprintf(buf, size: sizeof(buf), fmt: "%d\n" , ar->ps_state_enable); |
1579 | mutex_unlock(lock: &ar->conf_mutex); |
1580 | |
1581 | return simple_read_from_buffer(to: user_buf, count, ppos, from: buf, available: len); |
1582 | } |
1583 | |
1584 | static const struct file_operations fops_ps_state_enable = { |
1585 | .read = ath11k_read_ps_state_enable, |
1586 | .write = ath11k_write_ps_state_enable, |
1587 | .open = simple_open, |
1588 | .owner = THIS_MODULE, |
1589 | .llseek = default_llseek, |
1590 | }; |
1591 | |
1592 | int ath11k_debugfs_register(struct ath11k *ar) |
1593 | { |
1594 | struct ath11k_base *ab = ar->ab; |
1595 | char pdev_name[10]; |
1596 | char buf[100] = {0}; |
1597 | |
1598 | snprintf(buf: pdev_name, size: sizeof(pdev_name), fmt: "%s%u" , "mac" , ar->pdev_idx); |
1599 | |
1600 | ar->debug.debugfs_pdev = debugfs_create_dir(name: pdev_name, parent: ab->debugfs_soc); |
1601 | if (IS_ERR(ptr: ar->debug.debugfs_pdev)) |
1602 | return PTR_ERR(ptr: ar->debug.debugfs_pdev); |
1603 | |
1604 | /* Create a symlink under ieee80211/phy* */ |
1605 | snprintf(buf, size: 100, fmt: "../../ath11k/%pd2" , ar->debug.debugfs_pdev); |
1606 | debugfs_create_symlink(name: "ath11k" , parent: ar->hw->wiphy->debugfsdir, dest: buf); |
1607 | |
1608 | ath11k_debugfs_htt_stats_init(ar); |
1609 | |
1610 | ath11k_debugfs_fw_stats_init(ar); |
1611 | |
1612 | debugfs_create_file(name: "ext_tx_stats" , mode: 0644, |
1613 | parent: ar->debug.debugfs_pdev, data: ar, |
1614 | fops: &fops_extd_tx_stats); |
1615 | debugfs_create_file(name: "ext_rx_stats" , mode: 0644, |
1616 | parent: ar->debug.debugfs_pdev, data: ar, |
1617 | fops: &fops_extd_rx_stats); |
1618 | debugfs_create_file(name: "pktlog_filter" , mode: 0644, |
1619 | parent: ar->debug.debugfs_pdev, data: ar, |
1620 | fops: &fops_pktlog_filter); |
1621 | debugfs_create_file(name: "fw_dbglog_config" , mode: 0600, |
1622 | parent: ar->debug.debugfs_pdev, data: ar, |
1623 | fops: &fops_fw_dbglog); |
1624 | |
1625 | if (ar->hw->wiphy->bands[NL80211_BAND_5GHZ]) { |
1626 | debugfs_create_file(name: "dfs_simulate_radar" , mode: 0200, |
1627 | parent: ar->debug.debugfs_pdev, data: ar, |
1628 | fops: &fops_simulate_radar); |
1629 | debugfs_create_bool(name: "dfs_block_radar_events" , mode: 0200, |
1630 | parent: ar->debug.debugfs_pdev, |
1631 | value: &ar->dfs_block_radar_events); |
1632 | } |
1633 | |
1634 | if (ab->hw_params.dbr_debug_support) |
1635 | debugfs_create_file(name: "enable_dbr_debug" , mode: 0200, parent: ar->debug.debugfs_pdev, |
1636 | data: ar, fops: &fops_dbr_debug); |
1637 | |
1638 | debugfs_create_file(name: "ps_state_enable" , mode: 0600, parent: ar->debug.debugfs_pdev, data: ar, |
1639 | fops: &fops_ps_state_enable); |
1640 | |
1641 | if (test_bit(WMI_TLV_SERVICE_PEER_POWER_SAVE_DURATION_SUPPORT, |
1642 | ar->ab->wmi_ab.svc_map)) { |
1643 | debugfs_create_file(name: "ps_timekeeper_enable" , mode: 0600, |
1644 | parent: ar->debug.debugfs_pdev, data: ar, |
1645 | fops: &fops_ps_timekeeper_enable); |
1646 | |
1647 | debugfs_create_file(name: "reset_ps_duration" , mode: 0200, |
1648 | parent: ar->debug.debugfs_pdev, data: ar, |
1649 | fops: &fops_reset_ps_duration); |
1650 | } |
1651 | |
1652 | return 0; |
1653 | } |
1654 | |
1655 | void ath11k_debugfs_unregister(struct ath11k *ar) |
1656 | { |
1657 | struct ath11k_debug_dbr *dbr_debug; |
1658 | struct ath11k_dbg_dbr_data *dbr_dbg_data; |
1659 | int i; |
1660 | |
1661 | for (i = 0; i < WMI_DIRECT_BUF_MAX; i++) { |
1662 | dbr_debug = ar->debug.dbr_debug[i]; |
1663 | if (!dbr_debug) |
1664 | continue; |
1665 | |
1666 | dbr_dbg_data = &dbr_debug->dbr_dbg_data; |
1667 | kfree(objp: dbr_dbg_data->entries); |
1668 | debugfs_remove_recursive(dentry: dbr_debug->dbr_debugfs); |
1669 | kfree(objp: dbr_debug); |
1670 | ar->debug.dbr_debug[i] = NULL; |
1671 | } |
1672 | } |
1673 | |
1674 | static ssize_t ath11k_write_twt_add_dialog(struct file *file, |
1675 | const char __user *ubuf, |
1676 | size_t count, loff_t *ppos) |
1677 | { |
1678 | struct ath11k_vif *arvif = file->private_data; |
1679 | struct wmi_twt_add_dialog_params params = { 0 }; |
1680 | struct wmi_twt_enable_params twt_params = {0}; |
1681 | struct ath11k *ar = arvif->ar; |
1682 | u8 buf[128] = {0}; |
1683 | int ret; |
1684 | |
1685 | if (ar->twt_enabled == 0) { |
1686 | ath11k_err(ab: ar->ab, fmt: "twt support is not enabled\n" ); |
1687 | return -EOPNOTSUPP; |
1688 | } |
1689 | |
1690 | ret = simple_write_to_buffer(to: buf, available: sizeof(buf) - 1, ppos, from: ubuf, count); |
1691 | if (ret < 0) |
1692 | return ret; |
1693 | |
1694 | buf[ret] = '\0'; |
1695 | ret = sscanf(buf, |
1696 | "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx %u %u %u %u %u %hhu %hhu %hhu %hhu %hhu" , |
1697 | ¶ms.peer_macaddr[0], |
1698 | ¶ms.peer_macaddr[1], |
1699 | ¶ms.peer_macaddr[2], |
1700 | ¶ms.peer_macaddr[3], |
1701 | ¶ms.peer_macaddr[4], |
1702 | ¶ms.peer_macaddr[5], |
1703 | ¶ms.dialog_id, |
1704 | ¶ms.wake_intvl_us, |
1705 | ¶ms.wake_intvl_mantis, |
1706 | ¶ms.wake_dura_us, |
1707 | ¶ms.sp_offset_us, |
1708 | ¶ms.twt_cmd, |
1709 | ¶ms.flag_bcast, |
1710 | ¶ms.flag_trigger, |
1711 | ¶ms.flag_flow_type, |
1712 | ¶ms.flag_protection); |
1713 | if (ret != 16) |
1714 | return -EINVAL; |
1715 | |
1716 | /* In the case of station vif, TWT is entirely handled by |
1717 | * the firmware based on the input parameters in the TWT enable |
1718 | * WMI command that is sent to the target during assoc. |
1719 | * For manually testing the TWT feature, we need to first disable |
1720 | * TWT and send enable command again with TWT input parameter |
1721 | * sta_cong_timer_ms set to 0. |
1722 | */ |
1723 | if (arvif->vif->type == NL80211_IFTYPE_STATION) { |
1724 | ath11k_wmi_send_twt_disable_cmd(ar, pdev_id: ar->pdev->pdev_id); |
1725 | |
1726 | ath11k_wmi_fill_default_twt_params(twt_params: &twt_params); |
1727 | twt_params.sta_cong_timer_ms = 0; |
1728 | |
1729 | ath11k_wmi_send_twt_enable_cmd(ar, pdev_id: ar->pdev->pdev_id, params: &twt_params); |
1730 | } |
1731 | |
1732 | params.vdev_id = arvif->vdev_id; |
1733 | |
1734 | ret = ath11k_wmi_send_twt_add_dialog_cmd(ar: arvif->ar, params: ¶ms); |
1735 | if (ret) |
1736 | goto err_twt_add_dialog; |
1737 | |
1738 | return count; |
1739 | |
1740 | err_twt_add_dialog: |
1741 | if (arvif->vif->type == NL80211_IFTYPE_STATION) { |
1742 | ath11k_wmi_send_twt_disable_cmd(ar, pdev_id: ar->pdev->pdev_id); |
1743 | ath11k_wmi_fill_default_twt_params(twt_params: &twt_params); |
1744 | ath11k_wmi_send_twt_enable_cmd(ar, pdev_id: ar->pdev->pdev_id, params: &twt_params); |
1745 | } |
1746 | |
1747 | return ret; |
1748 | } |
1749 | |
1750 | static ssize_t ath11k_write_twt_del_dialog(struct file *file, |
1751 | const char __user *ubuf, |
1752 | size_t count, loff_t *ppos) |
1753 | { |
1754 | struct ath11k_vif *arvif = file->private_data; |
1755 | struct wmi_twt_del_dialog_params params = { 0 }; |
1756 | struct wmi_twt_enable_params twt_params = {0}; |
1757 | struct ath11k *ar = arvif->ar; |
1758 | u8 buf[64] = {0}; |
1759 | int ret; |
1760 | |
1761 | if (ar->twt_enabled == 0) { |
1762 | ath11k_err(ab: ar->ab, fmt: "twt support is not enabled\n" ); |
1763 | return -EOPNOTSUPP; |
1764 | } |
1765 | |
1766 | ret = simple_write_to_buffer(to: buf, available: sizeof(buf) - 1, ppos, from: ubuf, count); |
1767 | if (ret < 0) |
1768 | return ret; |
1769 | |
1770 | buf[ret] = '\0'; |
1771 | ret = sscanf(buf, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx %u" , |
1772 | ¶ms.peer_macaddr[0], |
1773 | ¶ms.peer_macaddr[1], |
1774 | ¶ms.peer_macaddr[2], |
1775 | ¶ms.peer_macaddr[3], |
1776 | ¶ms.peer_macaddr[4], |
1777 | ¶ms.peer_macaddr[5], |
1778 | ¶ms.dialog_id); |
1779 | if (ret != 7) |
1780 | return -EINVAL; |
1781 | |
1782 | params.vdev_id = arvif->vdev_id; |
1783 | |
1784 | ret = ath11k_wmi_send_twt_del_dialog_cmd(ar: arvif->ar, params: ¶ms); |
1785 | if (ret) |
1786 | return ret; |
1787 | |
1788 | if (arvif->vif->type == NL80211_IFTYPE_STATION) { |
1789 | ath11k_wmi_send_twt_disable_cmd(ar, pdev_id: ar->pdev->pdev_id); |
1790 | ath11k_wmi_fill_default_twt_params(twt_params: &twt_params); |
1791 | ath11k_wmi_send_twt_enable_cmd(ar, pdev_id: ar->pdev->pdev_id, params: &twt_params); |
1792 | } |
1793 | |
1794 | return count; |
1795 | } |
1796 | |
1797 | static ssize_t ath11k_write_twt_pause_dialog(struct file *file, |
1798 | const char __user *ubuf, |
1799 | size_t count, loff_t *ppos) |
1800 | { |
1801 | struct ath11k_vif *arvif = file->private_data; |
1802 | struct wmi_twt_pause_dialog_params params = { 0 }; |
1803 | u8 buf[64] = {0}; |
1804 | int ret; |
1805 | |
1806 | if (arvif->ar->twt_enabled == 0) { |
1807 | ath11k_err(ab: arvif->ar->ab, fmt: "twt support is not enabled\n" ); |
1808 | return -EOPNOTSUPP; |
1809 | } |
1810 | |
1811 | ret = simple_write_to_buffer(to: buf, available: sizeof(buf) - 1, ppos, from: ubuf, count); |
1812 | if (ret < 0) |
1813 | return ret; |
1814 | |
1815 | buf[ret] = '\0'; |
1816 | ret = sscanf(buf, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx %u" , |
1817 | ¶ms.peer_macaddr[0], |
1818 | ¶ms.peer_macaddr[1], |
1819 | ¶ms.peer_macaddr[2], |
1820 | ¶ms.peer_macaddr[3], |
1821 | ¶ms.peer_macaddr[4], |
1822 | ¶ms.peer_macaddr[5], |
1823 | ¶ms.dialog_id); |
1824 | if (ret != 7) |
1825 | return -EINVAL; |
1826 | |
1827 | params.vdev_id = arvif->vdev_id; |
1828 | |
1829 | ret = ath11k_wmi_send_twt_pause_dialog_cmd(ar: arvif->ar, params: ¶ms); |
1830 | if (ret) |
1831 | return ret; |
1832 | |
1833 | return count; |
1834 | } |
1835 | |
1836 | static ssize_t ath11k_write_twt_resume_dialog(struct file *file, |
1837 | const char __user *ubuf, |
1838 | size_t count, loff_t *ppos) |
1839 | { |
1840 | struct ath11k_vif *arvif = file->private_data; |
1841 | struct wmi_twt_resume_dialog_params params = { 0 }; |
1842 | u8 buf[64] = {0}; |
1843 | int ret; |
1844 | |
1845 | if (arvif->ar->twt_enabled == 0) { |
1846 | ath11k_err(ab: arvif->ar->ab, fmt: "twt support is not enabled\n" ); |
1847 | return -EOPNOTSUPP; |
1848 | } |
1849 | |
1850 | ret = simple_write_to_buffer(to: buf, available: sizeof(buf) - 1, ppos, from: ubuf, count); |
1851 | if (ret < 0) |
1852 | return ret; |
1853 | |
1854 | buf[ret] = '\0'; |
1855 | ret = sscanf(buf, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx %u %u %u" , |
1856 | ¶ms.peer_macaddr[0], |
1857 | ¶ms.peer_macaddr[1], |
1858 | ¶ms.peer_macaddr[2], |
1859 | ¶ms.peer_macaddr[3], |
1860 | ¶ms.peer_macaddr[4], |
1861 | ¶ms.peer_macaddr[5], |
1862 | ¶ms.dialog_id, |
1863 | ¶ms.sp_offset_us, |
1864 | ¶ms.next_twt_size); |
1865 | if (ret != 9) |
1866 | return -EINVAL; |
1867 | |
1868 | params.vdev_id = arvif->vdev_id; |
1869 | |
1870 | ret = ath11k_wmi_send_twt_resume_dialog_cmd(ar: arvif->ar, params: ¶ms); |
1871 | if (ret) |
1872 | return ret; |
1873 | |
1874 | return count; |
1875 | } |
1876 | |
1877 | static const struct file_operations ath11k_fops_twt_add_dialog = { |
1878 | .write = ath11k_write_twt_add_dialog, |
1879 | .open = simple_open |
1880 | }; |
1881 | |
1882 | static const struct file_operations ath11k_fops_twt_del_dialog = { |
1883 | .write = ath11k_write_twt_del_dialog, |
1884 | .open = simple_open |
1885 | }; |
1886 | |
1887 | static const struct file_operations ath11k_fops_twt_pause_dialog = { |
1888 | .write = ath11k_write_twt_pause_dialog, |
1889 | .open = simple_open |
1890 | }; |
1891 | |
1892 | static const struct file_operations ath11k_fops_twt_resume_dialog = { |
1893 | .write = ath11k_write_twt_resume_dialog, |
1894 | .open = simple_open |
1895 | }; |
1896 | |
1897 | void ath11k_debugfs_op_vif_add(struct ieee80211_hw *hw, |
1898 | struct ieee80211_vif *vif) |
1899 | { |
1900 | struct ath11k_vif *arvif = ath11k_vif_to_arvif(vif); |
1901 | struct ath11k_base *ab = arvif->ar->ab; |
1902 | struct dentry *debugfs_twt; |
1903 | |
1904 | if (arvif->vif->type != NL80211_IFTYPE_AP && |
1905 | !(arvif->vif->type == NL80211_IFTYPE_STATION && |
1906 | test_bit(WMI_TLV_SERVICE_STA_TWT, ab->wmi_ab.svc_map))) |
1907 | return; |
1908 | |
1909 | debugfs_twt = debugfs_create_dir(name: "twt" , |
1910 | parent: arvif->vif->debugfs_dir); |
1911 | debugfs_create_file(name: "add_dialog" , mode: 0200, parent: debugfs_twt, |
1912 | data: arvif, fops: &ath11k_fops_twt_add_dialog); |
1913 | |
1914 | debugfs_create_file(name: "del_dialog" , mode: 0200, parent: debugfs_twt, |
1915 | data: arvif, fops: &ath11k_fops_twt_del_dialog); |
1916 | |
1917 | debugfs_create_file(name: "pause_dialog" , mode: 0200, parent: debugfs_twt, |
1918 | data: arvif, fops: &ath11k_fops_twt_pause_dialog); |
1919 | |
1920 | debugfs_create_file(name: "resume_dialog" , mode: 0200, parent: debugfs_twt, |
1921 | data: arvif, fops: &ath11k_fops_twt_resume_dialog); |
1922 | } |
1923 | |
1924 | |