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