| // 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; |
| } |