]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
wifi: ath11k: Add support unassociated client CFR
authorVenkateswara Naralasetty <quic_vnaralas@quicinc.com>
Tue, 30 Dec 2025 08:25:17 +0000 (13:55 +0530)
committerJeff Johnson <jeff.johnson@oss.qualcomm.com>
Fri, 16 Jan 2026 01:19:37 +0000 (17:19 -0800)
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>
drivers/net/wireless/ath/ath11k/cfr.c
drivers/net/wireless/ath/ath11k/cfr.h
drivers/net/wireless/ath/ath11k/mac.c
drivers/net/wireless/ath/ath11k/wmi.c
drivers/net/wireless/ath/ath11k/wmi.h

index bf0b880e8746678353654bc37787756b7b1175b7..e22b0151833caad05738b71c67590baabf6208fb 100644 (file)
@@ -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,
index 7d161f7f7be8465d7d5aa4655fbcda866add9416..e7b69e98cbf5378866f3d1d4a53ccc59e9148c8f 100644 (file)
@@ -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,
index efd2e75f96139033b10a2177d1fd2acd235c1fb9..a7a1914b34c3e9037964a3adfb6a7479e7e69f4e 100644 (file)
@@ -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;
index b14edc0820a242121bdb496d51ab72cedaef745a..b40a31414a471224a276fe1c9f203a3781f43e18 100644 (file)
@@ -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,
index 1562d169ba9ab42b1af09f01bf726a9de9cf0a6e..afc78fa4389bc44382a7edf222b7164eb818651d 100644 (file)
@@ -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,