]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
wifi: ath11k: Register handler for CFR capture event
authorVenkateswara Naralasetty <quic_vnaralas@quicinc.com>
Tue, 30 Dec 2025 08:25:20 +0000 (13:55 +0530)
committerJeff Johnson <jeff.johnson@oss.qualcomm.com>
Fri, 16 Jan 2026 01:19:38 +0000 (17:19 -0800)
Firmware sends CFR meta data through the WMI event
WMI_PEER_CFR_CAPTURE_EVENT. Parse the meta data coming from the firmware
and invoke correlate_and_relay function to correlate the CFR meta data
with the CFR payload coming from the other WMI event
WMI_PDEV_DMA_RING_BUF_RELEASE_EVENT.

Release the buffer to user space once correlate and relay return
success.

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-7-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 ee7626bd4b1a5ef5f3d2287a2a1cff851e7d8918..61bf1c0884f72841eb2667a014e2740bedf010fc 100644 (file)
@@ -252,6 +252,160 @@ static int ath11k_cfr_process_data(struct ath11k *ar,
        return status;
 }
 
+static void ath11k_cfr_fill_hdr_info(struct ath11k *ar,
+                                    struct ath11k_csi_cfr_header *header,
+                                    struct ath11k_cfr_peer_tx_param *params)
+{
+       struct ath11k_cfr *cfr;
+
+       cfr = &ar->cfr;
+       header->cfr_metadata_version = ATH11K_CFR_META_VERSION_4;
+       header->cfr_data_version = ATH11K_CFR_DATA_VERSION_1;
+       header->cfr_metadata_len = sizeof(struct cfr_metadata);
+       header->chip_type = ar->ab->hw_rev;
+       header->meta_data.status = FIELD_GET(WMI_CFR_PEER_CAPTURE_STATUS,
+                                            params->status);
+       header->meta_data.capture_bw = params->bandwidth;
+
+       /*
+        * FW reports phymode will always be HE mode.
+        * Replace it with cached phy mode during peer assoc
+        */
+       header->meta_data.phy_mode = cfr->phymode;
+
+       header->meta_data.prim20_chan = params->primary_20mhz_chan;
+       header->meta_data.center_freq1 = params->band_center_freq1;
+       header->meta_data.center_freq2 = params->band_center_freq2;
+
+       /*
+        * CFR capture is triggered by the ACK of a QoS Null frame:
+        * - 20 MHz: Legacy ACK
+        * - 40/80/160 MHz: DUP Legacy ACK
+        */
+       header->meta_data.capture_mode = params->bandwidth ?
+               ATH11K_CFR_CAPTURE_DUP_LEGACY_ACK : ATH11K_CFR_CAPTURE_LEGACY_ACK;
+       header->meta_data.capture_type = params->capture_method;
+       header->meta_data.num_rx_chain = ar->num_rx_chains;
+       header->meta_data.sts_count = params->spatial_streams;
+       header->meta_data.timestamp = params->timestamp_us;
+       ether_addr_copy(header->meta_data.peer_addr, params->peer_mac_addr);
+       memcpy(header->meta_data.chain_rssi, params->chain_rssi,
+              sizeof(params->chain_rssi));
+       memcpy(header->meta_data.chain_phase, params->chain_phase,
+              sizeof(params->chain_phase));
+       memcpy(header->meta_data.agc_gain, params->agc_gain,
+              sizeof(params->agc_gain));
+}
+
+int ath11k_process_cfr_capture_event(struct ath11k_base *ab,
+                                    struct ath11k_cfr_peer_tx_param *params)
+{
+       struct ath11k_look_up_table *lut = NULL;
+       u32 end_magic = ATH11K_CFR_END_MAGIC;
+       struct ath11k_csi_cfr_header *header;
+       struct ath11k_dbring_element *buff;
+       struct ath11k_cfr *cfr;
+       dma_addr_t buf_addr;
+       struct ath11k *ar;
+       u8 tx_status;
+       int status;
+       int i;
+
+       rcu_read_lock();
+       ar = ath11k_mac_get_ar_by_vdev_id(ab, params->vdev_id);
+       if (!ar) {
+               rcu_read_unlock();
+               ath11k_warn(ab, "Failed to get ar for vdev id %d\n",
+                           params->vdev_id);
+               return -ENOENT;
+       }
+
+       cfr = &ar->cfr;
+       rcu_read_unlock();
+
+       if (WMI_CFR_CAPTURE_STATUS_PEER_PS & params->status) {
+               ath11k_warn(ab, "CFR capture failed as peer %pM is in powersave",
+                           params->peer_mac_addr);
+               return -EINVAL;
+       }
+
+       if (!(WMI_CFR_PEER_CAPTURE_STATUS & params->status)) {
+               ath11k_warn(ab, "CFR capture failed for the peer : %pM",
+                           params->peer_mac_addr);
+               cfr->tx_peer_status_cfr_fail++;
+               return -EINVAL;
+       }
+
+       tx_status = FIELD_GET(WMI_CFR_FRAME_TX_STATUS, params->status);
+       if (tx_status != WMI_FRAME_TX_STATUS_OK) {
+               ath11k_warn(ab, "WMI tx status %d for the peer %pM",
+                           tx_status, params->peer_mac_addr);
+               cfr->tx_evt_status_cfr_fail++;
+               return -EINVAL;
+       }
+
+       buf_addr = (((u64)FIELD_GET(WMI_CFR_CORRELATION_INFO2_BUF_ADDR_HIGH,
+                                   params->correlation_info_2)) << 32) |
+                  params->correlation_info_1;
+
+       spin_lock_bh(&cfr->lut_lock);
+
+       if (!cfr->lut) {
+               spin_unlock_bh(&cfr->lut_lock);
+               return -EINVAL;
+       }
+
+       for (i = 0; i < cfr->lut_num; i++) {
+               struct ath11k_look_up_table *temp = &cfr->lut[i];
+
+               if (temp->dbr_address == buf_addr) {
+                       lut = &cfr->lut[i];
+                       break;
+               }
+       }
+
+       if (!lut) {
+               spin_unlock_bh(&cfr->lut_lock);
+               ath11k_warn(ab, "lut failure to process tx event\n");
+               cfr->tx_dbr_lookup_fail++;
+               return -EINVAL;
+       }
+
+       lut->tx_ppdu_id = FIELD_GET(WMI_CFR_CORRELATION_INFO2_PPDU_ID,
+                                   params->correlation_info_2);
+       lut->txrx_tstamp = jiffies;
+
+       header = &lut->header;
+       header->start_magic_num = ATH11K_CFR_START_MAGIC;
+       header->vendorid = VENDOR_QCA;
+       header->platform_type = PLATFORM_TYPE_ARM;
+
+       ath11k_cfr_fill_hdr_info(ar, header, params);
+
+       status = ath11k_cfr_correlate_and_relay(ar, lut,
+                                               ATH11K_CORRELATE_TX_EVENT);
+       if (status == ATH11K_CORRELATE_STATUS_RELEASE) {
+               ath11k_dbg(ab, ATH11K_DBG_CFR,
+                          "Releasing CFR data to user space");
+               ath11k_cfr_rfs_write(ar, &lut->header,
+                                    sizeof(struct ath11k_csi_cfr_header),
+                                    lut->data, lut->data_len,
+                                    &end_magic, sizeof(u32));
+               buff = lut->buff;
+               ath11k_cfr_release_lut_entry(lut);
+
+               ath11k_dbring_bufs_replenish(ar, &cfr->rx_ring, buff,
+                                            WMI_DIRECT_BUF_CFR);
+       } else if (status == ATH11K_CORRELATE_STATUS_HOLD) {
+               ath11k_dbg(ab, ATH11K_DBG_CFR,
+                          "dbr event is not yet received holding buf\n");
+       }
+
+       spin_unlock_bh(&cfr->lut_lock);
+
+       return 0;
+}
+
 /* Helper function to check whether the given peer mac address
  * is in unassociated peer pool or not.
  */
@@ -711,6 +865,13 @@ void ath11k_cfr_lut_update_paddr(struct ath11k *ar, dma_addr_t paddr,
                cfr->lut[buf_id].dbr_address = paddr;
 }
 
+void ath11k_cfr_update_phymode(struct ath11k *ar, enum wmi_phy_mode phymode)
+{
+       struct ath11k_cfr *cfr = &ar->cfr;
+
+       cfr->phymode = phymode;
+}
+
 static void ath11k_cfr_ring_free(struct ath11k *ar)
 {
        struct ath11k_cfr *cfr = &ar->cfr;
index c8e5086674d2866e141022308911c632dcc0fecb..94fcb706f2ef2f7cccfa369f9fdd8443182f1107 100644 (file)
@@ -27,8 +27,37 @@ enum ath11k_cfr_correlate_event_type {
 struct ath11k_sta;
 struct ath11k_per_peer_cfr_capture;
 
+#define ATH11K_CFR_START_MAGIC 0xDEADBEAF
 #define ATH11K_CFR_END_MAGIC 0xBEAFDEAD
 
+#define VENDOR_QCA 0x8cfdf0
+#define PLATFORM_TYPE_ARM 2
+
+enum ath11k_cfr_meta_version {
+       ATH11K_CFR_META_VERSION_NONE,
+       ATH11K_CFR_META_VERSION_1,
+       ATH11K_CFR_META_VERSION_2,
+       ATH11K_CFR_META_VERSION_3,
+       ATH11K_CFR_META_VERSION_4,
+       ATH11K_CFR_META_VERSION_MAX = 0xFF,
+};
+
+enum ath11k_cfr_data_version {
+       ATH11K_CFR_DATA_VERSION_NONE,
+       ATH11K_CFR_DATA_VERSION_1,
+       ATH11K_CFR_DATA_VERSION_MAX = 0xFF,
+};
+
+enum ath11k_cfr_capture_ack_mode {
+       ATH11K_CFR_CAPTURE_LEGACY_ACK,
+       ATH11K_CFR_CAPTURE_DUP_LEGACY_ACK,
+       ATH11K_CFR_CAPTURE_HT_ACK,
+       ATH11K_CFR_CAPTURE_VHT_ACK,
+
+       /*Always keep this at last*/
+       ATH11K_CFR_CAPTURE_INVALID_ACK
+};
+
 enum ath11k_cfr_correlate_status {
        ATH11K_CORRELATE_STATUS_RELEASE,
        ATH11K_CORRELATE_STATUS_HOLD,
@@ -41,6 +70,28 @@ enum ath11k_cfr_preamble_type {
        ATH11K_CFR_PREAMBLE_TYPE_VHT,
 };
 
+struct ath11k_cfr_peer_tx_param {
+       u32 capture_method;
+       u32 vdev_id;
+       u8 peer_mac_addr[ETH_ALEN];
+       u32 primary_20mhz_chan;
+       u32 bandwidth;
+       u32 phy_mode;
+       u32 band_center_freq1;
+       u32 band_center_freq2;
+       u32 spatial_streams;
+       u32 correlation_info_1;
+       u32 correlation_info_2;
+       u32 status;
+       u32 timestamp_us;
+       u32 counter;
+       u32 chain_rssi[WMI_MAX_CHAINS];
+       u16 chain_phase[WMI_MAX_CHAINS];
+       u32 cfo_measurement;
+       u8 agc_gain[HOST_MAX_CHAINS];
+       u32 rx_start_ts;
+};
+
 struct cfr_metadata {
        u8 peer_addr[ETH_ALEN];
        u8 status;
@@ -70,7 +121,7 @@ struct ath11k_csi_cfr_header {
        u8 cfr_data_version;
        u8 chip_type;
        u8 platform_type;
-       u32 reserved;
+       u32 cfr_metadata_len;
        struct cfr_metadata meta_data;
 } __packed;
 
@@ -144,6 +195,7 @@ struct ath11k_cfr {
        u64 clear_txrx_event;
        u64 cfr_dma_aborts;
        bool enabled;
+       enum wmi_phy_mode phymode;
        struct cfr_unassoc_pool_entry unassoc_pool[ATH11K_MAX_CFR_ENABLED_CLIENTS];
 };
 
@@ -181,8 +233,15 @@ int ath11k_cfr_send_peer_cfr_capture_cmd(struct ath11k *ar,
                                         const u8 *peer_mac);
 struct ath11k_dbring *ath11k_cfr_get_dbring(struct ath11k *ar);
 void ath11k_cfr_release_lut_entry(struct ath11k_look_up_table *lut);
-
+int ath11k_process_cfr_capture_event(struct ath11k_base *ab,
+                                    struct ath11k_cfr_peer_tx_param *params);
+void ath11k_cfr_update_phymode(struct ath11k *ar, enum wmi_phy_mode phymode);
 #else
+static inline void ath11k_cfr_update_phymode(struct ath11k *ar,
+                                            enum wmi_phy_mode phymode)
+{
+}
+
 static inline int ath11k_cfr_init(struct ath11k_base *ab)
 {
        return 0;
@@ -238,5 +297,12 @@ struct ath11k_dbring *ath11k_cfr_get_dbring(struct ath11k *ar)
 {
        return NULL;
 }
+
+static inline
+int ath11k_process_cfr_capture_event(struct ath11k_base *ab,
+                                    struct ath11k_cfr_peer_tx_param *params)
+{
+       return 0;
+}
 #endif /* CONFIG_ATH11K_CFR */
 #endif /* ATH11K_CFR_H */
index a7a1914b34c3e9037964a3adfb6a7479e7e69f4e..ea634d60b2c8ecceb2b4bf7606f024f44c5bad07 100644 (file)
@@ -2911,6 +2911,8 @@ static void ath11k_peer_assoc_h_phymode(struct ath11k *ar,
 
        arg->peer_phymode = phymode;
        WARN_ON(phymode == MODE_UNKNOWN);
+
+       ath11k_cfr_update_phymode(ar, phymode);
 }
 
 static void ath11k_peer_assoc_prepare(struct ath11k *ar,
index b40a31414a471224a276fe1c9f203a3781f43e18..451cc4c719aecb8de7795446a13a745ea2fd8df8 100644 (file)
@@ -8805,6 +8805,93 @@ out:
        kfree(tb);
 }
 
+static void ath11k_wmi_tlv_cfr_capture_event_fixed_param(const void *ptr,
+                                                        void *data)
+{
+       struct ath11k_cfr_peer_tx_param *tx_params = data;
+       const struct ath11k_wmi_cfr_peer_tx_event_param *params = ptr;
+
+       tx_params->capture_method = params->capture_method;
+       tx_params->vdev_id = params->vdev_id;
+       ether_addr_copy(tx_params->peer_mac_addr, params->mac_addr.addr);
+       tx_params->primary_20mhz_chan = params->chan_mhz;
+       tx_params->bandwidth = params->bandwidth;
+       tx_params->phy_mode = params->phy_mode;
+       tx_params->band_center_freq1 = params->band_center_freq1;
+       tx_params->band_center_freq2 = params->band_center_freq2;
+       tx_params->spatial_streams = params->sts_count;
+       tx_params->correlation_info_1 = params->correlation_info_1;
+       tx_params->correlation_info_2 = params->correlation_info_2;
+       tx_params->status = params->status;
+       tx_params->timestamp_us = params->timestamp_us;
+       tx_params->counter = params->counter;
+       tx_params->rx_start_ts = params->rx_start_ts;
+
+       memcpy(tx_params->chain_rssi, params->chain_rssi,
+              sizeof(tx_params->chain_rssi));
+
+       if (WMI_CFR_CFO_MEASUREMENT_VALID & params->cfo_measurement)
+               tx_params->cfo_measurement = FIELD_GET(WMI_CFR_CFO_MEASUREMENT_RAW_DATA,
+                                                      params->cfo_measurement);
+}
+
+static void ath11k_wmi_tlv_cfr_capture_phase_fixed_param(const void *ptr,
+                                                        void *data)
+{
+       struct ath11k_cfr_peer_tx_param *tx_params = data;
+       const struct ath11k_wmi_cfr_peer_tx_event_phase_param *params = ptr;
+       int i;
+
+       for (i = 0; i < WMI_MAX_CHAINS; i++) {
+               tx_params->chain_phase[i] = params->chain_phase[i];
+               tx_params->agc_gain[i] = params->agc_gain[i];
+       }
+}
+
+static int ath11k_wmi_tlv_cfr_capture_evt_parse(struct ath11k_base *ab,
+                                               u16 tag, u16 len,
+                                               const void *ptr, void *data)
+{
+       switch (tag) {
+       case WMI_TAG_PEER_CFR_CAPTURE_EVENT:
+               ath11k_wmi_tlv_cfr_capture_event_fixed_param(ptr, data);
+               break;
+       case WMI_TAG_CFR_CAPTURE_PHASE_PARAM:
+               ath11k_wmi_tlv_cfr_capture_phase_fixed_param(ptr, data);
+               break;
+       default:
+               ath11k_warn(ab, "Invalid tag received tag %d len %d\n",
+                           tag, len);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static void ath11k_wmi_parse_cfr_capture_event(struct ath11k_base *ab,
+                                              struct sk_buff *skb)
+{
+       struct ath11k_cfr_peer_tx_param params = {};
+       int ret;
+
+       ath11k_dbg_dump(ab, ATH11K_DBG_CFR_DUMP, "cfr_dump:", "",
+                       skb->data, skb->len);
+
+       ret = ath11k_wmi_tlv_iter(ab, skb->data, skb->len,
+                                 ath11k_wmi_tlv_cfr_capture_evt_parse,
+                                 &params);
+       if (ret) {
+               ath11k_warn(ab, "failed to parse cfr capture event tlv %d\n",
+                           ret);
+               return;
+       }
+
+       ret = ath11k_process_cfr_capture_event(ab, &params);
+       if (ret)
+               ath11k_dbg(ab, ATH11K_DBG_CFR,
+                          "failed to process cfr capture ret = %d\n", ret);
+}
+
 static void ath11k_wmi_tlv_op_rx(struct ath11k_base *ab, struct sk_buff *skb)
 {
        struct wmi_cmd_hdr *cmd_hdr;
@@ -8935,6 +9022,9 @@ static void ath11k_wmi_tlv_op_rx(struct ath11k_base *ab, struct sk_buff *skb)
        case WMI_P2P_NOA_EVENTID:
                ath11k_wmi_p2p_noa_event(ab, skb);
                break;
+       case WMI_PEER_CFR_CAPTURE_EVENTID:
+               ath11k_wmi_parse_cfr_capture_event(ab, skb);
+               break;
        default:
                ath11k_dbg(ab, ATH11K_DBG_WMI, "unsupported event id 0x%x\n", id);
                break;
index afc78fa4389bc44382a7edf222b7164eb818651d..baed501b640b4ef92e08f575f3b59562e6c43d41 100644 (file)
@@ -1889,6 +1889,8 @@ enum wmi_tlv_tag {
        WMI_TAG_NDP_EVENT,
        WMI_TAG_PDEV_PEER_PKTLOG_FILTER_CMD = 0x301,
        WMI_TAG_PDEV_PEER_PKTLOG_FILTER_INFO,
+       WMI_TAG_PEER_CFR_CAPTURE_EVENT = 0x317,
+       WMI_TAG_CFR_CAPTURE_PHASE_PARAM = 0x33b,
        WMI_TAG_FILS_DISCOVERY_TMPL_CMD = 0x344,
        WMI_TAG_PDEV_SRG_BSS_COLOR_BITMAP_CMD = 0x37b,
        WMI_TAG_PDEV_SRG_PARTIAL_BSSID_BITMAP_CMD,
@@ -4237,6 +4239,48 @@ enum ath11k_wmi_cfr_capture_method {
        WMI_CFR_CAPTURE_METHOD_MAX,
 };
 
+#define WMI_CFR_FRAME_TX_STATUS GENMASK(1, 0)
+#define WMI_CFR_CAPTURE_STATUS_PEER_PS BIT(30)
+#define WMI_CFR_PEER_CAPTURE_STATUS BIT(31)
+
+#define WMI_CFR_CORRELATION_INFO2_BUF_ADDR_HIGH GENMASK(3, 0)
+#define WMI_CFR_CORRELATION_INFO2_PPDU_ID GENMASK(31, 16)
+
+#define WMI_CFR_CFO_MEASUREMENT_VALID BIT(0)
+#define WMI_CFR_CFO_MEASUREMENT_RAW_DATA GENMASK(14, 1)
+
+struct ath11k_wmi_cfr_peer_tx_event_param {
+       u32 capture_method;
+       u32 vdev_id;
+       struct wmi_mac_addr mac_addr;
+       u32 chan_mhz;
+       u32 bandwidth;
+       u32 phy_mode;
+       u32 band_center_freq1;
+       u32 band_center_freq2;
+       u32 sts_count;
+       u32 correlation_info_1;
+       u32 correlation_info_2;
+       u32 status;
+       u32 timestamp_us;
+       u32 counter;
+       u32 chain_rssi[WMI_MAX_CHAINS];
+       u32 cfo_measurement;
+       u32 rx_start_ts;
+} __packed;
+
+struct ath11k_wmi_cfr_peer_tx_event_phase_param {
+       u32 chain_phase[WMI_MAX_CHAINS];
+       u8 agc_gain[WMI_MAX_CHAINS];
+} __packed;
+
+enum ath11k_wmi_frame_tx_status {
+       WMI_FRAME_TX_STATUS_OK,
+       WMI_FRAME_TX_STATUS_XRETRY,
+       WMI_FRAME_TX_STATUS_DROP,
+       WMI_FRAME_TX_STATUS_FILTERED,
+};
+
 struct wmi_peer_cfr_capture_conf_arg {
        enum ath11k_wmi_cfr_capture_bw bw;
        enum ath11k_wmi_cfr_capture_method method;