diff options
Diffstat (limited to 'drivers/net/wireless/ath/ath11k/spectral.c')
-rw-r--r-- | drivers/net/wireless/ath/ath11k/spectral.c | 1064 |
1 files changed, 1064 insertions, 0 deletions
diff --git a/drivers/net/wireless/ath/ath11k/spectral.c b/drivers/net/wireless/ath/ath11k/spectral.c new file mode 100644 index 000000000..705868198 --- /dev/null +++ b/drivers/net/wireless/ath/ath11k/spectral.c @@ -0,0 +1,1064 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear +/* + * Copyright (c) 2019-2020 The Linux Foundation. All rights reserved. + */ + +#include <linux/relay.h> +#include "core.h" +#include "debug.h" + +#define ATH11K_SPECTRAL_NUM_RESP_PER_EVENT 2 +#define ATH11K_SPECTRAL_EVENT_TIMEOUT_MS 1 + +#define ATH11K_SPECTRAL_DWORD_SIZE 4 +#define ATH11K_SPECTRAL_MIN_BINS 32 +#define ATH11K_SPECTRAL_MIN_IB_BINS (ATH11K_SPECTRAL_MIN_BINS >> 1) +#define ATH11K_SPECTRAL_MAX_IB_BINS(x) ((x)->hw_params.spectral.max_fft_bins >> 1) + +#define ATH11K_SPECTRAL_SCAN_COUNT_MAX 4095 + +/* Max channel computed by sum of 2g and 5g band channels */ +#define ATH11K_SPECTRAL_TOTAL_CHANNEL 41 +#define ATH11K_SPECTRAL_SAMPLES_PER_CHANNEL 70 +#define ATH11K_SPECTRAL_PER_SAMPLE_SIZE(x) (sizeof(struct fft_sample_ath11k) + \ + ATH11K_SPECTRAL_MAX_IB_BINS(x)) +#define ATH11K_SPECTRAL_TOTAL_SAMPLE (ATH11K_SPECTRAL_TOTAL_CHANNEL * \ + ATH11K_SPECTRAL_SAMPLES_PER_CHANNEL) +#define ATH11K_SPECTRAL_SUB_BUFF_SIZE(x) ATH11K_SPECTRAL_PER_SAMPLE_SIZE(x) +#define ATH11K_SPECTRAL_NUM_SUB_BUF ATH11K_SPECTRAL_TOTAL_SAMPLE + +#define ATH11K_SPECTRAL_20MHZ 20 +#define ATH11K_SPECTRAL_40MHZ 40 +#define ATH11K_SPECTRAL_80MHZ 80 +#define ATH11K_SPECTRAL_160MHZ 160 + +#define ATH11K_SPECTRAL_SIGNATURE 0xFA + +#define ATH11K_SPECTRAL_TAG_RADAR_SUMMARY 0x0 +#define ATH11K_SPECTRAL_TAG_RADAR_FFT 0x1 +#define ATH11K_SPECTRAL_TAG_SCAN_SUMMARY 0x2 +#define ATH11K_SPECTRAL_TAG_SCAN_SEARCH 0x3 + +#define SPECTRAL_TLV_HDR_LEN GENMASK(15, 0) +#define SPECTRAL_TLV_HDR_TAG GENMASK(23, 16) +#define SPECTRAL_TLV_HDR_SIGN GENMASK(31, 24) + +#define SPECTRAL_SUMMARY_INFO0_AGC_TOTAL_GAIN GENMASK(7, 0) +#define SPECTRAL_SUMMARY_INFO0_OB_FLAG BIT(8) +#define SPECTRAL_SUMMARY_INFO0_GRP_IDX GENMASK(16, 9) +#define SPECTRAL_SUMMARY_INFO0_RECENT_RFSAT BIT(17) +#define SPECTRAL_SUMMARY_INFO0_INBAND_PWR_DB GENMASK(27, 18) +#define SPECTRAL_SUMMARY_INFO0_FALSE_SCAN BIT(28) +#define SPECTRAL_SUMMARY_INFO0_DETECTOR_ID GENMASK(30, 29) +#define SPECTRAL_SUMMARY_INFO0_PRI80 BIT(31) + +#define SPECTRAL_SUMMARY_INFO2_PEAK_SIGNED_IDX GENMASK(11, 0) +#define SPECTRAL_SUMMARY_INFO2_PEAK_MAGNITUDE GENMASK(21, 12) +#define SPECTRAL_SUMMARY_INFO2_NARROWBAND_MASK GENMASK(29, 22) +#define SPECTRAL_SUMMARY_INFO2_GAIN_CHANGE BIT(30) + +struct spectral_tlv { + __le32 timestamp; + __le32 header; +} __packed; + +struct spectral_summary_fft_report { + __le32 timestamp; + __le32 tlv_header; + __le32 info0; + __le32 reserve0; + __le32 info2; + __le32 reserve1; +} __packed; + +struct ath11k_spectral_summary_report { + struct wmi_dma_buf_release_meta_data meta; + u32 timestamp; + u8 agc_total_gain; + u8 grp_idx; + u16 inb_pwr_db; + s16 peak_idx; + u16 peak_mag; + u8 detector_id; + bool out_of_band_flag; + bool rf_saturation; + bool primary80; + bool gain_change; + bool false_scan; +}; + +#define SPECTRAL_FFT_REPORT_INFO0_DETECTOR_ID GENMASK(1, 0) +#define SPECTRAL_FFT_REPORT_INFO0_FFT_NUM GENMASK(4, 2) +#define SPECTRAL_FFT_REPORT_INFO0_RADAR_CHECK GENMASK(16, 5) +#define SPECTRAL_FFT_REPORT_INFO0_PEAK_SIGNED_IDX GENMASK(27, 17) +#define SPECTRAL_FFT_REPORT_INFO0_CHAIN_IDX GENMASK(30, 28) + +#define SPECTRAL_FFT_REPORT_INFO1_BASE_PWR_DB GENMASK(8, 0) +#define SPECTRAL_FFT_REPORT_INFO1_TOTAL_GAIN_DB GENMASK(16, 9) + +#define SPECTRAL_FFT_REPORT_INFO2_NUM_STRONG_BINS GENMASK(7, 0) +#define SPECTRAL_FFT_REPORT_INFO2_PEAK_MAGNITUDE GENMASK(17, 8) +#define SPECTRAL_FFT_REPORT_INFO2_AVG_PWR_DB GENMASK(24, 18) +#define SPECTRAL_FFT_REPORT_INFO2_REL_PWR_DB GENMASK(31, 25) + +struct spectral_search_fft_report { + __le32 timestamp; + __le32 tlv_header; + __le32 info0; + __le32 info1; + __le32 info2; + __le32 reserve0; + u8 bins[]; +} __packed; + +struct ath11k_spectral_search_report { + u32 timestamp; + u8 detector_id; + u8 fft_count; + u16 radar_check; + s16 peak_idx; + u8 chain_idx; + u16 base_pwr_db; + u8 total_gain_db; + u8 strong_bin_count; + u16 peak_mag; + u8 avg_pwr_db; + u8 rel_pwr_db; +}; + +static struct dentry *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 remove_buf_file_handler(struct dentry *dentry) +{ + debugfs_remove(dentry); + + return 0; +} + +static const struct rchan_callbacks rfs_scan_cb = { + .create_buf_file = create_buf_file_handler, + .remove_buf_file = remove_buf_file_handler, +}; + +static struct ath11k_vif *ath11k_spectral_get_vdev(struct ath11k *ar) +{ + struct ath11k_vif *arvif; + + lockdep_assert_held(&ar->conf_mutex); + + if (list_empty(&ar->arvifs)) + return NULL; + + /* if there already is a vif doing spectral, return that. */ + list_for_each_entry(arvif, &ar->arvifs, list) + if (arvif->spectral_enabled) + return arvif; + + /* otherwise, return the first vif. */ + return list_first_entry(&ar->arvifs, typeof(*arvif), list); +} + +static int ath11k_spectral_scan_trigger(struct ath11k *ar) +{ + struct ath11k_vif *arvif; + int ret; + + lockdep_assert_held(&ar->conf_mutex); + + arvif = ath11k_spectral_get_vdev(ar); + if (!arvif) + return -ENODEV; + + if (ar->spectral.mode == ATH11K_SPECTRAL_DISABLED) + return 0; + + ar->spectral.is_primary = true; + + ret = ath11k_wmi_vdev_spectral_enable(ar, arvif->vdev_id, + ATH11K_WMI_SPECTRAL_TRIGGER_CMD_CLEAR, + ATH11K_WMI_SPECTRAL_ENABLE_CMD_ENABLE); + if (ret) + return ret; + + ret = ath11k_wmi_vdev_spectral_enable(ar, arvif->vdev_id, + ATH11K_WMI_SPECTRAL_TRIGGER_CMD_TRIGGER, + ATH11K_WMI_SPECTRAL_ENABLE_CMD_ENABLE); + if (ret) + return ret; + + return 0; +} + +static int ath11k_spectral_scan_config(struct ath11k *ar, + enum ath11k_spectral_mode mode) +{ + struct ath11k_wmi_vdev_spectral_conf_param param = { 0 }; + struct ath11k_vif *arvif; + int ret, count; + + lockdep_assert_held(&ar->conf_mutex); + + arvif = ath11k_spectral_get_vdev(ar); + if (!arvif) + return -ENODEV; + + arvif->spectral_enabled = (mode != ATH11K_SPECTRAL_DISABLED); + + spin_lock_bh(&ar->spectral.lock); + ar->spectral.mode = mode; + spin_unlock_bh(&ar->spectral.lock); + + ret = ath11k_wmi_vdev_spectral_enable(ar, arvif->vdev_id, + ATH11K_WMI_SPECTRAL_TRIGGER_CMD_CLEAR, + ATH11K_WMI_SPECTRAL_ENABLE_CMD_DISABLE); + if (ret) { + ath11k_warn(ar->ab, "failed to enable spectral scan: %d\n", ret); + return ret; + } + + if (mode == ATH11K_SPECTRAL_DISABLED) + return 0; + + if (mode == ATH11K_SPECTRAL_BACKGROUND) + count = ATH11K_WMI_SPECTRAL_COUNT_DEFAULT; + else + count = max_t(u16, 1, ar->spectral.count); + + param.vdev_id = arvif->vdev_id; + param.scan_count = count; + param.scan_fft_size = ar->spectral.fft_size; + param.scan_period = ATH11K_WMI_SPECTRAL_PERIOD_DEFAULT; + param.scan_priority = ATH11K_WMI_SPECTRAL_PRIORITY_DEFAULT; + param.scan_gc_ena = ATH11K_WMI_SPECTRAL_GC_ENA_DEFAULT; + param.scan_restart_ena = ATH11K_WMI_SPECTRAL_RESTART_ENA_DEFAULT; + param.scan_noise_floor_ref = ATH11K_WMI_SPECTRAL_NOISE_FLOOR_REF_DEFAULT; + param.scan_init_delay = ATH11K_WMI_SPECTRAL_INIT_DELAY_DEFAULT; + param.scan_nb_tone_thr = ATH11K_WMI_SPECTRAL_NB_TONE_THR_DEFAULT; + param.scan_str_bin_thr = ATH11K_WMI_SPECTRAL_STR_BIN_THR_DEFAULT; + param.scan_wb_rpt_mode = ATH11K_WMI_SPECTRAL_WB_RPT_MODE_DEFAULT; + param.scan_rssi_rpt_mode = ATH11K_WMI_SPECTRAL_RSSI_RPT_MODE_DEFAULT; + param.scan_rssi_thr = ATH11K_WMI_SPECTRAL_RSSI_THR_DEFAULT; + param.scan_pwr_format = ATH11K_WMI_SPECTRAL_PWR_FORMAT_DEFAULT; + param.scan_rpt_mode = ATH11K_WMI_SPECTRAL_RPT_MODE_DEFAULT; + param.scan_bin_scale = ATH11K_WMI_SPECTRAL_BIN_SCALE_DEFAULT; + param.scan_dbm_adj = ATH11K_WMI_SPECTRAL_DBM_ADJ_DEFAULT; + param.scan_chn_mask = ATH11K_WMI_SPECTRAL_CHN_MASK_DEFAULT; + + ret = ath11k_wmi_vdev_spectral_conf(ar, ¶m); + if (ret) { + ath11k_warn(ar->ab, "failed to configure spectral scan: %d\n", ret); + return ret; + } + + return 0; +} + +static ssize_t ath11k_read_file_spec_scan_ctl(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k *ar = file->private_data; + char *mode = ""; + size_t len; + enum ath11k_spectral_mode spectral_mode; + + mutex_lock(&ar->conf_mutex); + spectral_mode = ar->spectral.mode; + mutex_unlock(&ar->conf_mutex); + + switch (spectral_mode) { + case ATH11K_SPECTRAL_DISABLED: + mode = "disable"; + break; + case ATH11K_SPECTRAL_BACKGROUND: + mode = "background"; + break; + case ATH11K_SPECTRAL_MANUAL: + mode = "manual"; + break; + } + + len = strlen(mode); + return simple_read_from_buffer(user_buf, count, ppos, mode, len); +} + +static ssize_t ath11k_write_file_spec_scan_ctl(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k *ar = file->private_data; + char buf[32]; + ssize_t len; + int ret; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + + mutex_lock(&ar->conf_mutex); + + if (strncmp("trigger", buf, 7) == 0) { + if (ar->spectral.mode == ATH11K_SPECTRAL_MANUAL || + ar->spectral.mode == ATH11K_SPECTRAL_BACKGROUND) { + /* reset the configuration to adopt possibly changed + * debugfs parameters + */ + ret = ath11k_spectral_scan_config(ar, ar->spectral.mode); + if (ret) { + ath11k_warn(ar->ab, "failed to reconfigure spectral scan: %d\n", + ret); + goto unlock; + } + + ret = ath11k_spectral_scan_trigger(ar); + if (ret) { + ath11k_warn(ar->ab, "failed to trigger spectral scan: %d\n", + ret); + } + } else { + ret = -EINVAL; + } + } else if (strncmp("background", buf, 10) == 0) { + ret = ath11k_spectral_scan_config(ar, ATH11K_SPECTRAL_BACKGROUND); + } else if (strncmp("manual", buf, 6) == 0) { + ret = ath11k_spectral_scan_config(ar, ATH11K_SPECTRAL_MANUAL); + } else if (strncmp("disable", buf, 7) == 0) { + ret = ath11k_spectral_scan_config(ar, ATH11K_SPECTRAL_DISABLED); + } else { + ret = -EINVAL; + } + +unlock: + mutex_unlock(&ar->conf_mutex); + + if (ret) + return ret; + + return count; +} + +static const struct file_operations fops_scan_ctl = { + .read = ath11k_read_file_spec_scan_ctl, + .write = ath11k_write_file_spec_scan_ctl, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath11k_read_file_spectral_count(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; + u16 spectral_count; + + mutex_lock(&ar->conf_mutex); + spectral_count = ar->spectral.count; + mutex_unlock(&ar->conf_mutex); + + len = sprintf(buf, "%d\n", spectral_count); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t ath11k_write_file_spectral_count(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k *ar = file->private_data; + unsigned long val; + char buf[32]; + ssize_t len; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + if (val > ATH11K_SPECTRAL_SCAN_COUNT_MAX) + return -EINVAL; + + mutex_lock(&ar->conf_mutex); + ar->spectral.count = val; + mutex_unlock(&ar->conf_mutex); + + return count; +} + +static const struct file_operations fops_scan_count = { + .read = ath11k_read_file_spectral_count, + .write = ath11k_write_file_spectral_count, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath11k_read_file_spectral_bins(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k *ar = file->private_data; + char buf[32]; + unsigned int bins, fft_size; + size_t len; + + mutex_lock(&ar->conf_mutex); + + fft_size = ar->spectral.fft_size; + bins = 1 << fft_size; + + mutex_unlock(&ar->conf_mutex); + + len = sprintf(buf, "%d\n", bins); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t ath11k_write_file_spectral_bins(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath11k *ar = file->private_data; + unsigned long val; + char buf[32]; + ssize_t len; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + if (val < ATH11K_SPECTRAL_MIN_BINS || + val > ar->ab->hw_params.spectral.max_fft_bins) + return -EINVAL; + + if (!is_power_of_2(val)) + return -EINVAL; + + mutex_lock(&ar->conf_mutex); + ar->spectral.fft_size = ilog2(val); + mutex_unlock(&ar->conf_mutex); + + return count; +} + +static const struct file_operations fops_scan_bins = { + .read = ath11k_read_file_spectral_bins, + .write = ath11k_write_file_spectral_bins, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static int ath11k_spectral_pull_summary(struct ath11k *ar, + struct wmi_dma_buf_release_meta_data *meta, + struct spectral_summary_fft_report *summary, + struct ath11k_spectral_summary_report *report) +{ + report->timestamp = __le32_to_cpu(summary->timestamp); + report->agc_total_gain = FIELD_GET(SPECTRAL_SUMMARY_INFO0_AGC_TOTAL_GAIN, + __le32_to_cpu(summary->info0)); + report->out_of_band_flag = FIELD_GET(SPECTRAL_SUMMARY_INFO0_OB_FLAG, + __le32_to_cpu(summary->info0)); + report->grp_idx = FIELD_GET(SPECTRAL_SUMMARY_INFO0_GRP_IDX, + __le32_to_cpu(summary->info0)); + report->rf_saturation = FIELD_GET(SPECTRAL_SUMMARY_INFO0_RECENT_RFSAT, + __le32_to_cpu(summary->info0)); + report->inb_pwr_db = FIELD_GET(SPECTRAL_SUMMARY_INFO0_INBAND_PWR_DB, + __le32_to_cpu(summary->info0)); + report->false_scan = FIELD_GET(SPECTRAL_SUMMARY_INFO0_FALSE_SCAN, + __le32_to_cpu(summary->info0)); + report->detector_id = FIELD_GET(SPECTRAL_SUMMARY_INFO0_DETECTOR_ID, + __le32_to_cpu(summary->info0)); + report->primary80 = FIELD_GET(SPECTRAL_SUMMARY_INFO0_PRI80, + __le32_to_cpu(summary->info0)); + report->peak_idx = FIELD_GET(SPECTRAL_SUMMARY_INFO2_PEAK_SIGNED_IDX, + __le32_to_cpu(summary->info2)); + report->peak_mag = FIELD_GET(SPECTRAL_SUMMARY_INFO2_PEAK_MAGNITUDE, + __le32_to_cpu(summary->info2)); + report->gain_change = FIELD_GET(SPECTRAL_SUMMARY_INFO2_GAIN_CHANGE, + __le32_to_cpu(summary->info2)); + + memcpy(&report->meta, meta, sizeof(*meta)); + + return 0; +} + +static int ath11k_spectral_pull_search(struct ath11k *ar, + struct spectral_search_fft_report *search, + struct ath11k_spectral_search_report *report) +{ + report->timestamp = __le32_to_cpu(search->timestamp); + report->detector_id = FIELD_GET(SPECTRAL_FFT_REPORT_INFO0_DETECTOR_ID, + __le32_to_cpu(search->info0)); + report->fft_count = FIELD_GET(SPECTRAL_FFT_REPORT_INFO0_FFT_NUM, + __le32_to_cpu(search->info0)); + report->radar_check = FIELD_GET(SPECTRAL_FFT_REPORT_INFO0_RADAR_CHECK, + __le32_to_cpu(search->info0)); + report->peak_idx = FIELD_GET(SPECTRAL_FFT_REPORT_INFO0_PEAK_SIGNED_IDX, + __le32_to_cpu(search->info0)); + report->chain_idx = FIELD_GET(SPECTRAL_FFT_REPORT_INFO0_CHAIN_IDX, + __le32_to_cpu(search->info0)); + report->base_pwr_db = FIELD_GET(SPECTRAL_FFT_REPORT_INFO1_BASE_PWR_DB, + __le32_to_cpu(search->info1)); + report->total_gain_db = FIELD_GET(SPECTRAL_FFT_REPORT_INFO1_TOTAL_GAIN_DB, + __le32_to_cpu(search->info1)); + report->strong_bin_count = FIELD_GET(SPECTRAL_FFT_REPORT_INFO2_NUM_STRONG_BINS, + __le32_to_cpu(search->info2)); + report->peak_mag = FIELD_GET(SPECTRAL_FFT_REPORT_INFO2_PEAK_MAGNITUDE, + __le32_to_cpu(search->info2)); + report->avg_pwr_db = FIELD_GET(SPECTRAL_FFT_REPORT_INFO2_AVG_PWR_DB, + __le32_to_cpu(search->info2)); + report->rel_pwr_db = FIELD_GET(SPECTRAL_FFT_REPORT_INFO2_REL_PWR_DB, + __le32_to_cpu(search->info2)); + + return 0; +} + +static u8 ath11k_spectral_get_max_exp(s8 max_index, u8 max_magnitude, + int bin_len, u8 *bins) +{ + int dc_pos; + u8 max_exp; + + dc_pos = bin_len / 2; + + /* peak index outside of bins */ + if (dc_pos <= max_index || -dc_pos >= max_index) + return 0; + + for (max_exp = 0; max_exp < 8; max_exp++) { + if (bins[dc_pos + max_index] == (max_magnitude >> max_exp)) + break; + } + + /* max_exp not found */ + if (bins[dc_pos + max_index] != (max_magnitude >> max_exp)) + return 0; + + return max_exp; +} + +static void ath11k_spectral_parse_fft(u8 *outbins, u8 *inbins, int num_bins, u8 fft_sz) +{ + int i, j; + + i = 0; + j = 0; + while (i < num_bins) { + outbins[i] = inbins[j]; + i++; + j += fft_sz; + } +} + +static +int ath11k_spectral_process_fft(struct ath11k *ar, + struct ath11k_spectral_summary_report *summary, + void *data, + struct fft_sample_ath11k *fft_sample, + u32 data_len) +{ + struct ath11k_base *ab = ar->ab; + struct spectral_search_fft_report *fft_report = data; + struct ath11k_spectral_search_report search; + struct spectral_tlv *tlv; + int tlv_len, bin_len, num_bins; + u16 length, freq; + u8 chan_width_mhz, bin_sz; + int ret; + u32 check_length; + bool fragment_sample = false; + + lockdep_assert_held(&ar->spectral.lock); + + if (!ab->hw_params.spectral.fft_sz) { + ath11k_warn(ab, "invalid bin size type for hw rev %d\n", + ab->hw_rev); + return -EINVAL; + } + + tlv = (struct spectral_tlv *)data; + tlv_len = FIELD_GET(SPECTRAL_TLV_HDR_LEN, __le32_to_cpu(tlv->header)); + /* convert Dword into bytes */ + tlv_len *= ATH11K_SPECTRAL_DWORD_SIZE; + bin_len = tlv_len - ab->hw_params.spectral.fft_hdr_len; + + if (data_len < (bin_len + sizeof(*fft_report))) { + ath11k_warn(ab, "mismatch in expected bin len %d and data len %d\n", + bin_len, data_len); + return -EINVAL; + } + + bin_sz = ab->hw_params.spectral.fft_sz + ab->hw_params.spectral.fft_pad_sz; + num_bins = bin_len / bin_sz; + /* Only In-band bins are useful to user for visualize */ + num_bins >>= 1; + + if (num_bins < ATH11K_SPECTRAL_MIN_IB_BINS || + num_bins > ATH11K_SPECTRAL_MAX_IB_BINS(ab) || + !is_power_of_2(num_bins)) { + ath11k_warn(ab, "Invalid num of bins %d\n", num_bins); + return -EINVAL; + } + + check_length = sizeof(*fft_report) + (num_bins * ab->hw_params.spectral.fft_sz); + ret = ath11k_dbring_validate_buffer(ar, data, check_length); + if (ret) { + ath11k_warn(ar->ab, "found magic value in fft data, dropping\n"); + return ret; + } + + ret = ath11k_spectral_pull_search(ar, data, &search); + if (ret) { + ath11k_warn(ab, "failed to pull search report %d\n", ret); + return ret; + } + + chan_width_mhz = summary->meta.ch_width; + + switch (chan_width_mhz) { + case ATH11K_SPECTRAL_20MHZ: + case ATH11K_SPECTRAL_40MHZ: + case ATH11K_SPECTRAL_80MHZ: + fft_sample->chan_width_mhz = chan_width_mhz; + break; + case ATH11K_SPECTRAL_160MHZ: + if (ab->hw_params.spectral.fragment_160mhz) { + chan_width_mhz /= 2; + fragment_sample = true; + } + fft_sample->chan_width_mhz = chan_width_mhz; + break; + default: + ath11k_warn(ab, "invalid channel width %d\n", chan_width_mhz); + return -EINVAL; + } + + length = sizeof(*fft_sample) - sizeof(struct fft_sample_tlv) + num_bins; + fft_sample->tlv.type = ATH_FFT_SAMPLE_ATH11K; + fft_sample->tlv.length = __cpu_to_be16(length); + + fft_sample->tsf = __cpu_to_be32(search.timestamp); + fft_sample->max_magnitude = __cpu_to_be16(search.peak_mag); + fft_sample->max_index = FIELD_GET(SPECTRAL_FFT_REPORT_INFO0_PEAK_SIGNED_IDX, + __le32_to_cpu(fft_report->info0)); + + summary->inb_pwr_db >>= 1; + fft_sample->rssi = __cpu_to_be16(summary->inb_pwr_db); + fft_sample->noise = __cpu_to_be32(summary->meta.noise_floor[search.chain_idx]); + + freq = summary->meta.freq1; + fft_sample->freq1 = __cpu_to_be16(freq); + + freq = summary->meta.freq2; + fft_sample->freq2 = __cpu_to_be16(freq); + + /* If freq2 is available then the spectral scan results are fragmented + * as primary and secondary + */ + if (fragment_sample && freq) { + if (!ar->spectral.is_primary) + fft_sample->freq1 = cpu_to_be16(freq); + + /* We have to toggle the is_primary to handle the next report */ + ar->spectral.is_primary = !ar->spectral.is_primary; + } + + ath11k_spectral_parse_fft(fft_sample->data, fft_report->bins, num_bins, + ab->hw_params.spectral.fft_sz); + + fft_sample->max_exp = ath11k_spectral_get_max_exp(fft_sample->max_index, + search.peak_mag, + num_bins, + fft_sample->data); + + if (ar->spectral.rfs_scan) + relay_write(ar->spectral.rfs_scan, fft_sample, + length + sizeof(struct fft_sample_tlv)); + + return 0; +} + +static int ath11k_spectral_process_data(struct ath11k *ar, + struct ath11k_dbring_data *param) +{ + struct ath11k_base *ab = ar->ab; + struct spectral_tlv *tlv; + struct spectral_summary_fft_report *summary = NULL; + struct ath11k_spectral_summary_report summ_rpt; + struct fft_sample_ath11k *fft_sample = NULL; + u8 *data; + u32 data_len, i; + u8 sign, tag; + int tlv_len, sample_sz; + int ret; + bool quit = false; + + spin_lock_bh(&ar->spectral.lock); + + if (!ar->spectral.enabled) { + ret = -EINVAL; + goto unlock; + } + + sample_sz = sizeof(*fft_sample) + ATH11K_SPECTRAL_MAX_IB_BINS(ab); + fft_sample = kmalloc(sample_sz, GFP_ATOMIC); + if (!fft_sample) { + ret = -ENOBUFS; + goto unlock; + } + + data = param->data; + data_len = param->data_sz; + i = 0; + while (!quit && (i < data_len)) { + if ((i + sizeof(*tlv)) > data_len) { + ath11k_warn(ab, "failed to parse spectral tlv hdr at bytes %d\n", + i); + ret = -EINVAL; + goto err; + } + + tlv = (struct spectral_tlv *)&data[i]; + sign = FIELD_GET(SPECTRAL_TLV_HDR_SIGN, + __le32_to_cpu(tlv->header)); + if (sign != ATH11K_SPECTRAL_SIGNATURE) { + ath11k_warn(ab, "Invalid sign 0x%x at bytes %d\n", + sign, i); + ret = -EINVAL; + goto err; + } + + tlv_len = FIELD_GET(SPECTRAL_TLV_HDR_LEN, + __le32_to_cpu(tlv->header)); + /* convert Dword into bytes */ + tlv_len *= ATH11K_SPECTRAL_DWORD_SIZE; + if ((i + sizeof(*tlv) + tlv_len) > data_len) { + ath11k_warn(ab, "failed to parse spectral tlv payload at bytes %d tlv_len:%d data_len:%d\n", + i, tlv_len, data_len); + ret = -EINVAL; + goto err; + } + + tag = FIELD_GET(SPECTRAL_TLV_HDR_TAG, + __le32_to_cpu(tlv->header)); + switch (tag) { + case ATH11K_SPECTRAL_TAG_SCAN_SUMMARY: + /* HW bug in tlv length of summary report, + * HW report 3 DWORD size but the data payload + * is 4 DWORD size (16 bytes). + * Need to remove this workaround once HW bug fixed + */ + tlv_len = sizeof(*summary) - sizeof(*tlv) + + ab->hw_params.spectral.summary_pad_sz; + + if (tlv_len < (sizeof(*summary) - sizeof(*tlv))) { + ath11k_warn(ab, "failed to parse spectral summary at bytes %d tlv_len:%d\n", + i, tlv_len); + ret = -EINVAL; + goto err; + } + + ret = ath11k_dbring_validate_buffer(ar, data, tlv_len); + if (ret) { + ath11k_warn(ar->ab, "found magic value in spectral summary, dropping\n"); + goto err; + } + + summary = (struct spectral_summary_fft_report *)tlv; + ath11k_spectral_pull_summary(ar, ¶m->meta, + summary, &summ_rpt); + break; + case ATH11K_SPECTRAL_TAG_SCAN_SEARCH: + if (tlv_len < (sizeof(struct spectral_search_fft_report) - + sizeof(*tlv))) { + ath11k_warn(ab, "failed to parse spectral search fft at bytes %d\n", + i); + ret = -EINVAL; + goto err; + } + + memset(fft_sample, 0, sample_sz); + ret = ath11k_spectral_process_fft(ar, &summ_rpt, tlv, + fft_sample, + data_len - i); + if (ret) { + ath11k_warn(ab, "failed to process spectral fft at bytes %d\n", + i); + goto err; + } + quit = true; + break; + } + + i += sizeof(*tlv) + tlv_len; + } + + ret = 0; + +err: + kfree(fft_sample); +unlock: + spin_unlock_bh(&ar->spectral.lock); + return ret; +} + +static int ath11k_spectral_ring_alloc(struct ath11k *ar, + struct ath11k_dbring_cap *db_cap) +{ + struct ath11k_spectral *sp = &ar->spectral; + int ret; + + ret = ath11k_dbring_srng_setup(ar, &sp->rx_ring, + 0, db_cap->min_elem); + if (ret) { + ath11k_warn(ar->ab, "failed to setup db ring\n"); + return ret; + } + + ath11k_dbring_set_cfg(ar, &sp->rx_ring, + ATH11K_SPECTRAL_NUM_RESP_PER_EVENT, + ATH11K_SPECTRAL_EVENT_TIMEOUT_MS, + ath11k_spectral_process_data); + + ret = ath11k_dbring_buf_setup(ar, &sp->rx_ring, db_cap); + if (ret) { + ath11k_warn(ar->ab, "failed to setup db ring buffer\n"); + goto srng_cleanup; + } + + ret = ath11k_dbring_wmi_cfg_setup(ar, &sp->rx_ring, + WMI_DIRECT_BUF_SPECTRAL); + if (ret) { + ath11k_warn(ar->ab, "failed to setup db ring cfg\n"); + goto buffer_cleanup; + } + + return 0; + +buffer_cleanup: + ath11k_dbring_buf_cleanup(ar, &sp->rx_ring); +srng_cleanup: + ath11k_dbring_srng_cleanup(ar, &sp->rx_ring); + return ret; +} + +static inline void ath11k_spectral_ring_free(struct ath11k *ar) +{ + struct ath11k_spectral *sp = &ar->spectral; + + ath11k_dbring_srng_cleanup(ar, &sp->rx_ring); + ath11k_dbring_buf_cleanup(ar, &sp->rx_ring); +} + +static inline void ath11k_spectral_debug_unregister(struct ath11k *ar) +{ + debugfs_remove(ar->spectral.scan_bins); + ar->spectral.scan_bins = NULL; + + debugfs_remove(ar->spectral.scan_count); + ar->spectral.scan_count = NULL; + + debugfs_remove(ar->spectral.scan_ctl); + ar->spectral.scan_ctl = NULL; + + if (ar->spectral.rfs_scan) { + relay_close(ar->spectral.rfs_scan); + ar->spectral.rfs_scan = NULL; + } +} + +int ath11k_spectral_vif_stop(struct ath11k_vif *arvif) +{ + if (!arvif->spectral_enabled) + return 0; + + return ath11k_spectral_scan_config(arvif->ar, ATH11K_SPECTRAL_DISABLED); +} + +void ath11k_spectral_reset_buffer(struct ath11k *ar) +{ + if (!ar->spectral.enabled) + return; + + if (ar->spectral.rfs_scan) + relay_reset(ar->spectral.rfs_scan); +} + +void ath11k_spectral_deinit(struct ath11k_base *ab) +{ + struct ath11k *ar; + struct ath11k_spectral *sp; + int i; + + for (i = 0; i < ab->num_radios; i++) { + ar = ab->pdevs[i].ar; + sp = &ar->spectral; + + if (!sp->enabled) + continue; + + mutex_lock(&ar->conf_mutex); + ath11k_spectral_scan_config(ar, ATH11K_SPECTRAL_DISABLED); + mutex_unlock(&ar->conf_mutex); + + spin_lock_bh(&sp->lock); + sp->enabled = false; + spin_unlock_bh(&sp->lock); + + ath11k_spectral_debug_unregister(ar); + ath11k_spectral_ring_free(ar); + } +} + +static inline int ath11k_spectral_debug_register(struct ath11k *ar) +{ + int ret; + + ar->spectral.rfs_scan = relay_open("spectral_scan", + ar->debug.debugfs_pdev, + ATH11K_SPECTRAL_SUB_BUFF_SIZE(ar->ab), + ATH11K_SPECTRAL_NUM_SUB_BUF, + &rfs_scan_cb, NULL); + if (!ar->spectral.rfs_scan) { + ath11k_warn(ar->ab, "failed to open relay in pdev %d\n", + ar->pdev_idx); + return -EINVAL; + } + + ar->spectral.scan_ctl = debugfs_create_file("spectral_scan_ctl", + 0600, + ar->debug.debugfs_pdev, ar, + &fops_scan_ctl); + if (!ar->spectral.scan_ctl) { + ath11k_warn(ar->ab, "failed to open debugfs in pdev %d\n", + ar->pdev_idx); + ret = -EINVAL; + goto debug_unregister; + } + + ar->spectral.scan_count = debugfs_create_file("spectral_count", + 0600, + ar->debug.debugfs_pdev, ar, + &fops_scan_count); + if (!ar->spectral.scan_count) { + ath11k_warn(ar->ab, "failed to open debugfs in pdev %d\n", + ar->pdev_idx); + ret = -EINVAL; + goto debug_unregister; + } + + ar->spectral.scan_bins = debugfs_create_file("spectral_bins", + 0600, + ar->debug.debugfs_pdev, ar, + &fops_scan_bins); + if (!ar->spectral.scan_bins) { + ath11k_warn(ar->ab, "failed to open debugfs in pdev %d\n", + ar->pdev_idx); + ret = -EINVAL; + goto debug_unregister; + } + + return 0; + +debug_unregister: + ath11k_spectral_debug_unregister(ar); + return ret; +} + +int ath11k_spectral_init(struct ath11k_base *ab) +{ + struct ath11k *ar; + struct ath11k_spectral *sp; + struct ath11k_dbring_cap db_cap; + int ret; + int i; + + if (!test_bit(WMI_TLV_SERVICE_FREQINFO_IN_METADATA, + ab->wmi_ab.svc_map)) + return 0; + + if (!ab->hw_params.spectral.fft_sz) + return 0; + + for (i = 0; i < ab->num_radios; i++) { + ar = ab->pdevs[i].ar; + sp = &ar->spectral; + + ret = ath11k_dbring_get_cap(ar->ab, ar->pdev_idx, + WMI_DIRECT_BUF_SPECTRAL, + &db_cap); + if (ret) + continue; + + idr_init(&sp->rx_ring.bufs_idr); + spin_lock_init(&sp->rx_ring.idr_lock); + spin_lock_init(&sp->lock); + + ret = ath11k_spectral_ring_alloc(ar, &db_cap); + if (ret) { + ath11k_warn(ab, "failed to init spectral ring for pdev %d\n", + i); + goto deinit; + } + + spin_lock_bh(&sp->lock); + + sp->mode = ATH11K_SPECTRAL_DISABLED; + sp->count = ATH11K_WMI_SPECTRAL_COUNT_DEFAULT; + sp->fft_size = ATH11K_WMI_SPECTRAL_FFT_SIZE_DEFAULT; + sp->enabled = true; + + spin_unlock_bh(&sp->lock); + + ret = ath11k_spectral_debug_register(ar); + if (ret) { + ath11k_warn(ab, "failed to register spectral for pdev %d\n", + i); + goto deinit; + } + } + + return 0; + +deinit: + ath11k_spectral_deinit(ab); + return ret; +} + +enum ath11k_spectral_mode ath11k_spectral_get_mode(struct ath11k *ar) +{ + if (ar->spectral.enabled) + return ar->spectral.mode; + else + return ATH11K_SPECTRAL_DISABLED; +} + +struct ath11k_dbring *ath11k_spectral_get_dbring(struct ath11k *ar) +{ + if (ar->spectral.enabled) + return &ar->spectral.rx_ring; + else + return NULL; +} |