blob: eb15353b64c9fc4c6be06522779fa4869d192bac [file] [log] [blame]
// SPDX-License-Identifier: BSD-3-Clause-Clear
/*
* Copyright (c) 2018-2020 The Linux Foundation. All rights reserved.
*/
#include <linux/of.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 "pktlog.h"
#include "qmi.h"
struct dentry *debugfs_ath11k;
struct dentry *debugfs_debug_infra;
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",
};
#ifdef CONFIG_MAC80211_DEBUGFS
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 };
u8 buf[128] = {0};
int ret;
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",
&params.peer_macaddr[0],
&params.peer_macaddr[1],
&params.peer_macaddr[2],
&params.peer_macaddr[3],
&params.peer_macaddr[4],
&params.peer_macaddr[5],
&params.dialog_id,
&params.wake_intvl_us,
&params.wake_intvl_mantis,
&params.wake_dura_us,
&params.sp_offset_us,
&params.twt_cmd,
&params.flag_bcast,
&params.flag_trigger,
&params.flag_flow_type,
&params.flag_protection);
if (ret != 16)
return -EINVAL;
params.vdev_id = arvif->vdev_id;
ret = ath11k_wmi_send_twt_add_dialog_cmd(arvif->ar, &params);
return ret ? ret : count;
}
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 };
u8 buf[64] = {0};
int ret;
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",
&params.peer_macaddr[0],
&params.peer_macaddr[1],
&params.peer_macaddr[2],
&params.peer_macaddr[3],
&params.peer_macaddr[4],
&params.peer_macaddr[5],
&params.dialog_id);
if (ret != 7)
return -EINVAL;
params.vdev_id = arvif->vdev_id;
ret = ath11k_wmi_send_twt_del_dialog_cmd(arvif->ar, &params);
return ret ? ret : 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;
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",
&params.peer_macaddr[0],
&params.peer_macaddr[1],
&params.peer_macaddr[2],
&params.peer_macaddr[3],
&params.peer_macaddr[4],
&params.peer_macaddr[5],
&params.dialog_id);
if (ret != 7)
return -EINVAL;
params.vdev_id = arvif->vdev_id;
ret = ath11k_wmi_send_twt_pause_dialog_cmd(arvif->ar, &params);
return ret ? ret : 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;
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",
&params.peer_macaddr[0],
&params.peer_macaddr[1],
&params.peer_macaddr[2],
&params.peer_macaddr[3],
&params.peer_macaddr[4],
&params.peer_macaddr[5],
&params.dialog_id,
&params.sp_offset_us,
&params.next_twt_size);
if (ret != 9)
return -EINVAL;
params.vdev_id = arvif->vdev_id;
ret = ath11k_wmi_send_twt_resume_dialog_cmd(arvif->ar, &params);
return ret ? ret : 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_twt(struct ath11k_vif *arvif, bool enable)
{
if (!enable && arvif->debugfs_twt) {
debugfs_remove_recursive(arvif->debugfs_twt);
arvif->debugfs_twt = NULL;
return;
}
if (arvif->debugfs_twt)
return;
arvif->debugfs_twt = debugfs_create_dir("twt", arvif->vif->debugfs_dir);
if (IS_ERR_OR_NULL(arvif->debugfs_twt)) {
ath11k_warn(arvif->ar->ab, "failed to create twt debugfs: %p\n", arvif->debugfs_twt);
arvif->debugfs_twt = NULL;
return;
}
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);
}
#endif
static ssize_t ath11k_write_wmi_ctrl_path_stats(struct file *file,
const char __user *ubuf,
size_t count, loff_t *ppos)
{
struct ath11k_vif *arvif = file->private_data;
struct wmi_ctrl_path_stats_cmd_param param = {0};
struct ath11k *ar = arvif->ar;
u8 buf[128] = {0};
int ret;
mutex_lock(&ar->conf_mutex);
if (ar->state != ATH11K_STATE_ON) {
ath11k_warn(ar->ab, "pdev %d not in ON state\n", ar->pdev->pdev_id);
mutex_unlock(&ar->conf_mutex);
return -ENETDOWN;
}
mutex_unlock(&ar->conf_mutex);
ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, ubuf, count);
if (ret < 0) {
return ret;
}
buf[ret] = '\0';
ret = sscanf(buf, "%u %u", &param.stats_id, &param.action);
if (ret != 2)
return -EINVAL;
if (!param.action || param.action > WMI_REQ_CTRL_PATH_STAT_RESET)
return -EINVAL;
ret = ath11k_wmi_send_wmi_ctrl_stats_cmd(arvif->ar, &param);
return ret ? ret : count;
}
int wmi_ctrl_path_pdev_stat(struct ath11k_vif *arvif, char __user *ubuf,
size_t count, loff_t *ppos)
{
const int size = 2048;
char *buf;
u8 i;
int len = 0, ret_val;
u16 index_tx = 0;
u16 index_rx = 0;
char fw_tx_mgmt_subtype[WMI_MAX_STRING_LEN] = {0};
char fw_rx_mgmt_subtype[WMI_MAX_STRING_LEN] = {0};
struct wmi_ctrl_path_stats_list *stats;
struct wmi_ctrl_path_pdev_stats *pdev_stats;
buf = kzalloc(size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
list_for_each_entry (stats, &arvif->ar->debug.wmi_list, list) {
if (!stats) {
break;
}
pdev_stats = stats->stats_ptr;
for (i = 0; i < WMI_MGMT_FRAME_SUBTYPE_MAX; i++) {
index_tx += snprintf(&fw_tx_mgmt_subtype[index_tx],
WMI_MAX_STRING_LEN - index_tx,
" %u:%u,", i,
pdev_stats->tx_mgmt_subtype[i]);
index_rx += snprintf(&fw_rx_mgmt_subtype[index_rx],
WMI_MAX_STRING_LEN - index_rx,
" %u:%u,", i,
pdev_stats->rx_mgmt_subtype[i]);
}
len += scnprintf(buf + len, size - len,
"WMI_CTRL_PATH_PDEV_TX_STATS: \n");
len += scnprintf(buf + len, size - len,
"fw_tx_mgmt_subtype = %s \n",
fw_tx_mgmt_subtype);
len += scnprintf(buf + len, size - len,
"fw_rx_mgmt_subtype = %s \n",
fw_rx_mgmt_subtype);
len += scnprintf(buf + len, size - len,
"scan_fail_dfs_violation_time_ms = %u \n",
pdev_stats->scan_fail_dfs_violation_time_ms);
len += scnprintf(buf + len, size - len,
"nol_chk_fail_last_chan_freq = %u \n",
pdev_stats->nol_chk_fail_last_chan_freq);
len += scnprintf(buf + len, size - len,
"nol_chk_fail_time_stamp_ms = %u \n",
pdev_stats->nol_chk_fail_time_stamp_ms);
len += scnprintf(buf + len, size - len,
"tot_peer_create_cnt = %u \n",
pdev_stats->tot_peer_create_cnt);
len += scnprintf(buf + len, size - len,
"tot_peer_del_cnt = %u \n",
pdev_stats->tot_peer_del_cnt);
len += scnprintf(buf + len, size - len,
"tot_peer_del_resp_cnt = %u \n",
pdev_stats->tot_peer_del_resp_cnt);
len += scnprintf(buf + len, size - len,
"vdev_pause_fail_rt_to_sched_algo_fifo_full_cnt = %u \n",
pdev_stats->vdev_pause_fail_rt_to_sched_algo_fifo_full_cnt);
}
ret_val = simple_read_from_buffer(ubuf, count, ppos, buf, len);
ath11k_wmi_crl_path_stats_list_free(&arvif->ar->debug.wmi_list);
kfree(buf);
return ret_val;
}
int wmi_ctrl_path_cal_stat(struct ath11k_vif *arvif, char __user *ubuf,
size_t count, loff_t *ppos)
{
const int size = 4096;
char *buf;
u8 cal_type_mask, cal_prof_mask, is_periodic_cal;
int len = 0, ret_val;
struct wmi_ctrl_path_stats_list *stats;
struct wmi_ctrl_path_cal_stats *cal_stats;
buf = kzalloc(size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
len += scnprintf(buf + len, size - len,
"WMI_CTRL_PATH_CAL_STATS\n");
len += scnprintf(buf + len, size - len,
"%-25s %-25s %-17s %-16s %-16s %-16s\n",
"cal_profile", "cal_type",
"cal_triggered_cnt", "cal_fail_cnt",
"cal_fcs_cnt", "cal_fcs_fail_cnt");
list_for_each_entry (stats, &arvif->ar->debug.wmi_list, list) {
if (!stats)
break;
cal_stats = stats->stats_ptr;
cal_prof_mask = FIELD_GET(WMI_CTRL_PATH_CAL_PROF_MASK,
cal_stats->cal_info);
if (cal_prof_mask == WMI_CTRL_PATH_STATS_CAL_PROFILE_INVALID)
continue;
cal_type_mask = FIELD_GET(WMI_CTRL_PATH_CAL_TYPE_MASK,
cal_stats->cal_info);
is_periodic_cal = FIELD_GET(WMI_CTRL_PATH_IS_PERIODIC_CAL,
cal_stats->cal_info);
if (!is_periodic_cal) {
len +=
scnprintf(buf + len, size - len,
"%-25s %-25s %-17d %-16d %-16d %-16d\n",
wmi_ctrl_path_cal_prof_id_to_name(cal_prof_mask),
wmi_ctrl_path_cal_type_id_to_name(cal_type_mask),
cal_stats->cal_triggered_cnt,
cal_stats->cal_fail_cnt,
cal_stats->cal_fcs_cnt,
cal_stats->cal_fcs_fail_cnt);
} else {
len +=
scnprintf(buf + len, size - len,
"%-25s %-25s %-17d %-16d %-16d %-16d\n",
"PERIODIC_CAL",
wmi_ctrl_path_periodic_cal_type_id_to_name(cal_type_mask),
cal_stats->cal_triggered_cnt,
cal_stats->cal_fail_cnt,
cal_stats->cal_fcs_cnt,
cal_stats->cal_fcs_fail_cnt);
}
}
ret_val = simple_read_from_buffer(ubuf, count, ppos, buf, len);
ath11k_wmi_crl_path_stats_list_free(&arvif->ar->debug.wmi_list);
kfree(buf);
return ret_val;
}
int wmi_ctrl_path_btcoex_stat(struct ath11k_vif *arvif, const char __user *ubuf,
size_t count, loff_t *ppos)
{
struct wmi_ctrl_path_stats_list *stats;
struct wmi_ctrl_path_btcoex_stats *btcoex_stats;
const int size = 2048;
int len = 0, ret_val;
char *buf;
buf = kzalloc(size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
list_for_each_entry(stats, &arvif->ar->debug.wmi_list, list) {
if (!stats)
break;
btcoex_stats = stats->stats_ptr;
len += scnprintf(buf + len, size - len,
"WMI_CTRL_PATH_BTCOEX_STATS:\n");
len += scnprintf(buf + len, size - len,
"pdev_id = %u\n",
btcoex_stats->pdev_id);
len += scnprintf(buf + len, size - len,
"bt_tx_req_cntr = %u\n",
btcoex_stats->bt_tx_req_cntr);
len += scnprintf(buf + len, size - len,
"bt_rx_req_cntr = %u\n",
btcoex_stats->bt_rx_req_cntr);
len += scnprintf(buf + len, size - len,
"bt_req_nack_cntr = %u\n",
btcoex_stats->bt_req_nack_cntr);
len += scnprintf(buf + len, size - len,
"wl_tx_req_nack_schd_bt_reason_cntr = %u\n",
btcoex_stats->wl_tx_req_nack_schd_bt_reason_cntr);
len += scnprintf(buf + len, size - len,
"wl_tx_req_nack_current_bt_reason_cntr = %u\n",
btcoex_stats->wl_tx_req_nack_current_bt_reason_cntr);
len += scnprintf(buf + len, size - len,
"wl_tx_req_nack_other_wlan_tx_reason_cntr = %u\n",
btcoex_stats->wl_tx_req_nack_other_wlan_tx_reason_cntr);
len += scnprintf(buf + len, size - len,
"wl_in_tx_abort_cntr = %u\n",
btcoex_stats->wl_in_tx_abort_cntr);
len += scnprintf(buf + len, size - len,
"wl_tx_auto_resp_req_cntr = %u\n",
btcoex_stats->wl_tx_auto_resp_req_cntr);
len += scnprintf(buf + len, size - len,
"wl_tx_req_ack_cntr = %u\n",
btcoex_stats->wl_tx_req_ack_cntr);
len += scnprintf(buf + len, size - len,
"wl_tx_req_cntr = %u\n",
btcoex_stats->wl_tx_req_cntr);
}
ret_val = simple_read_from_buffer(ubuf, count, ppos, buf, len);
ath11k_wmi_crl_path_stats_list_free(&arvif->ar->debug.wmi_list);
kfree(buf);
return ret_val;
}
static ssize_t ath11k_read_wmi_ctrl_path_stats(struct file *file,
char __user *ubuf,
size_t count, loff_t *ppos)
{
struct ath11k_vif *arvif = file->private_data;
int ret_val = 0;
u32 tagid = 0;
tagid = arvif->ar->debug.wmi_ctrl_path_stats_tagid;
switch (tagid) {
case WMI_CTRL_PATH_PDEV_STATS:
ret_val = wmi_ctrl_path_pdev_stat(arvif, ubuf, count, ppos);
break;
case WMI_CTRL_PATH_CAL_STATS:
ret_val = wmi_ctrl_path_cal_stat(arvif, ubuf, count, ppos);
break;
case WMI_CTRL_PATH_BTCOEX_STATS:
ret_val = wmi_ctrl_path_btcoex_stat(arvif, ubuf, count, ppos);
break;
/* Add case for newly wmi ctrl path added stats here */
default :
/* Unsupported tag */
break;
}
return ret_val;
}
static const struct file_operations ath11k_fops_wmi_ctrl_stats = {
.write = ath11k_write_wmi_ctrl_path_stats,
.open = simple_open,
.read = ath11k_read_wmi_ctrl_path_stats,
};
static ssize_t ath11k_write_mac_filter(struct file *file,
const char __user *ubuf,
size_t count, loff_t *ppos)
{
struct ath11k_vif *arvif = file->private_data;
struct ath11k_mac_filter *mac_filter;
struct ath11k_mac_filter *i;
struct ath11k *ar = arvif->ar;
bool found = false;
u8 buf[64] = {0};
int ret;
unsigned int val;
ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, ubuf, count);
if (ret < 0)
return ret;
mac_filter = kzalloc(sizeof(*mac_filter), GFP_ATOMIC);
if (!mac_filter)
return -ENOMEM;
buf[ret] = '\0';
ret = sscanf(buf, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx %u",
&mac_filter->peer_mac[0], &mac_filter->peer_mac[1],
&mac_filter->peer_mac[2], &mac_filter->peer_mac[3],
&mac_filter->peer_mac[4], &mac_filter->peer_mac[5],
&val);
mutex_lock(&ar->conf_mutex);
if (!list_empty(&arvif->mac_filters)) {
list_for_each_entry(i, &arvif->mac_filters, list) {
if (ether_addr_equal(i->peer_mac, mac_filter->peer_mac)) {
found = true;
break;
}
}
}
if (!found && val) {
list_add(&mac_filter->list, &arvif->mac_filters);
arvif->mac_filter_count++;
} else if (found && !val) {
list_del(&i->list);
kfree(i);
arvif->mac_filter_count--;
}
mutex_unlock(&ar->conf_mutex);
return count;
}
static ssize_t ath11k_read_mac_filter(struct file *file, char __user *ubuf,
size_t count, loff_t *ppos)
{
struct ath11k_vif *arvif = file->private_data;
struct ath11k *ar = arvif->ar;
struct ath11k_mac_filter *i;
int len = 0, ret;
char *buf;
int size;
size = (arvif->mac_filter_count * 20) + 25;
buf = kzalloc(size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
mutex_lock(&ar->conf_mutex);
if (list_empty(&arvif->mac_filters)) {
len += scnprintf(buf + len, size - len, "List is Empty\n");
goto exit;
}
len += scnprintf(buf + len, size - len, "Mac address entries :\n");
list_for_each_entry(i, &arvif->mac_filters, list) {
len += scnprintf(buf + len, size - len, "%pM\n", i->peer_mac);
}
exit:
mutex_unlock(&ar->conf_mutex);
ret = simple_read_from_buffer(ubuf, count, ppos, buf, len);
kfree(buf);
return ret;
}
static const struct file_operations fops_mac_filter = {
.read = ath11k_read_mac_filter,
.write = ath11k_write_mac_filter,
.open = simple_open
};
void ath11k_debugfs_dbg_mac_filter(struct ath11k_vif *arvif)
{
debugfs_create_file("mac_filter", 0644,
arvif->vif->debugfs_dir, arvif,
&fops_mac_filter);
INIT_LIST_HEAD(&arvif->mac_filters);
}
void ath11k_debugfs_wmi_ctrl_stats(struct ath11k_vif *arvif)
{
debugfs_create_file("wmi_ctrl_stats", 0644,
arvif->vif->debugfs_dir, arvif,
&ath11k_fops_wmi_ctrl_stats);
INIT_LIST_HEAD(&arvif->ar->debug.wmi_list);
init_completion(&arvif->ar->debug.wmi_ctrl_path_stats_rcvd);
}
static ssize_t ath11k_wbm_tx_comp_stats_read(struct file *file,
char __user *user_buf,
size_t count,
loff_t *ppos)
{
struct ath11k_vif *arvif = file->private_data;
struct ath11k *ar = arvif->ar;
char buf[256] = {0};
int len = 0;
char *fields[] = {[HAL_WBM_REL_HTT_TX_COMP_STATUS_OK] = "Acked pkt count",
[HAL_WBM_REL_HTT_TX_COMP_STATUS_TTL] = "Status ttl pkt count",
[HAL_WBM_REL_HTT_TX_COMP_STATUS_DROP] = "Dropped pkt count",
[HAL_WBM_REL_HTT_TX_COMP_STATUS_REINJ] = "Reinj pkt count",
[HAL_WBM_REL_HTT_TX_COMP_STATUS_INSPECT] = "Inspect pkt count",
[HAL_WBM_REL_HTT_TX_COMP_STATUS_MEC_NOTIFY] = "MEC notify pkt count"};
int idx;
mutex_lock(&ar->conf_mutex);
if (!arvif->is_started) {
len += scnprintf(buf + len, sizeof(buf) - len, "vif not started\n");
goto out;
}
len += scnprintf(buf + len, sizeof(buf) - len, "WBM tx completion stats of data pkts :\n");
for(idx = 0; idx <= HAL_WBM_REL_HTT_TX_COMP_STATUS_MEC_NOTIFY; idx++) {
len += scnprintf(buf + len, sizeof(buf) - len,
"%-23s : %llu\n",
fields[idx],
arvif->wbm_tx_comp_stats[idx]);
}
out:
mutex_unlock(&ar->conf_mutex);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static ssize_t ath11k_wbm_tx_comp_stats_write(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k_vif *arvif = file->private_data;
struct ath11k *ar = arvif->ar;
u8 reset;
int ret;
ret = kstrtou8_from_user(user_buf, count, 0, &reset);
if (ret || reset != 1)
return -EINVAL;
mutex_lock(&ar->conf_mutex);
if (arvif->is_started)
memset(arvif->wbm_tx_comp_stats, 0, sizeof(arvif->wbm_tx_comp_stats));
mutex_unlock(&ar->conf_mutex);
return count;
}
static const struct file_operations fops_wbm_tx_comp_stats = {
.write = ath11k_wbm_tx_comp_stats_write,
.read = ath11k_wbm_tx_comp_stats_read,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
void ath11k_debugfs_wbm_tx_comp_stats(struct ath11k_vif *arvif)
{
debugfs_create_file("wbm_tx_completion_stats", S_IRUSR | S_IWUSR,
arvif->vif->debugfs_dir, arvif, &fops_wbm_tx_comp_stats);
}
static ssize_t ath11k_write_ampdu_aggr_size(struct file *file,
const char __user *ubuf,
size_t count, loff_t *ppos)
{
struct ath11k_vif *arvif = file->private_data;
struct ath11k_base *ab = arvif->ar->ab;
struct ath11k *ar = arvif->ar;
unsigned int tx_aggr_size = 0;
int ret;
struct set_custom_aggr_size_params params = {0};
mutex_lock(&ar->conf_mutex);
if (ar->state != ATH11K_STATE_ON) {
ath11k_warn(ar->ab, "pdev %d not in ON state\n", ar->pdev->pdev_id);
mutex_unlock(&ar->conf_mutex);
return -ENETDOWN;
}
mutex_unlock(&ar->conf_mutex);
if (kstrtouint_from_user(ubuf, count, 0, &tx_aggr_size))
return -EINVAL;
if (tx_aggr_size > ATH11K_CONFIG_AGGR_MAX_AMPDU_SIZE) {
ath11k_warn(ab, "Valid AMPDU Aggregation Size is in the range 0-255");
return -EINVAL;
}
params.aggr_type = WMI_VDEV_CUSTOM_AGGR_TYPE_AMPDU;
params.tx_aggr_size = tx_aggr_size;
params.rx_aggr_size_disable = true;
params.vdev_id = arvif->vdev_id;
ret = ath11k_wmi_send_aggr_size_cmd(arvif->ar, &params);
if (ret)
ath11k_warn(ab, "Failed to set ampdu config vdev_id %d"
"ret %d \n",params.vdev_id, ret);
return ret ? ret : count;
}
static const struct file_operations fops_ampdu_aggr_size = {
.write = ath11k_write_ampdu_aggr_size,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_write_amsdu_aggr_size(struct file *file,
const char __user *ubuf,
size_t count, loff_t *ppos)
{
struct ath11k_vif *arvif = file->private_data;
struct ath11k_base *ab = arvif->ar->ab;
unsigned int tx_aggr_size = 0;
int ret;
struct set_custom_aggr_size_params params = {0};
if (kstrtouint_from_user(ubuf, count, 0, &tx_aggr_size))
return -EINVAL;
if (tx_aggr_size > ATH11K_CONFIG_AGGR_MAX_AMSDU_SIZE) {
ath11k_warn(ab, "Valid AMSDU Aggregation size is in the range 0-7");
return -EINVAL;
}
params.aggr_type = WMI_VDEV_CUSTOM_AGGR_TYPE_AMSDU;
params.tx_aggr_size = tx_aggr_size;
params.rx_aggr_size_disable = true;
params.vdev_id = arvif->vdev_id;
ret = ath11k_wmi_send_aggr_size_cmd(arvif->ar, &params);
if (ret)
ath11k_warn(ab, "Failed to set amsdu config vdev_id %d"
"ret %d \n",params.vdev_id, ret);
return ret ? ret : count;
}
static const struct file_operations fops_amsdu_aggr_size = {
.write = ath11k_write_amsdu_aggr_size,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
void ath11k_debug_aggr_size_config_init(struct ath11k_vif *arvif)
{
debugfs_create_file("ampdu_aggr_size", 0644,
arvif->vif->debugfs_dir, arvif,
&fops_ampdu_aggr_size);
debugfs_create_file("amsdu_aggr_size", 0644,
arvif->vif->debugfs_dir, arvif,
&fops_amsdu_aggr_size);
}
static void ath11k_debugfs_fw_stats_reset(struct ath11k *ar)
{
spin_lock_bh(&ar->data_lock);
ar->fw_stats_done = false;
ath11k_fw_stats_reset(ar);
spin_unlock_bh(&ar->data_lock);
}
void ath11k_debugfs_fw_stats_process(struct ath11k *ar, struct ath11k_fw_stats *stats)
{
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;
/* WMI_REQUEST_PDEV_STAT request has been already processed. Hence, not doing it again */
if (stats->stats_id == WMI_REQUEST_VDEV_STAT) {
if (list_empty(&stats->vdevs)) {
ath11k_warn(ab, "empty vdev stats");
return;
}
/* 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->fw_stats_done = true;
num_vdev = 0;
}
return;
}
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
*/
is_end = ((++num_bcn) == ar->num_started_vdevs);
list_splice_tail_init(&stats->bcn,
&ar->debug.fw_stats.bcn);
if (is_end) {
ar->fw_stats_done = true;
num_bcn = 0;
}
}
}
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->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->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->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_read_trace_qdss(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
const char buf[] =
"'1` - this will start qdss trace collection\n"
"`0` - this will stop and save the qdss trace collection\n";
return simple_read_from_buffer(user_buf, count, ppos, buf, strlen(buf));
}
static ssize_t
ath11k_write_trace_qdss(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;
int i, ret, radioup = 0;
bool qdss_enable;
if (kstrtobool_from_user(user_buf, count, &qdss_enable))
return -EINVAL;
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;
}
}
if (radioup == 0) {
ath11k_err(ab, "radio is not up\n");
ret = -ENETDOWN;
goto exit;
}
if (qdss_enable) {
if (ab->is_qdss_tracing) {
ret = count;
goto exit;
}
ath11k_config_qdss(ab);
} else {
if (!ab->is_qdss_tracing) {
ret = count;
goto exit;
}
if (ab->hw_params.is_qdss_support) {
ret = ath11k_send_qdss_trace_mode_req(ab,
QMI_WLANFW_QDSS_TRACE_OFF_V01);
if (ret < 0)
ath11k_warn(ab,
"Failed to trace QDSS: %d\n", ret);
}
}
ret = count;
exit:
return ret;
}
static const struct file_operations fops_trace_qdss = {
.read = ath11k_read_trace_qdss,
.write = ath11k_write_trace_qdss,
.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 (ar->monitor_started) {
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;
ath11k_nss_ext_rx_stats(ar->ab, &tlv_filter);
}
ar->debug.rx_filter = tlv_filter.rx_filter;
tlv_filter.offset_valid = false;
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;
}
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;
ATH11K_MEMORY_STATS_INC(ab, malloc_size, size);
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: handled %u dropped %u\n",
rxdma_err[i], soc_stats->rxdma_error[i],
soc_stats->rxdma_error_drop[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: handled %u dropped %u\n",
reo_err[i], soc_stats->reo_error[i],
soc_stats->reo_error_drop[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, "\nTCL Ring idr 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.idr_na[i]);
len += scnprintf(buf + len, size - len, "\nMax Transmit Failures: %d\n",
atomic_read(&soc_stats->tx_err.max_fail));
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);
ATH11K_MEMORY_STATS_DEC(ab, malloc_size, size);
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_rx_hash(struct file *file,
const char __user *ubuf,
size_t count, loff_t *ppos)
{
struct ath11k_base *ab = file->private_data;
struct ath11k_pdev *pdev;
u32 rx_hash;
u8 buf[128] = {0};
int ret, i, radioup = 0;
for (i = 0; i < ab->num_radios; i++) {
pdev = &ab->pdevs[i];
if (pdev && pdev->ar) {
radioup = 1;
break;
}
}
if (radioup == 0) {
ath11k_err(ab, "radio is not up\n");
ret = -ENETDOWN;
goto exit;
}
ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, ubuf, count);
if (ret < 0)
goto exit;
buf[ret] = '\0';
ret = sscanf(buf, "%x", &rx_hash);
if (!ret) {
ret = -EINVAL;
goto exit;
}
if (rx_hash != ab->rx_hash) {
ab->rx_hash = rx_hash;
if (rx_hash)
ath11k_hal_reo_hash_setup(ab, rx_hash);
}
ret = count;
exit:
return ret;
}
static const struct file_operations fops_soc_rx_hash = {
.open = simple_open,
.write = ath11k_write_rx_hash,
};
static void ath11k_debug_config_mon_status(struct ath11k *ar, bool enable)
{
struct htt_rx_ring_tlv_filter tlv_filter = {0};
struct ath11k_base *ab = ar->ab;
int i;
u32 ring_id;
if (enable)
tlv_filter = ath11k_mac_mon_status_filter_default;
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;
ath11k_dp_tx_htt_rx_filter_setup(ar->ab, ring_id,
ar->dp.mac_id + i,
HAL_RXDMA_MONITOR_STATUS,
DP_RX_BUFFER_SIZE,
&tlv_filter);
}
}
static ssize_t ath11k_write_stats_disable(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;
bool disable;
int ret, i, radioup = 0;
u32 mask = 0;
for (i = 0; i < ab->num_radios; i++) {
pdev = &ab->pdevs[i];
if (pdev && pdev->ar) {
radioup = 1;
break;
}
}
if (radioup == 0) {
ath11k_err(ab, "radio is not up\n");
ret = -ENETDOWN;
goto exit;
}
if (kstrtobool_from_user(user_buf, count, &disable))
return -EINVAL;
if (disable != ab->stats_disable) {
ab->stats_disable = disable;
for (i = 0; i < ab->num_radios; i++) {
pdev = &ab->pdevs[i];
if (pdev && pdev->ar) {
ath11k_debug_config_mon_status(pdev->ar, !disable);
if (!disable)
mask = HTT_PPDU_STATS_TAG_DEFAULT;
ath11k_dp_tx_htt_h2t_ppdu_stats_req(pdev->ar, mask);
}
}
}
ret = count;
exit:
return ret;
}
static const struct file_operations fops_soc_stats_disable = {
.open = simple_open,
.write = ath11k_write_stats_disable,
};
static ssize_t ath11k_debug_write_fw_recovery(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k_base *ab = file->private_data;
struct ath11k *ar;
struct ath11k_pdev *pdev;
struct device *dev = ab->dev;
bool multi_pd_arch = false;
unsigned int value;
int ret, i;
if (kstrtouint_from_user(user_buf, count, 0, &value))
return -EINVAL;
if (value < ATH11K_FW_RECOVERY_DISABLE ||
value > ATH11K_FW_RECOVERY_ENABLE_SSR_ONLY) {
ath11k_warn(ab, "Please enter: 0 = Disable, 1 = Enable (auto recover),"
"2 = Enable SSR only");
ret = -EINVAL;
goto exit;
}
for (i = 0; i < ab->num_radios; i++) {
pdev = &ab->pdevs[i];
ar = pdev->ar;
if (ar && ar->state == ATH11K_STATE_ON)
break;
}
multi_pd_arch = of_property_read_bool(dev->of_node, "qcom,multipd_arch");
if (multi_pd_arch) {
if (value == ATH11K_FW_RECOVERY_DISABLE ||
value == ATH11K_FW_RECOVERY_ENABLE_SSR_ONLY) {
ath11k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_MPD_USERPD_SSR,
0, ar->pdev->pdev_id);
} else if (value == ATH11K_FW_RECOVERY_ENABLE_AUTO)
ath11k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_MPD_USERPD_SSR,
1, ar->pdev->pdev_id);
}
ab->fw_recovery_support = value ? true : false;
ret = count;
exit:
return ret;
}
static ssize_t ath11k_debug_read_fw_recovery(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k_base *ab = file->private_data;
char buf[32];
size_t len;
len = scnprintf(buf, sizeof(buf), "%u\n", ab->fw_recovery_support);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static const struct file_operations fops_fw_recovery = {
.read = ath11k_debug_read_fw_recovery,
.write = ath11k_debug_write_fw_recovery,
.open = simple_open,
};
static ssize_t
ath11k_debug_read_enable_memory_stats(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k_base *ab = file->private_data;
char buf[10];
size_t len;
len = scnprintf(buf, sizeof(buf), "%d\n", ab->enable_memory_stats);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static ssize_t
ath11k_debug_write_enable_memory_stats(struct file *file,
const char __user *ubuf,
size_t count, loff_t *ppos)
{
struct ath11k_base *ab = file->private_data;
bool enable;
int ret;
if (kstrtobool_from_user(ubuf, count, &enable))
return -EINVAL;
if (enable == ab->enable_memory_stats) {
ret = count;
goto exit;
}
ab->enable_memory_stats = enable;
ret = count;
exit:
return ret;
}
static const struct file_operations fops_enable_memory_stats = {
.read = ath11k_debug_read_enable_memory_stats,
.write = ath11k_debug_write_enable_memory_stats,
.owner = THIS_MODULE,
.llseek = default_llseek,
.open = simple_open,
};
static ssize_t ath11k_debug_dump_memory_stats(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k_base *ab = file->private_data;
struct ath11k_memory_stats *memory_stats = &ab->memory_stats;
int len = 0, retval;
const int size = 4096;
char *buf;
buf = kzalloc(size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
len += scnprintf(buf + len, size - len, "MEMORY STATS IN BYTES:\n");
len += scnprintf(buf + len, size - len, "malloc size : %u\n",
atomic_read(&memory_stats->malloc_size));
len += scnprintf(buf + len, size - len, "ce_ring_alloc size: %u\n",
atomic_read(&memory_stats->ce_ring_alloc));
len += scnprintf(buf + len, size - len, "dma_alloc size:: %u\n",
atomic_read(&memory_stats->dma_alloc));
len += scnprintf(buf + len, size - len, "htc_skb_alloc size: %u\n",
atomic_read(&memory_stats->htc_skb_alloc));
len += scnprintf(buf + len, size - len, "wmi tx skb alloc size: %u\n",
atomic_read(&memory_stats->wmi_tx_skb_alloc));
len += scnprintf(buf + len, size - len, "per peer object: %u\n",
atomic_read(&memory_stats->per_peer_object));
len += scnprintf(buf + len, size - len, "rx_post_buf size: %u\n",
atomic_read(&memory_stats->ce_rx_pipe));
len += scnprintf(buf + len, size - len, "Total size: %u\n\n",
(atomic_read(&memory_stats->malloc_size) +
atomic_read(&memory_stats->ce_ring_alloc) +
atomic_read(&memory_stats->dma_alloc) +
atomic_read(&memory_stats->htc_skb_alloc) +
atomic_read(&memory_stats->wmi_tx_skb_alloc) +
atomic_read(&memory_stats->per_peer_object) +
atomic_read(&memory_stats->ce_rx_pipe)));
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_memory_stats = {
.read = ath11k_debug_dump_memory_stats,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_write_ce_latency_stats(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k_base *ab = file->private_data;
bool enable;
int ret;
if (kstrtobool_from_user(user_buf, count, &enable))
return -EINVAL;
if (enable == ab->ce_latency_stats_enable) {
ret = count;
goto exit;
}
ab->ce_latency_stats_enable = enable;
ret = count;
exit:
return ret;
}
static ssize_t ath11k_read_ce_latency_stats(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k_base *ab = file->private_data;
int len = 0, retval;
const int size = 12288;
char *buf;
struct ath11k_ce_pipe *ce_pipe;
int i, j;
unsigned int last_sched, last_exec;
char *ce_time_dur[CE_TIME_DURATION_MAX] = {
"ce_time_dur_100US", "ce_time_dur_200US", "ce_time_dur_300US",
"ce_time_dur_400US", "ce_time_dur_500US"};
buf = kzalloc(size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
len += scnprintf(buf + len, size - len, "CE_LATENCY_STATS:\n");
for (i = 0; i < ab->hw_params.ce_count; i++) {
ce_pipe = &ab->ce.ce_pipe[i];
len += scnprintf(buf + len, size - len, "CE_id %u ", i);
len += scnprintf(buf + len, size - len, "pipe_num %d ",
ce_pipe->pipe_num);
len += scnprintf(buf + len, size - len, "%ums before, ",
jiffies_to_msecs(jiffies - ce_pipe->timestamp));
len += scnprintf(buf + len, size - len, "sched_delay_gt_500US %u, ",
ce_pipe->sched_delay_gt_500US);
len += scnprintf(buf + len, size - len, "exec_delay_gt_500US %u,\n",
ce_pipe->sched_delay_gt_500US);
for (j = 0; j < CE_TIME_DURATION_MAX; j++) {
last_sched = jiffies_to_msecs(jiffies -
ce_pipe->tracker[j].sched_last_update);
last_exec = jiffies_to_msecs(jiffies -
ce_pipe->tracker[j].exec_last_update);
len += scnprintf(buf + len, size - len, "%-17s,\t ", ce_time_dur[j]);
len += scnprintf(buf + len, size - len, "last_sched_before %10ums,\t ",
((ce_pipe->tracker[j].sched_last_update > 0) ? last_sched : 0));
len += scnprintf(buf + len, size - len, "tot_sched_cnt %20llu,\t ",
ce_pipe->tracker[j].sched_count);
len += scnprintf(buf + len, size - len, "last_exec_before %10ums,\t ",
((ce_pipe->tracker[j].exec_last_update > 0) ? last_exec : 0));
len += scnprintf(buf + len, size - len, "tot_exec_cnt %20llu\n",
ce_pipe->tracker[j].exec_count);
}
}
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_ce_latency_stats = {
.write = ath11k_write_ce_latency_stats,
.open = simple_open,
.read = ath11k_read_ce_latency_stats,
};
static ssize_t ath11k_read_fw_dbglog(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k_base *ab = file->private_data;
size_t len;
char buf[128];
len = scnprintf(buf, sizeof(buf), "%u 0x%016llx\n",
ab->fw_dbglog_param, ab->fw_dbglog_val);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static ssize_t ath11k_write_fw_dbglog(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k_base *ab = file->private_data;
struct ath11k *ar = ab->pdevs[0].ar;
char buf[128] = {0};
unsigned int param;
u64 value;
int ret;
if (!ar)
return -EINVAL;
mutex_lock(&ar->conf_mutex);
ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos,
user_buf, count);
if (ret <= 0)
goto out;
ret = sscanf(buf, "%u %llx", &param, &value);
if (ret != 2) {
ret = -EINVAL;
goto out;
}
ab->fw_dbglog_param = param;
ab->fw_dbglog_val = value;
ret = ath11k_wmi_dbglog_cfg(ar, param, value);
if (ret) {
ath11k_warn(ar->ab, "dbglog cfg 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 = {
.read = ath11k_read_fw_dbglog,
.write = ath11k_write_fw_dbglog,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_debugfs_write_warm_hw_reset(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;
bool val;
if (kstrtobool_from_user(user_buf, count, &val))
return -EFAULT;
if (!val)
return -EINVAL;
mutex_lock(&ar->conf_mutex);
if (ar->state != ATH11K_STATE_ON) {
ret = -ENETDOWN;
goto exit;
}
ret = ath11k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_PDEV_RESET,
WMI_RST_MODE_WARM_RESET, pdev->pdev_id);
if (ret) {
ath11k_warn(ar->ab, "failed to enable warm hw reset: %d\n", ret);
goto exit;
}
ret = count;
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static const struct file_operations fops_warm_hw_reset = {
.write = ath11k_debugfs_write_warm_hw_reset,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_write_non_aggr_sw_retry_thold(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;
u8 retry_thold;
int ret;
if (kstrtou8_from_user(user_buf, count, 0, &retry_thold))
return -EINVAL;
if (retry_thold > ATH11K_MAX_AGGR_RETRY_COUNT)
return -EINVAL;
mutex_lock(&ar->conf_mutex);
if (ar->non_aggr_sw_retry_thold == retry_thold) {
ret = count;
goto exit;
}
if (ar->state != ATH11K_STATE_ON) {
ret = -ENETDOWN;
goto exit;
}
ret = ath11k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_NON_AGG_SW_RETRY_TH,
retry_thold, pdev->pdev_id);
if (ret) {
ath11k_warn(ar->ab, "failed to set non-aggr retry thold: %d\n", ret);
goto exit;
}
ar->non_aggr_sw_retry_thold = retry_thold;
ret = count;
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static ssize_t ath11k_read_non_aggr_sw_retry_thold(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
int len = 0;
char buf[32];
mutex_lock(&ar->conf_mutex);
len = scnprintf(buf, sizeof(buf) - len, "%d\n",
ar->non_aggr_sw_retry_thold);
mutex_unlock(&ar->conf_mutex);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static const struct file_operations fops_non_aggr_sw_retry_thold = {
.read = ath11k_read_non_aggr_sw_retry_thold,
.write = ath11k_write_non_aggr_sw_retry_thold,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_write_aggr_sw_retry_thold(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;
u8 retry_thold;
int ret;
if (kstrtou8_from_user(user_buf, count, 0, &retry_thold))
return -EINVAL;
if (retry_thold > ATH11K_MAX_AGGR_RETRY_COUNT)
return -EINVAL;
mutex_lock(&ar->conf_mutex);
if (ar->aggr_sw_retry_thold == retry_thold) {
ret = count;
goto exit;
}
if (ar->state != ATH11K_STATE_ON) {
ret = -ENETDOWN;
goto exit;
}
ret = ath11k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_AGG_SW_RETRY_TH,
retry_thold, pdev->pdev_id);
if (ret) {
ath11k_warn(ar->ab, "failed to set aggr retry thold: %d\n", ret);
goto exit;
}
ar->aggr_sw_retry_thold = retry_thold;
ret = count;
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static ssize_t ath11k_read_aggr_sw_retry_thold(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
int len = 0;
char buf[32];
mutex_lock(&ar->conf_mutex);
len = scnprintf(buf, sizeof(buf) - len, "%d\n",
ar->aggr_sw_retry_thold);
mutex_unlock(&ar->conf_mutex);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static const struct file_operations fops_aggr_sw_retry_thold = {
.read = ath11k_read_aggr_sw_retry_thold,
.write = ath11k_write_aggr_sw_retry_thold,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_debugfs_fw_reset_stats_read(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k_base *ab = file->private_data;
int ret;
size_t len = 0, buf_len = 500;
char *buf;
buf = kmalloc(buf_len, GFP_KERNEL);
if (!buf)
return -ENOMEM;
spin_lock_bh(&ab->base_lock);
len += scnprintf(buf + len, buf_len - len,
"fw_crash_counter\t\t%d\n", ab->stats.fw_crash_counter);
spin_unlock_bh(&ab->base_lock);
ret = simple_read_from_buffer(user_buf, count, ppos, buf, len);
kfree(buf);
return ret;
}
static const struct file_operations fops_fw_reset_stats = {
.open = simple_open,
.read = ath11k_debugfs_fw_reset_stats_read,
.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);
if (ab->hw_params.is_qdss_support)
debugfs_create_file("trace_qdss", 0600, ab->debugfs_soc, ab,
&fops_trace_qdss);
debugfs_create_file("soc_dp_stats", 0600, ab->debugfs_soc, ab,
&fops_soc_dp_stats);
debugfs_create_file("set_fw_recovery", 0600, ab->debugfs_soc, ab,
&fops_fw_recovery);
debugfs_create_file("enable_memory_stats", 0600, ab->debugfs_soc,
ab, &fops_enable_memory_stats);
debugfs_create_file("memory_stats", 0600, ab->debugfs_soc, ab,
&fops_memory_stats);
debugfs_create_file("ce_latency_stats", 0600, ab->debugfs_soc, ab,
&fops_ce_latency_stats);
debugfs_create_file("fw_dbglog_config", 0600, ab->debugfs_soc, ab,
&fops_fw_dbglog);
debugfs_create_file("rx_hash", 0600, ab->debugfs_soc, ab,
&fops_soc_rx_hash);
debugfs_create_file("fw_reset_stats", 0400, ab->debugfs_soc, ab,
&fops_fw_reset_stats);
return 0;
}
void ath11k_debugfs_pdev_destroy(struct ath11k_base *ab)
{
}
int ath11k_debugfs_soc_create(struct ath11k_base *ab)
{
struct device *dev = ab->dev;
char soc_name[64] = {0};
if (!(IS_ERR_OR_NULL(ab->debugfs_soc)))
return 0;
if (ab->hif.bus == ATH11K_BUS_AHB) {
if (ab->userpd_id) {
snprintf(soc_name, sizeof(soc_name), "%s_%d",
ab->hw_params.name, ab->userpd_id);
} else {
snprintf(soc_name, sizeof(soc_name), "%s", ab->hw_params.name);
}
} else {
snprintf(soc_name, sizeof(soc_name), "%s_%s",
ab->hw_params.name, dev_name(dev));
}
ab->debugfs_soc = debugfs_create_dir(soc_name, debugfs_ath11k);
if (IS_ERR_OR_NULL(ab->debugfs_soc)) {
if (IS_ERR(ab->debugfs_soc))
return PTR_ERR(ab->debugfs_soc);
return -ENOMEM;
}
debugfs_create_file("stats_disable", 0600, ab->debugfs_soc, ab,
&fops_soc_stats_disable);
return 0;
}
void ath11k_debugfs_soc_destroy(struct ath11k_base *ab)
{
debugfs_remove_recursive(ab->debugfs_soc);
ab->debugfs_soc = NULL;
}
EXPORT_SYMBOL(ath11k_debugfs_soc_destroy);
int ath11k_debugfs_create()
{
debugfs_ath11k = debugfs_create_dir("ath11k", NULL);
if (IS_ERR_OR_NULL(debugfs_ath11k)) {
if (IS_ERR(debugfs_ath11k))
return PTR_ERR(debugfs_ath11k);
return -ENOMEM;
}
return 0;
}
void ath11k_debugfs_destroy()
{
debugfs_remove_recursive(debugfs_ath11k);
debugfs_ath11k = NULL;
debugfs_debug_infra = NULL;
}
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.vdevs);
INIT_LIST_HEAD(&ar->debug.fw_stats.bcn);
memset(ar->debug.ftmr_enabled, -1, sizeof(ar->debug.ftmr_enabled));
/* pdevs list and fw_stats_complete completion is already initialised
* in ath11k_fw_stats_init
*/
}
/* TX delay stats class names ordered by TID. */
static const char ath11k_tx_delay_stats_names[][7] = {
"AC_BE",
"AC_BK",
"AC_BK+",
"AC_BE+",
"AC_VI",
"AC_VI+",
"AC_VO",
"AC_VO+",
"TID8",
"TID9",
"TID10",
"TID11",
"TID12",
"TID13",
"TID14",
"TID15",
};
#define ATH11K_TX_DELAY_STATS_NAMES_SIZE ARRAY_SIZE(ath11k_tx_delay_stats_names)
/* Returns start time of the transmit delay histogram stats bin. */
static inline int ath11k_tx_delay_bin_to_ms(int bin)
{
int bin_ms;
/* The first two bins span 1ms (i.e. [0, 1), and [1, 2)) are returned
* directly. All other power-of-two bucket ranges are subdivided into
* two bins, with the even numbered bin covering the first half of the
* range and the odd numbered bin offset by 1/2 of the range.
*/
if (bin < 2)
return bin;
bin_ms = 1 << (bin / 2);
if (bin % 2)
bin_ms += bin_ms >> 1;
return bin_ms;
}
static ssize_t ath11k_tx_delay_stats_dump(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
char *buf;
unsigned int len = 0, buf_len = 4096;
ssize_t ret_cnt;
struct ath11k_tx_delay_stats *stats;
u32 tid, bin, total, counter, target, index;
u32 percentile[] = {5, 10, 25, 50, 75, 90, 95, 99};
buf = kzalloc(buf_len, GFP_KERNEL);
if (!buf)
return -ENOMEM;
len += scnprintf(buf + len, buf_len - len,
"Frames pending in driver: %d\n",
ar->dp.num_tx_pending);
BUILD_BUG_ON(ATH11K_TX_DELAY_STATS_NAMES_SIZE != IEEE80211_NUM_TIDS);
for (tid = 0; tid < IEEE80211_NUM_TIDS; tid++) {
stats = ar->debug.tx_delay_stats[tid];
total = 0;
for (bin = 0; bin < ATH11K_TX_DELAY_STATS_MAX_BINS; bin++)
total += stats->counts[bin];
/* Skip class that has no activity to make output more
* concise.
*/
if (total == 0)
continue;
len += scnprintf(buf + len, buf_len - len,
"TX latency stats for %s: %d frames\n",
ath11k_tx_delay_stats_names[tid], total);
for (counter = 0, bin = 0, index = 0;
index < ARRAY_SIZE(percentile); index++) {
target = total * percentile[index] / 100;
while ((counter < target) &&
(bin < ATH11K_TX_DELAY_STATS_MAX_BINS)) {
counter += stats->counts[bin];
bin++;
}
len += scnprintf(buf + len, buf_len - len,
"P%d:\t<%dms\n", percentile[index],
ath11k_tx_delay_bin_to_ms(bin));
}
}
ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len);
kfree(buf);
return ret_cnt;
}
static ssize_t ath11k_tx_delay_stats_clear(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
int val, ret;
ret = kstrtoint_from_user(user_buf, count, 0, &val);
if (ret)
return ret;
if (val != 0)
return -EINVAL;
memset(ar->debug.tx_delay_stats[0], 0,
sizeof(struct ath11k_tx_delay_stats) *
ARRAY_SIZE(ar->debug.tx_delay_stats));
return count;
}
static const struct file_operations fops_tx_delay_stats = {
.read = ath11k_tx_delay_stats_dump,
.write = ath11k_tx_delay_stats_clear,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_tx_delay_histo_dump(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k_tx_delay_stats *stats = file->private_data;
struct ath11k_tx_delay_stats stats_local;
char *buf;
unsigned int len = 0, buf_len = 4096, i;
ssize_t ret_cnt;
memcpy(&stats_local, stats, sizeof(struct ath11k_tx_delay_stats));
buf = kzalloc(buf_len, GFP_KERNEL);
if (!buf)
return 0;
len += scnprintf(buf + len, buf_len - len, "TX delay histogram(ms)\n");
for (i = 0; i < ATH11K_DELAY_STATS_SCALED_BINS; i++) {
len += scnprintf(buf + len, buf_len - len,
"[%4u - %4u):%8u ",
ath11k_tx_delay_bin_to_ms(i),
ath11k_tx_delay_bin_to_ms(i + 1),
stats_local.counts[i]);
if (i % 5 == 4)
len += scnprintf(buf + len, buf_len - len, "\n");
}
len += scnprintf(buf + len, buf_len - len, "[%4d - inf):%8u ",
ath11k_tx_delay_bin_to_ms(i), stats_local.counts[i]);
len += scnprintf(buf + len, buf_len - len, "\n");
ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len);
kfree(buf);
return ret_cnt;
}
static ssize_t ath11k_tx_delay_histo_reset(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k_tx_delay_stats *stats = file->private_data;
int val, ret;
ret = kstrtoint_from_user(user_buf, count, 0, &val);
if (ret)
return ret;
if (val != 0)
return -EINVAL;
memset(stats, 0, sizeof(struct ath11k_tx_delay_stats));
return count;
}
static const struct file_operations fops_tx_delay_histo = {
.read = ath11k_tx_delay_histo_dump,
.write = ath11k_tx_delay_histo_reset,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
void ath11k_init_tx_latency_stats(struct ath11k *ar)
{
size_t tx_delay_stats_size;
struct ath11k_tx_delay_stats *pbuf, *buf;
struct dentry *tx_delay_histo_dir;
int i;
tx_delay_stats_size = sizeof(struct ath11k_tx_delay_stats) *
ARRAY_SIZE(ar->debug.tx_delay_stats);
pbuf = kzalloc(tx_delay_stats_size, GFP_KERNEL);
if (!pbuf) {
ath11k_err(ar->ab, "Unable to allocate memory for latency stats\n");
return;
}
tx_delay_histo_dir = debugfs_create_dir("tx_delay_histogram",
ar->debug.debugfs_pdev);
if (IS_ERR_OR_NULL(tx_delay_histo_dir)) {
ath11k_err(ar->ab, "Failed to create debugfs dir tx_delay_stats\n");
kfree(pbuf);
return;
}
buf = pbuf;
for (i = 0; i < ARRAY_SIZE(ar->debug.tx_delay_stats); i++) {
ar->debug.tx_delay_stats[i] = buf;
buf++;
}
for (i = 0; i < ATH11K_TX_DELAY_STATS_NAMES_SIZE; i++) {
debugfs_create_file(ath11k_tx_delay_stats_names[i], 0644,
tx_delay_histo_dir,
ar->debug.tx_delay_stats[i],
&fops_tx_delay_histo);
}
debugfs_create_file("stats", 0644, tx_delay_histo_dir, ar,
&fops_tx_delay_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 = {0};
u32 rx_filter = 0, ring_id, filter, mode;
u8 buf[128] = {0};
int i, ret, rx_buf_sz;
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 */
tlv_filter.offset_valid = false;
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,
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_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,
rx_buf_sz, &tlv_filter);
if (ret) {
ath11k_warn(ab, "failed to set rx filter for monitor status ring\n");
goto out;
}
}
ath11k_info(ar->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] = {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;
if (ar->state != ATH11K_STATE_ON) {
ath11k_warn(ar->ab, "pdev %d not in ON state\n", ar->pdev->pdev_id);
mutex_unlock(&ar->conf_mutex);
return -ENETDOWN;
}
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_write_simulate_awgn(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
int ret;
u32 chan_bw_interference_bitmap;
mutex_lock(&ar->conf_mutex);
if (ar->state != ATH11K_STATE_ON) {
ret = -ENETDOWN;
goto exit;
}
if (kstrtou32_from_user(user_buf, count, 0, &chan_bw_interference_bitmap))
return -EINVAL;
ret = ath11k_wmi_simulate_awgn(ar, chan_bw_interference_bitmap);
if (ret)
goto exit;
ret = count;
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static const struct file_operations fops_simulate_awgn = {
.write = ath11k_write_simulate_awgn,
.open = simple_open
};
static ssize_t ath11k_write_btcoex(struct file *file,
const char __user *ubuf,
size_t count, loff_t *ppos)
{
struct ath11k_vif *arvif;
struct ath11k *ar = file->private_data;
char buf[256];
size_t buf_size;
int ret,coex = -1;
enum qca_wlan_priority_type wlan_prio_mask = 0;
int wlan_weight = 0;
if (!ar)
return -EINVAL;
buf_size = min(count, (sizeof(buf) - 1));
if (copy_from_user(buf, ubuf, buf_size))
return -EFAULT;
buf[buf_size] = '\0';
ret = sscanf(buf, "%d %u %d" , &coex, &wlan_prio_mask, &wlan_weight);
if (!ret)
return -EINVAL;
if (wlan_weight == -1)
wlan_weight = 0;
if(wlan_prio_mask == -1)
wlan_prio_mask =0;
if(wlan_weight < 0 || wlan_prio_mask < 0)
return -EINVAL;
if(coex != 1 && coex != -1 && coex)
return -EINVAL;
mutex_lock(&ar->conf_mutex);
arvif = list_first_entry(&ar->arvifs, typeof(*arvif), list);
if(coex == 1 && !test_bit(ATH11K_FLAG_BTCOEX, &ar->dev_flags))
set_bit(ATH11K_FLAG_BTCOEX, &ar->dev_flags);
if(coex == -1 && !test_bit(ATH11K_FLAG_BTCOEX, &ar->dev_flags)){
ret = -EINVAL;
goto exit;
}
if (!coex)
clear_bit(ATH11K_FLAG_BTCOEX, &ar->dev_flags);
ret = ath11k_mac_coex_config(ar, arvif, coex, wlan_prio_mask, wlan_weight);
if (ret)
goto exit;
ar->coex.wlan_prio_mask = wlan_prio_mask;
ar->coex.wlan_weight = wlan_weight;
ret = count;
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static ssize_t ath11k_read_btcoex(struct file *file, char __user *ubuf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
char buf[256];
int len=0;
if (!ar)
return -EINVAL;
mutex_lock(&ar->conf_mutex);
len = scnprintf(buf, sizeof(buf) - len, "%d %d %d\n",
test_bit(ATH11K_FLAG_BTCOEX, &ar->dev_flags),
ar->coex.wlan_prio_mask,
ar->coex.wlan_weight);
mutex_unlock(&ar->conf_mutex);
return simple_read_from_buffer(ubuf, count, ppos, buf, len);
}
static const struct file_operations fops__btcoex = {
.read = ath11k_read_btcoex,
.write = ath11k_write_btcoex,
.open = simple_open
};
static ssize_t ath11k_write_btcoex_duty_cycle(struct file *file,
const char __user *ubuf,
size_t count, loff_t *ppos)
{
struct ath11k_vif *arvif;
struct ath11k *ar = file->private_data;
struct coex_config_arg coex_config;
char buf[256];
size_t buf_size;
u32 duty_cycle,wlan_duration;
int ret;
if (!ar)
return -EINVAL;
if (!test_bit(ATH11K_FLAG_BTCOEX, &ar->dev_flags))
return -EINVAL;
if (ar->coex.coex_algo_type != COEX_ALGO_OCS) {
ath11k_err(ar->ab,"duty cycle algo is not enabled");
return -EINVAL;
}
buf_size = min(count, (sizeof(buf) - 1));
if (copy_from_user(buf, ubuf, buf_size))
return -EFAULT;
buf[buf_size] = '\0';
ret = sscanf(buf, "%d %d" , &duty_cycle, &wlan_duration);
if (!ret)
return -EINVAL;
/*Maximum duty_cycle period allowed is 100 Miliseconds*/
if (duty_cycle < wlan_duration || !duty_cycle || !wlan_duration || duty_cycle > 100000)
return -EINVAL;
mutex_lock(&ar->conf_mutex);
arvif = list_first_entry(&ar->arvifs, typeof(*arvif), list);
coex_config.vdev_id = arvif->vdev_id;
coex_config.config_type = WMI_COEX_CONFIG_AP_TDM;
coex_config.duty_cycle = duty_cycle;
coex_config.wlan_duration = wlan_duration;
ret = ath11k_send_coex_config_cmd(ar, &coex_config);
if (ret) {
ath11k_warn(ar->ab,
"failed to set coex config vdev_id %d ret %d\n",
coex_config.vdev_id, ret);
goto exit;
}
ar->coex.duty_cycle = duty_cycle;
ar->coex.wlan_duration = wlan_duration;
ret = count;
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static ssize_t ath11k_read_btcoex_duty_cycle(struct file *file, char __user *ubuf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
char buf[256];
int len =0;
if (!ar)
return -EINVAL;
len = scnprintf(buf, sizeof(buf) - len, "%d %d\n",
ar->coex.duty_cycle,ar->coex.wlan_duration);
return simple_read_from_buffer(ubuf, count, ppos, buf, len);
}
static const struct file_operations fops__btcoex_duty_cycle = {
.read = ath11k_read_btcoex_duty_cycle,
.write = ath11k_write_btcoex_duty_cycle,
.open = simple_open
};
static ssize_t ath11k_write_btcoex_algo(struct file *file,
const char __user *ubuf,
size_t count, loff_t *ppos)
{
struct ath11k_vif *arvif;
struct ath11k *ar = file->private_data;
unsigned int coex_algo;
struct coex_config_arg coex_config;
int ret;
if (kstrtouint_from_user(ubuf, count, 0, &coex_algo))
return -EINVAL;
if (coex_algo >= COEX_ALGO_MAX_SUPPORTED)
return -EINVAL;
mutex_lock(&ar->conf_mutex);
arvif = list_first_entry(&ar->arvifs, typeof(*arvif), list);
ar->coex.coex_algo_type = coex_algo;
coex_config.vdev_id = arvif->vdev_id;
coex_config.config_type = WMI_COEX_CONFIG_FORCED_ALGO;
coex_config.coex_algo = coex_algo;
ret = ath11k_send_coex_config_cmd(ar, &coex_config);
if (ret) {
ath11k_warn(ar->ab,
"failed to set coex algorithm vdev_id %d ret %d\n",
coex_config.vdev_id, ret);
goto exit;
}
ret = count;
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static ssize_t ath11k_read_btcoex_algo(struct file *file, char __user *ubuf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
char buf[32];
int len = 0;
len = scnprintf(buf, sizeof(buf) - len, "%d\n",
ar->coex.coex_algo_type);
return simple_read_from_buffer(ubuf, count, ppos, buf, len);
}
static const struct file_operations fops_btcoex_algo = {
.read = ath11k_read_btcoex_algo,
.write = ath11k_write_btcoex_algo,
.open = simple_open
};
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;
if (ps_timekeeper_enable > 1)
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;
}
if (ar->ps_timekeeper_enable == ps_timekeeper_enable) {
ret = count;
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;
int len = 0;
char buf[32];
mutex_lock(&ar->conf_mutex);
len = scnprintf(buf, sizeof(buf) - len, "%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 = WMI_PEER_RESET_PS_DURATION;
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;
if (reset_ps_duration != 1)
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->reset_ps_duration = reset_ps_duration;
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 = WMI_PEER_RESET_PS_TIME;
arsta->ps_total_duration = WMI_PEER_RESET_PS_DURATION;
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;
if (ps_state_enable > 1 || ps_state_enable < 0)
return -EINVAL;
mutex_lock(&ar->conf_mutex);
if (ar->state != ATH11K_STATE_ON) {
ath11k_warn(ar->ab, "pdev %d not in ON state\n", ar->pdev->pdev_id);
ret = -ENETDOWN;
goto exit;
}
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 = WMI_PEER_RESET_PS_TIME_KEEPER;
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;
int len = 0;
char buf[32];
mutex_lock(&ar->conf_mutex);
len = scnprintf(buf, sizeof(buf) - len, "%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,
};
static ssize_t ath11k_dump_mgmt_stats(struct file *file, char __user *ubuf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
#ifndef CONFIG_ATH11K_MEM_PROFILE_512M
struct ath11k_base *ab = ar->ab;
#endif
struct ath11k_vif *arvif = NULL;
struct ath11k_mgmt_frame_stats *mgmt_stats;
int len = 0, ret, i;
int size = (TARGET_NUM_VDEVS - 1) * 1500;
char *buf;
const char *mgmt_frm_type[ATH11K_STATS_MGMT_FRM_TYPE_MAX-1] = {"assoc_req", "assoc_resp",
"reassoc_req", "reassoc_resp",
"probe_req", "probe_resp",
"timing_advertisement", "reserved",
"beacon", "atim", "disassoc",
"auth", "deauth", "action", "action_no_ack"};
if (ar->state != ATH11K_STATE_ON)
return -ENETDOWN;
buf = kzalloc(size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
ATH11K_MEMORY_STATS_INC(ar->ab, malloc_size, size);
mutex_lock(&ar->conf_mutex);
spin_lock_bh(&ar->data_lock);
list_for_each_entry (arvif, &ar->arvifs, list) {
if (!arvif)
break;
if (arvif->vdev_type == WMI_VDEV_TYPE_MONITOR)
continue;
mgmt_stats = &arvif->mgmt_stats;
len += scnprintf(buf + len, size - len, "MGMT frame stats for vdev %u :\n",
arvif->vdev_id);
len += scnprintf(buf + len, size - len, " TX stats :\n ");
len += scnprintf(buf + len, size - len, " Success frames:\n");
for (i = 0; i < ATH11K_STATS_MGMT_FRM_TYPE_MAX-1; i++)
len += scnprintf(buf + len, size - len, " %s: %d\n", mgmt_frm_type[i],
mgmt_stats->tx_succ_cnt[i]);
len += scnprintf(buf + len, size - len, " Failed frames:\n");
for (i = 0; i < ATH11K_STATS_MGMT_FRM_TYPE_MAX-1; i++)
len += scnprintf(buf + len, size - len, " %s: %d\n", mgmt_frm_type[i],
mgmt_stats->tx_fail_cnt[i]);
len += scnprintf(buf + len, size - len, " RX stats :\n");
len += scnprintf(buf + len, size - len, " Success frames:\n");
for (i = 0; i < ATH11K_STATS_MGMT_FRM_TYPE_MAX-1; i++)
len += scnprintf(buf + len, size - len, " %s: %d\n", mgmt_frm_type[i],
mgmt_stats->rx_cnt[i]);
len += scnprintf(buf + len, size - len, " Tx completion stats :\n");
len += scnprintf(buf + len, size - len, " success completions:\n");
for (i = 0; i < ATH11K_STATS_MGMT_FRM_TYPE_MAX-1; i++)
len += scnprintf(buf + len, size - len, " %s: %d\n", mgmt_frm_type[i],
mgmt_stats->tx_compl_succ[i]);
len += scnprintf(buf + len, size - len, " failure completions:\n");
for (i = 0; i < ATH11K_STATS_MGMT_FRM_TYPE_MAX-1; i++)
len += scnprintf(buf + len, size - len, " %s: %d\n", mgmt_frm_type[i],
mgmt_stats->tx_compl_fail[i]);
}
spin_unlock_bh(&ar->data_lock);
if (len > size)
len = size;
ret = simple_read_from_buffer(ubuf, count, ppos, buf, len);
mutex_unlock(&ar->conf_mutex);
kfree(buf);
ATH11K_MEMORY_STATS_DEC(ar->ab, malloc_size, size);
return ret;
}
static const struct file_operations fops_dump_mgmt_stats = {
.read = ath11k_dump_mgmt_stats,
.open = simple_open
};
static ssize_t ath11k_write_enable_m3_dump(struct file *file,
const char __user *ubuf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
bool enable;
int ret;
if (kstrtobool_from_user(ubuf, count, &enable))
return -EINVAL;
mutex_lock(&ar->conf_mutex);
if (ar->state != ATH11K_STATE_ON) {
ret = -ENETDOWN;
goto exit;
}
if (enable == ar->debug.enable_m3_dump) {
ret = count;
goto exit;
}
ret = ath11k_wmi_pdev_m3_dump_enable(ar, enable);
if (ret) {
ath11k_warn(ar->ab,
"failed to enable m3 ssr dump %d\n",
ret);
goto exit;
}
ar->debug.enable_m3_dump = enable;
ret = count;
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static ssize_t ath11k_read_enable_m3_dump(struct file *file,
char __user *ubuf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
char buf[32];
size_t len = 0;
mutex_lock(&ar->conf_mutex);
len = scnprintf(buf, sizeof(buf) - len, "%d\n",
ar->debug.enable_m3_dump);
mutex_unlock(&ar->conf_mutex);
return simple_read_from_buffer(ubuf, count, ppos, buf, len);
}
static const struct file_operations fops_enable_m3_dump = {
.read = ath11k_read_enable_m3_dump,
.write = ath11k_write_enable_m3_dump,
.open = simple_open
};
static ssize_t ath11k_athdiag_read(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
u8 *buf;
int ret;
if (*ppos <= 0)
return -EINVAL;
if (!count)
return 0;
mutex_lock(&ar->conf_mutex);
buf = vmalloc(count);
if (!buf) {
ret = -ENOMEM;
goto exit;
}
ret = ath11k_qmi_mem_read(ar->ab, *ppos, buf, count);
if (ret < 0) {
ath11k_warn(ar->ab, "failed to read address 0x%08x via diagnose window from debugfs: %d\n",
(u32)(*ppos), ret);
goto exit;
}
ret = copy_to_user(user_buf, buf, count);
if (ret) {
ret = -EFAULT;
goto exit;
}
count -= ret;
*ppos += count;
ret = count;
exit:
vfree(buf);
mutex_unlock(&ar->conf_mutex);
return ret;
}
static ssize_t ath11k_athdiag_write(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
u8 *buf;
int ret;
if (*ppos <= 0)
return -EINVAL;
if (!count)
return 0;
mutex_lock(&ar->conf_mutex);
if (ar->state != ATH11K_STATE_ON) {
ath11k_warn(ar->ab, "pdev %d not in ON state\n", ar->pdev->pdev_id);
mutex_unlock(&ar->conf_mutex);
return -ENETDOWN;
}
mutex_unlock(&ar->conf_mutex);
buf = vmalloc(count);
if (!buf) {
ret = -ENOMEM;
goto exit;
}
ret = copy_from_user(buf, user_buf, count);
if (ret) {
ret = -EFAULT;
goto exit;
}
ret = ath11k_qmi_mem_write(ar->ab, *ppos, buf, count);
if (ret < 0) {
ath11k_warn(ar->ab, "failed to write address 0x%08x via diagnose window from debugfs: %d\n",
(u32)(*ppos), ret);
goto exit;
}
*ppos += count;
ret = count;
exit:
vfree(buf);
mutex_unlock(&ar->conf_mutex);
return ret;
}
static const struct file_operations fops_athdiag = {
.read = ath11k_athdiag_read,
.write = ath11k_athdiag_write,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static int ath11k_get_tpc_ctl_mode(struct wmi_tpc_stats_event *tpc_stats,
u32 pream_idx, int *mode)
{
switch (pream_idx) {
case WMI_TPC_PREAM_CCK:
*mode = ATH11K_TPC_STATS_CTL_MODE_CCK;
break;
case WMI_TPC_PREAM_OFDM:
*mode = ATH11K_TPC_STATS_CTL_MODE_OFDM;
break;
case WMI_TPC_PREAM_HT20:
case WMI_TPC_PREAM_VHT20:
case WMI_TPC_PREAM_HE20:
*mode = ATH11K_TPC_STATS_CTL_MODE_BW_20;
break;
case WMI_TPC_PREAM_HT40:
case WMI_TPC_PREAM_VHT40:
case WMI_TPC_PREAM_HE40:
*mode = ATH11K_TPC_STATS_CTL_MODE_BW_40;
break;
case WMI_TPC_PREAM_VHT80:
case WMI_TPC_PREAM_HE80:
*mode = ATH11K_TPC_STATS_CTL_MODE_BW_80;
break;
case WMI_TPC_PREAM_VHT160:
case WMI_TPC_PREAM_HE160:
*mode = ATH11K_TPC_STATS_CTL_MODE_BW_160;
break;
default:
return -EINVAL;
}
if (tpc_stats->tpc_config.chan_freq >= 5180) {
/* Index of 5G is one less than 2.4G due to absence of CCK */
*mode -= 1;
}
return 0;
}
static s16 ath11k_tpc_get_rate(struct ath11k *ar,
struct wmi_tpc_stats_event *tpc_stats,
u32 rate_idx, u32 num_chains, u32 rate_code,
u32 pream_idx, u8 type)
{
s8 rates_ctl_min, tpc_ctl, tpc_ctl_pri, tpc_ctl_sec;
u8 chain_idx, stm_idx, num_streams;
s16 rates, tpc, reg_pwr;
u32 tot_nss, tot_modes, txbf_on_off, chan_pri_sec;
u32 index_offset1, index_offset2, index_offset3;
int mode, ret, txbf_enabled;
bool is_mu;
num_streams = 1 + ATH11K_HW_NSS(rate_code);
chain_idx = num_chains - 1;
stm_idx = num_streams - 1;
mode = -1;
ret = ath11k_get_tpc_ctl_mode(tpc_stats, pream_idx, &mode);
if (ret) {
ath11k_warn(ar->ab, "Invalid mode index received\n");
tpc = TPC_INVAL;
goto out;
}
if (num_chains < num_streams) {
tpc = TPC_INVAL;
goto out;
}
if (__le32_to_cpu(tpc_stats->tpc_config.num_tx_chain) <= 1) {
tpc = TPC_INVAL;
goto out;
}
if (type == ATH11K_DBG_TPC_STATS_MU_WITH_TXBF ||
type == ATH11K_DBG_TPC_STATS_SU_WITH_TXBF)
txbf_enabled = 1;
else
txbf_enabled = 0;
if (type == ATH11K_DBG_TPC_STATS_MU_WITH_TXBF ||
type == ATH11K_DBG_TPC_STATS_MU) {
is_mu = true;
} else {
is_mu = false;
}
/* Below is the min calculation of ctl array, rates array and
* regulator power table. tpc is minimum of all 3
*/
if (chain_idx < 4) {
if (is_mu) {
rates = FIELD_GET(ATH11K_TPC_RATE_ARRAY_MU,
tpc_stats->rates_array1.rate_array[rate_idx]);
} else {
rates = FIELD_GET(ATH11K_TPC_RATE_ARRAY_SU,
tpc_stats->rates_array1.rate_array[rate_idx]);
}
} else {
if (is_mu) {
rates = FIELD_GET(ATH11K_TPC_RATE_ARRAY_MU,
tpc_stats->rates_array2.rate_array[rate_idx]);
} else {
rates = FIELD_GET(ATH11K_TPC_RATE_ARRAY_SU,
tpc_stats->rates_array2.rate_array[rate_idx]);
}
}
/* ctl_160 array is accessed for BW 160. Mode is subtracted by -1 as
* 5G index is one less than 2G due to absence of CCK
* ctl array are 4 dimension array which is packed linearly, hence
* needs to be stitched back based on the dimension values.
* formula : when buf[i][j][k][l] values can be taken as
* buf[i*d3*d2*d1 + j*d2*d1 + k*d1 + l]
*/
if (tpc_stats->tlvs_rcvd & WMI_TPC_CTL_PWR_160ARRAY &&
mode == ATH11K_TPC_STATS_CTL_MODE_BW_160 - 1) {
tot_nss = tpc_stats->ctl_160array.d1;
txbf_on_off = tpc_stats->ctl_160array.d2;
chan_pri_sec = tpc_stats->ctl_160array.d3;
index_offset1 = chan_pri_sec * txbf_on_off * tot_nss;
index_offset2 = txbf_on_off * tot_nss;
index_offset3 = tot_nss;
if (num_streams < 2)
num_streams = 2;
tpc_ctl_pri = *(tpc_stats->ctl_160array.ctl_pwr_table +
((num_chains / 2) - 1) * index_offset1 +
0 + txbf_enabled * index_offset3 +
(((num_streams) / 2) - 1));
tpc_ctl_sec = *(tpc_stats->ctl_160array.ctl_pwr_table +
((num_chains / 2) - 1) * index_offset1 +
1 * index_offset2 + txbf_enabled * index_offset3 +
(((num_streams) / 2) - 1));
/* Taking min of pri and sec channel for 160 Mhz */
tpc_ctl = min_t(s8, tpc_ctl_pri, tpc_ctl_sec);
} else if (tpc_stats->tlvs_rcvd & WMI_TPC_CTL_PWR_ARRAY) {
tot_nss = tpc_stats->ctl_array.d1;
tot_modes = tpc_stats->ctl_array.d2;
txbf_on_off = tpc_stats->ctl_array.d3;
index_offset1 = txbf_on_off * tot_modes * tot_nss;
index_offset2 = tot_modes * tot_nss;
index_offset3 = tot_nss;
tpc_ctl = *(tpc_stats->ctl_array.ctl_pwr_table +
chain_idx * index_offset1 + txbf_enabled * index_offset2
+ mode * index_offset3 + stm_idx);
} else {
tpc_ctl = TPC_MAX;
ath11k_info(ar->ab,
"ctl array for tpc stats not received from fw\n");
}
rates_ctl_min = min_t(s16, rates, tpc_ctl);
reg_pwr = tpc_stats->max_reg_allowed_power.reg_pwr_array[chain_idx];
tpc = min_t(s16, rates_ctl_min, reg_pwr);
/* MODULATION_LIMIT is the maximum power limit,tpc should not exceed
* modulation limt even if min tpc of all three array is greater
* modulation limit
*/
tpc = min_t(s16, tpc, MODULATION_LIMIT);
out:
return tpc;
}
static bool ath11k_he_supports_extra_mcs(struct ath11k *ar, int freq)
{
struct ath11k_pdev_cap *cap = &ar->pdev->cap;
struct ath11k_band_cap *cap_band;
bool extra_mcs_supported;
if (freq <= ATH11K_2G_MAX_FREQUENCY)
cap_band = &cap->band[NL80211_BAND_2GHZ];
else
cap_band = &cap->band[NL80211_BAND_5GHZ];
extra_mcs_supported = FIELD_GET(HE_EXTRA_MCS_SUPPORT, cap_band->he_cap_info[1]);
return extra_mcs_supported;
}
static int ath11k_tpc_fill_pream(struct ath11k *ar, char *buf, int buf_len, int len,
int pream_idx, int max_nss, int max_rates,
int pream_type, int tpc_type, int rate_idx)
{
int nss, rates, chains;
u8 active_tx_chains;
u16 rate_code, tpc;
struct wmi_tpc_stats_event *tpc_stats = ar->tpc_stats;
static const char pream_str[WMI_TPC_PREAM_MAX][MAX_TPC_PREAM_STR_LEN] = {
"CCK",
"OFDM",
"HT20",
"HT40",
"VHT20",
"VHT40",
"VHT80",
"VHT160",
"HE20",
"HE40",
"HE80",
"HE160"};
active_tx_chains = ar->num_tx_chains;
for (nss = 0; nss < max_nss; nss++) {
for (rates = 0; rates < max_rates; rates++, rate_idx++) {
/* FW send extra MCS(10&11) for VHT and HE rates,
* this is not used. Hence skipping it here
*/
if (pream_type == WMI_RATE_PREAMBLE_VHT &&
rates > ATH11K_VHT_MCS_MAX)
continue;
if (pream_type == WMI_RATE_PREAMBLE_HE &&
rates > ATH11K_HE_MCS_MAX)
continue;
rate_code = ATH11K_HW_RATE_CODE(rates, nss, pream_type);
len += scnprintf(buf + len, buf_len - len,
"%d\t %s\t 0x%03x\t", rate_idx,
pream_str[pream_idx], rate_code);
for (chains = 0; chains < active_tx_chains; chains++) {
/* check for 160Mhz where two chains requires to
* support one spatial stream.
*/
if ((pream_idx == WMI_TPC_PREAM_VHT160 ||
pream_idx == WMI_TPC_PREAM_HE160) &&
(((chains + 1) / 2) < nss + 1)) {
len += scnprintf(buf + len,
buf_len - len,
"\t%s", "NA");
} else if (nss > chains) {
len += scnprintf(buf + len,
buf_len - len,
"\t%s", "NA");
} else {
tpc = ath11k_tpc_get_rate(ar, tpc_stats, rate_idx,
chains + 1, rate_code,
pream_idx, tpc_type);
if (tpc == TPC_INVAL) {
len += scnprintf(buf + len,
buf_len - len, "\tNA");
} else {
len += scnprintf(buf + len,
buf_len - len, "\t%d",
tpc);
}
}
}
len += scnprintf(buf + len, buf_len - len, "\n");
}
}
return len;
}
static int ath11k_tpc_stats_print(struct ath11k *ar,
struct wmi_tpc_stats_event *tpc_stats,
char *buf, size_t len, u8 type)
{
u32 i, pream_idx = 0, rate_pream_idx = 0, total_rates = 0;
u8 nss, active_tx_chains;
size_t buf_len = ATH11K_TPC_STATS_BUF_SIZE;
bool he_ext_mcs;
static const char type_str[ATH11K_DBG_TPC_MAX_STATS][13] = {"SU",
"SU WITH TXBF",
"MU",
"MU WITH TXBF"};
u8 max_rates[WMI_TPC_PREAM_MAX] = {ATH11K_CCK_RATES,
ATH11K_OFDM_RATES,
AT11K_HT_RATES,
AT11K_HT_RATES,
ATH11K_VHT_RATES,
ATH11K_VHT_RATES,
ATH11K_VHT_RATES,
ATH11K_VHT_RATES,
ATH11K_HE_RATES,
ATH11K_HE_RATES,
ATH11K_HE_RATES,
ATH11K_HE_RATES};
u8 max_nss[WMI_TPC_PREAM_MAX] = {ATH11K_NSS_1, ATH11K_NSS_1,
ATH11K_NSS_4, ATH11K_NSS_4,
ATH11K_NSS_8, ATH11K_NSS_8,
ATH11K_NSS_8, ATH11K_NSS_4,
ATH11K_NSS_8, ATH11K_NSS_8,
ATH11K_NSS_8, ATH11K_NSS_4};
u16 rate_idx[WMI_TPC_PREAM_MAX] = {0};
u8 pream_type[WMI_TPC_PREAM_MAX] = {WMI_RATE_PREAMBLE_CCK,
WMI_RATE_PREAMBLE_OFDM,
WMI_RATE_PREAMBLE_HT,
WMI_RATE_PREAMBLE_HT,
WMI_RATE_PREAMBLE_VHT,
WMI_RATE_PREAMBLE_VHT,
WMI_RATE_PREAMBLE_VHT,
WMI_RATE_PREAMBLE_VHT,
WMI_RATE_PREAMBLE_HE,
WMI_RATE_PREAMBLE_HE,
WMI_RATE_PREAMBLE_HE,
WMI_RATE_PREAMBLE_HE};
active_tx_chains = ar->num_tx_chains;
he_ext_mcs = ath11k_he_supports_extra_mcs(ar, tpc_stats->tpc_config.chan_freq);
/* mcs 12&13 is sent by FW for qcn9000 in rate array, skipping it as
* it is not supported
*/
if (he_ext_mcs) {
for (i = WMI_TPC_PREAM_HE20; i <= WMI_TPC_PREAM_HE160; ++i)
max_rates[i] = ATH11K_HE_RATES_WITH_EXTRA_MCS;
}
if (type == ATH11K_DBG_TPC_STATS_MU ||
type == ATH11K_DBG_TPC_STATS_MU_WITH_TXBF)
pream_idx = WMI_TPC_PREAM_VHT20;
/* Enumerate all the rate indices */
for (i = rate_pream_idx + 1 ; i < WMI_TPC_PREAM_MAX; i++) {
nss = (max_nss[i - 1] < tpc_stats->tpc_config.num_tx_chain ?
max_nss[i - 1] : tpc_stats->tpc_config.num_tx_chain);
if ((i == WMI_TPC_PREAM_VHT160 || i == WMI_TPC_PREAM_HE160) &&
(!(tpc_stats->tlvs_rcvd & WMI_TPC_CTL_PWR_160ARRAY))) {
/* ratesarray doesnot hold any 160Mhz power info,
* so skipping here
*/
rate_idx[i] = rate_idx[i - 1] + max_rates[i - 1] * nss;
max_rates[i] = 0;
max_nss[i] = 0;
continue;
}
rate_idx[i] = rate_idx[i - 1] + max_rates[i - 1] * nss;
}
for (i = 0 ; i < WMI_TPC_PREAM_MAX; i++) {
nss = (max_nss[i] < tpc_stats->tpc_config.num_tx_chain ?
max_nss[i] : tpc_stats->tpc_config.num_tx_chain);
total_rates += max_rates[i] * nss;
}
len += scnprintf(buf + len, buf_len - len,
"No.of rates-%d\n", total_rates);
len += scnprintf(buf + len, buf_len - len,
"**************** %s ****************\n",
type_str[type]);
len += scnprintf(buf + len, buf_len - len,
"\t\t\t\tTPC values for Active chains\n");
len += scnprintf(buf + len, buf_len - len,
"Rate idx Preamble Rate code");
for (i = 1; i <= active_tx_chains; ++i) {
len += scnprintf(buf + len, buf_len - len,
"\t%d-Chain", i);
}
len += scnprintf(buf + len, buf_len - len, "\n");
for (i = pream_idx; i < WMI_TPC_PREAM_MAX; i++) {
if (tpc_stats->tpc_config.chan_freq <= 2483) {
if (i == WMI_TPC_PREAM_VHT80 ||
i == WMI_TPC_PREAM_VHT160 ||
i == WMI_TPC_PREAM_HE80 ||
i == WMI_TPC_PREAM_HE160) {
continue;
}
} else {
if (i == WMI_TPC_PREAM_CCK)
continue;
}
nss = (max_nss[i] < ar->num_tx_chains ? max_nss[i] : ar->num_tx_chains);
if (i == WMI_TPC_PREAM_VHT160 || i == WMI_TPC_PREAM_HE160) {
/* Skip 160MHz in power table if not supported */
if (!(tpc_stats->tlvs_rcvd & WMI_TPC_CTL_PWR_160ARRAY))
continue;
}
len = ath11k_tpc_fill_pream(ar, buf, buf_len, len, i, nss,
max_rates[i], pream_type[i],
type, rate_idx[i]);
}
return len;
}
static void ath11k_tpc_stats_fill(struct ath11k *ar,
struct wmi_tpc_stats_event *tpc_stats,
char *buf)
{
struct wmi_tpc_configs *tpc;
size_t len = 0;
size_t buf_len = ATH11K_TPC_STATS_BUF_SIZE;
spin_lock_bh(&ar->data_lock);
if (!tpc_stats) {
ath11k_warn(ar->ab, "failed to find tpc stats\n");
goto unlock;
}
tpc = &tpc_stats->tpc_config;
len += scnprintf(buf + len, buf_len - len, "\n");
len += scnprintf(buf + len, buf_len - len,
"*************** TPC config **************\n");
len += scnprintf(buf + len, buf_len - len,
"* powers are in 0.25 dBm steps\n");
len += scnprintf(buf + len, buf_len - len,
"reg domain-%d\t\tchan freq-%d\n",
tpc->reg_domain, tpc->chan_freq);
len += scnprintf(buf + len, buf_len - len,
"power limit-%d\t\tmax reg-domain Power-%d\n",
(tpc->twice_max_reg_power) / 2, tpc->power_limit);
len += scnprintf(buf + len, buf_len - len,
"No.of tx chain-%d\t",
ar->num_tx_chains);
ath11k_tpc_stats_print(ar, tpc_stats, buf, len,
ar->tpc_stats_type);
unlock:
spin_unlock_bh(&ar->data_lock);
}
static int ath11k_debug_tpc_stats_request(struct ath11k *ar)
{
int ret;
unsigned long time_left;
struct ath11k_base *ab = ar->ab;
lockdep_assert_held(&ar->conf_mutex);
reinit_completion(&ar->tpc_complete);
ret = ath11k_wmi_pdev_get_tpc_table_cmdid(ar);
if (ret) {
ath11k_warn(ab, "failed to request tpc table cmdid: %d\n", ret);
return ret;
}
spin_lock_bh(&ar->data_lock);
ar->tpc_request = true;
spin_unlock_bh(&ar->data_lock);
time_left = wait_for_completion_timeout(&ar->tpc_complete,
TPC_STATS_WAIT_TIME);
spin_lock_bh(&ar->data_lock);
ar->tpc_request = false;
spin_unlock_bh(&ar->data_lock);
if (time_left == 0)
return -ETIMEDOUT;
return 0;
}
static int ath11k_tpc_stats_open(struct inode *inode, struct file *file)
{
struct ath11k *ar = inode->i_private;
void *buf;
int ret;
mutex_lock(&ar->conf_mutex);
if (ar->state != ATH11K_STATE_ON) {
ath11k_warn(ar->ab, "Interface not up\n");
ret = -ENETDOWN;
goto err_unlock;
}
buf = vmalloc(ATH11K_TPC_STATS_BUF_SIZE);
if (!buf) {
ret = -ENOMEM;
goto err_unlock;
}
ret = ath11k_debug_tpc_stats_request(ar);
if (ret) {
ath11k_warn(ar->ab, "failed to request tpc stats: %d\n",
ret);
spin_lock_bh(&ar->data_lock);
ath11k_wmi_free_tpc_stats_mem(ar);
spin_unlock_bh(&ar->data_lock);
goto err_free;
}
ath11k_tpc_stats_fill(ar, ar->tpc_stats, buf);
file->private_data = buf;
spin_lock_bh(&ar->data_lock);
ath11k_wmi_free_tpc_stats_mem(ar);
spin_unlock_bh(&ar->data_lock);
mutex_unlock(&ar->conf_mutex);
return 0;
err_free:
vfree(buf);
err_unlock:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static int ath11k_tpc_stats_release(struct inode *inode,
struct file *file)
{
vfree(file->private_data);
return 0;
}
static ssize_t ath11k_tpc_stats_read(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
const char *buf = file->private_data;
unsigned int len = strlen(buf);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static ssize_t ath11k_read_tpc_stats_type(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
char buf[32];
size_t len;
len = scnprintf(buf, sizeof(buf), "%u\n", ar->tpc_stats_type);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static ssize_t ath11k_write_tpc_stats_type(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
u8 type;
int ret;
ret = kstrtou8_from_user(user_buf, count, 0, &type);
if (ret)
return ret;
if (type >= ATH11K_DBG_TPC_MAX_STATS)
return -E2BIG;
ar->tpc_stats_type = type;
ret = count;
return ret;
}
static const struct file_operations fops_tpc_stats_type = {
.read = ath11k_read_tpc_stats_type,
.write = ath11k_write_tpc_stats_type,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static const struct file_operations fops_tpc_stats = {
.open = ath11k_tpc_stats_open,
.release = ath11k_tpc_stats_release,
.read = ath11k_tpc_stats_read,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
void ath11k_dbring_add_debug_entry(struct ath11k *ar,
enum wmi_direct_buffer_module id,
enum ath11k_db_ring_dbg_event event,
struct hal_srng *srng)
{
struct ath11k_db_module_debug *db_module_debug;
struct ath11k_db_ring_debug *db_ring_debug;
struct ath11k_db_ring_debug_entry *entry;
if (id > WMI_DIRECT_BUF_MAX || event > DBR_RING_DEBUG_EVENT_MAX)
return;
db_module_debug = ar->debug.module_debug[id];
if (!db_module_debug)
return;
db_ring_debug = &db_module_debug->db_ring_debug;
if (!db_module_debug->db_ring_debug_enabled)
return;
if (db_ring_debug->entries) {
entry = &db_ring_debug->entries[db_ring_debug->db_ring_debug_idx];
entry->hp = srng->u.src_ring.hp;
entry->tp = *srng->u.src_ring.tp_addr;
entry->timestamp = jiffies;
entry->event = event;
db_ring_debug->db_ring_debug_idx++;
if (db_ring_debug->db_ring_debug_idx ==
db_ring_debug->num_ring_debug_entries)
db_ring_debug->db_ring_debug_idx = 0;
}
}
static ssize_t ath11k_dbr_dump_debug_entries(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k_db_ring_debug *db_ring_debug = file->private_data;
static const char * const event_id_to_string[] = {"empty", "Rx", "Replenish"};
int size = 25 * 1024;
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");
for (i = 0; i < db_ring_debug->num_ring_debug_entries; i++) {
len += scnprintf(buf + len, size - len,
"|%4u|%8u|%8u|%11llu|%8s|\n", i,
db_ring_debug->entries[i].hp,
db_ring_debug->entries[i].tp,
db_ring_debug->entries[i].timestamp,
event_id_to_string[db_ring_debug->entries[i].event]);
}
ret = simple_read_from_buffer(user_buf, count, ppos, buf, len);
kfree(buf);
return ret;
}
static const struct file_operations fops_dump_dbr_debug_entries = {
.read = ath11k_dbr_dump_debug_entries,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static void ath11k_db_module_debugfs_destroy(struct ath11k *ar, int module_id)
{
struct ath11k_db_module_debug *module_debug;
struct ath11k_db_ring_debug *db_ring_debug;
module_debug = ar->debug.module_debug[module_id];
db_ring_debug = &module_debug->db_ring_debug;
if (!module_debug || !module_debug->module_debugfs)
return;
debugfs_remove_recursive(module_debug->module_debugfs);
module_debug->module_debugfs = NULL;
module_debug->db_ring_debug_enabled = false;
kfree(db_ring_debug->entries);
db_ring_debug->entries = NULL;
kfree(module_debug);
ar->debug.module_debug[module_id] = NULL;
}
static int ath11k_db_module_debugfs_init(struct ath11k *ar, int module_id)
{
struct ath11k_db_module_debug *module_debug;
struct ath11k_db_ring_debug *db_ring_debug;
static const char * const module_id_to_str[] = {"spectral", "CFR"};
u32 size;
if (!ar->debug.module_debug[module_id])
ar->debug.module_debug[module_id] = kzalloc(sizeof(*module_debug),
GFP_KERNEL);
module_debug = ar->debug.module_debug[module_id];
db_ring_debug = &module_debug->db_ring_debug;
if (module_debug->module_debugfs)
return 0;
module_debug->module_debugfs = debugfs_create_dir(module_id_to_str[module_id],
ar->debug.debugfs_pdev);
if (IS_ERR_OR_NULL(module_debug->module_debugfs)) {
if (IS_ERR(module_debug->module_debugfs))
return PTR_ERR(module_debug->module_debugfs);
return -ENOMEM;
}
size = ATH11K_DBR_DEUBG_ENTRIES_MAX *
sizeof(struct ath11k_db_ring_debug_entry);
module_debug->db_ring_debug_enabled = true;
db_ring_debug->num_ring_debug_entries = ATH11K_DBR_DEUBG_ENTRIES_MAX;
db_ring_debug->db_ring_debug_idx = 0;
db_ring_debug->entries = kzalloc(size, GFP_KERNEL);
if (!db_ring_debug->entries)
return -ENOMEM;
debugfs_create_file("dump_db_ring_debug", 0444, module_debug->module_debugfs,
db_ring_debug, &fops_dump_dbr_debug_entries);
return 0;
}
static ssize_t ath11k_write_enable_dbr_debug(struct file *file,
const char __user *ubuf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
char buf[32] = {0};
u32 module_id, enable;
int ret;
static const char usage[] = "usage: echo <module_id> <val> "
"module_id:0-Spectral 1-CFR val:0-disable 1-enable";
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", &module_id, &enable);
if (ret != 2) {
ath11k_warn(ar->ab, "%s\n", usage);
ret = -EINVAL;
goto out;
}
if (module_id > 1 || enable > 1) {
ath11k_warn(ar->ab, "%s\n", usage);
ret = -EINVAL;
goto out;
}
if (enable) {
ret = ath11k_db_module_debugfs_init(ar, module_id);
if (ret) {
ath11k_warn(ar->ab, "db ring module debgfs init failed: %d\n",
ret);
goto out;
}
} else {
ath11k_db_module_debugfs_destroy(ar, module_id);
}
ret = count;
out:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static const struct file_operations fops_dbr_debug = {
.write = ath11k_write_enable_dbr_debug,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_write_disable_dynamic_bw(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.disable_dynamic_bw) {
ret = count;
goto out;
}
ret = ath11k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_DYNAMIC_BW, !filter,
ar->pdev->pdev_id);
if (ret) {
ath11k_err(ar->ab, "failed to %s dynamic bw: %d\n",
filter ? "disable" : "enable", ret);
goto out;
}
ar->debug.disable_dynamic_bw = filter;
ret = count;
out:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static ssize_t ath11k_read_disable_dynamic_bw(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.disable_dynamic_bw);
mutex_unlock(&ar->conf_mutex);
return simple_read_from_buffer(ubuf, count, ppos, buf, len);
}
static const struct file_operations fops_disable_dyn_bw = {
.read = ath11k_read_disable_dynamic_bw,
.write = ath11k_write_disable_dynamic_bw,
.open = simple_open
};
static ssize_t ath11k_read_ani_enable(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
unsigned long time_left;
int ret;
int len;
char buf[128];
mutex_lock(&ar->conf_mutex);
if (ar->state != ATH11K_STATE_ON) {
ret = -ENETDOWN;
goto unlock;
}
reinit_completion(&ar->ani_status_event);
ret = ath11k_wmi_pdev_get_ani_status(ar);
if (ret) {
ath11k_warn(ar->ab, "failed to get ani status: %d\n", ret);
goto unlock;
}
time_left = wait_for_completion_timeout(&ar->ani_status_event,
1 * HZ);
if (time_left == 0) {
ret = -ETIMEDOUT;
goto unlock;
}
len = scnprintf(buf, sizeof(buf), "%d\n", ar->ani_enabled);
mutex_unlock(&ar->conf_mutex);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
unlock:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static ssize_t ath11k_write_ani_enable(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
int ret;
u8 enable;
if (kstrtou8_from_user(user_buf, count, 0, &enable))
return -EINVAL;
mutex_lock(&ar->conf_mutex);
if (ar->state != ATH11K_STATE_ON) {
ath11k_warn(ar->ab, "pdev %d is not in ON state\n", ar->pdev->pdev_id);
mutex_unlock(&ar->conf_mutex);
return -ENETDOWN;
}
if (ar->ani_enabled == enable) {
ret = count;
goto exit;
}
ret = ath11k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_ANI_ENABLE,
enable, ar->pdev->pdev_id);
if (ret) {
ath11k_warn(ar->ab, "ani_enable failed from debugfs: %d\n", ret);
goto exit;
}
spin_lock_bh(&ar->data_lock);
ar->ani_enabled = enable;
spin_unlock_bh(&ar->data_lock);
ret = count;
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static const struct file_operations fops_ani_enable = {
.read = ath11k_read_ani_enable,
.write = ath11k_write_ani_enable,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_read_ani_poll_period(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
int len = 0;
char buf[32];
len = scnprintf(buf, sizeof(buf) - len, "%u\n", ar->ab->ani_poll_period);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static ssize_t ath11k_write_ani_poll_period(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
int ret;
u32 ani_poll_period;
if (kstrtou32_from_user(user_buf, count, 0, &ani_poll_period))
return -EINVAL;
if(ani_poll_period > ATH11K_ANI_POLL_PERIOD_MAX)
return -EINVAL;
mutex_lock(&ar->conf_mutex);
if (ar->state != ATH11K_STATE_ON) {
ath11k_warn(ar->ab, "pdev %d not in ON state\n", ar->pdev->pdev_id);
mutex_unlock(&ar->conf_mutex);
return -ENETDOWN;
}
ret = ath11k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_ANI_POLL_PERIOD,
ani_poll_period, ar->pdev->pdev_id);
if (ret) {
ath11k_warn(ar->ab, "ani poll period write failed in debugfs: %d\n", ret);
goto exit;
}
ar->ab->ani_poll_period = ani_poll_period;
ret = count;
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static const struct file_operations fops_ani_poll_period = {
.read = ath11k_read_ani_poll_period,
.write = ath11k_write_ani_poll_period,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_read_ani_listen_period(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
int len = 0;
char buf[32];
len = scnprintf(buf, sizeof(buf) - len, "%u\n", ar->ab->ani_listen_period);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static ssize_t ath11k_write_ani_listen_period(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
int ret;
u32 ani_listen_period = 0;
if (kstrtou32_from_user(user_buf, count, 0, &ani_listen_period))
return -EINVAL;
if(ani_listen_period > ATH11K_ANI_LISTEN_PERIOD_MAX)
return -EINVAL;
mutex_lock(&ar->conf_mutex);
if (ar->state != ATH11K_STATE_ON) {
ath11k_warn(ar->ab, "pdev %d not in ON state\n", ar->pdev->pdev_id);
mutex_unlock(&ar->conf_mutex);
return -ENETDOWN;
}
ret = ath11k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_ANI_LISTEN_PERIOD,
ani_listen_period, ar->pdev->pdev_id);
if (ret) {
ath11k_warn(ar->ab, "ani listen period write failed in debugfs: %d\n", ret);
goto exit;
}
ar->ab->ani_listen_period = ani_listen_period;
ret = count;
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static const struct file_operations fops_ani_listen_period = {
.read = ath11k_read_ani_listen_period,
.write = ath11k_write_ani_listen_period,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static int ath11k_debug_get_ani_level(struct ath11k *ar)
{
unsigned long time_left;
int ret;
lockdep_assert_held(&ar->conf_mutex);
reinit_completion(&(ar->ab->ani_ofdm_event));
ret = ath11k_wmi_pdev_get_ani_level(ar, WMI_PDEV_GET_ANI_OFDM_CONFIG_CMDID,
ar->pdev->pdev_id);
if (ret) {
ath11k_warn(ar->ab, "failed to request ofdm ani level: %d\n", ret);
return ret;
}
time_left = wait_for_completion_timeout(&ar->ab->ani_ofdm_event, 1 * HZ);
if (time_left == 0)
return -ETIMEDOUT;
if (ar->ab->target_caps.phy_capability & WHAL_WLAN_11G_CAPABILITY) {
reinit_completion(&(ar->ab->ani_cck_event));
ret = ath11k_wmi_pdev_get_ani_level(ar, WMI_PDEV_GET_ANI_CCK_CONFIG_CMDID,
ar->pdev->pdev_id);
if (ret) {
ath11k_warn(ar->ab, "failed to request cck ani level: %d\n", ret);
return ret;
}
time_left = wait_for_completion_timeout(&ar->ab->ani_cck_event, 1 * HZ);
if (time_left == 0)
return -ETIMEDOUT;
}
return 0;
}
static ssize_t ath11k_read_ani_level(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
char buf[128];
int ret, len = 0;
mutex_lock(&ar->conf_mutex);
if (ar->state != ATH11K_STATE_ON) {
ret = -ENETDOWN;
goto unlock;
}
if(!ar->ani_enabled) {
len += scnprintf(buf, sizeof(buf), "ANI is disabled\n");
} else {
ret = ath11k_debug_get_ani_level(ar);
if (ret) {
ath11k_warn(ar->ab, "failed to request ani level: %d\n", ret);
goto unlock;
}
len += scnprintf(buf, sizeof(buf), "ofdm level %d cck level %d\n",
ar->ab->ani_ofdm_level, ar->ab->ani_cck_level);
}
mutex_unlock(&ar->conf_mutex);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
unlock:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static ssize_t ath11k_write_ani_level(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
char buf[32] = {0};
ssize_t rc;
u32 ofdm_param = 0, cck_param = 0;
int ofdm_level, cck_level;
int ret;
rc = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count);
if (rc < 0)
return rc;
buf[*ppos - 1] = '\0';
ret = sscanf(buf, "%d %d", &ofdm_level, &cck_level);
if (ret != 2)
return -EINVAL;
mutex_lock(&ar->conf_mutex);
if (ar->state != ATH11K_STATE_ON && ar->state != ATH11K_STATE_RESTARTED) {
ret = -ENETDOWN;
goto exit;
}
if ((ofdm_level >= ATH11K_ANI_LEVEL_MIN && ofdm_level <= ATH11K_ANI_LEVEL_MAX) ||
(ofdm_level == ATH11K_ANI_LEVEL_AUTO)) {
ofdm_param = WMI_PDEV_PARAM_ANI_OFDM_LEVEL;
} else {
ret = -EINVAL;
goto exit;
}
if((ar->ab->target_caps.phy_capability & WHAL_WLAN_11G_CAPABILITY)) {
if ((cck_level >= ATH11K_ANI_LEVEL_MIN &&
cck_level <= ATH11K_ANI_LEVEL_MAX) ||
(cck_level == ATH11K_ANI_LEVEL_AUTO)) {
cck_param = WMI_PDEV_PARAM_ANI_CCK_LEVEL;
} else {
ret = -EINVAL;
goto exit;
}
}
ret = ath11k_wmi_pdev_set_param(ar, ofdm_param, ofdm_level, ar->pdev->pdev_id);
if (ret) {
ath11k_warn(ar->ab, "failed to set ANI ofdm level :%d\n", ret);
goto exit;
}
if (cck_param) {
ret = ath11k_wmi_pdev_set_param(ar, cck_param, cck_level,
ar->pdev->pdev_id);
if (ret) {
ath11k_warn(ar->ab, "failed to set ANI cck level :%d\n", ret);
goto exit;
}
}
ret = count;
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static const struct file_operations fops_ani_level = {
.write = ath11k_write_ani_level,
.read = ath11k_read_ani_level,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_medium_busy_read(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
u8 buf[50];
size_t len = 0;
mutex_lock(&ar->conf_mutex);
len += scnprintf(buf + len, sizeof(buf) - len,
"Medium Busy in percentage %u\n",
ar->hw->medium_busy);
mutex_unlock(&ar->conf_mutex);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static const struct file_operations fops_medium_busy = {
.read = ath11k_medium_busy_read,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_coex_priority_read(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
u8 buf[128];
size_t len = 0;
int i;
mutex_lock(&ar->conf_mutex);
for (i = 0; i < ATH11K_MAX_COEX_PRIORITY_LEVEL; i++) {
len += scnprintf(buf + len, sizeof(buf) - len,
"priority[%d] : %u\n", i,
ar->debug.coex_priority_level[i]);
}
mutex_unlock(&ar->conf_mutex);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static ssize_t ath11k_coex_priority_write(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
struct ath11k_vif *arvif = NULL;
struct coex_config_arg coex_config;
char buf[128] = {0};
int ret;
u32 temp_priority[ATH11K_MAX_COEX_PRIORITY_LEVEL] = {0};
u32 config_type = 0xFF;
mutex_lock(&ar->conf_mutex);
if (ar->state != ATH11K_STATE_ON &&
ar->state != ATH11K_STATE_RESTARTED) {
ret = -ENETDOWN;
goto exit;
}
ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count);
if (ret < 0)
goto exit;
ret = sscanf(buf, "%x %x %x %x", &config_type, &temp_priority[0],
&temp_priority[1], &temp_priority[2]);
if ((config_type == 1 && ret != 4) || (config_type > 1)) {
ret = -EINVAL;
goto exit;
}
list_for_each_entry(arvif, &ar->arvifs, list) {
coex_config.vdev_id = arvif->vdev_id;
if (config_type == 1) {
coex_config.config_type = WMI_COEX_CONFIG_THREE_WAY_COEX_START;
coex_config.priority0 = temp_priority[0];
coex_config.priority1 = temp_priority[1];
coex_config.priority2 = temp_priority[2];
} else {
coex_config.config_type = WMI_COEX_CONFIG_THREE_WAY_COEX_RESET;
}
ret = ath11k_send_coex_config_cmd(ar, &coex_config);
if (ret) {
ath11k_warn(ar->ab,
"failed to set coex config vdev_id %d ret %d\n",
coex_config.vdev_id, ret);
goto exit;
}
}
memcpy(ar->debug.coex_priority_level, temp_priority,
sizeof(ar->debug.coex_priority_level));
ret = count;
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static const struct file_operations fops_coex_priority = {
.read = ath11k_coex_priority_read,
.write = ath11k_coex_priority_write,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_debugfs_write_burst_enable(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
int ret;
u8 enable;
if (kstrtou8_from_user(user_buf, count, 0, &enable))
return -EINVAL;
mutex_lock(&ar->conf_mutex);
if (ar->debug.burst_enabled == enable) {
ret = count;
goto exit;
}
if (ar->state != ATH11K_STATE_ON &&
ar->state != ATH11K_STATE_RESTARTED) {
ret = -ENETDOWN;
goto exit;
}
ret = ath11k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_BURST_ENABLE,
enable, ar->pdev->pdev_id);
if (ret) {
ath11k_warn(ar->ab, "failed to set burst value %d\n",
ret);
goto exit;
}
ar->debug.burst_enabled = enable;
ret = count;
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static ssize_t ath11k_debugfs_read_burst_enable(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
size_t len;
char buf[32];
len = scnprintf(buf, sizeof(buf), "%d\n", ar->debug.burst_enabled);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static const struct file_operations fops_burst_enable = {
.read = ath11k_debugfs_read_burst_enable,
.write = ath11k_debugfs_write_burst_enable,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_debugfs_write_burst_dur(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
u32 dur[4], aggr_burst;
int ret;
int ac;
char buf[128];
simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count);
/* make sure that buf is null terminated */
buf[*ppos - 1] = '\0';
ret = sscanf(buf, "%u %u %u %u", &dur[0], &dur[1], &dur[2], &dur[3]);
if (!ret)
return -EINVAL;
mutex_lock(&ar->conf_mutex);
if (ar->state != ATH11K_STATE_ON &&
ar->state != ATH11K_STATE_RESTARTED) {
ret = -ENETDOWN;
goto exit;
}
for (ac = 0; ac < 4; ac++) {
if (dur[ac] < MIN_BURST_DUR || dur[ac] > MAX_BURST_DUR) {
ret = -EINVAL;
goto exit;
}
aggr_burst = FIELD_PREP(ATH11K_AGGR_BURST_AC, ac) |
FIELD_PREP(ATH11K_AGGR_BURST_DUR, dur[ac]);
ret = ath11k_wmi_pdev_set_param(ar,
WMI_PDEV_PARAM_AGGR_BURST,
aggr_burst,
ar->pdev->pdev_id);
if (ret) {
ath11k_warn(ar->ab, "failed to set aggr burst duration for ac %d: %d\n",
ac, ret);
goto exit;
}
ar->debug.burst_dur[ac] = dur[ac];
}
ret = count;
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static ssize_t ath11k_debugfs_read_burst_dur(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
int len = 0;
char buf[128];
len = scnprintf(buf, sizeof(buf) - len, "%u %u %u %u\n",
ar->debug.burst_dur[0], ar->debug.burst_dur[1],
ar->debug.burst_dur[2], ar->debug.burst_dur[3]);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static const struct file_operations fops_burst_dur = {
.read = ath11k_debugfs_read_burst_dur,
.write = ath11k_debugfs_write_burst_dur,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_debugfs_write_rts_threshold(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
int rts_threshold, ret = 0, vif_type;
struct ath11k_vif *arvif;
struct ieee80211_vif *vif;
char buf[128] = {0};
ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count);
if (ret < 0)
return ret;
ret = sscanf(buf, "%d %d", &rts_threshold, &vif_type);
if (ret || vif_type >= NUM_NL80211_IFTYPES)
return ret;
mutex_lock(&ar->conf_mutex);
list_for_each_entry(arvif, &ar->arvifs, list) {
vif = arvif->vif;
if (vif->type == vif_type) {
ret = ath11k_wmi_vdev_set_param_cmd(ar, arvif->vdev_id,
WMI_VDEV_PARAM_RTS_THRESHOLD,
rts_threshold);
if (ret) {
ath11k_warn(ar->ab,
"Failed to set rts threshold for vdev %d: %d\n",
arvif->vdev_id, ret);
break;
}
}
}
ar->rts_threshold[vif_type] = rts_threshold;
mutex_unlock(&ar->conf_mutex);
return count;
}
static ssize_t ath11k_debugfs_read_rts_threshold(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
char buf[2048] = {0};
int len = 0, i;
mutex_lock(&ar->conf_mutex);
len += scnprintf(buf, sizeof(buf) - len, "VIF type\tRTS threshold\n");
for (i = 0; i < NUM_NL80211_IFTYPES; i++) {
if (ar->rts_threshold[i])
len += scnprintf(buf + len, sizeof(buf) - len, "%d\t\t%d\n", i, ar->rts_threshold[i]);
}
mutex_unlock(&ar->conf_mutex);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static const struct file_operations fops_rts_threshold = {
.read = ath11k_debugfs_read_rts_threshold,
.write = ath11k_debugfs_write_rts_threshold,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_write_ftm_resp(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
struct ath11k_vif *arvif;
unsigned int vdev_id, enable, param;
char buf[64] = {0};
int ret;
simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count);
buf[sizeof(buf) - 1] = '\0';
ret = sscanf(buf, "%u %u", &vdev_id, &enable);
if (ret != 2)
return -EINVAL;
mutex_lock(&ar->conf_mutex);
if (ar->state != ATH11K_STATE_ON) {
ret = -ENETDOWN;
goto exit;
}
arvif = ath11k_mac_get_arvif(ar, vdev_id);
if (!arvif) {
ret = -EINVAL;
goto exit;
}
if (enable == ar->debug.ftmr_enabled[vdev_id]) {
ret = count;
goto exit;
}
param = WMI_VDEV_PARAM_ENABLE_DISABLE_RTT_RESPONDER_ROLE;
ret = ath11k_wmi_vdev_set_param_cmd(ar, arvif->vdev_id, param, enable);
if (ret) {
ath11k_warn(ar->ab, "Failed to set ftm responder %i: %d\n",
arvif->vdev_id, ret);
goto exit;
}
ar->debug.ftmr_enabled[vdev_id] = enable;
ret = count;
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static ssize_t ath11k_read_ftm_resp(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
struct ath11k_vif *arvif;
ssize_t ret_cnt;
int len = 0, buf_len = 4096;
char *buf;
buf = kzalloc(buf_len, GFP_KERNEL);
if (!buf)
return -ENOMEM;
len += scnprintf(buf + len, buf_len - len, "%17s vdev_id status\n",
"mac");
mutex_lock(&ar->conf_mutex);
list_for_each_entry(arvif, &ar->arvifs, list)
len += scnprintf(buf + len, buf_len - len, "%pM %7u %6u\n",
arvif->vif->addr, arvif->vdev_id,
ar->debug.ftmr_enabled[arvif->vdev_id]);
mutex_unlock(&ar->conf_mutex);
ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len);
kfree(buf);
return ret_cnt;
}
static const struct file_operations fops_ftm_resp= {
.read = ath11k_read_ftm_resp,
.write = ath11k_write_ftm_resp,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_read_tx_wmi_mgmt(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
size_t len;
char buf[256];
struct sk_buff_head *q = &ar->wmi_mgmt_tx_queue;
u32 tx_queue_len = 0;
spin_lock_bh(&ar->data_lock);
tx_queue_len = skb_queue_len(q);
spin_unlock_bh(&ar->data_lock);
len = scnprintf(buf, sizeof(buf),
"tx_queue_len: %u num_tx_wmi_mgmt: %llu num_tx_work: %llu\n",
tx_queue_len, ar->debug.num_tx_wmi_mgmt,
ar->debug.num_tx_work);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static const struct file_operations fops_tx_wmi_mgmt = {
.read = ath11k_read_tx_wmi_mgmt,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_read_napi_poll_budget(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
int len = 0;
char buf[32];
len = scnprintf(buf, sizeof(buf) - len, "%u\n", ar->ab->napi_poll_budget);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static ssize_t ath11k_write_napi_poll_budget(struct file *file,
const char __user *user_buf, size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
int ret;
u32 napi_poll_budget = 0;
if (kstrtou32_from_user(user_buf, count, 0, &napi_poll_budget))
return -EINVAL;
if(napi_poll_budget > NAPI_POLL_WEIGHT)
return -EINVAL;
if(napi_poll_budget < 4)
return -EINVAL;
mutex_lock(&ar->conf_mutex);
ar->ab->napi_poll_budget = napi_poll_budget;
ret = count;
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static const struct file_operations fops_napi_poll_budget = {
.read = ath11k_read_napi_poll_budget,
.write = ath11k_write_napi_poll_budget,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_read_gro_support(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
int len = 0;
char buf[32];
len = scnprintf(buf, sizeof(buf) - len, "%u\n",
ar->ab->gro_support_enabled);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static ssize_t ath11k_write_gro_support(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
int ret;
bool gro_support = false;
if (kstrtobool_from_user(user_buf, count, &gro_support))
return -EINVAL;
mutex_lock(&ar->conf_mutex);
ar->ab->gro_support_enabled = gro_support;
ret = count;
mutex_unlock(&ar->conf_mutex);
return ret;
}
static const struct file_operations fops_gro_support = {
.read = ath11k_read_gro_support,
.write = ath11k_write_gro_support,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static ssize_t ath11k_write_mgmt_retry_limit(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;
u8 retry_limit;
int ret;
if (kstrtou8_from_user(user_buf, count, 0, &retry_limit))
return -EINVAL;
if (retry_limit < ATH11K_MIN_MGMT_RETRY_LIMIT_COUNT ||
retry_limit > ATH11K_MAX_MGMT_RETRY_LIMIT_COUNT)
return -EINVAL;
mutex_lock(&ar->conf_mutex);
if (ar->mgmt_retry_limit == retry_limit) {
ret = count;
goto exit;
}
if (ar->state != ATH11K_STATE_ON) {
ret = -ENETDOWN;
goto exit;
}
ret = ath11k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_MGMT_RETRY_LIMIT,
retry_limit, pdev->pdev_id);
if (ret) {
ath11k_warn(ar->ab, "failed to set mgmt retry limit: %d\n", ret);
goto exit;
}
ar->mgmt_retry_limit = retry_limit;
ret = count;
exit:
mutex_unlock(&ar->conf_mutex);
return ret;
}
static ssize_t ath11k_read_mgmt_retry_limit(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct ath11k *ar = file->private_data;
int len = 0;
char buf[8];
mutex_lock(&ar->conf_mutex);
len = scnprintf(buf, sizeof(buf) - len, "%d\n",
ar->mgmt_retry_limit);
mutex_unlock(&ar->conf_mutex);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static const struct file_operations fops_mgmt_retry_limit = {
.read = ath11k_read_mgmt_retry_limit,
.write = ath11k_write_mgmt_retry_limit,
.open = simple_open,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
int ath11k_debugfs_register(struct ath11k *ar)
{
struct ath11k_base *ab = ar->ab;
struct dentry *dentry = ab->debugfs_soc, *child;
char pdev_name[5];
char buf[100] = {0}, *f_name;
if (!(IS_ERR_OR_NULL(ar->debug.debugfs_pdev)))
return 0;
snprintf(pdev_name, sizeof(pdev_name), "%s%d", "mac", ar->pdev_idx);
ar->debug.debugfs_pdev = debugfs_create_dir(pdev_name, dentry);
if (IS_ERR(ar->debug.debugfs_pdev))
return PTR_ERR(ar->debug.debugfs_pdev);
if (ab->qmi.target.num_macs != 1)
goto phy_symlinks;
spin_lock(&dentry->d_lock);
list_for_each_entry(child, &dentry->d_subdirs, d_child) {
if (child->d_flags & DCACHE_DIRECTORY_TYPE)
continue;
f_name = child->d_name.name;
snprintf(buf, 100, "../%s", f_name);
debugfs_create_symlink(f_name, ar->debug.debugfs_pdev, buf);
}
spin_unlock(&dentry->d_lock);
phy_symlinks:
/* 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);
ath11k_init_tx_latency_stats(ar);
ath11k_init_pktlog(ar);
ath11k_smart_ant_debugfs_init(ar);
init_completion(&ar->tpc_complete);
init_completion(&ab->ani_ofdm_event);
init_completion(&ab->ani_cck_event);
init_completion(&ar->ani_status_event);
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("btcoex", 0644,
ar->debug.debugfs_pdev, ar,
&fops__btcoex);
debugfs_create_file("btcoex_duty_cycle", 0644,
ar->debug.debugfs_pdev, ar,
&fops__btcoex_duty_cycle);
debugfs_create_file("btcoex_algorithm", 0644,
ar->debug.debugfs_pdev, ar,
&fops_btcoex_algo);
debugfs_create_file("dump_mgmt_stats", 0644,
ar->debug.debugfs_pdev, ar,
&fops_dump_mgmt_stats);
debugfs_create_file("disable_dynamic_bw", 0644,
ar->debug.debugfs_pdev, ar,
&fops_disable_dyn_bw);
debugfs_create_file("ps_state_enable", 0600, ar->debug.debugfs_pdev, ar,
&fops_ps_state_enable);
debugfs_create_file("napi_poll_budget", 0644, ar->debug.debugfs_pdev, ar,
&fops_napi_poll_budget);
debugfs_create_file("gro_support_enabled", 0644, ar->debug.debugfs_pdev,
ar, &fops_gro_support);
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);
}
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 (ar->hw->wiphy->bands[NL80211_BAND_6GHZ]) {
debugfs_create_file("simulate_awgn", 0200,
ar->debug.debugfs_pdev, ar,
&fops_simulate_awgn);
}
debugfs_create_file("enable_m3_dump", 0644,
ar->debug.debugfs_pdev, ar,
&fops_enable_m3_dump);
debugfs_create_file("tpc_stats", 0400, ar->debug.debugfs_pdev,
ar, &fops_tpc_stats);
debugfs_create_file("tpc_stats_type", 0600, ar->debug.debugfs_pdev,
ar, &fops_tpc_stats_type);
debugfs_create_file("athdiag", S_IRUSR | S_IWUSR,
ar->debug.debugfs_pdev, ar,
&fops_athdiag);
debugfs_create_file("enable_dbr_debug", 0200, ar->debug.debugfs_pdev,
ar, &fops_dbr_debug);
debugfs_create_file("ani_enable", S_IRUSR | S_IWUSR,
ar->debug.debugfs_pdev, ar, &fops_ani_enable);
debugfs_create_file("ani_level", S_IRUSR | S_IWUSR,
ar->debug.debugfs_pdev, ar, &fops_ani_level);
debugfs_create_file("ani_poll_period", S_IRUSR | S_IWUSR,
ar->debug.debugfs_pdev, ar, &fops_ani_poll_period);
debugfs_create_file("ani_listen_period", S_IRUSR | S_IWUSR,
ar->debug.debugfs_pdev, ar, &fops_ani_listen_period);
debugfs_create_file("coex_priority", 0600,
ar->debug.debugfs_pdev, ar, &fops_coex_priority);
debugfs_create_file("medium_busy", S_IRUSR, ar->debug.debugfs_pdev, ar,
&fops_medium_busy);
debugfs_create_file("burst_enable", 0600,
ar->debug.debugfs_pdev, ar, &fops_burst_enable);
debugfs_create_file("burst_dur", 0600,
ar->debug.debugfs_pdev, ar, &fops_burst_dur);
debugfs_create_file("rts_threshold", 0600,
ar->debug.debugfs_pdev, ar, &fops_rts_threshold);
debugfs_create_file("warm_hw_reset", 0600, ar->debug.debugfs_pdev, ar,
&fops_warm_hw_reset);
debugfs_create_file("non_aggr_sw_retry_thold", 0600,
ar->debug.debugfs_pdev,
ar, &fops_non_aggr_sw_retry_thold);
debugfs_create_file("aggr_sw_retry_thold", 0600,
ar->debug.debugfs_pdev,
ar, &fops_aggr_sw_retry_thold);
debugfs_create_file("ftm_resp", 0600,
ar->debug.debugfs_pdev,
ar, &fops_ftm_resp);
debugfs_create_file("mgmt_retry_limit", 0600,
ar->debug.debugfs_pdev,
ar, &fops_mgmt_retry_limit);
if (!ab->userpd_id) {
debugfs_create_file("tx_wmi_mgmt", 0600,
ar->debug.debugfs_pdev,
ar, &fops_tx_wmi_mgmt);
ar->debug.num_tx_work = 0;
ar->debug.num_tx_wmi_mgmt = 0;
}
return 0;
}
void ath11k_debugfs_unregister(struct ath11k *ar)
{
struct ath11k_db_module_debug *module_debug;
struct ath11k_db_ring_debug *db_ring_debug;
int i;
for (i = 0; i < WMI_DIRECT_BUF_MAX; i++) {
module_debug = ar->debug.module_debug[i];
if (!module_debug)
continue;
db_ring_debug = &module_debug->db_ring_debug;
module_debug->db_ring_debug_enabled = false;
kfree(db_ring_debug->entries);
db_ring_debug->entries = NULL;
debugfs_remove_recursive(module_debug->module_debugfs);
kfree(module_debug);
ar->debug.module_debug[i] = NULL;
}
ath11k_deinit_pktlog(ar);
kfree(ar->debug.tx_delay_stats[0]);
debugfs_remove_recursive(ar->debug.debugfs_pdev);
ar->debug.debugfs_pdev = NULL;
}