1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2b203ffc3SJouni Malinen /*
3b203ffc3SJouni Malinen * Off-channel operation helpers
4b203ffc3SJouni Malinen *
5b203ffc3SJouni Malinen * Copyright 2003, Jouni Malinen <jkmaline@cc.hut.fi>
6b203ffc3SJouni Malinen * Copyright 2004, Instant802 Networks, Inc.
7b203ffc3SJouni Malinen * Copyright 2005, Devicescape Software, Inc.
8b203ffc3SJouni Malinen * Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
9b203ffc3SJouni Malinen * Copyright 2007, Michael Wu <flamingice@sourmilk.net>
10b203ffc3SJouni Malinen * Copyright 2009 Johannes Berg <johannes@sipsolutions.net>
11c6968d4fSAnjaneyulu * Copyright (C) 2019, 2022-2023 Intel Corporation
12b203ffc3SJouni Malinen */
13bc3b2d7fSPaul Gortmaker #include <linux/export.h>
14b203ffc3SJouni Malinen #include <net/mac80211.h>
15b203ffc3SJouni Malinen #include "ieee80211_i.h"
162eb278e0SJohannes Berg #include "driver-ops.h"
17b203ffc3SJouni Malinen
18b203ffc3SJouni Malinen /*
19b23b025fSBen Greear * Tell our hardware to disable PS.
20b23b025fSBen Greear * Optionally inform AP that we will go to sleep so that it will buffer
21b23b025fSBen Greear * the frames while we are doing off-channel work. This is optional
22b23b025fSBen Greear * because we *may* be doing work on-operating channel, and want our
23b23b025fSBen Greear * hardware unconditionally awake, but still let the AP send us normal frames.
24b203ffc3SJouni Malinen */
ieee80211_offchannel_ps_enable(struct ieee80211_sub_if_data * sdata)25559cef99SRajkumar Manoharan static void ieee80211_offchannel_ps_enable(struct ieee80211_sub_if_data *sdata)
26b203ffc3SJouni Malinen {
27b203ffc3SJouni Malinen struct ieee80211_local *local = sdata->local;
284730d597SLuis R. Rodriguez struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
29dba0491fSLoic Poulain bool offchannel_ps_enabled = false;
30b203ffc3SJouni Malinen
31b203ffc3SJouni Malinen /* FIXME: what to do when local->pspolling is true? */
32b203ffc3SJouni Malinen
33b203ffc3SJouni Malinen del_timer_sync(&local->dynamic_ps_timer);
343bc3c0d7SLuis R. Rodriguez del_timer_sync(&ifmgd->bcn_mon_timer);
354730d597SLuis R. Rodriguez del_timer_sync(&ifmgd->conn_mon_timer);
364730d597SLuis R. Rodriguez
37b203ffc3SJouni Malinen cancel_work_sync(&local->dynamic_ps_enable_work);
38b203ffc3SJouni Malinen
39b203ffc3SJouni Malinen if (local->hw.conf.flags & IEEE80211_CONF_PS) {
40dba0491fSLoic Poulain offchannel_ps_enabled = true;
41b203ffc3SJouni Malinen local->hw.conf.flags &= ~IEEE80211_CONF_PS;
42b203ffc3SJouni Malinen ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
43b203ffc3SJouni Malinen }
44b203ffc3SJouni Malinen
45dba0491fSLoic Poulain if (!offchannel_ps_enabled ||
4630686bf7SJohannes Berg !ieee80211_hw_check(&local->hw, PS_NULLFUNC_STACK))
47b203ffc3SJouni Malinen /*
48b203ffc3SJouni Malinen * If power save was enabled, no need to send a nullfunc
49b203ffc3SJouni Malinen * frame because AP knows that we are sleeping. But if the
50b203ffc3SJouni Malinen * hardware is creating the nullfunc frame for power save
51b203ffc3SJouni Malinen * status (ie. IEEE80211_HW_PS_NULLFUNC_STACK is not
52b203ffc3SJouni Malinen * enabled) and power save was enabled, the firmware just
53b203ffc3SJouni Malinen * sent a null frame with power save disabled. So we need
54b203ffc3SJouni Malinen * to send a new nullfunc frame to inform the AP that we
55b203ffc3SJouni Malinen * are again sleeping.
56b203ffc3SJouni Malinen */
57076cdcb1SJohannes Berg ieee80211_send_nullfunc(local, sdata, true);
58b203ffc3SJouni Malinen }
59b203ffc3SJouni Malinen
60dba0491fSLoic Poulain /* inform AP that we are awake again */
ieee80211_offchannel_ps_disable(struct ieee80211_sub_if_data * sdata)61b203ffc3SJouni Malinen static void ieee80211_offchannel_ps_disable(struct ieee80211_sub_if_data *sdata)
62b203ffc3SJouni Malinen {
63b203ffc3SJouni Malinen struct ieee80211_local *local = sdata->local;
64b203ffc3SJouni Malinen
65b203ffc3SJouni Malinen if (!local->ps_sdata)
66076cdcb1SJohannes Berg ieee80211_send_nullfunc(local, sdata, false);
67dba0491fSLoic Poulain else if (local->hw.conf.dynamic_ps_timeout > 0) {
68b203ffc3SJouni Malinen /*
69dba0491fSLoic Poulain * the dynamic_ps_timer had been running before leaving the
70dba0491fSLoic Poulain * operating channel, restart the timer now and send a nullfunc
71dba0491fSLoic Poulain * frame to inform the AP that we are awake so that AP sends
72dba0491fSLoic Poulain * the buffered packets (if any).
73b203ffc3SJouni Malinen */
74076cdcb1SJohannes Berg ieee80211_send_nullfunc(local, sdata, false);
75b203ffc3SJouni Malinen mod_timer(&local->dynamic_ps_timer, jiffies +
76b203ffc3SJouni Malinen msecs_to_jiffies(local->hw.conf.dynamic_ps_timeout));
77b203ffc3SJouni Malinen }
784730d597SLuis R. Rodriguez
793bc3c0d7SLuis R. Rodriguez ieee80211_sta_reset_beacon_monitor(sdata);
804730d597SLuis R. Rodriguez ieee80211_sta_reset_conn_monitor(sdata);
81b203ffc3SJouni Malinen }
82b203ffc3SJouni Malinen
ieee80211_offchannel_stop_vifs(struct ieee80211_local * local)83aacde9eeSStanislaw Gruszka void ieee80211_offchannel_stop_vifs(struct ieee80211_local *local)
84b203ffc3SJouni Malinen {
85b203ffc3SJouni Malinen struct ieee80211_sub_if_data *sdata;
86b203ffc3SJouni Malinen
87fe57d9f5SJohannes Berg if (WARN_ON(local->use_chanctx))
88fe57d9f5SJohannes Berg return;
89fe57d9f5SJohannes Berg
90b23b025fSBen Greear /*
91b23b025fSBen Greear * notify the AP about us leaving the channel and stop all
92b23b025fSBen Greear * STA interfaces.
93b23b025fSBen Greear */
946c17b77bSSeth Forshee
959c35d7d2SSeth Forshee /*
969c35d7d2SSeth Forshee * Stop queues and transmit all frames queued by the driver
979c35d7d2SSeth Forshee * before sending nullfunc to enable powersave at the AP.
989c35d7d2SSeth Forshee */
99445ea4e8SJohannes Berg ieee80211_stop_queues_by_reason(&local->hw, IEEE80211_MAX_QUEUE_MAP,
100cca07b00SLuciano Coelho IEEE80211_QUEUE_STOP_REASON_OFFCHANNEL,
101cca07b00SLuciano Coelho false);
1023b24f4c6SEmmanuel Grumbach ieee80211_flush_queues(local, NULL, false);
1036c17b77bSSeth Forshee
104b203ffc3SJouni Malinen mutex_lock(&local->iflist_mtx);
105b203ffc3SJouni Malinen list_for_each_entry(sdata, &local->interfaces, list) {
106b203ffc3SJouni Malinen if (!ieee80211_sdata_running(sdata))
107b203ffc3SJouni Malinen continue;
108b203ffc3SJouni Malinen
109708d50edSAyala Beker if (sdata->vif.type == NL80211_IFTYPE_P2P_DEVICE ||
110708d50edSAyala Beker sdata->vif.type == NL80211_IFTYPE_NAN)
111f142c6b9SJohannes Berg continue;
112f142c6b9SJohannes Berg
113b23b025fSBen Greear if (sdata->vif.type != NL80211_IFTYPE_MONITOR)
114b23b025fSBen Greear set_bit(SDATA_STATE_OFFCHANNEL, &sdata->state);
115b23b025fSBen Greear
116b23b025fSBen Greear /* Check to see if we should disable beaconing. */
117d6a83228SJohannes Berg if (sdata->vif.bss_conf.enable_beacon) {
118d6a83228SJohannes Berg set_bit(SDATA_STATE_OFFCHANNEL_BEACON_STOPPED,
119d6a83228SJohannes Berg &sdata->state);
120d6a83228SJohannes Berg sdata->vif.bss_conf.enable_beacon = false;
1217b7090b4SJohannes Berg ieee80211_link_info_change_notify(
122d8675a63SJohannes Berg sdata, &sdata->deflink,
123d8675a63SJohannes Berg BSS_CHANGED_BEACON_ENABLED);
124d6a83228SJohannes Berg }
125b203ffc3SJouni Malinen
126aacde9eeSStanislaw Gruszka if (sdata->vif.type == NL80211_IFTYPE_STATION &&
127b23b025fSBen Greear sdata->u.mgd.associated)
128559cef99SRajkumar Manoharan ieee80211_offchannel_ps_enable(sdata);
129b203ffc3SJouni Malinen }
130b203ffc3SJouni Malinen mutex_unlock(&local->iflist_mtx);
131b203ffc3SJouni Malinen }
132b203ffc3SJouni Malinen
ieee80211_offchannel_return(struct ieee80211_local * local)133aacde9eeSStanislaw Gruszka void ieee80211_offchannel_return(struct ieee80211_local *local)
134b203ffc3SJouni Malinen {
135b203ffc3SJouni Malinen struct ieee80211_sub_if_data *sdata;
136b203ffc3SJouni Malinen
137fe57d9f5SJohannes Berg if (WARN_ON(local->use_chanctx))
138fe57d9f5SJohannes Berg return;
139fe57d9f5SJohannes Berg
140b203ffc3SJouni Malinen mutex_lock(&local->iflist_mtx);
141b203ffc3SJouni Malinen list_for_each_entry(sdata, &local->interfaces, list) {
142f142c6b9SJohannes Berg if (sdata->vif.type == NL80211_IFTYPE_P2P_DEVICE)
143f142c6b9SJohannes Berg continue;
144f142c6b9SJohannes Berg
145f6e8cb72SEliad Peller if (sdata->vif.type != NL80211_IFTYPE_MONITOR)
146f6e8cb72SEliad Peller clear_bit(SDATA_STATE_OFFCHANNEL, &sdata->state);
147f6e8cb72SEliad Peller
148b203ffc3SJouni Malinen if (!ieee80211_sdata_running(sdata))
149b203ffc3SJouni Malinen continue;
150b203ffc3SJouni Malinen
151b203ffc3SJouni Malinen /* Tell AP we're back */
152aacde9eeSStanislaw Gruszka if (sdata->vif.type == NL80211_IFTYPE_STATION &&
153aacde9eeSStanislaw Gruszka sdata->u.mgd.associated)
154b203ffc3SJouni Malinen ieee80211_offchannel_ps_disable(sdata);
155b203ffc3SJouni Malinen
156d6a83228SJohannes Berg if (test_and_clear_bit(SDATA_STATE_OFFCHANNEL_BEACON_STOPPED,
157d6a83228SJohannes Berg &sdata->state)) {
158d6a83228SJohannes Berg sdata->vif.bss_conf.enable_beacon = true;
1597b7090b4SJohannes Berg ieee80211_link_info_change_notify(
160d8675a63SJohannes Berg sdata, &sdata->deflink,
161d8675a63SJohannes Berg BSS_CHANGED_BEACON_ENABLED);
162b203ffc3SJouni Malinen }
163d6a83228SJohannes Berg }
164b203ffc3SJouni Malinen mutex_unlock(&local->iflist_mtx);
1656c17b77bSSeth Forshee
166445ea4e8SJohannes Berg ieee80211_wake_queues_by_reason(&local->hw, IEEE80211_MAX_QUEUE_MAP,
167cca07b00SLuciano Coelho IEEE80211_QUEUE_STOP_REASON_OFFCHANNEL,
168cca07b00SLuciano Coelho false);
169b203ffc3SJouni Malinen }
17021f83589SJohannes Berg
ieee80211_roc_notify_destroy(struct ieee80211_roc_work * roc)171aaa016ccSJohannes Berg static void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc)
1722eb278e0SJohannes Berg {
173aaa016ccSJohannes Berg /* was never transmitted */
174aaa016ccSJohannes Berg if (roc->frame) {
175aaa016ccSJohannes Berg cfg80211_mgmt_tx_status(&roc->sdata->wdev, roc->mgmt_tx_cookie,
176aaa016ccSJohannes Berg roc->frame->data, roc->frame->len,
177aaa016ccSJohannes Berg false, GFP_KERNEL);
178aaa016ccSJohannes Berg ieee80211_free_txskb(&roc->sdata->local->hw, roc->frame);
179aaa016ccSJohannes Berg }
180aaa016ccSJohannes Berg
181aaa016ccSJohannes Berg if (!roc->mgmt_tx_cookie)
182aaa016ccSJohannes Berg cfg80211_remain_on_channel_expired(&roc->sdata->wdev,
183aaa016ccSJohannes Berg roc->cookie, roc->chan,
184aaa016ccSJohannes Berg GFP_KERNEL);
185ddb754aaSJames Prestwood else
186ddb754aaSJames Prestwood cfg80211_tx_mgmt_expired(&roc->sdata->wdev,
187ddb754aaSJames Prestwood roc->mgmt_tx_cookie,
188ddb754aaSJames Prestwood roc->chan, GFP_KERNEL);
189aaa016ccSJohannes Berg
190aaa016ccSJohannes Berg list_del(&roc->list);
191aaa016ccSJohannes Berg kfree(roc);
192aaa016ccSJohannes Berg }
193aaa016ccSJohannes Berg
ieee80211_end_finished_rocs(struct ieee80211_local * local,unsigned long now)194aaa016ccSJohannes Berg static unsigned long ieee80211_end_finished_rocs(struct ieee80211_local *local,
195aaa016ccSJohannes Berg unsigned long now)
196aaa016ccSJohannes Berg {
197aaa016ccSJohannes Berg struct ieee80211_roc_work *roc, *tmp;
198aaa016ccSJohannes Berg long remaining_dur_min = LONG_MAX;
199aaa016ccSJohannes Berg
200aaa016ccSJohannes Berg lockdep_assert_held(&local->mtx);
201aaa016ccSJohannes Berg
202aaa016ccSJohannes Berg list_for_each_entry_safe(roc, tmp, &local->roc_list, list) {
203aaa016ccSJohannes Berg long remaining;
204aaa016ccSJohannes Berg
205aaa016ccSJohannes Berg if (!roc->started)
206aaa016ccSJohannes Berg break;
207aaa016ccSJohannes Berg
208aaa016ccSJohannes Berg remaining = roc->start_time +
209aaa016ccSJohannes Berg msecs_to_jiffies(roc->duration) -
210aaa016ccSJohannes Berg now;
211aaa016ccSJohannes Berg
2121b894521SIlan Peer /* In case of HW ROC, it is possible that the HW finished the
2131b894521SIlan Peer * ROC session before the actual requested time. In such a case
2141b894521SIlan Peer * end the ROC session (disregarding the remaining time).
2151b894521SIlan Peer */
2161b894521SIlan Peer if (roc->abort || roc->hw_begun || remaining <= 0)
217aaa016ccSJohannes Berg ieee80211_roc_notify_destroy(roc);
218aaa016ccSJohannes Berg else
219aaa016ccSJohannes Berg remaining_dur_min = min(remaining_dur_min, remaining);
220aaa016ccSJohannes Berg }
221aaa016ccSJohannes Berg
222aaa016ccSJohannes Berg return remaining_dur_min;
223aaa016ccSJohannes Berg }
224aaa016ccSJohannes Berg
ieee80211_recalc_sw_work(struct ieee80211_local * local,unsigned long now)225aaa016ccSJohannes Berg static bool ieee80211_recalc_sw_work(struct ieee80211_local *local,
226aaa016ccSJohannes Berg unsigned long now)
227aaa016ccSJohannes Berg {
228aaa016ccSJohannes Berg long dur = ieee80211_end_finished_rocs(local, now);
229aaa016ccSJohannes Berg
230aaa016ccSJohannes Berg if (dur == LONG_MAX)
231aaa016ccSJohannes Berg return false;
232aaa016ccSJohannes Berg
23356dd205fSJohannes Berg wiphy_delayed_work_queue(local->hw.wiphy, &local->roc_work, dur);
234aaa016ccSJohannes Berg return true;
235aaa016ccSJohannes Berg }
236aaa016ccSJohannes Berg
ieee80211_handle_roc_started(struct ieee80211_roc_work * roc,unsigned long start_time)237aaa016ccSJohannes Berg static void ieee80211_handle_roc_started(struct ieee80211_roc_work *roc,
238aaa016ccSJohannes Berg unsigned long start_time)
239aaa016ccSJohannes Berg {
240aaa016ccSJohannes Berg if (WARN_ON(roc->notified))
2412eb278e0SJohannes Berg return;
2422eb278e0SJohannes Berg
243aaa016ccSJohannes Berg roc->start_time = start_time;
244aaa016ccSJohannes Berg roc->started = true;
245aaa016ccSJohannes Berg
2462eb278e0SJohannes Berg if (roc->mgmt_tx_cookie) {
2472eb278e0SJohannes Berg if (!WARN_ON(!roc->frame)) {
24855de908aSJohannes Berg ieee80211_tx_skb_tid_band(roc->sdata, roc->frame, 7,
24908aca29aSMathy Vanhoef roc->chan->band);
2502eb278e0SJohannes Berg roc->frame = NULL;
2512eb278e0SJohannes Berg }
2522eb278e0SJohannes Berg } else {
25350febf6aSJohannes Berg cfg80211_ready_on_channel(&roc->sdata->wdev, roc->cookie,
25442d97a59SJohannes Berg roc->chan, roc->req_duration,
25542d97a59SJohannes Berg GFP_KERNEL);
2562eb278e0SJohannes Berg }
2572eb278e0SJohannes Berg
2582eb278e0SJohannes Berg roc->notified = true;
2592eb278e0SJohannes Berg }
2602eb278e0SJohannes Berg
ieee80211_hw_roc_start(struct wiphy * wiphy,struct wiphy_work * work)26156dd205fSJohannes Berg static void ieee80211_hw_roc_start(struct wiphy *wiphy, struct wiphy_work *work)
26221f83589SJohannes Berg {
26321f83589SJohannes Berg struct ieee80211_local *local =
26421f83589SJohannes Berg container_of(work, struct ieee80211_local, hw_roc_start);
265aaa016ccSJohannes Berg struct ieee80211_roc_work *roc;
26621f83589SJohannes Berg
26721f83589SJohannes Berg mutex_lock(&local->mtx);
26821f83589SJohannes Berg
269aaa016ccSJohannes Berg list_for_each_entry(roc, &local->roc_list, list) {
2702eb278e0SJohannes Berg if (!roc->started)
271aaa016ccSJohannes Berg break;
2722eb278e0SJohannes Berg
273e6a8a3aaSJohannes Berg roc->hw_begun = true;
274aaa016ccSJohannes Berg ieee80211_handle_roc_started(roc, local->hw_roc_start_time);
2752eb278e0SJohannes Berg }
276aaa016ccSJohannes Berg
27721f83589SJohannes Berg mutex_unlock(&local->mtx);
27821f83589SJohannes Berg }
27921f83589SJohannes Berg
ieee80211_ready_on_channel(struct ieee80211_hw * hw)28021f83589SJohannes Berg void ieee80211_ready_on_channel(struct ieee80211_hw *hw)
28121f83589SJohannes Berg {
28221f83589SJohannes Berg struct ieee80211_local *local = hw_to_local(hw);
28321f83589SJohannes Berg
2842eb278e0SJohannes Berg local->hw_roc_start_time = jiffies;
2852eb278e0SJohannes Berg
28621f83589SJohannes Berg trace_api_ready_on_channel(local);
28721f83589SJohannes Berg
28856dd205fSJohannes Berg wiphy_work_queue(hw->wiphy, &local->hw_roc_start);
28921f83589SJohannes Berg }
29021f83589SJohannes Berg EXPORT_SYMBOL_GPL(ieee80211_ready_on_channel);
29121f83589SJohannes Berg
_ieee80211_start_next_roc(struct ieee80211_local * local)292aaa016ccSJohannes Berg static void _ieee80211_start_next_roc(struct ieee80211_local *local)
2932eb278e0SJohannes Berg {
294aaa016ccSJohannes Berg struct ieee80211_roc_work *roc, *tmp;
295aaa016ccSJohannes Berg enum ieee80211_roc_type type;
296aaa016ccSJohannes Berg u32 min_dur, max_dur;
2972eb278e0SJohannes Berg
2982eb278e0SJohannes Berg lockdep_assert_held(&local->mtx);
2992eb278e0SJohannes Berg
300aaa016ccSJohannes Berg if (WARN_ON(list_empty(&local->roc_list)))
3012eb278e0SJohannes Berg return;
3022eb278e0SJohannes Berg
3032eb278e0SJohannes Berg roc = list_first_entry(&local->roc_list, struct ieee80211_roc_work,
3042eb278e0SJohannes Berg list);
3052eb278e0SJohannes Berg
306aaa016ccSJohannes Berg if (WARN_ON(roc->started))
3070f6b3f59SJohannes Berg return;
3080f6b3f59SJohannes Berg
309aaa016ccSJohannes Berg min_dur = roc->duration;
310aaa016ccSJohannes Berg max_dur = roc->duration;
311aaa016ccSJohannes Berg type = roc->type;
312aaa016ccSJohannes Berg
313aaa016ccSJohannes Berg list_for_each_entry(tmp, &local->roc_list, list) {
314aaa016ccSJohannes Berg if (tmp == roc)
315aaa016ccSJohannes Berg continue;
316aaa016ccSJohannes Berg if (tmp->sdata != roc->sdata || tmp->chan != roc->chan)
317aaa016ccSJohannes Berg break;
318aaa016ccSJohannes Berg max_dur = max(tmp->duration, max_dur);
319aaa016ccSJohannes Berg min_dur = min(tmp->duration, min_dur);
320aaa016ccSJohannes Berg type = max(tmp->type, type);
321aaa016ccSJohannes Berg }
322aaa016ccSJohannes Berg
3232eb278e0SJohannes Berg if (local->ops->remain_on_channel) {
324aaa016ccSJohannes Berg int ret = drv_remain_on_channel(local, roc->sdata, roc->chan,
325aaa016ccSJohannes Berg max_dur, type);
3262eb278e0SJohannes Berg
3272eb278e0SJohannes Berg if (ret) {
3282eb278e0SJohannes Berg wiphy_warn(local->hw.wiphy,
3292eb278e0SJohannes Berg "failed to start next HW ROC (%d)\n", ret);
3302eb278e0SJohannes Berg /*
3312eb278e0SJohannes Berg * queue the work struct again to avoid recursion
3322eb278e0SJohannes Berg * when multiple failures occur
3332eb278e0SJohannes Berg */
334aaa016ccSJohannes Berg list_for_each_entry(tmp, &local->roc_list, list) {
335aaa016ccSJohannes Berg if (tmp->sdata != roc->sdata ||
336aaa016ccSJohannes Berg tmp->chan != roc->chan)
337aaa016ccSJohannes Berg break;
338aaa016ccSJohannes Berg tmp->started = true;
339aaa016ccSJohannes Berg tmp->abort = true;
340aaa016ccSJohannes Berg }
34156dd205fSJohannes Berg wiphy_work_queue(local->hw.wiphy, &local->hw_roc_done);
342aaa016ccSJohannes Berg return;
343aaa016ccSJohannes Berg }
344aaa016ccSJohannes Berg
345aaa016ccSJohannes Berg /* we'll notify about the start once the HW calls back */
346aaa016ccSJohannes Berg list_for_each_entry(tmp, &local->roc_list, list) {
347aaa016ccSJohannes Berg if (tmp->sdata != roc->sdata || tmp->chan != roc->chan)
348aaa016ccSJohannes Berg break;
349aaa016ccSJohannes Berg tmp->started = true;
3502eb278e0SJohannes Berg }
3512eb278e0SJohannes Berg } else {
352b4b177a5SJohannes Berg /* If actually operating on the desired channel (with at least
353b4b177a5SJohannes Berg * 20 MHz channel width) don't stop all the operations but still
354b4b177a5SJohannes Berg * treat it as though the ROC operation started properly, so
355b4b177a5SJohannes Berg * other ROC operations won't interfere with this one.
356b4b177a5SJohannes Berg */
357b4b177a5SJohannes Berg roc->on_channel = roc->chan == local->_oper_chandef.chan &&
358b4b177a5SJohannes Berg local->_oper_chandef.width != NL80211_CHAN_WIDTH_5 &&
359b4b177a5SJohannes Berg local->_oper_chandef.width != NL80211_CHAN_WIDTH_10;
360b4b177a5SJohannes Berg
361b4b177a5SJohannes Berg /* start this ROC */
3622eb278e0SJohannes Berg ieee80211_recalc_idle(local);
3632eb278e0SJohannes Berg
364b4b177a5SJohannes Berg if (!roc->on_channel) {
365b4b177a5SJohannes Berg ieee80211_offchannel_stop_vifs(local);
366b4b177a5SJohannes Berg
3672eb278e0SJohannes Berg local->tmp_channel = roc->chan;
3682eb278e0SJohannes Berg ieee80211_hw_config(local, 0);
369b4b177a5SJohannes Berg }
3702eb278e0SJohannes Berg
37156dd205fSJohannes Berg wiphy_delayed_work_queue(local->hw.wiphy, &local->roc_work,
372aaa016ccSJohannes Berg msecs_to_jiffies(min_dur));
3732eb278e0SJohannes Berg
374aaa016ccSJohannes Berg /* tell userspace or send frame(s) */
375aaa016ccSJohannes Berg list_for_each_entry(tmp, &local->roc_list, list) {
376aaa016ccSJohannes Berg if (tmp->sdata != roc->sdata || tmp->chan != roc->chan)
377aaa016ccSJohannes Berg break;
3782eb278e0SJohannes Berg
379aaa016ccSJohannes Berg tmp->on_channel = roc->on_channel;
380aaa016ccSJohannes Berg ieee80211_handle_roc_started(tmp, jiffies);
381aaa016ccSJohannes Berg }
382aaa016ccSJohannes Berg }
383aaa016ccSJohannes Berg }
384aaa016ccSJohannes Berg
ieee80211_start_next_roc(struct ieee80211_local * local)385aaa016ccSJohannes Berg void ieee80211_start_next_roc(struct ieee80211_local *local)
386aaa016ccSJohannes Berg {
387aaa016ccSJohannes Berg struct ieee80211_roc_work *roc;
388aaa016ccSJohannes Berg
389aaa016ccSJohannes Berg lockdep_assert_held(&local->mtx);
390aaa016ccSJohannes Berg
391aaa016ccSJohannes Berg if (list_empty(&local->roc_list)) {
392aaa016ccSJohannes Berg ieee80211_run_deferred_scan(local);
393aaa016ccSJohannes Berg return;
394aaa016ccSJohannes Berg }
395aaa016ccSJohannes Berg
396470f4d61SEliad Peller /* defer roc if driver is not started (i.e. during reconfig) */
397470f4d61SEliad Peller if (local->in_reconfig)
398470f4d61SEliad Peller return;
399470f4d61SEliad Peller
400aaa016ccSJohannes Berg roc = list_first_entry(&local->roc_list, struct ieee80211_roc_work,
401aaa016ccSJohannes Berg list);
402aaa016ccSJohannes Berg
403aaa016ccSJohannes Berg if (WARN_ON_ONCE(roc->started))
404aaa016ccSJohannes Berg return;
405aaa016ccSJohannes Berg
406aaa016ccSJohannes Berg if (local->ops->remain_on_channel) {
407aaa016ccSJohannes Berg _ieee80211_start_next_roc(local);
4082eb278e0SJohannes Berg } else {
409aaa016ccSJohannes Berg /* delay it a bit */
41056dd205fSJohannes Berg wiphy_delayed_work_queue(local->hw.wiphy, &local->roc_work,
411aaa016ccSJohannes Berg round_jiffies_relative(HZ / 2));
412aaa016ccSJohannes Berg }
413aaa016ccSJohannes Berg }
4142eb278e0SJohannes Berg
__ieee80211_roc_work(struct ieee80211_local * local)415aaa016ccSJohannes Berg static void __ieee80211_roc_work(struct ieee80211_local *local)
416aaa016ccSJohannes Berg {
417aaa016ccSJohannes Berg struct ieee80211_roc_work *roc;
418aaa016ccSJohannes Berg bool on_channel;
419aaa016ccSJohannes Berg
420aaa016ccSJohannes Berg lockdep_assert_held(&local->mtx);
421aaa016ccSJohannes Berg
422aaa016ccSJohannes Berg if (WARN_ON(local->ops->remain_on_channel))
423aaa016ccSJohannes Berg return;
424aaa016ccSJohannes Berg
425aaa016ccSJohannes Berg roc = list_first_entry_or_null(&local->roc_list,
426aaa016ccSJohannes Berg struct ieee80211_roc_work, list);
427aaa016ccSJohannes Berg if (!roc)
428aaa016ccSJohannes Berg return;
429aaa016ccSJohannes Berg
430aaa016ccSJohannes Berg if (!roc->started) {
431aaa016ccSJohannes Berg WARN_ON(local->use_chanctx);
432aaa016ccSJohannes Berg _ieee80211_start_next_roc(local);
433aaa016ccSJohannes Berg } else {
434aaa016ccSJohannes Berg on_channel = roc->on_channel;
435aaa016ccSJohannes Berg if (ieee80211_recalc_sw_work(local, jiffies))
436aaa016ccSJohannes Berg return;
437aaa016ccSJohannes Berg
438aaa016ccSJohannes Berg /* careful - roc pointer became invalid during recalc */
439aaa016ccSJohannes Berg
440aaa016ccSJohannes Berg if (!on_channel) {
4413b24f4c6SEmmanuel Grumbach ieee80211_flush_queues(local, NULL, false);
4422eb278e0SJohannes Berg
4432eb278e0SJohannes Berg local->tmp_channel = NULL;
4442eb278e0SJohannes Berg ieee80211_hw_config(local, 0);
4452eb278e0SJohannes Berg
446aacde9eeSStanislaw Gruszka ieee80211_offchannel_return(local);
4472eb278e0SJohannes Berg }
4482eb278e0SJohannes Berg
4492eb278e0SJohannes Berg ieee80211_recalc_idle(local);
4502eb278e0SJohannes Berg ieee80211_start_next_roc(local);
451aaa016ccSJohannes Berg }
4522eb278e0SJohannes Berg }
4532eb278e0SJohannes Berg
ieee80211_roc_work(struct wiphy * wiphy,struct wiphy_work * work)45456dd205fSJohannes Berg static void ieee80211_roc_work(struct wiphy *wiphy, struct wiphy_work *work)
455aaa016ccSJohannes Berg {
456aaa016ccSJohannes Berg struct ieee80211_local *local =
457aaa016ccSJohannes Berg container_of(work, struct ieee80211_local, roc_work.work);
458aaa016ccSJohannes Berg
459aaa016ccSJohannes Berg mutex_lock(&local->mtx);
460aaa016ccSJohannes Berg __ieee80211_roc_work(local);
4612eb278e0SJohannes Berg mutex_unlock(&local->mtx);
4622eb278e0SJohannes Berg }
4632eb278e0SJohannes Berg
ieee80211_hw_roc_done(struct wiphy * wiphy,struct wiphy_work * work)46456dd205fSJohannes Berg static void ieee80211_hw_roc_done(struct wiphy *wiphy, struct wiphy_work *work)
46521f83589SJohannes Berg {
46621f83589SJohannes Berg struct ieee80211_local *local =
46721f83589SJohannes Berg container_of(work, struct ieee80211_local, hw_roc_done);
46821f83589SJohannes Berg
46921f83589SJohannes Berg mutex_lock(&local->mtx);
47021f83589SJohannes Berg
471aaa016ccSJohannes Berg ieee80211_end_finished_rocs(local, jiffies);
47271ecfa18SJohannes Berg
4732eb278e0SJohannes Berg /* if there's another roc, start it now */
4742eb278e0SJohannes Berg ieee80211_start_next_roc(local);
47521f83589SJohannes Berg
47621f83589SJohannes Berg mutex_unlock(&local->mtx);
47721f83589SJohannes Berg }
47821f83589SJohannes Berg
ieee80211_remain_on_channel_expired(struct ieee80211_hw * hw)47921f83589SJohannes Berg void ieee80211_remain_on_channel_expired(struct ieee80211_hw *hw)
48021f83589SJohannes Berg {
48121f83589SJohannes Berg struct ieee80211_local *local = hw_to_local(hw);
48221f83589SJohannes Berg
48321f83589SJohannes Berg trace_api_remain_on_channel_expired(local);
48421f83589SJohannes Berg
48556dd205fSJohannes Berg wiphy_work_queue(hw->wiphy, &local->hw_roc_done);
48621f83589SJohannes Berg }
48721f83589SJohannes Berg EXPORT_SYMBOL_GPL(ieee80211_remain_on_channel_expired);
48821f83589SJohannes Berg
489aaa016ccSJohannes Berg static bool
ieee80211_coalesce_hw_started_roc(struct ieee80211_local * local,struct ieee80211_roc_work * new_roc,struct ieee80211_roc_work * cur_roc)490aaa016ccSJohannes Berg ieee80211_coalesce_hw_started_roc(struct ieee80211_local *local,
491a2fcfccbSJohannes Berg struct ieee80211_roc_work *new_roc,
492a2fcfccbSJohannes Berg struct ieee80211_roc_work *cur_roc)
493a2fcfccbSJohannes Berg {
494a2fcfccbSJohannes Berg unsigned long now = jiffies;
495aaa016ccSJohannes Berg unsigned long remaining;
496aaa016ccSJohannes Berg
497aaa016ccSJohannes Berg if (WARN_ON(!cur_roc->started))
498aaa016ccSJohannes Berg return false;
499aaa016ccSJohannes Berg
500aaa016ccSJohannes Berg /* if it was scheduled in the hardware, but not started yet,
501aaa016ccSJohannes Berg * we can only combine if the older one had a longer duration
502aaa016ccSJohannes Berg */
503aaa016ccSJohannes Berg if (!cur_roc->hw_begun && new_roc->duration > cur_roc->duration)
504aaa016ccSJohannes Berg return false;
505aaa016ccSJohannes Berg
506aaa016ccSJohannes Berg remaining = cur_roc->start_time +
507a2fcfccbSJohannes Berg msecs_to_jiffies(cur_roc->duration) -
508a2fcfccbSJohannes Berg now;
509a2fcfccbSJohannes Berg
510a2fcfccbSJohannes Berg /* if it doesn't fit entirely, schedule a new one */
511a2fcfccbSJohannes Berg if (new_roc->duration > jiffies_to_msecs(remaining))
512a2fcfccbSJohannes Berg return false;
513a2fcfccbSJohannes Berg
514aaa016ccSJohannes Berg /* add just after the current one so we combine their finish later */
515aaa016ccSJohannes Berg list_add(&new_roc->list, &cur_roc->list);
516a2fcfccbSJohannes Berg
517aaa016ccSJohannes Berg /* if the existing one has already begun then let this one also
518aaa016ccSJohannes Berg * begin, otherwise they'll both be marked properly by the work
519aaa016ccSJohannes Berg * struct that runs once the driver notifies us of the beginning
520aaa016ccSJohannes Berg */
521e6a8a3aaSJohannes Berg if (cur_roc->hw_begun) {
522e6a8a3aaSJohannes Berg new_roc->hw_begun = true;
523aaa016ccSJohannes Berg ieee80211_handle_roc_started(new_roc, now);
524e6a8a3aaSJohannes Berg }
525aaa016ccSJohannes Berg
526a2fcfccbSJohannes Berg return true;
527a2fcfccbSJohannes Berg }
528a2fcfccbSJohannes Berg
ieee80211_start_roc_work(struct ieee80211_local * local,struct ieee80211_sub_if_data * sdata,struct ieee80211_channel * channel,unsigned int duration,u64 * cookie,struct sk_buff * txskb,enum ieee80211_roc_type type)529a2fcfccbSJohannes Berg static int ieee80211_start_roc_work(struct ieee80211_local *local,
530a2fcfccbSJohannes Berg struct ieee80211_sub_if_data *sdata,
531a2fcfccbSJohannes Berg struct ieee80211_channel *channel,
532a2fcfccbSJohannes Berg unsigned int duration, u64 *cookie,
533a2fcfccbSJohannes Berg struct sk_buff *txskb,
534a2fcfccbSJohannes Berg enum ieee80211_roc_type type)
535a2fcfccbSJohannes Berg {
536a2fcfccbSJohannes Berg struct ieee80211_roc_work *roc, *tmp;
537aaa016ccSJohannes Berg bool queued = false, combine_started = true;
538a2fcfccbSJohannes Berg int ret;
539a2fcfccbSJohannes Berg
540a2fcfccbSJohannes Berg lockdep_assert_held(&local->mtx);
541a2fcfccbSJohannes Berg
542b6011960SThomas Pedersen if (channel->freq_offset)
543b6011960SThomas Pedersen /* this may work, but is untested */
544b6011960SThomas Pedersen return -EOPNOTSUPP;
545b6011960SThomas Pedersen
546a2fcfccbSJohannes Berg if (local->use_chanctx && !local->ops->remain_on_channel)
547a2fcfccbSJohannes Berg return -EOPNOTSUPP;
548a2fcfccbSJohannes Berg
549a2fcfccbSJohannes Berg roc = kzalloc(sizeof(*roc), GFP_KERNEL);
550a2fcfccbSJohannes Berg if (!roc)
551a2fcfccbSJohannes Berg return -ENOMEM;
552a2fcfccbSJohannes Berg
553a2fcfccbSJohannes Berg /*
554a2fcfccbSJohannes Berg * If the duration is zero, then the driver
555a2fcfccbSJohannes Berg * wouldn't actually do anything. Set it to
556a2fcfccbSJohannes Berg * 10 for now.
557a2fcfccbSJohannes Berg *
558a2fcfccbSJohannes Berg * TODO: cancel the off-channel operation
559a2fcfccbSJohannes Berg * when we get the SKB's TX status and
560a2fcfccbSJohannes Berg * the wait time was zero before.
561a2fcfccbSJohannes Berg */
562a2fcfccbSJohannes Berg if (!duration)
563a2fcfccbSJohannes Berg duration = 10;
564a2fcfccbSJohannes Berg
565a2fcfccbSJohannes Berg roc->chan = channel;
566a2fcfccbSJohannes Berg roc->duration = duration;
567a2fcfccbSJohannes Berg roc->req_duration = duration;
568a2fcfccbSJohannes Berg roc->frame = txskb;
569a2fcfccbSJohannes Berg roc->type = type;
570a2fcfccbSJohannes Berg roc->sdata = sdata;
571a2fcfccbSJohannes Berg
572a2fcfccbSJohannes Berg /*
573a2fcfccbSJohannes Berg * cookie is either the roc cookie (for normal roc)
574a2fcfccbSJohannes Berg * or the SKB (for mgmt TX)
575a2fcfccbSJohannes Berg */
576a2fcfccbSJohannes Berg if (!txskb) {
577a2fcfccbSJohannes Berg roc->cookie = ieee80211_mgmt_tx_cookie(local);
578a2fcfccbSJohannes Berg *cookie = roc->cookie;
579a2fcfccbSJohannes Berg } else {
580a2fcfccbSJohannes Berg roc->mgmt_tx_cookie = *cookie;
581a2fcfccbSJohannes Berg }
582a2fcfccbSJohannes Berg
583aaa016ccSJohannes Berg /* if there's no need to queue, handle it immediately */
584aaa016ccSJohannes Berg if (list_empty(&local->roc_list) &&
585aaa016ccSJohannes Berg !local->scanning && !ieee80211_is_radar_required(local)) {
586a2fcfccbSJohannes Berg /* if not HW assist, just queue & schedule work */
587a2fcfccbSJohannes Berg if (!local->ops->remain_on_channel) {
588aaa016ccSJohannes Berg list_add_tail(&roc->list, &local->roc_list);
58956dd205fSJohannes Berg wiphy_delayed_work_queue(local->hw.wiphy,
590aaa016ccSJohannes Berg &local->roc_work, 0);
591aaa016ccSJohannes Berg } else {
592aaa016ccSJohannes Berg /* otherwise actually kick it off here
593aaa016ccSJohannes Berg * (for error handling)
594aaa016ccSJohannes Berg */
595aaa016ccSJohannes Berg ret = drv_remain_on_channel(local, sdata, channel,
596aaa016ccSJohannes Berg duration, type);
597a2fcfccbSJohannes Berg if (ret) {
598a2fcfccbSJohannes Berg kfree(roc);
599a2fcfccbSJohannes Berg return ret;
600a2fcfccbSJohannes Berg }
601a2fcfccbSJohannes Berg roc->started = true;
602aaa016ccSJohannes Berg list_add_tail(&roc->list, &local->roc_list);
603aaa016ccSJohannes Berg }
604a2fcfccbSJohannes Berg
605aaa016ccSJohannes Berg return 0;
606aaa016ccSJohannes Berg }
607aaa016ccSJohannes Berg
608aaa016ccSJohannes Berg /* otherwise handle queueing */
609aaa016ccSJohannes Berg
610a2fcfccbSJohannes Berg list_for_each_entry(tmp, &local->roc_list, list) {
611a2fcfccbSJohannes Berg if (tmp->chan != channel || tmp->sdata != sdata)
612a2fcfccbSJohannes Berg continue;
613a2fcfccbSJohannes Berg
614a2fcfccbSJohannes Berg /*
615aaa016ccSJohannes Berg * Extend this ROC if possible: If it hasn't started, add
616aaa016ccSJohannes Berg * just after the new one to combine.
617a2fcfccbSJohannes Berg */
618a2fcfccbSJohannes Berg if (!tmp->started) {
619aaa016ccSJohannes Berg list_add(&roc->list, &tmp->list);
620a2fcfccbSJohannes Berg queued = true;
621a2fcfccbSJohannes Berg break;
622a2fcfccbSJohannes Berg }
623a2fcfccbSJohannes Berg
624aaa016ccSJohannes Berg if (!combine_started)
625aaa016ccSJohannes Berg continue;
626aaa016ccSJohannes Berg
627aaa016ccSJohannes Berg if (!local->ops->remain_on_channel) {
628aaa016ccSJohannes Berg /* If there's no hardware remain-on-channel, and
629aaa016ccSJohannes Berg * doing so won't push us over the maximum r-o-c
630aaa016ccSJohannes Berg * we allow, then we can just add the new one to
631aaa016ccSJohannes Berg * the list and mark it as having started now.
632aaa016ccSJohannes Berg * If it would push over the limit, don't try to
633aaa016ccSJohannes Berg * combine with other started ones (that haven't
634aaa016ccSJohannes Berg * been running as long) but potentially sort it
635aaa016ccSJohannes Berg * with others that had the same fate.
636a2fcfccbSJohannes Berg */
637aaa016ccSJohannes Berg unsigned long now = jiffies;
638aaa016ccSJohannes Berg u32 elapsed = jiffies_to_msecs(now - tmp->start_time);
639aaa016ccSJohannes Berg struct wiphy *wiphy = local->hw.wiphy;
640aaa016ccSJohannes Berg u32 max_roc = wiphy->max_remain_on_channel_duration;
641aaa016ccSJohannes Berg
642aaa016ccSJohannes Berg if (elapsed + roc->duration > max_roc) {
643aaa016ccSJohannes Berg combine_started = false;
644aaa016ccSJohannes Berg continue;
645aaa016ccSJohannes Berg }
646aaa016ccSJohannes Berg
647aaa016ccSJohannes Berg list_add(&roc->list, &tmp->list);
648a2fcfccbSJohannes Berg queued = true;
649aaa016ccSJohannes Berg roc->on_channel = tmp->on_channel;
650aaa016ccSJohannes Berg ieee80211_handle_roc_started(roc, now);
651e9db4557SJohannes Berg ieee80211_recalc_sw_work(local, now);
652a2fcfccbSJohannes Berg break;
653a2fcfccbSJohannes Berg }
654a2fcfccbSJohannes Berg
655aaa016ccSJohannes Berg queued = ieee80211_coalesce_hw_started_roc(local, roc, tmp);
656aaa016ccSJohannes Berg if (queued)
657aaa016ccSJohannes Berg break;
658aaa016ccSJohannes Berg /* if it wasn't queued, perhaps it can be combined with
659aaa016ccSJohannes Berg * another that also couldn't get combined previously,
660aaa016ccSJohannes Berg * but no need to check for already started ones, since
661aaa016ccSJohannes Berg * that can't work.
662a2fcfccbSJohannes Berg */
663aaa016ccSJohannes Berg combine_started = false;
664a2fcfccbSJohannes Berg }
665a2fcfccbSJohannes Berg
666a2fcfccbSJohannes Berg if (!queued)
667a2fcfccbSJohannes Berg list_add_tail(&roc->list, &local->roc_list);
668a2fcfccbSJohannes Berg
669a2fcfccbSJohannes Berg return 0;
670a2fcfccbSJohannes Berg }
671a2fcfccbSJohannes Berg
ieee80211_remain_on_channel(struct wiphy * wiphy,struct wireless_dev * wdev,struct ieee80211_channel * chan,unsigned int duration,u64 * cookie)672a2fcfccbSJohannes Berg int ieee80211_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev,
673a2fcfccbSJohannes Berg struct ieee80211_channel *chan,
674a2fcfccbSJohannes Berg unsigned int duration, u64 *cookie)
675a2fcfccbSJohannes Berg {
676a2fcfccbSJohannes Berg struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
677a2fcfccbSJohannes Berg struct ieee80211_local *local = sdata->local;
678a2fcfccbSJohannes Berg int ret;
679a2fcfccbSJohannes Berg
680a2fcfccbSJohannes Berg mutex_lock(&local->mtx);
681a2fcfccbSJohannes Berg ret = ieee80211_start_roc_work(local, sdata, chan,
682a2fcfccbSJohannes Berg duration, cookie, NULL,
683a2fcfccbSJohannes Berg IEEE80211_ROC_TYPE_NORMAL);
684a2fcfccbSJohannes Berg mutex_unlock(&local->mtx);
685a2fcfccbSJohannes Berg
686a2fcfccbSJohannes Berg return ret;
687a2fcfccbSJohannes Berg }
688a2fcfccbSJohannes Berg
ieee80211_cancel_roc(struct ieee80211_local * local,u64 cookie,bool mgmt_tx)689a2fcfccbSJohannes Berg static int ieee80211_cancel_roc(struct ieee80211_local *local,
690a2fcfccbSJohannes Berg u64 cookie, bool mgmt_tx)
691a2fcfccbSJohannes Berg {
692a2fcfccbSJohannes Berg struct ieee80211_roc_work *roc, *tmp, *found = NULL;
693a2fcfccbSJohannes Berg int ret;
694a2fcfccbSJohannes Berg
6957d37fcd4SJohannes Berg if (!cookie)
6967d37fcd4SJohannes Berg return -ENOENT;
6977d37fcd4SJohannes Berg
69856dd205fSJohannes Berg wiphy_work_flush(local->hw.wiphy, &local->hw_roc_start);
6996e46d8ceSAvraham Stern
700a2fcfccbSJohannes Berg mutex_lock(&local->mtx);
701a2fcfccbSJohannes Berg list_for_each_entry_safe(roc, tmp, &local->roc_list, list) {
702a2fcfccbSJohannes Berg if (!mgmt_tx && roc->cookie != cookie)
703a2fcfccbSJohannes Berg continue;
704a2fcfccbSJohannes Berg else if (mgmt_tx && roc->mgmt_tx_cookie != cookie)
705a2fcfccbSJohannes Berg continue;
706a2fcfccbSJohannes Berg
707a2fcfccbSJohannes Berg found = roc;
708a2fcfccbSJohannes Berg break;
709a2fcfccbSJohannes Berg }
710a2fcfccbSJohannes Berg
711a2fcfccbSJohannes Berg if (!found) {
712a2fcfccbSJohannes Berg mutex_unlock(&local->mtx);
713a2fcfccbSJohannes Berg return -ENOENT;
714a2fcfccbSJohannes Berg }
715a2fcfccbSJohannes Berg
716aaa016ccSJohannes Berg if (!found->started) {
717aaa016ccSJohannes Berg ieee80211_roc_notify_destroy(found);
718aaa016ccSJohannes Berg goto out_unlock;
719aaa016ccSJohannes Berg }
720a2fcfccbSJohannes Berg
721a2fcfccbSJohannes Berg if (local->ops->remain_on_channel) {
7225db4c4b9SEmmanuel Grumbach ret = drv_cancel_remain_on_channel(local, roc->sdata);
723a2fcfccbSJohannes Berg if (WARN_ON_ONCE(ret)) {
724a2fcfccbSJohannes Berg mutex_unlock(&local->mtx);
725a2fcfccbSJohannes Berg return ret;
726a2fcfccbSJohannes Berg }
727aaa016ccSJohannes Berg
728aaa016ccSJohannes Berg /* TODO:
729aaa016ccSJohannes Berg * if multiple items were combined here then we really shouldn't
730aaa016ccSJohannes Berg * cancel them all - we should wait for as much time as needed
731aaa016ccSJohannes Berg * for the longest remaining one, and only then cancel ...
732aaa016ccSJohannes Berg */
733aaa016ccSJohannes Berg list_for_each_entry_safe(roc, tmp, &local->roc_list, list) {
734aaa016ccSJohannes Berg if (!roc->started)
735aaa016ccSJohannes Berg break;
736aaa016ccSJohannes Berg if (roc == found)
737aaa016ccSJohannes Berg found = NULL;
738aaa016ccSJohannes Berg ieee80211_roc_notify_destroy(roc);
739a2fcfccbSJohannes Berg }
740a2fcfccbSJohannes Berg
741aaa016ccSJohannes Berg /* that really must not happen - it was started */
742aaa016ccSJohannes Berg WARN_ON(found);
743a2fcfccbSJohannes Berg
744a2fcfccbSJohannes Berg ieee80211_start_next_roc(local);
745a2fcfccbSJohannes Berg } else {
746aaa016ccSJohannes Berg /* go through work struct to return to the operating channel */
747a2fcfccbSJohannes Berg found->abort = true;
74856dd205fSJohannes Berg wiphy_delayed_work_queue(local->hw.wiphy, &local->roc_work, 0);
749a2fcfccbSJohannes Berg }
750a2fcfccbSJohannes Berg
751aaa016ccSJohannes Berg out_unlock:
752aaa016ccSJohannes Berg mutex_unlock(&local->mtx);
753aaa016ccSJohannes Berg
754a2fcfccbSJohannes Berg return 0;
755a2fcfccbSJohannes Berg }
756a2fcfccbSJohannes Berg
ieee80211_cancel_remain_on_channel(struct wiphy * wiphy,struct wireless_dev * wdev,u64 cookie)757a2fcfccbSJohannes Berg int ieee80211_cancel_remain_on_channel(struct wiphy *wiphy,
758a2fcfccbSJohannes Berg struct wireless_dev *wdev, u64 cookie)
759a2fcfccbSJohannes Berg {
760a2fcfccbSJohannes Berg struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
761a2fcfccbSJohannes Berg struct ieee80211_local *local = sdata->local;
762a2fcfccbSJohannes Berg
763a2fcfccbSJohannes Berg return ieee80211_cancel_roc(local, cookie, false);
764a2fcfccbSJohannes Berg }
765a2fcfccbSJohannes Berg
ieee80211_mgmt_tx(struct wiphy * wiphy,struct wireless_dev * wdev,struct cfg80211_mgmt_tx_params * params,u64 * cookie)766a2fcfccbSJohannes Berg int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
767a2fcfccbSJohannes Berg struct cfg80211_mgmt_tx_params *params, u64 *cookie)
768a2fcfccbSJohannes Berg {
769a2fcfccbSJohannes Berg struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
770a2fcfccbSJohannes Berg struct ieee80211_local *local = sdata->local;
7715ee00dbdSJohannes Berg struct sk_buff *skb;
772e1e68b14SJohannes Berg struct sta_info *sta = NULL;
773a2fcfccbSJohannes Berg const struct ieee80211_mgmt *mgmt = (void *)params->buf;
774a2fcfccbSJohannes Berg bool need_offchan = false;
775e1e68b14SJohannes Berg bool mlo_sta = false;
776e1e68b14SJohannes Berg int link_id = -1;
777a2fcfccbSJohannes Berg u32 flags;
778a2fcfccbSJohannes Berg int ret;
779a2fcfccbSJohannes Berg u8 *data;
780a2fcfccbSJohannes Berg
781a2fcfccbSJohannes Berg if (params->dont_wait_for_ack)
782a2fcfccbSJohannes Berg flags = IEEE80211_TX_CTL_NO_ACK;
783a2fcfccbSJohannes Berg else
784a2fcfccbSJohannes Berg flags = IEEE80211_TX_INTFL_NL80211_FRAME_TX |
785a2fcfccbSJohannes Berg IEEE80211_TX_CTL_REQ_TX_STATUS;
786a2fcfccbSJohannes Berg
787a2fcfccbSJohannes Berg if (params->no_cck)
788a2fcfccbSJohannes Berg flags |= IEEE80211_TX_CTL_NO_CCK_RATE;
789a2fcfccbSJohannes Berg
790a2fcfccbSJohannes Berg switch (sdata->vif.type) {
791a2fcfccbSJohannes Berg case NL80211_IFTYPE_ADHOC:
792f276e20bSJohannes Berg if (!sdata->vif.cfg.ibss_joined)
793a2fcfccbSJohannes Berg need_offchan = true;
794a2fcfccbSJohannes Berg #ifdef CONFIG_MAC80211_MESH
795fc0561dcSGustavo A. R. Silva fallthrough;
796a2fcfccbSJohannes Berg case NL80211_IFTYPE_MESH_POINT:
797a2fcfccbSJohannes Berg if (ieee80211_vif_is_mesh(&sdata->vif) &&
798a2fcfccbSJohannes Berg !sdata->u.mesh.mesh_id_len)
799a2fcfccbSJohannes Berg need_offchan = true;
800a2fcfccbSJohannes Berg #endif
801fc0561dcSGustavo A. R. Silva fallthrough;
802a2fcfccbSJohannes Berg case NL80211_IFTYPE_AP:
803a2fcfccbSJohannes Berg case NL80211_IFTYPE_AP_VLAN:
804a2fcfccbSJohannes Berg case NL80211_IFTYPE_P2P_GO:
805a2fcfccbSJohannes Berg if (sdata->vif.type != NL80211_IFTYPE_ADHOC &&
806a2fcfccbSJohannes Berg !ieee80211_vif_is_mesh(&sdata->vif) &&
807bfd8403aSJohannes Berg !sdata->bss->active)
808a2fcfccbSJohannes Berg need_offchan = true;
809e1e68b14SJohannes Berg
810e1e68b14SJohannes Berg rcu_read_lock();
811e1e68b14SJohannes Berg sta = sta_info_get_bss(sdata, mgmt->da);
812e1e68b14SJohannes Berg mlo_sta = sta && sta->sta.mlo;
813e1e68b14SJohannes Berg
814a2fcfccbSJohannes Berg if (!ieee80211_is_action(mgmt->frame_control) ||
815a2fcfccbSJohannes Berg mgmt->u.action.category == WLAN_CATEGORY_PUBLIC ||
816a2fcfccbSJohannes Berg mgmt->u.action.category == WLAN_CATEGORY_SELF_PROTECTED ||
817e1e68b14SJohannes Berg mgmt->u.action.category == WLAN_CATEGORY_SPECTRUM_MGMT) {
818a2fcfccbSJohannes Berg rcu_read_unlock();
819e1e68b14SJohannes Berg break;
820e1e68b14SJohannes Berg }
821e1e68b14SJohannes Berg
822e1e68b14SJohannes Berg if (!sta) {
823e1e68b14SJohannes Berg rcu_read_unlock();
824a2fcfccbSJohannes Berg return -ENOLINK;
825e1e68b14SJohannes Berg }
826e1e68b14SJohannes Berg if (params->link_id >= 0 &&
827e1e68b14SJohannes Berg !(sta->sta.valid_links & BIT(params->link_id))) {
828e1e68b14SJohannes Berg rcu_read_unlock();
829e1e68b14SJohannes Berg return -ENOLINK;
830e1e68b14SJohannes Berg }
831e1e68b14SJohannes Berg link_id = params->link_id;
832e1e68b14SJohannes Berg rcu_read_unlock();
833a2fcfccbSJohannes Berg break;
834a2fcfccbSJohannes Berg case NL80211_IFTYPE_STATION:
835a2fcfccbSJohannes Berg case NL80211_IFTYPE_P2P_CLIENT:
836a2fcfccbSJohannes Berg sdata_lock(sdata);
837a2fcfccbSJohannes Berg if (!sdata->u.mgd.associated ||
838a2fcfccbSJohannes Berg (params->offchan && params->wait &&
839a2fcfccbSJohannes Berg local->ops->remain_on_channel &&
840e1e68b14SJohannes Berg memcmp(sdata->vif.cfg.ap_addr, mgmt->bssid, ETH_ALEN)))
841a2fcfccbSJohannes Berg need_offchan = true;
842a2fcfccbSJohannes Berg sdata_unlock(sdata);
843a2fcfccbSJohannes Berg break;
844a2fcfccbSJohannes Berg case NL80211_IFTYPE_P2P_DEVICE:
845a2fcfccbSJohannes Berg need_offchan = true;
846a2fcfccbSJohannes Berg break;
847cb3b7d87SAyala Beker case NL80211_IFTYPE_NAN:
848a2fcfccbSJohannes Berg default:
849a2fcfccbSJohannes Berg return -EOPNOTSUPP;
850a2fcfccbSJohannes Berg }
851a2fcfccbSJohannes Berg
852a2fcfccbSJohannes Berg /* configurations requiring offchan cannot work if no channel has been
853a2fcfccbSJohannes Berg * specified
854a2fcfccbSJohannes Berg */
855a2fcfccbSJohannes Berg if (need_offchan && !params->chan)
856a2fcfccbSJohannes Berg return -EINVAL;
857a2fcfccbSJohannes Berg
858a2fcfccbSJohannes Berg mutex_lock(&local->mtx);
859a2fcfccbSJohannes Berg
860a2fcfccbSJohannes Berg /* Check if the operating channel is the requested channel */
861e1e68b14SJohannes Berg if (!params->chan && mlo_sta) {
862e1e68b14SJohannes Berg need_offchan = false;
863e1e68b14SJohannes Berg } else if (!need_offchan) {
86454283409SAndrei Otcheretianski struct ieee80211_chanctx_conf *chanctx_conf = NULL;
86554283409SAndrei Otcheretianski int i;
866a2fcfccbSJohannes Berg
867a2fcfccbSJohannes Berg rcu_read_lock();
86854283409SAndrei Otcheretianski /* Check all the links first */
86954283409SAndrei Otcheretianski for (i = 0; i < ARRAY_SIZE(sdata->vif.link_conf); i++) {
870d8675a63SJohannes Berg struct ieee80211_bss_conf *conf;
871d8675a63SJohannes Berg
872d8675a63SJohannes Berg conf = rcu_dereference(sdata->vif.link_conf[i]);
873d8675a63SJohannes Berg if (!conf)
87454283409SAndrei Otcheretianski continue;
87554283409SAndrei Otcheretianski
876d8675a63SJohannes Berg chanctx_conf = rcu_dereference(conf->chanctx_conf);
87754283409SAndrei Otcheretianski if (!chanctx_conf)
87854283409SAndrei Otcheretianski continue;
87954283409SAndrei Otcheretianski
880e1e68b14SJohannes Berg if (mlo_sta && params->chan == chanctx_conf->def.chan &&
881e1e68b14SJohannes Berg ether_addr_equal(sdata->vif.addr, mgmt->sa)) {
882e1e68b14SJohannes Berg link_id = i;
883e1e68b14SJohannes Berg break;
884e1e68b14SJohannes Berg }
885e1e68b14SJohannes Berg
886d8675a63SJohannes Berg if (ether_addr_equal(conf->addr, mgmt->sa))
88754283409SAndrei Otcheretianski break;
88854283409SAndrei Otcheretianski
88954283409SAndrei Otcheretianski chanctx_conf = NULL;
89054283409SAndrei Otcheretianski }
891a2fcfccbSJohannes Berg
892a2fcfccbSJohannes Berg if (chanctx_conf) {
893a2fcfccbSJohannes Berg need_offchan = params->chan &&
894a2fcfccbSJohannes Berg (params->chan !=
895a2fcfccbSJohannes Berg chanctx_conf->def.chan);
896a2fcfccbSJohannes Berg } else {
897a2fcfccbSJohannes Berg need_offchan = true;
898a2fcfccbSJohannes Berg }
899a2fcfccbSJohannes Berg rcu_read_unlock();
900a2fcfccbSJohannes Berg }
901a2fcfccbSJohannes Berg
902a2fcfccbSJohannes Berg if (need_offchan && !params->offchan) {
903a2fcfccbSJohannes Berg ret = -EBUSY;
904a2fcfccbSJohannes Berg goto out_unlock;
905a2fcfccbSJohannes Berg }
906a2fcfccbSJohannes Berg
907a2fcfccbSJohannes Berg skb = dev_alloc_skb(local->hw.extra_tx_headroom + params->len);
908a2fcfccbSJohannes Berg if (!skb) {
909a2fcfccbSJohannes Berg ret = -ENOMEM;
910a2fcfccbSJohannes Berg goto out_unlock;
911a2fcfccbSJohannes Berg }
912a2fcfccbSJohannes Berg skb_reserve(skb, local->hw.extra_tx_headroom);
913a2fcfccbSJohannes Berg
91459ae1d12SJohannes Berg data = skb_put_data(skb, params->buf, params->len);
915a2fcfccbSJohannes Berg
916a2fcfccbSJohannes Berg /* Update CSA counters */
917d0a9123eSJohannes Berg if (sdata->vif.bss_conf.csa_active &&
918a2fcfccbSJohannes Berg (sdata->vif.type == NL80211_IFTYPE_AP ||
919a2fcfccbSJohannes Berg sdata->vif.type == NL80211_IFTYPE_MESH_POINT ||
920a2fcfccbSJohannes Berg sdata->vif.type == NL80211_IFTYPE_ADHOC) &&
921a2fcfccbSJohannes Berg params->n_csa_offsets) {
922a2fcfccbSJohannes Berg int i;
923a2fcfccbSJohannes Berg struct beacon_data *beacon = NULL;
924a2fcfccbSJohannes Berg
925a2fcfccbSJohannes Berg rcu_read_lock();
926a2fcfccbSJohannes Berg
927a2fcfccbSJohannes Berg if (sdata->vif.type == NL80211_IFTYPE_AP)
928bfd8403aSJohannes Berg beacon = rcu_dereference(sdata->deflink.u.ap.beacon);
929a2fcfccbSJohannes Berg else if (sdata->vif.type == NL80211_IFTYPE_ADHOC)
930a2fcfccbSJohannes Berg beacon = rcu_dereference(sdata->u.ibss.presp);
931a2fcfccbSJohannes Berg else if (ieee80211_vif_is_mesh(&sdata->vif))
932a2fcfccbSJohannes Berg beacon = rcu_dereference(sdata->u.mesh.beacon);
933a2fcfccbSJohannes Berg
934a2fcfccbSJohannes Berg if (beacon)
935a2fcfccbSJohannes Berg for (i = 0; i < params->n_csa_offsets; i++)
936a2fcfccbSJohannes Berg data[params->csa_offsets[i]] =
9378552a434SJohn Crispin beacon->cntdwn_current_counter;
938a2fcfccbSJohannes Berg
939a2fcfccbSJohannes Berg rcu_read_unlock();
940a2fcfccbSJohannes Berg }
941a2fcfccbSJohannes Berg
942a2fcfccbSJohannes Berg IEEE80211_SKB_CB(skb)->flags = flags;
943*d54455a3SPing-Ke Shih IEEE80211_SKB_CB(skb)->control.flags |= IEEE80211_TX_CTRL_DONT_USE_RATE_MASK;
944a2fcfccbSJohannes Berg
945a2fcfccbSJohannes Berg skb->dev = sdata->dev;
946a2fcfccbSJohannes Berg
947a2fcfccbSJohannes Berg if (!params->dont_wait_for_ack) {
948a2fcfccbSJohannes Berg /* make a copy to preserve the frame contents
949a2fcfccbSJohannes Berg * in case of encryption.
950a2fcfccbSJohannes Berg */
9515ee00dbdSJohannes Berg ret = ieee80211_attach_ack_skb(local, skb, cookie, GFP_KERNEL);
9525ee00dbdSJohannes Berg if (ret) {
953a2fcfccbSJohannes Berg kfree_skb(skb);
954a2fcfccbSJohannes Berg goto out_unlock;
955a2fcfccbSJohannes Berg }
956a2fcfccbSJohannes Berg } else {
957a2fcfccbSJohannes Berg /* Assign a dummy non-zero cookie, it's not sent to
958a2fcfccbSJohannes Berg * userspace in this case but we rely on its value
959a2fcfccbSJohannes Berg * internally in the need_offchan case to distinguish
960a2fcfccbSJohannes Berg * mgmt-tx from remain-on-channel.
961a2fcfccbSJohannes Berg */
962a2fcfccbSJohannes Berg *cookie = 0xffffffff;
963a2fcfccbSJohannes Berg }
964a2fcfccbSJohannes Berg
965a2fcfccbSJohannes Berg if (!need_offchan) {
966e1e68b14SJohannes Berg ieee80211_tx_skb_tid(sdata, skb, 7, link_id);
967a2fcfccbSJohannes Berg ret = 0;
968a2fcfccbSJohannes Berg goto out_unlock;
969a2fcfccbSJohannes Berg }
970a2fcfccbSJohannes Berg
971a2fcfccbSJohannes Berg IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_CTL_TX_OFFCHAN |
972a2fcfccbSJohannes Berg IEEE80211_TX_INTFL_OFFCHAN_TX_OK;
973a2fcfccbSJohannes Berg if (ieee80211_hw_check(&local->hw, QUEUE_CONTROL))
974a2fcfccbSJohannes Berg IEEE80211_SKB_CB(skb)->hw_queue =
975a2fcfccbSJohannes Berg local->hw.offchannel_tx_hw_queue;
976a2fcfccbSJohannes Berg
977a2fcfccbSJohannes Berg /* This will handle all kinds of coalescing and immediate TX */
978a2fcfccbSJohannes Berg ret = ieee80211_start_roc_work(local, sdata, params->chan,
979a2fcfccbSJohannes Berg params->wait, cookie, skb,
980a2fcfccbSJohannes Berg IEEE80211_ROC_TYPE_MGMT_TX);
981a2fcfccbSJohannes Berg if (ret)
982a2fcfccbSJohannes Berg ieee80211_free_txskb(&local->hw, skb);
983a2fcfccbSJohannes Berg out_unlock:
984a2fcfccbSJohannes Berg mutex_unlock(&local->mtx);
985a2fcfccbSJohannes Berg return ret;
986a2fcfccbSJohannes Berg }
987a2fcfccbSJohannes Berg
ieee80211_mgmt_tx_cancel_wait(struct wiphy * wiphy,struct wireless_dev * wdev,u64 cookie)988a2fcfccbSJohannes Berg int ieee80211_mgmt_tx_cancel_wait(struct wiphy *wiphy,
989a2fcfccbSJohannes Berg struct wireless_dev *wdev, u64 cookie)
990a2fcfccbSJohannes Berg {
991a2fcfccbSJohannes Berg struct ieee80211_local *local = wiphy_priv(wiphy);
992a2fcfccbSJohannes Berg
993a2fcfccbSJohannes Berg return ieee80211_cancel_roc(local, cookie, true);
994a2fcfccbSJohannes Berg }
995a2fcfccbSJohannes Berg
ieee80211_roc_setup(struct ieee80211_local * local)9962eb278e0SJohannes Berg void ieee80211_roc_setup(struct ieee80211_local *local)
99721f83589SJohannes Berg {
99856dd205fSJohannes Berg wiphy_work_init(&local->hw_roc_start, ieee80211_hw_roc_start);
99956dd205fSJohannes Berg wiphy_work_init(&local->hw_roc_done, ieee80211_hw_roc_done);
100056dd205fSJohannes Berg wiphy_delayed_work_init(&local->roc_work, ieee80211_roc_work);
10012eb278e0SJohannes Berg INIT_LIST_HEAD(&local->roc_list);
10022eb278e0SJohannes Berg }
10032eb278e0SJohannes Berg
ieee80211_roc_purge(struct ieee80211_local * local,struct ieee80211_sub_if_data * sdata)1004c8f994eeSJohannes Berg void ieee80211_roc_purge(struct ieee80211_local *local,
1005c8f994eeSJohannes Berg struct ieee80211_sub_if_data *sdata)
10062eb278e0SJohannes Berg {
10072eb278e0SJohannes Berg struct ieee80211_roc_work *roc, *tmp;
1008aaa016ccSJohannes Berg bool work_to_do = false;
10092eb278e0SJohannes Berg
10102eb278e0SJohannes Berg mutex_lock(&local->mtx);
10112eb278e0SJohannes Berg list_for_each_entry_safe(roc, tmp, &local->roc_list, list) {
1012c8f994eeSJohannes Berg if (sdata && roc->sdata != sdata)
10132eb278e0SJohannes Berg continue;
10142eb278e0SJohannes Berg
1015aaa016ccSJohannes Berg if (roc->started) {
1016aaa016ccSJohannes Berg if (local->ops->remain_on_channel) {
10172eb278e0SJohannes Berg /* can race, so ignore return value */
1018c6968d4fSAnjaneyulu drv_cancel_remain_on_channel(local, roc->sdata);
1019aaa016ccSJohannes Berg ieee80211_roc_notify_destroy(roc);
10202eb278e0SJohannes Berg } else {
1021aaa016ccSJohannes Berg roc->abort = true;
1022aaa016ccSJohannes Berg work_to_do = true;
1023aaa016ccSJohannes Berg }
1024aaa016ccSJohannes Berg } else {
1025aaa016ccSJohannes Berg ieee80211_roc_notify_destroy(roc);
10262eb278e0SJohannes Berg }
10272eb278e0SJohannes Berg }
1028aaa016ccSJohannes Berg if (work_to_do)
1029aaa016ccSJohannes Berg __ieee80211_roc_work(local);
1030aaa016ccSJohannes Berg mutex_unlock(&local->mtx);
103121f83589SJohannes Berg }
1032