// SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. */ #include "debugfs.h" #include "core.h" #include "debug.h" #include "wmi.h" #include "hal_rx.h" #include "dp_tx.h" #include "debugfs_htt_stats.h" #include "peer.h" static const char *htt_bp_umac_ring[HTT_SW_UMAC_RING_IDX_MAX] = { "REO2SW1_RING", "REO2SW2_RING", "REO2SW3_RING", "REO2SW4_RING", "WBM2REO_LINK_RING", "REO2TCL_RING", "REO2FW_RING", "RELEASE_RING", "PPE_RELEASE_RING", "TCL2TQM_RING", "TQM_RELEASE_RING", "REO_RELEASE_RING", "WBM2SW0_RELEASE_RING", "WBM2SW1_RELEASE_RING", "WBM2SW2_RELEASE_RING", "WBM2SW3_RELEASE_RING", "REO_CMD_RING", "REO_STATUS_RING", }; static const char *htt_bp_lmac_ring[HTT_SW_LMAC_RING_IDX_MAX] = { "FW2RXDMA_BUF_RING", "FW2RXDMA_STATUS_RING", "FW2RXDMA_LINK_RING", "SW2RXDMA_BUF_RING", "WBM2RXDMA_LINK_RING", "RXDMA2FW_RING", "RXDMA2SW_RING", "RXDMA2RELEASE_RING", "RXDMA2REO_RING", "MONITOR_STATUS_RING", "MONITOR_BUF_RING", "MONITOR_DESC_RING", "MONITOR_DEST_RING", }; static void ath11k_fw_stats_pdevs_free(struct list_head *head) { struct ath11k_fw_stats_pdev *i, *tmp; list_for_each_entry_safe(i, tmp, head, list) { list_del(&i->list); kfree(i); } } static void ath11k_fw_stats_vdevs_free(struct list_head *head) { struct ath11k_fw_stats_vdev *i, *tmp; list_for_each_entry_safe(i, tmp, head, list) { list_del(&i->list); kfree(i); } } static void ath11k_fw_stats_bcn_free(struct list_head *head) { struct ath11k_fw_stats_bcn *i, *tmp; list_for_each_entry_safe(i, tmp, head, list) { list_del(&i->list); kfree(i); } } static void ath11k_debugfs_fw_stats_reset(struct ath11k *ar) { spin_lock_bh(&ar->data_lock); ar->debug.fw_stats_done = false; ath11k_fw_stats_pdevs_free(&ar->debug.fw_stats.pdevs); ath11k_fw_stats_vdevs_free(&ar->debug.fw_stats.vdevs); spin_unlock_bh(&ar->data_lock); } void ath11k_debugfs_fw_stats_process(struct ath11k_base *ab, struct sk_buff *skb) { struct ath11k_fw_stats stats = {}; struct ath11k *ar; struct ath11k_pdev *pdev; bool is_end; static unsigned int num_vdev, num_bcn; size_t total_vdevs_started = 0; int i, ret; INIT_LIST_HEAD(&stats.pdevs); INIT_LIST_HEAD(&stats.vdevs); INIT_LIST_HEAD(&stats.bcn); ret = ath11k_wmi_pull_fw_stats(ab, skb, &stats); if (ret) { ath11k_warn(ab, "failed to pull fw stats: %d\n", ret); goto free; } rcu_read_lock(); ar = ath11k_mac_get_ar_by_pdev_id(ab, stats.pdev_id); if (!ar) { rcu_read_unlock(); ath11k_warn(ab, "failed to get ar for pdev_id %d: %d\n", stats.pdev_id, ret); goto free; } spin_lock_bh(&ar->data_lock); if (stats.stats_id == WMI_REQUEST_PDEV_STAT) { list_splice_tail_init(&stats.pdevs, &ar->debug.fw_stats.pdevs); ar->debug.fw_stats_done = true; goto complete; } if (stats.stats_id == WMI_REQUEST_VDEV_STAT) { if (list_empty(&stats.vdevs)) { ath11k_warn(ab, "empty vdev stats"); goto complete; } /* FW sends all the active VDEV stats irrespective of PDEV, * hence limit until the count of all VDEVs started */ for (i = 0; i < ab->num_radios; i++) { pdev = rcu_dereference(ab->pdevs_active[i]); if (pdev && pdev->ar) total_vdevs_started += ar->num_started_vdevs; } is_end = ((++num_vdev) == total_vdevs_started); list_splice_tail_init(&stats.vdevs, &ar->debug.fw_stats.vdevs); if (is_end) { ar->debug.fw_stats_done = true; num_vdev = 0; } goto complete; } if (stats.stats_id == WMI_REQUEST_BCN_STAT) { if (list_empty(&stats.bcn)) { ath11k_warn(ab, "empty bcn stats"); goto complete; } /* Mark end until we reached the count of all started VDEVs * within the PDEV */ is_end = ((++num_bcn) == ar->num_started_vdevs); list_splice_tail_init(&stats.bcn, &ar->debug.fw_stats.bcn); if (is_end) { ar->debug.fw_stats_done = true; num_bcn = 0; } } complete: complete(&ar->debug.fw_stats_complete); rcu_read_unlock(); spin_unlock_bh(&ar->data_lock); free: ath11k_fw_stats_pdevs_free(&stats.pdevs); ath11k_fw_stats_vdevs_free(&stats.vdevs); ath11k_fw_stats_bcn_free(&stats.bcn); } static int ath11k_debugfs_fw_stats_request(struct ath11k *ar, struct stats_request_params *req_param) { struct ath11k_base *ab = ar->ab; unsigned long timeout, time_left; int ret; lockdep_assert_held(&ar->conf_mutex); /* FW stats can get split when exceeding the stats data buffer limit. * In that case, since there is no end marking for the back-to-back * received 'update stats' event, we keep a 3 seconds timeout in case, * fw_stats_done is not marked yet */ timeout = jiffies + msecs_to_jiffies(3 * HZ); ath11k_debugfs_fw_stats_reset(ar); reinit_completion(&ar->debug.fw_stats_complete); ret = ath11k_wmi_send_stats_request_cmd(ar, req_param); if (ret) { ath11k_warn(ab, "could not request fw stats (%d)\n", ret); return ret; } time_left = wait_for_completion_timeout(&ar->debug.fw_stats_complete, 1 * HZ); if (!time_left) return -ETIMEDOUT; for (;;) { if (time_after(jiffies, timeout)) break; spin_lock_bh(&ar->data_lock); if (ar->debug.fw_stats_done) { spin_unlock_bh(&ar->data_lock); break; } spin_unlock_bh(&ar->data_lock); } return 0; } static int ath11k_open_pdev_stats(struct inode *inode, struct file *file) { struct ath11k *ar = inode->i_private; struct ath11k_base *ab = ar->ab; struct stats_request_params req_param; void *buf = NULL; int ret; mutex_lock(&ar->conf_mutex); if (ar->state != ATH11K_STATE_ON) { ret = -ENETDOWN; goto err_unlock; } buf = vmalloc(ATH11K_FW_STATS_BUF_SIZE); if (!buf) { ret = -ENOMEM; goto err_unlock; } req_param.pdev_id = ar->pdev->pdev_id; req_param.vdev_id = 0; req_param.stats_id = WMI_REQUEST_PDEV_STAT; ret = ath11k_debugfs_fw_stats_request(ar, &req_param); if (ret) { ath11k_warn(ab, "failed to request fw pdev stats: %d\n", ret); goto err_free; } ath11k_wmi_fw_stats_fill(ar, &ar->debug.fw_stats, req_param.stats_id, buf); file->private_data = buf; mutex_unlock(&ar->conf_mutex); return 0; err_free: vfree(buf); err_unlock: mutex_unlock(&ar->conf_mutex); return ret; } static int ath11k_release_pdev_stats(struct inode *inode, struct file *file) { vfree(file->private_data); return 0; } static ssize_t ath11k_read_pdev_stats(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { const char *buf = file->private_data; size_t len = strlen(buf); return simple_read_from_buffer(user_buf, count, ppos, buf, len); } static const struct file_operations fops_pdev_stats = { .open = ath11k_open_pdev_stats, .release = ath11k_release_pdev_stats, .read = ath11k_read_pdev_stats, .owner = THIS_MODULE, .llseek = default_llseek, }; static int ath11k_open_vdev_stats(struct inode *inode, struct file *file) { struct ath11k *ar = inode->i_private; struct stats_request_params req_param; void *buf = NULL; int ret; mutex_lock(&ar->conf_mutex); if (ar->state != ATH11K_STATE_ON) { ret = -ENETDOWN; goto err_unlock; } buf = vmalloc(ATH11K_FW_STATS_BUF_SIZE); if (!buf) { ret = -ENOMEM; goto err_unlock; } req_param.pdev_id = ar->pdev->pdev_id; /* VDEV stats is always sent for all active VDEVs from FW */ req_param.vdev_id = 0; req_param.stats_id = WMI_REQUEST_VDEV_STAT; ret = ath11k_debugfs_fw_stats_request(ar, &req_param); if (ret) { ath11k_warn(ar->ab, "failed to request fw vdev stats: %d\n", ret); goto err_free; } ath11k_wmi_fw_stats_fill(ar, &ar->debug.fw_stats, req_param.stats_id, buf); file->private_data = buf; mutex_unlock(&ar->conf_mutex); return 0; err_free: vfree(buf); err_unlock: mutex_unlock(&ar->conf_mutex); return ret; } static int ath11k_release_vdev_stats(struct inode *inode, struct file *file) { vfree(file->private_data); return 0; } static ssize_t ath11k_read_vdev_stats(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { const char *buf = file->private_data; size_t len = strlen(buf); return simple_read_from_buffer(user_buf, count, ppos, buf, len); } static const struct file_operations fops_vdev_stats = { .open = ath11k_open_vdev_stats, .release = ath11k_release_vdev_stats, .read = ath11k_read_vdev_stats, .owner = THIS_MODULE, .llseek = default_llseek, }; static int ath11k_open_bcn_stats(struct inode *inode, struct file *file) { struct ath11k *ar = inode->i_private; struct ath11k_vif *arvif; struct stats_request_params req_param; void *buf = NULL; int ret; mutex_lock(&ar->conf_mutex); if (ar->state != ATH11K_STATE_ON) { ret = -ENETDOWN; goto err_unlock; } buf = vmalloc(ATH11K_FW_STATS_BUF_SIZE); if (!buf) { ret = -ENOMEM; goto err_unlock; } req_param.stats_id = WMI_REQUEST_BCN_STAT; req_param.pdev_id = ar->pdev->pdev_id; /* loop all active VDEVs for bcn stats */ list_for_each_entry(arvif, &ar->arvifs, list) { if (!arvif->is_up) continue; req_param.vdev_id = arvif->vdev_id; ret = ath11k_debugfs_fw_stats_request(ar, &req_param); if (ret) { ath11k_warn(ar->ab, "failed to request fw bcn stats: %d\n", ret); goto err_free; } } ath11k_wmi_fw_stats_fill(ar, &ar->debug.fw_stats, req_param.stats_id, buf); /* since beacon stats request is looped for all active VDEVs, saved fw * stats is not freed for each request until done for all active VDEVs */ spin_lock_bh(&ar->data_lock); ath11k_fw_stats_bcn_free(&ar->debug.fw_stats.bcn); spin_unlock_bh(&ar->data_lock); file->private_data = buf; mutex_unlock(&ar->conf_mutex); return 0; err_free: vfree(buf); err_unlock: mutex_unlock(&ar->conf_mutex); return ret; } static int ath11k_release_bcn_stats(struct inode *inode, struct file *file) { vfree(file->private_data); return 0; } static ssize_t ath11k_read_bcn_stats(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { const char *buf = file->private_data; size_t len = strlen(buf); return simple_read_from_buffer(user_buf, count, ppos, buf, len); } static const struct file_operations fops_bcn_stats = { .open = ath11k_open_bcn_stats, .release = ath11k_release_bcn_stats, .read = ath11k_read_bcn_stats, .owner = THIS_MODULE, .llseek = default_llseek, }; static ssize_t ath11k_read_simulate_fw_crash(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { const char buf[] = "To simulate firmware crash write one of the keywords to this file:\n" "`assert` - this will send WMI_FORCE_FW_HANG_CMDID to firmware to cause assert.\n" "`hw-restart` - this will simply queue hw restart without fw/hw actually crashing.\n"; return simple_read_from_buffer(user_buf, count, ppos, buf, strlen(buf)); } /* Simulate firmware crash: * 'soft': Call wmi command causing firmware hang. This firmware hang is * recoverable by warm firmware reset. * 'hard': Force firmware crash by setting any vdev parameter for not allowed * vdev id. This is hard firmware crash because it is recoverable only by cold * firmware reset. */ static ssize_t ath11k_write_simulate_fw_crash(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { struct ath11k_base *ab = file->private_data; struct ath11k_pdev *pdev; struct ath11k *ar = ab->pdevs[0].ar; char buf[32] = {0}; ssize_t rc; int i, ret, radioup = 0; for (i = 0; i < ab->num_radios; i++) { pdev = &ab->pdevs[i]; ar = pdev->ar; if (ar && ar->state == ATH11K_STATE_ON) { radioup = 1; break; } } /* filter partial writes and invalid commands */ if (*ppos != 0 || count >= sizeof(buf) || count == 0) return -EINVAL; rc = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count); if (rc < 0) return rc; /* drop the possible '\n' from the end */ if (buf[*ppos - 1] == '\n') buf[*ppos - 1] = '\0'; if (radioup == 0) { ret = -ENETDOWN; goto exit; } if (!strcmp(buf, "assert")) { ath11k_info(ab, "simulating firmware assert crash\n"); ret = ath11k_wmi_force_fw_hang_cmd(ar, ATH11K_WMI_FW_HANG_ASSERT_TYPE, ATH11K_WMI_FW_HANG_DELAY); } else { ret = -EINVAL; goto exit; } if (ret) { ath11k_warn(ab, "failed to simulate firmware crash: %d\n", ret); goto exit; } ret = count; exit: return ret; } static const struct file_operations fops_simulate_fw_crash = { .read = ath11k_read_simulate_fw_crash, .write = ath11k_write_simulate_fw_crash, .open = simple_open, .owner = THIS_MODULE, .llseek = default_llseek, }; static ssize_t ath11k_write_enable_extd_tx_stats(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos) { struct ath11k *ar = file->private_data; u32 filter; int ret; if (kstrtouint_from_user(ubuf, count, 0, &filter)) return -EINVAL; mutex_lock(&ar->conf_mutex); if (ar->state != ATH11K_STATE_ON) { ret = -ENETDOWN; goto out; } if (filter == ar->debug.extd_tx_stats) { ret = count; goto out; } ar->debug.extd_tx_stats = filter; ret = count; out: mutex_unlock(&ar->conf_mutex); return ret; } static ssize_t ath11k_read_enable_extd_tx_stats(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) { char buf[32] = {0}; struct ath11k *ar = file->private_data; int len = 0; mutex_lock(&ar->conf_mutex); len = scnprintf(buf, sizeof(buf) - len, "%08x\n", ar->debug.extd_tx_stats); mutex_unlock(&ar->conf_mutex); return simple_read_from_buffer(ubuf, count, ppos, buf, len); } static const struct file_operations fops_extd_tx_stats = { .read = ath11k_read_enable_extd_tx_stats, .write = ath11k_write_enable_extd_tx_stats, .open = simple_open }; static ssize_t ath11k_write_extd_rx_stats(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos) { struct ath11k *ar = file->private_data; struct ath11k_base *ab = ar->ab; struct htt_rx_ring_tlv_filter tlv_filter = {0}; u32 enable, rx_filter = 0, ring_id; int i; int ret; if (kstrtouint_from_user(ubuf, count, 0, &enable)) return -EINVAL; mutex_lock(&ar->conf_mutex); if (ar->state != ATH11K_STATE_ON) { ret = -ENETDOWN; goto exit; } if (enable > 1) { ret = -EINVAL; goto exit; } if (enable == ar->debug.extd_rx_stats) { ret = count; goto exit; } if (enable) { rx_filter = HTT_RX_FILTER_TLV_FLAGS_MPDU_START; rx_filter |= HTT_RX_FILTER_TLV_FLAGS_PPDU_START; rx_filter |= HTT_RX_FILTER_TLV_FLAGS_PPDU_END; rx_filter |= HTT_RX_FILTER_TLV_FLAGS_PPDU_END_USER_STATS; rx_filter |= HTT_RX_FILTER_TLV_FLAGS_PPDU_END_USER_STATS_EXT; rx_filter |= HTT_RX_FILTER_TLV_FLAGS_PPDU_END_STATUS_DONE; tlv_filter.rx_filter = rx_filter; tlv_filter.pkt_filter_flags0 = HTT_RX_FP_MGMT_FILTER_FLAGS0; tlv_filter.pkt_filter_flags1 = HTT_RX_FP_MGMT_FILTER_FLAGS1; tlv_filter.pkt_filter_flags2 = HTT_RX_FP_CTRL_FILTER_FLASG2; tlv_filter.pkt_filter_flags3 = HTT_RX_FP_CTRL_FILTER_FLASG3 | HTT_RX_FP_DATA_FILTER_FLASG3; } else { tlv_filter = ath11k_mac_mon_status_filter_default; } ar->debug.rx_filter = tlv_filter.rx_filter; for (i = 0; i < ab->hw_params.num_rxmda_per_pdev; i++) { ring_id = ar->dp.rx_mon_status_refill_ring[i].refill_buf_ring.ring_id; ret = ath11k_dp_tx_htt_rx_filter_setup(ar->ab, ring_id, ar->dp.mac_id, HAL_RXDMA_MONITOR_STATUS, DP_RX_BUFFER_SIZE, &tlv_filter); if (ret) { ath11k_warn(ar->ab, "failed to set rx filter for monitor status ring\n"); goto exit; } } ar->debug.extd_rx_stats = enable; ret = count; exit: mutex_unlock(&ar->conf_mutex); return ret; } static ssize_t ath11k_read_extd_rx_stats(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) { struct ath11k *ar = file->private_data; char buf[32]; int len = 0; mutex_lock(&ar->conf_mutex); len = scnprintf(buf, sizeof(buf) - len, "%d\n", ar->debug.extd_rx_stats); mutex_unlock(&ar->conf_mutex); return simple_read_from_buffer(ubuf, count, ppos, buf, len); } static const struct file_operations fops_extd_rx_stats = { .read = ath11k_read_extd_rx_stats, .write = ath11k_write_extd_rx_stats, .open = simple_open, }; static int ath11k_fill_bp_stats(struct ath11k_base *ab, struct ath11k_bp_stats *bp_stats, char *buf, int len, int size) { lockdep_assert_held(&ab->base_lock); len += scnprintf(buf + len, size - len, "count: %u\n", bp_stats->count); len += scnprintf(buf + len, size - len, "hp: %u\n", bp_stats->hp); len += scnprintf(buf + len, size - len, "tp: %u\n", bp_stats->tp); len += scnprintf(buf + len, size - len, "seen before: %ums\n\n", jiffies_to_msecs(jiffies - bp_stats->jiffies)); return len; } static ssize_t ath11k_debugfs_dump_soc_ring_bp_stats(struct ath11k_base *ab, char *buf, int size) { struct ath11k_bp_stats *bp_stats; bool stats_rxd = false; u8 i, pdev_idx; int len = 0; len += scnprintf(buf + len, size - len, "\nBackpressure Stats\n"); len += scnprintf(buf + len, size - len, "==================\n"); spin_lock_bh(&ab->base_lock); for (i = 0; i < HTT_SW_UMAC_RING_IDX_MAX; i++) { bp_stats = &ab->soc_stats.bp_stats.umac_ring_bp_stats[i]; if (!bp_stats->count) continue; len += scnprintf(buf + len, size - len, "Ring: %s\n", htt_bp_umac_ring[i]); len = ath11k_fill_bp_stats(ab, bp_stats, buf, len, size); stats_rxd = true; } for (i = 0; i < HTT_SW_LMAC_RING_IDX_MAX; i++) { for (pdev_idx = 0; pdev_idx < MAX_RADIOS; pdev_idx++) { bp_stats = &ab->soc_stats.bp_stats.lmac_ring_bp_stats[i][pdev_idx]; if (!bp_stats->count) continue; len += scnprintf(buf + len, size - len, "Ring: %s\n", htt_bp_lmac_ring[i]); len += scnprintf(buf + len, size - len, "pdev: %d\n", pdev_idx); len = ath11k_fill_bp_stats(ab, bp_stats, buf, len, size); stats_rxd = true; } } spin_unlock_bh(&ab->base_lock); if (!stats_rxd) len += scnprintf(buf + len, size - len, "No Ring Backpressure stats received\n\n"); return len; } static ssize_t ath11k_debugfs_dump_soc_dp_stats(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { struct ath11k_base *ab = file->private_data; struct ath11k_soc_dp_stats *soc_stats = &ab->soc_stats; int len = 0, i, retval; const int size = 4096; static const char *rxdma_err[HAL_REO_ENTR_RING_RXDMA_ECODE_MAX] = { "Overflow", "MPDU len", "FCS", "Decrypt", "TKIP MIC", "Unencrypt", "MSDU len", "MSDU limit", "WiFi parse", "AMSDU parse", "SA timeout", "DA timeout", "Flow timeout", "Flush req"}; static const char *reo_err[HAL_REO_DEST_RING_ERROR_CODE_MAX] = { "Desc addr zero", "Desc inval", "AMPDU in non BA", "Non BA dup", "BA dup", "Frame 2k jump", "BAR 2k jump", "Frame OOR", "BAR OOR", "No BA session", "Frame SN equal SSN", "PN check fail", "2k err", "PN err", "Desc blocked"}; char *buf; buf = kzalloc(size, GFP_KERNEL); if (!buf) return -ENOMEM; len += scnprintf(buf + len, size - len, "SOC RX STATS:\n\n"); len += scnprintf(buf + len, size - len, "err ring pkts: %u\n", soc_stats->err_ring_pkts); len += scnprintf(buf + len, size - len, "Invalid RBM: %u\n\n", soc_stats->invalid_rbm); len += scnprintf(buf + len, size - len, "RXDMA errors:\n"); for (i = 0; i < HAL_REO_ENTR_RING_RXDMA_ECODE_MAX; i++) len += scnprintf(buf + len, size - len, "%s: %u\n", rxdma_err[i], soc_stats->rxdma_error[i]); len += scnprintf(buf + len, size - len, "\nREO errors:\n"); for (i = 0; i < HAL_REO_DEST_RING_ERROR_CODE_MAX; i++) len += scnprintf(buf + len, size - len, "%s: %u\n", reo_err[i], soc_stats->reo_error[i]); len += scnprintf(buf + len, size - len, "\nHAL REO errors:\n"); len += scnprintf(buf + len, size - len, "ring0: %u\nring1: %u\nring2: %u\nring3: %u\n", soc_stats->hal_reo_error[0], soc_stats->hal_reo_error[1], soc_stats->hal_reo_error[2], soc_stats->hal_reo_error[3]); len += scnprintf(buf + len, size - len, "\nSOC TX STATS:\n"); len += scnprintf(buf + len, size - len, "\nTCL Ring Full Failures:\n"); for (i = 0; i < DP_TCL_NUM_RING_MAX; i++) len += scnprintf(buf + len, size - len, "ring%d: %u\n", i, soc_stats->tx_err.desc_na[i]); len += scnprintf(buf + len, size - len, "\nMisc Transmit Failures: %d\n", atomic_read(&soc_stats->tx_err.misc_fail)); len += ath11k_debugfs_dump_soc_ring_bp_stats(ab, buf + len, size - len); if (len > size) len = size; retval = simple_read_from_buffer(user_buf, count, ppos, buf, len); kfree(buf); return retval; } static const struct file_operations fops_soc_dp_stats = { .read = ath11k_debugfs_dump_soc_dp_stats, .open = simple_open, .owner = THIS_MODULE, .llseek = default_llseek, }; int ath11k_debugfs_pdev_create(struct ath11k_base *ab) { if (test_bit(ATH11K_FLAG_REGISTERED, &ab->dev_flags)) return 0; debugfs_create_file("simulate_fw_crash", 0600, ab->debugfs_soc, ab, &fops_simulate_fw_crash); debugfs_create_file("soc_dp_stats", 0600, ab->debugfs_soc, ab, &fops_soc_dp_stats); return 0; } void ath11k_debugfs_pdev_destroy(struct ath11k_base *ab) { debugfs_remove_recursive(ab->debugfs_soc); ab->debugfs_soc = NULL; } int ath11k_debugfs_soc_create(struct ath11k_base *ab) { struct dentry *root; bool dput_needed; char name[64]; int ret; root = debugfs_lookup("ath11k", NULL); if (!root) { root = debugfs_create_dir("ath11k", NULL); if (IS_ERR_OR_NULL(root)) return PTR_ERR(root); dput_needed = false; } else { /* a dentry from lookup() needs dput() after we don't use it */ dput_needed = true; } scnprintf(name, sizeof(name), "%s-%s", ath11k_bus_str(ab->hif.bus), dev_name(ab->dev)); ab->debugfs_soc = debugfs_create_dir(name, root); if (IS_ERR_OR_NULL(ab->debugfs_soc)) { ret = PTR_ERR(ab->debugfs_soc); goto out; } ret = 0; out: if (dput_needed) dput(root); return ret; } void ath11k_debugfs_soc_destroy(struct ath11k_base *ab) { debugfs_remove_recursive(ab->debugfs_soc); ab->debugfs_soc = NULL; /* We are not removing ath11k directory on purpose, even if it * would be empty. This simplifies the directory handling and it's * a minor cosmetic issue to leave an empty ath11k directory to * debugfs. */ } void ath11k_debugfs_fw_stats_init(struct ath11k *ar) { struct dentry *fwstats_dir = debugfs_create_dir("fw_stats", ar->debug.debugfs_pdev); ar->debug.fw_stats.debugfs_fwstats = fwstats_dir; /* all stats debugfs files created are under "fw_stats" directory * created per PDEV */ debugfs_create_file("pdev_stats", 0600, fwstats_dir, ar, &fops_pdev_stats); debugfs_create_file("vdev_stats", 0600, fwstats_dir, ar, &fops_vdev_stats); debugfs_create_file("beacon_stats", 0600, fwstats_dir, ar, &fops_bcn_stats); INIT_LIST_HEAD(&ar->debug.fw_stats.pdevs); INIT_LIST_HEAD(&ar->debug.fw_stats.vdevs); INIT_LIST_HEAD(&ar->debug.fw_stats.bcn); init_completion(&ar->debug.fw_stats_complete); } static ssize_t ath11k_write_pktlog_filter(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos) { struct ath11k *ar = file->private_data; struct ath11k_base *ab = ar->ab; struct htt_rx_ring_tlv_filter tlv_filter = {0}; u32 rx_filter = 0, ring_id, filter, mode; u8 buf[128] = {0}; int i, ret; ssize_t rc; mutex_lock(&ar->conf_mutex); if (ar->state != ATH11K_STATE_ON) { ret = -ENETDOWN; goto out; } rc = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, ubuf, count); if (rc < 0) { ret = rc; goto out; } buf[rc] = '\0'; ret = sscanf(buf, "0x%x %u", &filter, &mode); if (ret != 2) { ret = -EINVAL; goto out; } if (filter) { ret = ath11k_wmi_pdev_pktlog_enable(ar, filter); if (ret) { ath11k_warn(ar->ab, "failed to enable pktlog filter %x: %d\n", ar->debug.pktlog_filter, ret); goto out; } } else { ret = ath11k_wmi_pdev_pktlog_disable(ar); if (ret) { ath11k_warn(ar->ab, "failed to disable pktlog: %d\n", ret); goto out; } } #define HTT_RX_FILTER_TLV_LITE_MODE \ (HTT_RX_FILTER_TLV_FLAGS_PPDU_START | \ HTT_RX_FILTER_TLV_FLAGS_PPDU_END | \ HTT_RX_FILTER_TLV_FLAGS_PPDU_END_USER_STATS | \ HTT_RX_FILTER_TLV_FLAGS_PPDU_END_USER_STATS_EXT | \ HTT_RX_FILTER_TLV_FLAGS_PPDU_END_STATUS_DONE | \ HTT_RX_FILTER_TLV_FLAGS_MPDU_START) if (mode == ATH11K_PKTLOG_MODE_FULL) { rx_filter = HTT_RX_FILTER_TLV_LITE_MODE | HTT_RX_FILTER_TLV_FLAGS_MSDU_START | HTT_RX_FILTER_TLV_FLAGS_MSDU_END | HTT_RX_FILTER_TLV_FLAGS_MPDU_END | HTT_RX_FILTER_TLV_FLAGS_PACKET_HEADER | HTT_RX_FILTER_TLV_FLAGS_ATTENTION; } else if (mode == ATH11K_PKTLOG_MODE_LITE) { ret = ath11k_dp_tx_htt_h2t_ppdu_stats_req(ar, HTT_PPDU_STATS_TAG_PKTLOG); if (ret) { ath11k_err(ar->ab, "failed to enable pktlog lite: %d\n", ret); goto out; } rx_filter = HTT_RX_FILTER_TLV_LITE_MODE; } else { ret = ath11k_dp_tx_htt_h2t_ppdu_stats_req(ar, HTT_PPDU_STATS_TAG_DEFAULT); if (ret) { ath11k_err(ar->ab, "failed to send htt ppdu stats req: %d\n", ret); goto out; } } tlv_filter.rx_filter = rx_filter; if (rx_filter) { tlv_filter.pkt_filter_flags0 = HTT_RX_FP_MGMT_FILTER_FLAGS0; tlv_filter.pkt_filter_flags1 = HTT_RX_FP_MGMT_FILTER_FLAGS1; tlv_filter.pkt_filter_flags2 = HTT_RX_FP_CTRL_FILTER_FLASG2; tlv_filter.pkt_filter_flags3 = HTT_RX_FP_CTRL_FILTER_FLASG3 | HTT_RX_FP_DATA_FILTER_FLASG3; } for (i = 0; i < ab->hw_params.num_rxmda_per_pdev; i++) { ring_id = ar->dp.rx_mon_status_refill_ring[i].refill_buf_ring.ring_id; ret = ath11k_dp_tx_htt_rx_filter_setup(ab, ring_id, ar->dp.mac_id + i, HAL_RXDMA_MONITOR_STATUS, DP_RX_BUFFER_SIZE, &tlv_filter); if (ret) { ath11k_warn(ab, "failed to set rx filter for monitor status ring\n"); goto out; } } ath11k_dbg(ab, ATH11K_DBG_WMI, "pktlog filter %d mode %s\n", filter, ((mode == ATH11K_PKTLOG_MODE_FULL) ? "full" : "lite")); ar->debug.pktlog_filter = filter; ar->debug.pktlog_mode = mode; ret = count; out: mutex_unlock(&ar->conf_mutex); return ret; } static ssize_t ath11k_read_pktlog_filter(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) { char buf[32] = {0}; struct ath11k *ar = file->private_data; int len = 0; mutex_lock(&ar->conf_mutex); len = scnprintf(buf, sizeof(buf) - len, "%08x %08x\n", ar->debug.pktlog_filter, ar->debug.pktlog_mode); mutex_unlock(&ar->conf_mutex); return simple_read_from_buffer(ubuf, count, ppos, buf, len); } static const struct file_operations fops_pktlog_filter = { .read = ath11k_read_pktlog_filter, .write = ath11k_write_pktlog_filter, .open = simple_open }; static ssize_t ath11k_write_simulate_radar(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { struct ath11k *ar = file->private_data; int ret; ret = ath11k_wmi_simulate_radar(ar); if (ret) return ret; return count; } static const struct file_operations fops_simulate_radar = { .write = ath11k_write_simulate_radar, .open = simple_open }; int ath11k_debugfs_register(struct ath11k *ar) { struct ath11k_base *ab = ar->ab; char pdev_name[5]; char buf[100] = {0}; snprintf(pdev_name, sizeof(pdev_name), "%s%d", "mac", ar->pdev_idx); ar->debug.debugfs_pdev = debugfs_create_dir(pdev_name, ab->debugfs_soc); if (IS_ERR(ar->debug.debugfs_pdev)) return PTR_ERR(ar->debug.debugfs_pdev); /* Create a symlink under ieee80211/phy* */ snprintf(buf, 100, "../../ath11k/%pd2", ar->debug.debugfs_pdev); debugfs_create_symlink("ath11k", ar->hw->wiphy->debugfsdir, buf); ath11k_debugfs_htt_stats_init(ar); ath11k_debugfs_fw_stats_init(ar); debugfs_create_file("ext_tx_stats", 0644, ar->debug.debugfs_pdev, ar, &fops_extd_tx_stats); debugfs_create_file("ext_rx_stats", 0644, ar->debug.debugfs_pdev, ar, &fops_extd_rx_stats); debugfs_create_file("pktlog_filter", 0644, ar->debug.debugfs_pdev, ar, &fops_pktlog_filter); if (ar->hw->wiphy->bands[NL80211_BAND_5GHZ]) { debugfs_create_file("dfs_simulate_radar", 0200, ar->debug.debugfs_pdev, ar, &fops_simulate_radar); debugfs_create_bool("dfs_block_radar_events", 0200, ar->debug.debugfs_pdev, &ar->dfs_block_radar_events); } return 0; } void ath11k_debugfs_unregister(struct ath11k *ar) { }