From: Viktor Barna <viktor.barna@xxxxxxxxxx> (Part of the split. Please, take a look at the cover letter for more details). Signed-off-by: Viktor Barna <viktor.barna@xxxxxxxxxx> --- drivers/net/wireless/celeno/cl8k/scan.c | 392 ++++++++++++++++++++++++ 1 file changed, 392 insertions(+) create mode 100644 drivers/net/wireless/celeno/cl8k/scan.c diff --git a/drivers/net/wireless/celeno/cl8k/scan.c b/drivers/net/wireless/celeno/cl8k/scan.c new file mode 100644 index 000000000000..10076d93620e --- /dev/null +++ b/drivers/net/wireless/celeno/cl8k/scan.c @@ -0,0 +1,392 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +/* Copyright(c) 2019-2022, Celeno Communications Ltd. */ + +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/slab.h> + +#include "channel.h" +#include "chip.h" +#include "calib.h" +#include "debug.h" +#include "rates.h" +#include "vif.h" +#include "hw.h" +#include "scan.h" + +#define CL_MIN_SCAN_TIME_MS 50 +#define CL_MIN_WAIT_TIME_MS 20 + +static const char SCANNER_KTHREAD_NAME[] = "cl_scanner_kthread"; + +int cl_scan_channel_switch(struct cl_hw *cl_hw, u8 channel, u8 bw, + bool allow_recalib) +{ + struct cl_vif *cl_vif = cl_vif_get_first(cl_hw); + struct cfg80211_chan_def *chandef = NULL; + struct cfg80211_chan_def local_chandef; + struct ieee80211_channel *chan = NULL; + u16 freq = ieee80211_channel_to_frequency(channel, cl_hw->nl_band); + int ret = 0; + + if (!cl_vif) { + ret = -EINVAL; + goto exit; + } + + chandef = &cl_vif->vif->bss_conf.chandef; + local_chandef = *chandef; + + chan = ieee80211_get_channel(cl_hw->hw->wiphy, freq); + if (!chan) { + cl_dbg_err(cl_hw, "Channel %u wasn't found!\n", channel); + ret = -EINVAL; + goto exit; + } + + local_chandef.chan = chan; + if (cl_chandef_calc(cl_hw, channel, bw, + &local_chandef.width, + &local_chandef.chan->center_freq, + &local_chandef.center_freq1)) { + cl_dbg_err(cl_hw, "Failed to extract chandef data for ch:%d\n", + channel); + ret = -EINVAL; + goto exit; + } + + *chandef = local_chandef; + cl_hw->hw->conf.chandef = local_chandef; + + if (cl_hw->chip->conf->ce_calib_runtime_en && allow_recalib) + ret = cl_calib_runtime_and_switch_channel(cl_hw, channel, bw, + freq, + chandef->center_freq1); + else + ret = cl_msg_tx_set_channel(cl_hw, channel, bw, freq, + chandef->center_freq1, + CL_CALIB_PARAMS_DEFAULT_STRUCT); +exit: + return ret; +} + +static int cl_scan_channel(struct cl_chan_scanner *scanner, u8 ch_idx) +{ + u8 main_channel; + enum cl_channel_bw main_bw; + s32 res = 0; + bool is_off_channel; + u64 scan_time_jiffies; + + /* + * 1. Save current channel + * 2. Disable tx + * 3. jump to new channel + * 4. Enable promiscious + * 5. Enable BSS collection + * 6. Reset stats counters + * 7. Sleep for scan_time + * 8. Calculate stats + * 9. Disable promiscious + * 10. Disable BSS collection + * 11. Switch to current channel + * 12. Enable tx + **/ + + cl_dbg_trace(scanner->cl_hw, "Starting scan on channel %u, scan time %u(ms)\n", + scanner->channels[ch_idx].channel, scanner->scan_time); + + /* Save current channel */ + res = mutex_lock_interruptible(&scanner->cl_hw->set_channel_mutex); + if (res != 0) + return res; + main_channel = scanner->cl_hw->channel; + main_bw = scanner->cl_hw->bw; + mutex_unlock(&scanner->cl_hw->set_channel_mutex); + + cl_dbg_trace(scanner->cl_hw, "Main channel is %u with bw %u\n", + main_channel, main_bw); + + is_off_channel = (scanner->channels[ch_idx].channel != main_channel) || + (scanner->channels[ch_idx].scan_bw != main_bw); + + /* Jump to new channel */ + if (is_off_channel) { + /* Disable tx */ + cl_tx_en(scanner->cl_hw, CL_TX_EN_SCAN, false); + + res = cl_scan_channel_switch(scanner->cl_hw, + scanner->channels[ch_idx].channel, + scanner->channels[ch_idx].scan_bw, + false); + if (res) { + cl_dbg_err(scanner->cl_hw, + "Channel switch failed: ch - %u, bw - %u, err - %d\n", + scanner->channels[ch_idx].channel, + scanner->channels[ch_idx].scan_bw, res); + goto enable_tx; + } + } else { + cl_dbg_trace(scanner->cl_hw, "Scan on main channel %u\n", main_channel); + } + + /* Enable promiscious mode */ + cl_rx_filter_set_promiscuous(scanner->cl_hw); + + /* Reset channel stats */ + cl_get_initial_channel_stats(scanner->cl_hw, &scanner->channels[ch_idx]); + + /* Sleep for scan time */ + scan_time_jiffies = msecs_to_jiffies(scanner->scan_time); + res = wait_for_completion_interruptible_timeout(&scanner->abort_completion, + scan_time_jiffies); + if (res > 0) { + cl_dbg_err(scanner->cl_hw, "Scan on channel %u, bw %u, idx %u was aborted\n", + scanner->channels[ch_idx].channel, + scanner->channels[ch_idx].scan_bw, ch_idx); + res = 0; + } + + /* Calculate stats */ + cl_get_final_channel_stats(scanner->cl_hw, &scanner->channels[ch_idx]); + + /* Disable promiscious */ + cl_rx_filter_restore_flags(scanner->cl_hw); + + if (is_off_channel) { + res = cl_scan_channel_switch(scanner->cl_hw, main_channel, main_bw, false); + if (res) + cl_dbg_err(scanner->cl_hw, + "Switching to main ch %u, bw %u failed, err - %d\n", + main_channel, main_bw, res); +enable_tx: + /* Enable tx */ + cl_tx_en(scanner->cl_hw, CL_TX_EN_SCAN, true); + } + + cl_dbg_trace(scanner->cl_hw, "Scan on channel %u finished, actual scan_time is %u ms\n", + scanner->channels[ch_idx].channel, scanner->channels[ch_idx].scan_time_ms); + + return res; +} + +static s32 cl_run_off_channel_scan(struct cl_chan_scanner *scanner) +{ + u8 i = 0, scanned_channels = 0; + s32 ret = 0; + + for (i = 0; i < scanner->channels_num && !scanner->scan_aborted; ++i) { + if (!scanner->channels[i].scan_enabled) + continue; + + scanner->curr_ch_idx = i; + ret = cl_scan_channel(scanner, i); + if (ret) + cl_dbg_err(scanner->cl_hw, "scan failed, err - %d, channel - %u\n", + ret, scanner->channels[i].channel); + + if (scanner->scan_aborted) + break; + + cl_dbg_trace(scanner->cl_hw, "Scan on chan %u finished, waiting for time %u\n", + scanner->channels[i].channel, scanner->wait_time); + + ++scanned_channels; + if (scanned_channels != scanner->scan_channels_num) { + u64 wait_time_jiffies; + + wait_time_jiffies = msecs_to_jiffies(scanner->wait_time); + ret = wait_for_completion_interruptible_timeout(&scanner->abort_completion, + wait_time_jiffies); + if (ret > 0) { + cl_dbg_err(scanner->cl_hw, "Off-channel scan was aborted\n"); + ret = 0; + } + } + } + + if (scanner->completion_cb) + scanner->completion_cb(scanner->cl_hw, scanner->completion_arg); + + cl_dbg_info(scanner->cl_hw, "Off-channel scan on %u channels finished\n", + scanner->scan_channels_num); + + return ret; +} + +static s32 cl_off_channel_scan_thread_fn(void *args) +{ + struct cl_chan_scanner *scanner = args; + + while (!kthread_should_stop()) { + if (atomic_read(&scanner->scan_thread_busy)) { + cl_run_off_channel_scan(scanner); + atomic_set(&scanner->scan_thread_busy, 0); + wake_up_interruptible(&scanner->wq); + } + + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + } + + return 0; +} + +static bool cl_is_scan_available(struct cl_chan_scanner *scanner) +{ + if (atomic_cmpxchg(&scanner->scan_thread_busy, 0, 1) == 1) { + cl_dbg_warn(scanner->cl_hw, "Off-channel scan is already in progress\n"); + return false; + } + + return true; +} + +static enum cl_channel_bw cl_scanner_fix_input_bw(struct cl_chan_scanner *scanner, u8 bw) +{ + return (bw >= CHNL_BW_MAX) ? scanner->cl_hw->bw : bw; +} + +static void cl_scanner_disable_everywhere(struct cl_chan_scanner *scanner) +{ + u8 j; + + for (j = 0; j < scanner->channels_num; ++j) + scanner->channels[j].scan_enabled = false; +} + +s32 cl_trigger_off_channel_scan(struct cl_chan_scanner *scanner, u32 scan_time, u32 wait_time, + const u8 *channels, enum cl_channel_bw scan_bw, u8 channels_num, + void (*completion_cb)(struct cl_hw *cl_hw, void *arg), + void *completion_arg) +{ + u8 i, j; + + if (!channels || scan_bw > CHNL_BW_MAX) + return -EINVAL; + + if (!scanner->scans_enabled) + return 0; + + if (channels_num > scanner->channels_num) { + cl_dbg_err(scanner->cl_hw, "channels num %u is invalid, max is %u\n", + channels_num, scanner->channels_num); + return -ERANGE; + } + + if (!cl_is_scan_available(scanner)) + return -EBUSY; + + scanner->completion_arg = completion_arg; + scanner->completion_cb = completion_cb; + scanner->scan_time = max_t(u32, scan_time, CL_MIN_SCAN_TIME_MS); + scanner->wait_time = max_t(u32, wait_time, CL_MIN_WAIT_TIME_MS); + scanner->scan_bw = cl_scanner_fix_input_bw(scanner, scan_bw); + scanner->scan_aborted = false; + + cl_scanner_disable_everywhere(scanner); + + scanner->scan_channels_num = 0; + for (j = 0; j < scanner->channels_num; ++j) { + for (i = 0; i < channels_num; ++i) { + if (channels[i] != scanner->channels[j].channel) + continue; + + if (!cl_chan_info_get(scanner->cl_hw, scanner->channels[j].channel, + scanner->scan_bw)) { + cl_dbg_warn(scanner->cl_hw, "channel %u with bw %u is disabled\n", + scanner->channels[j].channel, scanner->scan_bw); + continue; + } + + scanner->channels[j].scan_enabled = true; + ++scanner->scan_channels_num; + } + } + + reinit_completion(&scanner->abort_completion); + + wake_up_process(scanner->scan_thread); + + return 0; +} + +void cl_abort_scan(struct cl_chan_scanner *scanner) +{ + scanner->scan_aborted = true; + complete(&scanner->abort_completion); + cl_dbg_info(scanner->cl_hw, "Off-channel scan was aborted\n"); +} + +bool cl_is_scan_in_progress(const struct cl_chan_scanner *scanner) +{ + return atomic_read(&scanner->scan_thread_busy); +} + +int cl_scanner_init(struct cl_hw *cl_hw) +{ + u8 i, j; + s32 ret = 0; + u32 channels_num; + struct cl_chan_scanner *scanner; + + cl_hw->scanner = vzalloc(sizeof(*cl_hw->scanner)); + if (!cl_hw->scanner) + return -ENOMEM; + + scanner = cl_hw->scanner; + init_completion(&scanner->abort_completion); + + scanner->cl_hw = cl_hw; + scanner->scans_enabled = true; + + channels_num = cl_channel_num(scanner->cl_hw); + for (i = 0, j = 0; i < channels_num; ++i) { + u32 freq; + + freq = cl_channel_idx_to_freq(cl_hw, i); + if (!ieee80211_get_channel(cl_hw->hw->wiphy, freq)) + continue; + + ret = cl_init_channel_stats(scanner->cl_hw, &scanner->channels[j], freq); + if (ret) + return ret; + + cl_dbg_trace(scanner->cl_hw, "Stats for channel %u at index %u initialized\n", + scanner->channels[j].channel, j); + ++j; + } + + scanner->channels_num = j; + + atomic_set(&scanner->scan_thread_busy, 0); + init_waitqueue_head(&scanner->wq); + + scanner->scan_thread = kthread_run(cl_off_channel_scan_thread_fn, + scanner, SCANNER_KTHREAD_NAME); + if (IS_ERR(scanner->scan_thread)) { + cl_dbg_err(scanner->cl_hw, "unable to create kthread %s, err - %ld\n", + SCANNER_KTHREAD_NAME, PTR_ERR(scanner->scan_thread)); + return PTR_ERR(scanner->scan_thread); + } + cl_dbg_trace(scanner->cl_hw, "%s kthread was created, pid - %u\n", + SCANNER_KTHREAD_NAME, scanner->scan_thread->pid); + + return ret; +} + +void cl_scanner_deinit(struct cl_hw *cl_hw) +{ + struct cl_chan_scanner *scanner = cl_hw->scanner; + + if (!scanner->scans_enabled) + goto out; + + if (scanner->scan_thread) + kthread_stop(scanner->scan_thread); + + out: + vfree(cl_hw->scanner); + cl_hw->scanner = NULL; +} -- 2.36.1