Path: blob/main/sys/contrib/dev/athk/ath11k/cfr.c
283314 views
// SPDX-License-Identifier: BSD-3-Clause-Clear1/*2* Copyright (c) 2020-2021 The Linux Foundation. All rights reserved.3* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.4*/56#include <linux/relay.h>7#include "core.h"8#include "debug.h"910struct ath11k_dbring *ath11k_cfr_get_dbring(struct ath11k *ar)11{12if (ar->cfr_enabled)13return &ar->cfr.rx_ring;1415return NULL;16}1718static int ath11k_cfr_calculate_tones_from_dma_hdr(struct ath11k_cfr_dma_hdr *hdr)19{20u8 bw = FIELD_GET(CFIR_DMA_HDR_INFO1_UPLOAD_PKT_BW, hdr->info1);21u8 preamble = FIELD_GET(CFIR_DMA_HDR_INFO1_PREAMBLE_TYPE, hdr->info1);2223switch (preamble) {24case ATH11K_CFR_PREAMBLE_TYPE_LEGACY:25fallthrough;26case ATH11K_CFR_PREAMBLE_TYPE_VHT:27switch (bw) {28case 0:29return TONES_IN_20MHZ;30case 1: /* DUP40/VHT40 */31return TONES_IN_40MHZ;32case 2: /* DUP80/VHT80 */33return TONES_IN_80MHZ;34case 3: /* DUP160/VHT160 */35return TONES_IN_160MHZ;36default:37return TONES_INVALID;38}39case ATH11K_CFR_PREAMBLE_TYPE_HT:40switch (bw) {41case 0:42return TONES_IN_20MHZ;43case 1:44return TONES_IN_40MHZ;45default:46return TONES_INVALID;47}48default:49return TONES_INVALID;50}51}5253void ath11k_cfr_release_lut_entry(struct ath11k_look_up_table *lut)54{55memset(lut, 0, sizeof(*lut));56}5758static void ath11k_cfr_rfs_write(struct ath11k *ar, const void *head,59u32 head_len, const void *data, u32 data_len,60const void *tail, int tail_data)61{62struct ath11k_cfr *cfr = &ar->cfr;6364if (!cfr->rfs_cfr_capture)65return;6667relay_write(cfr->rfs_cfr_capture, head, head_len);68relay_write(cfr->rfs_cfr_capture, data, data_len);69relay_write(cfr->rfs_cfr_capture, tail, tail_data);70relay_flush(cfr->rfs_cfr_capture);71}7273static void ath11k_cfr_free_pending_dbr_events(struct ath11k *ar)74{75struct ath11k_cfr *cfr = &ar->cfr;76struct ath11k_look_up_table *lut;77int i;7879if (!cfr->lut)80return;8182for (i = 0; i < cfr->lut_num; i++) {83lut = &cfr->lut[i];84if (lut->dbr_recv && !lut->tx_recv &&85lut->dbr_tstamp < cfr->last_success_tstamp) {86ath11k_dbring_bufs_replenish(ar, &cfr->rx_ring, lut->buff,87WMI_DIRECT_BUF_CFR);88ath11k_cfr_release_lut_entry(lut);89cfr->flush_dbr_cnt++;90}91}92}9394/**95* ath11k_cfr_correlate_and_relay() - Correlate and relay CFR events96* @ar: Pointer to ath11k structure97* @lut: Lookup table for correlation98* @event_type: Type of event received (TX or DBR)99*100* Correlates WMI_PDEV_DMA_RING_BUF_RELEASE_EVENT (DBR) and101* WMI_PEER_CFR_CAPTURE_EVENT (TX capture) by PPDU ID. If both events102* are present and the PPDU IDs match, returns CORRELATE_STATUS_RELEASE103* to relay thecorrelated data to userspace. Otherwise returns104* CORRELATE_STATUS_HOLD to wait for the other event.105*106* Also checks pending DBR events and clears them when no corresponding TX107* capture event is received for the PPDU.108*109* Return: CORRELATE_STATUS_RELEASE or CORRELATE_STATUS_HOLD110*/111112static enum ath11k_cfr_correlate_status113ath11k_cfr_correlate_and_relay(struct ath11k *ar,114struct ath11k_look_up_table *lut,115u8 event_type)116{117enum ath11k_cfr_correlate_status status;118struct ath11k_cfr *cfr = &ar->cfr;119u64 diff;120121if (event_type == ATH11K_CORRELATE_TX_EVENT) {122if (lut->tx_recv)123cfr->cfr_dma_aborts++;124cfr->tx_evt_cnt++;125lut->tx_recv = true;126} else if (event_type == ATH11K_CORRELATE_DBR_EVENT) {127cfr->dbr_evt_cnt++;128lut->dbr_recv = true;129}130131if (lut->dbr_recv && lut->tx_recv) {132if (lut->dbr_ppdu_id == lut->tx_ppdu_id) {133/*134* 64-bit counters make wraparound highly improbable,135* wraparound handling is omitted.136*/137cfr->last_success_tstamp = lut->dbr_tstamp;138if (lut->dbr_tstamp > lut->txrx_tstamp) {139diff = lut->dbr_tstamp - lut->txrx_tstamp;140ath11k_dbg(ar->ab, ATH11K_DBG_CFR,141"txrx event -> dbr event delay = %u ms",142jiffies_to_msecs(diff));143} else if (lut->txrx_tstamp > lut->dbr_tstamp) {144diff = lut->txrx_tstamp - lut->dbr_tstamp;145ath11k_dbg(ar->ab, ATH11K_DBG_CFR,146"dbr event -> txrx event delay = %u ms",147jiffies_to_msecs(diff));148}149150ath11k_cfr_free_pending_dbr_events(ar);151152cfr->release_cnt++;153status = ATH11K_CORRELATE_STATUS_RELEASE;154} else {155/*156* Discard TXRX event on PPDU ID mismatch because multiple PPDUs157* may share the same DMA address due to ucode aborts.158*/159160ath11k_dbg(ar->ab, ATH11K_DBG_CFR,161"Received dbr event twice for the same lut entry");162lut->tx_recv = false;163lut->tx_ppdu_id = 0;164cfr->clear_txrx_event++;165cfr->cfr_dma_aborts++;166status = ATH11K_CORRELATE_STATUS_HOLD;167}168} else {169status = ATH11K_CORRELATE_STATUS_HOLD;170}171172return status;173}174175static int ath11k_cfr_process_data(struct ath11k *ar,176struct ath11k_dbring_data *param)177{178u32 end_magic = ATH11K_CFR_END_MAGIC;179struct ath11k_csi_cfr_header *header;180struct ath11k_cfr_dma_hdr *dma_hdr;181struct ath11k_cfr *cfr = &ar->cfr;182struct ath11k_look_up_table *lut;183struct ath11k_base *ab = ar->ab;184u32 buf_id, tones, length;185u8 num_chains;186int status;187u8 *data;188189data = param->data;190buf_id = param->buf_id;191192if (param->data_sz < sizeof(*dma_hdr))193return -EINVAL;194195dma_hdr = (struct ath11k_cfr_dma_hdr *)data;196197tones = ath11k_cfr_calculate_tones_from_dma_hdr(dma_hdr);198if (tones == TONES_INVALID) {199ath11k_warn(ar->ab, "Number of tones received is invalid\n");200return -EINVAL;201}202203num_chains = FIELD_GET(CFIR_DMA_HDR_INFO1_NUM_CHAINS,204dma_hdr->info1);205206length = sizeof(*dma_hdr);207length += tones * (num_chains + 1);208209spin_lock_bh(&cfr->lut_lock);210211if (!cfr->lut) {212spin_unlock_bh(&cfr->lut_lock);213return -EINVAL;214}215216lut = &cfr->lut[buf_id];217218ath11k_dbg_dump(ab, ATH11K_DBG_CFR_DUMP, "data_from_buf_rel:", "",219data, length);220221lut->buff = param->buff;222lut->data = data;223lut->data_len = length;224lut->dbr_ppdu_id = dma_hdr->phy_ppdu_id;225lut->dbr_tstamp = jiffies;226227memcpy(&lut->hdr, dma_hdr, sizeof(*dma_hdr));228229header = &lut->header;230header->meta_data.channel_bw = FIELD_GET(CFIR_DMA_HDR_INFO1_UPLOAD_PKT_BW,231dma_hdr->info1);232header->meta_data.length = length;233234status = ath11k_cfr_correlate_and_relay(ar, lut,235ATH11K_CORRELATE_DBR_EVENT);236if (status == ATH11K_CORRELATE_STATUS_RELEASE) {237ath11k_dbg(ab, ATH11K_DBG_CFR,238"releasing CFR data to user space");239ath11k_cfr_rfs_write(ar, &lut->header,240sizeof(struct ath11k_csi_cfr_header),241lut->data, lut->data_len,242&end_magic, sizeof(u32));243ath11k_cfr_release_lut_entry(lut);244} else if (status == ATH11K_CORRELATE_STATUS_HOLD) {245ath11k_dbg(ab, ATH11K_DBG_CFR,246"tx event is not yet received holding the buf");247}248249spin_unlock_bh(&cfr->lut_lock);250251return status;252}253254static void ath11k_cfr_fill_hdr_info(struct ath11k *ar,255struct ath11k_csi_cfr_header *header,256struct ath11k_cfr_peer_tx_param *params)257{258struct ath11k_cfr *cfr;259260cfr = &ar->cfr;261header->cfr_metadata_version = ATH11K_CFR_META_VERSION_4;262header->cfr_data_version = ATH11K_CFR_DATA_VERSION_1;263header->cfr_metadata_len = sizeof(struct cfr_metadata);264header->chip_type = ar->ab->hw_rev;265header->meta_data.status = FIELD_GET(WMI_CFR_PEER_CAPTURE_STATUS,266params->status);267header->meta_data.capture_bw = params->bandwidth;268269/*270* FW reports phymode will always be HE mode.271* Replace it with cached phy mode during peer assoc272*/273header->meta_data.phy_mode = cfr->phymode;274275header->meta_data.prim20_chan = params->primary_20mhz_chan;276header->meta_data.center_freq1 = params->band_center_freq1;277header->meta_data.center_freq2 = params->band_center_freq2;278279/*280* CFR capture is triggered by the ACK of a QoS Null frame:281* - 20 MHz: Legacy ACK282* - 40/80/160 MHz: DUP Legacy ACK283*/284header->meta_data.capture_mode = params->bandwidth ?285ATH11K_CFR_CAPTURE_DUP_LEGACY_ACK : ATH11K_CFR_CAPTURE_LEGACY_ACK;286header->meta_data.capture_type = params->capture_method;287header->meta_data.num_rx_chain = ar->num_rx_chains;288header->meta_data.sts_count = params->spatial_streams;289header->meta_data.timestamp = params->timestamp_us;290ether_addr_copy(header->meta_data.peer_addr, params->peer_mac_addr);291memcpy(header->meta_data.chain_rssi, params->chain_rssi,292sizeof(params->chain_rssi));293memcpy(header->meta_data.chain_phase, params->chain_phase,294sizeof(params->chain_phase));295memcpy(header->meta_data.agc_gain, params->agc_gain,296sizeof(params->agc_gain));297}298299int ath11k_process_cfr_capture_event(struct ath11k_base *ab,300struct ath11k_cfr_peer_tx_param *params)301{302struct ath11k_look_up_table *lut = NULL;303u32 end_magic = ATH11K_CFR_END_MAGIC;304struct ath11k_csi_cfr_header *header;305struct ath11k_dbring_element *buff;306struct ath11k_cfr *cfr;307dma_addr_t buf_addr;308struct ath11k *ar;309u8 tx_status;310int status;311int i;312313rcu_read_lock();314ar = ath11k_mac_get_ar_by_vdev_id(ab, params->vdev_id);315if (!ar) {316rcu_read_unlock();317ath11k_warn(ab, "Failed to get ar for vdev id %d\n",318params->vdev_id);319return -ENOENT;320}321322cfr = &ar->cfr;323rcu_read_unlock();324325if (WMI_CFR_CAPTURE_STATUS_PEER_PS & params->status) {326ath11k_warn(ab, "CFR capture failed as peer %pM is in powersave",327params->peer_mac_addr);328return -EINVAL;329}330331if (!(WMI_CFR_PEER_CAPTURE_STATUS & params->status)) {332ath11k_warn(ab, "CFR capture failed for the peer : %pM",333params->peer_mac_addr);334cfr->tx_peer_status_cfr_fail++;335return -EINVAL;336}337338tx_status = FIELD_GET(WMI_CFR_FRAME_TX_STATUS, params->status);339if (tx_status != WMI_FRAME_TX_STATUS_OK) {340ath11k_warn(ab, "WMI tx status %d for the peer %pM",341tx_status, params->peer_mac_addr);342cfr->tx_evt_status_cfr_fail++;343return -EINVAL;344}345346buf_addr = (((u64)FIELD_GET(WMI_CFR_CORRELATION_INFO2_BUF_ADDR_HIGH,347params->correlation_info_2)) << 32) |348params->correlation_info_1;349350spin_lock_bh(&cfr->lut_lock);351352if (!cfr->lut) {353spin_unlock_bh(&cfr->lut_lock);354return -EINVAL;355}356357for (i = 0; i < cfr->lut_num; i++) {358struct ath11k_look_up_table *temp = &cfr->lut[i];359360if (temp->dbr_address == buf_addr) {361lut = &cfr->lut[i];362break;363}364}365366if (!lut) {367spin_unlock_bh(&cfr->lut_lock);368ath11k_warn(ab, "lut failure to process tx event\n");369cfr->tx_dbr_lookup_fail++;370return -EINVAL;371}372373lut->tx_ppdu_id = FIELD_GET(WMI_CFR_CORRELATION_INFO2_PPDU_ID,374params->correlation_info_2);375lut->txrx_tstamp = jiffies;376377header = &lut->header;378header->start_magic_num = ATH11K_CFR_START_MAGIC;379header->vendorid = VENDOR_QCA;380header->platform_type = PLATFORM_TYPE_ARM;381382ath11k_cfr_fill_hdr_info(ar, header, params);383384status = ath11k_cfr_correlate_and_relay(ar, lut,385ATH11K_CORRELATE_TX_EVENT);386if (status == ATH11K_CORRELATE_STATUS_RELEASE) {387ath11k_dbg(ab, ATH11K_DBG_CFR,388"Releasing CFR data to user space");389ath11k_cfr_rfs_write(ar, &lut->header,390sizeof(struct ath11k_csi_cfr_header),391lut->data, lut->data_len,392&end_magic, sizeof(u32));393buff = lut->buff;394ath11k_cfr_release_lut_entry(lut);395396ath11k_dbring_bufs_replenish(ar, &cfr->rx_ring, buff,397WMI_DIRECT_BUF_CFR);398} else if (status == ATH11K_CORRELATE_STATUS_HOLD) {399ath11k_dbg(ab, ATH11K_DBG_CFR,400"dbr event is not yet received holding buf\n");401}402403spin_unlock_bh(&cfr->lut_lock);404405return 0;406}407408/* Helper function to check whether the given peer mac address409* is in unassociated peer pool or not.410*/411bool ath11k_cfr_peer_is_in_cfr_unassoc_pool(struct ath11k *ar, const u8 *peer_mac)412{413struct ath11k_cfr *cfr = &ar->cfr;414struct cfr_unassoc_pool_entry *entry;415int i;416417if (!ar->cfr_enabled)418return false;419420spin_lock_bh(&cfr->lock);421for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {422entry = &cfr->unassoc_pool[i];423if (!entry->is_valid)424continue;425426if (ether_addr_equal(peer_mac, entry->peer_mac)) {427spin_unlock_bh(&cfr->lock);428return true;429}430}431432spin_unlock_bh(&cfr->lock);433434return false;435}436437void ath11k_cfr_update_unassoc_pool_entry(struct ath11k *ar,438const u8 *peer_mac)439{440struct ath11k_cfr *cfr = &ar->cfr;441struct cfr_unassoc_pool_entry *entry;442int i;443444spin_lock_bh(&cfr->lock);445for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {446entry = &cfr->unassoc_pool[i];447if (!entry->is_valid)448continue;449450if (ether_addr_equal(peer_mac, entry->peer_mac) &&451entry->period == 0) {452memset(entry->peer_mac, 0, ETH_ALEN);453entry->is_valid = false;454cfr->cfr_enabled_peer_cnt--;455break;456}457}458459spin_unlock_bh(&cfr->lock);460}461462void ath11k_cfr_decrement_peer_count(struct ath11k *ar,463struct ath11k_sta *arsta)464{465struct ath11k_cfr *cfr = &ar->cfr;466467spin_lock_bh(&cfr->lock);468469if (arsta->cfr_capture.cfr_enable)470cfr->cfr_enabled_peer_cnt--;471472spin_unlock_bh(&cfr->lock);473}474475static enum ath11k_wmi_cfr_capture_bw476ath11k_cfr_bw_to_fw_cfr_bw(enum ath11k_cfr_capture_bw bw)477{478switch (bw) {479case ATH11K_CFR_CAPTURE_BW_20:480return WMI_PEER_CFR_CAPTURE_BW_20;481case ATH11K_CFR_CAPTURE_BW_40:482return WMI_PEER_CFR_CAPTURE_BW_40;483case ATH11K_CFR_CAPTURE_BW_80:484return WMI_PEER_CFR_CAPTURE_BW_80;485default:486return WMI_PEER_CFR_CAPTURE_BW_MAX;487}488}489490static enum ath11k_wmi_cfr_capture_method491ath11k_cfr_method_to_fw_cfr_method(enum ath11k_cfr_capture_method method)492{493switch (method) {494case ATH11K_CFR_CAPTURE_METHOD_NULL_FRAME:495return WMI_CFR_CAPTURE_METHOD_NULL_FRAME;496case ATH11K_CFR_CAPTURE_METHOD_NULL_FRAME_WITH_PHASE:497return WMI_CFR_CAPTURE_METHOD_NULL_FRAME_WITH_PHASE;498case ATH11K_CFR_CAPTURE_METHOD_PROBE_RESP:499return WMI_CFR_CAPTURE_METHOD_PROBE_RESP;500default:501return WMI_CFR_CAPTURE_METHOD_MAX;502}503}504505int ath11k_cfr_send_peer_cfr_capture_cmd(struct ath11k *ar,506struct ath11k_sta *arsta,507struct ath11k_per_peer_cfr_capture *params,508const u8 *peer_mac)509{510struct ath11k_cfr *cfr = &ar->cfr;511struct wmi_peer_cfr_capture_conf_arg arg;512enum ath11k_wmi_cfr_capture_bw bw;513enum ath11k_wmi_cfr_capture_method method;514int ret = 0;515516if (cfr->cfr_enabled_peer_cnt >= ATH11K_MAX_CFR_ENABLED_CLIENTS &&517!arsta->cfr_capture.cfr_enable) {518ath11k_err(ar->ab, "CFR enable peer threshold reached %u\n",519cfr->cfr_enabled_peer_cnt);520return -ENOSPC;521}522523if (params->cfr_enable == arsta->cfr_capture.cfr_enable &&524params->cfr_period == arsta->cfr_capture.cfr_period &&525params->cfr_method == arsta->cfr_capture.cfr_method &&526params->cfr_bw == arsta->cfr_capture.cfr_bw)527return ret;528529if (!params->cfr_enable && !arsta->cfr_capture.cfr_enable)530return ret;531532bw = ath11k_cfr_bw_to_fw_cfr_bw(params->cfr_bw);533if (bw >= WMI_PEER_CFR_CAPTURE_BW_MAX) {534ath11k_warn(ar->ab, "FW doesn't support configured bw %d\n",535params->cfr_bw);536return -EINVAL;537}538539method = ath11k_cfr_method_to_fw_cfr_method(params->cfr_method);540if (method >= WMI_CFR_CAPTURE_METHOD_MAX) {541ath11k_warn(ar->ab, "FW doesn't support configured method %d\n",542params->cfr_method);543return -EINVAL;544}545546arg.request = params->cfr_enable;547arg.periodicity = params->cfr_period;548arg.bw = bw;549arg.method = method;550551ret = ath11k_wmi_peer_set_cfr_capture_conf(ar, arsta->arvif->vdev_id,552peer_mac, &arg);553if (ret) {554ath11k_warn(ar->ab,555"failed to send cfr capture info: vdev_id %u peer %pM: %d\n",556arsta->arvif->vdev_id, peer_mac, ret);557return ret;558}559560spin_lock_bh(&cfr->lock);561562if (params->cfr_enable &&563params->cfr_enable != arsta->cfr_capture.cfr_enable)564cfr->cfr_enabled_peer_cnt++;565else if (!params->cfr_enable)566cfr->cfr_enabled_peer_cnt--;567568spin_unlock_bh(&cfr->lock);569570arsta->cfr_capture.cfr_enable = params->cfr_enable;571arsta->cfr_capture.cfr_period = params->cfr_period;572arsta->cfr_capture.cfr_method = params->cfr_method;573arsta->cfr_capture.cfr_bw = params->cfr_bw;574575return ret;576}577578void ath11k_cfr_update_unassoc_pool(struct ath11k *ar,579struct ath11k_per_peer_cfr_capture *params,580u8 *peer_mac)581{582struct ath11k_cfr *cfr = &ar->cfr;583struct cfr_unassoc_pool_entry *entry;584int available_idx = -1;585int i;586587guard(spinlock_bh)(&cfr->lock);588589if (!params->cfr_enable) {590for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {591entry = &cfr->unassoc_pool[i];592if (ether_addr_equal(peer_mac, entry->peer_mac)) {593memset(entry->peer_mac, 0, ETH_ALEN);594entry->is_valid = false;595cfr->cfr_enabled_peer_cnt--;596break;597}598}599return;600}601602if (cfr->cfr_enabled_peer_cnt >= ATH11K_MAX_CFR_ENABLED_CLIENTS) {603ath11k_info(ar->ab, "Max cfr peer threshold reached\n");604return;605}606607for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {608entry = &cfr->unassoc_pool[i];609610if (ether_addr_equal(peer_mac, entry->peer_mac)) {611ath11k_info(ar->ab,612"peer entry already present updating params\n");613entry->period = params->cfr_period;614available_idx = -1;615break;616}617618if (available_idx < 0 && !entry->is_valid)619available_idx = i;620}621622if (available_idx >= 0) {623entry = &cfr->unassoc_pool[available_idx];624ether_addr_copy(entry->peer_mac, peer_mac);625entry->period = params->cfr_period;626entry->is_valid = true;627cfr->cfr_enabled_peer_cnt++;628}629}630631static ssize_t ath11k_read_file_enable_cfr(struct file *file,632char __user *user_buf,633size_t count, loff_t *ppos)634{635struct ath11k *ar = file->private_data;636char buf[32] = {};637size_t len;638639mutex_lock(&ar->conf_mutex);640len = scnprintf(buf, sizeof(buf), "%d\n", ar->cfr_enabled);641mutex_unlock(&ar->conf_mutex);642643return simple_read_from_buffer(user_buf, count, ppos, buf, len);644}645646static ssize_t ath11k_write_file_enable_cfr(struct file *file,647const char __user *ubuf,648size_t count, loff_t *ppos)649{650struct ath11k *ar = file->private_data;651u32 enable_cfr;652int ret;653654if (kstrtouint_from_user(ubuf, count, 0, &enable_cfr))655return -EINVAL;656657guard(mutex)(&ar->conf_mutex);658659if (ar->state != ATH11K_STATE_ON)660return -ENETDOWN;661662if (enable_cfr > 1)663return -EINVAL;664665if (ar->cfr_enabled == enable_cfr)666return count;667668ret = ath11k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_PER_PEER_CFR_ENABLE,669enable_cfr, ar->pdev->pdev_id);670if (ret) {671ath11k_warn(ar->ab,672"Failed to enable/disable per peer cfr %d\n", ret);673return ret;674}675676ar->cfr_enabled = enable_cfr;677678return count;679}680681static const struct file_operations fops_enable_cfr = {682.read = ath11k_read_file_enable_cfr,683.write = ath11k_write_file_enable_cfr,684.open = simple_open,685.owner = THIS_MODULE,686.llseek = default_llseek,687};688689static ssize_t ath11k_write_file_cfr_unassoc(struct file *file,690const char __user *ubuf,691size_t count, loff_t *ppos)692{693struct ath11k *ar = file->private_data;694struct ath11k_cfr *cfr = &ar->cfr;695struct cfr_unassoc_pool_entry *entry;696char buf[64] = {};697u8 peer_mac[6];698u32 cfr_capture_enable;699u32 cfr_capture_period;700int available_idx = -1;701int ret, i;702703simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, ubuf, count);704705guard(mutex)(&ar->conf_mutex);706guard(spinlock_bh)(&cfr->lock);707708if (ar->state != ATH11K_STATE_ON)709return -ENETDOWN;710711if (!ar->cfr_enabled) {712ath11k_err(ar->ab, "CFR is not enabled on this pdev %d\n",713ar->pdev_idx);714return -EINVAL;715}716717ret = sscanf(buf, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx %u %u",718&peer_mac[0], &peer_mac[1], &peer_mac[2], &peer_mac[3],719&peer_mac[4], &peer_mac[5], &cfr_capture_enable,720&cfr_capture_period);721722if (ret < 1)723return -EINVAL;724725if (cfr_capture_enable && ret != 8)726return -EINVAL;727728if (!cfr_capture_enable) {729for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {730entry = &cfr->unassoc_pool[i];731if (ether_addr_equal(peer_mac, entry->peer_mac)) {732memset(entry->peer_mac, 0, ETH_ALEN);733entry->is_valid = false;734cfr->cfr_enabled_peer_cnt--;735}736}737738return count;739}740741if (cfr->cfr_enabled_peer_cnt >= ATH11K_MAX_CFR_ENABLED_CLIENTS) {742ath11k_info(ar->ab, "Max cfr peer threshold reached\n");743return count;744}745746for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {747entry = &cfr->unassoc_pool[i];748749if (available_idx < 0 && !entry->is_valid)750available_idx = i;751752if (ether_addr_equal(peer_mac, entry->peer_mac)) {753ath11k_info(ar->ab,754"peer entry already present updating params\n");755entry->period = cfr_capture_period;756return count;757}758}759760if (available_idx >= 0) {761entry = &cfr->unassoc_pool[available_idx];762ether_addr_copy(entry->peer_mac, peer_mac);763entry->period = cfr_capture_period;764entry->is_valid = true;765cfr->cfr_enabled_peer_cnt++;766}767768return count;769}770771static ssize_t ath11k_read_file_cfr_unassoc(struct file *file,772char __user *ubuf,773size_t count, loff_t *ppos)774{775struct ath11k *ar = file->private_data;776struct ath11k_cfr *cfr = &ar->cfr;777struct cfr_unassoc_pool_entry *entry;778char buf[512] = {};779int len = 0, i;780781spin_lock_bh(&cfr->lock);782783for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {784entry = &cfr->unassoc_pool[i];785if (entry->is_valid)786len += scnprintf(buf + len, sizeof(buf) - len,787"peer: %pM period: %u\n",788entry->peer_mac, entry->period);789}790791spin_unlock_bh(&cfr->lock);792793return simple_read_from_buffer(ubuf, count, ppos, buf, len);794}795796static const struct file_operations fops_configure_cfr_unassoc = {797.write = ath11k_write_file_cfr_unassoc,798.read = ath11k_read_file_cfr_unassoc,799.open = simple_open,800.owner = THIS_MODULE,801.llseek = default_llseek,802};803804static void ath11k_cfr_debug_unregister(struct ath11k *ar)805{806debugfs_remove(ar->cfr.enable_cfr);807ar->cfr.enable_cfr = NULL;808debugfs_remove(ar->cfr.cfr_unassoc);809ar->cfr.cfr_unassoc = NULL;810811relay_close(ar->cfr.rfs_cfr_capture);812ar->cfr.rfs_cfr_capture = NULL;813}814815static struct dentry *ath11k_cfr_create_buf_file_handler(const char *filename,816struct dentry *parent,817umode_t mode,818struct rchan_buf *buf,819int *is_global)820{821struct dentry *buf_file;822823buf_file = debugfs_create_file(filename, mode, parent, buf,824&relay_file_operations);825*is_global = 1;826return buf_file;827}828829static int ath11k_cfr_remove_buf_file_handler(struct dentry *dentry)830{831debugfs_remove(dentry);832833return 0;834}835836static const struct rchan_callbacks rfs_cfr_capture_cb = {837.create_buf_file = ath11k_cfr_create_buf_file_handler,838.remove_buf_file = ath11k_cfr_remove_buf_file_handler,839};840841static void ath11k_cfr_debug_register(struct ath11k *ar)842{843ar->cfr.rfs_cfr_capture = relay_open("cfr_capture",844ar->debug.debugfs_pdev,845ar->ab->hw_params.cfr_stream_buf_size,846ar->ab->hw_params.cfr_num_stream_bufs,847&rfs_cfr_capture_cb, NULL);848849ar->cfr.enable_cfr = debugfs_create_file("enable_cfr", 0600,850ar->debug.debugfs_pdev, ar,851&fops_enable_cfr);852853ar->cfr.cfr_unassoc = debugfs_create_file("cfr_unassoc", 0600,854ar->debug.debugfs_pdev, ar,855&fops_configure_cfr_unassoc);856}857858void ath11k_cfr_lut_update_paddr(struct ath11k *ar, dma_addr_t paddr,859u32 buf_id)860{861struct ath11k_cfr *cfr = &ar->cfr;862863if (cfr->lut)864cfr->lut[buf_id].dbr_address = paddr;865}866867void ath11k_cfr_update_phymode(struct ath11k *ar, enum wmi_phy_mode phymode)868{869struct ath11k_cfr *cfr = &ar->cfr;870871cfr->phymode = phymode;872}873874static void ath11k_cfr_ring_free(struct ath11k *ar)875{876struct ath11k_cfr *cfr = &ar->cfr;877878ath11k_dbring_buf_cleanup(ar, &cfr->rx_ring);879ath11k_dbring_srng_cleanup(ar, &cfr->rx_ring);880}881882static int ath11k_cfr_ring_alloc(struct ath11k *ar,883struct ath11k_dbring_cap *db_cap)884{885struct ath11k_cfr *cfr = &ar->cfr;886int ret;887888ret = ath11k_dbring_srng_setup(ar, &cfr->rx_ring,889ATH11K_CFR_NUM_RING_ENTRIES,890db_cap->min_elem);891if (ret) {892ath11k_warn(ar->ab, "failed to setup db ring: %d\n", ret);893return ret;894}895896ath11k_dbring_set_cfg(ar, &cfr->rx_ring,897ATH11K_CFR_NUM_RESP_PER_EVENT,898ATH11K_CFR_EVENT_TIMEOUT_MS,899ath11k_cfr_process_data);900901ret = ath11k_dbring_buf_setup(ar, &cfr->rx_ring, db_cap);902if (ret) {903ath11k_warn(ar->ab, "failed to setup db ring buffer: %d\n", ret);904goto srng_cleanup;905}906907ret = ath11k_dbring_wmi_cfg_setup(ar, &cfr->rx_ring, WMI_DIRECT_BUF_CFR);908if (ret) {909ath11k_warn(ar->ab, "failed to setup db ring cfg: %d\n", ret);910goto buffer_cleanup;911}912913return 0;914915buffer_cleanup:916ath11k_dbring_buf_cleanup(ar, &cfr->rx_ring);917srng_cleanup:918ath11k_dbring_srng_cleanup(ar, &cfr->rx_ring);919return ret;920}921922void ath11k_cfr_deinit(struct ath11k_base *ab)923{924struct ath11k_cfr *cfr;925struct ath11k *ar;926int i;927928if (!test_bit(WMI_TLV_SERVICE_CFR_CAPTURE_SUPPORT, ab->wmi_ab.svc_map) ||929!ab->hw_params.cfr_support)930return;931932for (i = 0; i < ab->num_radios; i++) {933ar = ab->pdevs[i].ar;934cfr = &ar->cfr;935936if (!cfr->enabled)937continue;938939ath11k_cfr_debug_unregister(ar);940ath11k_cfr_ring_free(ar);941942spin_lock_bh(&cfr->lut_lock);943kfree(cfr->lut);944cfr->lut = NULL;945cfr->enabled = false;946spin_unlock_bh(&cfr->lut_lock);947}948}949950int ath11k_cfr_init(struct ath11k_base *ab)951{952struct ath11k_dbring_cap db_cap;953struct ath11k_cfr *cfr;954u32 num_lut_entries;955struct ath11k *ar;956int i, ret;957958if (!test_bit(WMI_TLV_SERVICE_CFR_CAPTURE_SUPPORT, ab->wmi_ab.svc_map) ||959!ab->hw_params.cfr_support)960return 0;961962for (i = 0; i < ab->num_radios; i++) {963ar = ab->pdevs[i].ar;964cfr = &ar->cfr;965966ret = ath11k_dbring_get_cap(ar->ab, ar->pdev_idx,967WMI_DIRECT_BUF_CFR, &db_cap);968if (ret)969continue;970971idr_init(&cfr->rx_ring.bufs_idr);972spin_lock_init(&cfr->rx_ring.idr_lock);973spin_lock_init(&cfr->lock);974spin_lock_init(&cfr->lut_lock);975976num_lut_entries = min_t(u32, CFR_MAX_LUT_ENTRIES, db_cap.min_elem);977cfr->lut = kzalloc_objs(*cfr->lut, num_lut_entries);978if (!cfr->lut) {979ret = -ENOMEM;980goto err;981}982983ret = ath11k_cfr_ring_alloc(ar, &db_cap);984if (ret) {985ath11k_warn(ab, "failed to init cfr ring for pdev %d: %d\n",986i, ret);987spin_lock_bh(&cfr->lut_lock);988kfree(cfr->lut);989cfr->lut = NULL;990cfr->enabled = false;991spin_unlock_bh(&cfr->lut_lock);992goto err;993}994995cfr->lut_num = num_lut_entries;996cfr->enabled = true;997998ath11k_cfr_debug_register(ar);999}10001001return 0;10021003err:1004for (i = i - 1; i >= 0; i--) {1005ar = ab->pdevs[i].ar;1006cfr = &ar->cfr;10071008if (!cfr->enabled)1009continue;10101011ath11k_cfr_debug_unregister(ar);1012ath11k_cfr_ring_free(ar);10131014spin_lock_bh(&cfr->lut_lock);1015kfree(cfr->lut);1016cfr->lut = NULL;1017cfr->enabled = false;1018spin_unlock_bh(&cfr->lut_lock);1019}1020return ret;1021}102210231024