[PATCH v2 6/9] wifi: wfx: implement wfx_remain_on_channel()

Jérôme Pouiller
2023-09-27T16:33:58Z

With some conditions, the device is able to send/receive frames during
scan operation. So, it is possible to use it implement the "remain on
channel" feature. We just ask for a passive scan (without sending any
probe request) on one channel.

This architecture allows to leverage some interesting features:
- if the device is AP, the device switches channel just after the next
beacon and the beacons are stopped during the off-channel interval.
- if the device is connected, it advertises it is asleep before to
switch channel (so the AP should stop to try to send data)

Signed-off-by: Jérôme Pouiller <jerome.pouiller@silabs.com>
---
drivers/net/wireless/silabs/wfx/scan.c | 50 ++++++++++++++++++++++++++
drivers/net/wireless/silabs/wfx/scan.h | 1 +
drivers/net/wireless/silabs/wfx/sta.c | 1 +
drivers/net/wireless/silabs/wfx/wfx.h | 5 ++-
4 files changed, 56 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/silabs/wfx/scan.c b/drivers/net/wireless/silabs/wfx/scan.c
index 51338fd43ae4f..57a2d63dd2a62 100644
--- a/drivers/net/wireless/silabs/wfx/scan.c
+++ b/drivers/net/wireless/silabs/wfx/scan.c
@@ -146,14 +146,64 @@ void wfx_scan_complete(struct wfx_vif *wvif, int nb_chan_done)
complete(&wvif->scan_complete);
}

+void wfx_remain_on_channel_work(struct work_struct *work)
+{
+ struct wfx_vif *wvif = container_of(work, struct wfx_vif, remain_on_channel_work);
+ struct ieee80211_channel *chan = wvif->remain_on_channel_chan;
+ int duration = wvif->remain_on_channel_duration;
+ int ret;
+
+ /* Hijack scan request to implement Remain-On-Channel */
+ mutex_lock(&wvif->wdev->conf_mutex);
+ mutex_lock(&wvif->scan_lock);
+ if (wvif->join_in_progress) {
+ dev_info(wvif->wdev->dev, "abort in-progress REQ_JOIN");
+ wfx_reset(wvif);
+ }
+ wfx_tx_lock_flush(wvif->wdev);
+
+ reinit_completion(&wvif->scan_complete);
+ ret = wfx_hif_scan_uniq(wvif, chan, duration);
+ if (ret)
+ goto end;
+ ieee80211_ready_on_channel(wvif->wdev->hw);
+ ret = wait_for_completion_timeout(&wvif->scan_complete,
+ msecs_to_jiffies(duration * 120 / 100));
+ if (!ret) {
+ wfx_hif_stop_scan(wvif);
+ ret = wait_for_completion_timeout(&wvif->scan_complete, 1 * HZ);
+ dev_dbg(wvif->wdev->dev, "roc timeout\n");
+ }
+ if (!ret)
+ dev_err(wvif->wdev->dev, "roc didn't stop\n");
+ ieee80211_remain_on_channel_expired(wvif->wdev->hw);
+end:
+ wfx_tx_unlock(wvif->wdev);
+ mutex_unlock(&wvif->scan_lock);
+ mutex_unlock(&wvif->wdev->conf_mutex);
+}
+
int wfx_remain_on_channel(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_channel *chan, int duration,
enum ieee80211_roc_type type)
{
+ struct wfx_dev *wdev = hw->priv;
+ struct wfx_vif *wvif = (struct wfx_vif *)vif->drv_priv;
+
+ if (wfx_api_older_than(wdev, 3, 10))
+ return -EOPNOTSUPP;
+
+ wvif->remain_on_channel_duration = duration;
+ wvif->remain_on_channel_chan = chan;
+ schedule_work(&wvif->remain_on_channel_work);
return 0;
}

int wfx_cancel_remain_on_channel(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
{
+ struct wfx_vif *wvif = (struct wfx_vif *)vif->drv_priv;
+
+ wfx_hif_stop_scan(wvif);
+ flush_work(&wvif->remain_on_channel_work);
return 0;
}
diff --git a/drivers/net/wireless/silabs/wfx/scan.h b/drivers/net/wireless/silabs/wfx/scan.h
index 2f8361769303e..995ab8c6cb5ef 100644
--- a/drivers/net/wireless/silabs/wfx/scan.h
+++ b/drivers/net/wireless/silabs/wfx/scan.h
@@ -19,6 +19,7 @@ int wfx_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
void wfx_cancel_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
void wfx_scan_complete(struct wfx_vif *wvif, int nb_chan_done);

+void wfx_remain_on_channel_work(struct work_struct *work);
int wfx_remain_on_channel(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_channel *chan, int duration,
enum ieee80211_roc_type type);
diff --git a/drivers/net/wireless/silabs/wfx/sta.c b/drivers/net/wireless/silabs/wfx/sta.c
index c58db2bcea87b..f42341c2baffb 100644
--- a/drivers/net/wireless/silabs/wfx/sta.c
+++ b/drivers/net/wireless/silabs/wfx/sta.c
@@ -733,6 +733,7 @@ int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
mutex_init(&wvif->scan_lock);
init_completion(&wvif->scan_complete);
INIT_WORK(&wvif->scan_work, wfx_hw_scan_work);
+ INIT_WORK(&wvif->remain_on_channel_work, wfx_remain_on_channel_work);

wfx_tx_queues_init(wvif);
wfx_tx_policy_init(wvif);
diff --git a/drivers/net/wireless/silabs/wfx/wfx.h b/drivers/net/wireless/silabs/wfx/wfx.h
index 13ba84b3b2c33..5fd80c423d6f6 100644
--- a/drivers/net/wireless/silabs/wfx/wfx.h
+++ b/drivers/net/wireless/silabs/wfx/wfx.h
@@ -69,6 +69,7 @@ struct wfx_vif {

bool after_dtim_tx_allowed;
bool join_in_progress;
+ struct completion set_pm_mode_complete;

struct delayed_work beacon_loss_work;

@@ -88,7 +89,9 @@ struct wfx_vif {
bool scan_abort;
struct ieee80211_scan_request *scan_req;

- struct completion set_pm_mode_complete;
+ struct ieee80211_channel *remain_on_channel_chan;
+ int remain_on_channel_duration;
+ struct work_struct remain_on_channel_work;
};

static inline struct ieee80211_vif *wvif_to_vif(struct wfx_vif *wvif)
--
2.39.2