// SPDX-License-Identifier: BSD-3-Clause-Clear
/*
* Copyright (c) 2018-2020 The Linux Foundation. All rights reserved.
* Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#include <linux/export.h>
#include <linux/vmalloc.h>
#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"
#include "hif.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" ,
};
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_debug_dbr *dbr_debug;
struct ath11k_dbg_dbr_data *dbr_data;
struct ath11k_dbg_dbr_entry *entry;
if (id >= WMI_DIRECT_BUF_MAX || event >= ATH11K_DBG_DBR_EVENT_MAX)
return ;
dbr_debug = ar->debug.dbr_debug[id];
if (!dbr_debug)
return ;
if (!dbr_debug->dbr_debug_enabled)
return ;
dbr_data = &dbr_debug->dbr_dbg_data;
spin_lock_bh(&dbr_data->lock);
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);
}
void ath11k_debugfs_fw_stats_process(struct ath11k *ar, struct ath11k_fw_stats *stats)
{
struct ath11k_base *ab = ar->ab;
bool is_end = true ;
/* WMI_REQUEST_PDEV_STAT, WMI_REQUEST_RSSI_PER_CHAIN_STAT and
* WMI_REQUEST_VDEV_STAT requests have been already processed.
*/
if (stats->stats_id == WMI_REQUEST_BCN_STAT) {
if (list_empty(&stats->bcn)) {
ath11k_warn(ab, "empty bcn stats" );
return ;
}
/* Mark end until we reached the count of all started VDEVs
* within the PDEV
*/
if (ar->num_started_vdevs)
is_end = ((++ar->fw_stats.num_bcn_recvd) ==
ar->num_started_vdevs);
list_splice_tail_init(&stats->bcn,
&ar->fw_stats.bcn);
if (is_end)
complete(&ar->fw_stats_done);
}
}
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_mac_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->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_mac_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->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_mac_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->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->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] = {};
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 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 ;
}
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] = {};
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 = {};
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 (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;
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_rxdma_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 < ab->hw_params.max_tx_ring; 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,
};
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] = {};
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))
return 0;
debugfs_create_file("simulate_fw_crash" , 0600, ab->debugfs_soc, ab,
&fops_simulate_fw_crash);
debugfs_create_file("soc_dp_stats" , 0400, 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;
}
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.
*/
}
EXPORT_SYMBOL(ath11k_debugfs_soc_destroy);
void ath11k_debugfs_fw_stats_init(struct ath11k *ar)
{
struct dentry *fwstats_dir = debugfs_create_dir("fw_stats" ,
ar->debug.debugfs_pdev);
ar->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);
}
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 = {};
u32 rx_filter = 0, ring_id, filter, mode;
u8 buf[128] = {};
int i, ret, rx_buf_sz = 0;
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;
}
}
/* Clear rx filter set for monitor mode and rx status */
for (i = 0; i < ab->hw_params.num_rxdma_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,
rx_buf_sz, &tlv_filter);
if (ret) {
ath11k_warn(ar->ab, "failed to set rx filter for monitor status ring\n" );
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;
rx_buf_sz = DP_RX_BUFFER_SIZE;
} 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;
rx_buf_sz = DP_RX_BUFFER_SIZE_LITE;
} else {
rx_buf_sz = DP_RX_BUFFER_SIZE;
tlv_filter = ath11k_mac_mon_status_filter_default;
rx_filter = tlv_filter.rx_filter;
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_rxdma_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,
rx_buf_sz, &tlv_filter);
if (ret) {
ath11k_warn(ab, "failed to set rx filter for monitor status ring\n" );
goto out;
}
}
ath11k_info(ab, "pktlog mode %s\n" ,
((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] = {};
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
};
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] = {};
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: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 = ath11k_sta_to_arsta(sta);
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 = ath11k_sta_to_arsta(sta);
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;
char pdev_name[10];
char buf[100] = {};
snprintf(pdev_name, sizeof (pdev_name), "%s%u" , "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);
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,
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);
}
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 = {};
struct wmi_twt_enable_params twt_params = {};
struct ath11k *ar = arvif->ar;
u8 buf[128] = {};
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 = {};
struct wmi_twt_enable_params twt_params = {};
struct ath11k *ar = arvif->ar;
u8 buf[64] = {};
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 = {};
u8 buf[64] = {};
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 = {};
u8 buf[64] = {};
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_op_vif_add(struct ieee80211_hw *hw,
struct ieee80211_vif *vif)
{
struct ath11k_vif *arvif = ath11k_vif_to_arvif(vif);
struct ath11k_base *ab = arvif->ar->ab;
struct dentry *debugfs_twt;
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 ;
debugfs_twt = debugfs_create_dir("twt" ,
arvif->vif->debugfs_dir);
debugfs_create_file("add_dialog" , 0200, debugfs_twt,
arvif, &ath11k_fops_twt_add_dialog);
debugfs_create_file("del_dialog" , 0200, debugfs_twt,
arvif, &ath11k_fops_twt_del_dialog);
debugfs_create_file("pause_dialog" , 0200, debugfs_twt,
arvif, &ath11k_fops_twt_pause_dialog);
debugfs_create_file("resume_dialog" , 0200, debugfs_twt,
arvif, &ath11k_fops_twt_resume_dialog);
}
Messung V0.5 C=99 H=88 G=93
¤ Dauer der Verarbeitung: 0.13 Sekunden
¤
*© Formatika GbR, Deutschland