diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-02-11 19:31:52 -0800 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-02-11 19:31:52 -0800 |
| commit | 37a93dd5c49b5fda807fd204edf2547c3493319c (patch) | |
| tree | ce1ef5a642b9ea3d7242156438eb96dc5607a752 /drivers/net/wireless/ath/ath11k | |
| parent | 098b6e44cbaa2d526d06af90c862d13fb414a0ec (diff) | |
| parent | 83310d613382f74070fc8b402f3f6c2af8439ead (diff) | |
Merge tag 'net-next-7.0' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextipvs/mainipvs/HEADipvs-next/mainipvs-next/HEADdavem/net-next/maindavem/net-next/HEAD
Pull networking updates from Paolo Abeni:
"Core & protocols:
- A significant effort all around the stack to guide the compiler to
make the right choice when inlining code, to avoid unneeded calls
for small helper and stack canary overhead in the fast-path.
This generates better and faster code with very small or no text
size increases, as in many cases the call generated more code than
the actual inlined helper.
- Extend AccECN implementation so that is now functionally complete,
also allow the user-space enabling it on a per network namespace
basis.
- Add support for memory providers with large (above 4K) rx buffer.
Paired with hw-gro, larger rx buffer sizes reduce the number of
buffers traversing the stack, dincreasing single stream CPU usage
by up to ~30%.
- Do not add HBH header to Big TCP GSO packets. This simplifies the
RX path, the TX path and the NIC drivers, and is possible because
user-space taps can now interpret correctly such packets without
the HBH hint.
- Allow IPv6 routes to be configured with a gateway address that is
resolved out of a different interface than the one specified,
aligning IPv6 to IPv4 behavior.
- Multi-queue aware sch_cake. This makes it possible to scale the
rate shaper of sch_cake across multiple CPUs, while still enforcing
a single global rate on the interface.
- Add support for the nbcon (new buffer console) infrastructure to
netconsole, enabling lock-free, priority-based console operations
that are safer in crash scenarios.
- Improve the TCP ipv6 output path to cache the flow information,
saving cpu cycles, reducing cache line misses and stack use.
- Improve netfilter packet tracker to resolve clashes for most
protocols, avoiding unneeded drops on rare occasions.
- Add IP6IP6 tunneling acceleration to the flowtable infrastructure.
- Reduce tcp socket size by one cache line.
- Notify neighbour changes atomically, avoiding inconsistencies
between the notification sequence and the actual states sequence.
- Add vsock namespace support, allowing complete isolation of vsocks
across different network namespaces.
- Improve xsk generic performances with cache-alignment-oriented
optimizations.
- Support netconsole automatic target recovery, allowing netconsole
to reestablish targets when underlying low-level interface comes
back online.
Driver API:
- Support for switching the working mode (automatic vs manual) of a
DPLL device via netlink.
- Introduce PHY ports representation to expose multiple front-facing
media ports over a single MAC.
- Introduce "rx-polarity" and "tx-polarity" device tree properties,
to generalize polarity inversion requirements for differential
signaling.
- Add helper to create, prepare and enable managed clocks.
Device drivers:
- Add Huawei hinic3 PF etherner driver.
- Add DWMAC glue driver for Motorcomm YT6801 PCIe ethernet
controller.
- Add ethernet driver for MaxLinear MxL862xx switches
- Remove parallel-port Ethernet driver.
- Convert existing driver timestamp configuration reporting to
hwtstamp_get and remove legacy ioctl().
- Convert existing drivers to .get_rx_ring_count(), simplifing the RX
ring count retrieval. Also remove the legacy fallback path.
- Ethernet high-speed NICs:
- Broadcom (bnxt, bng):
- bnxt: add FW interface update to support FEC stats histogram
and NVRAM defragmentation
- bng: add TSO and H/W GRO support
- nVidia/Mellanox (mlx5):
- improve latency of channel restart operations, reducing the
used H/W resources
- add TSO support for UDP over GRE over VLAN
- add flow counters support for hardware steering (HWS) rules
- use a static memory area to store headers for H/W GRO,
leading to 12% RX tput improvement
- Intel (100G, ice, idpf):
- ice: reorganizes layout of Tx and Rx rings for cacheline
locality and utilizes __cacheline_group* macros on the new
layouts
- ice: introduces Synchronous Ethernet (SyncE) support
- Meta (fbnic):
- adds debugfs for firmware mailbox and tx/rx rings vectors
- Ethernet virtual:
- geneve: introduce GRO/GSO support for double UDP encapsulation
- Ethernet NICs consumer, and embedded:
- Synopsys (stmmac):
- some code refactoring and cleanups
- RealTek (r8169):
- add support for RTL8127ATF (10G Fiber SFP)
- add dash and LTR support
- Airoha:
- AN8811HB 2.5 Gbps phy support
- Freescale (fec):
- add XDP zero-copy support
- Thunderbolt:
- add get link setting support to allow bonding
- Renesas:
- add support for RZ/G3L GBETH SoC
- Ethernet switches:
- Maxlinear:
- support R(G)MII slow rate configuration
- add support for Intel GSW150
- Motorcomm (yt921x):
- add DCB/QoS support
- TI:
- icssm-prueth: support bridging (STP/RSTP) via the switchdev
framework
- Ethernet PHYs:
- Realtek:
- enable SGMII and 2500Base-X in-band auto-negotiation
- simplify and reunify C22/C45 drivers
- Micrel: convert bindings to DT schema
- CAN:
- move skb headroom content into skb extensions, making CAN
metadata access more robust
- CAN drivers:
- rcar_canfd:
- add support for FD-only mode
- add support for the RZ/T2H SoC
- sja1000: cleanup the CAN state handling
- WiFi:
- implement EPPKE/802.1X over auth frames support
- split up drop reasons better, removing generic RX_DROP
- additional FTM capabilities: 6 GHz support, supported number of
spatial streams and supported number of LTF repetitions
- better mac80211 iterators to enumerate resources
- initial UHR (Wi-Fi 8) support for cfg80211/mac80211
- WiFi drivers:
- Qualcomm/Atheros:
- ath11k: support for Channel Frequency Response measurement
- ath12k: a significant driver refactor to support multi-wiphy
devices and and pave the way for future device support in the
same driver (rather than splitting to ath13k)
- ath12k: support for the QCC2072 chipset
- Intel:
- iwlwifi: partial Neighbor Awareness Networking (NAN) support
- iwlwifi: initial support for U-NII-9 and IEEE 802.11bn
- RealTek (rtw89):
- preparations for RTL8922DE support
- Bluetooth:
- implement setsockopt(BT_PHY) to set the connection packet type/PHY
- set link_policy on incoming ACL connections
- Bluetooth drivers:
- btusb: add support for MediaTek7920, Realtek RTL8761BU and 8851BE
- btqca: add WCN6855 firmware priority selection feature"
* tag 'net-next-7.0' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1254 commits)
bnge/bng_re: Add a new HSI
net: macb: Fix tx/rx malfunction after phy link down and up
af_unix: Fix memleak of newsk in unix_stream_connect().
net: ti: icssg-prueth: Add optional dependency on HSR
net: dsa: add basic initial driver for MxL862xx switches
net: mdio: add unlocked mdiodev C45 bus accessors
net: dsa: add tag format for MxL862xx switches
dt-bindings: net: dsa: add MaxLinear MxL862xx
selftests: drivers: net: hw: Modify toeplitz.c to poll for packets
octeontx2-pf: Unregister devlink on probe failure
net: renesas: rswitch: fix forwarding offload statemachine
ionic: Rate limit unknown xcvr type messages
tcp: inet6_csk_xmit() optimization
tcp: populate inet->cork.fl.u.ip6 in tcp_v6_syn_recv_sock()
tcp: populate inet->cork.fl.u.ip6 in tcp_v6_connect()
ipv6: inet6_csk_xmit() and inet6_csk_update_pmtu() use inet->cork.fl.u.ip6
ipv6: use inet->cork.fl.u.ip6 and np->final in ip6_datagram_dst_update()
ipv6: use np->final in inet6_sk_rebuild_header()
ipv6: add daddr/final storage in struct ipv6_pinfo
net: stmmac: qcom-ethqos: fix qcom_ethqos_serdes_powerup()
...
Diffstat (limited to 'drivers/net/wireless/ath/ath11k')
21 files changed, 1956 insertions, 56 deletions
diff --git a/drivers/net/wireless/ath/ath11k/Kconfig b/drivers/net/wireless/ath/ath11k/Kconfig index 659ef134ef16..47dfd39caa89 100644 --- a/drivers/net/wireless/ath/ath11k/Kconfig +++ b/drivers/net/wireless/ath/ath11k/Kconfig @@ -58,3 +58,14 @@ config ATH11K_SPECTRAL Enable ath11k spectral scan support Say Y to enable access to the FFT/spectral data via debugfs. + +config ATH11K_CFR + bool "ath11k channel frequency response support" + depends on ATH11K_DEBUGFS + depends on RELAY + help + Enable ath11k channel frequency response dump support. + This option exposes debugfs nodes that will allow the user + to enable, disable, and dump data. + + Say Y to enable CFR data dump collection via debugfs. diff --git a/drivers/net/wireless/ath/ath11k/Makefile b/drivers/net/wireless/ath/ath11k/Makefile index d9092414b362..b1435fcf3e1b 100644 --- a/drivers/net/wireless/ath/ath11k/Makefile +++ b/drivers/net/wireless/ath/ath11k/Makefile @@ -28,6 +28,7 @@ ath11k-$(CONFIG_THERMAL) += thermal.o ath11k-$(CONFIG_ATH11K_SPECTRAL) += spectral.o ath11k-$(CONFIG_PM) += wow.o ath11k-$(CONFIG_DEV_COREDUMP) += coredump.o +ath11k-$(CONFIG_ATH11K_CFR) += cfr.o obj-$(CONFIG_ATH11K_AHB) += ath11k_ahb.o ath11k_ahb-y += ahb.o diff --git a/drivers/net/wireless/ath/ath11k/cfr.c b/drivers/net/wireless/ath/ath11k/cfr.c new file mode 100644 index 000000000000..61bf1c0884f7 --- /dev/null +++ b/drivers/net/wireless/ath/ath11k/cfr.c @@ -0,0 +1,1023 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear +/* + * Copyright (c) 2020-2021 The Linux Foundation. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include <linux/relay.h> +#include "core.h" +#include "debug.h" + +struct ath11k_dbring *ath11k_cfr_get_dbring(struct ath11k *ar) +{ + if (ar->cfr_enabled) + return &ar->cfr.rx_ring; + + return NULL; +} + +static int ath11k_cfr_calculate_tones_from_dma_hdr(struct ath11k_cfr_dma_hdr *hdr) +{ + u8 bw = FIELD_GET(CFIR_DMA_HDR_INFO1_UPLOAD_PKT_BW, hdr->info1); + u8 preamble = FIELD_GET(CFIR_DMA_HDR_INFO1_PREAMBLE_TYPE, hdr->info1); + + switch (preamble) { + case ATH11K_CFR_PREAMBLE_TYPE_LEGACY: + fallthrough; + case ATH11K_CFR_PREAMBLE_TYPE_VHT: + switch (bw) { + case 0: + return TONES_IN_20MHZ; + case 1: /* DUP40/VHT40 */ + return TONES_IN_40MHZ; + case 2: /* DUP80/VHT80 */ + return TONES_IN_80MHZ; + case 3: /* DUP160/VHT160 */ + return TONES_IN_160MHZ; + default: + return TONES_INVALID; + } + case ATH11K_CFR_PREAMBLE_TYPE_HT: + switch (bw) { + case 0: + return TONES_IN_20MHZ; + case 1: + return TONES_IN_40MHZ; + default: + return TONES_INVALID; + } + default: + return TONES_INVALID; + } +} + +void ath11k_cfr_release_lut_entry(struct ath11k_look_up_table *lut) +{ + memset(lut, 0, sizeof(*lut)); +} + +static void ath11k_cfr_rfs_write(struct ath11k *ar, const void *head, + u32 head_len, const void *data, u32 data_len, + const void *tail, int tail_data) +{ + struct ath11k_cfr *cfr = &ar->cfr; + + if (!cfr->rfs_cfr_capture) + return; + + relay_write(cfr->rfs_cfr_capture, head, head_len); + relay_write(cfr->rfs_cfr_capture, data, data_len); + relay_write(cfr->rfs_cfr_capture, tail, tail_data); + relay_flush(cfr->rfs_cfr_capture); +} + +static void ath11k_cfr_free_pending_dbr_events(struct ath11k *ar) +{ + struct ath11k_cfr *cfr = &ar->cfr; + struct ath11k_look_up_table *lut; + int i; + + if (!cfr->lut) + return; + + for (i = 0; i < cfr->lut_num; i++) { + lut = &cfr->lut[i]; + if (lut->dbr_recv && !lut->tx_recv && + lut->dbr_tstamp < cfr->last_success_tstamp) { + ath11k_dbring_bufs_replenish(ar, &cfr->rx_ring, lut->buff, + WMI_DIRECT_BUF_CFR); + ath11k_cfr_release_lut_entry(lut); + cfr->flush_dbr_cnt++; + } + } +} + +/** + * ath11k_cfr_correlate_and_relay() - Correlate and relay CFR events + * @ar: Pointer to ath11k structure + * @lut: Lookup table for correlation + * @event_type: Type of event received (TX or DBR) + * + * Correlates WMI_PDEV_DMA_RING_BUF_RELEASE_EVENT (DBR) and + * WMI_PEER_CFR_CAPTURE_EVENT (TX capture) by PPDU ID. If both events + * are present and the PPDU IDs match, returns CORRELATE_STATUS_RELEASE + * to relay thecorrelated data to userspace. Otherwise returns + * CORRELATE_STATUS_HOLD to wait for the other event. + * + * Also checks pending DBR events and clears them when no corresponding TX + * capture event is received for the PPDU. + * + * Return: CORRELATE_STATUS_RELEASE or CORRELATE_STATUS_HOLD + */ + +static enum ath11k_cfr_correlate_status +ath11k_cfr_correlate_and_relay(struct ath11k *ar, + struct ath11k_look_up_table *lut, + u8 event_type) +{ + enum ath11k_cfr_correlate_status status; + struct ath11k_cfr *cfr = &ar->cfr; + u64 diff; + + if (event_type == ATH11K_CORRELATE_TX_EVENT) { + if (lut->tx_recv) + cfr->cfr_dma_aborts++; + cfr->tx_evt_cnt++; + lut->tx_recv = true; + } else if (event_type == ATH11K_CORRELATE_DBR_EVENT) { + cfr->dbr_evt_cnt++; + lut->dbr_recv = true; + } + + if (lut->dbr_recv && lut->tx_recv) { + if (lut->dbr_ppdu_id == lut->tx_ppdu_id) { + /* + * 64-bit counters make wraparound highly improbable, + * wraparound handling is omitted. + */ + cfr->last_success_tstamp = lut->dbr_tstamp; + if (lut->dbr_tstamp > lut->txrx_tstamp) { + diff = lut->dbr_tstamp - lut->txrx_tstamp; + ath11k_dbg(ar->ab, ATH11K_DBG_CFR, + "txrx event -> dbr event delay = %u ms", + jiffies_to_msecs(diff)); + } else if (lut->txrx_tstamp > lut->dbr_tstamp) { + diff = lut->txrx_tstamp - lut->dbr_tstamp; + ath11k_dbg(ar->ab, ATH11K_DBG_CFR, + "dbr event -> txrx event delay = %u ms", + jiffies_to_msecs(diff)); + } + + ath11k_cfr_free_pending_dbr_events(ar); + + cfr->release_cnt++; + status = ATH11K_CORRELATE_STATUS_RELEASE; + } else { + /* + * Discard TXRX event on PPDU ID mismatch because multiple PPDUs + * may share the same DMA address due to ucode aborts. + */ + + ath11k_dbg(ar->ab, ATH11K_DBG_CFR, + "Received dbr event twice for the same lut entry"); + lut->tx_recv = false; + lut->tx_ppdu_id = 0; + cfr->clear_txrx_event++; + cfr->cfr_dma_aborts++; + status = ATH11K_CORRELATE_STATUS_HOLD; + } + } else { + status = ATH11K_CORRELATE_STATUS_HOLD; + } + + return status; +} + +static int ath11k_cfr_process_data(struct ath11k *ar, + struct ath11k_dbring_data *param) +{ + u32 end_magic = ATH11K_CFR_END_MAGIC; + struct ath11k_csi_cfr_header *header; + struct ath11k_cfr_dma_hdr *dma_hdr; + struct ath11k_cfr *cfr = &ar->cfr; + struct ath11k_look_up_table *lut; + struct ath11k_base *ab = ar->ab; + u32 buf_id, tones, length; + u8 num_chains; + int status; + u8 *data; + + data = param->data; + buf_id = param->buf_id; + + if (param->data_sz < sizeof(*dma_hdr)) + return -EINVAL; + + dma_hdr = (struct ath11k_cfr_dma_hdr *)data; + + tones = ath11k_cfr_calculate_tones_from_dma_hdr(dma_hdr); + if (tones == TONES_INVALID) { + ath11k_warn(ar->ab, "Number of tones received is invalid\n"); + return -EINVAL; + } + + num_chains = FIELD_GET(CFIR_DMA_HDR_INFO1_NUM_CHAINS, + dma_hdr->info1); + + length = sizeof(*dma_hdr); + length += tones * (num_chains + 1); + + spin_lock_bh(&cfr->lut_lock); + + if (!cfr->lut) { + spin_unlock_bh(&cfr->lut_lock); + return -EINVAL; + } + + lut = &cfr->lut[buf_id]; + + ath11k_dbg_dump(ab, ATH11K_DBG_CFR_DUMP, "data_from_buf_rel:", "", + data, length); + + lut->buff = param->buff; + lut->data = data; + lut->data_len = length; + lut->dbr_ppdu_id = dma_hdr->phy_ppdu_id; + lut->dbr_tstamp = jiffies; + + memcpy(&lut->hdr, dma_hdr, sizeof(*dma_hdr)); + + header = &lut->header; + header->meta_data.channel_bw = FIELD_GET(CFIR_DMA_HDR_INFO1_UPLOAD_PKT_BW, + dma_hdr->info1); + header->meta_data.length = length; + + status = ath11k_cfr_correlate_and_relay(ar, lut, + ATH11K_CORRELATE_DBR_EVENT); + if (status == ATH11K_CORRELATE_STATUS_RELEASE) { + ath11k_dbg(ab, ATH11K_DBG_CFR, + "releasing CFR data to user space"); + ath11k_cfr_rfs_write(ar, &lut->header, + sizeof(struct ath11k_csi_cfr_header), + lut->data, lut->data_len, + &end_magic, sizeof(u32)); + ath11k_cfr_release_lut_entry(lut); + } else if (status == ATH11K_CORRELATE_STATUS_HOLD) { + ath11k_dbg(ab, ATH11K_DBG_CFR, + "tx event is not yet received holding the buf"); + } + + spin_unlock_bh(&cfr->lut_lock); + + return status; +} + +static void ath11k_cfr_fill_hdr_info(struct ath11k *ar, + struct ath11k_csi_cfr_header *header, + struct ath11k_cfr_peer_tx_param *params) +{ + struct ath11k_cfr *cfr; + + cfr = &ar->cfr; + header->cfr_metadata_version = ATH11K_CFR_META_VERSION_4; + header->cfr_data_version = ATH11K_CFR_DATA_VERSION_1; + header->cfr_metadata_len = sizeof(struct cfr_metadata); + header->chip_type = ar->ab->hw_rev; + header->meta_data.status = FIELD_GET(WMI_CFR_PEER_CAPTURE_STATUS, + params->status); + header->meta_data.capture_bw = params->bandwidth; + + /* + * FW reports phymode will always be HE mode. + * Replace it with cached phy mode during peer assoc + */ + header->meta_data.phy_mode = cfr->phymode; + + header->meta_data.prim20_chan = params->primary_20mhz_chan; + header->meta_data.center_freq1 = params->band_center_freq1; + header->meta_data.center_freq2 = params->band_center_freq2; + + /* + * CFR capture is triggered by the ACK of a QoS Null frame: + * - 20 MHz: Legacy ACK + * - 40/80/160 MHz: DUP Legacy ACK + */ + header->meta_data.capture_mode = params->bandwidth ? + ATH11K_CFR_CAPTURE_DUP_LEGACY_ACK : ATH11K_CFR_CAPTURE_LEGACY_ACK; + header->meta_data.capture_type = params->capture_method; + header->meta_data.num_rx_chain = ar->num_rx_chains; + header->meta_data.sts_count = params->spatial_streams; + header->meta_data.timestamp = params->timestamp_us; + ether_addr_copy(header->meta_data.peer_addr, params->peer_mac_addr); + memcpy(header->meta_data.chain_rssi, params->chain_rssi, + sizeof(params->chain_rssi)); + memcpy(header->meta_data.chain_phase, params->chain_phase, + sizeof(params->chain_phase)); + memcpy(header->meta_data.agc_gain, params->agc_gain, + sizeof(params->agc_gain)); +} + +int ath11k_process_cfr_capture_event(struct ath11k_base *ab, + struct ath11k_cfr_peer_tx_param *params) +{ + struct ath11k_look_up_table *lut = NULL; + u32 end_magic = ATH11K_CFR_END_MAGIC; + struct ath11k_csi_cfr_header *header; + struct ath11k_dbring_element *buff; + struct ath11k_cfr *cfr; + dma_addr_t buf_addr; + struct ath11k *ar; + u8 tx_status; + int status; + int i; + + rcu_read_lock(); + ar = ath11k_mac_get_ar_by_vdev_id(ab, params->vdev_id); + if (!ar) { + rcu_read_unlock(); + ath11k_warn(ab, "Failed to get ar for vdev id %d\n", + params->vdev_id); + return -ENOENT; + } + + cfr = &ar->cfr; + rcu_read_unlock(); + + if (WMI_CFR_CAPTURE_STATUS_PEER_PS & params->status) { + ath11k_warn(ab, "CFR capture failed as peer %pM is in powersave", + params->peer_mac_addr); + return -EINVAL; + } + + if (!(WMI_CFR_PEER_CAPTURE_STATUS & params->status)) { + ath11k_warn(ab, "CFR capture failed for the peer : %pM", + params->peer_mac_addr); + cfr->tx_peer_status_cfr_fail++; + return -EINVAL; + } + + tx_status = FIELD_GET(WMI_CFR_FRAME_TX_STATUS, params->status); + if (tx_status != WMI_FRAME_TX_STATUS_OK) { + ath11k_warn(ab, "WMI tx status %d for the peer %pM", + tx_status, params->peer_mac_addr); + cfr->tx_evt_status_cfr_fail++; + return -EINVAL; + } + + buf_addr = (((u64)FIELD_GET(WMI_CFR_CORRELATION_INFO2_BUF_ADDR_HIGH, + params->correlation_info_2)) << 32) | + params->correlation_info_1; + + spin_lock_bh(&cfr->lut_lock); + + if (!cfr->lut) { + spin_unlock_bh(&cfr->lut_lock); + return -EINVAL; + } + + for (i = 0; i < cfr->lut_num; i++) { + struct ath11k_look_up_table *temp = &cfr->lut[i]; + + if (temp->dbr_address == buf_addr) { + lut = &cfr->lut[i]; + break; + } + } + + if (!lut) { + spin_unlock_bh(&cfr->lut_lock); + ath11k_warn(ab, "lut failure to process tx event\n"); + cfr->tx_dbr_lookup_fail++; + return -EINVAL; + } + + lut->tx_ppdu_id = FIELD_GET(WMI_CFR_CORRELATION_INFO2_PPDU_ID, + params->correlation_info_2); + lut->txrx_tstamp = jiffies; + + header = &lut->header; + header->start_magic_num = ATH11K_CFR_START_MAGIC; + header->vendorid = VENDOR_QCA; + header->platform_type = PLATFORM_TYPE_ARM; + + ath11k_cfr_fill_hdr_info(ar, header, params); + + status = ath11k_cfr_correlate_and_relay(ar, lut, + ATH11K_CORRELATE_TX_EVENT); + if (status == ATH11K_CORRELATE_STATUS_RELEASE) { + ath11k_dbg(ab, ATH11K_DBG_CFR, + "Releasing CFR data to user space"); + ath11k_cfr_rfs_write(ar, &lut->header, + sizeof(struct ath11k_csi_cfr_header), + lut->data, lut->data_len, + &end_magic, sizeof(u32)); + buff = lut->buff; + ath11k_cfr_release_lut_entry(lut); + + ath11k_dbring_bufs_replenish(ar, &cfr->rx_ring, buff, + WMI_DIRECT_BUF_CFR); + } else if (status == ATH11K_CORRELATE_STATUS_HOLD) { + ath11k_dbg(ab, ATH11K_DBG_CFR, + "dbr event is not yet received holding buf\n"); + } + + spin_unlock_bh(&cfr->lut_lock); + + return 0; +} + +/* Helper function to check whether the given peer mac address + * is in unassociated peer pool or not. + */ +bool ath11k_cfr_peer_is_in_cfr_unassoc_pool(struct ath11k *ar, const u8 *peer_mac) +{ + struct ath11k_cfr *cfr = &ar->cfr; + struct cfr_unassoc_pool_entry *entry; + int i; + + if (!ar->cfr_enabled) + return false; + + spin_lock_bh(&cfr->lock); + for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) { + entry = &cfr->unassoc_pool[i]; + if (!entry->is_valid) + continue; + + if (ether_addr_equal(peer_mac, entry->peer_mac)) { + spin_unlock_bh(&cfr->lock); + return true; + } + } + + spin_unlock_bh(&cfr->lock); + + return false; +} + +void ath11k_cfr_update_unassoc_pool_entry(struct ath11k *ar, + const u8 *peer_mac) +{ + struct ath11k_cfr *cfr = &ar->cfr; + struct cfr_unassoc_pool_entry *entry; + int i; + + spin_lock_bh(&cfr->lock); + for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) { + entry = &cfr->unassoc_pool[i]; + if (!entry->is_valid) + continue; + + if (ether_addr_equal(peer_mac, entry->peer_mac) && + entry->period == 0) { + memset(entry->peer_mac, 0, ETH_ALEN); + entry->is_valid = false; + cfr->cfr_enabled_peer_cnt--; + break; + } + } + + spin_unlock_bh(&cfr->lock); +} + +void ath11k_cfr_decrement_peer_count(struct ath11k *ar, + struct ath11k_sta *arsta) +{ + struct ath11k_cfr *cfr = &ar->cfr; + + spin_lock_bh(&cfr->lock); + + if (arsta->cfr_capture.cfr_enable) + cfr->cfr_enabled_peer_cnt--; + + spin_unlock_bh(&cfr->lock); +} + +static enum ath11k_wmi_cfr_capture_bw +ath11k_cfr_bw_to_fw_cfr_bw(enum ath11k_cfr_capture_bw bw) +{ + switch (bw) { + case ATH11K_CFR_CAPTURE_BW_20: + return WMI_PEER_CFR_CAPTURE_BW_20; + case ATH11K_CFR_CAPTURE_BW_40: + return WMI_PEER_CFR_CAPTURE_BW_40; + case ATH11K_CFR_CAPTURE_BW_80: + return WMI_PEER_CFR_CAPTURE_BW_80; + default: + return WMI_PEER_CFR_CAPTURE_BW_MAX; + } +} + +static enum ath11k_wmi_cfr_capture_method +ath11k_cfr_method_to_fw_cfr_method(enum ath11k_cfr_capture_method method) +{ + switch (method) { + case ATH11K_CFR_CAPTURE_METHOD_NULL_FRAME: + return WMI_CFR_CAPTURE_METHOD_NULL_FRAME; + case ATH11K_CFR_CAPTURE_METHOD_NULL_FRAME_WITH_PHASE: + return WMI_CFR_CAPTURE_METHOD_NULL_FRAME_WITH_PHASE; + case ATH11K_CFR_CAPTURE_METHOD_PROBE_RESP: + return WMI_CFR_CAPTURE_METHOD_PROBE_RESP; + default: + return WMI_CFR_CAPTURE_METHOD_MAX; + } +} + +int ath11k_cfr_send_peer_cfr_capture_cmd(struct ath11k *ar, + struct ath11k_sta *arsta, + struct ath11k_per_peer_cfr_capture *params, + const u8 *peer_mac) +{ + struct ath11k_cfr *cfr = &ar->cfr; + struct wmi_peer_cfr_capture_conf_arg arg; + enum ath11k_wmi_cfr_capture_bw bw; + enum ath11k_wmi_cfr_capture_method method; + int ret = 0; + + if (cfr->cfr_enabled_peer_cnt >= ATH11K_MAX_CFR_ENABLED_CLIENTS && + !arsta->cfr_capture.cfr_enable) { + ath11k_err(ar->ab, "CFR enable peer threshold reached %u\n", + cfr->cfr_enabled_peer_cnt); + return -ENOSPC; + } + + if (params->cfr_enable == arsta->cfr_capture.cfr_enable && + params->cfr_period == arsta->cfr_capture.cfr_period && + params->cfr_method == arsta->cfr_capture.cfr_method && + params->cfr_bw == arsta->cfr_capture.cfr_bw) + return ret; + + if (!params->cfr_enable && !arsta->cfr_capture.cfr_enable) + return ret; + + bw = ath11k_cfr_bw_to_fw_cfr_bw(params->cfr_bw); + if (bw >= WMI_PEER_CFR_CAPTURE_BW_MAX) { + ath11k_warn(ar->ab, "FW doesn't support configured bw %d\n", + params->cfr_bw); + return -EINVAL; + } + + method = ath11k_cfr_method_to_fw_cfr_method(params->cfr_method); + if (method >= WMI_CFR_CAPTURE_METHOD_MAX) { + ath11k_warn(ar->ab, "FW doesn't support configured method %d\n", + params->cfr_method); + return -EINVAL; + } + + arg.request = params->cfr_enable; + arg.periodicity = params->cfr_period; + arg.bw = bw; + arg.method = method; + + ret = ath11k_wmi_peer_set_cfr_capture_conf(ar, arsta->arvif->vdev_id, + peer_mac, &arg); + if (ret) { + ath11k_warn(ar->ab, + "failed to send cfr capture info: vdev_id %u peer %pM: %d\n", + arsta->arvif->vdev_id, peer_mac, ret); + return ret; + } + + spin_lock_bh(&cfr->lock); + + if (params->cfr_enable && + params->cfr_enable != arsta->cfr_capture.cfr_enable) + cfr->cfr_enabled_peer_cnt++; + else if (!params->cfr_enable) + cfr->cfr_enabled_peer_cnt--; + + spin_unlock_bh(&cfr->lock); + + arsta->cfr_capture.cfr_enable = params->cfr_enable; + arsta->cfr_capture.cfr_period = params->cfr_period; + arsta->cfr_capture.cfr_method = params->cfr_method; + arsta->cfr_capture.cfr_bw = params->cfr_bw; + + return ret; +} + +void ath11k_cfr_update_unassoc_pool(struct ath11k *ar, + struct ath11k_per_peer_cfr_capture *params, + u8 *peer_mac) +{ + struct ath11k_cfr *cfr = &ar->cfr; + struct cfr_unassoc_pool_entry *entry; + int available_idx = -1; + int i; + + guard(spinlock_bh)(&cfr->lock); + + if (!params->cfr_enable) { + for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) { + entry = &cfr->unassoc_pool[i]; + if (ether_addr_equal(peer_mac, entry->peer_mac)) { + memset(entry->peer_mac, 0, ETH_ALEN); + entry->is_valid = false; + cfr->cfr_enabled_peer_cnt--; + break; + } + } + return; + } + + if (cfr->cfr_enabled_peer_cnt >= ATH11K_MAX_CFR_ENABLED_CLIENTS) { + ath11k_info(ar->ab, "Max cfr peer threshold reached\n"); + return; + } + + for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) { + entry = &cfr->unassoc_pool[i]; + + if (ether_addr_equal(peer_mac, entry->peer_mac)) { + ath11k_info(ar->ab, + "peer entry already present updating params\n"); + entry->period = params->cfr_period; + available_idx = -1; + break; + } + + if (available_idx < 0 && !entry->is_valid) + available_idx = i; + } + + if (available_idx >= 0) { + entry = &cfr->unassoc_pool[available_idx]; + ether_addr_copy(entry->peer_mac, peer_mac); + entry->period = params->cfr_period; + entry->is_valid = true; + cfr->cfr_enabled_peer_cnt++; + } +} + +static ssize_t ath11k_read_file_enable_cfr(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k *ar = file->private_data; + char buf[32] = {}; + size_t len; + + mutex_lock(&ar->conf_mutex); + len = scnprintf(buf, sizeof(buf), "%d\n", ar->cfr_enabled); + mutex_unlock(&ar->conf_mutex); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t ath11k_write_file_enable_cfr(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct ath11k *ar = file->private_data; + u32 enable_cfr; + int ret; + + if (kstrtouint_from_user(ubuf, count, 0, &enable_cfr)) + return -EINVAL; + + guard(mutex)(&ar->conf_mutex); + + if (ar->state != ATH11K_STATE_ON) + return -ENETDOWN; + + if (enable_cfr > 1) + return -EINVAL; + + if (ar->cfr_enabled == enable_cfr) + return count; + + ret = ath11k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_PER_PEER_CFR_ENABLE, + enable_cfr, ar->pdev->pdev_id); + if (ret) { + ath11k_warn(ar->ab, + "Failed to enable/disable per peer cfr %d\n", ret); + return ret; + } + + ar->cfr_enabled = enable_cfr; + + return count; +} + +static const struct file_operations fops_enable_cfr = { + .read = ath11k_read_file_enable_cfr, + .write = ath11k_write_file_enable_cfr, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath11k_write_file_cfr_unassoc(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct ath11k *ar = file->private_data; + struct ath11k_cfr *cfr = &ar->cfr; + struct cfr_unassoc_pool_entry *entry; + char buf[64] = {}; + u8 peer_mac[6]; + u32 cfr_capture_enable; + u32 cfr_capture_period; + int available_idx = -1; + int ret, i; + + simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, ubuf, count); + + guard(mutex)(&ar->conf_mutex); + guard(spinlock_bh)(&cfr->lock); + + if (ar->state != ATH11K_STATE_ON) + return -ENETDOWN; + + if (!ar->cfr_enabled) { + ath11k_err(ar->ab, "CFR is not enabled on this pdev %d\n", + ar->pdev_idx); + return -EINVAL; + } + + ret = sscanf(buf, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx %u %u", + &peer_mac[0], &peer_mac[1], &peer_mac[2], &peer_mac[3], + &peer_mac[4], &peer_mac[5], &cfr_capture_enable, + &cfr_capture_period); + + if (ret < 1) + return -EINVAL; + + if (cfr_capture_enable && ret != 8) + return -EINVAL; + + if (!cfr_capture_enable) { + for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) { + entry = &cfr->unassoc_pool[i]; + if (ether_addr_equal(peer_mac, entry->peer_mac)) { + memset(entry->peer_mac, 0, ETH_ALEN); + entry->is_valid = false; + cfr->cfr_enabled_peer_cnt--; + } + } + + return count; + } + + if (cfr->cfr_enabled_peer_cnt >= ATH11K_MAX_CFR_ENABLED_CLIENTS) { + ath11k_info(ar->ab, "Max cfr peer threshold reached\n"); + return count; + } + + for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) { + entry = &cfr->unassoc_pool[i]; + + if (available_idx < 0 && !entry->is_valid) + available_idx = i; + + if (ether_addr_equal(peer_mac, entry->peer_mac)) { + ath11k_info(ar->ab, + "peer entry already present updating params\n"); + entry->period = cfr_capture_period; + return count; + } + } + + if (available_idx >= 0) { + entry = &cfr->unassoc_pool[available_idx]; + ether_addr_copy(entry->peer_mac, peer_mac); + entry->period = cfr_capture_period; + entry->is_valid = true; + cfr->cfr_enabled_peer_cnt++; + } + + return count; +} + +static ssize_t ath11k_read_file_cfr_unassoc(struct file *file, + char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct ath11k *ar = file->private_data; + struct ath11k_cfr *cfr = &ar->cfr; + struct cfr_unassoc_pool_entry *entry; + char buf[512] = {}; + int len = 0, i; + + spin_lock_bh(&cfr->lock); + + for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) { + entry = &cfr->unassoc_pool[i]; + if (entry->is_valid) + len += scnprintf(buf + len, sizeof(buf) - len, + "peer: %pM period: %u\n", + entry->peer_mac, entry->period); + } + + spin_unlock_bh(&cfr->lock); + + return simple_read_from_buffer(ubuf, count, ppos, buf, len); +} + +static const struct file_operations fops_configure_cfr_unassoc = { + .write = ath11k_write_file_cfr_unassoc, + .read = ath11k_read_file_cfr_unassoc, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static void ath11k_cfr_debug_unregister(struct ath11k *ar) +{ + debugfs_remove(ar->cfr.enable_cfr); + ar->cfr.enable_cfr = NULL; + debugfs_remove(ar->cfr.cfr_unassoc); + ar->cfr.cfr_unassoc = NULL; + + relay_close(ar->cfr.rfs_cfr_capture); + ar->cfr.rfs_cfr_capture = NULL; +} + +static struct dentry *ath11k_cfr_create_buf_file_handler(const char *filename, + struct dentry *parent, + umode_t mode, + struct rchan_buf *buf, + int *is_global) +{ + struct dentry *buf_file; + + buf_file = debugfs_create_file(filename, mode, parent, buf, + &relay_file_operations); + *is_global = 1; + return buf_file; +} + +static int ath11k_cfr_remove_buf_file_handler(struct dentry *dentry) +{ + debugfs_remove(dentry); + + return 0; +} + +static const struct rchan_callbacks rfs_cfr_capture_cb = { + .create_buf_file = ath11k_cfr_create_buf_file_handler, + .remove_buf_file = ath11k_cfr_remove_buf_file_handler, +}; + +static void ath11k_cfr_debug_register(struct ath11k *ar) +{ + ar->cfr.rfs_cfr_capture = relay_open("cfr_capture", + ar->debug.debugfs_pdev, + ar->ab->hw_params.cfr_stream_buf_size, + ar->ab->hw_params.cfr_num_stream_bufs, + &rfs_cfr_capture_cb, NULL); + + ar->cfr.enable_cfr = debugfs_create_file("enable_cfr", 0600, + ar->debug.debugfs_pdev, ar, + &fops_enable_cfr); + + ar->cfr.cfr_unassoc = debugfs_create_file("cfr_unassoc", 0600, + ar->debug.debugfs_pdev, ar, + &fops_configure_cfr_unassoc); +} + +void ath11k_cfr_lut_update_paddr(struct ath11k *ar, dma_addr_t paddr, + u32 buf_id) +{ + struct ath11k_cfr *cfr = &ar->cfr; + + if (cfr->lut) + cfr->lut[buf_id].dbr_address = paddr; +} + +void ath11k_cfr_update_phymode(struct ath11k *ar, enum wmi_phy_mode phymode) +{ + struct ath11k_cfr *cfr = &ar->cfr; + + cfr->phymode = phymode; +} + +static void ath11k_cfr_ring_free(struct ath11k *ar) +{ + struct ath11k_cfr *cfr = &ar->cfr; + + ath11k_dbring_buf_cleanup(ar, &cfr->rx_ring); + ath11k_dbring_srng_cleanup(ar, &cfr->rx_ring); +} + +static int ath11k_cfr_ring_alloc(struct ath11k *ar, + struct ath11k_dbring_cap *db_cap) +{ + struct ath11k_cfr *cfr = &ar->cfr; + int ret; + + ret = ath11k_dbring_srng_setup(ar, &cfr->rx_ring, + ATH11K_CFR_NUM_RING_ENTRIES, + db_cap->min_elem); + if (ret) { + ath11k_warn(ar->ab, "failed to setup db ring: %d\n", ret); + return ret; + } + + ath11k_dbring_set_cfg(ar, &cfr->rx_ring, + ATH11K_CFR_NUM_RESP_PER_EVENT, + ATH11K_CFR_EVENT_TIMEOUT_MS, + ath11k_cfr_process_data); + + ret = ath11k_dbring_buf_setup(ar, &cfr->rx_ring, db_cap); + if (ret) { + ath11k_warn(ar->ab, "failed to setup db ring buffer: %d\n", ret); + goto srng_cleanup; + } + + ret = ath11k_dbring_wmi_cfg_setup(ar, &cfr->rx_ring, WMI_DIRECT_BUF_CFR); + if (ret) { + ath11k_warn(ar->ab, "failed to setup db ring cfg: %d\n", ret); + goto buffer_cleanup; + } + + return 0; + +buffer_cleanup: + ath11k_dbring_buf_cleanup(ar, &cfr->rx_ring); +srng_cleanup: + ath11k_dbring_srng_cleanup(ar, &cfr->rx_ring); + return ret; +} + +void ath11k_cfr_deinit(struct ath11k_base *ab) +{ + struct ath11k_cfr *cfr; + struct ath11k *ar; + int i; + + if (!test_bit(WMI_TLV_SERVICE_CFR_CAPTURE_SUPPORT, ab->wmi_ab.svc_map) || + !ab->hw_params.cfr_support) + return; + + for (i = 0; i < ab->num_radios; i++) { + ar = ab->pdevs[i].ar; + cfr = &ar->cfr; + + if (!cfr->enabled) + continue; + + ath11k_cfr_debug_unregister(ar); + ath11k_cfr_ring_free(ar); + + spin_lock_bh(&cfr->lut_lock); + kfree(cfr->lut); + cfr->lut = NULL; + cfr->enabled = false; + spin_unlock_bh(&cfr->lut_lock); + } +} + +int ath11k_cfr_init(struct ath11k_base *ab) +{ + struct ath11k_dbring_cap db_cap; + struct ath11k_cfr *cfr; + u32 num_lut_entries; + struct ath11k *ar; + int i, ret; + + if (!test_bit(WMI_TLV_SERVICE_CFR_CAPTURE_SUPPORT, ab->wmi_ab.svc_map) || + !ab->hw_params.cfr_support) + return 0; + + for (i = 0; i < ab->num_radios; i++) { + ar = ab->pdevs[i].ar; + cfr = &ar->cfr; + + ret = ath11k_dbring_get_cap(ar->ab, ar->pdev_idx, + WMI_DIRECT_BUF_CFR, &db_cap); + if (ret) + continue; + + idr_init(&cfr->rx_ring.bufs_idr); + spin_lock_init(&cfr->rx_ring.idr_lock); + spin_lock_init(&cfr->lock); + spin_lock_init(&cfr->lut_lock); + + num_lut_entries = min_t(u32, CFR_MAX_LUT_ENTRIES, db_cap.min_elem); + cfr->lut = kcalloc(num_lut_entries, sizeof(*cfr->lut), + GFP_KERNEL); + if (!cfr->lut) { + ret = -ENOMEM; + goto err; + } + + ret = ath11k_cfr_ring_alloc(ar, &db_cap); + if (ret) { + ath11k_warn(ab, "failed to init cfr ring for pdev %d: %d\n", + i, ret); + spin_lock_bh(&cfr->lut_lock); + kfree(cfr->lut); + cfr->lut = NULL; + cfr->enabled = false; + spin_unlock_bh(&cfr->lut_lock); + goto err; + } + + cfr->lut_num = num_lut_entries; + cfr->enabled = true; + + ath11k_cfr_debug_register(ar); + } + + return 0; + +err: + for (i = i - 1; i >= 0; i--) { + ar = ab->pdevs[i].ar; + cfr = &ar->cfr; + + if (!cfr->enabled) + continue; + + ath11k_cfr_debug_unregister(ar); + ath11k_cfr_ring_free(ar); + + spin_lock_bh(&cfr->lut_lock); + kfree(cfr->lut); + cfr->lut = NULL; + cfr->enabled = false; + spin_unlock_bh(&cfr->lut_lock); + } + return ret; +} diff --git a/drivers/net/wireless/ath/ath11k/cfr.h b/drivers/net/wireless/ath/ath11k/cfr.h new file mode 100644 index 000000000000..94fcb706f2ef --- /dev/null +++ b/drivers/net/wireless/ath/ath11k/cfr.h @@ -0,0 +1,308 @@ +/* SPDX-License-Identifier: BSD-3-Clause-Clear */ +/* + * Copyright (c) 2020-2021 The Linux Foundation. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef ATH11K_CFR_H +#define ATH11K_CFR_H + +#include "dbring.h" +#include "wmi.h" + +#define ATH11K_CFR_NUM_RESP_PER_EVENT 1 +#define ATH11K_CFR_EVENT_TIMEOUT_MS 1 +#define ATH11K_CFR_NUM_RING_ENTRIES 1 + +#define ATH11K_MAX_CFR_ENABLED_CLIENTS 10 +#define CFR_MAX_LUT_ENTRIES 136 + +#define HOST_MAX_CHAINS 8 + +enum ath11k_cfr_correlate_event_type { + ATH11K_CORRELATE_DBR_EVENT, + ATH11K_CORRELATE_TX_EVENT, +}; + +struct ath11k_sta; +struct ath11k_per_peer_cfr_capture; + +#define ATH11K_CFR_START_MAGIC 0xDEADBEAF +#define ATH11K_CFR_END_MAGIC 0xBEAFDEAD + +#define VENDOR_QCA 0x8cfdf0 +#define PLATFORM_TYPE_ARM 2 + +enum ath11k_cfr_meta_version { + ATH11K_CFR_META_VERSION_NONE, + ATH11K_CFR_META_VERSION_1, + ATH11K_CFR_META_VERSION_2, + ATH11K_CFR_META_VERSION_3, + ATH11K_CFR_META_VERSION_4, + ATH11K_CFR_META_VERSION_MAX = 0xFF, +}; + +enum ath11k_cfr_data_version { + ATH11K_CFR_DATA_VERSION_NONE, + ATH11K_CFR_DATA_VERSION_1, + ATH11K_CFR_DATA_VERSION_MAX = 0xFF, +}; + +enum ath11k_cfr_capture_ack_mode { + ATH11K_CFR_CAPTURE_LEGACY_ACK, + ATH11K_CFR_CAPTURE_DUP_LEGACY_ACK, + ATH11K_CFR_CAPTURE_HT_ACK, + ATH11K_CFR_CAPTURE_VHT_ACK, + + /*Always keep this at last*/ + ATH11K_CFR_CAPTURE_INVALID_ACK +}; + +enum ath11k_cfr_correlate_status { + ATH11K_CORRELATE_STATUS_RELEASE, + ATH11K_CORRELATE_STATUS_HOLD, + ATH11K_CORRELATE_STATUS_ERR, +}; + +enum ath11k_cfr_preamble_type { + ATH11K_CFR_PREAMBLE_TYPE_LEGACY, + ATH11K_CFR_PREAMBLE_TYPE_HT, + ATH11K_CFR_PREAMBLE_TYPE_VHT, +}; + +struct ath11k_cfr_peer_tx_param { + u32 capture_method; + u32 vdev_id; + u8 peer_mac_addr[ETH_ALEN]; + u32 primary_20mhz_chan; + u32 bandwidth; + u32 phy_mode; + u32 band_center_freq1; + u32 band_center_freq2; + u32 spatial_streams; + u32 correlation_info_1; + u32 correlation_info_2; + u32 status; + u32 timestamp_us; + u32 counter; + u32 chain_rssi[WMI_MAX_CHAINS]; + u16 chain_phase[WMI_MAX_CHAINS]; + u32 cfo_measurement; + u8 agc_gain[HOST_MAX_CHAINS]; + u32 rx_start_ts; +}; + +struct cfr_metadata { + u8 peer_addr[ETH_ALEN]; + u8 status; + u8 capture_bw; + u8 channel_bw; + u8 phy_mode; + u16 prim20_chan; + u16 center_freq1; + u16 center_freq2; + u8 capture_mode; + u8 capture_type; + u8 sts_count; + u8 num_rx_chain; + u32 timestamp; + u32 length; + u32 chain_rssi[HOST_MAX_CHAINS]; + u16 chain_phase[HOST_MAX_CHAINS]; + u32 cfo_measurement; + u8 agc_gain[HOST_MAX_CHAINS]; + u32 rx_start_ts; +} __packed; + +struct ath11k_csi_cfr_header { + u32 start_magic_num; + u32 vendorid; + u8 cfr_metadata_version; + u8 cfr_data_version; + u8 chip_type; + u8 platform_type; + u32 cfr_metadata_len; + struct cfr_metadata meta_data; +} __packed; + +#define TONES_IN_20MHZ 256 +#define TONES_IN_40MHZ 512 +#define TONES_IN_80MHZ 1024 +#define TONES_IN_160MHZ 2048 /* 160 MHz isn't supported yet */ +#define TONES_INVALID 0 + +#define CFIR_DMA_HDR_INFO0_TAG GENMASK(7, 0) +#define CFIR_DMA_HDR_INFO0_LEN GENMASK(13, 8) + +#define CFIR_DMA_HDR_INFO1_UPLOAD_DONE GENMASK(0, 0) +#define CFIR_DMA_HDR_INFO1_CAPTURE_TYPE GENMASK(3, 1) +#define CFIR_DMA_HDR_INFO1_PREAMBLE_TYPE GENMASK(5, 4) +#define CFIR_DMA_HDR_INFO1_NSS GENMASK(8, 6) +#define CFIR_DMA_HDR_INFO1_NUM_CHAINS GENMASK(11, 9) +#define CFIR_DMA_HDR_INFO1_UPLOAD_PKT_BW GENMASK(14, 12) +#define CFIR_DMA_HDR_INFO1_SW_PEER_ID_VALID GENMASK(15, 15) + +struct ath11k_cfr_dma_hdr { + u16 info0; + u16 info1; + u16 sw_peer_id; + u16 phy_ppdu_id; +}; + +struct ath11k_look_up_table { + bool dbr_recv; + bool tx_recv; + u8 *data; + u32 data_len; + u16 dbr_ppdu_id; + u16 tx_ppdu_id; + dma_addr_t dbr_address; + struct ath11k_csi_cfr_header header; + struct ath11k_cfr_dma_hdr hdr; + u64 txrx_tstamp; + u64 dbr_tstamp; + u32 header_length; + u32 payload_length; + struct ath11k_dbring_element *buff; +}; + +struct cfr_unassoc_pool_entry { + u8 peer_mac[ETH_ALEN]; + u32 period; + bool is_valid; +}; + +struct ath11k_cfr { + struct ath11k_dbring rx_ring; + /* Protects cfr data */ + spinlock_t lock; + /* Protect for lut entries */ + spinlock_t lut_lock; + struct ath11k_look_up_table *lut; + struct dentry *enable_cfr; + struct dentry *cfr_unassoc; + struct rchan *rfs_cfr_capture; + u8 cfr_enabled_peer_cnt; + u32 lut_num; + u64 tx_evt_cnt; + u64 dbr_evt_cnt; + u64 release_cnt; + u64 tx_peer_status_cfr_fail; + u64 tx_evt_status_cfr_fail; + u64 tx_dbr_lookup_fail; + u64 last_success_tstamp; + u64 flush_dbr_cnt; + u64 clear_txrx_event; + u64 cfr_dma_aborts; + bool enabled; + enum wmi_phy_mode phymode; + struct cfr_unassoc_pool_entry unassoc_pool[ATH11K_MAX_CFR_ENABLED_CLIENTS]; +}; + +enum ath11k_cfr_capture_method { + ATH11K_CFR_CAPTURE_METHOD_NULL_FRAME, + ATH11K_CFR_CAPTURE_METHOD_NULL_FRAME_WITH_PHASE, + ATH11K_CFR_CAPTURE_METHOD_PROBE_RESP, + ATH11K_CFR_CAPTURE_METHOD_MAX, +}; + +enum ath11k_cfr_capture_bw { + ATH11K_CFR_CAPTURE_BW_20, + ATH11K_CFR_CAPTURE_BW_40, + ATH11K_CFR_CAPTURE_BW_80, + ATH11K_CFR_CAPTURE_BW_MAX, +}; + +#ifdef CONFIG_ATH11K_CFR +int ath11k_cfr_init(struct ath11k_base *ab); +void ath11k_cfr_deinit(struct ath11k_base *ab); +void ath11k_cfr_lut_update_paddr(struct ath11k *ar, dma_addr_t paddr, + u32 buf_id); +void ath11k_cfr_decrement_peer_count(struct ath11k *ar, + struct ath11k_sta *arsta); +void ath11k_cfr_update_unassoc_pool_entry(struct ath11k *ar, + const u8 *peer_mac); +bool ath11k_cfr_peer_is_in_cfr_unassoc_pool(struct ath11k *ar, + const u8 *peer_mac); +void ath11k_cfr_update_unassoc_pool(struct ath11k *ar, + struct ath11k_per_peer_cfr_capture *params, + u8 *peer_mac); +int ath11k_cfr_send_peer_cfr_capture_cmd(struct ath11k *ar, + struct ath11k_sta *arsta, + struct ath11k_per_peer_cfr_capture *params, + const u8 *peer_mac); +struct ath11k_dbring *ath11k_cfr_get_dbring(struct ath11k *ar); +void ath11k_cfr_release_lut_entry(struct ath11k_look_up_table *lut); +int ath11k_process_cfr_capture_event(struct ath11k_base *ab, + struct ath11k_cfr_peer_tx_param *params); +void ath11k_cfr_update_phymode(struct ath11k *ar, enum wmi_phy_mode phymode); +#else +static inline void ath11k_cfr_update_phymode(struct ath11k *ar, + enum wmi_phy_mode phymode) +{ +} + +static inline int ath11k_cfr_init(struct ath11k_base *ab) +{ + return 0; +} + +static inline void ath11k_cfr_deinit(struct ath11k_base *ab) +{ +} + +static inline void ath11k_cfr_lut_update_paddr(struct ath11k *ar, + dma_addr_t paddr, u32 buf_id) +{ +} + +static inline void ath11k_cfr_decrement_peer_count(struct ath11k *ar, + struct ath11k_sta *arsta) +{ +} + +static inline void ath11k_cfr_update_unassoc_pool_entry(struct ath11k *ar, + const u8 *peer_mac) +{ +} + +static inline bool +ath11k_cfr_peer_is_in_cfr_unassoc_pool(struct ath11k *ar, const u8 *peer_mac) +{ + return false; +} + +static inline void +ath11k_cfr_update_unassoc_pool(struct ath11k *ar, + struct ath11k_per_peer_cfr_capture *params, + u8 *peer_mac) +{ +} + +static inline int +ath11k_cfr_send_peer_cfr_capture_cmd(struct ath11k *ar, + struct ath11k_sta *arsta, + struct ath11k_per_peer_cfr_capture *params, + const u8 *peer_mac) +{ + return 0; +} + +static inline void ath11k_cfr_release_lut_entry(struct ath11k_look_up_table *lut) +{ +} + +static inline +struct ath11k_dbring *ath11k_cfr_get_dbring(struct ath11k *ar) +{ + return NULL; +} + +static inline +int ath11k_process_cfr_capture_event(struct ath11k_base *ab, + struct ath11k_cfr_peer_tx_param *params) +{ + return 0; +} +#endif /* CONFIG_ATH11K_CFR */ +#endif /* ATH11K_CFR_H */ diff --git a/drivers/net/wireless/ath/ath11k/core.c b/drivers/net/wireless/ath/ath11k/core.c index 812686173ac8..3f6f4db5b7ee 100644 --- a/drivers/net/wireless/ath/ath11k/core.c +++ b/drivers/net/wireless/ath/ath11k/core.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved. * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ @@ -100,7 +99,6 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { .supports_regdb = false, .fix_l1ss = true, .credit_flow = false, - .max_tx_ring = DP_TCL_NUM_RING_MAX, .hal_params = &ath11k_hw_hal_params_ipq8074, .supports_dynamic_smps_6ghz = false, .alloc_cacheable_memory = true, @@ -126,6 +124,9 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { .smp2p_wow_exit = false, .support_dual_stations = false, .pdev_suspend = false, + .cfr_support = true, + .cfr_num_stream_bufs = 255, + .cfr_stream_buf_size = 8200, }, { .hw_rev = ATH11K_HW_IPQ6018_HW10, @@ -184,7 +185,6 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { .supports_regdb = false, .fix_l1ss = true, .credit_flow = false, - .max_tx_ring = DP_TCL_NUM_RING_MAX, .hal_params = &ath11k_hw_hal_params_ipq8074, .supports_dynamic_smps_6ghz = false, .alloc_cacheable_memory = true, @@ -211,6 +211,9 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { .support_fw_mac_sequence = false, .support_dual_stations = false, .pdev_suspend = false, + .cfr_support = false, + .cfr_num_stream_bufs = 0, + .cfr_stream_buf_size = 0, }, { .name = "qca6390 hw2.0", @@ -271,7 +274,6 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { .supports_regdb = false, .fix_l1ss = true, .credit_flow = true, - .max_tx_ring = DP_TCL_NUM_RING_MAX_QCA6390, .hal_params = &ath11k_hw_hal_params_qca6390, .supports_dynamic_smps_6ghz = false, .alloc_cacheable_memory = false, @@ -301,6 +303,9 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { .support_fw_mac_sequence = true, .support_dual_stations = true, .pdev_suspend = false, + .cfr_support = false, + .cfr_num_stream_bufs = 0, + .cfr_stream_buf_size = 0, }, { .name = "qcn9074 hw1.0", @@ -358,7 +363,6 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { .supports_regdb = false, .fix_l1ss = true, .credit_flow = false, - .max_tx_ring = DP_TCL_NUM_RING_MAX, .hal_params = &ath11k_hw_hal_params_ipq8074, .supports_dynamic_smps_6ghz = true, .alloc_cacheable_memory = true, @@ -385,6 +389,9 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { .support_fw_mac_sequence = false, .support_dual_stations = false, .pdev_suspend = false, + .cfr_support = false, + .cfr_num_stream_bufs = 0, + .cfr_stream_buf_size = 0, }, { .name = "wcn6855 hw2.0", @@ -445,7 +452,6 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { .supports_regdb = true, .fix_l1ss = false, .credit_flow = true, - .max_tx_ring = DP_TCL_NUM_RING_MAX_QCA6390, .hal_params = &ath11k_hw_hal_params_qca6390, .supports_dynamic_smps_6ghz = false, .alloc_cacheable_memory = false, @@ -475,6 +481,9 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { .support_fw_mac_sequence = true, .support_dual_stations = true, .pdev_suspend = false, + .cfr_support = false, + .cfr_num_stream_bufs = 0, + .cfr_stream_buf_size = 0, }, { .name = "wcn6855 hw2.1", @@ -533,7 +542,6 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { .supports_regdb = true, .fix_l1ss = false, .credit_flow = true, - .max_tx_ring = DP_TCL_NUM_RING_MAX_QCA6390, .hal_params = &ath11k_hw_hal_params_qca6390, .supports_dynamic_smps_6ghz = false, .alloc_cacheable_memory = false, @@ -563,6 +571,9 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { .support_fw_mac_sequence = true, .support_dual_stations = true, .pdev_suspend = false, + .cfr_support = true, + .cfr_num_stream_bufs = 255, + .cfr_stream_buf_size = 8200, }, { .name = "wcn6750 hw1.0", @@ -619,7 +630,6 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { .supports_regdb = true, .fix_l1ss = false, .credit_flow = true, - .max_tx_ring = DP_TCL_NUM_RING_MAX, .hal_params = &ath11k_hw_hal_params_wcn6750, .supports_dynamic_smps_6ghz = false, .alloc_cacheable_memory = false, @@ -646,6 +656,9 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { .support_fw_mac_sequence = true, .support_dual_stations = false, .pdev_suspend = true, + .cfr_support = false, + .cfr_num_stream_bufs = 0, + .cfr_stream_buf_size = 0, }, { .hw_rev = ATH11K_HW_IPQ5018_HW10, @@ -662,7 +675,6 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { .qmi_service_ins_id = ATH11K_QMI_WLFW_SERVICE_INS_ID_V01_IPQ8074, .ring_mask = &ath11k_hw_ring_mask_ipq8074, .credit_flow = false, - .max_tx_ring = 1, .spectral = { .fft_sz = 2, .fft_pad_sz = 0, @@ -698,7 +710,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { .supports_regdb = false, .idle_ps = false, .supports_suspend = false, - .hal_params = &ath11k_hw_hal_params_ipq8074, + .hal_params = &ath11k_hw_hal_params_ipq5018, .single_pdev_only = false, .coldboot_cal_mm = true, .coldboot_cal_ftm = true, @@ -729,6 +741,9 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { .support_fw_mac_sequence = false, .support_dual_stations = false, .pdev_suspend = false, + .cfr_support = false, + .cfr_num_stream_bufs = 0, + .cfr_stream_buf_size = 0, }, { .name = "qca2066 hw2.1", @@ -789,7 +804,6 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { .supports_regdb = true, .fix_l1ss = false, .credit_flow = true, - .max_tx_ring = DP_TCL_NUM_RING_MAX_QCA6390, .hal_params = &ath11k_hw_hal_params_qca6390, .supports_dynamic_smps_6ghz = false, .alloc_cacheable_memory = false, @@ -818,6 +832,9 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { .smp2p_wow_exit = false, .support_fw_mac_sequence = true, .support_dual_stations = true, + .cfr_support = false, + .cfr_num_stream_bufs = 0, + .cfr_stream_buf_size = 0, }, { .name = "qca6698aq hw2.1", @@ -876,7 +893,6 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { .supports_regdb = true, .fix_l1ss = false, .credit_flow = true, - .max_tx_ring = DP_TCL_NUM_RING_MAX_QCA6390, .hal_params = &ath11k_hw_hal_params_qca6390, .supports_dynamic_smps_6ghz = false, .alloc_cacheable_memory = false, @@ -906,6 +922,9 @@ static const struct ath11k_hw_params ath11k_hw_params[] = { .support_fw_mac_sequence = true, .support_dual_stations = true, .pdev_suspend = false, + .cfr_support = true, + .cfr_num_stream_bufs = 255, + .cfr_stream_buf_size = 8200, }, }; @@ -994,9 +1013,64 @@ static const struct dmi_system_id ath11k_pm_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "21F9"), }, }, + { + .driver_data = (void *)ATH11K_PM_WOW, + .matches = { /* Z13 G1 */ + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21D2"), + }, + }, + { + .driver_data = (void *)ATH11K_PM_WOW, + .matches = { /* Z13 G1 */ + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21D3"), + }, + }, + { + .driver_data = (void *)ATH11K_PM_WOW, + .matches = { /* Z16 G1 */ + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21D4"), + }, + }, + { + .driver_data = (void *)ATH11K_PM_WOW, + .matches = { /* Z16 G1 */ + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21D5"), + }, + }, {} }; +static const struct __ath11k_core_usecase_firmware_table { + u32 hw_rev; + const char *compatible; + const char *firmware_name; +} ath11k_core_usecase_firmware_table[] = { + { ATH11K_HW_WCN6855_HW21, "qcom,lemans-evk", "nfa765"}, + { ATH11K_HW_WCN6855_HW21, "qcom,monaco-evk", "nfa765"}, + { ATH11K_HW_WCN6855_HW21, "qcom,hamoa-iot-evk", "nfa765"}, + { /* Sentinel */ } +}; + +const char *ath11k_core_get_usecase_firmware(struct ath11k_base *ab) +{ + const struct __ath11k_core_usecase_firmware_table *entry = NULL; + + entry = ath11k_core_usecase_firmware_table; + while (entry->compatible) { + if (ab->hw_rev == entry->hw_rev && + of_machine_is_compatible(entry->compatible)) + return entry->firmware_name; + entry++; + } + + return NULL; +} +EXPORT_SYMBOL(ath11k_core_get_usecase_firmware); + void ath11k_fw_stats_pdevs_free(struct list_head *head) { struct ath11k_fw_stats_pdev *i, *tmp; @@ -1987,8 +2061,16 @@ static int ath11k_core_pdev_create(struct ath11k_base *ab) goto err_thermal_unregister; } + ret = ath11k_cfr_init(ab); + if (ret) { + ath11k_err(ab, "failed to init cfr %d\n", ret); + goto err_spectral_unregister; + } + return 0; +err_spectral_unregister: + ath11k_spectral_deinit(ab); err_thermal_unregister: ath11k_thermal_unregister(ab); err_mac_unregister: @@ -2038,6 +2120,7 @@ static void ath11k_core_pdev_suspend_target(struct ath11k_base *ab) static void ath11k_core_pdev_destroy(struct ath11k_base *ab) { + ath11k_cfr_deinit(ab); ath11k_spectral_deinit(ab); ath11k_thermal_unregister(ab); ath11k_mac_unregister(ab); @@ -2250,6 +2333,7 @@ static int ath11k_core_reconfigure_on_crash(struct ath11k_base *ab) mutex_lock(&ab->core_lock); ath11k_thermal_unregister(ab); ath11k_dp_pdev_free(ab); + ath11k_cfr_deinit(ab); ath11k_spectral_deinit(ab); ath11k_ce_cleanup_pipes(ab); ath11k_wmi_detach(ab); diff --git a/drivers/net/wireless/ath/ath11k/core.h b/drivers/net/wireless/ath/ath11k/core.h index e8780b05ce11..a0d725923ef2 100644 --- a/drivers/net/wireless/ath/ath11k/core.h +++ b/drivers/net/wireless/ath/ath11k/core.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-3-Clause-Clear */ /* * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #ifndef ATH11K_CORE_H @@ -35,6 +35,7 @@ #include "wow.h" #include "fw.h" #include "coredump.h" +#include "cfr.h" #define SM(_v, _f) (((_v) << _f##_LSB) & _f##_MASK) @@ -531,6 +532,13 @@ struct ath11k_per_ppdu_tx_stats { DECLARE_EWMA(avg_rssi, 10, 8) +struct ath11k_per_peer_cfr_capture { + enum ath11k_cfr_capture_method cfr_method; + enum ath11k_cfr_capture_bw cfr_bw; + u32 cfr_enable; + u32 cfr_period; +}; + struct ath11k_sta { struct ath11k_vif *arvif; @@ -571,6 +579,10 @@ struct ath11k_sta { bool peer_current_ps_valid; u32 bw_prev; + +#ifdef CONFIG_ATH11K_CFR + struct ath11k_per_peer_cfr_capture cfr_capture; +#endif }; #define ATH11K_MIN_5G_FREQ 4150 @@ -795,6 +807,11 @@ struct ath11k { bool ps_state_enable; bool ps_timekeeper_enable; s8 max_allowed_tx_power; + +#ifdef CONFIG_ATH11K_CFR + struct ath11k_cfr cfr; +#endif + bool cfr_enabled; }; struct ath11k_band_cap { @@ -1275,6 +1292,7 @@ bool ath11k_core_coldboot_cal_support(struct ath11k_base *ab); const struct firmware *ath11k_core_firmware_request(struct ath11k_base *ab, const char *filename); +const char *ath11k_core_get_usecase_firmware(struct ath11k_base *ab); static inline const char *ath11k_scan_state_str(enum ath11k_scan_state state) { @@ -1329,6 +1347,9 @@ static inline void ath11k_core_create_firmware_path(struct ath11k_base *ab, of_property_read_string(ab->dev->of_node, "firmware-name", &fw_name); + if (!fw_name) + fw_name = ath11k_core_get_usecase_firmware(ab); + if (fw_name && strncmp(filename, "board", 5)) snprintf(buf, buf_len, "%s/%s/%s/%s", ATH11K_FW_DIR, ab->hw_params.fw.dir, fw_name, filename); diff --git a/drivers/net/wireless/ath/ath11k/dbring.c b/drivers/net/wireless/ath/ath11k/dbring.c index 520d8b8662a2..d6994ce6ebff 100644 --- a/drivers/net/wireless/ath/ath11k/dbring.c +++ b/drivers/net/wireless/ath/ath11k/dbring.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2019-2020 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2023 Qualcomm Innovation Center, Inc. All rights reserved. * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ @@ -37,10 +36,10 @@ static void ath11k_dbring_fill_magic_value(struct ath11k *ar, memset32(buffer, ATH11K_DB_MAGIC_VALUE, size); } -static int ath11k_dbring_bufs_replenish(struct ath11k *ar, - struct ath11k_dbring *ring, - struct ath11k_dbring_element *buff, - enum wmi_direct_buffer_module id) +int ath11k_dbring_bufs_replenish(struct ath11k *ar, + struct ath11k_dbring *ring, + struct ath11k_dbring_element *buff, + enum wmi_direct_buffer_module id) { struct ath11k_base *ab = ar->ab; struct hal_srng *srng; @@ -80,6 +79,9 @@ static int ath11k_dbring_bufs_replenish(struct ath11k *ar, goto err_idr_remove; } + if (id == WMI_DIRECT_BUF_CFR) + ath11k_cfr_lut_update_paddr(ar, paddr, buf_id); + buff->paddr = paddr; cookie = FIELD_PREP(DP_RXDMA_BUF_COOKIE_PDEV_ID, ar->pdev_idx) | @@ -155,12 +157,11 @@ int ath11k_dbring_wmi_cfg_setup(struct ath11k *ar, enum wmi_direct_buffer_module id) { struct ath11k_wmi_pdev_dma_ring_cfg_req_cmd param = {}; - int ret; + int ret, i; if (id >= WMI_DIRECT_BUF_MAX) return -EINVAL; - param.pdev_id = DP_SW2HW_MACID(ring->pdev_id); param.module_id = id; param.base_paddr_lo = lower_32_bits(ring->refill_srng.paddr); param.base_paddr_hi = upper_32_bits(ring->refill_srng.paddr); @@ -173,10 +174,23 @@ int ath11k_dbring_wmi_cfg_setup(struct ath11k *ar, param.num_resp_per_event = ring->num_resp_per_event; param.event_timeout_ms = ring->event_timeout_ms; - ret = ath11k_wmi_pdev_dma_ring_cfg(ar, ¶m); - if (ret) { - ath11k_warn(ar->ab, "failed to setup db ring cfg\n"); - return ret; + /* For single pdev, 2GHz and 5GHz use one DBR. */ + if (ar->ab->hw_params.single_pdev_only) { + for (i = 0; i < ar->ab->target_pdev_count; i++) { + param.pdev_id = ar->ab->target_pdev_ids[i].pdev_id; + ret = ath11k_wmi_pdev_dma_ring_cfg(ar, ¶m); + if (ret) { + ath11k_warn(ar->ab, "failed to setup db ring cfg\n"); + return ret; + } + } + } else { + param.pdev_id = DP_SW2HW_MACID(ring->pdev_id); + ret = ath11k_wmi_pdev_dma_ring_cfg(ar, ¶m); + if (ret) { + ath11k_warn(ar->ab, "failed to setup db ring cfg\n"); + return ret; + } } return 0; @@ -281,10 +295,15 @@ int ath11k_dbring_buffer_release_event(struct ath11k_base *ab, int size; dma_addr_t paddr; int ret = 0; + int status; pdev_idx = ev->fixed.pdev_id; module_id = ev->fixed.module_id; + if (ab->hw_params.single_pdev_only && + pdev_idx < ab->target_pdev_count) + pdev_idx = 0; + if (pdev_idx >= ab->num_radios) { ath11k_warn(ab, "Invalid pdev id %d\n", pdev_idx); return -EINVAL; @@ -310,6 +329,9 @@ int ath11k_dbring_buffer_release_event(struct ath11k_base *ab, case WMI_DIRECT_BUF_SPECTRAL: ring = ath11k_spectral_get_dbring(ar); break; + case WMI_DIRECT_BUF_CFR: + ring = ath11k_cfr_get_dbring(ar); + break; default: ring = NULL; ath11k_warn(ab, "Recv dma buffer release ev on unsupp module %d\n", @@ -360,8 +382,12 @@ int ath11k_dbring_buffer_release_event(struct ath11k_base *ab, handler_data.data = PTR_ALIGN(vaddr_unalign, ring->buf_align); handler_data.data_sz = ring->buf_sz; + handler_data.buff = buff; + handler_data.buf_id = buf_id; - ring->handler(ar, &handler_data); + status = ring->handler(ar, &handler_data); + if (status == ATH11K_CORRELATE_STATUS_HOLD) + continue; } buff->paddr = 0; diff --git a/drivers/net/wireless/ath/ath11k/dbring.h b/drivers/net/wireless/ath/ath11k/dbring.h index 2f93b78a50df..e5f244dfa963 100644 --- a/drivers/net/wireless/ath/ath11k/dbring.h +++ b/drivers/net/wireless/ath/ath11k/dbring.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-3-Clause-Clear */ /* * Copyright (c) 2019-2020 The Linux Foundation. All rights reserved. - * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #ifndef ATH11K_DBRING_H @@ -21,6 +21,8 @@ struct ath11k_dbring_data { void *data; u32 data_sz; struct wmi_dma_buf_release_meta_data meta; + struct ath11k_dbring_element *buff; + u32 buf_id; }; struct ath11k_dbring_buf_release_event { @@ -61,6 +63,10 @@ int ath11k_dbring_set_cfg(struct ath11k *ar, u32 event_timeout_ms, int (*handler)(struct ath11k *, struct ath11k_dbring_data *)); +int ath11k_dbring_bufs_replenish(struct ath11k *ar, + struct ath11k_dbring *ring, + struct ath11k_dbring_element *buff, + enum wmi_direct_buffer_module id); int ath11k_dbring_wmi_cfg_setup(struct ath11k *ar, struct ath11k_dbring *ring, enum wmi_direct_buffer_module id); diff --git a/drivers/net/wireless/ath/ath11k/debug.h b/drivers/net/wireless/ath/ath11k/debug.h index cc8934d15697..aaa0034527a5 100644 --- a/drivers/net/wireless/ath/ath11k/debug.h +++ b/drivers/net/wireless/ath/ath11k/debug.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-3-Clause-Clear */ /* * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. - * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #ifndef _ATH11K_DEBUG_H_ @@ -27,6 +27,8 @@ enum ath11k_debug_mask { ATH11K_DBG_DP_TX = 0x00002000, ATH11K_DBG_DP_RX = 0x00004000, ATH11K_DBG_CE = 0x00008000, + ATH11K_DBG_CFR = 0x00010000, + ATH11K_DBG_CFR_DUMP = 0x00020000, }; static inline const char *ath11k_dbg_str(enum ath11k_debug_mask mask) @@ -64,6 +66,10 @@ static inline const char *ath11k_dbg_str(enum ath11k_debug_mask mask) return "dp_rx"; case ATH11K_DBG_CE: return "ce"; + case ATH11K_DBG_CFR: + return "cfr"; + case ATH11K_DBG_CFR_DUMP: + return "cfr_dump"; /* no default handler to allow compiler to check that the * enum is fully handled diff --git a/drivers/net/wireless/ath/ath11k/debugfs.c b/drivers/net/wireless/ath/ath11k/debugfs.c index 977f945b6e66..50f344803e8f 100644 --- a/drivers/net/wireless/ath/ath11k/debugfs.c +++ b/drivers/net/wireless/ath/ath11k/debugfs.c @@ -707,7 +707,7 @@ static ssize_t ath11k_debugfs_dump_soc_dp_stats(struct file *file, len += scnprintf(buf + len, size - len, "\nSOC TX STATS:\n"); len += scnprintf(buf + len, size - len, "\nTCL Ring Full Failures:\n"); - for (i = 0; i < ab->hw_params.max_tx_ring; i++) + for (i = 0; i < ab->hw_params.hal_params->num_tx_rings; i++) len += scnprintf(buf + len, size - len, "ring%d: %u\n", i, soc_stats->tx_err.desc_na[i]); diff --git a/drivers/net/wireless/ath/ath11k/debugfs_sta.c b/drivers/net/wireless/ath/ath11k/debugfs_sta.c index d89d0f28d890..621a8a8df4b8 100644 --- a/drivers/net/wireless/ath/ath11k/debugfs_sta.c +++ b/drivers/net/wireless/ath/ath11k/debugfs_sta.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2023 Qualcomm Innovation Center, Inc. All rights reserved. * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ @@ -240,6 +239,140 @@ static const struct file_operations fops_tx_stats = { .llseek = default_llseek, }; +#ifdef CONFIG_ATH11K_CFR +static ssize_t ath11k_dbg_sta_write_cfr_capture(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ieee80211_sta *sta = file->private_data; + struct ath11k_sta *arsta = ath11k_sta_to_arsta(sta); + struct ath11k *ar = arsta->arvif->ar; + struct ath11k_cfr *cfr = &ar->cfr; + struct wmi_peer_cfr_capture_conf_arg arg; + u32 cfr_capture_enable = 0, cfr_capture_bw = 0; + u32 cfr_capture_method = 0, cfr_capture_period = 0; + char buf[64] = {}; + int ret; + + simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count); + + guard(mutex)(&ar->conf_mutex); + + if (ar->state != ATH11K_STATE_ON) + return -ENETDOWN; + + if (!ar->cfr_enabled) + return -EINVAL; + + ret = sscanf(buf, "%u %u %u %u", &cfr_capture_enable, &cfr_capture_bw, + &cfr_capture_period, &cfr_capture_method); + + if (ret < 1 || (cfr_capture_enable && ret != 4)) + return -EINVAL; + + if (cfr_capture_enable == arsta->cfr_capture.cfr_enable && + (cfr_capture_period && + cfr_capture_period == arsta->cfr_capture.cfr_period) && + cfr_capture_bw == arsta->cfr_capture.cfr_bw && + cfr_capture_method == arsta->cfr_capture.cfr_method) + return count; + + if (!cfr_capture_enable && + cfr_capture_enable == arsta->cfr_capture.cfr_enable) + return count; + + if (cfr_capture_enable > WMI_PEER_CFR_CAPTURE_ENABLE || + cfr_capture_bw > WMI_PEER_CFR_CAPTURE_BW_80 || + cfr_capture_method > ATH11K_CFR_CAPTURE_METHOD_NULL_FRAME_WITH_PHASE || + cfr_capture_period > WMI_PEER_CFR_PERIODICITY_MAX) + return -EINVAL; + + /* Target expects cfr period in multiple of 10 */ + if (cfr_capture_period % 10) { + ath11k_err(ar->ab, "periodicity should be 10x\n"); + return -EINVAL; + } + + if (ar->cfr.cfr_enabled_peer_cnt >= ATH11K_MAX_CFR_ENABLED_CLIENTS && + !arsta->cfr_capture.cfr_enable) { + ath11k_err(ar->ab, "CFR enable peer threshold reached %u\n", + ar->cfr.cfr_enabled_peer_cnt); + return -EINVAL; + } + + if (!cfr_capture_enable) { + cfr_capture_bw = arsta->cfr_capture.cfr_bw; + cfr_capture_period = arsta->cfr_capture.cfr_period; + cfr_capture_method = arsta->cfr_capture.cfr_method; + } + + arg.request = cfr_capture_enable; + arg.periodicity = cfr_capture_period; + arg.bw = cfr_capture_bw; + arg.method = cfr_capture_method; + + ret = ath11k_wmi_peer_set_cfr_capture_conf(ar, arsta->arvif->vdev_id, + sta->addr, &arg); + if (ret) { + ath11k_warn(ar->ab, + "failed to send cfr capture info: vdev_id %u peer %pM: %d\n", + arsta->arvif->vdev_id, sta->addr, ret); + return ret; + } + + spin_lock_bh(&ar->cfr.lock); + + if (cfr_capture_enable && + cfr_capture_enable != arsta->cfr_capture.cfr_enable) + cfr->cfr_enabled_peer_cnt++; + else if (!cfr_capture_enable) + cfr->cfr_enabled_peer_cnt--; + + spin_unlock_bh(&ar->cfr.lock); + + arsta->cfr_capture.cfr_enable = cfr_capture_enable; + arsta->cfr_capture.cfr_period = cfr_capture_period; + arsta->cfr_capture.cfr_bw = cfr_capture_bw; + arsta->cfr_capture.cfr_method = cfr_capture_method; + + return count; +} + +static ssize_t ath11k_dbg_sta_read_cfr_capture(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ieee80211_sta *sta = file->private_data; + struct ath11k_sta *arsta = ath11k_sta_to_arsta(sta); + struct ath11k *ar = arsta->arvif->ar; + char buf[512] = {}; + int len = 0; + + mutex_lock(&ar->conf_mutex); + + len += scnprintf(buf + len, sizeof(buf) - len, "cfr_enabled = %d\n", + arsta->cfr_capture.cfr_enable); + len += scnprintf(buf + len, sizeof(buf) - len, "bandwidth = %d\n", + arsta->cfr_capture.cfr_bw); + len += scnprintf(buf + len, sizeof(buf) - len, "period = %d\n", + arsta->cfr_capture.cfr_period); + len += scnprintf(buf + len, sizeof(buf) - len, "cfr_method = %d\n", + arsta->cfr_capture.cfr_method); + + mutex_unlock(&ar->conf_mutex); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations fops_peer_cfr_capture = { + .write = ath11k_dbg_sta_write_cfr_capture, + .read = ath11k_dbg_sta_read_cfr_capture, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; +#endif /* CONFIG_ATH11K_CFR */ + static ssize_t ath11k_dbg_sta_dump_rx_stats(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) @@ -877,6 +1010,13 @@ void ath11k_debugfs_sta_op_add(struct ieee80211_hw *hw, struct ieee80211_vif *vi debugfs_create_file("htt_peer_stats_reset", 0600, dir, sta, &fops_htt_peer_stats_reset); +#ifdef CONFIG_ATH11K_CFR + if (test_bit(WMI_TLV_SERVICE_CFR_CAPTURE_SUPPORT, + ar->ab->wmi_ab.svc_map)) + debugfs_create_file("cfr_capture", 0600, dir, sta, + &fops_peer_cfr_capture); +#endif/* CONFIG_ATH11K_CFR */ + debugfs_create_file("peer_ps_state", 0400, dir, sta, &fops_peer_ps_state); diff --git a/drivers/net/wireless/ath/ath11k/dp.c b/drivers/net/wireless/ath/ath11k/dp.c index 56b1a657e0b0..c940de285276 100644 --- a/drivers/net/wireless/ath/ath11k/dp.c +++ b/drivers/net/wireless/ath/ath11k/dp.c @@ -344,7 +344,7 @@ void ath11k_dp_stop_shadow_timers(struct ath11k_base *ab) if (!ab->hw_params.supports_shadow_regs) return; - for (i = 0; i < ab->hw_params.max_tx_ring; i++) + for (i = 0; i < ab->hw_params.hal_params->num_tx_rings; i++) ath11k_dp_shadow_stop_timer(ab, &ab->dp.tx_ring_timer[i]); ath11k_dp_shadow_stop_timer(ab, &ab->dp.reo_cmd_timer); @@ -359,7 +359,7 @@ static void ath11k_dp_srng_common_cleanup(struct ath11k_base *ab) ath11k_dp_srng_cleanup(ab, &dp->wbm_desc_rel_ring); ath11k_dp_srng_cleanup(ab, &dp->tcl_cmd_ring); ath11k_dp_srng_cleanup(ab, &dp->tcl_status_ring); - for (i = 0; i < ab->hw_params.max_tx_ring; i++) { + for (i = 0; i < ab->hw_params.hal_params->num_tx_rings; i++) { ath11k_dp_srng_cleanup(ab, &dp->tx_ring[i].tcl_data_ring); ath11k_dp_srng_cleanup(ab, &dp->tx_ring[i].tcl_comp_ring); } @@ -400,7 +400,7 @@ static int ath11k_dp_srng_common_setup(struct ath11k_base *ab) goto err; } - for (i = 0; i < ab->hw_params.max_tx_ring; i++) { + for (i = 0; i < ab->hw_params.hal_params->num_tx_rings; i++) { tcl_num = ab->hw_params.hal_params->tcl2wbm_rbm_map[i].tcl_ring_num; wbm_num = ab->hw_params.hal_params->tcl2wbm_rbm_map[i].wbm_ring_num; @@ -782,7 +782,7 @@ int ath11k_dp_service_srng(struct ath11k_base *ab, int i, j; int tot_work_done = 0; - for (i = 0; i < ab->hw_params.max_tx_ring; i++) { + for (i = 0; i < ab->hw_params.hal_params->num_tx_rings; i++) { if (BIT(ab->hw_params.hal_params->tcl2wbm_rbm_map[i].wbm_ring_num) & ab->hw_params.ring_mask->tx[grp_id]) ath11k_dp_tx_completion_handler(ab, i); @@ -1035,7 +1035,7 @@ void ath11k_dp_free(struct ath11k_base *ab) ath11k_dp_reo_cmd_list_cleanup(ab); - for (i = 0; i < ab->hw_params.max_tx_ring; i++) { + for (i = 0; i < ab->hw_params.hal_params->num_tx_rings; i++) { spin_lock_bh(&dp->tx_ring[i].tx_idr_lock); idr_for_each(&dp->tx_ring[i].txbuf_idr, ath11k_dp_tx_pending_cleanup, ab); @@ -1086,7 +1086,7 @@ int ath11k_dp_alloc(struct ath11k_base *ab) size = sizeof(struct hal_wbm_release_ring) * DP_TX_COMP_RING_SIZE; - for (i = 0; i < ab->hw_params.max_tx_ring; i++) { + for (i = 0; i < ab->hw_params.hal_params->num_tx_rings; i++) { idr_init(&dp->tx_ring[i].txbuf_idr); spin_lock_init(&dp->tx_ring[i].tx_idr_lock); dp->tx_ring[i].tcl_data_ring_id = i; diff --git a/drivers/net/wireless/ath/ath11k/dp.h b/drivers/net/wireless/ath/ath11k/dp.h index 7a55afd33be8..1bd513f68a3c 100644 --- a/drivers/net/wireless/ath/ath11k/dp.h +++ b/drivers/net/wireless/ath/ath11k/dp.h @@ -199,7 +199,6 @@ struct ath11k_pdev_dp { #define DP_BA_WIN_SZ_MAX 256 #define DP_TCL_NUM_RING_MAX 3 -#define DP_TCL_NUM_RING_MAX_QCA6390 1 #define DP_IDLE_SCATTER_BUFS_MAX 16 diff --git a/drivers/net/wireless/ath/ath11k/dp_tx.c b/drivers/net/wireless/ath/ath11k/dp_tx.c index 562aba66582f..86e1e6c27b36 100644 --- a/drivers/net/wireless/ath/ath11k/dp_tx.c +++ b/drivers/net/wireless/ath/ath11k/dp_tx.c @@ -91,6 +91,7 @@ int ath11k_dp_tx(struct ath11k *ar, struct ath11k_vif *arvif, struct hal_srng *tcl_ring; struct ieee80211_hdr *hdr = (void *)skb->data; struct dp_tx_ring *tx_ring; + size_t num_tx_rings = ab->hw_params.hal_params->num_tx_rings; void *hal_tcl_desc; u8 pool_id; u8 hal_ring_id; @@ -113,7 +114,7 @@ int ath11k_dp_tx(struct ath11k *ar, struct ath11k_vif *arvif, tcl_ring_sel: tcl_ring_retry = false; - ti.ring_id = ring_selector % ab->hw_params.max_tx_ring; + ti.ring_id = ring_selector % num_tx_rings; ti.rbm_id = ab->hw_params.hal_params->tcl2wbm_rbm_map[ti.ring_id].rbm_id; ring_map |= BIT(ti.ring_id); @@ -126,7 +127,7 @@ tcl_ring_sel: spin_unlock_bh(&tx_ring->tx_idr_lock); if (unlikely(ret < 0)) { - if (ring_map == (BIT(ab->hw_params.max_tx_ring) - 1) || + if (ring_map == (BIT(num_tx_rings) - 1) || !ab->hw_params.tcl_ring_retry) { atomic_inc(&ab->soc_stats.tx_err.misc_fail); return -ENOSPC; @@ -244,8 +245,8 @@ tcl_ring_sel: * checking this ring earlier for each pkt tx. * Restart ring selection if some rings are not checked yet. */ - if (unlikely(ring_map != (BIT(ab->hw_params.max_tx_ring)) - 1) && - ab->hw_params.tcl_ring_retry && ab->hw_params.max_tx_ring > 1) { + if (unlikely(ring_map != (BIT(num_tx_rings)) - 1) && + ab->hw_params.tcl_ring_retry && num_tx_rings > 1) { tcl_ring_retry = true; ring_selector++; } diff --git a/drivers/net/wireless/ath/ath11k/hal.c b/drivers/net/wireless/ath/ath11k/hal.c index 0c797b8d0a27..e821e5a62c1c 100644 --- a/drivers/net/wireless/ath/ath11k/hal.c +++ b/drivers/net/wireless/ath/ath11k/hal.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2024 Qualcomm Innovation Center, Inc. All rights reserved. * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include <linux/dma-mapping.h> @@ -184,7 +183,7 @@ static const struct hal_srng_config hw_srng_config_template[] = { }, { /* RXDMA DIR BUF */ .start_ring_id = HAL_SRNG_RING_ID_RXDMA_DIR_BUF, - .max_rings = 1, + .max_rings = 2, .entry_size = 8 >> 2, /* TODO: Define the struct */ .lmac_ring = true, .ring_dir = HAL_SRNG_DIR_SRC, diff --git a/drivers/net/wireless/ath/ath11k/hw.c b/drivers/net/wireless/ath/ath11k/hw.c index caa6dc12a790..d19c4b372a2a 100644 --- a/drivers/net/wireless/ath/ath11k/hw.c +++ b/drivers/net/wireless/ath/ath11k/hw.c @@ -2707,6 +2707,14 @@ const struct ath11k_hw_regs wcn6750_regs = { .hal_reo1_misc_ctl = 0x000005d8, }; +static const struct ath11k_hw_tcl2wbm_rbm_map ath11k_hw_tcl2wbm_rbm_map_ipq5018[] = { + { + .tcl_ring_num = 0, + .wbm_ring_num = 0, + .rbm_id = HAL_RX_BUF_RBM_SW0_BM, + }, +}; + static const struct ath11k_hw_tcl2wbm_rbm_map ath11k_hw_tcl2wbm_rbm_map_ipq8074[] = { { .tcl_ring_num = 0, @@ -2822,19 +2830,28 @@ const struct ath11k_hw_regs ipq5018_regs = { .hal_wbm1_release_ring_base_lsb = 0x0000097c, }; +const struct ath11k_hw_hal_params ath11k_hw_hal_params_ipq5018 = { + .rx_buf_rbm = HAL_RX_BUF_RBM_SW3_BM, + .tcl2wbm_rbm_map = ath11k_hw_tcl2wbm_rbm_map_ipq5018, + .num_tx_rings = ARRAY_SIZE(ath11k_hw_tcl2wbm_rbm_map_ipq5018), +}; + const struct ath11k_hw_hal_params ath11k_hw_hal_params_ipq8074 = { .rx_buf_rbm = HAL_RX_BUF_RBM_SW3_BM, .tcl2wbm_rbm_map = ath11k_hw_tcl2wbm_rbm_map_ipq8074, + .num_tx_rings = ARRAY_SIZE(ath11k_hw_tcl2wbm_rbm_map_ipq8074), }; const struct ath11k_hw_hal_params ath11k_hw_hal_params_qca6390 = { .rx_buf_rbm = HAL_RX_BUF_RBM_SW1_BM, - .tcl2wbm_rbm_map = ath11k_hw_tcl2wbm_rbm_map_ipq8074, + .tcl2wbm_rbm_map = ath11k_hw_tcl2wbm_rbm_map_ipq5018, + .num_tx_rings = ARRAY_SIZE(ath11k_hw_tcl2wbm_rbm_map_ipq5018), }; const struct ath11k_hw_hal_params ath11k_hw_hal_params_wcn6750 = { .rx_buf_rbm = HAL_RX_BUF_RBM_SW1_BM, .tcl2wbm_rbm_map = ath11k_hw_tcl2wbm_rbm_map_wcn6750, + .num_tx_rings = ARRAY_SIZE(ath11k_hw_tcl2wbm_rbm_map_wcn6750), }; static const struct cfg80211_sar_freq_ranges ath11k_hw_sar_freq_ranges_wcn6855[] = { diff --git a/drivers/net/wireless/ath/ath11k/hw.h b/drivers/net/wireless/ath/ath11k/hw.h index 52d9f4c13b13..4996536fbd14 100644 --- a/drivers/net/wireless/ath/ath11k/hw.h +++ b/drivers/net/wireless/ath/ath11k/hw.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-3-Clause-Clear */ /* * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2024 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #ifndef ATH11K_HW_H @@ -134,6 +134,7 @@ struct ath11k_hw_tcl2wbm_rbm_map { struct ath11k_hw_hal_params { enum hal_rx_buf_return_buf_manager rx_buf_rbm; const struct ath11k_hw_tcl2wbm_rbm_map *tcl2wbm_rbm_map; + size_t num_tx_rings; }; struct ath11k_hw_params { @@ -198,7 +199,6 @@ struct ath11k_hw_params { bool supports_regdb; bool fix_l1ss; bool credit_flow; - u8 max_tx_ring; const struct ath11k_hw_hal_params *hal_params; bool supports_dynamic_smps_6ghz; bool alloc_cacheable_memory; @@ -228,6 +228,9 @@ struct ath11k_hw_params { bool support_fw_mac_sequence; bool support_dual_stations; bool pdev_suspend; + bool cfr_support; + u32 cfr_num_stream_bufs; + u32 cfr_stream_buf_size; }; struct ath11k_hw_ops { @@ -291,6 +294,7 @@ extern const struct ce_ie_addr ath11k_ce_ie_addr_ipq5018; extern const struct ce_remap ath11k_ce_remap_ipq5018; +extern const struct ath11k_hw_hal_params ath11k_hw_hal_params_ipq5018; extern const struct ath11k_hw_hal_params ath11k_hw_hal_params_ipq8074; extern const struct ath11k_hw_hal_params ath11k_hw_hal_params_qca6390; extern const struct ath11k_hw_hal_params ath11k_hw_hal_params_wcn6750; diff --git a/drivers/net/wireless/ath/ath11k/mac.c b/drivers/net/wireless/ath/ath11k/mac.c index 3276fe443502..4dfd08b58416 100644 --- a/drivers/net/wireless/ath/ath11k/mac.c +++ b/drivers/net/wireless/ath/ath11k/mac.c @@ -2911,6 +2911,8 @@ static void ath11k_peer_assoc_h_phymode(struct ath11k *ar, arg->peer_phymode = phymode; WARN_ON(phymode == MODE_UNKNOWN); + + ath11k_cfr_update_phymode(ar, phymode); } static void ath11k_peer_assoc_prepare(struct ath11k *ar, @@ -6186,6 +6188,8 @@ static int ath11k_mac_mgmt_tx_wmi(struct ath11k *ar, struct ath11k_vif *arvif, dma_addr_t paddr; int buf_id; int ret; + bool tx_params_valid = false; + bool peer_in_unassoc_pool; ATH11K_SKB_CB(skb)->ar = ar; @@ -6224,7 +6228,18 @@ static int ath11k_mac_mgmt_tx_wmi(struct ath11k *ar, struct ath11k_vif *arvif, ATH11K_SKB_CB(skb)->paddr = paddr; - ret = ath11k_wmi_mgmt_send(ar, arvif->vdev_id, buf_id, skb); + peer_in_unassoc_pool = ath11k_cfr_peer_is_in_cfr_unassoc_pool(ar, hdr->addr1); + + if (ar->cfr_enabled && + ieee80211_is_probe_resp(hdr->frame_control) && + peer_in_unassoc_pool) + tx_params_valid = true; + + if (peer_in_unassoc_pool) + ath11k_cfr_update_unassoc_pool_entry(ar, hdr->addr1); + + ret = ath11k_wmi_mgmt_send(ar, arvif->vdev_id, buf_id, skb, + tx_params_valid); if (ret) { ath11k_warn(ar->ab, "failed to send mgmt frame: %d\n", ret); goto err_unmap_buf; @@ -7392,7 +7407,7 @@ err_vdev_del: idr_for_each(&ar->txmgmt_idr, ath11k_mac_vif_txmgmt_idr_remove, vif); - for (i = 0; i < ab->hw_params.max_tx_ring; i++) { + for (i = 0; i < ab->hw_params.hal_params->num_tx_rings; i++) { spin_lock_bh(&ab->dp.tx_ring[i].tx_idr_lock); idr_for_each(&ab->dp.tx_ring[i].txbuf_idr, ath11k_mac_vif_unref, vif); @@ -9979,6 +9994,8 @@ static int ath11k_mac_op_sta_state(struct ieee80211_hw *hw, } spin_unlock_bh(&ar->ab->base_lock); mutex_unlock(&ar->ab->tbl_mtx_lock); + + ath11k_cfr_decrement_peer_count(ar, arsta); } else if (old_state == IEEE80211_STA_AUTH && new_state == IEEE80211_STA_ASSOC && (vif->type == NL80211_IFTYPE_AP || @@ -10640,7 +10657,7 @@ static int __ath11k_mac_register(struct ath11k *ar) if (!ab->hw_params.supports_monitor) /* There's a race between calling ieee80211_register_hw() * and here where the monitor mode is enabled for a little - * while. But that time is so short and in practise it make + * while. But that time is so short and in practice it doesn't make * a difference in real life. */ ar->hw->wiphy->interface_modes &= ~BIT(NL80211_IFTYPE_MONITOR); diff --git a/drivers/net/wireless/ath/ath11k/reg.c b/drivers/net/wireless/ath/ath11k/reg.c index d62a2014315a..49b79648752c 100644 --- a/drivers/net/wireless/ath/ath11k/reg.c +++ b/drivers/net/wireless/ath/ath11k/reg.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include <linux/rtnetlink.h> @@ -926,8 +926,11 @@ int ath11k_reg_handle_chan_list(struct ath11k_base *ab, */ if (ab->default_regd[pdev_idx] && !ab->new_regd[pdev_idx] && !memcmp((char *)ab->default_regd[pdev_idx]->alpha2, - (char *)reg_info->alpha2, 2)) - goto retfail; + (char *)reg_info->alpha2, 2) && + power_type == IEEE80211_REG_UNSET_AP) { + ath11k_reg_reset_info(reg_info); + return 0; + } /* Intersect new rules with default regd if a new country setting was * requested, i.e a default regd was already set during initialization diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c index 110035dae8a6..451cc4c719ae 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.c +++ b/drivers/net/wireless/ath/ath11k/wmi.c @@ -651,11 +651,12 @@ static u32 ath11k_wmi_mgmt_get_freq(struct ath11k *ar, } int ath11k_wmi_mgmt_send(struct ath11k *ar, u32 vdev_id, u32 buf_id, - struct sk_buff *frame) + struct sk_buff *frame, bool tx_params_valid) { struct ath11k_pdev_wmi *wmi = ar->wmi; struct ieee80211_tx_info *info = IEEE80211_SKB_CB(frame); struct wmi_mgmt_send_cmd *cmd; + struct wmi_mgmt_send_params *params; struct wmi_tlv *frame_tlv; struct sk_buff *skb; u32 buf_len; @@ -665,6 +666,8 @@ int ath11k_wmi_mgmt_send(struct ath11k *ar, u32 vdev_id, u32 buf_id, frame->len : WMI_MGMT_SEND_DOWNLD_LEN; len = sizeof(*cmd) + sizeof(*frame_tlv) + roundup(buf_len, 4); + if (tx_params_valid) + len += sizeof(*params); skb = ath11k_wmi_alloc_skb(wmi->wmi_ab, len); if (!skb) @@ -680,7 +683,7 @@ int ath11k_wmi_mgmt_send(struct ath11k *ar, u32 vdev_id, u32 buf_id, cmd->paddr_hi = upper_32_bits(ATH11K_SKB_CB(frame)->paddr); cmd->frame_len = frame->len; cmd->buf_len = buf_len; - cmd->tx_params_valid = 0; + cmd->tx_params_valid = !!tx_params_valid; frame_tlv = (struct wmi_tlv *)(skb->data + sizeof(*cmd)); frame_tlv->header = FIELD_PREP(WMI_TLV_TAG, WMI_TAG_ARRAY_BYTE) | @@ -690,6 +693,15 @@ int ath11k_wmi_mgmt_send(struct ath11k *ar, u32 vdev_id, u32 buf_id, ath11k_ce_byte_swap(frame_tlv->value, buf_len); + if (tx_params_valid) { + params = + (struct wmi_mgmt_send_params *)(skb->data + (len - sizeof(*params))); + params->tlv_header = FIELD_PREP(WMI_TLV_TAG, WMI_TAG_TX_SEND_PARAMS) | + FIELD_PREP(WMI_TLV_LEN, + sizeof(*params) - TLV_HDR_SIZE); + params->tx_params_dword1 |= WMI_TX_PARAMS_DWORD1_CFR_CAPTURE; + } + ret = ath11k_wmi_cmd_send(wmi, skb, WMI_MGMT_TX_SEND_CMDID); if (ret) { ath11k_warn(ar->ab, @@ -3941,6 +3953,47 @@ int ath11k_wmi_fils_discovery_tmpl(struct ath11k *ar, u32 vdev_id, return 0; } +int ath11k_wmi_peer_set_cfr_capture_conf(struct ath11k *ar, + u32 vdev_id, const u8 *mac_addr, + struct wmi_peer_cfr_capture_conf_arg *arg) +{ + struct ath11k_pdev_wmi *wmi = ar->wmi; + struct wmi_peer_cfr_capture_cmd_fixed_param *cmd; + struct sk_buff *skb; + int ret; + + skb = ath11k_wmi_alloc_skb(wmi->wmi_ab, sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_peer_cfr_capture_cmd_fixed_param *)skb->data; + cmd->tlv_header = FIELD_PREP(WMI_TLV_TAG, + WMI_TAG_PEER_CFR_CAPTURE_CMD) | + FIELD_PREP(WMI_TLV_LEN, sizeof(*cmd) - TLV_HDR_SIZE); + + memcpy(&cmd->mac_addr, mac_addr, ETH_ALEN); + cmd->request = arg->request; + cmd->vdev_id = vdev_id; + cmd->periodicity = arg->periodicity; + cmd->bandwidth = arg->bw; + cmd->capture_method = arg->method; + + ret = ath11k_wmi_cmd_send(ar->wmi, skb, WMI_PEER_CFR_CAPTURE_CMDID); + if (ret) { + ath11k_warn(ar->ab, + "WMI vdev %d failed to send peer cfr capture cmd: %d\n", + vdev_id, ret); + dev_kfree_skb(skb); + } + + ath11k_dbg(ar->ab, ATH11K_DBG_WMI, + "WMI peer CFR capture cmd req %u id %u period %u bw %u mode %u\n", + arg->request, vdev_id, arg->periodicity, + arg->bw, arg->method); + + return ret; +} + int ath11k_wmi_probe_resp_tmpl(struct ath11k *ar, u32 vdev_id, struct sk_buff *tmpl) { @@ -8752,6 +8805,93 @@ out: kfree(tb); } +static void ath11k_wmi_tlv_cfr_capture_event_fixed_param(const void *ptr, + void *data) +{ + struct ath11k_cfr_peer_tx_param *tx_params = data; + const struct ath11k_wmi_cfr_peer_tx_event_param *params = ptr; + + tx_params->capture_method = params->capture_method; + tx_params->vdev_id = params->vdev_id; + ether_addr_copy(tx_params->peer_mac_addr, params->mac_addr.addr); + tx_params->primary_20mhz_chan = params->chan_mhz; + tx_params->bandwidth = params->bandwidth; + tx_params->phy_mode = params->phy_mode; + tx_params->band_center_freq1 = params->band_center_freq1; + tx_params->band_center_freq2 = params->band_center_freq2; + tx_params->spatial_streams = params->sts_count; + tx_params->correlation_info_1 = params->correlation_info_1; + tx_params->correlation_info_2 = params->correlation_info_2; + tx_params->status = params->status; + tx_params->timestamp_us = params->timestamp_us; + tx_params->counter = params->counter; + tx_params->rx_start_ts = params->rx_start_ts; + + memcpy(tx_params->chain_rssi, params->chain_rssi, + sizeof(tx_params->chain_rssi)); + + if (WMI_CFR_CFO_MEASUREMENT_VALID & params->cfo_measurement) + tx_params->cfo_measurement = FIELD_GET(WMI_CFR_CFO_MEASUREMENT_RAW_DATA, + params->cfo_measurement); +} + +static void ath11k_wmi_tlv_cfr_capture_phase_fixed_param(const void *ptr, + void *data) +{ + struct ath11k_cfr_peer_tx_param *tx_params = data; + const struct ath11k_wmi_cfr_peer_tx_event_phase_param *params = ptr; + int i; + + for (i = 0; i < WMI_MAX_CHAINS; i++) { + tx_params->chain_phase[i] = params->chain_phase[i]; + tx_params->agc_gain[i] = params->agc_gain[i]; + } +} + +static int ath11k_wmi_tlv_cfr_capture_evt_parse(struct ath11k_base *ab, + u16 tag, u16 len, + const void *ptr, void *data) +{ + switch (tag) { + case WMI_TAG_PEER_CFR_CAPTURE_EVENT: + ath11k_wmi_tlv_cfr_capture_event_fixed_param(ptr, data); + break; + case WMI_TAG_CFR_CAPTURE_PHASE_PARAM: + ath11k_wmi_tlv_cfr_capture_phase_fixed_param(ptr, data); + break; + default: + ath11k_warn(ab, "Invalid tag received tag %d len %d\n", + tag, len); + return -EINVAL; + } + + return 0; +} + +static void ath11k_wmi_parse_cfr_capture_event(struct ath11k_base *ab, + struct sk_buff *skb) +{ + struct ath11k_cfr_peer_tx_param params = {}; + int ret; + + ath11k_dbg_dump(ab, ATH11K_DBG_CFR_DUMP, "cfr_dump:", "", + skb->data, skb->len); + + ret = ath11k_wmi_tlv_iter(ab, skb->data, skb->len, + ath11k_wmi_tlv_cfr_capture_evt_parse, + ¶ms); + if (ret) { + ath11k_warn(ab, "failed to parse cfr capture event tlv %d\n", + ret); + return; + } + + ret = ath11k_process_cfr_capture_event(ab, ¶ms); + if (ret) + ath11k_dbg(ab, ATH11K_DBG_CFR, + "failed to process cfr capture ret = %d\n", ret); +} + static void ath11k_wmi_tlv_op_rx(struct ath11k_base *ab, struct sk_buff *skb) { struct wmi_cmd_hdr *cmd_hdr; @@ -8882,6 +9022,9 @@ static void ath11k_wmi_tlv_op_rx(struct ath11k_base *ab, struct sk_buff *skb) case WMI_P2P_NOA_EVENTID: ath11k_wmi_p2p_noa_event(ab, skb); break; + case WMI_PEER_CFR_CAPTURE_EVENTID: + ath11k_wmi_parse_cfr_capture_event(ab, skb); + break; default: ath11k_dbg(ab, ATH11K_DBG_WMI, "unsupported event id 0x%x\n", id); break; diff --git a/drivers/net/wireless/ath/ath11k/wmi.h b/drivers/net/wireless/ath/ath11k/wmi.h index 0f0de24a3840..baed501b640b 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.h +++ b/drivers/net/wireless/ath/ath11k/wmi.h @@ -362,6 +362,10 @@ enum wmi_tlv_cmd_id { WMI_PEER_REORDER_QUEUE_REMOVE_CMDID, WMI_PEER_SET_RX_BLOCKSIZE_CMDID, WMI_PEER_ANTDIV_INFO_REQ_CMDID, + WMI_PEER_RESERVED0_CMDID, + WMI_PEER_TID_MSDUQ_QDEPTH_THRESH_UPDATE_CMDID, + WMI_PEER_TID_CONFIGURATIONS_CMDID, + WMI_PEER_CFR_CAPTURE_CMDID, WMI_BCN_TX_CMDID = WMI_TLV_CMD(WMI_GRP_MGMT), WMI_PDEV_SEND_BCN_CMDID, WMI_BCN_TMPL_CMDID, @@ -981,6 +985,7 @@ enum wmi_tlv_pdev_param { WMI_PDEV_PARAM_RADIO_CHAN_STATS_ENABLE, WMI_PDEV_PARAM_RADIO_DIAGNOSIS_ENABLE, WMI_PDEV_PARAM_MESH_MCAST_ENABLE, + WMI_PDEV_PARAM_PER_PEER_CFR_ENABLE = 0xa8, WMI_PDEV_PARAM_SET_CMD_OBSS_PD_THRESHOLD = 0xbc, WMI_PDEV_PARAM_SET_CMD_OBSS_PD_PER_AC = 0xbe, WMI_PDEV_PARAM_ENABLE_SR_PROHIBIT = 0xc6, @@ -1884,6 +1889,8 @@ enum wmi_tlv_tag { WMI_TAG_NDP_EVENT, WMI_TAG_PDEV_PEER_PKTLOG_FILTER_CMD = 0x301, WMI_TAG_PDEV_PEER_PKTLOG_FILTER_INFO, + WMI_TAG_PEER_CFR_CAPTURE_EVENT = 0x317, + WMI_TAG_CFR_CAPTURE_PHASE_PARAM = 0x33b, WMI_TAG_FILS_DISCOVERY_TMPL_CMD = 0x344, WMI_TAG_PDEV_SRG_BSS_COLOR_BITMAP_CMD = 0x37b, WMI_TAG_PDEV_SRG_PARTIAL_BSSID_BITMAP_CMD, @@ -3832,7 +3839,8 @@ struct wmi_scan_prob_req_oui_cmd { #define WMI_TX_PARAMS_DWORD1_BW_MASK GENMASK(14, 8) #define WMI_TX_PARAMS_DWORD1_PREAMBLE_TYPE GENMASK(19, 15) #define WMI_TX_PARAMS_DWORD1_FRAME_TYPE BIT(20) -#define WMI_TX_PARAMS_DWORD1_RSVD GENMASK(31, 21) +#define WMI_TX_PARAMS_DWORD1_CFR_CAPTURE BIT(21) +#define WMI_TX_PARAMS_DWORD1_RSVD GENMASK(31, 22) struct wmi_mgmt_send_params { u32 tlv_header; @@ -4217,6 +4225,87 @@ enum cc_setting_code { */ }; +enum ath11k_wmi_cfr_capture_bw { + WMI_PEER_CFR_CAPTURE_BW_20, + WMI_PEER_CFR_CAPTURE_BW_40, + WMI_PEER_CFR_CAPTURE_BW_80, + WMI_PEER_CFR_CAPTURE_BW_MAX, +}; + +enum ath11k_wmi_cfr_capture_method { + WMI_CFR_CAPTURE_METHOD_NULL_FRAME, + WMI_CFR_CAPTURE_METHOD_NULL_FRAME_WITH_PHASE, + WMI_CFR_CAPTURE_METHOD_PROBE_RESP, + WMI_CFR_CAPTURE_METHOD_MAX, +}; + +#define WMI_CFR_FRAME_TX_STATUS GENMASK(1, 0) +#define WMI_CFR_CAPTURE_STATUS_PEER_PS BIT(30) +#define WMI_CFR_PEER_CAPTURE_STATUS BIT(31) + +#define WMI_CFR_CORRELATION_INFO2_BUF_ADDR_HIGH GENMASK(3, 0) +#define WMI_CFR_CORRELATION_INFO2_PPDU_ID GENMASK(31, 16) + +#define WMI_CFR_CFO_MEASUREMENT_VALID BIT(0) +#define WMI_CFR_CFO_MEASUREMENT_RAW_DATA GENMASK(14, 1) + +struct ath11k_wmi_cfr_peer_tx_event_param { + u32 capture_method; + u32 vdev_id; + struct wmi_mac_addr mac_addr; + u32 chan_mhz; + u32 bandwidth; + u32 phy_mode; + u32 band_center_freq1; + u32 band_center_freq2; + u32 sts_count; + u32 correlation_info_1; + u32 correlation_info_2; + u32 status; + u32 timestamp_us; + u32 counter; + u32 chain_rssi[WMI_MAX_CHAINS]; + u32 cfo_measurement; + u32 rx_start_ts; +} __packed; + +struct ath11k_wmi_cfr_peer_tx_event_phase_param { + u32 chain_phase[WMI_MAX_CHAINS]; + u8 agc_gain[WMI_MAX_CHAINS]; +} __packed; + +enum ath11k_wmi_frame_tx_status { + WMI_FRAME_TX_STATUS_OK, + WMI_FRAME_TX_STATUS_XRETRY, + WMI_FRAME_TX_STATUS_DROP, + WMI_FRAME_TX_STATUS_FILTERED, +}; + +struct wmi_peer_cfr_capture_conf_arg { + enum ath11k_wmi_cfr_capture_bw bw; + enum ath11k_wmi_cfr_capture_method method; + u32 request; + u32 periodicity; +}; + +struct wmi_peer_cfr_capture_cmd_fixed_param { + u32 tlv_header; + u32 request; + struct wmi_mac_addr mac_addr; + u32 vdev_id; + u32 periodicity; + /* BW of measurement - of type enum ath11k_wmi_cfr_capture_bw */ + u32 bandwidth; + /* Method used to capture CFR - of type enum ath11k_wmi_cfr_capture_method */ + u32 capture_method; +} __packed; + +#define WMI_PEER_CFR_CAPTURE_ENABLE 1 +#define WMI_PEER_CFR_CAPTURE_DISABLE 0 + +/*periodicity in ms */ +#define WMI_PEER_CFR_PERIODICITY_MAX 600000 + static inline enum cc_setting_code ath11k_wmi_cc_setting_code_to_reg(enum wmi_reg_cc_setting_code status_code) { @@ -6346,7 +6435,7 @@ int ath11k_wmi_cmd_send(struct ath11k_pdev_wmi *wmi, struct sk_buff *skb, u32 cmd_id); struct sk_buff *ath11k_wmi_alloc_skb(struct ath11k_wmi_base *wmi_sc, u32 len); int ath11k_wmi_mgmt_send(struct ath11k *ar, u32 vdev_id, u32 buf_id, - struct sk_buff *frame); + struct sk_buff *frame, bool tx_params_valid); int ath11k_wmi_p2p_go_bcn_ie(struct ath11k *ar, u32 vdev_id, const u8 *p2p_ie); int ath11k_wmi_bcn_tmpl(struct ath11k *ar, u32 vdev_id, @@ -6531,5 +6620,7 @@ bool ath11k_wmi_supports_6ghz_cc_ext(struct ath11k *ar); int ath11k_wmi_send_vdev_set_tpc_power(struct ath11k *ar, u32 vdev_id, struct ath11k_reg_tpc_power_info *param); - +int ath11k_wmi_peer_set_cfr_capture_conf(struct ath11k *ar, + u32 vdev_id, const u8 *mac, + struct wmi_peer_cfr_capture_conf_arg *arg); #endif |
