]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
wifi: ath11k: Register DBR event handler for CFR data
authorVenkateswara Naralasetty <quic_vnaralas@quicinc.com>
Tue, 30 Dec 2025 08:25:19 +0000 (13:55 +0530)
committerJeff Johnson <jeff.johnson@oss.qualcomm.com>
Fri, 16 Jan 2026 01:19:38 +0000 (17:19 -0800)
Add handler for WMI_PDEV_DMA_RING_BUF_RELEASE_EVENT which indicates CFR
data availability in the DB ring.

Add CFR data processing from DB ring buffers. Use correlate_and_relay
API to match CFR data with metadata from WMI_PEER_CFR_CAPTURE_EVENT.

Release buffer to userspace through relayfs on successful correlation,
otherwise hold buffer waiting for matching WMI event from firmware.

Add new debug masks:
 - ATH11K_DBG_CFR:      Enables CFR-related debug logs.
 - ATH11K_DBG_CFR_DUMP: Enables detailed CFR data dump for analysis.

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-6-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/dbring.c
drivers/net/wireless/ath/ath11k/dbring.h
drivers/net/wireless/ath/ath11k/debug.h

index 495b2c6742aaf455f4ee515ddbccd76e61ea6846..ee7626bd4b1a5ef5f3d2287a2a1cff851e7d8918 100644 (file)
 #include "core.h"
 #include "debug.h"
 
+struct ath11k_dbring *ath11k_cfr_get_dbring(struct ath11k *ar)
+{
+       if (ar->cfr_enabled)
+               return &ar->cfr.rx_ring;
+
+       return NULL;
+}
+
+static int ath11k_cfr_calculate_tones_from_dma_hdr(struct ath11k_cfr_dma_hdr *hdr)
+{
+       u8 bw = FIELD_GET(CFIR_DMA_HDR_INFO1_UPLOAD_PKT_BW, hdr->info1);
+       u8 preamble = FIELD_GET(CFIR_DMA_HDR_INFO1_PREAMBLE_TYPE, hdr->info1);
+
+       switch (preamble) {
+       case ATH11K_CFR_PREAMBLE_TYPE_LEGACY:
+               fallthrough;
+       case ATH11K_CFR_PREAMBLE_TYPE_VHT:
+               switch (bw) {
+               case 0:
+                       return TONES_IN_20MHZ;
+               case 1: /* DUP40/VHT40 */
+                       return TONES_IN_40MHZ;
+               case 2: /* DUP80/VHT80 */
+                       return TONES_IN_80MHZ;
+               case 3: /* DUP160/VHT160 */
+                       return TONES_IN_160MHZ;
+               default:
+                       return TONES_INVALID;
+               }
+       case ATH11K_CFR_PREAMBLE_TYPE_HT:
+               switch (bw) {
+               case 0:
+                       return TONES_IN_20MHZ;
+               case 1:
+                       return TONES_IN_40MHZ;
+               default:
+                       return TONES_INVALID;
+               }
+       default:
+               return TONES_INVALID;
+       }
+}
+
+void ath11k_cfr_release_lut_entry(struct ath11k_look_up_table *lut)
+{
+       memset(lut, 0, sizeof(*lut));
+}
+
+static void ath11k_cfr_rfs_write(struct ath11k *ar, const void *head,
+                                u32 head_len, const void *data, u32 data_len,
+                                const void *tail, int tail_data)
+{
+       struct ath11k_cfr *cfr = &ar->cfr;
+
+       if (!cfr->rfs_cfr_capture)
+               return;
+
+       relay_write(cfr->rfs_cfr_capture, head, head_len);
+       relay_write(cfr->rfs_cfr_capture, data, data_len);
+       relay_write(cfr->rfs_cfr_capture, tail, tail_data);
+       relay_flush(cfr->rfs_cfr_capture);
+}
+
+static void ath11k_cfr_free_pending_dbr_events(struct ath11k *ar)
+{
+       struct ath11k_cfr *cfr = &ar->cfr;
+       struct ath11k_look_up_table *lut;
+       int i;
+
+       if (!cfr->lut)
+               return;
+
+       for (i = 0; i < cfr->lut_num; i++) {
+               lut = &cfr->lut[i];
+               if (lut->dbr_recv && !lut->tx_recv &&
+                   lut->dbr_tstamp < cfr->last_success_tstamp) {
+                       ath11k_dbring_bufs_replenish(ar, &cfr->rx_ring, lut->buff,
+                                                    WMI_DIRECT_BUF_CFR);
+                       ath11k_cfr_release_lut_entry(lut);
+                       cfr->flush_dbr_cnt++;
+               }
+       }
+}
+
+/**
+ * ath11k_cfr_correlate_and_relay() - Correlate and relay CFR events
+ * @ar: Pointer to ath11k structure
+ * @lut: Lookup table for correlation
+ * @event_type: Type of event received (TX or DBR)
+ *
+ * Correlates WMI_PDEV_DMA_RING_BUF_RELEASE_EVENT (DBR) and
+ * WMI_PEER_CFR_CAPTURE_EVENT (TX capture) by PPDU ID. If both events
+ * are present and the PPDU IDs match, returns CORRELATE_STATUS_RELEASE
+ * to relay thecorrelated data to userspace. Otherwise returns
+ * CORRELATE_STATUS_HOLD to wait for the other event.
+ *
+ * Also checks pending DBR events and clears them when no corresponding TX
+ * capture event is received for the PPDU.
+ *
+ * Return: CORRELATE_STATUS_RELEASE or CORRELATE_STATUS_HOLD
+ */
+
+static enum ath11k_cfr_correlate_status
+ath11k_cfr_correlate_and_relay(struct ath11k *ar,
+                              struct ath11k_look_up_table *lut,
+                              u8 event_type)
+{
+       enum ath11k_cfr_correlate_status status;
+       struct ath11k_cfr *cfr = &ar->cfr;
+       u64 diff;
+
+       if (event_type == ATH11K_CORRELATE_TX_EVENT) {
+               if (lut->tx_recv)
+                       cfr->cfr_dma_aborts++;
+               cfr->tx_evt_cnt++;
+               lut->tx_recv = true;
+       } else if (event_type == ATH11K_CORRELATE_DBR_EVENT) {
+               cfr->dbr_evt_cnt++;
+               lut->dbr_recv = true;
+       }
+
+       if (lut->dbr_recv && lut->tx_recv) {
+               if (lut->dbr_ppdu_id == lut->tx_ppdu_id) {
+                       /*
+                        * 64-bit counters make wraparound highly improbable,
+                        * wraparound handling is omitted.
+                        */
+                       cfr->last_success_tstamp = lut->dbr_tstamp;
+                       if (lut->dbr_tstamp > lut->txrx_tstamp) {
+                               diff = lut->dbr_tstamp - lut->txrx_tstamp;
+                               ath11k_dbg(ar->ab, ATH11K_DBG_CFR,
+                                          "txrx event -> dbr event delay = %u ms",
+                                          jiffies_to_msecs(diff));
+                       } else if (lut->txrx_tstamp > lut->dbr_tstamp) {
+                               diff = lut->txrx_tstamp - lut->dbr_tstamp;
+                               ath11k_dbg(ar->ab, ATH11K_DBG_CFR,
+                                          "dbr event -> txrx event delay = %u ms",
+                                          jiffies_to_msecs(diff));
+                       }
+
+                       ath11k_cfr_free_pending_dbr_events(ar);
+
+                       cfr->release_cnt++;
+                       status = ATH11K_CORRELATE_STATUS_RELEASE;
+               } else {
+                       /*
+                        * Discard TXRX event on PPDU ID mismatch because multiple PPDUs
+                        * may share the same DMA address due to ucode aborts.
+                        */
+
+                       ath11k_dbg(ar->ab, ATH11K_DBG_CFR,
+                                  "Received dbr event twice for the same lut entry");
+                       lut->tx_recv = false;
+                       lut->tx_ppdu_id = 0;
+                       cfr->clear_txrx_event++;
+                       cfr->cfr_dma_aborts++;
+                       status = ATH11K_CORRELATE_STATUS_HOLD;
+               }
+       } else {
+               status = ATH11K_CORRELATE_STATUS_HOLD;
+       }
+
+       return status;
+}
+
 static int ath11k_cfr_process_data(struct ath11k *ar,
                                   struct ath11k_dbring_data *param)
 {
-       return 0;
+       u32 end_magic = ATH11K_CFR_END_MAGIC;
+       struct ath11k_csi_cfr_header *header;
+       struct ath11k_cfr_dma_hdr *dma_hdr;
+       struct ath11k_cfr *cfr = &ar->cfr;
+       struct ath11k_look_up_table *lut;
+       struct ath11k_base *ab = ar->ab;
+       u32 buf_id, tones, length;
+       u8 num_chains;
+       int status;
+       u8 *data;
+
+       data = param->data;
+       buf_id = param->buf_id;
+
+       if (param->data_sz < sizeof(*dma_hdr))
+               return -EINVAL;
+
+       dma_hdr = (struct ath11k_cfr_dma_hdr *)data;
+
+       tones = ath11k_cfr_calculate_tones_from_dma_hdr(dma_hdr);
+       if (tones == TONES_INVALID) {
+               ath11k_warn(ar->ab, "Number of tones received is invalid\n");
+               return -EINVAL;
+       }
+
+       num_chains = FIELD_GET(CFIR_DMA_HDR_INFO1_NUM_CHAINS,
+                              dma_hdr->info1);
+
+       length = sizeof(*dma_hdr);
+       length += tones * (num_chains + 1);
+
+       spin_lock_bh(&cfr->lut_lock);
+
+       if (!cfr->lut) {
+               spin_unlock_bh(&cfr->lut_lock);
+               return -EINVAL;
+       }
+
+       lut = &cfr->lut[buf_id];
+
+       ath11k_dbg_dump(ab, ATH11K_DBG_CFR_DUMP, "data_from_buf_rel:", "",
+                       data, length);
+
+       lut->buff = param->buff;
+       lut->data = data;
+       lut->data_len = length;
+       lut->dbr_ppdu_id = dma_hdr->phy_ppdu_id;
+       lut->dbr_tstamp = jiffies;
+
+       memcpy(&lut->hdr, dma_hdr, sizeof(*dma_hdr));
+
+       header = &lut->header;
+       header->meta_data.channel_bw = FIELD_GET(CFIR_DMA_HDR_INFO1_UPLOAD_PKT_BW,
+                                                dma_hdr->info1);
+       header->meta_data.length = length;
+
+       status = ath11k_cfr_correlate_and_relay(ar, lut,
+                                               ATH11K_CORRELATE_DBR_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));
+               ath11k_cfr_release_lut_entry(lut);
+       } else if (status == ATH11K_CORRELATE_STATUS_HOLD) {
+               ath11k_dbg(ab, ATH11K_DBG_CFR,
+                          "tx event is not yet received holding the buf");
+       }
+
+       spin_unlock_bh(&cfr->lut_lock);
+
+       return status;
 }
 
 /* Helper function to check whether the given peer mac address
index 19f136d34c65b415b3eb5703348eb08dd0aa8688..c8e5086674d2866e141022308911c632dcc0fecb 100644 (file)
 
 #define HOST_MAX_CHAINS 8
 
+enum ath11k_cfr_correlate_event_type {
+       ATH11K_CORRELATE_DBR_EVENT,
+       ATH11K_CORRELATE_TX_EVENT,
+};
+
 struct ath11k_sta;
 struct ath11k_per_peer_cfr_capture;
 
+#define ATH11K_CFR_END_MAGIC 0xBEAFDEAD
+
+enum ath11k_cfr_correlate_status {
+       ATH11K_CORRELATE_STATUS_RELEASE,
+       ATH11K_CORRELATE_STATUS_HOLD,
+       ATH11K_CORRELATE_STATUS_ERR,
+};
+
+enum ath11k_cfr_preamble_type {
+       ATH11K_CFR_PREAMBLE_TYPE_LEGACY,
+       ATH11K_CFR_PREAMBLE_TYPE_HT,
+       ATH11K_CFR_PREAMBLE_TYPE_VHT,
+};
+
+struct cfr_metadata {
+       u8 peer_addr[ETH_ALEN];
+       u8 status;
+       u8 capture_bw;
+       u8 channel_bw;
+       u8 phy_mode;
+       u16 prim20_chan;
+       u16 center_freq1;
+       u16 center_freq2;
+       u8 capture_mode;
+       u8 capture_type;
+       u8 sts_count;
+       u8 num_rx_chain;
+       u32 timestamp;
+       u32 length;
+       u32 chain_rssi[HOST_MAX_CHAINS];
+       u16 chain_phase[HOST_MAX_CHAINS];
+       u32 cfo_measurement;
+       u8 agc_gain[HOST_MAX_CHAINS];
+       u32 rx_start_ts;
+} __packed;
+
+struct ath11k_csi_cfr_header {
+       u32 start_magic_num;
+       u32 vendorid;
+       u8 cfr_metadata_version;
+       u8 cfr_data_version;
+       u8 chip_type;
+       u8 platform_type;
+       u32 reserved;
+       struct cfr_metadata meta_data;
+} __packed;
+
+#define TONES_IN_20MHZ  256
+#define TONES_IN_40MHZ  512
+#define TONES_IN_80MHZ  1024
+#define TONES_IN_160MHZ 2048 /* 160 MHz isn't supported yet */
+#define TONES_INVALID   0
+
+#define CFIR_DMA_HDR_INFO0_TAG GENMASK(7, 0)
+#define CFIR_DMA_HDR_INFO0_LEN GENMASK(13, 8)
+
+#define CFIR_DMA_HDR_INFO1_UPLOAD_DONE      GENMASK(0, 0)
+#define CFIR_DMA_HDR_INFO1_CAPTURE_TYPE     GENMASK(3, 1)
+#define CFIR_DMA_HDR_INFO1_PREAMBLE_TYPE    GENMASK(5, 4)
+#define CFIR_DMA_HDR_INFO1_NSS              GENMASK(8, 6)
+#define CFIR_DMA_HDR_INFO1_NUM_CHAINS       GENMASK(11, 9)
+#define CFIR_DMA_HDR_INFO1_UPLOAD_PKT_BW    GENMASK(14, 12)
+#define CFIR_DMA_HDR_INFO1_SW_PEER_ID_VALID GENMASK(15, 15)
+
 struct ath11k_cfr_dma_hdr {
        u16 info0;
        u16 info1;
@@ -37,6 +106,7 @@ struct ath11k_look_up_table {
        u16 dbr_ppdu_id;
        u16 tx_ppdu_id;
        dma_addr_t dbr_address;
+       struct ath11k_csi_cfr_header header;
        struct ath11k_cfr_dma_hdr hdr;
        u64 txrx_tstamp;
        u64 dbr_tstamp;
@@ -109,6 +179,8 @@ int ath11k_cfr_send_peer_cfr_capture_cmd(struct ath11k *ar,
                                         struct ath11k_sta *arsta,
                                         struct ath11k_per_peer_cfr_capture *params,
                                         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);
 
 #else
 static inline int ath11k_cfr_init(struct ath11k_base *ab)
@@ -156,5 +228,15 @@ ath11k_cfr_send_peer_cfr_capture_cmd(struct ath11k *ar,
 {
        return 0;
 }
+
+static inline void ath11k_cfr_release_lut_entry(struct ath11k_look_up_table *lut)
+{
+}
+
+static inline
+struct ath11k_dbring *ath11k_cfr_get_dbring(struct ath11k *ar)
+{
+       return NULL;
+}
 #endif /* CONFIG_ATH11K_CFR */
 #endif /* ATH11K_CFR_H */
index ed2b781a6bab64ea8269678e15c5e1a3fd2b6f5a..d6994ce6ebff4e0d4ad4a0855a6041e050599431 100644 (file)
@@ -295,6 +295,7 @@ int ath11k_dbring_buffer_release_event(struct ath11k_base *ab,
        int size;
        dma_addr_t paddr;
        int ret = 0;
+       int status;
 
        pdev_idx = ev->fixed.pdev_id;
        module_id = ev->fixed.module_id;
@@ -328,6 +329,9 @@ int ath11k_dbring_buffer_release_event(struct ath11k_base *ab,
        case WMI_DIRECT_BUF_SPECTRAL:
                ring = ath11k_spectral_get_dbring(ar);
                break;
+       case WMI_DIRECT_BUF_CFR:
+               ring = ath11k_cfr_get_dbring(ar);
+               break;
        default:
                ring = NULL;
                ath11k_warn(ab, "Recv dma buffer release ev on unsupp module %d\n",
@@ -378,8 +382,12 @@ int ath11k_dbring_buffer_release_event(struct ath11k_base *ab,
                        handler_data.data = PTR_ALIGN(vaddr_unalign,
                                                      ring->buf_align);
                        handler_data.data_sz = ring->buf_sz;
+                       handler_data.buff = buff;
+                       handler_data.buf_id = buf_id;
 
-                       ring->handler(ar, &handler_data);
+                       status = ring->handler(ar, &handler_data);
+                       if (status == ATH11K_CORRELATE_STATUS_HOLD)
+                               continue;
                }
 
                buff->paddr = 0;
index 0a380120f7a0ebfb1aa164e18d03e7a078dbb237..e5f244dfa963124afb599e1106af1838ed511749 100644 (file)
@@ -21,6 +21,8 @@ struct ath11k_dbring_data {
        void *data;
        u32 data_sz;
        struct wmi_dma_buf_release_meta_data meta;
+       struct ath11k_dbring_element *buff;
+       u32 buf_id;
 };
 
 struct ath11k_dbring_buf_release_event {
index cc8934d156977cfad43afe00927e5b8ea10068cf..aaa0034527a598b16adb71d8ff6d94521e57d8b8 100644 (file)
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: BSD-3-Clause-Clear */
 /*
  * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
- * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
  */
 
 #ifndef _ATH11K_DEBUG_H_
@@ -27,6 +27,8 @@ enum ath11k_debug_mask {
        ATH11K_DBG_DP_TX        = 0x00002000,
        ATH11K_DBG_DP_RX        = 0x00004000,
        ATH11K_DBG_CE           = 0x00008000,
+       ATH11K_DBG_CFR          = 0x00010000,
+       ATH11K_DBG_CFR_DUMP     = 0x00020000,
 };
 
 static inline const char *ath11k_dbg_str(enum ath11k_debug_mask mask)
@@ -64,6 +66,10 @@ static inline const char *ath11k_dbg_str(enum ath11k_debug_mask mask)
                return "dp_rx";
        case ATH11K_DBG_CE:
                return "ce";
+       case ATH11K_DBG_CFR:
+               return "cfr";
+       case ATH11K_DBG_CFR_DUMP:
+               return "cfr_dump";
 
        /* no default handler to allow compiler to check that the
         * enum is fully handled