1023 lines
26 KiB
C
1023 lines
26 KiB
C
// SPDX-License-Identifier: BSD-3-Clause-Clear
|
|
/*
|
|
* Copyright (c) 2020-2021 The Linux Foundation. All rights reserved.
|
|
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
|
|
*/
|
|
|
|
#include <linux/relay.h>
|
|
#include "core.h"
|
|
#include "debug.h"
|
|
|
|
struct ath11k_dbring *ath11k_cfr_get_dbring(struct ath11k *ar)
|
|
{
|
|
if (ar->cfr_enabled)
|
|
return &ar->cfr.rx_ring;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int ath11k_cfr_calculate_tones_from_dma_hdr(struct ath11k_cfr_dma_hdr *hdr)
|
|
{
|
|
u8 bw = FIELD_GET(CFIR_DMA_HDR_INFO1_UPLOAD_PKT_BW, hdr->info1);
|
|
u8 preamble = FIELD_GET(CFIR_DMA_HDR_INFO1_PREAMBLE_TYPE, hdr->info1);
|
|
|
|
switch (preamble) {
|
|
case ATH11K_CFR_PREAMBLE_TYPE_LEGACY:
|
|
fallthrough;
|
|
case ATH11K_CFR_PREAMBLE_TYPE_VHT:
|
|
switch (bw) {
|
|
case 0:
|
|
return TONES_IN_20MHZ;
|
|
case 1: /* DUP40/VHT40 */
|
|
return TONES_IN_40MHZ;
|
|
case 2: /* DUP80/VHT80 */
|
|
return TONES_IN_80MHZ;
|
|
case 3: /* DUP160/VHT160 */
|
|
return TONES_IN_160MHZ;
|
|
default:
|
|
return TONES_INVALID;
|
|
}
|
|
case ATH11K_CFR_PREAMBLE_TYPE_HT:
|
|
switch (bw) {
|
|
case 0:
|
|
return TONES_IN_20MHZ;
|
|
case 1:
|
|
return TONES_IN_40MHZ;
|
|
default:
|
|
return TONES_INVALID;
|
|
}
|
|
default:
|
|
return TONES_INVALID;
|
|
}
|
|
}
|
|
|
|
void ath11k_cfr_release_lut_entry(struct ath11k_look_up_table *lut)
|
|
{
|
|
memset(lut, 0, sizeof(*lut));
|
|
}
|
|
|
|
static void ath11k_cfr_rfs_write(struct ath11k *ar, const void *head,
|
|
u32 head_len, const void *data, u32 data_len,
|
|
const void *tail, int tail_data)
|
|
{
|
|
struct ath11k_cfr *cfr = &ar->cfr;
|
|
|
|
if (!cfr->rfs_cfr_capture)
|
|
return;
|
|
|
|
relay_write(cfr->rfs_cfr_capture, head, head_len);
|
|
relay_write(cfr->rfs_cfr_capture, data, data_len);
|
|
relay_write(cfr->rfs_cfr_capture, tail, tail_data);
|
|
relay_flush(cfr->rfs_cfr_capture);
|
|
}
|
|
|
|
static void ath11k_cfr_free_pending_dbr_events(struct ath11k *ar)
|
|
{
|
|
struct ath11k_cfr *cfr = &ar->cfr;
|
|
struct ath11k_look_up_table *lut;
|
|
int i;
|
|
|
|
if (!cfr->lut)
|
|
return;
|
|
|
|
for (i = 0; i < cfr->lut_num; i++) {
|
|
lut = &cfr->lut[i];
|
|
if (lut->dbr_recv && !lut->tx_recv &&
|
|
lut->dbr_tstamp < cfr->last_success_tstamp) {
|
|
ath11k_dbring_bufs_replenish(ar, &cfr->rx_ring, lut->buff,
|
|
WMI_DIRECT_BUF_CFR);
|
|
ath11k_cfr_release_lut_entry(lut);
|
|
cfr->flush_dbr_cnt++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ath11k_cfr_correlate_and_relay() - Correlate and relay CFR events
|
|
* @ar: Pointer to ath11k structure
|
|
* @lut: Lookup table for correlation
|
|
* @event_type: Type of event received (TX or DBR)
|
|
*
|
|
* Correlates WMI_PDEV_DMA_RING_BUF_RELEASE_EVENT (DBR) and
|
|
* WMI_PEER_CFR_CAPTURE_EVENT (TX capture) by PPDU ID. If both events
|
|
* are present and the PPDU IDs match, returns CORRELATE_STATUS_RELEASE
|
|
* to relay thecorrelated data to userspace. Otherwise returns
|
|
* CORRELATE_STATUS_HOLD to wait for the other event.
|
|
*
|
|
* Also checks pending DBR events and clears them when no corresponding TX
|
|
* capture event is received for the PPDU.
|
|
*
|
|
* Return: CORRELATE_STATUS_RELEASE or CORRELATE_STATUS_HOLD
|
|
*/
|
|
|
|
static enum ath11k_cfr_correlate_status
|
|
ath11k_cfr_correlate_and_relay(struct ath11k *ar,
|
|
struct ath11k_look_up_table *lut,
|
|
u8 event_type)
|
|
{
|
|
enum ath11k_cfr_correlate_status status;
|
|
struct ath11k_cfr *cfr = &ar->cfr;
|
|
u64 diff;
|
|
|
|
if (event_type == ATH11K_CORRELATE_TX_EVENT) {
|
|
if (lut->tx_recv)
|
|
cfr->cfr_dma_aborts++;
|
|
cfr->tx_evt_cnt++;
|
|
lut->tx_recv = true;
|
|
} else if (event_type == ATH11K_CORRELATE_DBR_EVENT) {
|
|
cfr->dbr_evt_cnt++;
|
|
lut->dbr_recv = true;
|
|
}
|
|
|
|
if (lut->dbr_recv && lut->tx_recv) {
|
|
if (lut->dbr_ppdu_id == lut->tx_ppdu_id) {
|
|
/*
|
|
* 64-bit counters make wraparound highly improbable,
|
|
* wraparound handling is omitted.
|
|
*/
|
|
cfr->last_success_tstamp = lut->dbr_tstamp;
|
|
if (lut->dbr_tstamp > lut->txrx_tstamp) {
|
|
diff = lut->dbr_tstamp - lut->txrx_tstamp;
|
|
ath11k_dbg(ar->ab, ATH11K_DBG_CFR,
|
|
"txrx event -> dbr event delay = %u ms",
|
|
jiffies_to_msecs(diff));
|
|
} else if (lut->txrx_tstamp > lut->dbr_tstamp) {
|
|
diff = lut->txrx_tstamp - lut->dbr_tstamp;
|
|
ath11k_dbg(ar->ab, ATH11K_DBG_CFR,
|
|
"dbr event -> txrx event delay = %u ms",
|
|
jiffies_to_msecs(diff));
|
|
}
|
|
|
|
ath11k_cfr_free_pending_dbr_events(ar);
|
|
|
|
cfr->release_cnt++;
|
|
status = ATH11K_CORRELATE_STATUS_RELEASE;
|
|
} else {
|
|
/*
|
|
* Discard TXRX event on PPDU ID mismatch because multiple PPDUs
|
|
* may share the same DMA address due to ucode aborts.
|
|
*/
|
|
|
|
ath11k_dbg(ar->ab, ATH11K_DBG_CFR,
|
|
"Received dbr event twice for the same lut entry");
|
|
lut->tx_recv = false;
|
|
lut->tx_ppdu_id = 0;
|
|
cfr->clear_txrx_event++;
|
|
cfr->cfr_dma_aborts++;
|
|
status = ATH11K_CORRELATE_STATUS_HOLD;
|
|
}
|
|
} else {
|
|
status = ATH11K_CORRELATE_STATUS_HOLD;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static int ath11k_cfr_process_data(struct ath11k *ar,
|
|
struct ath11k_dbring_data *param)
|
|
{
|
|
u32 end_magic = ATH11K_CFR_END_MAGIC;
|
|
struct ath11k_csi_cfr_header *header;
|
|
struct ath11k_cfr_dma_hdr *dma_hdr;
|
|
struct ath11k_cfr *cfr = &ar->cfr;
|
|
struct ath11k_look_up_table *lut;
|
|
struct ath11k_base *ab = ar->ab;
|
|
u32 buf_id, tones, length;
|
|
u8 num_chains;
|
|
int status;
|
|
u8 *data;
|
|
|
|
data = param->data;
|
|
buf_id = param->buf_id;
|
|
|
|
if (param->data_sz < sizeof(*dma_hdr))
|
|
return -EINVAL;
|
|
|
|
dma_hdr = (struct ath11k_cfr_dma_hdr *)data;
|
|
|
|
tones = ath11k_cfr_calculate_tones_from_dma_hdr(dma_hdr);
|
|
if (tones == TONES_INVALID) {
|
|
ath11k_warn(ar->ab, "Number of tones received is invalid\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
num_chains = FIELD_GET(CFIR_DMA_HDR_INFO1_NUM_CHAINS,
|
|
dma_hdr->info1);
|
|
|
|
length = sizeof(*dma_hdr);
|
|
length += tones * (num_chains + 1);
|
|
|
|
spin_lock_bh(&cfr->lut_lock);
|
|
|
|
if (!cfr->lut) {
|
|
spin_unlock_bh(&cfr->lut_lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
lut = &cfr->lut[buf_id];
|
|
|
|
ath11k_dbg_dump(ab, ATH11K_DBG_CFR_DUMP, "data_from_buf_rel:", "",
|
|
data, length);
|
|
|
|
lut->buff = param->buff;
|
|
lut->data = data;
|
|
lut->data_len = length;
|
|
lut->dbr_ppdu_id = dma_hdr->phy_ppdu_id;
|
|
lut->dbr_tstamp = jiffies;
|
|
|
|
memcpy(&lut->hdr, dma_hdr, sizeof(*dma_hdr));
|
|
|
|
header = &lut->header;
|
|
header->meta_data.channel_bw = FIELD_GET(CFIR_DMA_HDR_INFO1_UPLOAD_PKT_BW,
|
|
dma_hdr->info1);
|
|
header->meta_data.length = length;
|
|
|
|
status = ath11k_cfr_correlate_and_relay(ar, lut,
|
|
ATH11K_CORRELATE_DBR_EVENT);
|
|
if (status == ATH11K_CORRELATE_STATUS_RELEASE) {
|
|
ath11k_dbg(ab, ATH11K_DBG_CFR,
|
|
"releasing CFR data to user space");
|
|
ath11k_cfr_rfs_write(ar, &lut->header,
|
|
sizeof(struct ath11k_csi_cfr_header),
|
|
lut->data, lut->data_len,
|
|
&end_magic, sizeof(u32));
|
|
ath11k_cfr_release_lut_entry(lut);
|
|
} else if (status == ATH11K_CORRELATE_STATUS_HOLD) {
|
|
ath11k_dbg(ab, ATH11K_DBG_CFR,
|
|
"tx event is not yet received holding the buf");
|
|
}
|
|
|
|
spin_unlock_bh(&cfr->lut_lock);
|
|
|
|
return status;
|
|
}
|
|
|
|
static void ath11k_cfr_fill_hdr_info(struct ath11k *ar,
|
|
struct ath11k_csi_cfr_header *header,
|
|
struct ath11k_cfr_peer_tx_param *params)
|
|
{
|
|
struct ath11k_cfr *cfr;
|
|
|
|
cfr = &ar->cfr;
|
|
header->cfr_metadata_version = ATH11K_CFR_META_VERSION_4;
|
|
header->cfr_data_version = ATH11K_CFR_DATA_VERSION_1;
|
|
header->cfr_metadata_len = sizeof(struct cfr_metadata);
|
|
header->chip_type = ar->ab->hw_rev;
|
|
header->meta_data.status = FIELD_GET(WMI_CFR_PEER_CAPTURE_STATUS,
|
|
params->status);
|
|
header->meta_data.capture_bw = params->bandwidth;
|
|
|
|
/*
|
|
* FW reports phymode will always be HE mode.
|
|
* Replace it with cached phy mode during peer assoc
|
|
*/
|
|
header->meta_data.phy_mode = cfr->phymode;
|
|
|
|
header->meta_data.prim20_chan = params->primary_20mhz_chan;
|
|
header->meta_data.center_freq1 = params->band_center_freq1;
|
|
header->meta_data.center_freq2 = params->band_center_freq2;
|
|
|
|
/*
|
|
* CFR capture is triggered by the ACK of a QoS Null frame:
|
|
* - 20 MHz: Legacy ACK
|
|
* - 40/80/160 MHz: DUP Legacy ACK
|
|
*/
|
|
header->meta_data.capture_mode = params->bandwidth ?
|
|
ATH11K_CFR_CAPTURE_DUP_LEGACY_ACK : ATH11K_CFR_CAPTURE_LEGACY_ACK;
|
|
header->meta_data.capture_type = params->capture_method;
|
|
header->meta_data.num_rx_chain = ar->num_rx_chains;
|
|
header->meta_data.sts_count = params->spatial_streams;
|
|
header->meta_data.timestamp = params->timestamp_us;
|
|
ether_addr_copy(header->meta_data.peer_addr, params->peer_mac_addr);
|
|
memcpy(header->meta_data.chain_rssi, params->chain_rssi,
|
|
sizeof(params->chain_rssi));
|
|
memcpy(header->meta_data.chain_phase, params->chain_phase,
|
|
sizeof(params->chain_phase));
|
|
memcpy(header->meta_data.agc_gain, params->agc_gain,
|
|
sizeof(params->agc_gain));
|
|
}
|
|
|
|
int ath11k_process_cfr_capture_event(struct ath11k_base *ab,
|
|
struct ath11k_cfr_peer_tx_param *params)
|
|
{
|
|
struct ath11k_look_up_table *lut = NULL;
|
|
u32 end_magic = ATH11K_CFR_END_MAGIC;
|
|
struct ath11k_csi_cfr_header *header;
|
|
struct ath11k_dbring_element *buff;
|
|
struct ath11k_cfr *cfr;
|
|
dma_addr_t buf_addr;
|
|
struct ath11k *ar;
|
|
u8 tx_status;
|
|
int status;
|
|
int i;
|
|
|
|
rcu_read_lock();
|
|
ar = ath11k_mac_get_ar_by_vdev_id(ab, params->vdev_id);
|
|
if (!ar) {
|
|
rcu_read_unlock();
|
|
ath11k_warn(ab, "Failed to get ar for vdev id %d\n",
|
|
params->vdev_id);
|
|
return -ENOENT;
|
|
}
|
|
|
|
cfr = &ar->cfr;
|
|
rcu_read_unlock();
|
|
|
|
if (WMI_CFR_CAPTURE_STATUS_PEER_PS & params->status) {
|
|
ath11k_warn(ab, "CFR capture failed as peer %pM is in powersave",
|
|
params->peer_mac_addr);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!(WMI_CFR_PEER_CAPTURE_STATUS & params->status)) {
|
|
ath11k_warn(ab, "CFR capture failed for the peer : %pM",
|
|
params->peer_mac_addr);
|
|
cfr->tx_peer_status_cfr_fail++;
|
|
return -EINVAL;
|
|
}
|
|
|
|
tx_status = FIELD_GET(WMI_CFR_FRAME_TX_STATUS, params->status);
|
|
if (tx_status != WMI_FRAME_TX_STATUS_OK) {
|
|
ath11k_warn(ab, "WMI tx status %d for the peer %pM",
|
|
tx_status, params->peer_mac_addr);
|
|
cfr->tx_evt_status_cfr_fail++;
|
|
return -EINVAL;
|
|
}
|
|
|
|
buf_addr = (((u64)FIELD_GET(WMI_CFR_CORRELATION_INFO2_BUF_ADDR_HIGH,
|
|
params->correlation_info_2)) << 32) |
|
|
params->correlation_info_1;
|
|
|
|
spin_lock_bh(&cfr->lut_lock);
|
|
|
|
if (!cfr->lut) {
|
|
spin_unlock_bh(&cfr->lut_lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < cfr->lut_num; i++) {
|
|
struct ath11k_look_up_table *temp = &cfr->lut[i];
|
|
|
|
if (temp->dbr_address == buf_addr) {
|
|
lut = &cfr->lut[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!lut) {
|
|
spin_unlock_bh(&cfr->lut_lock);
|
|
ath11k_warn(ab, "lut failure to process tx event\n");
|
|
cfr->tx_dbr_lookup_fail++;
|
|
return -EINVAL;
|
|
}
|
|
|
|
lut->tx_ppdu_id = FIELD_GET(WMI_CFR_CORRELATION_INFO2_PPDU_ID,
|
|
params->correlation_info_2);
|
|
lut->txrx_tstamp = jiffies;
|
|
|
|
header = &lut->header;
|
|
header->start_magic_num = ATH11K_CFR_START_MAGIC;
|
|
header->vendorid = VENDOR_QCA;
|
|
header->platform_type = PLATFORM_TYPE_ARM;
|
|
|
|
ath11k_cfr_fill_hdr_info(ar, header, params);
|
|
|
|
status = ath11k_cfr_correlate_and_relay(ar, lut,
|
|
ATH11K_CORRELATE_TX_EVENT);
|
|
if (status == ATH11K_CORRELATE_STATUS_RELEASE) {
|
|
ath11k_dbg(ab, ATH11K_DBG_CFR,
|
|
"Releasing CFR data to user space");
|
|
ath11k_cfr_rfs_write(ar, &lut->header,
|
|
sizeof(struct ath11k_csi_cfr_header),
|
|
lut->data, lut->data_len,
|
|
&end_magic, sizeof(u32));
|
|
buff = lut->buff;
|
|
ath11k_cfr_release_lut_entry(lut);
|
|
|
|
ath11k_dbring_bufs_replenish(ar, &cfr->rx_ring, buff,
|
|
WMI_DIRECT_BUF_CFR);
|
|
} else if (status == ATH11K_CORRELATE_STATUS_HOLD) {
|
|
ath11k_dbg(ab, ATH11K_DBG_CFR,
|
|
"dbr event is not yet received holding buf\n");
|
|
}
|
|
|
|
spin_unlock_bh(&cfr->lut_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Helper function to check whether the given peer mac address
|
|
* is in unassociated peer pool or not.
|
|
*/
|
|
bool ath11k_cfr_peer_is_in_cfr_unassoc_pool(struct ath11k *ar, const u8 *peer_mac)
|
|
{
|
|
struct ath11k_cfr *cfr = &ar->cfr;
|
|
struct cfr_unassoc_pool_entry *entry;
|
|
int i;
|
|
|
|
if (!ar->cfr_enabled)
|
|
return false;
|
|
|
|
spin_lock_bh(&cfr->lock);
|
|
for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {
|
|
entry = &cfr->unassoc_pool[i];
|
|
if (!entry->is_valid)
|
|
continue;
|
|
|
|
if (ether_addr_equal(peer_mac, entry->peer_mac)) {
|
|
spin_unlock_bh(&cfr->lock);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
spin_unlock_bh(&cfr->lock);
|
|
|
|
return false;
|
|
}
|
|
|
|
void ath11k_cfr_update_unassoc_pool_entry(struct ath11k *ar,
|
|
const u8 *peer_mac)
|
|
{
|
|
struct ath11k_cfr *cfr = &ar->cfr;
|
|
struct cfr_unassoc_pool_entry *entry;
|
|
int i;
|
|
|
|
spin_lock_bh(&cfr->lock);
|
|
for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {
|
|
entry = &cfr->unassoc_pool[i];
|
|
if (!entry->is_valid)
|
|
continue;
|
|
|
|
if (ether_addr_equal(peer_mac, entry->peer_mac) &&
|
|
entry->period == 0) {
|
|
memset(entry->peer_mac, 0, ETH_ALEN);
|
|
entry->is_valid = false;
|
|
cfr->cfr_enabled_peer_cnt--;
|
|
break;
|
|
}
|
|
}
|
|
|
|
spin_unlock_bh(&cfr->lock);
|
|
}
|
|
|
|
void ath11k_cfr_decrement_peer_count(struct ath11k *ar,
|
|
struct ath11k_sta *arsta)
|
|
{
|
|
struct ath11k_cfr *cfr = &ar->cfr;
|
|
|
|
spin_lock_bh(&cfr->lock);
|
|
|
|
if (arsta->cfr_capture.cfr_enable)
|
|
cfr->cfr_enabled_peer_cnt--;
|
|
|
|
spin_unlock_bh(&cfr->lock);
|
|
}
|
|
|
|
static enum ath11k_wmi_cfr_capture_bw
|
|
ath11k_cfr_bw_to_fw_cfr_bw(enum ath11k_cfr_capture_bw bw)
|
|
{
|
|
switch (bw) {
|
|
case ATH11K_CFR_CAPTURE_BW_20:
|
|
return WMI_PEER_CFR_CAPTURE_BW_20;
|
|
case ATH11K_CFR_CAPTURE_BW_40:
|
|
return WMI_PEER_CFR_CAPTURE_BW_40;
|
|
case ATH11K_CFR_CAPTURE_BW_80:
|
|
return WMI_PEER_CFR_CAPTURE_BW_80;
|
|
default:
|
|
return WMI_PEER_CFR_CAPTURE_BW_MAX;
|
|
}
|
|
}
|
|
|
|
static enum ath11k_wmi_cfr_capture_method
|
|
ath11k_cfr_method_to_fw_cfr_method(enum ath11k_cfr_capture_method method)
|
|
{
|
|
switch (method) {
|
|
case ATH11K_CFR_CAPTURE_METHOD_NULL_FRAME:
|
|
return WMI_CFR_CAPTURE_METHOD_NULL_FRAME;
|
|
case ATH11K_CFR_CAPTURE_METHOD_NULL_FRAME_WITH_PHASE:
|
|
return WMI_CFR_CAPTURE_METHOD_NULL_FRAME_WITH_PHASE;
|
|
case ATH11K_CFR_CAPTURE_METHOD_PROBE_RESP:
|
|
return WMI_CFR_CAPTURE_METHOD_PROBE_RESP;
|
|
default:
|
|
return WMI_CFR_CAPTURE_METHOD_MAX;
|
|
}
|
|
}
|
|
|
|
int ath11k_cfr_send_peer_cfr_capture_cmd(struct ath11k *ar,
|
|
struct ath11k_sta *arsta,
|
|
struct ath11k_per_peer_cfr_capture *params,
|
|
const u8 *peer_mac)
|
|
{
|
|
struct ath11k_cfr *cfr = &ar->cfr;
|
|
struct wmi_peer_cfr_capture_conf_arg arg;
|
|
enum ath11k_wmi_cfr_capture_bw bw;
|
|
enum ath11k_wmi_cfr_capture_method method;
|
|
int ret = 0;
|
|
|
|
if (cfr->cfr_enabled_peer_cnt >= ATH11K_MAX_CFR_ENABLED_CLIENTS &&
|
|
!arsta->cfr_capture.cfr_enable) {
|
|
ath11k_err(ar->ab, "CFR enable peer threshold reached %u\n",
|
|
cfr->cfr_enabled_peer_cnt);
|
|
return -ENOSPC;
|
|
}
|
|
|
|
if (params->cfr_enable == arsta->cfr_capture.cfr_enable &&
|
|
params->cfr_period == arsta->cfr_capture.cfr_period &&
|
|
params->cfr_method == arsta->cfr_capture.cfr_method &&
|
|
params->cfr_bw == arsta->cfr_capture.cfr_bw)
|
|
return ret;
|
|
|
|
if (!params->cfr_enable && !arsta->cfr_capture.cfr_enable)
|
|
return ret;
|
|
|
|
bw = ath11k_cfr_bw_to_fw_cfr_bw(params->cfr_bw);
|
|
if (bw >= WMI_PEER_CFR_CAPTURE_BW_MAX) {
|
|
ath11k_warn(ar->ab, "FW doesn't support configured bw %d\n",
|
|
params->cfr_bw);
|
|
return -EINVAL;
|
|
}
|
|
|
|
method = ath11k_cfr_method_to_fw_cfr_method(params->cfr_method);
|
|
if (method >= WMI_CFR_CAPTURE_METHOD_MAX) {
|
|
ath11k_warn(ar->ab, "FW doesn't support configured method %d\n",
|
|
params->cfr_method);
|
|
return -EINVAL;
|
|
}
|
|
|
|
arg.request = params->cfr_enable;
|
|
arg.periodicity = params->cfr_period;
|
|
arg.bw = bw;
|
|
arg.method = method;
|
|
|
|
ret = ath11k_wmi_peer_set_cfr_capture_conf(ar, arsta->arvif->vdev_id,
|
|
peer_mac, &arg);
|
|
if (ret) {
|
|
ath11k_warn(ar->ab,
|
|
"failed to send cfr capture info: vdev_id %u peer %pM: %d\n",
|
|
arsta->arvif->vdev_id, peer_mac, ret);
|
|
return ret;
|
|
}
|
|
|
|
spin_lock_bh(&cfr->lock);
|
|
|
|
if (params->cfr_enable &&
|
|
params->cfr_enable != arsta->cfr_capture.cfr_enable)
|
|
cfr->cfr_enabled_peer_cnt++;
|
|
else if (!params->cfr_enable)
|
|
cfr->cfr_enabled_peer_cnt--;
|
|
|
|
spin_unlock_bh(&cfr->lock);
|
|
|
|
arsta->cfr_capture.cfr_enable = params->cfr_enable;
|
|
arsta->cfr_capture.cfr_period = params->cfr_period;
|
|
arsta->cfr_capture.cfr_method = params->cfr_method;
|
|
arsta->cfr_capture.cfr_bw = params->cfr_bw;
|
|
|
|
return ret;
|
|
}
|
|
|
|
void ath11k_cfr_update_unassoc_pool(struct ath11k *ar,
|
|
struct ath11k_per_peer_cfr_capture *params,
|
|
u8 *peer_mac)
|
|
{
|
|
struct ath11k_cfr *cfr = &ar->cfr;
|
|
struct cfr_unassoc_pool_entry *entry;
|
|
int available_idx = -1;
|
|
int i;
|
|
|
|
guard(spinlock_bh)(&cfr->lock);
|
|
|
|
if (!params->cfr_enable) {
|
|
for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {
|
|
entry = &cfr->unassoc_pool[i];
|
|
if (ether_addr_equal(peer_mac, entry->peer_mac)) {
|
|
memset(entry->peer_mac, 0, ETH_ALEN);
|
|
entry->is_valid = false;
|
|
cfr->cfr_enabled_peer_cnt--;
|
|
break;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (cfr->cfr_enabled_peer_cnt >= ATH11K_MAX_CFR_ENABLED_CLIENTS) {
|
|
ath11k_info(ar->ab, "Max cfr peer threshold reached\n");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {
|
|
entry = &cfr->unassoc_pool[i];
|
|
|
|
if (ether_addr_equal(peer_mac, entry->peer_mac)) {
|
|
ath11k_info(ar->ab,
|
|
"peer entry already present updating params\n");
|
|
entry->period = params->cfr_period;
|
|
available_idx = -1;
|
|
break;
|
|
}
|
|
|
|
if (available_idx < 0 && !entry->is_valid)
|
|
available_idx = i;
|
|
}
|
|
|
|
if (available_idx >= 0) {
|
|
entry = &cfr->unassoc_pool[available_idx];
|
|
ether_addr_copy(entry->peer_mac, peer_mac);
|
|
entry->period = params->cfr_period;
|
|
entry->is_valid = true;
|
|
cfr->cfr_enabled_peer_cnt++;
|
|
}
|
|
}
|
|
|
|
static ssize_t ath11k_read_file_enable_cfr(struct file *file,
|
|
char __user *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct ath11k *ar = file->private_data;
|
|
char buf[32] = {};
|
|
size_t len;
|
|
|
|
mutex_lock(&ar->conf_mutex);
|
|
len = scnprintf(buf, sizeof(buf), "%d\n", ar->cfr_enabled);
|
|
mutex_unlock(&ar->conf_mutex);
|
|
|
|
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
|
|
}
|
|
|
|
static ssize_t ath11k_write_file_enable_cfr(struct file *file,
|
|
const char __user *ubuf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct ath11k *ar = file->private_data;
|
|
u32 enable_cfr;
|
|
int ret;
|
|
|
|
if (kstrtouint_from_user(ubuf, count, 0, &enable_cfr))
|
|
return -EINVAL;
|
|
|
|
guard(mutex)(&ar->conf_mutex);
|
|
|
|
if (ar->state != ATH11K_STATE_ON)
|
|
return -ENETDOWN;
|
|
|
|
if (enable_cfr > 1)
|
|
return -EINVAL;
|
|
|
|
if (ar->cfr_enabled == enable_cfr)
|
|
return count;
|
|
|
|
ret = ath11k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_PER_PEER_CFR_ENABLE,
|
|
enable_cfr, ar->pdev->pdev_id);
|
|
if (ret) {
|
|
ath11k_warn(ar->ab,
|
|
"Failed to enable/disable per peer cfr %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ar->cfr_enabled = enable_cfr;
|
|
|
|
return count;
|
|
}
|
|
|
|
static const struct file_operations fops_enable_cfr = {
|
|
.read = ath11k_read_file_enable_cfr,
|
|
.write = ath11k_write_file_enable_cfr,
|
|
.open = simple_open,
|
|
.owner = THIS_MODULE,
|
|
.llseek = default_llseek,
|
|
};
|
|
|
|
static ssize_t ath11k_write_file_cfr_unassoc(struct file *file,
|
|
const char __user *ubuf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct ath11k *ar = file->private_data;
|
|
struct ath11k_cfr *cfr = &ar->cfr;
|
|
struct cfr_unassoc_pool_entry *entry;
|
|
char buf[64] = {};
|
|
u8 peer_mac[6];
|
|
u32 cfr_capture_enable;
|
|
u32 cfr_capture_period;
|
|
int available_idx = -1;
|
|
int ret, i;
|
|
|
|
simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, ubuf, count);
|
|
|
|
guard(mutex)(&ar->conf_mutex);
|
|
guard(spinlock_bh)(&cfr->lock);
|
|
|
|
if (ar->state != ATH11K_STATE_ON)
|
|
return -ENETDOWN;
|
|
|
|
if (!ar->cfr_enabled) {
|
|
ath11k_err(ar->ab, "CFR is not enabled on this pdev %d\n",
|
|
ar->pdev_idx);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = sscanf(buf, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx %u %u",
|
|
&peer_mac[0], &peer_mac[1], &peer_mac[2], &peer_mac[3],
|
|
&peer_mac[4], &peer_mac[5], &cfr_capture_enable,
|
|
&cfr_capture_period);
|
|
|
|
if (ret < 1)
|
|
return -EINVAL;
|
|
|
|
if (cfr_capture_enable && ret != 8)
|
|
return -EINVAL;
|
|
|
|
if (!cfr_capture_enable) {
|
|
for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {
|
|
entry = &cfr->unassoc_pool[i];
|
|
if (ether_addr_equal(peer_mac, entry->peer_mac)) {
|
|
memset(entry->peer_mac, 0, ETH_ALEN);
|
|
entry->is_valid = false;
|
|
cfr->cfr_enabled_peer_cnt--;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
if (cfr->cfr_enabled_peer_cnt >= ATH11K_MAX_CFR_ENABLED_CLIENTS) {
|
|
ath11k_info(ar->ab, "Max cfr peer threshold reached\n");
|
|
return count;
|
|
}
|
|
|
|
for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {
|
|
entry = &cfr->unassoc_pool[i];
|
|
|
|
if (available_idx < 0 && !entry->is_valid)
|
|
available_idx = i;
|
|
|
|
if (ether_addr_equal(peer_mac, entry->peer_mac)) {
|
|
ath11k_info(ar->ab,
|
|
"peer entry already present updating params\n");
|
|
entry->period = cfr_capture_period;
|
|
return count;
|
|
}
|
|
}
|
|
|
|
if (available_idx >= 0) {
|
|
entry = &cfr->unassoc_pool[available_idx];
|
|
ether_addr_copy(entry->peer_mac, peer_mac);
|
|
entry->period = cfr_capture_period;
|
|
entry->is_valid = true;
|
|
cfr->cfr_enabled_peer_cnt++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t ath11k_read_file_cfr_unassoc(struct file *file,
|
|
char __user *ubuf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct ath11k *ar = file->private_data;
|
|
struct ath11k_cfr *cfr = &ar->cfr;
|
|
struct cfr_unassoc_pool_entry *entry;
|
|
char buf[512] = {};
|
|
int len = 0, i;
|
|
|
|
spin_lock_bh(&cfr->lock);
|
|
|
|
for (i = 0; i < ATH11K_MAX_CFR_ENABLED_CLIENTS; i++) {
|
|
entry = &cfr->unassoc_pool[i];
|
|
if (entry->is_valid)
|
|
len += scnprintf(buf + len, sizeof(buf) - len,
|
|
"peer: %pM period: %u\n",
|
|
entry->peer_mac, entry->period);
|
|
}
|
|
|
|
spin_unlock_bh(&cfr->lock);
|
|
|
|
return simple_read_from_buffer(ubuf, count, ppos, buf, len);
|
|
}
|
|
|
|
static const struct file_operations fops_configure_cfr_unassoc = {
|
|
.write = ath11k_write_file_cfr_unassoc,
|
|
.read = ath11k_read_file_cfr_unassoc,
|
|
.open = simple_open,
|
|
.owner = THIS_MODULE,
|
|
.llseek = default_llseek,
|
|
};
|
|
|
|
static void ath11k_cfr_debug_unregister(struct ath11k *ar)
|
|
{
|
|
debugfs_remove(ar->cfr.enable_cfr);
|
|
ar->cfr.enable_cfr = NULL;
|
|
debugfs_remove(ar->cfr.cfr_unassoc);
|
|
ar->cfr.cfr_unassoc = NULL;
|
|
|
|
relay_close(ar->cfr.rfs_cfr_capture);
|
|
ar->cfr.rfs_cfr_capture = NULL;
|
|
}
|
|
|
|
static struct dentry *ath11k_cfr_create_buf_file_handler(const char *filename,
|
|
struct dentry *parent,
|
|
umode_t mode,
|
|
struct rchan_buf *buf,
|
|
int *is_global)
|
|
{
|
|
struct dentry *buf_file;
|
|
|
|
buf_file = debugfs_create_file(filename, mode, parent, buf,
|
|
&relay_file_operations);
|
|
*is_global = 1;
|
|
return buf_file;
|
|
}
|
|
|
|
static int ath11k_cfr_remove_buf_file_handler(struct dentry *dentry)
|
|
{
|
|
debugfs_remove(dentry);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct rchan_callbacks rfs_cfr_capture_cb = {
|
|
.create_buf_file = ath11k_cfr_create_buf_file_handler,
|
|
.remove_buf_file = ath11k_cfr_remove_buf_file_handler,
|
|
};
|
|
|
|
static void ath11k_cfr_debug_register(struct ath11k *ar)
|
|
{
|
|
ar->cfr.rfs_cfr_capture = relay_open("cfr_capture",
|
|
ar->debug.debugfs_pdev,
|
|
ar->ab->hw_params.cfr_stream_buf_size,
|
|
ar->ab->hw_params.cfr_num_stream_bufs,
|
|
&rfs_cfr_capture_cb, NULL);
|
|
|
|
ar->cfr.enable_cfr = debugfs_create_file("enable_cfr", 0600,
|
|
ar->debug.debugfs_pdev, ar,
|
|
&fops_enable_cfr);
|
|
|
|
ar->cfr.cfr_unassoc = debugfs_create_file("cfr_unassoc", 0600,
|
|
ar->debug.debugfs_pdev, ar,
|
|
&fops_configure_cfr_unassoc);
|
|
}
|
|
|
|
void ath11k_cfr_lut_update_paddr(struct ath11k *ar, dma_addr_t paddr,
|
|
u32 buf_id)
|
|
{
|
|
struct ath11k_cfr *cfr = &ar->cfr;
|
|
|
|
if (cfr->lut)
|
|
cfr->lut[buf_id].dbr_address = paddr;
|
|
}
|
|
|
|
void ath11k_cfr_update_phymode(struct ath11k *ar, enum wmi_phy_mode phymode)
|
|
{
|
|
struct ath11k_cfr *cfr = &ar->cfr;
|
|
|
|
cfr->phymode = phymode;
|
|
}
|
|
|
|
static void ath11k_cfr_ring_free(struct ath11k *ar)
|
|
{
|
|
struct ath11k_cfr *cfr = &ar->cfr;
|
|
|
|
ath11k_dbring_buf_cleanup(ar, &cfr->rx_ring);
|
|
ath11k_dbring_srng_cleanup(ar, &cfr->rx_ring);
|
|
}
|
|
|
|
static int ath11k_cfr_ring_alloc(struct ath11k *ar,
|
|
struct ath11k_dbring_cap *db_cap)
|
|
{
|
|
struct ath11k_cfr *cfr = &ar->cfr;
|
|
int ret;
|
|
|
|
ret = ath11k_dbring_srng_setup(ar, &cfr->rx_ring,
|
|
ATH11K_CFR_NUM_RING_ENTRIES,
|
|
db_cap->min_elem);
|
|
if (ret) {
|
|
ath11k_warn(ar->ab, "failed to setup db ring: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ath11k_dbring_set_cfg(ar, &cfr->rx_ring,
|
|
ATH11K_CFR_NUM_RESP_PER_EVENT,
|
|
ATH11K_CFR_EVENT_TIMEOUT_MS,
|
|
ath11k_cfr_process_data);
|
|
|
|
ret = ath11k_dbring_buf_setup(ar, &cfr->rx_ring, db_cap);
|
|
if (ret) {
|
|
ath11k_warn(ar->ab, "failed to setup db ring buffer: %d\n", ret);
|
|
goto srng_cleanup;
|
|
}
|
|
|
|
ret = ath11k_dbring_wmi_cfg_setup(ar, &cfr->rx_ring, WMI_DIRECT_BUF_CFR);
|
|
if (ret) {
|
|
ath11k_warn(ar->ab, "failed to setup db ring cfg: %d\n", ret);
|
|
goto buffer_cleanup;
|
|
}
|
|
|
|
return 0;
|
|
|
|
buffer_cleanup:
|
|
ath11k_dbring_buf_cleanup(ar, &cfr->rx_ring);
|
|
srng_cleanup:
|
|
ath11k_dbring_srng_cleanup(ar, &cfr->rx_ring);
|
|
return ret;
|
|
}
|
|
|
|
void ath11k_cfr_deinit(struct ath11k_base *ab)
|
|
{
|
|
struct ath11k_cfr *cfr;
|
|
struct ath11k *ar;
|
|
int i;
|
|
|
|
if (!test_bit(WMI_TLV_SERVICE_CFR_CAPTURE_SUPPORT, ab->wmi_ab.svc_map) ||
|
|
!ab->hw_params.cfr_support)
|
|
return;
|
|
|
|
for (i = 0; i < ab->num_radios; i++) {
|
|
ar = ab->pdevs[i].ar;
|
|
cfr = &ar->cfr;
|
|
|
|
if (!cfr->enabled)
|
|
continue;
|
|
|
|
ath11k_cfr_debug_unregister(ar);
|
|
ath11k_cfr_ring_free(ar);
|
|
|
|
spin_lock_bh(&cfr->lut_lock);
|
|
kfree(cfr->lut);
|
|
cfr->lut = NULL;
|
|
cfr->enabled = false;
|
|
spin_unlock_bh(&cfr->lut_lock);
|
|
}
|
|
}
|
|
|
|
int ath11k_cfr_init(struct ath11k_base *ab)
|
|
{
|
|
struct ath11k_dbring_cap db_cap;
|
|
struct ath11k_cfr *cfr;
|
|
u32 num_lut_entries;
|
|
struct ath11k *ar;
|
|
int i, ret;
|
|
|
|
if (!test_bit(WMI_TLV_SERVICE_CFR_CAPTURE_SUPPORT, ab->wmi_ab.svc_map) ||
|
|
!ab->hw_params.cfr_support)
|
|
return 0;
|
|
|
|
for (i = 0; i < ab->num_radios; i++) {
|
|
ar = ab->pdevs[i].ar;
|
|
cfr = &ar->cfr;
|
|
|
|
ret = ath11k_dbring_get_cap(ar->ab, ar->pdev_idx,
|
|
WMI_DIRECT_BUF_CFR, &db_cap);
|
|
if (ret)
|
|
continue;
|
|
|
|
idr_init(&cfr->rx_ring.bufs_idr);
|
|
spin_lock_init(&cfr->rx_ring.idr_lock);
|
|
spin_lock_init(&cfr->lock);
|
|
spin_lock_init(&cfr->lut_lock);
|
|
|
|
num_lut_entries = min_t(u32, CFR_MAX_LUT_ENTRIES, db_cap.min_elem);
|
|
cfr->lut = kzalloc_objs(*cfr->lut, num_lut_entries);
|
|
if (!cfr->lut) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
ret = ath11k_cfr_ring_alloc(ar, &db_cap);
|
|
if (ret) {
|
|
ath11k_warn(ab, "failed to init cfr ring for pdev %d: %d\n",
|
|
i, ret);
|
|
spin_lock_bh(&cfr->lut_lock);
|
|
kfree(cfr->lut);
|
|
cfr->lut = NULL;
|
|
cfr->enabled = false;
|
|
spin_unlock_bh(&cfr->lut_lock);
|
|
goto err;
|
|
}
|
|
|
|
cfr->lut_num = num_lut_entries;
|
|
cfr->enabled = true;
|
|
|
|
ath11k_cfr_debug_register(ar);
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
for (i = i - 1; i >= 0; i--) {
|
|
ar = ab->pdevs[i].ar;
|
|
cfr = &ar->cfr;
|
|
|
|
if (!cfr->enabled)
|
|
continue;
|
|
|
|
ath11k_cfr_debug_unregister(ar);
|
|
ath11k_cfr_ring_free(ar);
|
|
|
|
spin_lock_bh(&cfr->lut_lock);
|
|
kfree(cfr->lut);
|
|
cfr->lut = NULL;
|
|
cfr->enabled = false;
|
|
spin_unlock_bh(&cfr->lut_lock);
|
|
}
|
|
return ret;
|
|
}
|