mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-06-02 05:13:11 -04:00
wifi: ath11k: Add support unassociated client CFR
Provide debugfs interfaces support to config unassociated client CFR
from the user space.
To enable CFR capture for unassociated clients,
echo "<mac address> <val> <periodicity>"
> /sys/kernel/debug/ieee80211/phyX/ath11k/cfr_unassoc
Mac address: mac address of the client.
Val: 0 - start CFR capture
1 - stop CFR capture
Periodicity: Periodicity at which hardware is expected to collect CFR
dump.
0 - single shot capture.
non zero - for Periodic captures (value must be multiple of 10 ms)
Tested-on: IPQ8074 hw2.0 PCI IPQ8074 WLAN.HK.2.5.0.1-00991-QCAHKSWPL_SILICONZ-1
Tested-on: WCN6855 hw2.1 PCI WLAN.HSP.1.1-04685-QCAHSPSWPL_V1_V2_SILICONZ_IOE-1
Signed-off-by: Venkateswara Naralasetty <quic_vnaralas@quicinc.com>
Co-developed-by: Yu Zhang (Yuriy) <yu.zhang@oss.qualcomm.com>
Signed-off-by: Yu Zhang (Yuriy) <yu.zhang@oss.qualcomm.com>
Reviewed-by: Vasanthakumar Thiagarajan <vasanthakumar.thiagarajan@oss.qualcomm.com>
Reviewed-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
Signed-off-by: Qian Zhang <qian.zhang@oss.qualcomm.com>
Link: https://patch.msgid.link/20251230082520.3401007-4-qian.zhang@oss.qualcomm.com
Signed-off-by: Jeff Johnson <jeff.johnson@oss.qualcomm.com>
This commit is contained in:
committed by
Jeff Johnson
parent
9754d4ba4d
commit
b3d43d8903
@@ -14,6 +14,60 @@ static int ath11k_cfr_process_data(struct ath11k *ar,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Helper function to check whether the given peer mac address
|
||||
* is in unassociated peer pool or not.
|
||||
*/
|
||||
bool ath11k_cfr_peer_is_in_cfr_unassoc_pool(struct ath11k *ar, const u8 *peer_mac)
|
||||
{
|
||||
struct ath11k_cfr *cfr = &ar->cfr;
|
||||
struct cfr_unassoc_pool_entry *entry;
|
||||
int i;
|
||||
|
||||
if (!ar->cfr_enabled)
|
||||
return false;
|
||||
|
||||
spin_lock_bh(&cfr->lock);
|
||||
for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {
|
||||
entry = &cfr->unassoc_pool[i];
|
||||
if (!entry->is_valid)
|
||||
continue;
|
||||
|
||||
if (ether_addr_equal(peer_mac, entry->peer_mac)) {
|
||||
spin_unlock_bh(&cfr->lock);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_bh(&cfr->lock);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ath11k_cfr_update_unassoc_pool_entry(struct ath11k *ar,
|
||||
const u8 *peer_mac)
|
||||
{
|
||||
struct ath11k_cfr *cfr = &ar->cfr;
|
||||
struct cfr_unassoc_pool_entry *entry;
|
||||
int i;
|
||||
|
||||
spin_lock_bh(&cfr->lock);
|
||||
for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {
|
||||
entry = &cfr->unassoc_pool[i];
|
||||
if (!entry->is_valid)
|
||||
continue;
|
||||
|
||||
if (ether_addr_equal(peer_mac, entry->peer_mac) &&
|
||||
entry->period == 0) {
|
||||
memset(entry->peer_mac, 0, ETH_ALEN);
|
||||
entry->is_valid = false;
|
||||
cfr->cfr_enabled_peer_cnt--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_bh(&cfr->lock);
|
||||
}
|
||||
|
||||
void ath11k_cfr_decrement_peer_count(struct ath11k *ar,
|
||||
struct ath11k_sta *arsta)
|
||||
{
|
||||
@@ -130,6 +184,59 @@ int ath11k_cfr_send_peer_cfr_capture_cmd(struct ath11k *ar,
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ath11k_cfr_update_unassoc_pool(struct ath11k *ar,
|
||||
struct ath11k_per_peer_cfr_capture *params,
|
||||
u8 *peer_mac)
|
||||
{
|
||||
struct ath11k_cfr *cfr = &ar->cfr;
|
||||
struct cfr_unassoc_pool_entry *entry;
|
||||
int available_idx = -1;
|
||||
int i;
|
||||
|
||||
guard(spinlock_bh)(&cfr->lock);
|
||||
|
||||
if (!params->cfr_enable) {
|
||||
for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {
|
||||
entry = &cfr->unassoc_pool[i];
|
||||
if (ether_addr_equal(peer_mac, entry->peer_mac)) {
|
||||
memset(entry->peer_mac, 0, ETH_ALEN);
|
||||
entry->is_valid = false;
|
||||
cfr->cfr_enabled_peer_cnt--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (cfr->cfr_enabled_peer_cnt >= ATH11K_MAX_CFR_ENABLED_CLIENTS) {
|
||||
ath11k_info(ar->ab, "Max cfr peer threshold reached\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {
|
||||
entry = &cfr->unassoc_pool[i];
|
||||
|
||||
if (ether_addr_equal(peer_mac, entry->peer_mac)) {
|
||||
ath11k_info(ar->ab,
|
||||
"peer entry already present updating params\n");
|
||||
entry->period = params->cfr_period;
|
||||
available_idx = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (available_idx < 0 && !entry->is_valid)
|
||||
available_idx = i;
|
||||
}
|
||||
|
||||
if (available_idx >= 0) {
|
||||
entry = &cfr->unassoc_pool[available_idx];
|
||||
ether_addr_copy(entry->peer_mac, peer_mac);
|
||||
entry->period = params->cfr_period;
|
||||
entry->is_valid = true;
|
||||
cfr->cfr_enabled_peer_cnt++;
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t ath11k_read_file_enable_cfr(struct file *file,
|
||||
char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
@@ -188,10 +295,127 @@ static const struct file_operations fops_enable_cfr = {
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
static ssize_t ath11k_write_file_cfr_unassoc(struct file *file,
|
||||
const char __user *ubuf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct ath11k *ar = file->private_data;
|
||||
struct ath11k_cfr *cfr = &ar->cfr;
|
||||
struct cfr_unassoc_pool_entry *entry;
|
||||
char buf[64] = {};
|
||||
u8 peer_mac[6];
|
||||
u32 cfr_capture_enable;
|
||||
u32 cfr_capture_period;
|
||||
int available_idx = -1;
|
||||
int ret, i;
|
||||
|
||||
simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, ubuf, count);
|
||||
|
||||
guard(mutex)(&ar->conf_mutex);
|
||||
guard(spinlock_bh)(&cfr->lock);
|
||||
|
||||
if (ar->state != ATH11K_STATE_ON)
|
||||
return -ENETDOWN;
|
||||
|
||||
if (!ar->cfr_enabled) {
|
||||
ath11k_err(ar->ab, "CFR is not enabled on this pdev %d\n",
|
||||
ar->pdev_idx);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = sscanf(buf, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx %u %u",
|
||||
&peer_mac[0], &peer_mac[1], &peer_mac[2], &peer_mac[3],
|
||||
&peer_mac[4], &peer_mac[5], &cfr_capture_enable,
|
||||
&cfr_capture_period);
|
||||
|
||||
if (ret < 1)
|
||||
return -EINVAL;
|
||||
|
||||
if (cfr_capture_enable && ret != 8)
|
||||
return -EINVAL;
|
||||
|
||||
if (!cfr_capture_enable) {
|
||||
for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {
|
||||
entry = &cfr->unassoc_pool[i];
|
||||
if (ether_addr_equal(peer_mac, entry->peer_mac)) {
|
||||
memset(entry->peer_mac, 0, ETH_ALEN);
|
||||
entry->is_valid = false;
|
||||
cfr->cfr_enabled_peer_cnt--;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
if (cfr->cfr_enabled_peer_cnt >= ATH11K_MAX_CFR_ENABLED_CLIENTS) {
|
||||
ath11k_info(ar->ab, "Max cfr peer threshold reached\n");
|
||||
return count;
|
||||
}
|
||||
|
||||
for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {
|
||||
entry = &cfr->unassoc_pool[i];
|
||||
|
||||
if (available_idx < 0 && !entry->is_valid)
|
||||
available_idx = i;
|
||||
|
||||
if (ether_addr_equal(peer_mac, entry->peer_mac)) {
|
||||
ath11k_info(ar->ab,
|
||||
"peer entry already present updating params\n");
|
||||
entry->period = cfr_capture_period;
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
if (available_idx >= 0) {
|
||||
entry = &cfr->unassoc_pool[available_idx];
|
||||
ether_addr_copy(entry->peer_mac, peer_mac);
|
||||
entry->period = cfr_capture_period;
|
||||
entry->is_valid = true;
|
||||
cfr->cfr_enabled_peer_cnt++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t ath11k_read_file_cfr_unassoc(struct file *file,
|
||||
char __user *ubuf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct ath11k *ar = file->private_data;
|
||||
struct ath11k_cfr *cfr = &ar->cfr;
|
||||
struct cfr_unassoc_pool_entry *entry;
|
||||
char buf[512] = {};
|
||||
int len = 0, i;
|
||||
|
||||
spin_lock_bh(&cfr->lock);
|
||||
|
||||
for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {
|
||||
entry = &cfr->unassoc_pool[i];
|
||||
if (entry->is_valid)
|
||||
len += scnprintf(buf + len, sizeof(buf) - len,
|
||||
"peer: %pM period: %u\n",
|
||||
entry->peer_mac, entry->period);
|
||||
}
|
||||
|
||||
spin_unlock_bh(&cfr->lock);
|
||||
|
||||
return simple_read_from_buffer(ubuf, count, ppos, buf, len);
|
||||
}
|
||||
|
||||
static const struct file_operations fops_configure_cfr_unassoc = {
|
||||
.write = ath11k_write_file_cfr_unassoc,
|
||||
.read = ath11k_read_file_cfr_unassoc,
|
||||
.open = simple_open,
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
static void ath11k_cfr_debug_unregister(struct ath11k *ar)
|
||||
{
|
||||
debugfs_remove(ar->cfr.enable_cfr);
|
||||
ar->cfr.enable_cfr = NULL;
|
||||
debugfs_remove(ar->cfr.cfr_unassoc);
|
||||
ar->cfr.cfr_unassoc = NULL;
|
||||
}
|
||||
|
||||
static void ath11k_cfr_debug_register(struct ath11k *ar)
|
||||
@@ -199,6 +423,10 @@ static void ath11k_cfr_debug_register(struct ath11k *ar)
|
||||
ar->cfr.enable_cfr = debugfs_create_file("enable_cfr", 0600,
|
||||
ar->debug.debugfs_pdev, ar,
|
||||
&fops_enable_cfr);
|
||||
|
||||
ar->cfr.cfr_unassoc = debugfs_create_file("cfr_unassoc", 0600,
|
||||
ar->debug.debugfs_pdev, ar,
|
||||
&fops_configure_cfr_unassoc);
|
||||
}
|
||||
|
||||
void ath11k_cfr_lut_update_paddr(struct ath11k *ar, dma_addr_t paddr,
|
||||
|
||||
@@ -45,6 +45,12 @@ struct ath11k_look_up_table {
|
||||
struct ath11k_dbring_element *buff;
|
||||
};
|
||||
|
||||
struct cfr_unassoc_pool_entry {
|
||||
u8 peer_mac[ETH_ALEN];
|
||||
u32 period;
|
||||
bool is_valid;
|
||||
};
|
||||
|
||||
struct ath11k_cfr {
|
||||
struct ath11k_dbring rx_ring;
|
||||
/* Protects cfr data */
|
||||
@@ -53,6 +59,7 @@ struct ath11k_cfr {
|
||||
spinlock_t lut_lock;
|
||||
struct ath11k_look_up_table *lut;
|
||||
struct dentry *enable_cfr;
|
||||
struct dentry *cfr_unassoc;
|
||||
u8 cfr_enabled_peer_cnt;
|
||||
u32 lut_num;
|
||||
u64 tx_evt_cnt;
|
||||
@@ -66,6 +73,7 @@ struct ath11k_cfr {
|
||||
u64 clear_txrx_event;
|
||||
u64 cfr_dma_aborts;
|
||||
bool enabled;
|
||||
struct cfr_unassoc_pool_entry unassoc_pool[ATH11K_MAX_CFR_ENABLED_CLIENTS];
|
||||
};
|
||||
|
||||
enum ath11k_cfr_capture_method {
|
||||
@@ -89,6 +97,13 @@ void ath11k_cfr_lut_update_paddr(struct ath11k *ar, dma_addr_t paddr,
|
||||
u32 buf_id);
|
||||
void ath11k_cfr_decrement_peer_count(struct ath11k *ar,
|
||||
struct ath11k_sta *arsta);
|
||||
void ath11k_cfr_update_unassoc_pool_entry(struct ath11k *ar,
|
||||
const u8 *peer_mac);
|
||||
bool ath11k_cfr_peer_is_in_cfr_unassoc_pool(struct ath11k *ar,
|
||||
const u8 *peer_mac);
|
||||
void ath11k_cfr_update_unassoc_pool(struct ath11k *ar,
|
||||
struct ath11k_per_peer_cfr_capture *params,
|
||||
u8 *peer_mac);
|
||||
int ath11k_cfr_send_peer_cfr_capture_cmd(struct ath11k *ar,
|
||||
struct ath11k_sta *arsta,
|
||||
struct ath11k_per_peer_cfr_capture *params,
|
||||
@@ -114,6 +129,24 @@ static inline void ath11k_cfr_decrement_peer_count(struct ath11k *ar,
|
||||
{
|
||||
}
|
||||
|
||||
static inline void ath11k_cfr_update_unassoc_pool_entry(struct ath11k *ar,
|
||||
const u8 *peer_mac)
|
||||
{
|
||||
}
|
||||
|
||||
static inline bool
|
||||
ath11k_cfr_peer_is_in_cfr_unassoc_pool(struct ath11k *ar, const u8 *peer_mac)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline void
|
||||
ath11k_cfr_update_unassoc_pool(struct ath11k *ar,
|
||||
struct ath11k_per_peer_cfr_capture *params,
|
||||
u8 *peer_mac)
|
||||
{
|
||||
}
|
||||
|
||||
static inline int
|
||||
ath11k_cfr_send_peer_cfr_capture_cmd(struct ath11k *ar,
|
||||
struct ath11k_sta *arsta,
|
||||
|
||||
@@ -6186,6 +6186,8 @@ static int ath11k_mac_mgmt_tx_wmi(struct ath11k *ar, struct ath11k_vif *arvif,
|
||||
dma_addr_t paddr;
|
||||
int buf_id;
|
||||
int ret;
|
||||
bool tx_params_valid = false;
|
||||
bool peer_in_unassoc_pool;
|
||||
|
||||
ATH11K_SKB_CB(skb)->ar = ar;
|
||||
|
||||
@@ -6224,7 +6226,18 @@ static int ath11k_mac_mgmt_tx_wmi(struct ath11k *ar, struct ath11k_vif *arvif,
|
||||
|
||||
ATH11K_SKB_CB(skb)->paddr = paddr;
|
||||
|
||||
ret = ath11k_wmi_mgmt_send(ar, arvif->vdev_id, buf_id, skb);
|
||||
peer_in_unassoc_pool = ath11k_cfr_peer_is_in_cfr_unassoc_pool(ar, hdr->addr1);
|
||||
|
||||
if (ar->cfr_enabled &&
|
||||
ieee80211_is_probe_resp(hdr->frame_control) &&
|
||||
peer_in_unassoc_pool)
|
||||
tx_params_valid = true;
|
||||
|
||||
if (peer_in_unassoc_pool)
|
||||
ath11k_cfr_update_unassoc_pool_entry(ar, hdr->addr1);
|
||||
|
||||
ret = ath11k_wmi_mgmt_send(ar, arvif->vdev_id, buf_id, skb,
|
||||
tx_params_valid);
|
||||
if (ret) {
|
||||
ath11k_warn(ar->ab, "failed to send mgmt frame: %d\n", ret);
|
||||
goto err_unmap_buf;
|
||||
|
||||
@@ -651,11 +651,12 @@ static u32 ath11k_wmi_mgmt_get_freq(struct ath11k *ar,
|
||||
}
|
||||
|
||||
int ath11k_wmi_mgmt_send(struct ath11k *ar, u32 vdev_id, u32 buf_id,
|
||||
struct sk_buff *frame)
|
||||
struct sk_buff *frame, bool tx_params_valid)
|
||||
{
|
||||
struct ath11k_pdev_wmi *wmi = ar->wmi;
|
||||
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(frame);
|
||||
struct wmi_mgmt_send_cmd *cmd;
|
||||
struct wmi_mgmt_send_params *params;
|
||||
struct wmi_tlv *frame_tlv;
|
||||
struct sk_buff *skb;
|
||||
u32 buf_len;
|
||||
@@ -665,6 +666,8 @@ int ath11k_wmi_mgmt_send(struct ath11k *ar, u32 vdev_id, u32 buf_id,
|
||||
frame->len : WMI_MGMT_SEND_DOWNLD_LEN;
|
||||
|
||||
len = sizeof(*cmd) + sizeof(*frame_tlv) + roundup(buf_len, 4);
|
||||
if (tx_params_valid)
|
||||
len += sizeof(*params);
|
||||
|
||||
skb = ath11k_wmi_alloc_skb(wmi->wmi_ab, len);
|
||||
if (!skb)
|
||||
@@ -680,7 +683,7 @@ int ath11k_wmi_mgmt_send(struct ath11k *ar, u32 vdev_id, u32 buf_id,
|
||||
cmd->paddr_hi = upper_32_bits(ATH11K_SKB_CB(frame)->paddr);
|
||||
cmd->frame_len = frame->len;
|
||||
cmd->buf_len = buf_len;
|
||||
cmd->tx_params_valid = 0;
|
||||
cmd->tx_params_valid = !!tx_params_valid;
|
||||
|
||||
frame_tlv = (struct wmi_tlv *)(skb->data + sizeof(*cmd));
|
||||
frame_tlv->header = FIELD_PREP(WMI_TLV_TAG, WMI_TAG_ARRAY_BYTE) |
|
||||
@@ -690,6 +693,15 @@ int ath11k_wmi_mgmt_send(struct ath11k *ar, u32 vdev_id, u32 buf_id,
|
||||
|
||||
ath11k_ce_byte_swap(frame_tlv->value, buf_len);
|
||||
|
||||
if (tx_params_valid) {
|
||||
params =
|
||||
(struct wmi_mgmt_send_params *)(skb->data + (len - sizeof(*params)));
|
||||
params->tlv_header = FIELD_PREP(WMI_TLV_TAG, WMI_TAG_TX_SEND_PARAMS) |
|
||||
FIELD_PREP(WMI_TLV_LEN,
|
||||
sizeof(*params) - TLV_HDR_SIZE);
|
||||
params->tx_params_dword1 |= WMI_TX_PARAMS_DWORD1_CFR_CAPTURE;
|
||||
}
|
||||
|
||||
ret = ath11k_wmi_cmd_send(wmi, skb, WMI_MGMT_TX_SEND_CMDID);
|
||||
if (ret) {
|
||||
ath11k_warn(ar->ab,
|
||||
|
||||
@@ -6391,7 +6391,7 @@ int ath11k_wmi_cmd_send(struct ath11k_pdev_wmi *wmi, struct sk_buff *skb,
|
||||
u32 cmd_id);
|
||||
struct sk_buff *ath11k_wmi_alloc_skb(struct ath11k_wmi_base *wmi_sc, u32 len);
|
||||
int ath11k_wmi_mgmt_send(struct ath11k *ar, u32 vdev_id, u32 buf_id,
|
||||
struct sk_buff *frame);
|
||||
struct sk_buff *frame, bool tx_params_valid);
|
||||
int ath11k_wmi_p2p_go_bcn_ie(struct ath11k *ar, u32 vdev_id,
|
||||
const u8 *p2p_ie);
|
||||
int ath11k_wmi_bcn_tmpl(struct ath11k *ar, u32 vdev_id,
|
||||
|
||||
Reference in New Issue
Block a user