diff options
Diffstat (limited to 'drivers/net/wireless/ath/ath11k/debugfs.c')
-rw-r--r-- | drivers/net/wireless/ath/ath11k/debugfs.c | 968 |
1 files changed, 874 insertions, 94 deletions
diff --git a/drivers/net/wireless/ath/ath11k/debugfs.c b/drivers/net/wireless/ath/ath11k/debugfs.c index 80afd35337a1..ccdf3d5ba1ab 100644 --- a/drivers/net/wireless/ath/ath11k/debugfs.c +++ b/drivers/net/wireless/ath/ath11k/debugfs.c @@ -3,6 +3,8 @@ * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. */ +#include <linux/vmalloc.h> + #include "debugfs.h" #include "core.h" @@ -12,6 +14,7 @@ #include "dp_tx.h" #include "debugfs_htt_stats.h" #include "peer.h" +#include "hif.h" static const char *htt_bp_umac_ring[HTT_SW_UMAC_RING_IDX_MAX] = { "REO2SW1_RING", @@ -50,86 +53,74 @@ static const char *htt_bp_lmac_ring[HTT_SW_LMAC_RING_IDX_MAX] = { "MONITOR_DEST_RING", }; -static void ath11k_fw_stats_pdevs_free(struct list_head *head) +void ath11k_debugfs_add_dbring_entry(struct ath11k *ar, + enum wmi_direct_buffer_module id, + enum ath11k_dbg_dbr_event event, + struct hal_srng *srng) { - struct ath11k_fw_stats_pdev *i, *tmp; + struct ath11k_debug_dbr *dbr_debug; + struct ath11k_dbg_dbr_data *dbr_data; + struct ath11k_dbg_dbr_entry *entry; - list_for_each_entry_safe(i, tmp, head, list) { - list_del(&i->list); - kfree(i); - } -} + if (id >= WMI_DIRECT_BUF_MAX || event >= ATH11K_DBG_DBR_EVENT_MAX) + return; -static void ath11k_fw_stats_vdevs_free(struct list_head *head) -{ - struct ath11k_fw_stats_vdev *i, *tmp; + dbr_debug = ar->debug.dbr_debug[id]; + if (!dbr_debug) + return; - list_for_each_entry_safe(i, tmp, head, list) { - list_del(&i->list); - kfree(i); - } -} + if (!dbr_debug->dbr_debug_enabled) + return; -static void ath11k_fw_stats_bcn_free(struct list_head *head) -{ - struct ath11k_fw_stats_bcn *i, *tmp; + dbr_data = &dbr_debug->dbr_dbg_data; + + spin_lock_bh(&dbr_data->lock); - list_for_each_entry_safe(i, tmp, head, list) { - list_del(&i->list); - kfree(i); + if (dbr_data->entries) { + entry = &dbr_data->entries[dbr_data->dbr_debug_idx]; + entry->hp = srng->u.src_ring.hp; + entry->tp = *srng->u.src_ring.tp_addr; + entry->timestamp = jiffies; + entry->event = event; + + dbr_data->dbr_debug_idx++; + if (dbr_data->dbr_debug_idx == + dbr_data->num_ring_debug_entries) + dbr_data->dbr_debug_idx = 0; } + + spin_unlock_bh(&dbr_data->lock); } 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); + ar->fw_stats_done = false; + ath11k_fw_stats_pdevs_free(&ar->fw_stats.pdevs); + ath11k_fw_stats_vdevs_free(&ar->fw_stats.vdevs); spin_unlock_bh(&ar->data_lock); } -void ath11k_debugfs_fw_stats_process(struct ath11k_base *ab, struct sk_buff *skb) +void ath11k_debugfs_fw_stats_process(struct ath11k *ar, struct ath11k_fw_stats *stats) { - struct ath11k_fw_stats stats = {}; - struct ath11k *ar; + struct ath11k_base *ab = ar->ab; 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; - } + int i; - 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; - } + /* WMI_REQUEST_PDEV_STAT request has been already processed */ - 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_RSSI_PER_CHAIN_STAT) { + ar->fw_stats_done = true; + return; } - if (stats.stats_id == WMI_REQUEST_VDEV_STAT) { - if (list_empty(&stats.vdevs)) { + if (stats->stats_id == WMI_REQUEST_VDEV_STAT) { + if (list_empty(&stats->vdevs)) { ath11k_warn(ab, "empty vdev stats"); - goto complete; + return; } /* FW sends all the active VDEV stats irrespective of PDEV, * hence limit until the count of all VDEVs started @@ -142,43 +133,34 @@ void ath11k_debugfs_fw_stats_process(struct ath11k_base *ab, struct sk_buff *skb is_end = ((++num_vdev) == total_vdevs_started); - list_splice_tail_init(&stats.vdevs, - &ar->debug.fw_stats.vdevs); + list_splice_tail_init(&stats->vdevs, + &ar->fw_stats.vdevs); if (is_end) { - ar->debug.fw_stats_done = true; + ar->fw_stats_done = true; num_vdev = 0; } - goto complete; + return; } - if (stats.stats_id == WMI_REQUEST_BCN_STAT) { - if (list_empty(&stats.bcn)) { + if (stats->stats_id == WMI_REQUEST_BCN_STAT) { + if (list_empty(&stats->bcn)) { ath11k_warn(ab, "empty bcn stats"); - goto complete; + return; } /* 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); + list_splice_tail_init(&stats->bcn, + &ar->fw_stats.bcn); if (is_end) { - ar->debug.fw_stats_done = true; + ar->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, @@ -195,11 +177,11 @@ static int ath11k_debugfs_fw_stats_request(struct ath11k *ar, * 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); + timeout = jiffies + msecs_to_jiffies(3 * 1000); ath11k_debugfs_fw_stats_reset(ar); - reinit_completion(&ar->debug.fw_stats_complete); + reinit_completion(&ar->fw_stats_complete); ret = ath11k_wmi_send_stats_request_cmd(ar, req_param); @@ -209,9 +191,8 @@ static int ath11k_debugfs_fw_stats_request(struct ath11k *ar, return ret; } - time_left = - wait_for_completion_timeout(&ar->debug.fw_stats_complete, - 1 * HZ); + time_left = wait_for_completion_timeout(&ar->fw_stats_complete, 1 * HZ); + if (!time_left) return -ETIMEDOUT; @@ -220,7 +201,7 @@ static int ath11k_debugfs_fw_stats_request(struct ath11k *ar, break; spin_lock_bh(&ar->data_lock); - if (ar->debug.fw_stats_done) { + if (ar->fw_stats_done) { spin_unlock_bh(&ar->data_lock); break; } @@ -229,6 +210,38 @@ static int ath11k_debugfs_fw_stats_request(struct ath11k *ar, return 0; } +int ath11k_debugfs_get_fw_stats(struct ath11k *ar, u32 pdev_id, + u32 vdev_id, u32 stats_id) +{ + struct ath11k_base *ab = ar->ab; + struct stats_request_params req_param; + int ret; + + mutex_lock(&ar->conf_mutex); + + if (ar->state != ATH11K_STATE_ON) { + ret = -ENETDOWN; + goto err_unlock; + } + + req_param.pdev_id = pdev_id; + req_param.vdev_id = vdev_id; + req_param.stats_id = stats_id; + + ret = ath11k_debugfs_fw_stats_request(ar, &req_param); + if (ret) + ath11k_warn(ab, "failed to request fw stats: %d\n", ret); + + ath11k_dbg(ab, ATH11K_DBG_WMI, + "debug get fw stat pdev id %d vdev id %d stats id 0x%x\n", + pdev_id, vdev_id, stats_id); + +err_unlock: + mutex_unlock(&ar->conf_mutex); + + return ret; +} + static int ath11k_open_pdev_stats(struct inode *inode, struct file *file) { struct ath11k *ar = inode->i_private; @@ -260,8 +273,7 @@ static int ath11k_open_pdev_stats(struct inode *inode, struct file *file) goto err_free; } - ath11k_wmi_fw_stats_fill(ar, &ar->debug.fw_stats, req_param.stats_id, - buf); + ath11k_wmi_fw_stats_fill(ar, &ar->fw_stats, req_param.stats_id, buf); file->private_data = buf; @@ -332,8 +344,7 @@ static int ath11k_open_vdev_stats(struct inode *inode, struct file *file) goto err_free; } - ath11k_wmi_fw_stats_fill(ar, &ar->debug.fw_stats, req_param.stats_id, - buf); + ath11k_wmi_fw_stats_fill(ar, &ar->fw_stats, req_param.stats_id, buf); file->private_data = buf; @@ -410,14 +421,13 @@ static int ath11k_open_bcn_stats(struct inode *inode, struct file *file) } } - ath11k_wmi_fw_stats_fill(ar, &ar->debug.fw_stats, req_param.stats_id, - buf); + ath11k_wmi_fw_stats_fill(ar, &ar->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); + ath11k_fw_stats_bcn_free(&ar->fw_stats.bcn); spin_unlock_bh(&ar->data_lock); file->private_data = buf; @@ -518,6 +528,10 @@ static ssize_t ath11k_write_simulate_fw_crash(struct file *file, ret = ath11k_wmi_force_fw_hang_cmd(ar, ATH11K_WMI_FW_HANG_ASSERT_TYPE, ATH11K_WMI_FW_HANG_DELAY); + } else if (!strcmp(buf, "hw-restart")) { + ath11k_info(ab, "user requested hw restart\n"); + queue_work(ab->workqueue_aux, &ab->reset_work); + ret = 0; } else { ret = -EINVAL; goto exit; @@ -627,6 +641,12 @@ static ssize_t ath11k_write_extd_rx_stats(struct file *file, goto exit; } + if (test_bit(ATH11K_FLAG_MONITOR_STARTED, &ar->monitor_flags)) { + ar->debug.extd_rx_stats = enable; + ret = count; + goto exit; + } + if (enable) { rx_filter = HTT_RX_FILTER_TLV_FLAGS_MPDU_START; rx_filter |= HTT_RX_FILTER_TLV_FLAGS_PPDU_START; @@ -831,6 +851,126 @@ static const struct file_operations fops_soc_dp_stats = { .llseek = default_llseek, }; +static ssize_t ath11k_write_fw_dbglog(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k *ar = file->private_data; + char buf[128] = {0}; + struct ath11k_fw_dbglog dbglog; + unsigned int param, mod_id_index, is_end; + u64 value; + int ret, num; + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, + user_buf, count); + if (ret <= 0) + return ret; + + num = sscanf(buf, "%u %llx %u %u", ¶m, &value, &mod_id_index, &is_end); + + if (num < 2) + return -EINVAL; + + mutex_lock(&ar->conf_mutex); + if (param == WMI_DEBUG_LOG_PARAM_MOD_ENABLE_BITMAP || + param == WMI_DEBUG_LOG_PARAM_WOW_MOD_ENABLE_BITMAP) { + if (num != 4 || mod_id_index > (MAX_MODULE_ID_BITMAP_WORDS - 1)) { + ret = -EINVAL; + goto out; + } + ar->debug.module_id_bitmap[mod_id_index] = upper_32_bits(value); + if (!is_end) { + ret = count; + goto out; + } + } else { + if (num != 2) { + ret = -EINVAL; + goto out; + } + } + + dbglog.param = param; + dbglog.value = lower_32_bits(value); + ret = ath11k_wmi_fw_dbglog_cfg(ar, ar->debug.module_id_bitmap, &dbglog); + if (ret) { + ath11k_warn(ar->ab, "fw dbglog config failed from debugfs: %d\n", + ret); + goto out; + } + + ret = count; + +out: + mutex_unlock(&ar->conf_mutex); + return ret; +} + +static const struct file_operations fops_fw_dbglog = { + .write = ath11k_write_fw_dbglog, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static int ath11k_open_sram_dump(struct inode *inode, struct file *file) +{ + struct ath11k_base *ab = inode->i_private; + u8 *buf; + u32 start, end; + int ret; + + start = ab->hw_params.sram_dump.start; + end = ab->hw_params.sram_dump.end; + + buf = vmalloc(end - start + 1); + if (!buf) + return -ENOMEM; + + ret = ath11k_hif_read(ab, buf, start, end); + if (ret) { + ath11k_warn(ab, "failed to dump sram: %d\n", ret); + vfree(buf); + return ret; + } + + file->private_data = buf; + return 0; +} + +static ssize_t ath11k_read_sram_dump(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k_base *ab = file->f_inode->i_private; + const char *buf = file->private_data; + int len; + u32 start, end; + + start = ab->hw_params.sram_dump.start; + end = ab->hw_params.sram_dump.end; + len = end - start + 1; + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static int ath11k_release_sram_dump(struct inode *inode, struct file *file) +{ + vfree(file->private_data); + file->private_data = NULL; + + return 0; +} + +static const struct file_operations fops_sram_dump = { + .open = ath11k_open_sram_dump, + .read = ath11k_read_sram_dump, + .release = ath11k_release_sram_dump, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + int ath11k_debugfs_pdev_create(struct ath11k_base *ab) { if (test_bit(ATH11K_FLAG_REGISTERED, &ab->dev_flags)) @@ -846,6 +986,10 @@ int ath11k_debugfs_pdev_create(struct ath11k_base *ab) debugfs_create_file("soc_dp_stats", 0600, ab->debugfs_soc, ab, &fops_soc_dp_stats); + if (ab->hw_params.sram_dump.start != 0) + debugfs_create_file("sram", 0400, ab->debugfs_soc, ab, + &fops_sram_dump); + return 0; } @@ -874,7 +1018,7 @@ 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; + ar->fw_stats.debugfs_fwstats = fwstats_dir; /* all stats debugfs files created are under "fw_stats" directory * created per PDEV @@ -885,12 +1029,6 @@ void ath11k_debugfs_fw_stats_init(struct ath11k *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, @@ -1068,6 +1206,356 @@ static const struct file_operations fops_simulate_radar = { .open = simple_open }; +static ssize_t ath11k_debug_dump_dbr_entries(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k_dbg_dbr_data *dbr_dbg_data = file->private_data; + static const char * const event_id_to_string[] = {"empty", "Rx", "Replenish"}; + int size = ATH11K_DEBUG_DBR_ENTRIES_MAX * 100; + char *buf; + int i, ret; + int len = 0; + + buf = kzalloc(size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + len += scnprintf(buf + len, size - len, + "-----------------------------------------\n"); + len += scnprintf(buf + len, size - len, + "| idx | hp | tp | timestamp | event |\n"); + len += scnprintf(buf + len, size - len, + "-----------------------------------------\n"); + + spin_lock_bh(&dbr_dbg_data->lock); + + for (i = 0; i < dbr_dbg_data->num_ring_debug_entries; i++) { + len += scnprintf(buf + len, size - len, + "|%4u|%8u|%8u|%11llu|%8s|\n", i, + dbr_dbg_data->entries[i].hp, + dbr_dbg_data->entries[i].tp, + dbr_dbg_data->entries[i].timestamp, + event_id_to_string[dbr_dbg_data->entries[i].event]); + } + + spin_unlock_bh(&dbr_dbg_data->lock); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + + return ret; +} + +static const struct file_operations fops_debug_dump_dbr_entries = { + .read = ath11k_debug_dump_dbr_entries, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static void ath11k_debugfs_dbr_dbg_destroy(struct ath11k *ar, int dbr_id) +{ + struct ath11k_debug_dbr *dbr_debug; + struct ath11k_dbg_dbr_data *dbr_dbg_data; + + if (!ar->debug.dbr_debug[dbr_id]) + return; + + dbr_debug = ar->debug.dbr_debug[dbr_id]; + dbr_dbg_data = &dbr_debug->dbr_dbg_data; + + debugfs_remove_recursive(dbr_debug->dbr_debugfs); + kfree(dbr_dbg_data->entries); + kfree(dbr_debug); + ar->debug.dbr_debug[dbr_id] = NULL; +} + +static int ath11k_debugfs_dbr_dbg_init(struct ath11k *ar, int dbr_id) +{ + struct ath11k_debug_dbr *dbr_debug; + struct ath11k_dbg_dbr_data *dbr_dbg_data; + static const char * const dbr_id_to_str[] = {"spectral", "CFR"}; + + if (ar->debug.dbr_debug[dbr_id]) + return 0; + + ar->debug.dbr_debug[dbr_id] = kzalloc(sizeof(*dbr_debug), + GFP_KERNEL); + + if (!ar->debug.dbr_debug[dbr_id]) + return -ENOMEM; + + dbr_debug = ar->debug.dbr_debug[dbr_id]; + dbr_dbg_data = &dbr_debug->dbr_dbg_data; + + if (dbr_debug->dbr_debugfs) + return 0; + + dbr_debug->dbr_debugfs = debugfs_create_dir(dbr_id_to_str[dbr_id], + ar->debug.debugfs_pdev); + if (IS_ERR_OR_NULL(dbr_debug->dbr_debugfs)) { + if (IS_ERR(dbr_debug->dbr_debugfs)) + return PTR_ERR(dbr_debug->dbr_debugfs); + return -ENOMEM; + } + + dbr_debug->dbr_debug_enabled = true; + dbr_dbg_data->num_ring_debug_entries = ATH11K_DEBUG_DBR_ENTRIES_MAX; + dbr_dbg_data->dbr_debug_idx = 0; + dbr_dbg_data->entries = kcalloc(ATH11K_DEBUG_DBR_ENTRIES_MAX, + sizeof(struct ath11k_dbg_dbr_entry), + GFP_KERNEL); + if (!dbr_dbg_data->entries) + return -ENOMEM; + + spin_lock_init(&dbr_dbg_data->lock); + + debugfs_create_file("dump_dbr_debug", 0444, dbr_debug->dbr_debugfs, + dbr_dbg_data, &fops_debug_dump_dbr_entries); + + return 0; +} + +static ssize_t ath11k_debugfs_write_enable_dbr_dbg(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct ath11k *ar = file->private_data; + char buf[32] = {0}; + u32 dbr_id, enable; + int ret; + + mutex_lock(&ar->conf_mutex); + + if (ar->state != ATH11K_STATE_ON) { + ret = -ENETDOWN; + goto out; + } + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, ubuf, count); + if (ret < 0) + goto out; + + buf[ret] = '\0'; + ret = sscanf(buf, "%u %u", &dbr_id, &enable); + if (ret != 2 || dbr_id > 1 || enable > 1) { + ret = -EINVAL; + ath11k_warn(ar->ab, "usage: echo <dbr_id> <val> dbr_id:0-Spectral 1-CFR val:0-disable 1-enable\n"); + goto out; + } + + if (enable) { + ret = ath11k_debugfs_dbr_dbg_init(ar, dbr_id); + if (ret) { + ath11k_warn(ar->ab, "db ring module debugfs init failed: %d\n", + ret); + goto out; + } + } else { + ath11k_debugfs_dbr_dbg_destroy(ar, dbr_id); + } + + ret = count; +out: + mutex_unlock(&ar->conf_mutex); + return ret; +} + +static const struct file_operations fops_dbr_debug = { + .write = ath11k_debugfs_write_enable_dbr_dbg, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath11k_write_ps_timekeeper_enable(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k *ar = file->private_data; + ssize_t ret; + u8 ps_timekeeper_enable; + + if (kstrtou8_from_user(user_buf, count, 0, &ps_timekeeper_enable)) + return -EINVAL; + + mutex_lock(&ar->conf_mutex); + + if (ar->state != ATH11K_STATE_ON) { + ret = -ENETDOWN; + goto exit; + } + + if (!ar->ps_state_enable) { + ret = -EINVAL; + goto exit; + } + + ar->ps_timekeeper_enable = !!ps_timekeeper_enable; + ret = count; +exit: + mutex_unlock(&ar->conf_mutex); + + return ret; +} + +static ssize_t ath11k_read_ps_timekeeper_enable(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k *ar = file->private_data; + char buf[32]; + int len; + + mutex_lock(&ar->conf_mutex); + len = scnprintf(buf, sizeof(buf), "%d\n", ar->ps_timekeeper_enable); + mutex_unlock(&ar->conf_mutex); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations fops_ps_timekeeper_enable = { + .read = ath11k_read_ps_timekeeper_enable, + .write = ath11k_write_ps_timekeeper_enable, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static void ath11k_reset_peer_ps_duration(void *data, + struct ieee80211_sta *sta) +{ + struct ath11k *ar = data; + struct ath11k_sta *arsta = (struct ath11k_sta *)sta->drv_priv; + + spin_lock_bh(&ar->data_lock); + arsta->ps_total_duration = 0; + spin_unlock_bh(&ar->data_lock); +} + +static ssize_t ath11k_write_reset_ps_duration(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k *ar = file->private_data; + int ret; + u8 reset_ps_duration; + + if (kstrtou8_from_user(user_buf, count, 0, &reset_ps_duration)) + return -EINVAL; + + mutex_lock(&ar->conf_mutex); + + if (ar->state != ATH11K_STATE_ON) { + ret = -ENETDOWN; + goto exit; + } + + if (!ar->ps_state_enable) { + ret = -EINVAL; + goto exit; + } + + ieee80211_iterate_stations_atomic(ar->hw, + ath11k_reset_peer_ps_duration, + ar); + + ret = count; +exit: + mutex_unlock(&ar->conf_mutex); + return ret; +} + +static const struct file_operations fops_reset_ps_duration = { + .write = ath11k_write_reset_ps_duration, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static void ath11k_peer_ps_state_disable(void *data, + struct ieee80211_sta *sta) +{ + struct ath11k *ar = data; + struct ath11k_sta *arsta = (struct ath11k_sta *)sta->drv_priv; + + spin_lock_bh(&ar->data_lock); + arsta->peer_ps_state = WMI_PEER_PS_STATE_DISABLED; + arsta->ps_start_time = 0; + arsta->ps_total_duration = 0; + spin_unlock_bh(&ar->data_lock); +} + +static ssize_t ath11k_write_ps_state_enable(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k *ar = file->private_data; + struct ath11k_pdev *pdev = ar->pdev; + int ret; + u32 param; + u8 ps_state_enable; + + if (kstrtou8_from_user(user_buf, count, 0, &ps_state_enable)) + return -EINVAL; + + mutex_lock(&ar->conf_mutex); + + ps_state_enable = !!ps_state_enable; + + if (ar->ps_state_enable == ps_state_enable) { + ret = count; + goto exit; + } + + param = WMI_PDEV_PEER_STA_PS_STATECHG_ENABLE; + ret = ath11k_wmi_pdev_set_param(ar, param, ps_state_enable, pdev->pdev_id); + if (ret) { + ath11k_warn(ar->ab, "failed to enable ps_state_enable: %d\n", + ret); + goto exit; + } + ar->ps_state_enable = ps_state_enable; + + if (!ar->ps_state_enable) { + ar->ps_timekeeper_enable = false; + ieee80211_iterate_stations_atomic(ar->hw, + ath11k_peer_ps_state_disable, + ar); + } + + ret = count; + +exit: + mutex_unlock(&ar->conf_mutex); + + return ret; +} + +static ssize_t ath11k_read_ps_state_enable(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k *ar = file->private_data; + char buf[32]; + int len; + + mutex_lock(&ar->conf_mutex); + len = scnprintf(buf, sizeof(buf), "%d\n", ar->ps_state_enable); + mutex_unlock(&ar->conf_mutex); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations fops_ps_state_enable = { + .read = ath11k_read_ps_state_enable, + .write = ath11k_write_ps_state_enable, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + int ath11k_debugfs_register(struct ath11k *ar) { struct ath11k_base *ab = ar->ab; @@ -1097,6 +1585,9 @@ int ath11k_debugfs_register(struct ath11k *ar) debugfs_create_file("pktlog_filter", 0644, ar->debug.debugfs_pdev, ar, &fops_pktlog_filter); + debugfs_create_file("fw_dbglog_config", 0600, + ar->debug.debugfs_pdev, ar, + &fops_fw_dbglog); if (ar->hw->wiphy->bands[NL80211_BAND_5GHZ]) { debugfs_create_file("dfs_simulate_radar", 0200, @@ -1107,9 +1598,298 @@ int ath11k_debugfs_register(struct ath11k *ar) &ar->dfs_block_radar_events); } + if (ab->hw_params.dbr_debug_support) + debugfs_create_file("enable_dbr_debug", 0200, ar->debug.debugfs_pdev, + ar, &fops_dbr_debug); + + debugfs_create_file("ps_state_enable", 0600, ar->debug.debugfs_pdev, ar, + &fops_ps_state_enable); + + if (test_bit(WMI_TLV_SERVICE_PEER_POWER_SAVE_DURATION_SUPPORT, + ar->ab->wmi_ab.svc_map)) { + debugfs_create_file("ps_timekeeper_enable", 0600, + ar->debug.debugfs_pdev, ar, + &fops_ps_timekeeper_enable); + + debugfs_create_file("reset_ps_duration", 0200, + ar->debug.debugfs_pdev, ar, + &fops_reset_ps_duration); + } + return 0; } void ath11k_debugfs_unregister(struct ath11k *ar) { + struct ath11k_debug_dbr *dbr_debug; + struct ath11k_dbg_dbr_data *dbr_dbg_data; + int i; + + for (i = 0; i < WMI_DIRECT_BUF_MAX; i++) { + dbr_debug = ar->debug.dbr_debug[i]; + if (!dbr_debug) + continue; + + dbr_dbg_data = &dbr_debug->dbr_dbg_data; + kfree(dbr_dbg_data->entries); + debugfs_remove_recursive(dbr_debug->dbr_debugfs); + kfree(dbr_debug); + ar->debug.dbr_debug[i] = NULL; + } +} + +static ssize_t ath11k_write_twt_add_dialog(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct ath11k_vif *arvif = file->private_data; + struct wmi_twt_add_dialog_params params = { 0 }; + struct wmi_twt_enable_params twt_params = {0}; + struct ath11k *ar = arvif->ar; + u8 buf[128] = {0}; + int ret; + + if (ar->twt_enabled == 0) { + ath11k_err(ar->ab, "twt support is not enabled\n"); + return -EOPNOTSUPP; + } + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, ubuf, count); + if (ret < 0) + return ret; + + buf[ret] = '\0'; + ret = sscanf(buf, + "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx %u %u %u %u %u %hhu %hhu %hhu %hhu %hhu", + ¶ms.peer_macaddr[0], + ¶ms.peer_macaddr[1], + ¶ms.peer_macaddr[2], + ¶ms.peer_macaddr[3], + ¶ms.peer_macaddr[4], + ¶ms.peer_macaddr[5], + ¶ms.dialog_id, + ¶ms.wake_intvl_us, + ¶ms.wake_intvl_mantis, + ¶ms.wake_dura_us, + ¶ms.sp_offset_us, + ¶ms.twt_cmd, + ¶ms.flag_bcast, + ¶ms.flag_trigger, + ¶ms.flag_flow_type, + ¶ms.flag_protection); + if (ret != 16) + return -EINVAL; + + /* In the case of station vif, TWT is entirely handled by + * the firmware based on the input parameters in the TWT enable + * WMI command that is sent to the target during assoc. + * For manually testing the TWT feature, we need to first disable + * TWT and send enable command again with TWT input parameter + * sta_cong_timer_ms set to 0. + */ + if (arvif->vif->type == NL80211_IFTYPE_STATION) { + ath11k_wmi_send_twt_disable_cmd(ar, ar->pdev->pdev_id); + + ath11k_wmi_fill_default_twt_params(&twt_params); + twt_params.sta_cong_timer_ms = 0; + + ath11k_wmi_send_twt_enable_cmd(ar, ar->pdev->pdev_id, &twt_params); + } + + params.vdev_id = arvif->vdev_id; + + ret = ath11k_wmi_send_twt_add_dialog_cmd(arvif->ar, ¶ms); + if (ret) + goto err_twt_add_dialog; + + return count; + +err_twt_add_dialog: + if (arvif->vif->type == NL80211_IFTYPE_STATION) { + ath11k_wmi_send_twt_disable_cmd(ar, ar->pdev->pdev_id); + ath11k_wmi_fill_default_twt_params(&twt_params); + ath11k_wmi_send_twt_enable_cmd(ar, ar->pdev->pdev_id, &twt_params); + } + + return ret; +} + +static ssize_t ath11k_write_twt_del_dialog(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct ath11k_vif *arvif = file->private_data; + struct wmi_twt_del_dialog_params params = { 0 }; + struct wmi_twt_enable_params twt_params = {0}; + struct ath11k *ar = arvif->ar; + u8 buf[64] = {0}; + int ret; + + if (ar->twt_enabled == 0) { + ath11k_err(ar->ab, "twt support is not enabled\n"); + return -EOPNOTSUPP; + } + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, ubuf, count); + if (ret < 0) + return ret; + + buf[ret] = '\0'; + ret = sscanf(buf, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx %u", + ¶ms.peer_macaddr[0], + ¶ms.peer_macaddr[1], + ¶ms.peer_macaddr[2], + ¶ms.peer_macaddr[3], + ¶ms.peer_macaddr[4], + ¶ms.peer_macaddr[5], + ¶ms.dialog_id); + if (ret != 7) + return -EINVAL; + + params.vdev_id = arvif->vdev_id; + + ret = ath11k_wmi_send_twt_del_dialog_cmd(arvif->ar, ¶ms); + if (ret) + return ret; + + if (arvif->vif->type == NL80211_IFTYPE_STATION) { + ath11k_wmi_send_twt_disable_cmd(ar, ar->pdev->pdev_id); + ath11k_wmi_fill_default_twt_params(&twt_params); + ath11k_wmi_send_twt_enable_cmd(ar, ar->pdev->pdev_id, &twt_params); + } + + return count; +} + +static ssize_t ath11k_write_twt_pause_dialog(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct ath11k_vif *arvif = file->private_data; + struct wmi_twt_pause_dialog_params params = { 0 }; + u8 buf[64] = {0}; + int ret; + + if (arvif->ar->twt_enabled == 0) { + ath11k_err(arvif->ar->ab, "twt support is not enabled\n"); + return -EOPNOTSUPP; + } + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, ubuf, count); + if (ret < 0) + return ret; + + buf[ret] = '\0'; + ret = sscanf(buf, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx %u", + ¶ms.peer_macaddr[0], + ¶ms.peer_macaddr[1], + ¶ms.peer_macaddr[2], + ¶ms.peer_macaddr[3], + ¶ms.peer_macaddr[4], + ¶ms.peer_macaddr[5], + ¶ms.dialog_id); + if (ret != 7) + return -EINVAL; + + params.vdev_id = arvif->vdev_id; + + ret = ath11k_wmi_send_twt_pause_dialog_cmd(arvif->ar, ¶ms); + if (ret) + return ret; + + return count; +} + +static ssize_t ath11k_write_twt_resume_dialog(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct ath11k_vif *arvif = file->private_data; + struct wmi_twt_resume_dialog_params params = { 0 }; + u8 buf[64] = {0}; + int ret; + + if (arvif->ar->twt_enabled == 0) { + ath11k_err(arvif->ar->ab, "twt support is not enabled\n"); + return -EOPNOTSUPP; + } + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, ubuf, count); + if (ret < 0) + return ret; + + buf[ret] = '\0'; + ret = sscanf(buf, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx %u %u %u", + ¶ms.peer_macaddr[0], + ¶ms.peer_macaddr[1], + ¶ms.peer_macaddr[2], + ¶ms.peer_macaddr[3], + ¶ms.peer_macaddr[4], + ¶ms.peer_macaddr[5], + ¶ms.dialog_id, + ¶ms.sp_offset_us, + ¶ms.next_twt_size); + if (ret != 9) + return -EINVAL; + + params.vdev_id = arvif->vdev_id; + + ret = ath11k_wmi_send_twt_resume_dialog_cmd(arvif->ar, ¶ms); + if (ret) + return ret; + + return count; +} + +static const struct file_operations ath11k_fops_twt_add_dialog = { + .write = ath11k_write_twt_add_dialog, + .open = simple_open +}; + +static const struct file_operations ath11k_fops_twt_del_dialog = { + .write = ath11k_write_twt_del_dialog, + .open = simple_open +}; + +static const struct file_operations ath11k_fops_twt_pause_dialog = { + .write = ath11k_write_twt_pause_dialog, + .open = simple_open +}; + +static const struct file_operations ath11k_fops_twt_resume_dialog = { + .write = ath11k_write_twt_resume_dialog, + .open = simple_open +}; + +void ath11k_debugfs_add_interface(struct ath11k_vif *arvif) +{ + struct ath11k_base *ab = arvif->ar->ab; + + if (arvif->vif->type != NL80211_IFTYPE_AP && + !(arvif->vif->type == NL80211_IFTYPE_STATION && + test_bit(WMI_TLV_SERVICE_STA_TWT, ab->wmi_ab.svc_map))) + return; + + arvif->debugfs_twt = debugfs_create_dir("twt", + arvif->vif->debugfs_dir); + debugfs_create_file("add_dialog", 0200, arvif->debugfs_twt, + arvif, &ath11k_fops_twt_add_dialog); + + debugfs_create_file("del_dialog", 0200, arvif->debugfs_twt, + arvif, &ath11k_fops_twt_del_dialog); + + debugfs_create_file("pause_dialog", 0200, arvif->debugfs_twt, + arvif, &ath11k_fops_twt_pause_dialog); + + debugfs_create_file("resume_dialog", 0200, arvif->debugfs_twt, + arvif, &ath11k_fops_twt_resume_dialog); +} + +void ath11k_debugfs_remove_interface(struct ath11k_vif *arvif) +{ + if (!arvif->debugfs_twt) + return; + + debugfs_remove_recursive(arvif->debugfs_twt); + arvif->debugfs_twt = NULL; } |