wlan-ap-Telecominfraproject/feeds/wifi-ax/mac80211/patches/qca/238-ath11k-add-cfr-support-for-ipq8074.patch
John Crispin 8cd26b4b50 ipq807x: update to 11.4-CS
Signed-off-by: John Crispin <john@phrozen.org>
2021-09-14 09:16:23 +02:00

2045 lines
57 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

From 5cd2da1a4d9cf177444d3633d9cfdc0a68573587 Mon Sep 17 00:00:00 2001
From: Venkateswara Naralasetty <vnaralas@codeaurora.org>
Date: Wed, 10 Feb 2021 18:17:04 +0530
Subject: [PATCH] ath11k: add CFR capture support for ipq8074
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This change is to add single shot/periodic CFR capture
support for ipq8074.
To enable/disable cfr feature use command,
echo <val> > /sys/kernel/debug/ieee80211/phyX/ath11k/enable_cfr
where, val: 0 to disable CFR and 1 to enable CFR.
To enable CFR capture for associated peers,
echo "<val> <bw> <periodicity> <method>"
> /sys/kernel/debug/ieee80211/phyX/netdev\:wlanx/stations/<mac>/cfr_capture
val: 0 - stop CFR capture
1 - start CFR capture
bw: CFR capture bandwidth
0 - 20MHZ
1 - 40MHZ
2 - 80MHZ
Periodicity: Periodicity at which hardware is expceted to collect CFR dump.
0 - single shot capture.
non zero - for Periodic captures (value should be multiple of 10).
method: Method used by hardware to collect the CFR dump.
0 - from the ACKs of QOS NULL packets.
To enable CFR capture for unassociated clients,
echo “<mac address> <val> <periodicity>”
> /sys/kernel/debug/ieee80211/phyX/ath11k/cfr_unassoc
Mac address: mac address of the client.
Val: 0 - start CFR capture
1 stop CFR capture
Periodicity: Periodicity at which hardware is expceted to collect CFR dump.
0 - single shot capture.
non zero - for Periodic captures (value should be multiple of 10).
To collect the cfr dump,
cat /sys/kernel/debug/ieee80211/phy0/ath11k/cfr_capture0 > /tmp/cfr.bin
Signed-off-by: Venkateswara Naralasetty <vnaralas@codeaurora.org>
---
drivers/net/wireless/ath/ath11k/Kconfig | 9 +
drivers/net/wireless/ath/ath11k/Makefile | 1 +
drivers/net/wireless/ath/ath11k/cfr.c | 878 ++++++++++++++++++++++++++
drivers/net/wireless/ath/ath11k/cfr.h | 282 +++++++++
drivers/net/wireless/ath/ath11k/core.c | 15 +
drivers/net/wireless/ath/ath11k/core.h | 16 +-
drivers/net/wireless/ath/ath11k/dbring.c | 28 +-
drivers/net/wireless/ath/ath11k/dbring.h | 11 +-
drivers/net/wireless/ath/ath11k/debugfs.h | 2 +
drivers/net/wireless/ath/ath11k/debugfs_sta.c | 157 +++++
drivers/net/wireless/ath/ath11k/hal.c | 2 +-
drivers/net/wireless/ath/ath11k/hw.h | 4 +
drivers/net/wireless/ath/ath11k/mac.c | 11 +-
drivers/net/wireless/ath/ath11k/spectral.c | 2 +-
drivers/net/wireless/ath/ath11k/wmi.c | 137 +++-
drivers/net/wireless/ath/ath11k/wmi.h | 91 ++-
local-symbols | 1 +
17 files changed, 1629 insertions(+), 18 deletions(-)
create mode 100644 drivers/net/wireless/ath/ath11k/cfr.c
create mode 100644 drivers/net/wireless/ath/ath11k/cfr.h
--- a/drivers/net/wireless/ath/ath11k/Kconfig
+++ b/drivers/net/wireless/ath/ath11k/Kconfig
@@ -84,3 +84,12 @@ config ATH11K_MEM_PROFILE_512M
default n
---help---
Enables 512MB memory profile for ath11k
+
+config ATH11K_CFR
+ bool "QCA ath11k CFR support"
+ depends on ATH11K_DEBUGFS
+ depends on RELAY
+ help
+ Enable ath11k cfr dump support
+
+ Say Y to enable access to collect cfr data dump via debugfs.
--- a/drivers/net/wireless/ath/ath11k/Makefile
+++ b/drivers/net/wireless/ath/ath11k/Makefile
@@ -28,6 +28,7 @@ ath11k-$(CONFIG_WANT_DEV_COREDUMP) += co
ath11k-$(CPTCFG_ATH11K_SPECTRAL) += spectral.o
ath11k-$(CPTCFG_ATH11K_PKTLOG) += pktlog.o
ath11k-$(CPTCFG_ATH11K_NSS_SUPPORT) += nss.o
+ath11k-$(CPTCFG_ATH11K_CFR) += cfr.o
obj-$(CPTCFG_ATH11K_AHB) += ath11k_ahb.o
ath11k_ahb-y += ahb.o
--- /dev/null
+++ b/drivers/net/wireless/ath/ath11k/cfr.c
@@ -0,0 +1,878 @@
+// SPDX-License-Identifier: BSD-3-Clause-Clear
+/*
+ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/relay.h>
+#include "core.h"
+#include "debug.h"
+
+bool peer_is_in_cfr_unassoc_pool(struct ath11k *ar, 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)) {
+ /* Remove entry if it is single shot */
+ if (entry->period == 0) {
+ memset(entry->peer_mac, 0 , ETH_ALEN);
+ entry->is_valid = false;
+ cfr->cfr_enabled_peer_cnt--;
+ }
+ spin_unlock_bh(&cfr->lock);
+ return true;
+ }
+ }
+
+ spin_unlock_bh(&cfr->lock);
+
+ return false;
+}
+
+void ath11k_cfr_lut_update_paddr(struct ath11k *ar, dma_addr_t paddr,
+ u32 buf_id)
+{
+ struct ath11k_cfr *cfr = &ar->cfr;
+ struct ath11k_cfr_look_up_table *lut;
+
+ if (cfr->lut) {
+ lut = &cfr->lut[buf_id];
+ lut->dbr_address = paddr;
+ }
+}
+
+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);
+}
+
+struct ath11k_dbring *ath11k_cfr_get_dbring(struct ath11k *ar)
+{
+ if (ar->cfr_enabled)
+ return &ar->cfr.rx_ring;
+ else
+ return NULL;
+}
+
+static int cfr_calculate_tones_form_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_PREABLE_TYPE, hdr->info1);
+
+ switch (preamble) {
+ case ATH11K_CFR_PREAMBLE_TYPE_LEGACY:
+ 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;
+ }
+
+ case ATH11K_CFR_PREAMBLE_TYPE_HT:
+ switch (bw) {
+ case 0:
+ return TONES_IN_20MHZ;
+ case 1:
+ return TONES_IN_40MHZ;
+ }
+ }
+
+ return TONES_INVALID;
+}
+
+static inline
+void ath11k_cfr_release_lut_entry(struct ath11k_cfr_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_cfr_look_up_table *lut = NULL;
+ 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, GFP_ATOMIC);
+ 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 the both 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 will return CORRELATE_STATUS_ERR in
+ * case of any error.
+ *
+ * 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 int ath11k_cfr_correlate_and_relay(struct ath11k *ar,
+ struct ath11k_cfr_look_up_table *lut,
+ u8 event_type)
+{
+ 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) {
+ 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++;
+ return 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++;
+ return ATH11K_CORRELATE_STATUS_HOLD;
+ }
+ } else {
+ return ATH11K_CORRELATE_STATUS_HOLD;
+ }
+}
+
+static int ath11k_cfr_process_data(struct ath11k *ar,
+ struct ath11k_dbring_data *param)
+{
+ struct ath11k_base *ab = ar->ab;
+ struct ath11k_cfr *cfr = &ar->cfr;
+ struct ath11k_cfr_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;
+ int ret = 0;
+
+ data = param->data;
+ buf_id = param->buf_id;
+
+ memcpy(&dma_hdr, data, sizeof(struct ath11k_cfir_dma_hdr));
+
+ tones = cfr_calculate_tones_form_dma_hdr(&dma_hdr);
+ if (tones == TONES_INVALID) {
+ ath11k_err(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(struct ath11k_cfir_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];
+
+ if (!lut) {
+ ath11k_dbg(ab, ATH11K_DBG_CFR,
+ "lut failure to process cfr data id:%d\n", buf_id);
+ spin_unlock_bh(&cfr->lut_lock);
+ return -EINVAL;
+ }
+
+ 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(struct ath11k_cfir_dma_hdr));
+
+ header = &lut->header;
+ header->u.meta_v2.channel_bw = FIELD_GET(CFIR_DMA_HDR_INFO1_UPLOAD_PKT_BW,
+ dma_hdr.info1);
+ header->u.meta_v2.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);
+ ret = ATH11K_CORRELATE_STATUS_RELEASE;
+ } else if (status == ATH11K_CORRELATE_STATUS_HOLD) {
+ ret = ATH11K_CORRELATE_STATUS_HOLD;
+ ath11k_dbg(ab, ATH11K_DBG_CFR,
+ "tx event is not yet received holding the buf");
+ } else {
+ ret = ATH11K_CORRELATE_STATUS_ERR;
+ ath11k_cfr_release_lut_entry(lut);
+ ath11k_err(ab, "error in correlating events");
+ }
+
+ spin_unlock_bh(&cfr->lut_lock);
+
+ return ret;
+}
+
+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_2;
+ header->cfr_data_version = ATH11K_CFR_DATA_VERSION_1;
+ /* TODO: can we add this chip_type to hw param table */
+ header->chip_type = ATH11K_CFR_RADIO_IPQ8074;
+ header->u.meta_v2.status = FIELD_GET(WMI_CFR_PEER_CAPTURE_STATUS,
+ params->status);
+ header->u.meta_v2.capture_bw = params->bandwidth;
+ header->u.meta_v2.phy_mode = params->phy_mode;
+ header->u.meta_v2.prim20_chan = params->primary_20mhz_chan;
+ header->u.meta_v2.center_freq1 = params->band_center_freq1;
+ header->u.meta_v2.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->u.meta_v2.capture_mode = params->bandwidth ?
+ ATH11K_CFR_CAPTURE_DUP_LEGACY_ACK : ATH11K_CFR_CAPTURE_LEGACY_ACK;
+ header->u.meta_v2.capture_type = params->capture_method;
+ header->u.meta_v2.num_rx_chain = ar->num_rx_chains;
+ header->u.meta_v2.sts_count = params->spatial_streams;
+ header->u.meta_v2.timestamp = params->timestamp_us;
+ memcpy(header->u.meta_v2.peer_addr, params->peer_mac_addr, ETH_ALEN);
+ memcpy(header->u.meta_v2.chain_rssi, params->chain_rssi,
+ sizeof(params->chain_rssi));
+ memcpy(header->u.meta_v2.chain_phase, params->chain_phase,
+ sizeof(params->chain_phase));
+}
+
+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_cfr_look_up_table *lut = NULL, *temp = 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 ret = 0;
+ int status;
+ int i;
+
+ rcu_read_lock();
+ arvif = ath11k_mac_get_arvif_by_vdev_id(ab, params->vdev_id);
+ if (!arvif) {
+ ath11k_warn(ab, "Failed to get arvif for vdev id %d\n",
+ params->vdev_id);
+ rcu_read_unlock();
+ 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++) {
+ temp = &cfr->lut[i];
+ if (temp->dbr_address == buf_addr) {
+ lut = &cfr->lut[i];
+ break;
+ }
+ }
+
+ if (!lut) {
+ ath11k_dbg(ab, ATH11K_DBG_CFR,
+ "lut failure to process tx event\n");
+ cfr->tx_dbr_lookup_fail++;
+ spin_unlock_bh(&cfr->lut_lock);
+ 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->pltform_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, GFP_ATOMIC);
+ } else if (status == ATH11K_CORRELATE_STATUS_HOLD) {
+ ath11k_dbg(ab, ATH11K_DBG_CFR,
+ "dbr event is not yet received holding buf\n");
+ } else {
+ ath11k_cfr_release_lut_entry(lut);
+ ret = -EINVAL;
+ }
+
+ spin_unlock_bh(&cfr->lut_lock);
+ return ret;
+}
+
+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 struct rchan_callbacks rfs_cfr_capture_cb = {
+ .create_buf_file = create_buf_file_handler,
+ .remove_buf_file = remove_buf_file_handler,
+};
+
+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] = {0};
+ 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;
+ u8 enable_cfr;
+ int ret;
+
+ if (kstrtouint_from_user(ubuf, count, 0, &enable_cfr))
+ return -EINVAL;
+
+ mutex_lock(&ar->conf_mutex);
+
+ if (ar->state != ATH11K_STATE_ON) {
+ ret = -ENETDOWN;
+ goto out;
+ }
+
+ if (enable_cfr > 1) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (ar->cfr_enabled == enable_cfr) {
+ ret = count;
+ goto out;
+ }
+
+ 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);
+ goto out;
+ }
+
+ ar->cfr_enabled = enable_cfr;
+ ret = count;
+
+out:
+ mutex_unlock(&ar->conf_mutex);
+ return ret;
+}
+
+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] = {0};
+ 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);
+
+ mutex_lock(&ar->conf_mutex);
+ spin_lock_bh(&cfr->lock);
+
+ if (ar->state != ATH11K_STATE_ON) {
+ ret = -ENETDOWN;
+ goto out;
+ }
+
+ if (!ar->cfr_enabled) {
+ ret = -EINVAL;
+ ath11k_err(ar->ab, "CFR is not enabled on this pdev %d\n",
+ ar->pdev_idx);
+ goto out;
+ }
+
+ 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) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (cfr_capture_enable && ret != 8) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ 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--;
+ }
+ }
+
+ ret = count;
+ goto out;
+ }
+
+
+ if (cfr->cfr_enabled_peer_cnt >= ATH11K_MAX_CFR_ENABLED_CLIENTS) {
+ ath11k_info(ar->ab, "Max cfr peer threshold reached\n");
+ ret = count;
+ goto out;
+ }
+
+ 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;
+ ret = count;
+ goto out;
+ }
+ }
+
+ 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++;
+ }
+
+ ret = count;
+out:
+ spin_unlock_bh(&cfr->lock);
+ mutex_unlock(&ar->conf_mutex);
+ return ret;
+}
+
+static ssize_t ath11k_read_file_cfr_unassoc(struct file *file,
+ char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ char buf[512] = {0};
+ struct ath11k *ar = file->private_data;
+ struct ath11k_cfr *cfr = &ar->cfr;
+ struct cfr_unassoc_pool_entry *entry;
+ int len = 0, i;
+
+ mutex_lock(&ar->conf_mutex);
+ 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);
+ mutex_unlock(&ar->conf_mutex);
+
+ 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 inline 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;
+
+ if (ar->cfr.rfs_cfr_capture) {
+ relay_close(ar->cfr.rfs_cfr_capture);
+ ar->cfr.rfs_cfr_capture = NULL;
+ }
+}
+
+static inline int ath11k_cfr_debug_register(struct ath11k *ar)
+{
+ int ret;
+
+ 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;
+ }
+
+ ar->cfr.enable_cfr = debugfs_create_file("enable_cfr", 0600,
+ ar->debug.debugfs_pdev, ar,
+ &fops_enable_cfr);
+ if (!ar->cfr.enable_cfr) {
+ ath11k_warn(ar->ab, "failed to open debugfs in pdev %d\n",
+ ar->pdev_idx);
+ ret = -EINVAL;
+ goto debug_unregister;
+ }
+
+ ar->cfr.cfr_unassoc = debugfs_create_file("cfr_unassoc", 0600,
+ ar->debug.debugfs_pdev, ar,
+ &fops_configure_cfr_unassoc);
+
+ if (!ar->cfr.cfr_unassoc) {
+ ath11k_warn(ar->ab,
+ "failed to open debugfs for unassoc pool in pdev %d\n",
+ ar->pdev_idx);
+ ret = -EINVAL;
+ goto debug_unregister;
+ }
+
+ return 0;
+
+debug_unregister :
+ ath11k_cfr_debug_unregister(ar);
+ return ret;
+}
+
+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_ring_free(struct ath11k *ar)
+{
+ struct ath11k_cfr *cfr = &ar->cfr;
+
+ ath11k_dbring_srng_cleanup(ar, &cfr->rx_ring);
+ ath11k_dbring_buf_cleanup(ar, &cfr->rx_ring);
+}
+
+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_debug_unregister(ar);
+ ath11k_cfr_ring_free(ar);
+
+ spin_lock_bh(&cfr->lut_lock);
+ if (cfr->lut) {
+ kfree(cfr->lut);
+ cfr->lut = NULL;
+ }
+
+ spin_unlock_bh(&cfr->lut_lock);
+ ar->cfr_enabled = 0;
+ }
+}
+
+int ath11k_cfr_init(struct ath11k_base *ab)
+{
+ struct ath11k *ar;
+ struct ath11k_cfr *cfr;
+ struct ath11k_dbring_cap db_cap;
+ struct ath11k_cfr_look_up_table *lut;
+ 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((u32)CFR_MAX_LUT_ENTRIES, db_cap.min_elem);
+
+ cfr->lut = kzalloc(num_lut_entries * sizeof(*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;
+ }
+
+ spin_lock_bh(&cfr->lock);
+ cfr->lut_num = num_lut_entries;
+ spin_unlock_bh(&cfr->lock);
+
+ ret = ath11k_cfr_debug_register(ar);
+ if (ret) {
+ ath11k_warn(ab, "failed to register cfr for pdev %d\n", i);
+ goto deinit;
+ }
+ }
+
+ return 0;
+
+deinit:
+ ath11k_cfr_deinit(ab);
+ return ret;
+}
--- /dev/null
+++ b/drivers/net/wireless/ath/ath11k/cfr.h
@@ -0,0 +1,282 @@
+/* SPDX-License-Identifier: BSD-3-Clause-Clear */
+/*
+ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
+ */
+
+#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_CORRELATE_TX_EVENT 1
+#define ATH11K_CORRELATE_DBR_EVENT 0
+
+#define ATH11K_MAX_CFR_ENABLED_CLIENTS 10
+
+#define ATH11K_CFR_START_MAGIC 0xDEADBEAF
+#define ATH11K_CFR_END_MAGIC 0xBEAFDEAD
+
+#define ATH11K_CFR_RADIO_IPQ8074 23
+
+#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_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_CPATURE_VHT_ACK,
+
+ /*Always keep this at last*/
+ ATH11K_CFR_CPATURE_INVALID_ACK
+};
+
+enum ath11k_cfr_correlate_status {
+ ATH11K_CORRELATE_STATUS_RELEASE,
+ ATH11K_CORRELATE_STATUS_HOLD,
+ ATH11K_CORRELATE_STATUS_ERR,
+};
+
+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];
+};
+
+struct cfr_metadata_version_1 {
+ 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;
+} __packed;
+
+#define HOST_MAX_CHAINS 8
+
+struct cfr_metadata_version_2 {
+ 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];
+} __packed;
+
+struct ath11k_csi_cfr_header {
+ u32 start_magic_num;
+ u32 vendorid;
+ u8 cfr_metadata_version;
+ u8 cfr_data_version;
+ u8 chip_type;
+ u8 pltform_type;
+ u32 Reserved;
+ union {
+ struct cfr_metadata_version_1 meta_v1;
+ struct cfr_metadata_version_2 meta_v2;
+ } u;
+} __packed;
+
+enum ath11k_cfr_preamble_type {
+ ATH11K_CFR_PREAMBLE_TYPE_LEGACY,
+ ATH11K_CFR_PREAMBLE_TYPE_HT,
+ ATH11K_CFR_PREAMBLE_TYPE_VHT,
+};
+
+#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_PREABLE_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;
+ u16 sw_peer_id;
+ u16 phy_ppdu_id;
+};
+
+#define CFR_MAX_LUT_ENTRIES 136
+
+struct ath11k_cfr_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_csi_cfr_header header;
+ struct ath11k_cfir_dma_hdr hdr;
+ u64 txrx_tstamp;
+ u64 dbr_tstamp;
+ u32 header_length;
+ u32 payload_length;
+ struct ath11k_dbring_element *buff;
+};
+
+enum cfr_capture_type {
+ CFR_CAPTURE_METHOD_NULL_FRAME = 0,
+ CFR_CAPURE_METHOD_NULL_FRAME_WITH_PHASE = 1,
+ CFR_CAPTURE_METHOD_PROBE_RESP = 2,
+ CFR_CAPTURE_METHOD_TM = 3,
+ CFR_CAPTURE_METHOD_FTM = 4,
+ CFR_CAPTURE_METHOD_ACK_RESP_TO_TM_FTM = 5,
+ CFR_CAPTURE_METHOD_TA_RA_TYPE_FILTER = 6,
+ CFR_CAPTURE_METHOD_NDPA_NDP = 7,
+ CFR_CAPTURE_METHOD_ALL_PACKET = 8,
+ /* Add new capture methods before this line */
+ CFR_CAPTURE_METHOD_LAST_VALID,
+ CFR_CAPTURE_METHOD_AUTO = 0xff,
+ CFR_CAPTURE_METHOD_MAX,
+};
+
+struct cfr_unassoc_pool_entry {
+ u8 peer_mac[ETH_ALEN];
+ u32 period;
+ bool is_valid;
+};
+
+struct ath11k_cfr {
+ struct ath11k_dbring rx_ring;
+ /* Protects enabled for ath11k_cfr */
+ spinlock_t lock;
+ struct rchan *rfs_cfr_capture;
+ struct dentry *enable_cfr;
+ struct dentry *cfr_unassoc;
+ u8 cfr_enabled_peer_cnt;
+ struct ath11k_cfr_look_up_table *lut;
+ u32 lut_num;
+ u32 dbr_buf_size;
+ u32 dbr_num_bufs;
+ u32 max_mu_users;
+ /* protect look up table data */
+ 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;
+ struct cfr_unassoc_pool_entry unassoc_pool[ATH11K_MAX_CFR_ENABLED_CLIENTS];
+};
+
+#ifdef CPTCFG_ATH11K_CFR
+
+int ath11k_cfr_init(struct ath11k_base *ab);
+void ath11k_cfr_deinit(struct ath11k_base *ab);
+struct ath11k_dbring *ath11k_cfr_get_dbring(struct ath11k *ar);
+int ath11k_process_cfr_capture_event(struct ath11k_base *ab,
+ struct ath11k_cfr_peer_tx_param *params);
+bool peer_is_in_cfr_unassoc_pool(struct ath11k *ar, u8 *peer_mac);
+void ath11k_cfr_relase_lut_entry(struct ath11k_cfr_look_up_table *lut);
+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);
+#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
+struct ath11k_dbring *ath11k_cfr_get_dbring(struct ath11k *ar)
+{
+ return NULL;
+}
+static inline bool peer_is_in_cfr_unassoc_pool(struct ath11k *ar, u8 *peer_mac)
+{
+ return false;
+}
+static inline
+void ath11k_cfr_relase_lut_entry(struct ath11k_cfr_look_up_table *lut)
+{
+}
+static inline
+int ath11k_process_cfr_capture_event(struct ath11k_base *ab,
+ struct ath11k_cfr_peer_tx_param *params)
+{
+ return 0;
+}
+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)
+{
+}
+#endif /* CPTCFG_ATH11K_CFR */
+#endif /* ATH11K_CFR_H */
--- a/drivers/net/wireless/ath/ath11k/core.c
+++ b/drivers/net/wireless/ath/ath11k/core.c
@@ -103,6 +103,11 @@ static const struct ath11k_hw_params ath
.ce_fwlog_enable = false,
.fwmem_mode_change = false,
.is_qdss_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 = 8500,
},
{
.hw_rev = ATH11K_HW_IPQ6018_HW10,
@@ -843,8 +848,16 @@ static int ath11k_core_pdev_create(struc
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:
@@ -861,6 +874,7 @@ err_pdev_debug:
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);
@@ -1153,6 +1167,7 @@ static int ath11k_core_reconfigure_on_cr
ath11k_hif_irq_disable(ab);
ath11k_dp_pdev_free(ab);
ath11k_spectral_deinit(ab);
+ ath11k_cfr_deinit(ab);
ath11k_hif_stop(ab);
ath11k_wmi_detach(ab);
ath11k_dp_pdev_reo_cleanup(ab);
--- a/drivers/net/wireless/ath/ath11k/core.h
+++ b/drivers/net/wireless/ath/ath11k/core.h
@@ -27,6 +27,7 @@
#include "vendor.h"
#include "rx_desc.h"
#include "nss.h"
+#include "cfr.h"
extern unsigned int ath11k_skip_radio;
@@ -401,6 +402,13 @@ struct ath11k_htt_tx_stats {
u32 mu_group[MAX_MU_GROUP_ID];
};
+struct ath11k_per_peer_cfr_capture {
+ u32 cfr_enable;
+ u32 cfr_period;
+ u32 cfr_bandwidth;
+ u32 cfr_method;
+};
+
struct ath11k_per_ppdu_tx_stats {
u16 succ_pkts;
u16 failed_pkts;
@@ -458,6 +466,9 @@ struct ath11k_sta {
u32 ps_start_jiffies;
u8 peer_current_ps_valid;
u32 ps_total_duration;
+#ifdef CPTCFG_ATH11K_CFR
+ struct ath11k_per_peer_cfr_capture cfr_capture;
+#endif
};
#define ATH11K_MIN_5G_FREQ 4150
@@ -724,6 +735,10 @@ struct ath11k {
u8 ps_state_enable;
u8 ps_timekeeper_enable;
u8 reset_ps_duration;
+#ifdef CPTCFG_ATH11K_CFR
+ struct ath11k_cfr cfr;
+#endif
+ u8 cfr_enabled;
};
struct ath11k_band_cap {
--- a/drivers/net/wireless/ath/ath11k/dbring.c
+++ b/drivers/net/wireless/ath/ath11k/dbring.c
@@ -41,11 +41,11 @@ static void ath11k_dbring_fill_magic_val
}
}
-static int ath11k_dbring_bufs_replenish(struct ath11k *ar,
- struct ath11k_dbring *ring,
- struct ath11k_dbring_element *buff,
- enum wmi_direct_buffer_module id,
- gfp_t gfp)
+int ath11k_dbring_bufs_replenish(struct ath11k *ar,
+ struct ath11k_dbring *ring,
+ struct ath11k_dbring_element *buff,
+ enum wmi_direct_buffer_module id,
+ gfp_t gfp)
{
struct ath11k_base *ab = ar->ab;
struct hal_srng *srng;
@@ -85,6 +85,9 @@ static int ath11k_dbring_bufs_replenish(
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) |
@@ -200,8 +203,7 @@ int ath11k_dbring_set_cfg(struct ath11k
int ath11k_dbring_buf_setup(struct ath11k *ar,
struct ath11k_dbring *ring,
- struct ath11k_dbring_cap *db_cap,
- enum wmi_direct_buffer_module id)
+ struct ath11k_dbring_cap *db_cap)
{
struct ath11k_base *ab = ar->ab;
struct hal_srng *srng;
@@ -217,7 +219,7 @@ int ath11k_dbring_buf_setup(struct ath11
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, id, GFP_KERNEL);
+ ret = ath11k_dbring_fill_bufs(ar, ring, db_cap->id, GFP_KERNEL);
return ret;
}
@@ -283,6 +285,7 @@ int ath11k_dbring_buffer_release_event(s
int size;
dma_addr_t paddr;
int ret = 0;
+ int status;
pdev_idx = ev->fixed.pdev_id;
module_id = ev->fixed.module_id;
@@ -312,6 +315,9 @@ int ath11k_dbring_buffer_release_event(s
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",
@@ -362,8 +368,12 @@ int ath11k_dbring_buffer_release_event(s
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;
}
memset(buff, 0, size);
--- a/drivers/net/wireless/ath/ath11k/dbring.h
+++ b/drivers/net/wireless/ath/ath11k/dbring.h
@@ -20,6 +20,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 {
@@ -60,13 +62,18 @@ int ath11k_dbring_set_cfg(struct ath11k
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,
+ gfp_t gfp);
int ath11k_dbring_wmi_cfg_setup(struct ath11k *ar,
struct ath11k_dbring *ring,
enum wmi_direct_buffer_module id);
int ath11k_dbring_buf_setup(struct ath11k *ar,
struct ath11k_dbring *ring,
- struct ath11k_dbring_cap *db_cap,
- enum wmi_direct_buffer_module id);
+ struct ath11k_dbring_cap *db_cap);
int ath11k_dbring_srng_setup(struct ath11k *ar, struct ath11k_dbring *ring,
int ring_num, int num_entries);
int ath11k_dbring_buffer_release_event(struct ath11k_base *ab,
--- a/drivers/net/wireless/ath/ath11k/debugfs_sta.c
+++ b/drivers/net/wireless/ath/ath11k/debugfs_sta.c
@@ -1331,6 +1331,157 @@ static const struct file_operations fops
.llseek = default_llseek,
};
+#ifdef CPTCFG_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 = (struct ath11k_sta *)sta->drv_priv;
+ 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;
+ int ret;
+ char buf[64] = {0};
+
+ simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count);
+
+ mutex_lock(&ar->conf_mutex);
+
+ if (ar->state != ATH11K_STATE_ON) {
+ ret = -ENETDOWN;
+ goto out;
+ }
+
+ if (!ar->cfr_enabled) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ 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)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ 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_bandwidth &&
+ cfr_capture_method == arsta->cfr_capture.cfr_method) {
+ ret = count;
+ goto out;
+ }
+
+ if (!cfr_capture_enable &&
+ cfr_capture_enable == arsta->cfr_capture.cfr_enable) {
+ ret = count;
+ goto out;
+ }
+
+ if (cfr_capture_enable > WMI_PEER_CFR_CAPTURE_ENABLE ||
+ cfr_capture_bw > WMI_PEER_CFR_CAPTURE_BW_80MHZ ||
+ cfr_capture_bw > sta->bandwidth ||
+ cfr_capture_method > CFR_CAPURE_METHOD_NULL_FRAME_WITH_PHASE ||
+ cfr_capture_period > WMI_PEER_CFR_PERIODICITY_MAX) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Target expects cfr period in multiple of 10 */
+ if (cfr_capture_period % 10) {
+ ath11k_err(ar->ab, "periodicity should be 10x\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (ar->cfr.cfr_enabled_peer_cnt >= ATH11K_MAX_CFR_ENABLED_CLIENTS &&
+ !arsta->cfr_capture.cfr_enable) {
+ ret = -EINVAL;
+ ath11k_err(ar->ab, "CFR enable peer threshold reached %u\n",
+ ar->cfr.cfr_enabled_peer_cnt);
+ goto out;
+ }
+
+ if (!cfr_capture_enable) {
+ cfr_capture_bw = arsta->cfr_capture.cfr_bandwidth;
+ 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.bandwidth = cfr_capture_bw;
+ arg.capture_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, "failed to send cfr capture info: vdev_id %u peer %pM\n",
+ arsta->arvif->vdev_id, sta->addr);
+ goto out;
+ }
+
+ ret = count;
+
+ 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_bandwidth = cfr_capture_bw;
+ arsta->cfr_capture.cfr_method = cfr_capture_method;
+out:
+ mutex_unlock(&ar->conf_mutex);
+ return ret;
+}
+
+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 = (struct ath11k_sta *)sta->drv_priv;
+ struct ath11k *ar = arsta->arvif->ar;
+ char buf[512] = {0};
+ 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_bandwidth);
+ 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 /* CPTCFG_ATH11K_CFR */
+
void ath11k_debugfs_sta_op_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_sta *sta, struct dentry *dir)
{
@@ -1378,4 +1529,10 @@ void ath11k_debugfs_sta_op_add(struct ie
ar->ab->wmi_ab.svc_map))
debugfs_create_file("htt_peer_stats_reset", 0600, dir, sta,
&fops_htt_peer_stats_reset);
+#ifdef CPTCFG_ATH11K_CFR
+ if (test_bit(WMI_TLV_SERVICE_CFR_CAPTURE_SUPPORT,
+ ar->ab->wmi_ab.svc_map))
+ debugfs_create_file("cfr_capture", 0400, dir, sta,
+ &fops_peer_cfr_capture);
+#endif/* CPTCFG_ATH11K_CFR */
}
--- a/drivers/net/wireless/ath/ath11k/hal.c
+++ b/drivers/net/wireless/ath/ath11k/hal.c
@@ -181,7 +181,7 @@ static const struct hal_srng_config hw_s
},
{ /* 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,
--- a/drivers/net/wireless/ath/ath11k/hw.h
+++ b/drivers/net/wireless/ath/ath11k/hw.h
@@ -204,6 +204,10 @@ struct ath11k_hw_params {
bool ce_fwlog_enable;
bool fwmem_mode_change;
bool is_qdss_support;
+ bool cfr_support;
+ u32 cfr_dma_hdr_size;
+ u32 cfr_num_stream_bufs;
+ u32 cfr_stream_buf_size;
};
struct ath11k_hw_ops {
--- a/drivers/net/wireless/ath/ath11k/mac.c
+++ b/drivers/net/wireless/ath/ath11k/mac.c
@@ -4796,6 +4796,8 @@ static int ath11k_mac_op_sta_state(struc
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 ||
@@ -5727,6 +5729,7 @@ static int ath11k_mac_mgmt_tx_wmi(struct
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
struct ieee80211_tx_info *info;
dma_addr_t paddr;
+ bool tx_params_valid = false;
int buf_id;
int ret;
@@ -5766,7 +5769,13 @@ static int ath11k_mac_mgmt_tx_wmi(struct
return 0;
}
- ret = ath11k_wmi_mgmt_send(ar, arvif->vdev_id, buf_id, skb);
+ if (ar->cfr_enabled &&
+ ieee80211_is_probe_resp(hdr->frame_control) &&
+ peer_is_in_cfr_unassoc_pool(ar, hdr->addr1))
+ tx_params_valid = true;
+
+ 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;
--- a/drivers/net/wireless/ath/ath11k/spectral.c
+++ b/drivers/net/wireless/ath/ath11k/spectral.c
@@ -817,7 +817,7 @@ static int ath11k_spectral_ring_alloc(st
ATH11K_SPECTRAL_EVENT_TIMEOUT_MS,
ath11k_spectral_process_data);
- ret = ath11k_dbring_buf_setup(ar, &sp->rx_ring, db_cap, WMI_DIRECT_BUF_SPECTRAL);
+ ret = ath11k_dbring_buf_setup(ar, &sp->rx_ring, db_cap);
if (ret) {
ath11k_warn(ar->ab, "failed to setup db ring buffer\n");
goto srng_cleanup;
--- a/drivers/net/wireless/ath/ath11k/wmi.c
+++ b/drivers/net/wireless/ath/ath11k/wmi.c
@@ -612,10 +612,11 @@ struct sk_buff *ath11k_wmi_alloc_skb(str
}
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_cmd *cmd;
+ struct wmi_tx_send_params *params;
struct wmi_tlv *frame_tlv;
struct sk_buff *skb;
u32 buf_len;
@@ -626,6 +627,9 @@ int ath11k_wmi_mgmt_send(struct ath11k *
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)
return -ENOMEM;
@@ -640,7 +644,7 @@ int ath11k_wmi_mgmt_send(struct ath11k *
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) |
@@ -650,6 +654,13 @@ int ath11k_wmi_mgmt_send(struct ath11k *
ath11k_ce_byte_swap(frame_tlv->value, buf_len);
+ if (tx_params_valid) {
+ params = (struct wmi_tx_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,
@@ -4053,6 +4064,42 @@ int ath11k_wmi_fils_discovery_tmpl(struc
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);
+
+ ether_addr_copy(cmd->mac_addr.addr, mac_addr);
+ cmd->request = arg->request;
+ cmd->vdev_id = vdev_id;
+ cmd->periodicity = arg->periodicity;
+ cmd->bandwidth = arg->bandwidth;
+ cmd->capture_method = arg->capture_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);
+ }
+
+ return ret;
+}
+
int ath11k_wmi_probe_resp_tmpl(struct ath11k *ar, u32 vdev_id,
struct sk_buff *tmpl)
{
@@ -8928,6 +8975,89 @@ static void ath11k_wmi_diag_event(struct
ath11k_fwlog_write(ab,data, tlv_len);
}
+static void ath11k_wmi_tlv_cfr_cpature_event_fixed_param(const void *ptr,
+ void *data)
+{
+ struct ath11k_cfr_peer_tx_param *tx_params =
+ (struct ath11k_cfr_peer_tx_param *)data;
+ struct ath11k_wmi_cfr_peer_tx_event_param *params =
+ (struct ath11k_wmi_cfr_peer_tx_event_param *)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;
+ memcpy(tx_params->chain_rssi, params->chain_rssi,
+ sizeof(tx_params->chain_rssi));
+}
+
+static void ath11k_wmi_tlv_cfr_cpature_phase_fixed_param(const void *ptr,
+ void *data)
+{
+ struct ath11k_cfr_peer_tx_param *tx_params =
+ (struct ath11k_cfr_peer_tx_param *)data;
+ struct ath11k_wmi_cfr_peer_tx_event_phase_param *params =
+ (struct ath11k_wmi_cfr_peer_tx_event_phase_param *)ptr;
+ int i;
+
+ for (i = 0; i < WMI_MAX_CHAINS; i++)
+ tx_params->chain_phase[i] = params->chain_phase[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_cpature_event_fixed_param(ptr, data);
+ break;
+ case WMI_TAG_CFR_CAPTURE_PHASE_PARAM:
+ ath11k_wmi_tlv_cfr_cpature_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 cpature 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;
@@ -9077,6 +9207,9 @@ static void ath11k_wmi_tlv_op_rx(struct
case WMI_CTRL_PATH_STATS_EVENTID:
ath11k_wmi_ctrl_path_stats_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);
--- a/drivers/net/wireless/ath/ath11k/wmi.h
+++ b/drivers/net/wireless/ath/ath11k/wmi.h
@@ -339,6 +339,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,
@@ -966,6 +970,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,
@@ -1869,7 +1874,9 @@ 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_MUEDCA_PARAMS_CONFIG_EVENT = 0x32a,
+ 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,
@@ -3494,6 +3501,85 @@ struct wmi_bssid_arg {
const u8 *bssid;
};
+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 {
+ u32 request;
+ u32 periodicity;
+ u32 bandwidth;
+ u32 capture_method;
+};
+
+struct wmi_peer_cfr_capture_cmd_fixed_param {
+ u32 tlv_header;
+ u32 request;
+ struct wmi_mac_addr mac_addr;
+ u32 vdev_id;
+ u32 periodicity;
+ u32 bandwidth;
+ 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)
+
+#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 GENMASK(0, 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;
+} __packed;
+
+struct ath11k_wmi_cfr_peer_tx_event_phase_param {
+ u32 chain_phase[WMI_MAX_CHAINS];
+} __packed;
+
+enum ath11k_wmi_cfr_capture_bw {
+ WMI_PEER_CFR_CAPTURE_BW_20MHZ,
+ WMI_PEER_CFR_CAPTURE_BW_40MHZ,
+ WMI_PEER_CFR_CAPTURE_BW_80MHZ,
+ WMI_PEER_CFR_CAPTURE_BW_160MHZ,
+ WMI_PEER_CFR_CAPTURE_BW_80_80MHZ,
+ WMI_PEER_CFR_CAPTURE_BW_MAX,
+};
+
+struct ath11k_wmi_peer_cfr_capture_conf {
+ u32 request;
+ u32 periodicity;
+ u32 bandwidth;
+ u32 capture_method;
+};
+
struct wmi_start_scan_arg {
u32 scan_id;
u32 scan_req_id;
@@ -6334,7 +6420,7 @@ int ath11k_wmi_cmd_send(struct ath11k_pd
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_qos_null_send(struct ath11k *ar, u32 vdev_id, u32 buf_id,
struct sk_buff *frame);
int ath11k_wmi_bcn_tmpl(struct ath11k *ar, u32 vdev_id,
@@ -6496,4 +6582,7 @@ int ath11k_wmi_send_aggr_size_cmd(struct
int ath11k_wmi_send_wmi_ctrl_stats_cmd(struct ath11k *ar,
struct wmi_ctrl_path_stats_cmd_param *param);
int ath11k_wmi_dbglog_cfg(struct ath11k *ar, u32 param, u64 value);
+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
--- a/local-symbols
+++ b/local-symbols
@@ -140,3 +140,4 @@ ATH11K_DEBUGFS=
ATH11K_TRACING=
ATH11K_SPECTRAL=
ATH11K_PKTLOG=
+ATH11K_CFR=
--- 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 = 0x00008000,
+ ATH11K_DBG_CFR_DUMP = 0x00010000,
ATH11K_DBG_ANY = 0xffffffff,
};