Subject: [PATCHv2 0/6] ath11k: add single shot/periodic CFR capture support for IPQ8074

Channel Frequency Response feature is to get the channel state
information from the hardware based on the user configurations
and send the CSI data to user space.

CSI data is further processed in user space which can be used to
identify the motion of the objects.

CFR dumps will be collected from the received ACK's for the
transmited QOS NULL frames for the associated stations. For
unassociated stations CFR dumps will be collected from the
probe response ACK.

CFR format to user space:
___________________________________________
| CFR header | CFR payload | CFR tail data |
|____________|_____________|_______________|

CFR header contains the following fields,

* Start magic number 0xDEADBEAF - 4bytes
* vendor id - 4bytes
* cfr metadata version - 1byte
* cfr data version - 1byte
* chip type - 1byte
* platform type - 1byte
* CFR metadata length - 4bytes
* metadata - 92bytes
peer mac - 6bytes
capture status - 1byte (1 for success 0 for failure)
capture_bw - 1byte
channel_bw - 1byte
phy_mode - 1byte
prim20_chan - 2bytes
center_freq1 - 2bytes
center_freq2 - 2bytes
capture_mode - 1byte
capture_type - 1byte
sts_count - 1byte
num_rx_chain - 1byte
timestamp - 4bytes
length - 4bytes
chain_rssi - 32bytes (4bytes for each chain)
chain_phase - 16bytes (2bytes for each chain)
cfo_measurement - 4bytes
agc_gain - 8bytes (1 bytes for each chain)
rx_start_ts - 4bytes

CFR payload:

CFR payload contains 8bytes of ucode header followed by the
tone information. Tone order is positive tones, followed by
PHY memory garbage, followed by negative tones. Dummy tones
are uploaded to make number of tones always integer number
of 64. Number of tones is not preamble type dependent.

Each CFR tone has 14-bit I component and 14-bit Q component
and is sign extended to 16-bit I/Q. Two tones are packed
into one 64-bit unit as:

[63:0] = [Tone1_Q(63:48) Tone1_I(47:32) Tone0_Q(31:16) Tone0_I(15:0)]

CFR tail: end magic number 0xBEAFDEAD

Tested-on: IPQ8074 WLAN.HK.2.5.0.1-00991-QCAHKSWPL_SILICONZ-1

Venkateswara Naralasetty (6):
nl80211: vendor-cmd: qca: add command for CFR configuration
ath11k: CFR module initialization and deinitialization for IPQ8074
ath11k: register relayfs entries for CFR dump
ath11k: register vendor sub command for CFR configuration
ath11k: Register DBR event handler for CFR data
ath11k: Register handler for CFR capture event

v2:
* Fixed warnings reported by kernel test robot.

drivers/net/wireless/ath/ath11k/Kconfig | 9 +
drivers/net/wireless/ath/ath11k/Makefile | 4 +-
drivers/net/wireless/ath/ath11k/cfr.c | 817 +++++++++++++++++++++++++++++++
drivers/net/wireless/ath/ath11k/cfr.h | 304 ++++++++++++
drivers/net/wireless/ath/ath11k/core.c | 35 ++
drivers/net/wireless/ath/ath11k/core.h | 17 +
drivers/net/wireless/ath/ath11k/dbring.c | 33 +-
drivers/net/wireless/ath/ath11k/dbring.h | 7 +
drivers/net/wireless/ath/ath11k/debug.h | 2 +
drivers/net/wireless/ath/ath11k/hal.c | 3 +-
drivers/net/wireless/ath/ath11k/hw.h | 5 +
drivers/net/wireless/ath/ath11k/mac.c | 18 +-
drivers/net/wireless/ath/ath11k/vendor.c | 192 ++++++++
drivers/net/wireless/ath/ath11k/vendor.h | 13 +
drivers/net/wireless/ath/ath11k/wmi.c | 148 +++++-
drivers/net/wireless/ath/ath11k/wmi.h | 97 +++-
include/uapi/linux/nl80211-vnd-qca.h | 104 ++++
17 files changed, 1792 insertions(+), 16 deletions(-)
create mode 100644 drivers/net/wireless/ath/ath11k/cfr.c
create mode 100644 drivers/net/wireless/ath/ath11k/cfr.h
create mode 100644 drivers/net/wireless/ath/ath11k/vendor.c
create mode 100644 drivers/net/wireless/ath/ath11k/vendor.h
create mode 100644 include/uapi/linux/nl80211-vnd-qca.h

--
2.7.4


Subject: [PATCHv2 1/6] nl80211: vendor-cmd: qca: add command for CFR configuration

Channel Frequency Response feature is to get the channel state
information from the hardware based on the user configurations
and send the CSI data to user space.

CSI data is further processed in user space which can be used to
identify the motion of the objects.

Add vendor command support to configure per peer CFR parameters.

An example of usage:
iw dev wlanx vendor send 0x1374 0xad cfr-en <val> bw <bw> method <method>
periodicity <period> addr <mac_addr>

0x1374: vendor id
0xad: : vendor subcmd id
val: 0 to disable CFR capture
1 to enable CFR capture

bw: CFR capture bandwidth(use the values in enum nl80211_chan_width)
1 - 20MHZ
2 - 40MHZ
3 - 80MHZ

method: Method used by hardware to collect the CFR dump
0 - from the ACKs of QOS NULL packets
1 - from the ACKs of QOS NULL packets with phase
2 - from the ACK of probe response packet

periodicity: Periodicity in ms at which CFR dump need to be collect
0 - single shot capture
non zero - for Periodic captures

mac_addr: mac address of the peer for which CFR capture is requested.

Signed-off-by: Venkateswara Naralasetty <[email protected]>
---
v2:
* Updated the commit log.

include/uapi/linux/nl80211-vnd-qca.h | 104 +++++++++++++++++++++++++++++++++++
1 file changed, 104 insertions(+)
create mode 100644 include/uapi/linux/nl80211-vnd-qca.h

diff --git a/include/uapi/linux/nl80211-vnd-qca.h b/include/uapi/linux/nl80211-vnd-qca.h
new file mode 100644
index 0000000..3c1158d
--- /dev/null
+++ b/include/uapi/linux/nl80211-vnd-qca.h
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Qualcomm Atheros OUI and vendor specific assignments
+ * Copyright (c) 2014-2017, Qualcomm Atheros, Inc.
+ * Copyright (c) 2018-2020, The Linux Foundation
+ * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights
+ */
+
+#ifndef _UAPI_NL80211_VND_QCA_H
+#define _UAPI_NL80211_VND_QCA_H
+
+
+/* Vendor id to be used in vendor specific command and events to user space
+ * NOTE: The authoritative place for definition of QCA_NL80211_VENDOR_ID,
+ * vendor subcmd definitions prefixed with QCA_NL80211_VENDOR_SUBCMD, and
+ * qca_wlan_vendor_attr is open source file src/common/qca-vendor.h in
+ * git://w1.fi/srv/git/hostap.git; the values here are just a copy of that
+ */
+#define OUI_QCA 0x001374
+
+/**
+ * enum qca_nl80211_vendor_subcmds - QCA nl80211 vendor command identifiers
+ * @QCA_NL80211_VENDOR_SUBCMD_PEER_CFR_CAPTURE_CFG: This command is used to
+ * configure parameters per peer to capture Channel Frequency Response
+ * (CFR) and enable Periodic CFR capture. The attributes for this command
+ * are defined in enum qca_wlan_vendor_peer_cfr_capture_attr. This command
+ * can also be used to send CFR data from the driver to userspace when
+ * netlink events are used to send CFR data.
+ *
+ */
+enum qca_nl80211_vendor_subcmds {
+ QCA_NL80211_VENDOR_SUBCMD_PEER_CFR_CAPTURE_CFG = 173,
+};
+
+/**
+ * enum qca_wlan_vendor_cfr_method - QCA vendor CFR methods used by
+ * attribute QCA_WLAN_VENDOR_ATTR_PEER_CFR_METHOD as part of vendor
+ * command QCA_NL80211_VENDOR_SUBCMD_PEER_CFR_CAPTURE_CFG.
+ * @QCA_WLAN_VENDOR_CFR_METHOD_QOS_NULL: CFR method using QoS Null frame
+ * @QCA_WLAN_VENDOR_CFR_QOS_NULL_WITH_PHASE: CFR method using QoS Null frame
+ * with phase
+ * @QCA_WLAN_VENDOR_CFR_PROBE_RESPONSE: CFR method using Probe Response frame
+ */
+enum qca_wlan_vendor_cfr_method {
+ QCA_WLAN_VENDOR_CFR_METHOD_QOS_NULL = 0,
+ QCA_WLAN_VENDOR_CFR_QOS_NULL_WITH_PHASE = 1,
+ QCA_WLAN_VENDOR_CFR_PROBE_RESPONSE = 2,
+};
+
+/**
+ * enum qca_wlan_vendor_peer_cfr_capture_attr - Used by the vendor command
+ * QCA_NL80211_VENDOR_SUBCMD_PEER_CFR_CAPTURE_CFG to configure peer
+ * Channel Frequency Response capture parameters and enable periodic CFR
+ * capture.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_CFR_PEER_MAC_ADDR: Optional (6-byte MAC address)
+ * MAC address of peer. This is for CFR version 1 only.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_PEER_CFR_ENABLE: Required (flag)
+ * Enable peer CFR capture. This attribute is mandatory to enable peer CFR
+ * capture. If this attribute is not present, peer CFR capture is disabled.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_PEER_CFR_BANDWIDTH: Optional (u8)
+ * BW of measurement, attribute uses the values in enum nl80211_chan_width
+ * Supported values: 20, 40, 80, 80+80, 160.
+ * Note that all targets may not support all bandwidths.
+ * This attribute is mandatory for version 1 if attribute
+ * QCA_WLAN_VENDOR_ATTR_PEER_CFR_ENABLE is used.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_PEER_CFR_PERIODICITY: Optional (u32)
+ * Periodicity of CFR measurement in milliseconds.
+ * Periodicity should be a multiple of Base timer.
+ * Current Base timer value supported is 10 milliseconds (default).
+ * 0 for one shot capture.
+ * This attribute is mandatory for version 1 if attribute
+ * QCA_WLAN_VENDOR_ATTR_PEER_CFR_ENABLE is used.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_PEER_CFR_METHOD: Optional (u8)
+ * Method used to capture Channel Frequency Response.
+ * Attribute uses the values defined in enum qca_wlan_vendor_cfr_method.
+ * This attribute is mandatory for version 1 if attribute
+ * QCA_WLAN_VENDOR_ATTR_PEER_CFR_ENABLE is used.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_PERIODIC_CFR_CAPTURE_ENABLE: Optional (flag)
+ * Enable periodic CFR capture.
+ * This attribute is mandatory for version 1 to enable Periodic CFR capture.
+ * If this attribute is not present, periodic CFR capture is disabled.
+ */
+enum qca_wlan_vendor_peer_cfr_capture_attr {
+ QCA_WLAN_VENDOR_ATTR_PEER_CFR_CAPTURE_INVALID = 0,
+ QCA_WLAN_VENDOR_ATTR_CFR_PEER_MAC_ADDR = 1,
+ QCA_WLAN_VENDOR_ATTR_PEER_CFR_ENABLE = 2,
+ QCA_WLAN_VENDOR_ATTR_PEER_CFR_BANDWIDTH = 3,
+ QCA_WLAN_VENDOR_ATTR_PEER_CFR_PERIODICITY = 4,
+ QCA_WLAN_VENDOR_ATTR_PEER_CFR_METHOD = 5,
+ QCA_WLAN_VENDOR_ATTR_PERIODIC_CFR_CAPTURE_ENABLE = 6,
+
+ /* Keep last */
+ QCA_WLAN_VENDOR_ATTR_PEER_CFR_AFTER_LAST,
+ QCA_WLAN_VENDOR_ATTR_PEER_CFR_MAX =
+ QCA_WLAN_VENDOR_ATTR_PEER_CFR_AFTER_LAST - 1,
+};
+
+#endif /* QCA_VENDOR_H */
--
2.7.4

Subject: [PATCHv2 2/6] ath11k: CFR module initialization and deinitialization for IPQ8074

Add initialization and deinitialization sequence for CFR module.
CFR module will be initialized only when the following criteria passes,
* Enabled CFR support for the hardware through the
hardware param 'cfr_support'
* WMI service enabled for the CFR support
'WMI_TLV_SERVICE_CFR_CAPTURE_SUPPORT'

Also, provide a configuration option CONFIG_ATH11K_CFR to enable
CFR feature support during the compilation time.

CFR module initialization includes Direct Buffer(DB) ring initialization
where HW uses the DB ring buffers to copy CFR data to host.
Number of buffers and buffer size of the ring is based on the DB ring
capabilities advertised by the firmware through WMI service ready.
Also ring configurations are sent to firmware through
ath11k_dbring_wmi_cfg_setup().

Tested-on: IPQ8074 WLAN.HK.2.5.0.1-00991-QCAHKSWPL_SILICONZ-1

Signed-off-by: Venkateswara Naralasetty <[email protected]>
---
v2:
* Fixed warnings reported by kernel test robot.

drivers/net/wireless/ath/ath11k/Kconfig | 9 ++
drivers/net/wireless/ath/ath11k/Makefile | 1 +
drivers/net/wireless/ath/ath11k/cfr.c | 161 +++++++++++++++++++++++++++++++
drivers/net/wireless/ath/ath11k/cfr.h | 91 +++++++++++++++++
drivers/net/wireless/ath/ath11k/core.c | 35 +++++++
drivers/net/wireless/ath/ath11k/core.h | 6 ++
drivers/net/wireless/ath/ath11k/dbring.c | 23 +++--
drivers/net/wireless/ath/ath11k/dbring.h | 5 +
drivers/net/wireless/ath/ath11k/hal.c | 3 +-
drivers/net/wireless/ath/ath11k/hw.h | 5 +
drivers/net/wireless/ath/ath11k/wmi.h | 2 +
11 files changed, 332 insertions(+), 9 deletions(-)
create mode 100644 drivers/net/wireless/ath/ath11k/cfr.c
create mode 100644 drivers/net/wireless/ath/ath11k/cfr.h

diff --git a/drivers/net/wireless/ath/ath11k/Kconfig b/drivers/net/wireless/ath/ath11k/Kconfig
index ad5cc6c..0466d58 100644
--- a/drivers/net/wireless/ath/ath11k/Kconfig
+++ b/drivers/net/wireless/ath/ath11k/Kconfig
@@ -57,3 +57,12 @@ config ATH11K_SPECTRAL
Enable ath11k spectral scan support

Say Y to enable access to the FFT/spectral data via debugfs.
+
+config ATH11K_CFR
+ bool "QCA ath11k CFR support"
+ depends on ATH11K_DEBUGFS
+ depends on RELAY
+ help
+ Enable ath11k CFR support
+
+ Say Y to enable CFR data dump collection via debugfs.
diff --git a/drivers/net/wireless/ath/ath11k/Makefile b/drivers/net/wireless/ath/ath11k/Makefile
index c1fce41..36ffd2e 100644
--- a/drivers/net/wireless/ath/ath11k/Makefile
+++ b/drivers/net/wireless/ath/ath11k/Makefile
@@ -24,6 +24,7 @@ ath11k-$(CONFIG_NL80211_TESTMODE) += testmode.o
ath11k-$(CONFIG_ATH11K_TRACING) += trace.o
ath11k-$(CONFIG_THERMAL) += thermal.o
ath11k-$(CONFIG_ATH11K_SPECTRAL) += spectral.o
+ath11k-$(CONFIG_ATH11K_CFR) += cfr.o

obj-$(CONFIG_ATH11K_AHB) += ath11k_ahb.o
ath11k_ahb-y += ahb.o
diff --git a/drivers/net/wireless/ath/ath11k/cfr.c b/drivers/net/wireless/ath/ath11k/cfr.c
new file mode 100644
index 0000000..3d20082
--- /dev/null
+++ b/drivers/net/wireless/ath/ath11k/cfr.c
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: BSD-3-Clause-Clear
+/*
+ * Copyright (c) 2020-2021 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights
+ */
+
+#include <linux/relay.h>
+#include "core.h"
+#include "debug.h"
+
+static int ath11k_cfr_process_data(struct ath11k *ar,
+ struct ath11k_dbring_data *param)
+{
+ return 0;
+}
+
+void ath11k_cfr_lut_update_paddr(struct ath11k *ar, dma_addr_t paddr,
+ u32 buf_id)
+{
+ struct ath11k_cfr *cfr = &ar->cfr;
+ struct ath11k_look_up_table *lut;
+
+ if (cfr->lut) {
+ lut = &cfr->lut[buf_id];
+ lut->dbr_address = paddr;
+ }
+}
+
+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,
+ 1, db_cap->min_elem);
+ if (ret) {
+ ath11k_warn(ar->ab, "failed to setup db ring\n");
+ 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\n");
+ 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\n");
+ 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 *ar;
+ struct ath11k_cfr *cfr;
+ 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;
+
+ ath11k_cfr_ring_free(ar);
+
+ spin_lock_bh(&cfr->lut_lock);
+ kfree(cfr->lut);
+ cfr->lut = NULL;
+ spin_unlock_bh(&cfr->lut_lock);
+ ar->cfr_enabled = false;
+ }
+}
+
+int ath11k_cfr_init(struct ath11k_base *ab)
+{
+ struct ath11k *ar;
+ struct ath11k_cfr *cfr;
+ struct ath11k_dbring_cap db_cap;
+ u32 num_lut_entries;
+ int ret = 0;
+ int i;
+
+ if (!test_bit(WMI_TLV_SERVICE_CFR_CAPTURE_SUPPORT, ab->wmi_ab.svc_map) ||
+ !ab->hw_params.cfr_support)
+ return ret;
+
+ 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 = kcalloc(num_lut_entries, sizeof(*cfr->lut),
+ GFP_KERNEL);
+
+ if (!cfr->lut) {
+ ath11k_warn(ab, "failed to allocate lut for pdev %d\n", i);
+ return -ENOMEM;
+ }
+
+ ret = ath11k_cfr_ring_alloc(ar, &db_cap);
+ if (ret) {
+ ath11k_warn(ab, "failed to init cfr ring for pdev %d\n", i);
+ goto deinit;
+ }
+
+ cfr->lut_num = num_lut_entries;
+
+ ret = ath11k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_PER_PEER_CFR_ENABLE,
+ 1, ar->pdev->pdev_id);
+ if (ret) {
+ ath11k_warn(ab, "failed to enable cfr capture on pdev %d ret %d\n",
+ i, ret);
+ goto deinit;
+ }
+
+ ar->cfr_enabled = true;
+ }
+
+ return 0;
+
+deinit:
+ ath11k_cfr_deinit(ab);
+ return ret;
+}
diff --git a/drivers/net/wireless/ath/ath11k/cfr.h b/drivers/net/wireless/ath/ath11k/cfr.h
new file mode 100644
index 0000000..c050f03
--- /dev/null
+++ b/drivers/net/wireless/ath/ath11k/cfr.h
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: BSD-3-Clause-Clear */
+/*
+ * Copyright (c) 2020-2021 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights
+ */
+
+#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 CFR_MAX_LUT_ENTRIES 136
+
+#define HOST_MAX_CHAINS 8
+
+struct ath11k_cfir_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;
+ u32 tx_address1;
+ u32 tx_address2;
+ struct ath11k_cfir_dma_hdr hdr;
+ u64 txrx_tstamp;
+ u64 dbr_tstamp;
+ u32 header_length;
+ u32 payload_length;
+ struct ath11k_dbring_element *buff;
+};
+
+struct ath11k_cfr {
+ struct ath11k_dbring rx_ring;
+ /* Protects cfr data */
+ spinlock_t lock;
+ struct ath11k_look_up_table *lut;
+ u32 lut_num;
+ u32 dbr_buf_size;
+ u32 dbr_num_bufs;
+ u32 max_mu_users;
+ /* Protect for lut entries */
+ spinlock_t lut_lock;
+ u64 tx_evt_cnt;
+ u64 dbr_evt_cnt;
+ u64 total_tx_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 invalid_dma_length_cnt;
+ u64 clear_txrx_event;
+ u64 cfr_dma_aborts;
+ u64 flush_timeout_dbr_cnt;
+};
+
+#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);
+#else
+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)
+{
+}
+#endif /* CONFIG_ATH11K_CFR */
+#endif /* ATH11K_CFR_H */
diff --git a/drivers/net/wireless/ath/ath11k/core.c b/drivers/net/wireless/ath/ath11k/core.c
index 7c508e9..0d75e88 100644
--- a/drivers/net/wireless/ath/ath11k/core.c
+++ b/drivers/net/wireless/ath/ath11k/core.c
@@ -99,6 +99,11 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.supports_rssi_stats = false,
.fw_wmi_diag_event = false,
.current_cc_support = false,
+ .cfr_support = true,
+ .cfr_dma_hdr_size = sizeof(struct ath11k_cfir_dma_hdr),
+ .cfr_num_stream_bufs = 255,
+ /* csi_cfr_header + cfr header + max cfr payload */
+ .cfr_stream_buf_size = 8200,
},
{
.hw_rev = ATH11K_HW_IPQ6018_HW10,
@@ -164,6 +169,10 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.supports_rssi_stats = false,
.fw_wmi_diag_event = false,
.current_cc_support = false,
+ .cfr_support = false,
+ .cfr_dma_hdr_size = 0,
+ .cfr_num_stream_bufs = 0,
+ .cfr_stream_buf_size = 0,
},
{
.name = "qca6390 hw2.0",
@@ -228,6 +237,10 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.supports_rssi_stats = true,
.fw_wmi_diag_event = true,
.current_cc_support = true,
+ .cfr_support = false,
+ .cfr_dma_hdr_size = 0,
+ .cfr_num_stream_bufs = 0,
+ .cfr_stream_buf_size = 0,
},
{
.name = "qcn9074 hw1.0",
@@ -292,6 +305,10 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.supports_rssi_stats = false,
.fw_wmi_diag_event = false,
.current_cc_support = false,
+ .cfr_support = false,
+ .cfr_dma_hdr_size = 0,
+ .cfr_num_stream_bufs = 0,
+ .cfr_stream_buf_size = 0,
},
{
.name = "wcn6855 hw2.0",
@@ -356,6 +373,10 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.supports_rssi_stats = true,
.fw_wmi_diag_event = true,
.current_cc_support = true,
+ .cfr_support = false,
+ .cfr_dma_hdr_size = 0,
+ .cfr_num_stream_bufs = 0,
+ .cfr_stream_buf_size = 0,
},
{
.name = "wcn6855 hw2.1",
@@ -419,6 +440,10 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.supports_rssi_stats = true,
.fw_wmi_diag_event = true,
.current_cc_support = true,
+ .cfr_support = false,
+ .cfr_dma_hdr_size = 0,
+ .cfr_num_stream_bufs = 0,
+ .cfr_stream_buf_size = 0,
},
};

@@ -922,8 +947,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_dp_pdev_free:
@@ -938,6 +971,7 @@ static int ath11k_core_pdev_create(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);
@@ -1161,6 +1195,7 @@ static int ath11k_core_reconfigure_on_crash(struct ath11k_base *ab)
ath11k_thermal_unregister(ab);
ath11k_hif_irq_disable(ab);
ath11k_dp_pdev_free(ab);
+ ath11k_cfr_deinit(ab);
ath11k_spectral_deinit(ab);
ath11k_hif_stop(ab);
ath11k_wmi_detach(ab);
diff --git a/drivers/net/wireless/ath/ath11k/core.h b/drivers/net/wireless/ath/ath11k/core.h
index 10846e9..3cf026c 100644
--- a/drivers/net/wireless/ath/ath11k/core.h
+++ b/drivers/net/wireless/ath/ath11k/core.h
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: BSD-3-Clause-Clear */
/*
* Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights
*/

#ifndef ATH11K_CORE_H
@@ -23,6 +24,7 @@
#include "thermal.h"
#include "dbring.h"
#include "spectral.h"
+#include "cfr.h"

#define SM(_v, _f) (((_v) << _f##_LSB) & _f##_MASK)

@@ -612,6 +614,10 @@ struct ath11k {
bool regdom_set_by_user;
int hw_rate_code;
u8 twt_enabled;
+#ifdef CONFIG_ATH11K_CFR
+ struct ath11k_cfr cfr;
+#endif
+ bool cfr_enabled;
};

struct ath11k_band_cap {
diff --git a/drivers/net/wireless/ath/ath11k/dbring.c b/drivers/net/wireless/ath/ath11k/dbring.c
index eda67eb..1dea4a5 100644
--- a/drivers/net/wireless/ath/ath11k/dbring.c
+++ b/drivers/net/wireless/ath/ath11k/dbring.c
@@ -1,6 +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
*/

#include "core.h"
@@ -35,9 +36,10 @@ static void ath11k_dbring_fill_magic_value(struct ath11k *ar,
*temp++ = ATH11K_DB_MAGIC_VALUE;
}

-static int ath11k_dbring_bufs_replenish(struct ath11k *ar,
- struct ath11k_dbring *ring,
- struct ath11k_dbring_element *buff)
+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;
@@ -77,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) |
@@ -101,7 +106,8 @@ static int ath11k_dbring_bufs_replenish(struct ath11k *ar,
}

static int ath11k_dbring_fill_bufs(struct ath11k *ar,
- struct ath11k_dbring *ring)
+ struct ath11k_dbring *ring,
+ enum wmi_direct_buffer_module id)
{
struct ath11k_dbring_element *buff;
struct hal_srng *srng;
@@ -129,7 +135,7 @@ static int ath11k_dbring_fill_bufs(struct ath11k *ar,
kfree(buff);
break;
}
- ret = ath11k_dbring_bufs_replenish(ar, ring, buff);
+ ret = ath11k_dbring_bufs_replenish(ar, ring, buff, id);
if (ret) {
ath11k_warn(ar->ab, "failed to replenish db ring num_remain %d req_ent %d\n",
num_remain, req_entries);
@@ -210,7 +216,7 @@ int ath11k_dbring_buf_setup(struct ath11k *ar,
ring->hp_addr = ath11k_hal_srng_get_hp_addr(ar->ab, srng);
ring->tp_addr = ath11k_hal_srng_get_tp_addr(ar->ab, srng);

- ret = ath11k_dbring_fill_bufs(ar, ring);
+ ret = ath11k_dbring_fill_bufs(ar, ring, db_cap->id);

return ret;
}
@@ -270,7 +276,7 @@ int ath11k_dbring_buffer_release_event(struct ath11k_base *ab,
struct ath11k_buffer_addr desc;
u8 *vaddr_unalign;
u32 num_entry, num_buff_reaped;
- u8 pdev_idx, rbm;
+ u8 pdev_idx, rbm, module_id;
u32 cookie;
int buf_id;
int size;
@@ -278,6 +284,7 @@ int ath11k_dbring_buffer_release_event(struct ath11k_base *ab,
int ret = 0;

pdev_idx = ev->fixed.pdev_id;
+ module_id = ev->fixed.module_id;

if (pdev_idx >= ab->num_radios) {
ath11k_warn(ab, "Invalid pdev id %d\n", pdev_idx);
@@ -357,7 +364,7 @@ int ath11k_dbring_buffer_release_event(struct ath11k_base *ab,

buff->paddr = 0;
memset(buff->payload, 0, size);
- ath11k_dbring_bufs_replenish(ar, ring, buff);
+ ath11k_dbring_bufs_replenish(ar, ring, buff, module_id);
}

spin_unlock_bh(&srng->lock);
diff --git a/drivers/net/wireless/ath/ath11k/dbring.h b/drivers/net/wireless/ath/ath11k/dbring.h
index ef906c6..0c3683a 100644
--- a/drivers/net/wireless/ath/ath11k/dbring.h
+++ b/drivers/net/wireless/ath/ath11k/dbring.h
@@ -1,6 +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
*/

#ifndef ATH11K_DBRING_H
@@ -60,6 +61,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/drivers/net/wireless/ath/ath11k/hal.c b/drivers/net/wireless/ath/ath11k/hal.c
index 2ec09ae..d2f0472 100644
--- a/drivers/net/wireless/ath/ath11k/hal.c
+++ b/drivers/net/wireless/ath/ath11k/hal.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear
/*
* Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights
*/
#include <linux/dma-mapping.h>
#include "hal_tx.h"
@@ -181,7 +182,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/drivers/net/wireless/ath/ath11k/hw.h b/drivers/net/wireless/ath/ath11k/hw.h
index c10e1a0..610c868 100644
--- a/drivers/net/wireless/ath/ath11k/hw.h
+++ b/drivers/net/wireless/ath/ath11k/hw.h
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: BSD-3-Clause-Clear */
/*
* Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights
*/

#ifndef ATH11K_HW_H
@@ -193,6 +194,10 @@ struct ath11k_hw_params {
bool supports_rssi_stats;
bool fw_wmi_diag_event;
bool current_cc_support;
+ bool cfr_support;
+ u32 cfr_dma_hdr_size;
+ u32 cfr_num_stream_bufs;
+ u32 cfr_stream_buf_size;
};

struct ath11k_hw_ops {
diff --git a/drivers/net/wireless/ath/ath11k/wmi.h b/drivers/net/wireless/ath/ath11k/wmi.h
index 587f423..72e2e20 100644
--- a/drivers/net/wireless/ath/ath11k/wmi.h
+++ b/drivers/net/wireless/ath/ath11k/wmi.h
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: BSD-3-Clause-Clear */
/*
* Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights
*/

#ifndef ATH11K_WMI_H
@@ -951,6 +952,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,
--
2.7.4

Subject: [PATCHv2 3/6] ath11k: register relayfs entries for CFR dump

Provide a relayfs interface to collect the CFR dump from
the user space.

'/sys/kernel/debug/ieee80211/phyX/ath11k/cfr_capture' is exposed
to user space to get CFR data.

CFR format to user space:
___________________________________________
| CFR header | CFR payload | CFR tail data |
|____________|_____________|_______________|

CFR header contains the following fields,

* Start magic number 0xDEADBEAF - 4bytes
* vendor id - 4bytes
* cfr metadata version - 1byte
* cfr data version - 1byte
* chip type - 1byte
* platform type - 1byte
* CFR metadata length - 4bytes
* metadata - 92bytes
peer mac - 6bytes
capture status - 1byte (1 for success 0 for failure)
capture_bw - 1byte
channel_bw - 1byte
phy_mode - 1byte
prim20_chan - 2bytes
center_freq1 - 2bytes
center_freq2 - 2bytes
capture_mode - 1byte
capture_type - 1byte
sts_count - 1byte
num_rx_chain - 1byte
timestamp - 4bytes
length - 4bytes
chain_rssi - 32bytes (4bytes for each chain)
chain_phase - 16bytes (2bytes for each chain)
cfo_measurement - 4bytes
agc_gain - 8bytes (1 bytes for each chain)
rx_start_ts - 4bytes

CFR payload:

CFR payload contains 8bytes of ucode header followed by the
tone information. Tone order is positive tones, followed by
PHY memory garbage, followed by negative tones. Dummy tones
are uploaded to make number of tones always integer number
of 64. Number of tones is not preamble type dependent.

Each CFR tone has 14-bit I component and 14-bit Q component
and is sign extended to 16-bit I/Q. Two tones are packed
into one 64-bit unit as:

[63:0] = [Tone1_Q(63:48) Tone1_I(47:32) Tone0_Q(31:16) Tone0_I(15:0)]

CFR tail: end magic number 0xBEAFDEAD

Tested-on: IPQ8074 WLAN.HK.2.5.0.1-00991-QCAHKSWPL_SILICONZ-1

Signed-off-by: Venkateswara Naralasetty <[email protected]>
---
drivers/net/wireless/ath/ath11k/cfr.c | 43 +++++++++++++++++++++++++++++++++++
drivers/net/wireless/ath/ath11k/cfr.h | 1 +
2 files changed, 44 insertions(+)

diff --git a/drivers/net/wireless/ath/ath11k/cfr.c b/drivers/net/wireless/ath/ath11k/cfr.c
index 3d20082..4cf9fe3 100644
--- a/drivers/net/wireless/ath/ath11k/cfr.c
+++ b/drivers/net/wireless/ath/ath11k/cfr.c
@@ -14,6 +14,32 @@ static int ath11k_cfr_process_data(struct ath11k *ar,
return 0;
}

+static struct dentry *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 remove_buf_file_handler(struct dentry *dentry)
+{
+ debugfs_remove(dentry);
+
+ return 0;
+}
+
+static const struct rchan_callbacks rfs_cfr_capture_cb = {
+ .create_buf_file = create_buf_file_handler,
+ .remove_buf_file = remove_buf_file_handler,
+};
+
void ath11k_cfr_lut_update_paddr(struct ath11k *ar, dma_addr_t paddr,
u32 buf_id)
{
@@ -87,6 +113,11 @@ void ath11k_cfr_deinit(struct ath11k_base *ab)
ar = ab->pdevs[i].ar;
cfr = &ar->cfr;

+ if (ar->cfr.rfs_cfr_capture) {
+ relay_close(ar->cfr.rfs_cfr_capture);
+ ar->cfr.rfs_cfr_capture = NULL;
+ }
+
ath11k_cfr_ring_free(ar);

spin_lock_bh(&cfr->lut_lock);
@@ -151,6 +182,18 @@ int ath11k_cfr_init(struct ath11k_base *ab)
}

ar->cfr_enabled = true;
+
+ 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);
+ if (!ar->cfr.rfs_cfr_capture) {
+ ath11k_warn(ar->ab, "failed to open relay for cfr in pdev %d\n",
+ ar->pdev_idx);
+ return -EINVAL;
+ }
}

return 0;
diff --git a/drivers/net/wireless/ath/ath11k/cfr.h b/drivers/net/wireless/ath/ath11k/cfr.h
index c050f03..f39b82c 100644
--- a/drivers/net/wireless/ath/ath11k/cfr.h
+++ b/drivers/net/wireless/ath/ath11k/cfr.h
@@ -46,6 +46,7 @@ struct ath11k_cfr {
struct ath11k_dbring rx_ring;
/* Protects cfr data */
spinlock_t lock;
+ struct rchan *rfs_cfr_capture;
struct ath11k_look_up_table *lut;
u32 lut_num;
u32 dbr_buf_size;
--
2.7.4

Subject: [PATCHv2 6/6] ath11k: Register handler for CFR capture event

Firmware sends CFR meta data through the WMI event
WMI_PEER_CFR_CAPTURE_EVENT. Parse the meta data coming from the
firmware and invoke correlate_and_relay function to correlate the
CFR meta data with the CFR payload coming from the other WMI event
WMI_PDEV_DMA_RING_BUF_RELEASE_EVENT.

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

Tested-on: IPQ8074 WLAN.HK.2.5.0.1-00991-QCAHKSWPL_SILICONZ-1

Signed-off-by: Venkateswara Naralasetty <[email protected]>
---
drivers/net/wireless/ath/ath11k/cfr.c | 152 ++++++++++++++++++++++++++++++++++
drivers/net/wireless/ath/ath11k/cfr.h | 62 +++++++++++++-
drivers/net/wireless/ath/ath11k/wmi.c | 90 ++++++++++++++++++++
drivers/net/wireless/ath/ath11k/wmi.h | 44 ++++++++++
4 files changed, 347 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/ath/ath11k/cfr.c b/drivers/net/wireless/ath/ath11k/cfr.c
index b7dd7d3..8f98477 100644
--- a/drivers/net/wireless/ath/ath11k/cfr.c
+++ b/drivers/net/wireless/ath/ath11k/cfr.c
@@ -248,6 +248,158 @@ static int ath11k_cfr_process_data(struct ath11k *ar,
return status;
}

+static void ath11k_cfr_fill_hdr_info(struct ath11k *ar,
+ struct ath11k_csi_cfr_header *header,
+ struct ath11k_cfr_peer_tx_param *params)
+{
+ 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;
+ header->meta_data.phy_mode = params->phy_mode;
+ 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;
+
+ /* Currently CFR data is captured on ACK of a Qos NULL frame.
+ * For 20 MHz, ACK is Legacy and for 40/80/160, ACK is DUP Legacy.
+ */
+ 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 *ar;
+ struct ath11k_cfr *cfr;
+ struct ath11k_vif *arvif;
+ struct ath11k_look_up_table *lut = NULL;
+ struct ath11k_dbring_element *buff;
+ struct ath11k_csi_cfr_header *header;
+ dma_addr_t buf_addr;
+ u32 end_magic = ATH11K_CFR_END_MAGIC;
+ u8 tx_status;
+ int status;
+ int i;
+
+ rcu_read_lock();
+ arvif = ath11k_mac_get_arvif_by_vdev_id(ab, params->vdev_id);
+ if (!arvif) {
+ rcu_read_unlock();
+ ath11k_warn(ab, "Failed to get arvif for vdev id %d\n",
+ params->vdev_id);
+ return -ENOENT;
+ }
+
+ ar = arvif->ar;
+ cfr = &ar->cfr;
+ rcu_read_unlock();
+
+ if (WMI_CFR_CAPTURE_STATUS_PEER_PS & params->status) {
+ ath11k_dbg(ab, ATH11K_DBG_CFR,
+ "CFR capture failed as peer %pM is in powersave",
+ params->peer_mac_addr);
+ return -EINVAL;
+ }
+
+ if (!(WMI_CFR_PEER_CAPTURE_STATUS & params->status)) {
+ ath11k_dbg(ab, ATH11K_DBG_CFR,
+ "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_dbg(ab, ATH11K_DBG_CFR,
+ "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_dbg(ab, ATH11K_DBG_CFR,
+ "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->tx_address1 = params->correlation_info_1;
+ lut->tx_address2 = 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.
*/
diff --git a/drivers/net/wireless/ath/ath11k/cfr.h b/drivers/net/wireless/ath/ath11k/cfr.h
index 7ea4bb8..ca45632 100644
--- a/drivers/net/wireless/ath/ath11k/cfr.h
+++ b/drivers/net/wireless/ath/ath11k/cfr.h
@@ -24,8 +24,37 @@
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,
@@ -38,6 +67,28 @@ enum ath11k_cfr_preamble_type {
ATH11K_CFR_PREAMBLE_TYPE_VHT,
};

+struct ath11k_cfr_peer_tx_param {
+ u32 capture_method;
+ u32 vdev_id;
+ u8 peer_mac_addr[ETH_ALEN];
+ u32 primary_20mhz_chan;
+ u32 bandwidth;
+ u32 phy_mode;
+ u32 band_center_freq1;
+ u32 band_center_freq2;
+ u32 spatial_streams;
+ u32 correlation_info_1;
+ u32 correlation_info_2;
+ u32 status;
+ u32 timestamp_us;
+ u32 counter;
+ u32 chain_rssi[WMI_MAX_CHAINS];
+ u16 chain_phase[WMI_MAX_CHAINS];
+ u32 cfo_measurement;
+ u8 agc_gain[HOST_MAX_CHAINS];
+ u32 rx_start_ts;
+};
+
struct cfr_metadata {
u8 peer_addr[ETH_ALEN];
u8 status;
@@ -67,7 +118,7 @@ struct ath11k_csi_cfr_header {
u8 cfr_data_version;
u8 chip_type;
u8 platform_type;
- u32 reserved;
+ u32 cfr_metadata_len;
struct cfr_metadata meta_data;
} __packed;

@@ -183,6 +234,8 @@ int ath11k_cfr_send_peer_cfr_capture_cmd(struct ath11k *ar,
const u8 *peer_mac);
struct ath11k_dbring *ath11k_cfr_get_dbring(struct ath11k *ar);
void ath11k_cfr_release_lut_entry(struct ath11k_look_up_table *lut);
+int ath11k_process_cfr_capture_event(struct ath11k_base *ab,
+ struct ath11k_cfr_peer_tx_param *params);

#else
static inline int ath11k_cfr_init(struct ath11k_base *ab)
@@ -240,5 +293,12 @@ struct ath11k_dbring *ath11k_cfr_get_dbring(struct ath11k *ar)
{
return NULL;
}
+
+static inline
+int ath11k_process_cfr_capture_event(struct ath11k_base *ab,
+ struct ath11k_cfr_peer_tx_param *params)
+{
+ return 0;
+}
#endif /* CONFIG_ATH11K_CFR */
#endif /* ATH11K_CFR_H */
diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c
index f945d45..e273db2 100644
--- a/drivers/net/wireless/ath/ath11k/wmi.c
+++ b/drivers/net/wireless/ath/ath11k/wmi.c
@@ -7819,6 +7819,93 @@ static void ath11k_wmi_twt_add_dialog_event(struct ath11k_base *ab,
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;
@@ -7950,6 +8037,9 @@ static void ath11k_wmi_tlv_op_rx(struct ath11k_base *ab, struct sk_buff *skb)
case WMI_DIAG_EVENTID:
ath11k_wmi_diag_event(ab, skb);
break;
+ case WMI_PEER_CFR_CAPTURE_EVENTID:
+ ath11k_wmi_parse_cfr_capture_event(ab, skb);
+ break;
/* TODO: Add remaining events */
default:
ath11k_dbg(ab, ATH11K_DBG_WMI, "Unknown eventid: 0x%x\n", id);
diff --git a/drivers/net/wireless/ath/ath11k/wmi.h b/drivers/net/wireless/ath/ath11k/wmi.h
index 15357a0..1e22052 100644
--- a/drivers/net/wireless/ath/ath11k/wmi.h
+++ b/drivers/net/wireless/ath/ath11k/wmi.h
@@ -1857,6 +1857,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,
@@ -3354,6 +3356,48 @@ enum ath11k_wmi_cfr_capture_method {
WMI_CFR_CAPTURE_METHOD_MAX,
};

+#define WMI_CFR_FRAME_TX_STATUS GENMASK(1, 0)
+#define WMI_CFR_CAPTURE_STATUS_PEER_PS BIT(30)
+#define WMI_CFR_PEER_CAPTURE_STATUS BIT(31)
+
+#define WMI_CFR_CORRELATION_INFO2_BUF_ADDR_HIGH GENMASK(3, 0)
+#define WMI_CFR_CORRELATION_INFO2_PPDU_ID GENMASK(31, 16)
+
+#define WMI_CFR_CFO_MEASUREMENT_VALID BIT(0)
+#define WMI_CFR_CFO_MEASUREMENT_RAW_DATA GENMASK(14, 1)
+
+struct ath11k_wmi_cfr_peer_tx_event_param {
+ u32 capture_method;
+ u32 vdev_id;
+ struct wmi_mac_addr mac_addr;
+ u32 chan_mhz;
+ u32 bandwidth;
+ u32 phy_mode;
+ u32 band_center_freq1;
+ u32 band_center_freq2;
+ u32 sts_count;
+ u32 correlation_info_1;
+ u32 correlation_info_2;
+ u32 status;
+ u32 timestamp_us;
+ u32 counter;
+ u32 chain_rssi[WMI_MAX_CHAINS];
+ u32 cfo_measurement;
+ u32 rx_start_ts;
+} __packed;
+
+struct ath11k_wmi_cfr_peer_tx_event_phase_param {
+ u32 chain_phase[WMI_MAX_CHAINS];
+ u8 agc_gain[WMI_MAX_CHAINS];
+} __packed;
+
+enum ath11k_wmi_frame_tx_status {
+ WMI_FRAME_TX_STATUS_OK,
+ WMI_FRAME_TX_STATUS_XRETRY,
+ WMI_FRAME_TX_STATUS_DROP,
+ WMI_FRAME_TX_STATUS_FILTERED,
+};
+
struct wmi_peer_cfr_capture_conf_arg {
enum ath11k_wmi_cfr_capture_bw bw;
enum ath11k_wmi_cfr_capture_method method;
--
2.7.4

Subject: [PATCHv2 4/6] ath11k: register vendor sub command for CFR configuration

Add support to parse CFR parameters configured through
the vendor commands.

Also, send the required WMI commands to the firmware based
on the CFR configurations.

Tested-on: IPQ8074 WLAN.HK.2.5.0.1-00991-QCAHKSWPL_SILICONZ-1

Signed-off-by: Venkateswara Naralasetty <[email protected]>
---
drivers/net/wireless/ath/ath11k/Makefile | 3 +-
drivers/net/wireless/ath/ath11k/cfr.c | 227 +++++++++++++++++++++++++++++++
drivers/net/wireless/ath/ath11k/cfr.h | 72 ++++++++++
drivers/net/wireless/ath/ath11k/core.h | 11 ++
drivers/net/wireless/ath/ath11k/mac.c | 18 ++-
drivers/net/wireless/ath/ath11k/vendor.c | 192 ++++++++++++++++++++++++++
drivers/net/wireless/ath/ath11k/vendor.h | 13 ++
drivers/net/wireless/ath/ath11k/wmi.c | 58 +++++++-
drivers/net/wireless/ath/ath11k/wmi.h | 51 ++++++-
9 files changed, 639 insertions(+), 6 deletions(-)
create mode 100644 drivers/net/wireless/ath/ath11k/vendor.c
create mode 100644 drivers/net/wireless/ath/ath11k/vendor.h

diff --git a/drivers/net/wireless/ath/ath11k/Makefile b/drivers/net/wireless/ath/ath11k/Makefile
index 36ffd2e..7eb4c52 100644
--- a/drivers/net/wireless/ath/ath11k/Makefile
+++ b/drivers/net/wireless/ath/ath11k/Makefile
@@ -17,7 +17,8 @@ ath11k-y += core.o \
peer.o \
dbring.o \
hw.o \
- wow.o
+ wow.o \
+ vendor.o

ath11k-$(CONFIG_ATH11K_DEBUGFS) += debugfs.o debugfs_htt_stats.o debugfs_sta.o
ath11k-$(CONFIG_NL80211_TESTMODE) += testmode.o
diff --git a/drivers/net/wireless/ath/ath11k/cfr.c b/drivers/net/wireless/ath/ath11k/cfr.c
index 4cf9fe3..733c0fd 100644
--- a/drivers/net/wireless/ath/ath11k/cfr.c
+++ b/drivers/net/wireless/ath/ath11k/cfr.c
@@ -14,6 +14,233 @@ static int ath11k_cfr_process_data(struct ath11k *ar,
return 0;
}

+/* Helper function to check whether the given peer mac address
+ * is in unassociated peer pool or not.
+ */
+bool ath11k_cfr_peer_is_in_cfr_unassoc_pool(struct ath11k *ar, const u8 *peer_mac)
+{
+ struct ath11k_cfr *cfr = &ar->cfr;
+ struct cfr_unassoc_pool_entry *entry;
+ int i;
+
+ if (!ar->cfr_enabled)
+ return false;
+
+ spin_lock_bh(&cfr->lock);
+ for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {
+ entry = &cfr->unassoc_pool[i];
+ if (!entry->is_valid)
+ continue;
+
+ if (ether_addr_equal(peer_mac, entry->peer_mac)) {
+ spin_unlock_bh(&cfr->lock);
+ return true;
+ }
+ }
+
+ spin_unlock_bh(&cfr->lock);
+
+ return false;
+}
+
+void ath11k_cfr_update_unassoc_pool_entry(struct ath11k *ar,
+ const u8 *peer_mac)
+{
+ struct ath11k_cfr *cfr = &ar->cfr;
+ struct cfr_unassoc_pool_entry *entry;
+ int i;
+
+ spin_lock_bh(&cfr->lock);
+ for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {
+ entry = &cfr->unassoc_pool[i];
+ if (!entry->is_valid)
+ continue;
+
+ if (ether_addr_equal(peer_mac, entry->peer_mac) &&
+ entry->period == 0) {
+ memset(entry->peer_mac, 0, ETH_ALEN);
+ entry->is_valid = false;
+ cfr->cfr_enabled_peer_cnt--;
+ break;
+ }
+ }
+
+ spin_unlock_bh(&cfr->lock);
+}
+
+void ath11k_cfr_decrement_peer_count(struct ath11k *ar,
+ struct ath11k_sta *arsta)
+{
+ 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\n",
+ arsta->arvif->vdev_id, peer_mac);
+ 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 i;
+ int available_idx = -1;
+
+ spin_lock_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;
+ }
+ }
+
+ goto exit;
+ }
+
+ if (cfr->cfr_enabled_peer_cnt >= ATH11K_MAX_CFR_ENABLED_CLIENTS) {
+ ath11k_info(ar->ab, "Max cfr peer threshold reached\n");
+ goto exit;
+ }
+
+ 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++;
+ }
+
+exit:
+ spin_unlock_bh(&cfr->lock);
+}
+
static struct dentry *create_buf_file_handler(const char *filename,
struct dentry *parent,
umode_t mode,
diff --git a/drivers/net/wireless/ath/ath11k/cfr.h b/drivers/net/wireless/ath/ath11k/cfr.h
index f39b82c..e209dc0 100644
--- a/drivers/net/wireless/ath/ath11k/cfr.h
+++ b/drivers/net/wireless/ath/ath11k/cfr.h
@@ -13,10 +13,14 @@
#define ATH11K_CFR_NUM_RESP_PER_EVENT 1
#define ATH11K_CFR_EVENT_TIMEOUT_MS 1

+#define ATH11K_MAX_CFR_ENABLED_CLIENTS 10
#define CFR_MAX_LUT_ENTRIES 136

#define HOST_MAX_CHAINS 8

+struct ath11k_sta;
+struct ath11k_per_peer_cfr_capture;
+
struct ath11k_cfir_dma_hdr {
u16 info0;
u16 info1;
@@ -42,12 +46,19 @@ struct ath11k_look_up_table {
struct ath11k_dbring_element *buff;
};

+struct cfr_unassoc_pool_entry {
+ u8 peer_mac[ETH_ALEN];
+ u32 period;
+ bool is_valid;
+};
+
struct ath11k_cfr {
struct ath11k_dbring rx_ring;
/* Protects cfr data */
spinlock_t lock;
struct rchan *rfs_cfr_capture;
struct ath11k_look_up_table *lut;
+ u8 cfr_enabled_peer_cnt;
u32 lut_num;
u32 dbr_buf_size;
u32 dbr_num_bufs;
@@ -67,6 +78,21 @@ struct ath11k_cfr {
u64 clear_txrx_event;
u64 cfr_dma_aborts;
u64 flush_timeout_dbr_cnt;
+ 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
@@ -74,6 +100,20 @@ 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);
+
#else
static inline int ath11k_cfr_init(struct ath11k_base *ab)
{
@@ -88,5 +128,37 @@ 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;
+}
#endif /* CONFIG_ATH11K_CFR */
#endif /* ATH11K_CFR_H */
diff --git a/drivers/net/wireless/ath/ath11k/core.h b/drivers/net/wireless/ath/ath11k/core.h
index 3cf026c..63f3a30 100644
--- a/drivers/net/wireless/ath/ath11k/core.h
+++ b/drivers/net/wireless/ath/ath11k/core.h
@@ -25,6 +25,7 @@
#include "dbring.h"
#include "spectral.h"
#include "cfr.h"
+#include "vendor.h"

#define SM(_v, _f) (((_v) << _f##_LSB) & _f##_MASK)

@@ -375,6 +376,13 @@ struct ath11k_per_ppdu_tx_stats {
u32 retry_bytes;
};

+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;

@@ -405,6 +413,9 @@ struct ath11k_sta {

bool use_4addr_set;
u16 tcl_metadata;
+#ifdef CONFIG_ATH11K_CFR
+ struct ath11k_per_peer_cfr_capture cfr_capture;
+#endif
};

#define ATH11K_MIN_5G_FREQ 4150
diff --git a/drivers/net/wireless/ath/ath11k/mac.c b/drivers/net/wireless/ath/ath11k/mac.c
index ed89905..ee18a3b 100644
--- a/drivers/net/wireless/ath/ath11k/mac.c
+++ b/drivers/net/wireless/ath/ath11k/mac.c
@@ -4550,6 +4550,8 @@ static int ath11k_mac_op_sta_state(struct ieee80211_hw *hw,

kfree(arsta->rx_stats);
arsta->rx_stats = NULL;
+
+ ath11k_cfr_decrement_peer_count(ar, arsta);
} else if (old_state == IEEE80211_STA_AUTH &&
new_state == IEEE80211_STA_ASSOC &&
(vif->type == NL80211_IFTYPE_AP ||
@@ -5453,6 +5455,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;

@@ -5486,7 +5490,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;
@@ -8481,6 +8496,7 @@ static int __ath11k_mac_register(struct ath11k *ar)
}

ath11k_reg_init(ar);
+ ath11k_vendor_register(ar);

if (!test_bit(ATH11K_FLAG_RAW_MODE, &ab->dev_flags)) {
ar->hw->netdev_features = NETIF_F_HW_CSUM;
diff --git a/drivers/net/wireless/ath/ath11k/vendor.c b/drivers/net/wireless/ath/ath11k/vendor.c
new file mode 100644
index 0000000..d76e2ea
--- /dev/null
+++ b/drivers/net/wireless/ath/ath11k/vendor.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2021 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights
+ */
+
+#include <net/netlink.h>
+#include <net/mac80211.h>
+#include <uapi/linux/nl80211-vnd-qca.h>
+#include "core.h"
+#include "debug.h"
+#include "peer.h"
+
+static const struct nla_policy
+ath11k_vendor_cfr_config_policy[QCA_WLAN_VENDOR_ATTR_PEER_CFR_MAX + 1] = {
+ [QCA_WLAN_VENDOR_ATTR_CFR_PEER_MAC_ADDR] = NLA_POLICY_ETH_ADDR,
+ [QCA_WLAN_VENDOR_ATTR_PEER_CFR_ENABLE] = { .type = NLA_FLAG },
+ [QCA_WLAN_VENDOR_ATTR_PEER_CFR_BANDWIDTH] =
+ NLA_POLICY_RANGE(NLA_U8, 0, NL80211_CHAN_WIDTH_80),
+ [QCA_WLAN_VENDOR_ATTR_PEER_CFR_PERIODICITY] = { .type = NLA_U32},
+ NLA_POLICY_MIN(NLA_U32, 1),
+ [QCA_WLAN_VENDOR_ATTR_PEER_CFR_METHOD] =
+ NLA_POLICY_RANGE(NLA_U8, 0, QCA_WLAN_VENDOR_CFR_PROBE_RESPONSE),
+ [QCA_WLAN_VENDOR_ATTR_PERIODIC_CFR_CAPTURE_ENABLE] = { .type = NLA_FLAG },
+};
+
+static enum ath11k_cfr_capture_bw
+vendor_cfr_bw_to_ath11k_cfr_bw(enum nl80211_chan_width bw)
+{
+ switch (bw) {
+ case NL80211_CHAN_WIDTH_20:
+ return ATH11K_CFR_CAPTURE_BW_20;
+ case NL80211_CHAN_WIDTH_40:
+ return ATH11K_CFR_CAPTURE_BW_40;
+ case NL80211_CHAN_WIDTH_80:
+ return ATH11K_CFR_CAPTURE_BW_80;
+ default:
+ return ATH11K_CFR_CAPTURE_BW_MAX;
+ }
+}
+
+static enum ath11k_cfr_capture_method
+vendor_cfr_method_to_ath11k_cfr_method(enum qca_wlan_vendor_cfr_method method)
+{
+ switch (method) {
+ case QCA_WLAN_VENDOR_CFR_METHOD_QOS_NULL:
+ return ATH11K_CFR_CAPTURE_METHOD_NULL_FRAME;
+ case QCA_WLAN_VENDOR_CFR_QOS_NULL_WITH_PHASE:
+ return ATH11K_CFR_CAPTURE_METHOD_NULL_FRAME_WITH_PHASE;
+ case QCA_WLAN_VENDOR_CFR_PROBE_RESPONSE:
+ return ATH11K_CFR_CAPTURE_METHOD_PROBE_RESP;
+ default:
+ return ATH11K_CFR_CAPTURE_METHOD_MAX;
+ }
+}
+
+static int ath11k_vendor_parse_cfr_config(struct wiphy *wihpy,
+ struct wireless_dev *wdev,
+ const void *data,
+ int data_len)
+{
+ struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_PEER_CFR_MAX + 1];
+ struct ieee80211_vif *vif;
+ struct ath11k_vif *arvif;
+ struct ath11k *ar;
+ struct ath11k_peer *peer;
+ struct ath11k_sta *arsta = NULL;
+ struct ieee80211_sta *sta = NULL;
+ struct ath11k_per_peer_cfr_capture params;
+ enum qca_wlan_vendor_cfr_method method = QCA_WLAN_VENDOR_CFR_METHOD_QOS_NULL;
+ enum nl80211_chan_width bw = NL80211_CHAN_WIDTH_20;
+ enum ath11k_cfr_capture_method cfr_method;
+ enum ath11k_cfr_capture_bw cfr_bw;
+ u8 *mac_addr;
+ u32 periodicity = 0;
+ bool enable_cfr;
+ bool unassoc_peer = false;
+ int ret = 0;
+
+ if (!wdev)
+ return -EINVAL;
+
+ vif = wdev_to_ieee80211_vif(wdev);
+ if (!vif)
+ return -EINVAL;
+
+ arvif = (struct ath11k_vif *)vif->drv_priv;
+ ar = arvif->ar;
+
+ mutex_lock(&ar->conf_mutex);
+
+ ret = nla_parse(tb, QCA_WLAN_VENDOR_ATTR_PEER_CFR_MAX, data, data_len,
+ ath11k_vendor_cfr_config_policy, NULL);
+ if (ret) {
+ ath11k_warn(ar->ab, "invalid cfr config policy attribute\n");
+ goto exit;
+ }
+
+ /* MAC address is mandatory to enable/disable cfr capture*/
+ if (!tb[QCA_WLAN_VENDOR_ATTR_CFR_PEER_MAC_ADDR]) {
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ enable_cfr = nla_get_flag(tb[QCA_WLAN_VENDOR_ATTR_PEER_CFR_ENABLE]);
+ mac_addr = nla_data(tb[QCA_WLAN_VENDOR_ATTR_CFR_PEER_MAC_ADDR]);
+
+ if (enable_cfr &&
+ (!tb[QCA_WLAN_VENDOR_ATTR_PEER_CFR_BANDWIDTH] ||
+ !tb[QCA_WLAN_VENDOR_ATTR_PEER_CFR_METHOD] ||
+ !tb[QCA_WLAN_VENDOR_ATTR_PEER_CFR_PERIODICITY])) {
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ if (enable_cfr) {
+ periodicity = nla_get_u32(tb[QCA_WLAN_VENDOR_ATTR_PEER_CFR_PERIODICITY]);
+ bw = nla_get_u8(tb[QCA_WLAN_VENDOR_ATTR_PEER_CFR_BANDWIDTH]);
+ method = nla_get_u8(tb[QCA_WLAN_VENDOR_ATTR_PEER_CFR_METHOD]);
+ }
+
+ if (periodicity > WMI_PEER_CFR_PERIODICITY_MAX) {
+ ath11k_warn(ar->ab, "Invalid periodicity %u max supported %u\n",
+ periodicity, WMI_PEER_CFR_PERIODICITY_MAX);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ cfr_bw = vendor_cfr_bw_to_ath11k_cfr_bw(bw);
+ if (cfr_bw >= ATH11K_CFR_CAPTURE_BW_MAX) {
+ ath11k_warn(ar->ab, "Driver doesn't support configured bw %d\n", bw);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ cfr_method = vendor_cfr_method_to_ath11k_cfr_method(method);
+ if (cfr_method >= ATH11K_CFR_CAPTURE_METHOD_MAX) {
+ ath11k_warn(ar->ab, "Driver doesn't support configured method %d\n",
+ method);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ spin_lock_bh(&ar->ab->base_lock);
+ peer = ath11k_peer_find_by_addr(ar->ab, mac_addr);
+ if (!peer || !peer->sta) {
+ unassoc_peer = true;
+ } else {
+ sta = peer->sta;
+ arsta = (struct ath11k_sta *)sta->drv_priv;
+ }
+ spin_unlock_bh(&ar->ab->base_lock);
+
+ if (unassoc_peer && cfr_method != ATH11K_CFR_CAPTURE_METHOD_PROBE_RESP) {
+ ath11k_warn(ar->ab, "invalid capture method for an unassoc sta");
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ params.cfr_enable = enable_cfr;
+ params.cfr_period = periodicity;
+ params.cfr_bw = cfr_bw;
+ params.cfr_method = cfr_method;
+
+ if (unassoc_peer)
+ ath11k_cfr_update_unassoc_pool(ar, &params, mac_addr);
+ else
+ ret = ath11k_cfr_send_peer_cfr_capture_cmd(ar, arsta,
+ &params, mac_addr);
+exit:
+ mutex_unlock(&ar->conf_mutex);
+ return ret;
+}
+
+static struct wiphy_vendor_command ath11k_vendor_commands[] = {
+ {
+ .info.vendor_id = OUI_QCA,
+ .info.subcmd = QCA_NL80211_VENDOR_SUBCMD_PEER_CFR_CAPTURE_CFG,
+ .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_RUNNING,
+ .doit = ath11k_vendor_parse_cfr_config,
+ .policy = ath11k_vendor_cfr_config_policy,
+ .maxattr = QCA_WLAN_VENDOR_ATTR_PEER_CFR_MAX
+ }
+};
+
+int ath11k_vendor_register(struct ath11k *ar)
+{
+ ar->hw->wiphy->vendor_commands = ath11k_vendor_commands;
+ ar->hw->wiphy->n_vendor_commands = ARRAY_SIZE(ath11k_vendor_commands);
+
+ return 0;
+}
diff --git a/drivers/net/wireless/ath/ath11k/vendor.h b/drivers/net/wireless/ath/ath11k/vendor.h
new file mode 100644
index 0000000..93870ef
--- /dev/null
+++ b/drivers/net/wireless/ath/ath11k/vendor.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: BSD-3-Clause-Clear */
+/*
+ * Copyright (c) 2021 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights
+ */
+
+#ifndef ATH11K_VENDOR_H
+#define ATH11K_VENDOR_H
+
+int ath11k_vendor_register(struct ath11k *ar);
+
+#endif /* QCA_VENDOR_H */
+
diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c
index b4f86c4..f945d45 100644
--- a/drivers/net/wireless/ath/ath11k/wmi.c
+++ b/drivers/net/wireless/ath/ath11k/wmi.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear
/*
* Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights
*/
#include <linux/skbuff.h>
#include <linux/ctype.h>
@@ -621,9 +622,10 @@ 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)
{
struct ath11k_pdev_wmi *wmi = ar->wmi;
+ struct wmi_mgmt_send_params *params;
struct wmi_mgmt_send_cmd *cmd;
struct wmi_tlv *frame_tlv;
struct sk_buff *skb;
@@ -634,6 +636,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)
@@ -649,7 +653,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) |
@@ -659,6 +663,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,
@@ -3679,6 +3692,47 @@ int ath11k_wmi_fils_discovery_tmpl(struct ath11k *ar, u32 vdev_id,
return ret;
}

+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\n",
+ vdev_id);
+ 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)
{
diff --git a/drivers/net/wireless/ath/ath11k/wmi.h b/drivers/net/wireless/ath/ath11k/wmi.h
index 72e2e20..3fe195f 100644
--- a/drivers/net/wireless/ath/ath11k/wmi.h
+++ b/drivers/net/wireless/ath/ath11k/wmi.h
@@ -334,6 +334,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,
@@ -3336,6 +3340,45 @@ struct wmi_bssid_arg {
const u8 *bssid;
};

+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,
+};
+
+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 (10 * 60 * 1000)
+
struct wmi_start_scan_arg {
u32 scan_id;
u32 scan_req_id;
@@ -3704,7 +3747,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;
@@ -5559,7 +5603,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_bcn_tmpl(struct ath11k *ar, u32 vdev_id,
struct ieee80211_mutable_offsets *offs,
struct sk_buff *bcn);
@@ -5716,4 +5760,7 @@ int ath11k_wmi_scan_prob_req_oui(struct ath11k *ar,
const u8 mac_addr[ETH_ALEN]);
int ath11k_wmi_fw_dbglog_cfg(struct ath11k *ar, u32 *module_id_bitmap,
struct ath11k_fw_dbglog *dbglog);
+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
--
2.7.4

Subject: [PATCHv2 5/6] ath11k: Register DBR event handler for CFR data

Firmware sends an WMI event WMI_PDEV_DMA_RING_BUF_RELEASE_EVENT
to host to indicate the CFR data availability in the DB ring.

Host will reap the CFR data from the DB ring buffers and invoke
correlate_and_relay API to correlate the CFR data with the meta
data coming from the other WMI event WMI_PEER_CFR_CAPTURE_EVENT.

If correlate and relay function returns success then release the
buffer to user space through relayfs, otherwise hold the buffer
until other WMI event comes from the firmware.

Tested-on: IPQ8074 WLAN.HK.2.5.0.1-00991-QCAHKSWPL_SILICONZ-1

Signed-off-by: Venkateswara Naralasetty <[email protected]>
---
v2:
* Fixed warnings reported by kernel test robot.

drivers/net/wireless/ath/ath11k/cfr.c | 236 ++++++++++++++++++++++++++++++-
drivers/net/wireless/ath/ath11k/cfr.h | 80 +++++++++++
drivers/net/wireless/ath/ath11k/dbring.c | 10 +-
drivers/net/wireless/ath/ath11k/dbring.h | 2 +
drivers/net/wireless/ath/ath11k/debug.h | 2 +
drivers/net/wireless/ath/ath11k/wmi.h | 2 +-
6 files changed, 329 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/ath/ath11k/cfr.c b/drivers/net/wireless/ath/ath11k/cfr.c
index 733c0fd..b7dd7d3 100644
--- a/drivers/net/wireless/ath/ath11k/cfr.c
+++ b/drivers/net/wireless/ath/ath11k/cfr.c
@@ -8,10 +8,244 @@
#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 cfr_calculate_tones_from_dma_hdr(struct ath11k_cfir_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:
+ break;
+ }
+
+ case ATH11K_CFR_PREAMBLE_TYPE_HT:
+ switch (bw) {
+ case 0:
+ return TONES_IN_20MHZ;
+ case 1:
+ return TONES_IN_40MHZ;
+ }
+ }
+
+ 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 (!ar->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++;
+ }
+ }
+}
+
+/* Correlate and relay: This function correlate the data coming from
+ * WMI_PDEV_DMA_RING_BUF_RELEASE_EVENT(DBR event) and
+ * WMI_PEER_CFR_CAPTURE_EVENT(Tx capture event).
+ * If both the events are received and PPDU id matches from both the
+ * events, return CORRELATE_STATUS_RELEASE which means relay the
+ * correlated data to user space. Otherwise return CORRELATE_STATUS_HOLD
+ * which means wait for the second event to come.
+ *
+ * It also check for the pending DBR events and clear those events
+ * in case of corresponding TX capture event is not received for
+ * the PPDU.
+ */
+
+static enum ath11k_cfr_correlate_status
+ath11k_cfr_correlate_and_relay(struct ath11k *ar,
+ struct ath11k_look_up_table *lut,
+ u8 event_type)
+{
+ struct ath11k_cfr *cfr = &ar->cfr;
+ enum ath11k_cfr_correlate_status status;
+ 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) {
+ /* We are using 64-bit counters here. So, it may take
+ * several year to hit wraparound. Hence, not handling
+ * the wraparound condition.
+ */
+ 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 {
+ /* When there is a ppdu id mismatch, discard the TXRX
+ * event since multiple PPDUs are likely to have same
+ * dma addr, due to ucode aborts.
+ */
+
+ ath11k_dbg(ar->ab, ATH11K_DBG_CFR,
+ "Received dbr event twice for the same lut entry");
+ lut->tx_recv = false;
+ lut->tx_ppdu_id = 0;
+ cfr->clear_txrx_event++;
+ cfr->cfr_dma_aborts++;
+ status = ATH11K_CORRELATE_STATUS_HOLD;
+ }
+ } else {
+ status = ATH11K_CORRELATE_STATUS_HOLD;
+ }
+
+ return status;
+}
+
static int ath11k_cfr_process_data(struct ath11k *ar,
struct ath11k_dbring_data *param)
{
- return 0;
+ struct ath11k_base *ab = ar->ab;
+ struct ath11k_cfr *cfr = &ar->cfr;
+ struct ath11k_look_up_table *lut;
+ struct ath11k_csi_cfr_header *header;
+ struct ath11k_cfir_dma_hdr *dma_hdr;
+ u8 *data;
+ u32 end_magic = ATH11K_CFR_END_MAGIC;
+ u32 buf_id;
+ u32 tones;
+ u32 length;
+ int status;
+ u8 num_chains;
+
+ data = param->data;
+ buf_id = param->buf_id;
+
+ if (param->data_sz < sizeof(*dma_hdr))
+ return -EINVAL;
+
+ dma_hdr = (struct ath11k_cfir_dma_hdr *)data;
+
+ tones = cfr_calculate_tones_from_dma_hdr(dma_hdr);
+ if (tones == TONES_INVALID) {
+ ath11k_warn(ar->ab, "Number of tones received is invalid");
+ return -EINVAL;
+ }
+
+ num_chains = FIELD_GET(CFIR_DMA_HDR_INFO1_NUM_CHAINS,
+ dma_hdr->info1);
+
+ length = sizeof(*dma_hdr);
+ length += tones * (num_chains + 1);
+
+ spin_lock_bh(&cfr->lut_lock);
+
+ if (!cfr->lut) {
+ spin_unlock_bh(&cfr->lut_lock);
+ return -EINVAL;
+ }
+
+ lut = &cfr->lut[buf_id];
+
+ ath11k_dbg_dump(ab, ATH11K_DBG_CFR_DUMP, "data_from_buf_rel:", "",
+ data, length);
+
+ lut->buff = param->buff;
+ lut->data = data;
+ lut->data_len = length;
+ lut->dbr_ppdu_id = dma_hdr->phy_ppdu_id;
+ lut->dbr_tstamp = jiffies;
+
+ memcpy(&lut->hdr, dma_hdr, sizeof(*dma_hdr));
+
+ header = &lut->header;
+ header->meta_data.channel_bw = FIELD_GET(CFIR_DMA_HDR_INFO1_UPLOAD_PKT_BW,
+ dma_hdr->info1);
+ header->meta_data.length = length;
+
+ status = ath11k_cfr_correlate_and_relay(ar, lut,
+ ATH11K_CORRELATE_DBR_EVENT);
+ if (status == ATH11K_CORRELATE_STATUS_RELEASE) {
+ ath11k_dbg(ab, ATH11K_DBG_CFR,
+ "releasing CFR data to user space");
+ ath11k_cfr_rfs_write(ar, &lut->header,
+ sizeof(struct ath11k_csi_cfr_header),
+ lut->data, lut->data_len,
+ &end_magic, sizeof(u32));
+ ath11k_cfr_release_lut_entry(lut);
+ } else if (status == ATH11K_CORRELATE_STATUS_HOLD) {
+ ath11k_dbg(ab, ATH11K_DBG_CFR,
+ "tx event is not yet received holding the buf");
+ }
+
+ spin_unlock_bh(&cfr->lut_lock);
+
+ return status;
}

/* Helper function to check whether the given peer mac address
diff --git a/drivers/net/wireless/ath/ath11k/cfr.h b/drivers/net/wireless/ath/ath11k/cfr.h
index e209dc0..7ea4bb8 100644
--- a/drivers/net/wireless/ath/ath11k/cfr.h
+++ b/drivers/net/wireless/ath/ath11k/cfr.h
@@ -13,6 +13,9 @@
#define ATH11K_CFR_NUM_RESP_PER_EVENT 1
#define ATH11K_CFR_EVENT_TIMEOUT_MS 1

+#define ATH11K_CORRELATE_TX_EVENT 1
+#define ATH11K_CORRELATE_DBR_EVENT 0
+
#define ATH11K_MAX_CFR_ENABLED_CLIENTS 10
#define CFR_MAX_LUT_ENTRIES 136

@@ -21,6 +24,70 @@
struct ath11k_sta;
struct ath11k_per_peer_cfr_capture;

+#define ATH11K_CFR_END_MAGIC 0xBEAFDEAD
+
+enum ath11k_cfr_correlate_status {
+ ATH11K_CORRELATE_STATUS_RELEASE,
+ ATH11K_CORRELATE_STATUS_HOLD,
+ ATH11K_CORRELATE_STATUS_ERR,
+};
+
+enum ath11k_cfr_preamble_type {
+ ATH11K_CFR_PREAMBLE_TYPE_LEGACY,
+ ATH11K_CFR_PREAMBLE_TYPE_HT,
+ ATH11K_CFR_PREAMBLE_TYPE_VHT,
+};
+
+struct cfr_metadata {
+ u8 peer_addr[ETH_ALEN];
+ u8 status;
+ u8 capture_bw;
+ u8 channel_bw;
+ u8 phy_mode;
+ u16 prim20_chan;
+ u16 center_freq1;
+ u16 center_freq2;
+ u8 capture_mode;
+ u8 capture_type;
+ u8 sts_count;
+ u8 num_rx_chain;
+ u32 timestamp;
+ u32 length;
+ u32 chain_rssi[HOST_MAX_CHAINS];
+ u16 chain_phase[HOST_MAX_CHAINS];
+ u32 cfo_measurement;
+ u8 agc_gain[HOST_MAX_CHAINS];
+ u32 rx_start_ts;
+} __packed;
+
+struct ath11k_csi_cfr_header {
+ u32 start_magic_num;
+ u32 vendorid;
+ u8 cfr_metadata_version;
+ u8 cfr_data_version;
+ u8 chip_type;
+ u8 platform_type;
+ u32 reserved;
+ struct cfr_metadata meta_data;
+} __packed;
+
+#define TONES_IN_20MHZ 256
+#define TONES_IN_40MHZ 512
+#define TONES_IN_80MHZ 1024
+#define TONES_IN_160MHZ 2048 /* 160 MHz isn't supported yet */
+#define TONES_INVALID 0
+
+#define CFIR_DMA_HDR_INFO0_TAG GENMASK(7, 0)
+#define CFIR_DMA_HDR_INFO0_LEN GENMASK(13, 8)
+
+#define CFIR_DMA_HDR_INFO1_UPLOAD_DONE GENMASK(0, 0)
+#define CFIR_DMA_HDR_INFO1_CAPTURE_TYPE GENMASK(3, 1)
+#define CFIR_DMA_HDR_INFO1_PREAMBLE_TYPE GENMASK(5, 4)
+#define CFIR_DMA_HDR_INFO1_NSS GENMASK(8, 6)
+#define CFIR_DMA_HDR_INFO1_NUM_CHAINS GENMASK(11, 9)
+#define CFIR_DMA_HDR_INFO1_UPLOAD_PKT_BW GENMASK(14, 12)
+#define CFIR_DMA_HDR_INFO1_SW_PEER_ID_VALID GENMASK(15, 15)
+
struct ath11k_cfir_dma_hdr {
u16 info0;
u16 info1;
@@ -38,6 +105,7 @@ struct ath11k_look_up_table {
dma_addr_t dbr_address;
u32 tx_address1;
u32 tx_address2;
+ struct ath11k_csi_cfr_header header;
struct ath11k_cfir_dma_hdr hdr;
u64 txrx_tstamp;
u64 dbr_tstamp;
@@ -113,6 +181,8 @@ int ath11k_cfr_send_peer_cfr_capture_cmd(struct ath11k *ar,
struct ath11k_sta *arsta,
struct ath11k_per_peer_cfr_capture *params,
const u8 *peer_mac);
+struct ath11k_dbring *ath11k_cfr_get_dbring(struct ath11k *ar);
+void ath11k_cfr_release_lut_entry(struct ath11k_look_up_table *lut);

#else
static inline int ath11k_cfr_init(struct ath11k_base *ab)
@@ -160,5 +230,15 @@ ath11k_cfr_send_peer_cfr_capture_cmd(struct ath11k *ar,
{
return 0;
}
+
+static inline void ath11k_cfr_release_lut_entry(struct ath11k_look_up_table *lut)
+{
+}
+
+static inline
+struct ath11k_dbring *ath11k_cfr_get_dbring(struct ath11k *ar)
+{
+ return NULL;
+}
#endif /* CONFIG_ATH11K_CFR */
#endif /* ATH11K_CFR_H */
diff --git a/drivers/net/wireless/ath/ath11k/dbring.c b/drivers/net/wireless/ath/ath11k/dbring.c
index 1dea4a5..bcfdd0f 100644
--- a/drivers/net/wireless/ath/ath11k/dbring.c
+++ b/drivers/net/wireless/ath/ath11k/dbring.c
@@ -282,6 +282,7 @@ int ath11k_dbring_buffer_release_event(struct ath11k_base *ab,
int size;
dma_addr_t paddr;
int ret = 0;
+ int status;

pdev_idx = ev->fixed.pdev_id;
module_id = ev->fixed.module_id;
@@ -311,6 +312,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",
@@ -358,8 +362,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/drivers/net/wireless/ath/ath11k/dbring.h b/drivers/net/wireless/ath/ath11k/dbring.h
index 0c3683a..cd7d647 100644
--- a/drivers/net/wireless/ath/ath11k/dbring.h
+++ b/drivers/net/wireless/ath/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 {
diff --git a/drivers/net/wireless/ath/ath11k/debug.h b/drivers/net/wireless/ath/ath11k/debug.h
index fbbd5fe..e03b0b3 100644
--- a/drivers/net/wireless/ath/ath11k/debug.h
+++ b/drivers/net/wireless/ath/ath11k/debug.h
@@ -25,6 +25,8 @@ enum ath11k_debug_mask {
ATH11K_DBG_PCI = 0x00001000,
ATH11K_DBG_DP_TX = 0x00001000,
ATH11K_DBG_DP_RX = 0x00002000,
+ ATH11K_DBG_CFR = 0x00004000,
+ ATH11K_DBG_CFR_DUMP = 0x00008000,
ATH11K_DBG_ANY = 0xffffffff,
};

diff --git a/drivers/net/wireless/ath/ath11k/wmi.h b/drivers/net/wireless/ath/ath11k/wmi.h
index 3fe195f..15357a0 100644
--- a/drivers/net/wireless/ath/ath11k/wmi.h
+++ b/drivers/net/wireless/ath/ath11k/wmi.h
@@ -3377,7 +3377,7 @@ struct wmi_peer_cfr_capture_cmd_fixed_param {
#define WMI_PEER_CFR_CAPTURE_DISABLE 0

/*periodicity in ms */
-#define WMI_PEER_CFR_PERIODICITY_MAX (10 * 60 * 1000)
+#define WMI_PEER_CFR_PERIODICITY_MAX 600000

struct wmi_start_scan_arg {
u32 scan_id;
--
2.7.4

2022-03-11 13:21:44

by Johannes Berg

[permalink] [raw]
Subject: Re: [PATCHv2 1/6] nl80211: vendor-cmd: qca: add command for CFR configuration

On Wed, 2022-02-16 at 15:35 +0530, Venkateswara Naralasetty wrote:
> Channel Frequency Response feature is to get the channel state
> information from the hardware based on the user configurations
> and send the CSI data to user space.
>
> CSI data is further processed in user space which can be used to
> identify the motion of the objects.
>
> Add vendor command support to configure per peer CFR parameters.
>
> An example of usage:
> iw dev wlanx vendor send 0x1374 0xad cfr-en <val> bw <bw> method <method>
> periodicity <period> addr <mac_addr>
>
> 0x1374: vendor id
> 0xad: : vendor subcmd id
> val: 0 to disable CFR capture
> 1 to enable CFR capture
>
> bw: CFR capture bandwidth(use the values in enum nl80211_chan_width)
> 1 - 20MHZ
> 2 - 40MHZ
> 3 - 80MHZ
>
> method: Method used by hardware to collect the CFR dump
> 0 - from the ACKs of QOS NULL packets
> 1 - from the ACKs of QOS NULL packets with phase
> 2 - from the ACK of probe response packet
>
> periodicity: Periodicity in ms at which CFR dump need to be collect
> 0 - single shot capture
> non zero - for Periodic captures
>
> mac_addr: mac address of the peer for which CFR capture is requested.
>
> Signed-off-by: Venkateswara Naralasetty <[email protected]>
> ---
> v2:
> * Updated the commit log.
>

That's a bit better, thanks.

However, it still doesn't address the question of why it needs to be
vendor specific API.

Is this something fundamentally tied to the hardware? Fundamentally
vendor specific? I'm not sure I see why it would be?

johannes

Subject: RE: [PATCHv2 1/6] nl80211: vendor-cmd: qca: add command for CFR configuration

> -----Original Message-----
> From: Johannes Berg <[email protected]>
> Sent: Friday, March 11, 2022 4:34 PM
> To: Venkateswara Naralasetty (QUIC) <[email protected]>;
> [email protected]
> Cc: [email protected]
> Subject: Re: [PATCHv2 1/6] nl80211: vendor-cmd: qca: add command for CFR
> configuration
>
> On Wed, 2022-02-16 at 15:35 +0530, Venkateswara Naralasetty wrote:
> > Channel Frequency Response feature is to get the channel state
> > information from the hardware based on the user configurations and
> > send the CSI data to user space.
> >
> > CSI data is further processed in user space which can be used to
> > identify the motion of the objects.
> >
> > Add vendor command support to configure per peer CFR parameters.
> >
> > An example of usage:
> > iw dev wlanx vendor send 0x1374 0xad cfr-en <val> bw <bw> method
> > <method> periodicity <period> addr <mac_addr>
> >
> > 0x1374: vendor id
> > 0xad: : vendor subcmd id
> > val: 0 to disable CFR capture
> > 1 to enable CFR capture
> >
> > bw: CFR capture bandwidth(use the values in enum nl80211_chan_width)
> > 1 - 20MHZ
> > 2 - 40MHZ
> > 3 - 80MHZ
> >
> > method: Method used by hardware to collect the CFR dump
> > 0 - from the ACKs of QOS NULL packets
> > 1 - from the ACKs of QOS NULL packets with phase
> > 2 - from the ACK of probe response packet
> >
> > periodicity: Periodicity in ms at which CFR dump need to be collect
> > 0 - single shot capture
> > non zero - for Periodic captures
> >
> > mac_addr: mac address of the peer for which CFR capture is requested.
> >
> > Signed-off-by: Venkateswara Naralasetty <[email protected]>
> > ---
> > v2:
> > * Updated the commit log.
> >
>
> That's a bit better, thanks.
>
> However, it still doesn't address the question of why it needs to be vendor
> specific API.
>
> Is this something fundamentally tied to the hardware? Fundamentally
> vendor specific? I'm not sure I see why it would be?

CFR capture method can be different from HW to HW. Few HWs might not support all the methods.

>
> johannes