aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/wfx/scan.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--drivers/staging/wfx/scan.c294
1 files changed, 294 insertions, 0 deletions
diff --git a/drivers/staging/wfx/scan.c b/drivers/staging/wfx/scan.c
new file mode 100644
index 000000000000..35fcf9119f96
--- /dev/null
+++ b/drivers/staging/wfx/scan.c
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Scan related functions.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ */
+#include <net/mac80211.h>
+
+#include "scan.h"
+#include "wfx.h"
+#include "sta.h"
+#include "hif_tx_mib.h"
+
+static void __ieee80211_scan_completed_compat(struct ieee80211_hw *hw,
+ bool aborted)
+{
+ struct cfg80211_scan_info info = {
+ .aborted = aborted ? 1 : 0,
+ };
+
+ ieee80211_scan_completed(hw, &info);
+}
+
+static void wfx_scan_restart_delayed(struct wfx_vif *wvif)
+{
+ if (wvif->delayed_unjoin) {
+ wvif->delayed_unjoin = false;
+ if (!schedule_work(&wvif->unjoin_work))
+ wfx_tx_unlock(wvif->wdev);
+ } else if (wvif->delayed_link_loss) {
+ wvif->delayed_link_loss = 0;
+ wfx_cqm_bssloss_sm(wvif, 1, 0, 0);
+ }
+}
+
+static int wfx_scan_start(struct wfx_vif *wvif, struct wfx_scan_params *scan)
+{
+ int ret;
+ int tmo = 500;
+
+ if (wvif->state == WFX_STATE_PRE_STA)
+ return -EBUSY;
+
+ tmo += scan->scan_req.num_of_channels *
+ ((20 * (scan->scan_req.max_channel_time)) + 10);
+ atomic_set(&wvif->scan.in_progress, 1);
+ atomic_set(&wvif->wdev->scan_in_progress, 1);
+
+ schedule_delayed_work(&wvif->scan.timeout, msecs_to_jiffies(tmo));
+ ret = hif_scan(wvif, scan);
+ if (ret) {
+ wfx_scan_failed_cb(wvif);
+ atomic_set(&wvif->scan.in_progress, 0);
+ atomic_set(&wvif->wdev->scan_in_progress, 0);
+ cancel_delayed_work_sync(&wvif->scan.timeout);
+ wfx_scan_restart_delayed(wvif);
+ }
+ return ret;
+}
+
+int wfx_hw_scan(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_scan_request *hw_req)
+{
+ struct wfx_dev *wdev = hw->priv;
+ struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+ struct cfg80211_scan_request *req = &hw_req->req;
+ struct sk_buff *skb;
+ int i, ret;
+ struct hif_mib_template_frame *p;
+
+ if (!wvif)
+ return -EINVAL;
+
+ if (wvif->state == WFX_STATE_AP)
+ return -EOPNOTSUPP;
+
+ if (req->n_ssids == 1 && !req->ssids[0].ssid_len)
+ req->n_ssids = 0;
+
+ if (req->n_ssids > HIF_API_MAX_NB_SSIDS)
+ return -EINVAL;
+
+ skb = ieee80211_probereq_get(hw, wvif->vif->addr, NULL, 0, req->ie_len);
+ if (!skb)
+ return -ENOMEM;
+
+ if (req->ie_len)
+ memcpy(skb_put(skb, req->ie_len), req->ie, req->ie_len);
+
+ mutex_lock(&wdev->conf_mutex);
+
+ p = (struct hif_mib_template_frame *)skb_push(skb, 4);
+ p->frame_type = HIF_TMPLT_PRBREQ;
+ p->frame_length = cpu_to_le16(skb->len - 4);
+ ret = hif_set_template_frame(wvif, p);
+ skb_pull(skb, 4);
+
+ if (!ret)
+ /* Host want to be the probe responder. */
+ ret = wfx_fwd_probe_req(wvif, true);
+ if (ret) {
+ mutex_unlock(&wdev->conf_mutex);
+ dev_kfree_skb(skb);
+ return ret;
+ }
+
+ wfx_tx_lock_flush(wdev);
+
+ WARN(wvif->scan.req, "unexpected concurrent scan");
+ wvif->scan.req = req;
+ wvif->scan.n_ssids = 0;
+ wvif->scan.status = 0;
+ wvif->scan.begin = &req->channels[0];
+ wvif->scan.curr = wvif->scan.begin;
+ wvif->scan.end = &req->channels[req->n_channels];
+ wvif->scan.output_power = wdev->output_power;
+
+ for (i = 0; i < req->n_ssids; ++i) {
+ struct hif_ssid_def *dst = &wvif->scan.ssids[wvif->scan.n_ssids];
+
+ memcpy(&dst->ssid[0], req->ssids[i].ssid, sizeof(dst->ssid));
+ dst->ssid_length = req->ssids[i].ssid_len;
+ ++wvif->scan.n_ssids;
+ }
+
+ mutex_unlock(&wdev->conf_mutex);
+
+ if (skb)
+ dev_kfree_skb(skb);
+ schedule_work(&wvif->scan.work);
+ return 0;
+}
+
+void wfx_scan_work(struct work_struct *work)
+{
+ struct wfx_vif *wvif = container_of(work, struct wfx_vif, scan.work);
+ struct ieee80211_channel **it;
+ struct wfx_scan_params scan = {
+ .scan_req.scan_type.type = 0, /* Foreground */
+ };
+ struct ieee80211_channel *first;
+ bool first_run = (wvif->scan.begin == wvif->scan.curr &&
+ wvif->scan.begin != wvif->scan.end);
+ int i;
+
+ down(&wvif->scan.lock);
+ mutex_lock(&wvif->wdev->conf_mutex);
+
+ if (first_run) {
+ if (wvif->state == WFX_STATE_STA &&
+ !(wvif->powersave_mode.pm_mode.enter_psm)) {
+ struct hif_req_set_pm_mode pm = wvif->powersave_mode;
+
+ pm.pm_mode.enter_psm = 1;
+ wfx_set_pm(wvif, &pm);
+ }
+ }
+
+ if (!wvif->scan.req || wvif->scan.curr == wvif->scan.end) {
+ if (wvif->scan.output_power != wvif->wdev->output_power)
+ hif_set_output_power(wvif,
+ wvif->wdev->output_power * 10);
+
+ if (wvif->scan.status < 0)
+ dev_warn(wvif->wdev->dev, "scan failed\n");
+ else if (wvif->scan.req)
+ dev_dbg(wvif->wdev->dev, "scan completed\n");
+ else
+ dev_dbg(wvif->wdev->dev, "scan canceled\n");
+
+ wvif->scan.req = NULL;
+ wfx_scan_restart_delayed(wvif);
+ wfx_tx_unlock(wvif->wdev);
+ mutex_unlock(&wvif->wdev->conf_mutex);
+ __ieee80211_scan_completed_compat(wvif->wdev->hw,
+ wvif->scan.status ? 1 : 0);
+ up(&wvif->scan.lock);
+ if (wvif->state == WFX_STATE_STA &&
+ !(wvif->powersave_mode.pm_mode.enter_psm))
+ wfx_set_pm(wvif, &wvif->powersave_mode);
+ return;
+ }
+ first = *wvif->scan.curr;
+
+ for (it = wvif->scan.curr + 1, i = 1;
+ it != wvif->scan.end && i < HIF_API_MAX_NB_CHANNELS;
+ ++it, ++i) {
+ if ((*it)->band != first->band)
+ break;
+ if (((*it)->flags ^ first->flags) &
+ IEEE80211_CHAN_NO_IR)
+ break;
+ if (!(first->flags & IEEE80211_CHAN_NO_IR) &&
+ (*it)->max_power != first->max_power)
+ break;
+ }
+ scan.scan_req.band = first->band;
+
+ if (wvif->scan.req->no_cck)
+ scan.scan_req.max_transmit_rate = API_RATE_INDEX_G_6MBPS;
+ else
+ scan.scan_req.max_transmit_rate = API_RATE_INDEX_B_1MBPS;
+ scan.scan_req.num_of_probe_requests =
+ (first->flags & IEEE80211_CHAN_NO_IR) ? 0 : 2;
+ scan.scan_req.num_of_ssi_ds = wvif->scan.n_ssids;
+ scan.ssids = &wvif->scan.ssids[0];
+ scan.scan_req.num_of_channels = it - wvif->scan.curr;
+ scan.scan_req.probe_delay = 100;
+ // FIXME: Check if FW can do active scan while joined.
+ if (wvif->state == WFX_STATE_STA) {
+ scan.scan_req.scan_type.type = 1;
+ scan.scan_req.scan_flags.fbg = 1;
+ }
+
+ scan.ch = kcalloc(scan.scan_req.num_of_channels,
+ sizeof(u8), GFP_KERNEL);
+
+ if (!scan.ch) {
+ wvif->scan.status = -ENOMEM;
+ goto fail;
+ }
+ for (i = 0; i < scan.scan_req.num_of_channels; ++i)
+ scan.ch[i] = wvif->scan.curr[i]->hw_value;
+
+ if (wvif->scan.curr[0]->flags & IEEE80211_CHAN_NO_IR) {
+ scan.scan_req.min_channel_time = 50;
+ scan.scan_req.max_channel_time = 150;
+ } else {
+ scan.scan_req.min_channel_time = 10;
+ scan.scan_req.max_channel_time = 50;
+ }
+ if (!(first->flags & IEEE80211_CHAN_NO_IR) &&
+ wvif->scan.output_power != first->max_power) {
+ wvif->scan.output_power = first->max_power;
+ hif_set_output_power(wvif, wvif->scan.output_power * 10);
+ }
+ wvif->scan.status = wfx_scan_start(wvif, &scan);
+ kfree(scan.ch);
+ if (wvif->scan.status)
+ goto fail;
+ wvif->scan.curr = it;
+ mutex_unlock(&wvif->wdev->conf_mutex);
+ return;
+
+fail:
+ wvif->scan.curr = wvif->scan.end;
+ mutex_unlock(&wvif->wdev->conf_mutex);
+ up(&wvif->scan.lock);
+ schedule_work(&wvif->scan.work);
+}
+
+static void wfx_scan_complete(struct wfx_vif *wvif)
+{
+ up(&wvif->scan.lock);
+ atomic_set(&wvif->wdev->scan_in_progress, 0);
+
+ wfx_scan_work(&wvif->scan.work);
+}
+
+void wfx_scan_failed_cb(struct wfx_vif *wvif)
+{
+ if (cancel_delayed_work_sync(&wvif->scan.timeout) > 0) {
+ wvif->scan.status = -EIO;
+ schedule_work(&wvif->scan.timeout.work);
+ }
+}
+
+void wfx_scan_complete_cb(struct wfx_vif *wvif, struct hif_ind_scan_cmpl *arg)
+{
+ if (cancel_delayed_work_sync(&wvif->scan.timeout) > 0) {
+ wvif->scan.status = 1;
+ schedule_work(&wvif->scan.timeout.work);
+ }
+}
+
+void wfx_scan_timeout(struct work_struct *work)
+{
+ struct wfx_vif *wvif = container_of(work, struct wfx_vif,
+ scan.timeout.work);
+
+ if (atomic_xchg(&wvif->scan.in_progress, 0)) {
+ if (wvif->scan.status > 0) {
+ wvif->scan.status = 0;
+ } else if (!wvif->scan.status) {
+ dev_warn(wvif->wdev->dev, "timeout waiting for scan complete notification\n");
+ wvif->scan.status = -ETIMEDOUT;
+ wvif->scan.curr = wvif->scan.end;
+ hif_stop_scan(wvif);
+ }
+ wfx_scan_complete(wvif);
+ }
+}