new file mode 100644
@@ -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;
+}