xref: /openbmc/linux/net/mac80211/offchannel.c (revision 34d6f206a88c2651d216bd3487ac956a40b2ba8e)
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