summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjoern A. Zeeb <bz@FreeBSD.org>2026-04-13 22:25:44 +0000
committerBjoern A. Zeeb <bz@FreeBSD.org>2026-04-13 22:25:44 +0000
commitc3e04a1cd3b64885db5238b60db0da610586d3ce (patch)
tree80f09673937235b0a795b17347ddc30d069d341f
parent4c115eaec2fdbcd0c5fc614e0529fa68152501ef (diff)
-rw-r--r--Kconfig11
-rw-r--r--Makefile1
-rw-r--r--ce.c2
-rw-r--r--cfr.c1022
-rw-r--r--cfr.h308
-rw-r--r--core.c108
-rw-r--r--core.h23
-rw-r--r--dbring.c52
-rw-r--r--dbring.h8
-rw-r--r--debug.h8
-rw-r--r--debugfs.c10
-rw-r--r--debugfs_sta.c142
-rw-r--r--dp.c12
-rw-r--r--dp.h1
-rw-r--r--dp_rx.c21
-rw-r--r--dp_tx.c11
-rw-r--r--hal.c3
-rw-r--r--hw.c19
-rw-r--r--hw.h8
-rw-r--r--mac.c39
-rw-r--r--mhi.c6
-rw-r--r--peer.c2
-rw-r--r--qmi.c12
-rw-r--r--reg.c12
-rw-r--r--wmi.c167
-rw-r--r--wmi.h97
-rw-r--r--wow.c4
27 files changed, 1999 insertions, 110 deletions
diff --git a/Kconfig b/Kconfig
index 659ef134ef16..47dfd39caa89 100644
--- a/Kconfig
+++ b/Kconfig
@@ -58,3 +58,14 @@ config ATH11K_SPECTRAL
Enable ath11k spectral scan support
Say Y to enable access to the FFT/spectral data via debugfs.
+
+config ATH11K_CFR
+ bool "ath11k channel frequency response support"
+ depends on ATH11K_DEBUGFS
+ depends on RELAY
+ help
+ Enable ath11k channel frequency response dump support.
+ This option exposes debugfs nodes that will allow the user
+ to enable, disable, and dump data.
+
+ Say Y to enable CFR data dump collection via debugfs.
diff --git a/Makefile b/Makefile
index d9092414b362..b1435fcf3e1b 100644
--- a/Makefile
+++ b/Makefile
@@ -28,6 +28,7 @@ ath11k-$(CONFIG_THERMAL) += thermal.o
ath11k-$(CONFIG_ATH11K_SPECTRAL) += spectral.o
ath11k-$(CONFIG_PM) += wow.o
ath11k-$(CONFIG_DEV_COREDUMP) += coredump.o
+ath11k-$(CONFIG_ATH11K_CFR) += cfr.o
obj-$(CONFIG_ATH11K_AHB) += ath11k_ahb.o
ath11k_ahb-y += ahb.o
diff --git a/ce.c b/ce.c
index a7a163621b21..f2d5106adb01 100644
--- a/ce.c
+++ b/ce.c
@@ -615,7 +615,7 @@ ath11k_ce_alloc_ring(struct ath11k_base *ab, int nentries, int desc_sz)
struct ath11k_ce_ring *ce_ring;
dma_addr_t base_addr;
- ce_ring = kzalloc(struct_size(ce_ring, skb, nentries), GFP_KERNEL);
+ ce_ring = kzalloc_flex(*ce_ring, skb, nentries);
if (ce_ring == NULL)
return ERR_PTR(-ENOMEM);
diff --git a/cfr.c b/cfr.c
new file mode 100644
index 000000000000..a91f25fb6c6b
--- /dev/null
+++ b/cfr.c
@@ -0,0 +1,1022 @@
+// SPDX-License-Identifier: BSD-3-Clause-Clear
+/*
+ * Copyright (c) 2020-2021 The Linux Foundation. All rights reserved.
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/relay.h>
+#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)
+{
+ 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;
+}
+
+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.
+ */
+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)
+{
+ struct ath11k_cfr *cfr = &ar->cfr;
+
+ spin_lock_bh(&cfr->lock);
+
+ if (arsta->cfr_capture.cfr_enable)
+ cfr->cfr_enabled_peer_cnt--;
+
+ spin_unlock_bh(&cfr->lock);
+}
+
+static enum ath11k_wmi_cfr_capture_bw
+ath11k_cfr_bw_to_fw_cfr_bw(enum ath11k_cfr_capture_bw bw)
+{
+ switch (bw) {
+ case ATH11K_CFR_CAPTURE_BW_20:
+ return WMI_PEER_CFR_CAPTURE_BW_20;
+ case ATH11K_CFR_CAPTURE_BW_40:
+ return WMI_PEER_CFR_CAPTURE_BW_40;
+ case ATH11K_CFR_CAPTURE_BW_80:
+ return WMI_PEER_CFR_CAPTURE_BW_80;
+ default:
+ return WMI_PEER_CFR_CAPTURE_BW_MAX;
+ }
+}
+
+static enum ath11k_wmi_cfr_capture_method
+ath11k_cfr_method_to_fw_cfr_method(enum ath11k_cfr_capture_method method)
+{
+ switch (method) {
+ case ATH11K_CFR_CAPTURE_METHOD_NULL_FRAME:
+ return WMI_CFR_CAPTURE_METHOD_NULL_FRAME;
+ case ATH11K_CFR_CAPTURE_METHOD_NULL_FRAME_WITH_PHASE:
+ return WMI_CFR_CAPTURE_METHOD_NULL_FRAME_WITH_PHASE;
+ case ATH11K_CFR_CAPTURE_METHOD_PROBE_RESP:
+ return WMI_CFR_CAPTURE_METHOD_PROBE_RESP;
+ default:
+ return WMI_CFR_CAPTURE_METHOD_MAX;
+ }
+}
+
+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_cfr *cfr = &ar->cfr;
+ struct wmi_peer_cfr_capture_conf_arg arg;
+ enum ath11k_wmi_cfr_capture_bw bw;
+ enum ath11k_wmi_cfr_capture_method method;
+ int ret = 0;
+
+ if (cfr->cfr_enabled_peer_cnt >= ATH11K_MAX_CFR_ENABLED_CLIENTS &&
+ !arsta->cfr_capture.cfr_enable) {
+ ath11k_err(ar->ab, "CFR enable peer threshold reached %u\n",
+ cfr->cfr_enabled_peer_cnt);
+ return -ENOSPC;
+ }
+
+ if (params->cfr_enable == arsta->cfr_capture.cfr_enable &&
+ params->cfr_period == arsta->cfr_capture.cfr_period &&
+ params->cfr_method == arsta->cfr_capture.cfr_method &&
+ params->cfr_bw == arsta->cfr_capture.cfr_bw)
+ return ret;
+
+ if (!params->cfr_enable && !arsta->cfr_capture.cfr_enable)
+ return ret;
+
+ bw = ath11k_cfr_bw_to_fw_cfr_bw(params->cfr_bw);
+ if (bw >= WMI_PEER_CFR_CAPTURE_BW_MAX) {
+ ath11k_warn(ar->ab, "FW doesn't support configured bw %d\n",
+ params->cfr_bw);
+ return -EINVAL;
+ }
+
+ method = ath11k_cfr_method_to_fw_cfr_method(params->cfr_method);
+ if (method >= WMI_CFR_CAPTURE_METHOD_MAX) {
+ ath11k_warn(ar->ab, "FW doesn't support configured method %d\n",
+ params->cfr_method);
+ return -EINVAL;
+ }
+
+ arg.request = params->cfr_enable;
+ arg.periodicity = params->cfr_period;
+ arg.bw = bw;
+ arg.method = method;
+
+ ret = ath11k_wmi_peer_set_cfr_capture_conf(ar, arsta->arvif->vdev_id,
+ peer_mac, &arg);
+ if (ret) {
+ ath11k_warn(ar->ab,
+ "failed to send cfr capture info: vdev_id %u peer %pM: %d\n",
+ arsta->arvif->vdev_id, peer_mac, ret);
+ return ret;
+ }
+
+ spin_lock_bh(&cfr->lock);
+
+ if (params->cfr_enable &&
+ params->cfr_enable != arsta->cfr_capture.cfr_enable)
+ cfr->cfr_enabled_peer_cnt++;
+ else if (!params->cfr_enable)
+ cfr->cfr_enabled_peer_cnt--;
+
+ spin_unlock_bh(&cfr->lock);
+
+ arsta->cfr_capture.cfr_enable = params->cfr_enable;
+ arsta->cfr_capture.cfr_period = params->cfr_period;
+ arsta->cfr_capture.cfr_method = params->cfr_method;
+ arsta->cfr_capture.cfr_bw = params->cfr_bw;
+
+ 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)
+{
+ struct ath11k *ar = file->private_data;
+ char buf[32] = {};
+ size_t len;
+
+ mutex_lock(&ar->conf_mutex);
+ len = scnprintf(buf, sizeof(buf), "%d\n", ar->cfr_enabled);
+ mutex_unlock(&ar->conf_mutex);
+
+ return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+}
+
+static ssize_t ath11k_write_file_enable_cfr(struct file *file,
+ const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct ath11k *ar = file->private_data;
+ u32 enable_cfr;
+ int ret;
+
+ if (kstrtouint_from_user(ubuf, count, 0, &enable_cfr))
+ return -EINVAL;
+
+ guard(mutex)(&ar->conf_mutex);
+
+ if (ar->state != ATH11K_STATE_ON)
+ return -ENETDOWN;
+
+ if (enable_cfr > 1)
+ return -EINVAL;
+
+ if (ar->cfr_enabled == enable_cfr)
+ return count;
+
+ ret = ath11k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_PER_PEER_CFR_ENABLE,
+ enable_cfr, ar->pdev->pdev_id);
+ if (ret) {
+ ath11k_warn(ar->ab,
+ "Failed to enable/disable per peer cfr %d\n", ret);
+ return ret;
+ }
+
+ ar->cfr_enabled = enable_cfr;
+
+ return count;
+}
+
+static const struct file_operations fops_enable_cfr = {
+ .read = ath11k_read_file_enable_cfr,
+ .write = ath11k_write_file_enable_cfr,
+ .open = simple_open,
+ .owner = THIS_MODULE,
+ .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;
+
+ relay_close(ar->cfr.rfs_cfr_capture);
+ ar->cfr.rfs_cfr_capture = NULL;
+}
+
+static struct dentry *ath11k_cfr_create_buf_file_handler(const char *filename,
+ struct dentry *parent,
+ umode_t mode,
+ struct rchan_buf *buf,
+ int *is_global)
+{
+ struct dentry *buf_file;
+
+ buf_file = debugfs_create_file(filename, mode, parent, buf,
+ &relay_file_operations);
+ *is_global = 1;
+ return buf_file;
+}
+
+static int ath11k_cfr_remove_buf_file_handler(struct dentry *dentry)
+{
+ debugfs_remove(dentry);
+
+ return 0;
+}
+
+static const struct rchan_callbacks rfs_cfr_capture_cb = {
+ .create_buf_file = ath11k_cfr_create_buf_file_handler,
+ .remove_buf_file = ath11k_cfr_remove_buf_file_handler,
+};
+
+static void ath11k_cfr_debug_register(struct ath11k *ar)
+{
+ ar->cfr.rfs_cfr_capture = relay_open("cfr_capture",
+ ar->debug.debugfs_pdev,
+ ar->ab->hw_params.cfr_stream_buf_size,
+ ar->ab->hw_params.cfr_num_stream_bufs,
+ &rfs_cfr_capture_cb, NULL);
+
+ 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,
+ u32 buf_id)
+{
+ struct ath11k_cfr *cfr = &ar->cfr;
+
+ if (cfr->lut)
+ 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;
+
+ ath11k_dbring_buf_cleanup(ar, &cfr->rx_ring);
+ ath11k_dbring_srng_cleanup(ar, &cfr->rx_ring);
+}
+
+static int ath11k_cfr_ring_alloc(struct ath11k *ar,
+ struct ath11k_dbring_cap *db_cap)
+{
+ struct ath11k_cfr *cfr = &ar->cfr;
+ int ret;
+
+ ret = ath11k_dbring_srng_setup(ar, &cfr->rx_ring,
+ ATH11K_CFR_NUM_RING_ENTRIES,
+ db_cap->min_elem);
+ if (ret) {
+ ath11k_warn(ar->ab, "failed to setup db ring: %d\n", ret);
+ return ret;
+ }
+
+ ath11k_dbring_set_cfg(ar, &cfr->rx_ring,
+ ATH11K_CFR_NUM_RESP_PER_EVENT,
+ ATH11K_CFR_EVENT_TIMEOUT_MS,
+ ath11k_cfr_process_data);
+
+ ret = ath11k_dbring_buf_setup(ar, &cfr->rx_ring, db_cap);
+ if (ret) {
+ ath11k_warn(ar->ab, "failed to setup db ring buffer: %d\n", ret);
+ goto srng_cleanup;
+ }
+
+ ret = ath11k_dbring_wmi_cfg_setup(ar, &cfr->rx_ring, WMI_DIRECT_BUF_CFR);
+ if (ret) {
+ ath11k_warn(ar->ab, "failed to setup db ring cfg: %d\n", ret);
+ goto buffer_cleanup;
+ }
+
+ return 0;
+
+buffer_cleanup:
+ ath11k_dbring_buf_cleanup(ar, &cfr->rx_ring);
+srng_cleanup:
+ ath11k_dbring_srng_cleanup(ar, &cfr->rx_ring);
+ return ret;
+}
+
+void ath11k_cfr_deinit(struct ath11k_base *ab)
+{
+ struct ath11k_cfr *cfr;
+ struct ath11k *ar;
+ int i;
+
+ if (!test_bit(WMI_TLV_SERVICE_CFR_CAPTURE_SUPPORT, ab->wmi_ab.svc_map) ||
+ !ab->hw_params.cfr_support)
+ return;
+
+ for (i = 0; i < ab->num_radios; i++) {
+ ar = ab->pdevs[i].ar;
+ cfr = &ar->cfr;
+
+ if (!cfr->enabled)
+ continue;
+
+ ath11k_cfr_debug_unregister(ar);
+ ath11k_cfr_ring_free(ar);
+
+ spin_lock_bh(&cfr->lut_lock);
+ kfree(cfr->lut);
+ cfr->lut = NULL;
+ cfr->enabled = false;
+ spin_unlock_bh(&cfr->lut_lock);
+ }
+}
+
+int ath11k_cfr_init(struct ath11k_base *ab)
+{
+ struct ath11k_dbring_cap db_cap;
+ struct ath11k_cfr *cfr;
+ u32 num_lut_entries;
+ struct ath11k *ar;
+ int i, ret;
+
+ if (!test_bit(WMI_TLV_SERVICE_CFR_CAPTURE_SUPPORT, ab->wmi_ab.svc_map) ||
+ !ab->hw_params.cfr_support)
+ return 0;
+
+ for (i = 0; i < ab->num_radios; i++) {
+ ar = ab->pdevs[i].ar;
+ cfr = &ar->cfr;
+
+ ret = ath11k_dbring_get_cap(ar->ab, ar->pdev_idx,
+ WMI_DIRECT_BUF_CFR, &db_cap);
+ if (ret)
+ continue;
+
+ idr_init(&cfr->rx_ring.bufs_idr);
+ spin_lock_init(&cfr->rx_ring.idr_lock);
+ spin_lock_init(&cfr->lock);
+ spin_lock_init(&cfr->lut_lock);
+
+ num_lut_entries = min_t(u32, CFR_MAX_LUT_ENTRIES, db_cap.min_elem);
+ cfr->lut = kzalloc_objs(*cfr->lut, num_lut_entries);
+ if (!cfr->lut) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ ret = ath11k_cfr_ring_alloc(ar, &db_cap);
+ if (ret) {
+ ath11k_warn(ab, "failed to init cfr ring for pdev %d: %d\n",
+ i, ret);
+ spin_lock_bh(&cfr->lut_lock);
+ kfree(cfr->lut);
+ cfr->lut = NULL;
+ cfr->enabled = false;
+ spin_unlock_bh(&cfr->lut_lock);
+ goto err;
+ }
+
+ cfr->lut_num = num_lut_entries;
+ cfr->enabled = true;
+
+ ath11k_cfr_debug_register(ar);
+ }
+
+ return 0;
+
+err:
+ for (i = i - 1; i >= 0; i--) {
+ ar = ab->pdevs[i].ar;
+ cfr = &ar->cfr;
+
+ if (!cfr->enabled)
+ continue;
+
+ ath11k_cfr_debug_unregister(ar);
+ ath11k_cfr_ring_free(ar);
+
+ spin_lock_bh(&cfr->lut_lock);
+ kfree(cfr->lut);
+ cfr->lut = NULL;
+ cfr->enabled = false;
+ spin_unlock_bh(&cfr->lut_lock);
+ }
+ return ret;
+}
diff --git a/cfr.h b/cfr.h
new file mode 100644
index 000000000000..94fcb706f2ef
--- /dev/null
+++ b/cfr.h
@@ -0,0 +1,308 @@
+/* SPDX-License-Identifier: BSD-3-Clause-Clear */
+/*
+ * Copyright (c) 2020-2021 The Linux Foundation. All rights reserved.
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef ATH11K_CFR_H
+#define ATH11K_CFR_H
+
+#include "dbring.h"
+#include "wmi.h"
+
+#define ATH11K_CFR_NUM_RESP_PER_EVENT 1
+#define ATH11K_CFR_EVENT_TIMEOUT_MS 1
+#define ATH11K_CFR_NUM_RING_ENTRIES 1
+
+#define ATH11K_MAX_CFR_ENABLED_CLIENTS 10
+#define CFR_MAX_LUT_ENTRIES 136
+
+#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_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,
+ 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 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;
+ 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 cfr_metadata_len;
+ 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;
+ u16 sw_peer_id;
+ u16 phy_ppdu_id;
+};
+
+struct ath11k_look_up_table {
+ bool dbr_recv;
+ bool tx_recv;
+ u8 *data;
+ u32 data_len;
+ 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;
+ u32 header_length;
+ u32 payload_length;
+ 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 */
+ spinlock_t lock;
+ /* Protect for lut entries */
+ spinlock_t lut_lock;
+ struct ath11k_look_up_table *lut;
+ struct dentry *enable_cfr;
+ struct dentry *cfr_unassoc;
+ struct rchan *rfs_cfr_capture;
+ u8 cfr_enabled_peer_cnt;
+ u32 lut_num;
+ u64 tx_evt_cnt;
+ u64 dbr_evt_cnt;
+ u64 release_cnt;
+ u64 tx_peer_status_cfr_fail;
+ u64 tx_evt_status_cfr_fail;
+ u64 tx_dbr_lookup_fail;
+ u64 last_success_tstamp;
+ u64 flush_dbr_cnt;
+ 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];
+};
+
+enum ath11k_cfr_capture_method {
+ ATH11K_CFR_CAPTURE_METHOD_NULL_FRAME,
+ ATH11K_CFR_CAPTURE_METHOD_NULL_FRAME_WITH_PHASE,
+ ATH11K_CFR_CAPTURE_METHOD_PROBE_RESP,
+ ATH11K_CFR_CAPTURE_METHOD_MAX,
+};
+
+enum ath11k_cfr_capture_bw {
+ ATH11K_CFR_CAPTURE_BW_20,
+ ATH11K_CFR_CAPTURE_BW_40,
+ ATH11K_CFR_CAPTURE_BW_80,
+ ATH11K_CFR_CAPTURE_BW_MAX,
+};
+
+#ifdef CONFIG_ATH11K_CFR
+int ath11k_cfr_init(struct ath11k_base *ab);
+void ath11k_cfr_deinit(struct ath11k_base *ab);
+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,
+ 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;
+}
+
+static inline void ath11k_cfr_deinit(struct ath11k_base *ab)
+{
+}
+
+static inline void ath11k_cfr_lut_update_paddr(struct ath11k *ar,
+ dma_addr_t paddr, u32 buf_id)
+{
+}
+
+static inline void ath11k_cfr_decrement_peer_count(struct ath11k *ar,
+ struct ath11k_sta *arsta)
+{
+}
+
+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,
+ struct ath11k_per_peer_cfr_capture *params,
+ const u8 *peer_mac)
+{
+ 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;
+}
+
+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 */
diff --git a/core.c b/core.c
index 812686173ac8..3f6f4db5b7ee 100644
--- a/core.c
+++ b/core.c
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear
/*
* Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
- * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
@@ -100,7 +99,6 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.supports_regdb = false,
.fix_l1ss = true,
.credit_flow = false,
- .max_tx_ring = DP_TCL_NUM_RING_MAX,
.hal_params = &ath11k_hw_hal_params_ipq8074,
.supports_dynamic_smps_6ghz = false,
.alloc_cacheable_memory = true,
@@ -126,6 +124,9 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.smp2p_wow_exit = false,
.support_dual_stations = false,
.pdev_suspend = false,
+ .cfr_support = true,
+ .cfr_num_stream_bufs = 255,
+ .cfr_stream_buf_size = 8200,
},
{
.hw_rev = ATH11K_HW_IPQ6018_HW10,
@@ -184,7 +185,6 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.supports_regdb = false,
.fix_l1ss = true,
.credit_flow = false,
- .max_tx_ring = DP_TCL_NUM_RING_MAX,
.hal_params = &ath11k_hw_hal_params_ipq8074,
.supports_dynamic_smps_6ghz = false,
.alloc_cacheable_memory = true,
@@ -211,6 +211,9 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.support_fw_mac_sequence = false,
.support_dual_stations = false,
.pdev_suspend = false,
+ .cfr_support = false,
+ .cfr_num_stream_bufs = 0,
+ .cfr_stream_buf_size = 0,
},
{
.name = "qca6390 hw2.0",
@@ -271,7 +274,6 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.supports_regdb = false,
.fix_l1ss = true,
.credit_flow = true,
- .max_tx_ring = DP_TCL_NUM_RING_MAX_QCA6390,
.hal_params = &ath11k_hw_hal_params_qca6390,
.supports_dynamic_smps_6ghz = false,
.alloc_cacheable_memory = false,
@@ -301,6 +303,9 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.support_fw_mac_sequence = true,
.support_dual_stations = true,
.pdev_suspend = false,
+ .cfr_support = false,
+ .cfr_num_stream_bufs = 0,
+ .cfr_stream_buf_size = 0,
},
{
.name = "qcn9074 hw1.0",
@@ -358,7 +363,6 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.supports_regdb = false,
.fix_l1ss = true,
.credit_flow = false,
- .max_tx_ring = DP_TCL_NUM_RING_MAX,
.hal_params = &ath11k_hw_hal_params_ipq8074,
.supports_dynamic_smps_6ghz = true,
.alloc_cacheable_memory = true,
@@ -385,6 +389,9 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.support_fw_mac_sequence = false,
.support_dual_stations = false,
.pdev_suspend = false,
+ .cfr_support = false,
+ .cfr_num_stream_bufs = 0,
+ .cfr_stream_buf_size = 0,
},
{
.name = "wcn6855 hw2.0",
@@ -445,7 +452,6 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.supports_regdb = true,
.fix_l1ss = false,
.credit_flow = true,
- .max_tx_ring = DP_TCL_NUM_RING_MAX_QCA6390,
.hal_params = &ath11k_hw_hal_params_qca6390,
.supports_dynamic_smps_6ghz = false,
.alloc_cacheable_memory = false,
@@ -475,6 +481,9 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.support_fw_mac_sequence = true,
.support_dual_stations = true,
.pdev_suspend = false,
+ .cfr_support = false,
+ .cfr_num_stream_bufs = 0,
+ .cfr_stream_buf_size = 0,
},
{
.name = "wcn6855 hw2.1",
@@ -533,7 +542,6 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.supports_regdb = true,
.fix_l1ss = false,
.credit_flow = true,
- .max_tx_ring = DP_TCL_NUM_RING_MAX_QCA6390,
.hal_params = &ath11k_hw_hal_params_qca6390,
.supports_dynamic_smps_6ghz = false,
.alloc_cacheable_memory = false,
@@ -563,6 +571,9 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.support_fw_mac_sequence = true,
.support_dual_stations = true,
.pdev_suspend = false,
+ .cfr_support = true,
+ .cfr_num_stream_bufs = 255,
+ .cfr_stream_buf_size = 8200,
},
{
.name = "wcn6750 hw1.0",
@@ -619,7 +630,6 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.supports_regdb = true,
.fix_l1ss = false,
.credit_flow = true,
- .max_tx_ring = DP_TCL_NUM_RING_MAX,
.hal_params = &ath11k_hw_hal_params_wcn6750,
.supports_dynamic_smps_6ghz = false,
.alloc_cacheable_memory = false,
@@ -646,6 +656,9 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.support_fw_mac_sequence = true,
.support_dual_stations = false,
.pdev_suspend = true,
+ .cfr_support = false,
+ .cfr_num_stream_bufs = 0,
+ .cfr_stream_buf_size = 0,
},
{
.hw_rev = ATH11K_HW_IPQ5018_HW10,
@@ -662,7 +675,6 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.qmi_service_ins_id = ATH11K_QMI_WLFW_SERVICE_INS_ID_V01_IPQ8074,
.ring_mask = &ath11k_hw_ring_mask_ipq8074,
.credit_flow = false,
- .max_tx_ring = 1,
.spectral = {
.fft_sz = 2,
.fft_pad_sz = 0,
@@ -698,7 +710,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.supports_regdb = false,
.idle_ps = false,
.supports_suspend = false,
- .hal_params = &ath11k_hw_hal_params_ipq8074,
+ .hal_params = &ath11k_hw_hal_params_ipq5018,
.single_pdev_only = false,
.coldboot_cal_mm = true,
.coldboot_cal_ftm = true,
@@ -729,6 +741,9 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.support_fw_mac_sequence = false,
.support_dual_stations = false,
.pdev_suspend = false,
+ .cfr_support = false,
+ .cfr_num_stream_bufs = 0,
+ .cfr_stream_buf_size = 0,
},
{
.name = "qca2066 hw2.1",
@@ -789,7 +804,6 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.supports_regdb = true,
.fix_l1ss = false,
.credit_flow = true,
- .max_tx_ring = DP_TCL_NUM_RING_MAX_QCA6390,
.hal_params = &ath11k_hw_hal_params_qca6390,
.supports_dynamic_smps_6ghz = false,
.alloc_cacheable_memory = false,
@@ -818,6 +832,9 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.smp2p_wow_exit = false,
.support_fw_mac_sequence = true,
.support_dual_stations = true,
+ .cfr_support = false,
+ .cfr_num_stream_bufs = 0,
+ .cfr_stream_buf_size = 0,
},
{
.name = "qca6698aq hw2.1",
@@ -876,7 +893,6 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.supports_regdb = true,
.fix_l1ss = false,
.credit_flow = true,
- .max_tx_ring = DP_TCL_NUM_RING_MAX_QCA6390,
.hal_params = &ath11k_hw_hal_params_qca6390,
.supports_dynamic_smps_6ghz = false,
.alloc_cacheable_memory = false,
@@ -906,6 +922,9 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.support_fw_mac_sequence = true,
.support_dual_stations = true,
.pdev_suspend = false,
+ .cfr_support = true,
+ .cfr_num_stream_bufs = 255,
+ .cfr_stream_buf_size = 8200,
},
};
@@ -994,9 +1013,64 @@ static const struct dmi_system_id ath11k_pm_quirk_table[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "21F9"),
},
},
+ {
+ .driver_data = (void *)ATH11K_PM_WOW,
+ .matches = { /* Z13 G1 */
+ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "21D2"),
+ },
+ },
+ {
+ .driver_data = (void *)ATH11K_PM_WOW,
+ .matches = { /* Z13 G1 */
+ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "21D3"),
+ },
+ },
+ {
+ .driver_data = (void *)ATH11K_PM_WOW,
+ .matches = { /* Z16 G1 */
+ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "21D4"),
+ },
+ },
+ {
+ .driver_data = (void *)ATH11K_PM_WOW,
+ .matches = { /* Z16 G1 */
+ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "21D5"),
+ },
+ },
{}
};
+static const struct __ath11k_core_usecase_firmware_table {
+ u32 hw_rev;
+ const char *compatible;
+ const char *firmware_name;
+} ath11k_core_usecase_firmware_table[] = {
+ { ATH11K_HW_WCN6855_HW21, "qcom,lemans-evk", "nfa765"},
+ { ATH11K_HW_WCN6855_HW21, "qcom,monaco-evk", "nfa765"},
+ { ATH11K_HW_WCN6855_HW21, "qcom,hamoa-iot-evk", "nfa765"},
+ { /* Sentinel */ }
+};
+
+const char *ath11k_core_get_usecase_firmware(struct ath11k_base *ab)
+{
+ const struct __ath11k_core_usecase_firmware_table *entry = NULL;
+
+ entry = ath11k_core_usecase_firmware_table;
+ while (entry->compatible) {
+ if (ab->hw_rev == entry->hw_rev &&
+ of_machine_is_compatible(entry->compatible))
+ return entry->firmware_name;
+ entry++;
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL(ath11k_core_get_usecase_firmware);
+
void ath11k_fw_stats_pdevs_free(struct list_head *head)
{
struct ath11k_fw_stats_pdev *i, *tmp;
@@ -1987,8 +2061,16 @@ static int ath11k_core_pdev_create(struct ath11k_base *ab)
goto err_thermal_unregister;
}
+ ret = ath11k_cfr_init(ab);
+ if (ret) {
+ ath11k_err(ab, "failed to init cfr %d\n", ret);
+ goto err_spectral_unregister;
+ }
+
return 0;
+err_spectral_unregister:
+ ath11k_spectral_deinit(ab);
err_thermal_unregister:
ath11k_thermal_unregister(ab);
err_mac_unregister:
@@ -2038,6 +2120,7 @@ static void ath11k_core_pdev_suspend_target(struct ath11k_base *ab)
static void ath11k_core_pdev_destroy(struct ath11k_base *ab)
{
+ ath11k_cfr_deinit(ab);
ath11k_spectral_deinit(ab);
ath11k_thermal_unregister(ab);
ath11k_mac_unregister(ab);
@@ -2250,6 +2333,7 @@ static int ath11k_core_reconfigure_on_crash(struct ath11k_base *ab)
mutex_lock(&ab->core_lock);
ath11k_thermal_unregister(ab);
ath11k_dp_pdev_free(ab);
+ ath11k_cfr_deinit(ab);
ath11k_spectral_deinit(ab);
ath11k_ce_cleanup_pipes(ab);
ath11k_wmi_detach(ab);
diff --git a/core.h b/core.h
index e8780b05ce11..a0d725923ef2 100644
--- a/core.h
+++ b/core.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: BSD-3-Clause-Clear */
/*
* Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
- * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved.
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#ifndef ATH11K_CORE_H
@@ -35,6 +35,7 @@
#include "wow.h"
#include "fw.h"
#include "coredump.h"
+#include "cfr.h"
#define SM(_v, _f) (((_v) << _f##_LSB) & _f##_MASK)
@@ -531,6 +532,13 @@ struct ath11k_per_ppdu_tx_stats {
DECLARE_EWMA(avg_rssi, 10, 8)
+struct ath11k_per_peer_cfr_capture {
+ enum ath11k_cfr_capture_method cfr_method;
+ enum ath11k_cfr_capture_bw cfr_bw;
+ u32 cfr_enable;
+ u32 cfr_period;
+};
+
struct ath11k_sta {
struct ath11k_vif *arvif;
@@ -571,6 +579,10 @@ struct ath11k_sta {
bool peer_current_ps_valid;
u32 bw_prev;
+
+#ifdef CONFIG_ATH11K_CFR
+ struct ath11k_per_peer_cfr_capture cfr_capture;
+#endif
};
#define ATH11K_MIN_5G_FREQ 4150
@@ -795,6 +807,11 @@ struct ath11k {
bool ps_state_enable;
bool ps_timekeeper_enable;
s8 max_allowed_tx_power;
+
+#ifdef CONFIG_ATH11K_CFR
+ struct ath11k_cfr cfr;
+#endif
+ bool cfr_enabled;
};
struct ath11k_band_cap {
@@ -1275,6 +1292,7 @@ bool ath11k_core_coldboot_cal_support(struct ath11k_base *ab);
const struct firmware *ath11k_core_firmware_request(struct ath11k_base *ab,
const char *filename);
+const char *ath11k_core_get_usecase_firmware(struct ath11k_base *ab);
static inline const char *ath11k_scan_state_str(enum ath11k_scan_state state)
{
@@ -1329,6 +1347,9 @@ static inline void ath11k_core_create_firmware_path(struct ath11k_base *ab,
of_property_read_string(ab->dev->of_node, "firmware-name", &fw_name);
+ if (!fw_name)
+ fw_name = ath11k_core_get_usecase_firmware(ab);
+
if (fw_name && strncmp(filename, "board", 5))
snprintf(buf, buf_len, "%s/%s/%s/%s", ATH11K_FW_DIR,
ab->hw_params.fw.dir, fw_name, filename);
diff --git a/dbring.c b/dbring.c
index 520d8b8662a2..6ac18f8a1e5f 100644
--- a/dbring.c
+++ b/dbring.c
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear
/*
* Copyright (c) 2019-2020 The Linux Foundation. All rights reserved.
- * Copyright (c) 2021-2023 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
@@ -37,10 +36,10 @@ static void ath11k_dbring_fill_magic_value(struct ath11k *ar,
memset32(buffer, ATH11K_DB_MAGIC_VALUE, size);
}
-static int ath11k_dbring_bufs_replenish(struct ath11k *ar,
- struct ath11k_dbring *ring,
- struct ath11k_dbring_element *buff,
- enum wmi_direct_buffer_module id)
+int ath11k_dbring_bufs_replenish(struct ath11k *ar,
+ struct ath11k_dbring *ring,
+ struct ath11k_dbring_element *buff,
+ enum wmi_direct_buffer_module id)
{
struct ath11k_base *ab = ar->ab;
struct hal_srng *srng;
@@ -80,6 +79,9 @@ static int ath11k_dbring_bufs_replenish(struct ath11k *ar,
goto err_idr_remove;
}
+ if (id == WMI_DIRECT_BUF_CFR)
+ ath11k_cfr_lut_update_paddr(ar, paddr, buf_id);
+
buff->paddr = paddr;
cookie = FIELD_PREP(DP_RXDMA_BUF_COOKIE_PDEV_ID, ar->pdev_idx) |
@@ -125,7 +127,7 @@ static int ath11k_dbring_fill_bufs(struct ath11k *ar,
size = ring->buf_sz + align - 1;
while (num_remain > 0) {
- buff = kzalloc(sizeof(*buff), GFP_ATOMIC);
+ buff = kzalloc_obj(*buff, GFP_ATOMIC);
if (!buff)
break;
@@ -155,12 +157,11 @@ int ath11k_dbring_wmi_cfg_setup(struct ath11k *ar,
enum wmi_direct_buffer_module id)
{
struct ath11k_wmi_pdev_dma_ring_cfg_req_cmd param = {};
- int ret;
+ int ret, i;
if (id >= WMI_DIRECT_BUF_MAX)
return -EINVAL;
- param.pdev_id = DP_SW2HW_MACID(ring->pdev_id);
param.module_id = id;
param.base_paddr_lo = lower_32_bits(ring->refill_srng.paddr);
param.base_paddr_hi = upper_32_bits(ring->refill_srng.paddr);
@@ -173,10 +174,23 @@ int ath11k_dbring_wmi_cfg_setup(struct ath11k *ar,
param.num_resp_per_event = ring->num_resp_per_event;
param.event_timeout_ms = ring->event_timeout_ms;
- ret = ath11k_wmi_pdev_dma_ring_cfg(ar, &param);
- if (ret) {
- ath11k_warn(ar->ab, "failed to setup db ring cfg\n");
- return ret;
+ /* For single pdev, 2GHz and 5GHz use one DBR. */
+ if (ar->ab->hw_params.single_pdev_only) {
+ for (i = 0; i < ar->ab->target_pdev_count; i++) {
+ param.pdev_id = ar->ab->target_pdev_ids[i].pdev_id;
+ ret = ath11k_wmi_pdev_dma_ring_cfg(ar, &param);
+ if (ret) {
+ ath11k_warn(ar->ab, "failed to setup db ring cfg\n");
+ return ret;
+ }
+ }
+ } else {
+ param.pdev_id = DP_SW2HW_MACID(ring->pdev_id);
+ ret = ath11k_wmi_pdev_dma_ring_cfg(ar, &param);
+ if (ret) {
+ ath11k_warn(ar->ab, "failed to setup db ring cfg\n");
+ return ret;
+ }
}
return 0;
@@ -281,10 +295,15 @@ 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;
+ if (ab->hw_params.single_pdev_only &&
+ pdev_idx < ab->target_pdev_count)
+ pdev_idx = 0;
+
if (pdev_idx >= ab->num_radios) {
ath11k_warn(ab, "Invalid pdev id %d\n", pdev_idx);
return -EINVAL;
@@ -310,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",
@@ -360,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;
diff --git a/dbring.h b/dbring.h
index 2f93b78a50df..e5f244dfa963 100644
--- a/dbring.h
+++ b/dbring.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: BSD-3-Clause-Clear */
/*
* Copyright (c) 2019-2020 The Linux Foundation. All rights reserved.
- * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#ifndef ATH11K_DBRING_H
@@ -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 {
@@ -61,6 +63,10 @@ int ath11k_dbring_set_cfg(struct ath11k *ar,
u32 event_timeout_ms,
int (*handler)(struct ath11k *,
struct ath11k_dbring_data *));
+int ath11k_dbring_bufs_replenish(struct ath11k *ar,
+ struct ath11k_dbring *ring,
+ struct ath11k_dbring_element *buff,
+ enum wmi_direct_buffer_module id);
int ath11k_dbring_wmi_cfg_setup(struct ath11k *ar,
struct ath11k_dbring *ring,
enum wmi_direct_buffer_module id);
diff --git a/debug.h b/debug.h
index cc8934d15697..aaa0034527a5 100644
--- a/debug.h
+++ b/debug.h
@@ -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
diff --git a/debugfs.c b/debugfs.c
index 977f945b6e66..0c1138407838 100644
--- a/debugfs.c
+++ b/debugfs.c
@@ -707,7 +707,7 @@ static ssize_t ath11k_debugfs_dump_soc_dp_stats(struct file *file,
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 < ab->hw_params.max_tx_ring; i++)
+ for (i = 0; i < ab->hw_params.hal_params->num_tx_rings; i++)
len += scnprintf(buf + len, size - len, "ring%d: %u\n",
i, soc_stats->tx_err.desc_na[i]);
@@ -1193,8 +1193,7 @@ static int ath11k_debugfs_dbr_dbg_init(struct ath11k *ar, int dbr_id)
if (ar->debug.dbr_debug[dbr_id])
return 0;
- ar->debug.dbr_debug[dbr_id] = kzalloc(sizeof(*dbr_debug),
- GFP_KERNEL);
+ ar->debug.dbr_debug[dbr_id] = kzalloc_obj(*dbr_debug);
if (!ar->debug.dbr_debug[dbr_id])
return -ENOMEM;
@@ -1216,9 +1215,8 @@ static int ath11k_debugfs_dbr_dbg_init(struct ath11k *ar, int dbr_id)
dbr_debug->dbr_debug_enabled = true;
dbr_dbg_data->num_ring_debug_entries = ATH11K_DEBUG_DBR_ENTRIES_MAX;
dbr_dbg_data->dbr_debug_idx = 0;
- dbr_dbg_data->entries = kcalloc(ATH11K_DEBUG_DBR_ENTRIES_MAX,
- sizeof(struct ath11k_dbg_dbr_entry),
- GFP_KERNEL);
+ dbr_dbg_data->entries = kzalloc_objs(struct ath11k_dbg_dbr_entry,
+ ATH11K_DEBUG_DBR_ENTRIES_MAX);
if (!dbr_dbg_data->entries)
return -ENOMEM;
diff --git a/debugfs_sta.c b/debugfs_sta.c
index d89d0f28d890..621a8a8df4b8 100644
--- a/debugfs_sta.c
+++ b/debugfs_sta.c
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear
/*
* Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
- * Copyright (c) 2021-2023 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
@@ -240,6 +239,140 @@ static const struct file_operations fops_tx_stats = {
.llseek = default_llseek,
};
+#ifdef CONFIG_ATH11K_CFR
+static ssize_t ath11k_dbg_sta_write_cfr_capture(struct file *file,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct ieee80211_sta *sta = file->private_data;
+ struct ath11k_sta *arsta = ath11k_sta_to_arsta(sta);
+ struct ath11k *ar = arsta->arvif->ar;
+ struct ath11k_cfr *cfr = &ar->cfr;
+ struct wmi_peer_cfr_capture_conf_arg arg;
+ u32 cfr_capture_enable = 0, cfr_capture_bw = 0;
+ u32 cfr_capture_method = 0, cfr_capture_period = 0;
+ char buf[64] = {};
+ int ret;
+
+ simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count);
+
+ guard(mutex)(&ar->conf_mutex);
+
+ if (ar->state != ATH11K_STATE_ON)
+ return -ENETDOWN;
+
+ if (!ar->cfr_enabled)
+ return -EINVAL;
+
+ ret = sscanf(buf, "%u %u %u %u", &cfr_capture_enable, &cfr_capture_bw,
+ &cfr_capture_period, &cfr_capture_method);
+
+ if (ret < 1 || (cfr_capture_enable && ret != 4))
+ return -EINVAL;
+
+ if (cfr_capture_enable == arsta->cfr_capture.cfr_enable &&
+ (cfr_capture_period &&
+ cfr_capture_period == arsta->cfr_capture.cfr_period) &&
+ cfr_capture_bw == arsta->cfr_capture.cfr_bw &&
+ cfr_capture_method == arsta->cfr_capture.cfr_method)
+ return count;
+
+ if (!cfr_capture_enable &&
+ cfr_capture_enable == arsta->cfr_capture.cfr_enable)
+ return count;
+
+ if (cfr_capture_enable > WMI_PEER_CFR_CAPTURE_ENABLE ||
+ cfr_capture_bw > WMI_PEER_CFR_CAPTURE_BW_80 ||
+ cfr_capture_method > ATH11K_CFR_CAPTURE_METHOD_NULL_FRAME_WITH_PHASE ||
+ cfr_capture_period > WMI_PEER_CFR_PERIODICITY_MAX)
+ return -EINVAL;
+
+ /* Target expects cfr period in multiple of 10 */
+ if (cfr_capture_period % 10) {
+ ath11k_err(ar->ab, "periodicity should be 10x\n");
+ return -EINVAL;
+ }
+
+ if (ar->cfr.cfr_enabled_peer_cnt >= ATH11K_MAX_CFR_ENABLED_CLIENTS &&
+ !arsta->cfr_capture.cfr_enable) {
+ ath11k_err(ar->ab, "CFR enable peer threshold reached %u\n",
+ ar->cfr.cfr_enabled_peer_cnt);
+ return -EINVAL;
+ }
+
+ if (!cfr_capture_enable) {
+ cfr_capture_bw = arsta->cfr_capture.cfr_bw;
+ cfr_capture_period = arsta->cfr_capture.cfr_period;
+ cfr_capture_method = arsta->cfr_capture.cfr_method;
+ }
+
+ arg.request = cfr_capture_enable;
+ arg.periodicity = cfr_capture_period;
+ arg.bw = cfr_capture_bw;
+ arg.method = cfr_capture_method;
+
+ ret = ath11k_wmi_peer_set_cfr_capture_conf(ar, arsta->arvif->vdev_id,
+ sta->addr, &arg);
+ if (ret) {
+ ath11k_warn(ar->ab,
+ "failed to send cfr capture info: vdev_id %u peer %pM: %d\n",
+ arsta->arvif->vdev_id, sta->addr, ret);
+ return ret;
+ }
+
+ spin_lock_bh(&ar->cfr.lock);
+
+ if (cfr_capture_enable &&
+ cfr_capture_enable != arsta->cfr_capture.cfr_enable)
+ cfr->cfr_enabled_peer_cnt++;
+ else if (!cfr_capture_enable)
+ cfr->cfr_enabled_peer_cnt--;
+
+ spin_unlock_bh(&ar->cfr.lock);
+
+ arsta->cfr_capture.cfr_enable = cfr_capture_enable;
+ arsta->cfr_capture.cfr_period = cfr_capture_period;
+ arsta->cfr_capture.cfr_bw = cfr_capture_bw;
+ arsta->cfr_capture.cfr_method = cfr_capture_method;
+
+ return count;
+}
+
+static ssize_t ath11k_dbg_sta_read_cfr_capture(struct file *file,
+ char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct ieee80211_sta *sta = file->private_data;
+ struct ath11k_sta *arsta = ath11k_sta_to_arsta(sta);
+ struct ath11k *ar = arsta->arvif->ar;
+ char buf[512] = {};
+ int len = 0;
+
+ mutex_lock(&ar->conf_mutex);
+
+ len += scnprintf(buf + len, sizeof(buf) - len, "cfr_enabled = %d\n",
+ arsta->cfr_capture.cfr_enable);
+ len += scnprintf(buf + len, sizeof(buf) - len, "bandwidth = %d\n",
+ arsta->cfr_capture.cfr_bw);
+ len += scnprintf(buf + len, sizeof(buf) - len, "period = %d\n",
+ arsta->cfr_capture.cfr_period);
+ len += scnprintf(buf + len, sizeof(buf) - len, "cfr_method = %d\n",
+ arsta->cfr_capture.cfr_method);
+
+ mutex_unlock(&ar->conf_mutex);
+
+ return simple_read_from_buffer(user_buf, count, ppos, buf, len);
+}
+
+static const struct file_operations fops_peer_cfr_capture = {
+ .write = ath11k_dbg_sta_write_cfr_capture,
+ .read = ath11k_dbg_sta_read_cfr_capture,
+ .open = simple_open,
+ .owner = THIS_MODULE,
+ .llseek = default_llseek,
+};
+#endif /* CONFIG_ATH11K_CFR */
+
static ssize_t ath11k_dbg_sta_dump_rx_stats(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
@@ -877,6 +1010,13 @@ void ath11k_debugfs_sta_op_add(struct ieee80211_hw *hw, struct ieee80211_vif *vi
debugfs_create_file("htt_peer_stats_reset", 0600, dir, sta,
&fops_htt_peer_stats_reset);
+#ifdef CONFIG_ATH11K_CFR
+ if (test_bit(WMI_TLV_SERVICE_CFR_CAPTURE_SUPPORT,
+ ar->ab->wmi_ab.svc_map))
+ debugfs_create_file("cfr_capture", 0600, dir, sta,
+ &fops_peer_cfr_capture);
+#endif/* CONFIG_ATH11K_CFR */
+
debugfs_create_file("peer_ps_state", 0400, dir, sta,
&fops_peer_ps_state);
diff --git a/dp.c b/dp.c
index 56b1a657e0b0..c940de285276 100644
--- a/dp.c
+++ b/dp.c
@@ -344,7 +344,7 @@ void ath11k_dp_stop_shadow_timers(struct ath11k_base *ab)
if (!ab->hw_params.supports_shadow_regs)
return;
- for (i = 0; i < ab->hw_params.max_tx_ring; i++)
+ for (i = 0; i < ab->hw_params.hal_params->num_tx_rings; i++)
ath11k_dp_shadow_stop_timer(ab, &ab->dp.tx_ring_timer[i]);
ath11k_dp_shadow_stop_timer(ab, &ab->dp.reo_cmd_timer);
@@ -359,7 +359,7 @@ static void ath11k_dp_srng_common_cleanup(struct ath11k_base *ab)
ath11k_dp_srng_cleanup(ab, &dp->wbm_desc_rel_ring);
ath11k_dp_srng_cleanup(ab, &dp->tcl_cmd_ring);
ath11k_dp_srng_cleanup(ab, &dp->tcl_status_ring);
- for (i = 0; i < ab->hw_params.max_tx_ring; i++) {
+ for (i = 0; i < ab->hw_params.hal_params->num_tx_rings; i++) {
ath11k_dp_srng_cleanup(ab, &dp->tx_ring[i].tcl_data_ring);
ath11k_dp_srng_cleanup(ab, &dp->tx_ring[i].tcl_comp_ring);
}
@@ -400,7 +400,7 @@ static int ath11k_dp_srng_common_setup(struct ath11k_base *ab)
goto err;
}
- for (i = 0; i < ab->hw_params.max_tx_ring; i++) {
+ for (i = 0; i < ab->hw_params.hal_params->num_tx_rings; i++) {
tcl_num = ab->hw_params.hal_params->tcl2wbm_rbm_map[i].tcl_ring_num;
wbm_num = ab->hw_params.hal_params->tcl2wbm_rbm_map[i].wbm_ring_num;
@@ -782,7 +782,7 @@ int ath11k_dp_service_srng(struct ath11k_base *ab,
int i, j;
int tot_work_done = 0;
- for (i = 0; i < ab->hw_params.max_tx_ring; i++) {
+ for (i = 0; i < ab->hw_params.hal_params->num_tx_rings; i++) {
if (BIT(ab->hw_params.hal_params->tcl2wbm_rbm_map[i].wbm_ring_num) &
ab->hw_params.ring_mask->tx[grp_id])
ath11k_dp_tx_completion_handler(ab, i);
@@ -1035,7 +1035,7 @@ void ath11k_dp_free(struct ath11k_base *ab)
ath11k_dp_reo_cmd_list_cleanup(ab);
- for (i = 0; i < ab->hw_params.max_tx_ring; i++) {
+ for (i = 0; i < ab->hw_params.hal_params->num_tx_rings; i++) {
spin_lock_bh(&dp->tx_ring[i].tx_idr_lock);
idr_for_each(&dp->tx_ring[i].txbuf_idr,
ath11k_dp_tx_pending_cleanup, ab);
@@ -1086,7 +1086,7 @@ int ath11k_dp_alloc(struct ath11k_base *ab)
size = sizeof(struct hal_wbm_release_ring) * DP_TX_COMP_RING_SIZE;
- for (i = 0; i < ab->hw_params.max_tx_ring; i++) {
+ for (i = 0; i < ab->hw_params.hal_params->num_tx_rings; i++) {
idr_init(&dp->tx_ring[i].txbuf_idr);
spin_lock_init(&dp->tx_ring[i].tx_idr_lock);
dp->tx_ring[i].tcl_data_ring_id = i;
diff --git a/dp.h b/dp.h
index 7a55afd33be8..1bd513f68a3c 100644
--- a/dp.h
+++ b/dp.h
@@ -199,7 +199,6 @@ struct ath11k_pdev_dp {
#define DP_BA_WIN_SZ_MAX 256
#define DP_TCL_NUM_RING_MAX 3
-#define DP_TCL_NUM_RING_MAX_QCA6390 1
#define DP_IDLE_SCATTER_BUFS_MAX 16
diff --git a/dp_rx.c b/dp_rx.c
index b9e976ddcbbf..85defe11750d 100644
--- a/dp_rx.c
+++ b/dp_rx.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear
/*
* Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
- * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved.
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#include <linux/ieee80211.h>
@@ -772,7 +772,7 @@ static void ath11k_dp_rx_tid_del_func(struct ath11k_dp *dp, void *ctx,
return;
}
- elem = kzalloc(sizeof(*elem), GFP_ATOMIC);
+ elem = kzalloc_obj(*elem, GFP_ATOMIC);
if (!elem)
goto free_desc;
@@ -1110,9 +1110,8 @@ int ath11k_dp_rx_ampdu_stop(struct ath11k *ar,
struct ath11k_base *ab = ar->ab;
struct ath11k_peer *peer;
struct ath11k_sta *arsta = ath11k_sta_to_arsta(params->sta);
+ struct dp_rx_tid *rx_tid;
int vdev_id = arsta->arvif->vdev_id;
- dma_addr_t paddr;
- bool active;
int ret;
spin_lock_bh(&ab->base_lock);
@@ -1124,15 +1123,14 @@ int ath11k_dp_rx_ampdu_stop(struct ath11k *ar,
return -ENOENT;
}
- paddr = peer->rx_tid[params->tid].paddr;
- active = peer->rx_tid[params->tid].active;
+ rx_tid = &peer->rx_tid[params->tid];
- if (!active) {
+ if (!rx_tid->active) {
spin_unlock_bh(&ab->base_lock);
return 0;
}
- ret = ath11k_peer_rx_tid_reo_update(ar, peer, peer->rx_tid, 1, 0, false);
+ ret = ath11k_peer_rx_tid_reo_update(ar, peer, rx_tid, 1, 0, false);
spin_unlock_bh(&ab->base_lock);
if (ret) {
ath11k_warn(ab, "failed to update reo for rx tid %d: %d\n",
@@ -1141,7 +1139,8 @@ int ath11k_dp_rx_ampdu_stop(struct ath11k *ar,
}
ret = ath11k_wmi_peer_rx_reorder_queue_setup(ar, vdev_id,
- params->sta->addr, paddr,
+ params->sta->addr,
+ rx_tid->paddr,
params->tid, 1, 1);
if (ret)
ath11k_warn(ab, "failed to send wmi to delete rx tid %d\n",
@@ -1542,7 +1541,7 @@ struct htt_ppdu_stats_info *ath11k_dp_htt_get_ppdu_desc(struct ath11k *ar,
}
}
- ppdu_info = kzalloc(sizeof(*ppdu_info), GFP_ATOMIC);
+ ppdu_info = kzalloc_obj(*ppdu_info, GFP_ATOMIC);
if (!ppdu_info)
return NULL;
@@ -5485,7 +5484,7 @@ static int ath11k_dp_rx_full_mon_prepare_mpdu(struct ath11k_dp *dp,
struct sk_buff *head,
struct sk_buff *tail)
{
- mon_mpdu = kzalloc(sizeof(*mon_mpdu), GFP_ATOMIC);
+ mon_mpdu = kzalloc_obj(*mon_mpdu, GFP_ATOMIC);
if (!mon_mpdu)
return -ENOMEM;
diff --git a/dp_tx.c b/dp_tx.c
index 562aba66582f..9c2310665713 100644
--- a/dp_tx.c
+++ b/dp_tx.c
@@ -91,6 +91,7 @@ int ath11k_dp_tx(struct ath11k *ar, struct ath11k_vif *arvif,
struct hal_srng *tcl_ring;
struct ieee80211_hdr *hdr = (void *)skb->data;
struct dp_tx_ring *tx_ring;
+ size_t num_tx_rings = ab->hw_params.hal_params->num_tx_rings;
void *hal_tcl_desc;
u8 pool_id;
u8 hal_ring_id;
@@ -113,7 +114,7 @@ int ath11k_dp_tx(struct ath11k *ar, struct ath11k_vif *arvif,
tcl_ring_sel:
tcl_ring_retry = false;
- ti.ring_id = ring_selector % ab->hw_params.max_tx_ring;
+ ti.ring_id = ring_selector % num_tx_rings;
ti.rbm_id = ab->hw_params.hal_params->tcl2wbm_rbm_map[ti.ring_id].rbm_id;
ring_map |= BIT(ti.ring_id);
@@ -126,7 +127,7 @@ tcl_ring_sel:
spin_unlock_bh(&tx_ring->tx_idr_lock);
if (unlikely(ret < 0)) {
- if (ring_map == (BIT(ab->hw_params.max_tx_ring) - 1) ||
+ if (ring_map == (BIT(num_tx_rings) - 1) ||
!ab->hw_params.tcl_ring_retry) {
atomic_inc(&ab->soc_stats.tx_err.misc_fail);
return -ENOSPC;
@@ -244,8 +245,8 @@ tcl_ring_sel:
* checking this ring earlier for each pkt tx.
* Restart ring selection if some rings are not checked yet.
*/
- if (unlikely(ring_map != (BIT(ab->hw_params.max_tx_ring)) - 1) &&
- ab->hw_params.tcl_ring_retry && ab->hw_params.max_tx_ring > 1) {
+ if (unlikely(ring_map != (BIT(num_tx_rings)) - 1) &&
+ ab->hw_params.tcl_ring_retry && num_tx_rings > 1) {
tcl_ring_retry = true;
ring_selector++;
}
@@ -795,7 +796,7 @@ int ath11k_dp_tx_send_reo_cmd(struct ath11k_base *ab, struct dp_rx_tid *rx_tid,
* for tid delete command to free up the resource on the command status
* indication?
*/
- dp_cmd = kzalloc(sizeof(*dp_cmd), GFP_ATOMIC);
+ dp_cmd = kzalloc_obj(*dp_cmd, GFP_ATOMIC);
if (!dp_cmd)
return -ENOMEM;
diff --git a/hal.c b/hal.c
index 0c797b8d0a27..e821e5a62c1c 100644
--- a/hal.c
+++ b/hal.c
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear
/*
* Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
- * Copyright (c) 2021-2024 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#include <linux/dma-mapping.h>
@@ -184,7 +183,7 @@ static const struct hal_srng_config hw_srng_config_template[] = {
},
{ /* RXDMA DIR BUF */
.start_ring_id = HAL_SRNG_RING_ID_RXDMA_DIR_BUF,
- .max_rings = 1,
+ .max_rings = 2,
.entry_size = 8 >> 2, /* TODO: Define the struct */
.lmac_ring = true,
.ring_dir = HAL_SRNG_DIR_SRC,
diff --git a/hw.c b/hw.c
index caa6dc12a790..d19c4b372a2a 100644
--- a/hw.c
+++ b/hw.c
@@ -2707,6 +2707,14 @@ const struct ath11k_hw_regs wcn6750_regs = {
.hal_reo1_misc_ctl = 0x000005d8,
};
+static const struct ath11k_hw_tcl2wbm_rbm_map ath11k_hw_tcl2wbm_rbm_map_ipq5018[] = {
+ {
+ .tcl_ring_num = 0,
+ .wbm_ring_num = 0,
+ .rbm_id = HAL_RX_BUF_RBM_SW0_BM,
+ },
+};
+
static const struct ath11k_hw_tcl2wbm_rbm_map ath11k_hw_tcl2wbm_rbm_map_ipq8074[] = {
{
.tcl_ring_num = 0,
@@ -2822,19 +2830,28 @@ const struct ath11k_hw_regs ipq5018_regs = {
.hal_wbm1_release_ring_base_lsb = 0x0000097c,
};
+const struct ath11k_hw_hal_params ath11k_hw_hal_params_ipq5018 = {
+ .rx_buf_rbm = HAL_RX_BUF_RBM_SW3_BM,
+ .tcl2wbm_rbm_map = ath11k_hw_tcl2wbm_rbm_map_ipq5018,
+ .num_tx_rings = ARRAY_SIZE(ath11k_hw_tcl2wbm_rbm_map_ipq5018),
+};
+
const struct ath11k_hw_hal_params ath11k_hw_hal_params_ipq8074 = {
.rx_buf_rbm = HAL_RX_BUF_RBM_SW3_BM,
.tcl2wbm_rbm_map = ath11k_hw_tcl2wbm_rbm_map_ipq8074,
+ .num_tx_rings = ARRAY_SIZE(ath11k_hw_tcl2wbm_rbm_map_ipq8074),
};
const struct ath11k_hw_hal_params ath11k_hw_hal_params_qca6390 = {
.rx_buf_rbm = HAL_RX_BUF_RBM_SW1_BM,
- .tcl2wbm_rbm_map = ath11k_hw_tcl2wbm_rbm_map_ipq8074,
+ .tcl2wbm_rbm_map = ath11k_hw_tcl2wbm_rbm_map_ipq5018,
+ .num_tx_rings = ARRAY_SIZE(ath11k_hw_tcl2wbm_rbm_map_ipq5018),
};
const struct ath11k_hw_hal_params ath11k_hw_hal_params_wcn6750 = {
.rx_buf_rbm = HAL_RX_BUF_RBM_SW1_BM,
.tcl2wbm_rbm_map = ath11k_hw_tcl2wbm_rbm_map_wcn6750,
+ .num_tx_rings = ARRAY_SIZE(ath11k_hw_tcl2wbm_rbm_map_wcn6750),
};
static const struct cfg80211_sar_freq_ranges ath11k_hw_sar_freq_ranges_wcn6855[] = {
diff --git a/hw.h b/hw.h
index 52d9f4c13b13..4996536fbd14 100644
--- a/hw.h
+++ b/hw.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: BSD-3-Clause-Clear */
/*
* Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
- * Copyright (c) 2021-2024 Qualcomm Innovation Center, Inc. All rights reserved.
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#ifndef ATH11K_HW_H
@@ -134,6 +134,7 @@ struct ath11k_hw_tcl2wbm_rbm_map {
struct ath11k_hw_hal_params {
enum hal_rx_buf_return_buf_manager rx_buf_rbm;
const struct ath11k_hw_tcl2wbm_rbm_map *tcl2wbm_rbm_map;
+ size_t num_tx_rings;
};
struct ath11k_hw_params {
@@ -198,7 +199,6 @@ struct ath11k_hw_params {
bool supports_regdb;
bool fix_l1ss;
bool credit_flow;
- u8 max_tx_ring;
const struct ath11k_hw_hal_params *hal_params;
bool supports_dynamic_smps_6ghz;
bool alloc_cacheable_memory;
@@ -228,6 +228,9 @@ struct ath11k_hw_params {
bool support_fw_mac_sequence;
bool support_dual_stations;
bool pdev_suspend;
+ bool cfr_support;
+ u32 cfr_num_stream_bufs;
+ u32 cfr_stream_buf_size;
};
struct ath11k_hw_ops {
@@ -291,6 +294,7 @@ extern const struct ce_ie_addr ath11k_ce_ie_addr_ipq5018;
extern const struct ce_remap ath11k_ce_remap_ipq5018;
+extern const struct ath11k_hw_hal_params ath11k_hw_hal_params_ipq5018;
extern const struct ath11k_hw_hal_params ath11k_hw_hal_params_ipq8074;
extern const struct ath11k_hw_hal_params ath11k_hw_hal_params_qca6390;
extern const struct ath11k_hw_hal_params ath11k_hw_hal_params_wcn6750;
diff --git a/mac.c b/mac.c
index 3276fe443502..e4ee2ba1f669 100644
--- a/mac.c
+++ b/mac.c
@@ -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,
@@ -4219,7 +4221,7 @@ static int ath11k_mac_op_hw_scan(struct ieee80211_hw *hw,
if (ret)
goto exit;
- arg = kzalloc(sizeof(*arg), GFP_KERNEL);
+ arg = kzalloc_obj(*arg);
if (!arg) {
ret = -ENOMEM;
@@ -6186,6 +6188,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 +6228,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;
@@ -7392,7 +7407,7 @@ err_vdev_del:
idr_for_each(&ar->txmgmt_idr,
ath11k_mac_vif_txmgmt_idr_remove, vif);
- for (i = 0; i < ab->hw_params.max_tx_ring; i++) {
+ for (i = 0; i < ab->hw_params.hal_params->num_tx_rings; i++) {
spin_lock_bh(&ab->dp.tx_ring[i].tx_idr_lock);
idr_for_each(&ab->dp.tx_ring[i].txbuf_idr,
ath11k_mac_vif_unref, vif);
@@ -7861,7 +7876,7 @@ ath11k_mac_update_active_vif_chan(struct ath11k *ar,
if (arg.n_vifs == 0)
return;
- arg.vifs = kcalloc(arg.n_vifs, sizeof(arg.vifs[0]), GFP_KERNEL);
+ arg.vifs = kzalloc_objs(arg.vifs[0], arg.n_vifs);
if (!arg.vifs)
return;
@@ -9714,7 +9729,7 @@ static int ath11k_mac_op_remain_on_channel(struct ieee80211_hw *hw,
scan_time_msec = ar->hw->wiphy->max_remain_on_channel_duration * 2;
- arg = kzalloc(sizeof(*arg), GFP_KERNEL);
+ arg = kzalloc_obj(*arg);
if (!arg) {
ret = -ENOMEM;
goto exit;
@@ -9808,7 +9823,7 @@ static int ath11k_mac_station_add(struct ath11k *ar,
arvif->reinstall_group_keys = false;
}
- arsta->rx_stats = kzalloc(sizeof(*arsta->rx_stats), GFP_KERNEL);
+ arsta->rx_stats = kzalloc_obj(*arsta->rx_stats);
if (!arsta->rx_stats) {
ret = -ENOMEM;
goto dec_num_station;
@@ -9829,7 +9844,7 @@ static int ath11k_mac_station_add(struct ath11k *ar,
sta->addr, arvif->vdev_id);
if (ath11k_debugfs_is_extd_tx_stats_enabled(ar)) {
- arsta->tx_stats = kzalloc(sizeof(*arsta->tx_stats), GFP_KERNEL);
+ arsta->tx_stats = kzalloc_obj(*arsta->tx_stats);
if (!arsta->tx_stats) {
ret = -ENOMEM;
goto free_peer;
@@ -9979,6 +9994,8 @@ static int ath11k_mac_op_sta_state(struct ieee80211_hw *hw,
}
spin_unlock_bh(&ar->ab->base_lock);
mutex_unlock(&ar->ab->tbl_mtx_lock);
+
+ ath11k_cfr_decrement_peer_count(ar, arsta);
} else if (old_state == IEEE80211_STA_AUTH &&
new_state == IEEE80211_STA_ASSOC &&
(vif->type == NL80211_IFTYPE_AP ||
@@ -10261,7 +10278,7 @@ static void ath11k_mac_setup_mac_address_list(struct ath11k *ar)
return;
n_addresses = ar->ab->hw_params.num_vdevs;
- addresses = kcalloc(n_addresses, sizeof(*addresses), GFP_KERNEL);
+ addresses = kzalloc_objs(*addresses, n_addresses);
if (!addresses)
return;
@@ -10293,7 +10310,7 @@ static int ath11k_mac_setup_iface_combinations(struct ath11k *ar)
else
n_combos = 1;
- combinations = kcalloc(n_combos, sizeof(*combinations), GFP_KERNEL);
+ combinations = kzalloc_objs(*combinations, n_combos);
if (!combinations)
return -ENOMEM;
@@ -10302,7 +10319,7 @@ static int ath11k_mac_setup_iface_combinations(struct ath11k *ar)
else
n_limits = 2;
- limits = kcalloc(n_limits, sizeof(*limits), GFP_KERNEL);
+ limits = kzalloc_objs(*limits, n_limits);
if (!limits) {
kfree(combinations);
return -ENOMEM;
@@ -10640,7 +10657,7 @@ static int __ath11k_mac_register(struct ath11k *ar)
if (!ab->hw_params.supports_monitor)
/* There's a race between calling ieee80211_register_hw()
* and here where the monitor mode is enabled for a little
- * while. But that time is so short and in practise it make
+ * while. But that time is so short and in practice it doesn't make
* a difference in real life.
*/
ar->hw->wiphy->interface_modes &= ~BIT(NL80211_IFTYPE_MONITOR);
diff --git a/mhi.c b/mhi.c
index acd76e9392d3..f994233df2bb 100644
--- a/mhi.c
+++ b/mhi.c
@@ -34,7 +34,6 @@ static const struct mhi_channel_config ath11k_mhi_channels_qca6390[] = {
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
- .auto_queue = false,
},
{
.num = 21,
@@ -48,7 +47,6 @@ static const struct mhi_channel_config ath11k_mhi_channels_qca6390[] = {
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
- .auto_queue = true,
},
};
@@ -99,7 +97,6 @@ static const struct mhi_channel_config ath11k_mhi_channels_qcn9074[] = {
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
- .auto_queue = false,
},
{
.num = 21,
@@ -113,7 +110,6 @@ static const struct mhi_channel_config ath11k_mhi_channels_qcn9074[] = {
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
- .auto_queue = true,
},
};
@@ -211,7 +207,7 @@ static int ath11k_mhi_get_msi(struct ath11k_pci *ab_pci)
ath11k_dbg(ab, ATH11K_DBG_PCI, "num_vectors %d base_vector %d\n",
num_vectors, base_vector);
- irq = kcalloc(num_vectors, sizeof(int), GFP_KERNEL);
+ irq = kzalloc_objs(int, num_vectors);
if (!irq)
return -ENOMEM;
diff --git a/peer.c b/peer.c
index 6d0126c39301..027cc7aa58c8 100644
--- a/peer.c
+++ b/peer.c
@@ -125,7 +125,7 @@ void ath11k_peer_map_event(struct ath11k_base *ab, u8 vdev_id, u16 peer_id,
spin_lock_bh(&ab->base_lock);
peer = ath11k_peer_find(ab, vdev_id, mac_addr);
if (!peer) {
- peer = kzalloc(sizeof(*peer), GFP_ATOMIC);
+ peer = kzalloc_obj(*peer, GFP_ATOMIC);
if (!peer)
goto exit;
diff --git a/qmi.c b/qmi.c
index ff6a97e328b8..feebbc30f3df 100644
--- a/qmi.c
+++ b/qmi.c
@@ -1799,11 +1799,11 @@ static int ath11k_qmi_fw_ind_register_send(struct ath11k_base *ab)
struct qmi_txn txn;
int ret;
- req = kzalloc(sizeof(*req), GFP_KERNEL);
+ req = kzalloc_obj(*req);
if (!req)
return -ENOMEM;
- resp = kzalloc(sizeof(*resp), GFP_KERNEL);
+ resp = kzalloc_obj(*resp);
if (!resp) {
ret = -ENOMEM;
goto resp_out;
@@ -1878,7 +1878,7 @@ static int ath11k_qmi_respond_fw_mem_request(struct ath11k_base *ab)
int ret = 0, i;
bool delayed;
- req = kzalloc(sizeof(*req), GFP_KERNEL);
+ req = kzalloc_obj(*req);
if (!req)
return -ENOMEM;
@@ -2306,7 +2306,7 @@ static int ath11k_qmi_load_file_target_mem(struct ath11k_base *ab,
int ret = 0;
u32 remaining = len;
- req = kzalloc(sizeof(*req), GFP_KERNEL);
+ req = kzalloc_obj(*req);
if (!req)
return -ENOMEM;
@@ -2705,7 +2705,7 @@ static int ath11k_qmi_wlanfw_wlan_cfg_send(struct ath11k_base *ab)
ce_cfg = (struct ce_pipe_config *)ab->qmi.ce_cfg.tgt_ce;
svc_cfg = (struct service_to_pipe *)ab->qmi.ce_cfg.svc_to_ce_map;
- req = kzalloc(sizeof(*req), GFP_KERNEL);
+ req = kzalloc_obj(*req);
if (!req)
return -ENOMEM;
@@ -2929,7 +2929,7 @@ ath11k_qmi_driver_event_post(struct ath11k_qmi *qmi,
{
struct ath11k_qmi_driver_event *event;
- event = kzalloc(sizeof(*event), GFP_ATOMIC);
+ event = kzalloc_obj(*event, GFP_ATOMIC);
if (!event)
return -ENOMEM;
diff --git a/reg.c b/reg.c
index d62a2014315a..0879a132b67c 100644
--- a/reg.c
+++ b/reg.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear
/*
* Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
- * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved.
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#include <linux/rtnetlink.h>
@@ -146,8 +146,7 @@ int ath11k_reg_update_chan_list(struct ath11k *ar, bool wait)
if (WARN_ON(!num_channels))
return -EINVAL;
- params = kzalloc(struct_size(params, ch_param, num_channels),
- GFP_KERNEL);
+ params = kzalloc_flex(*params, ch_param, num_channels);
if (!params)
return -ENOMEM;
@@ -926,8 +925,11 @@ int ath11k_reg_handle_chan_list(struct ath11k_base *ab,
*/
if (ab->default_regd[pdev_idx] && !ab->new_regd[pdev_idx] &&
!memcmp((char *)ab->default_regd[pdev_idx]->alpha2,
- (char *)reg_info->alpha2, 2))
- goto retfail;
+ (char *)reg_info->alpha2, 2) &&
+ power_type == IEEE80211_REG_UNSET_AP) {
+ ath11k_reg_reset_info(reg_info);
+ return 0;
+ }
/* Intersect new rules with default regd if a new country setting was
* requested, i.e a default regd was already set during initialization
diff --git a/wmi.c b/wmi.c
index 110035dae8a6..40747fba3b0c 100644
--- a/wmi.c
+++ b/wmi.c
@@ -249,7 +249,7 @@ const void **ath11k_wmi_tlv_parse_alloc(struct ath11k_base *ab,
const void **tb;
int ret;
- tb = kcalloc(WMI_TAG_MAX, sizeof(*tb), gfp);
+ tb = kzalloc_objs(*tb, WMI_TAG_MAX, gfp);
if (!tb)
return ERR_PTR(-ENOMEM);
@@ -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,
@@ -3941,6 +3953,47 @@ int ath11k_wmi_fils_discovery_tmpl(struct ath11k *ar, u32 vdev_id,
return 0;
}
+int ath11k_wmi_peer_set_cfr_capture_conf(struct ath11k *ar,
+ u32 vdev_id, const u8 *mac_addr,
+ struct wmi_peer_cfr_capture_conf_arg *arg)
+{
+ struct ath11k_pdev_wmi *wmi = ar->wmi;
+ struct wmi_peer_cfr_capture_cmd_fixed_param *cmd;
+ struct sk_buff *skb;
+ int ret;
+
+ skb = ath11k_wmi_alloc_skb(wmi->wmi_ab, sizeof(*cmd));
+ if (!skb)
+ return -ENOMEM;
+
+ cmd = (struct wmi_peer_cfr_capture_cmd_fixed_param *)skb->data;
+ cmd->tlv_header = FIELD_PREP(WMI_TLV_TAG,
+ WMI_TAG_PEER_CFR_CAPTURE_CMD) |
+ FIELD_PREP(WMI_TLV_LEN, sizeof(*cmd) - TLV_HDR_SIZE);
+
+ memcpy(&cmd->mac_addr, mac_addr, ETH_ALEN);
+ cmd->request = arg->request;
+ cmd->vdev_id = vdev_id;
+ cmd->periodicity = arg->periodicity;
+ cmd->bandwidth = arg->bw;
+ cmd->capture_method = arg->method;
+
+ ret = ath11k_wmi_cmd_send(ar->wmi, skb, WMI_PEER_CFR_CAPTURE_CMDID);
+ if (ret) {
+ ath11k_warn(ar->ab,
+ "WMI vdev %d failed to send peer cfr capture cmd: %d\n",
+ vdev_id, ret);
+ dev_kfree_skb(skb);
+ }
+
+ ath11k_dbg(ar->ab, ATH11K_DBG_WMI,
+ "WMI peer CFR capture cmd req %u id %u period %u bw %u mode %u\n",
+ arg->request, vdev_id, arg->periodicity,
+ arg->bw, arg->method);
+
+ return ret;
+}
+
int ath11k_wmi_probe_resp_tmpl(struct ath11k *ar, u32 vdev_id,
struct sk_buff *tmpl)
{
@@ -4865,9 +4918,8 @@ static int ath11k_wmi_tlv_ext_soc_hal_reg_caps_parse(struct ath11k_base *soc,
}
if (!soc->reg_info_store) {
- soc->reg_info_store = kcalloc(soc->num_radios,
- sizeof(*soc->reg_info_store),
- GFP_ATOMIC);
+ soc->reg_info_store = kzalloc_objs(*soc->reg_info_store,
+ soc->num_radios, GFP_ATOMIC);
if (!soc->reg_info_store)
return -ENOMEM;
}
@@ -5185,8 +5237,7 @@ static struct cur_reg_rule
struct cur_reg_rule *reg_rule_ptr;
u32 count;
- reg_rule_ptr = kcalloc(num_reg_rules, sizeof(*reg_rule_ptr),
- GFP_ATOMIC);
+ reg_rule_ptr = kzalloc_objs(*reg_rule_ptr, num_reg_rules, GFP_ATOMIC);
if (!reg_rule_ptr)
return NULL;
@@ -5335,7 +5386,7 @@ static struct cur_reg_rule
struct cur_reg_rule *reg_rule_ptr;
u32 count;
- reg_rule_ptr = kcalloc(num_reg_rules, sizeof(*reg_rule_ptr), GFP_ATOMIC);
+ reg_rule_ptr = kzalloc_objs(*reg_rule_ptr, num_reg_rules, GFP_ATOMIC);
if (!reg_rule_ptr)
return NULL;
@@ -6641,7 +6692,7 @@ static int ath11k_wmi_tlv_fw_stats_data_parse(struct ath11k_base *ab,
data += sizeof(*src);
len -= sizeof(*src);
- dst = kzalloc(sizeof(*dst), GFP_ATOMIC);
+ dst = kzalloc_obj(*dst, GFP_ATOMIC);
if (!dst)
continue;
@@ -6684,7 +6735,7 @@ static int ath11k_wmi_tlv_fw_stats_data_parse(struct ath11k_base *ab,
data += sizeof(*src);
len -= sizeof(*src);
- dst = kzalloc(sizeof(*dst), GFP_ATOMIC);
+ dst = kzalloc_obj(*dst, GFP_ATOMIC);
if (!dst)
continue;
@@ -6707,7 +6758,7 @@ static int ath11k_wmi_tlv_fw_stats_data_parse(struct ath11k_base *ab,
data += sizeof(*src);
len -= sizeof(*src);
- dst = kzalloc(sizeof(*dst), GFP_ATOMIC);
+ dst = kzalloc_obj(*dst, GFP_ATOMIC);
if (!dst)
continue;
@@ -7194,7 +7245,7 @@ static int ath11k_reg_chan_list_event(struct ath11k_base *ab, struct sk_buff *sk
struct cur_regulatory_info *reg_info;
int ret;
- reg_info = kzalloc(sizeof(*reg_info), GFP_ATOMIC);
+ reg_info = kzalloc_obj(*reg_info, GFP_ATOMIC);
if (!reg_info)
return -ENOMEM;
@@ -8752,6 +8803,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;
@@ -8882,6 +9020,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;
diff --git a/wmi.h b/wmi.h
index 0f0de24a3840..baed501b640b 100644
--- a/wmi.h
+++ b/wmi.h
@@ -362,6 +362,10 @@ enum wmi_tlv_cmd_id {
WMI_PEER_REORDER_QUEUE_REMOVE_CMDID,
WMI_PEER_SET_RX_BLOCKSIZE_CMDID,
WMI_PEER_ANTDIV_INFO_REQ_CMDID,
+ WMI_PEER_RESERVED0_CMDID,
+ WMI_PEER_TID_MSDUQ_QDEPTH_THRESH_UPDATE_CMDID,
+ WMI_PEER_TID_CONFIGURATIONS_CMDID,
+ WMI_PEER_CFR_CAPTURE_CMDID,
WMI_BCN_TX_CMDID = WMI_TLV_CMD(WMI_GRP_MGMT),
WMI_PDEV_SEND_BCN_CMDID,
WMI_BCN_TMPL_CMDID,
@@ -981,6 +985,7 @@ enum wmi_tlv_pdev_param {
WMI_PDEV_PARAM_RADIO_CHAN_STATS_ENABLE,
WMI_PDEV_PARAM_RADIO_DIAGNOSIS_ENABLE,
WMI_PDEV_PARAM_MESH_MCAST_ENABLE,
+ WMI_PDEV_PARAM_PER_PEER_CFR_ENABLE = 0xa8,
WMI_PDEV_PARAM_SET_CMD_OBSS_PD_THRESHOLD = 0xbc,
WMI_PDEV_PARAM_SET_CMD_OBSS_PD_PER_AC = 0xbe,
WMI_PDEV_PARAM_ENABLE_SR_PROHIBIT = 0xc6,
@@ -1884,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,
@@ -3832,7 +3839,8 @@ struct wmi_scan_prob_req_oui_cmd {
#define WMI_TX_PARAMS_DWORD1_BW_MASK GENMASK(14, 8)
#define WMI_TX_PARAMS_DWORD1_PREAMBLE_TYPE GENMASK(19, 15)
#define WMI_TX_PARAMS_DWORD1_FRAME_TYPE BIT(20)
-#define WMI_TX_PARAMS_DWORD1_RSVD GENMASK(31, 21)
+#define WMI_TX_PARAMS_DWORD1_CFR_CAPTURE BIT(21)
+#define WMI_TX_PARAMS_DWORD1_RSVD GENMASK(31, 22)
struct wmi_mgmt_send_params {
u32 tlv_header;
@@ -4217,6 +4225,87 @@ enum cc_setting_code {
*/
};
+enum ath11k_wmi_cfr_capture_bw {
+ WMI_PEER_CFR_CAPTURE_BW_20,
+ WMI_PEER_CFR_CAPTURE_BW_40,
+ WMI_PEER_CFR_CAPTURE_BW_80,
+ WMI_PEER_CFR_CAPTURE_BW_MAX,
+};
+
+enum ath11k_wmi_cfr_capture_method {
+ WMI_CFR_CAPTURE_METHOD_NULL_FRAME,
+ WMI_CFR_CAPTURE_METHOD_NULL_FRAME_WITH_PHASE,
+ WMI_CFR_CAPTURE_METHOD_PROBE_RESP,
+ 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;
+ u32 request;
+ u32 periodicity;
+};
+
+struct wmi_peer_cfr_capture_cmd_fixed_param {
+ u32 tlv_header;
+ u32 request;
+ struct wmi_mac_addr mac_addr;
+ u32 vdev_id;
+ u32 periodicity;
+ /* BW of measurement - of type enum ath11k_wmi_cfr_capture_bw */
+ u32 bandwidth;
+ /* Method used to capture CFR - of type enum ath11k_wmi_cfr_capture_method */
+ u32 capture_method;
+} __packed;
+
+#define WMI_PEER_CFR_CAPTURE_ENABLE 1
+#define WMI_PEER_CFR_CAPTURE_DISABLE 0
+
+/*periodicity in ms */
+#define WMI_PEER_CFR_PERIODICITY_MAX 600000
+
static inline enum cc_setting_code
ath11k_wmi_cc_setting_code_to_reg(enum wmi_reg_cc_setting_code status_code)
{
@@ -6346,7 +6435,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,
@@ -6531,5 +6620,7 @@ bool ath11k_wmi_supports_6ghz_cc_ext(struct ath11k *ar);
int ath11k_wmi_send_vdev_set_tpc_power(struct ath11k *ar,
u32 vdev_id,
struct ath11k_reg_tpc_power_info *param);
-
+int ath11k_wmi_peer_set_cfr_capture_conf(struct ath11k *ar,
+ u32 vdev_id, const u8 *mac,
+ struct wmi_peer_cfr_capture_conf_arg *arg);
#endif
diff --git a/wow.c b/wow.c
index b6f08755129f..f02edc9b8a9e 100644
--- a/wow.c
+++ b/wow.c
@@ -381,7 +381,7 @@ static int ath11k_vif_wow_set_wakeups(struct ath11k_vif *arvif,
struct wmi_pno_scan_req *pno;
int ret;
- pno = kzalloc(sizeof(*pno), GFP_KERNEL);
+ pno = kzalloc_obj(*pno);
if (!pno)
return -ENOMEM;
@@ -495,7 +495,7 @@ static int ath11k_vif_wow_clean_nlo(struct ath11k_vif *arvif)
if (ar->nlo_enabled) {
struct wmi_pno_scan_req *pno;
- pno = kzalloc(sizeof(*pno), GFP_KERNEL);
+ pno = kzalloc_obj(*pno);
if (!pno)
return -ENOMEM;