[openwrt/openwrt] mac80211: allow scanning operating on DFS channels

LEDE Commits lede-commits at lists.infradead.org
Sat May 31 07:24:03 PDT 2025


nbd pushed a commit to openwrt/openwrt.git, branch main:
https://git.openwrt.org/91d33d156349796c88f9e4fafd4de2581b2a2d8b

commit 91d33d156349796c88f9e4fafd4de2581b2a2d8b
Author: Felix Fietkau <nbd at nbd.name>
AuthorDate: Thu May 22 09:47:04 2025 +0200

    mac80211: allow scanning operating on DFS channels
    
    Only for multi-radio configurations where the scan would not affect the radio
    running DFS detection
    
    Signed-off-by: Felix Fietkau <nbd at nbd.name>
---
 ...211-allow-scanning-while-on-radar-channel.patch | 372 +++++++++++++++++++++
 1 file changed, 372 insertions(+)

diff --git a/package/kernel/mac80211/patches/subsys/350-mac80211-allow-scanning-while-on-radar-channel.patch b/package/kernel/mac80211/patches/subsys/350-mac80211-allow-scanning-while-on-radar-channel.patch
new file mode 100644
index 0000000000..d469955a90
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/350-mac80211-allow-scanning-while-on-radar-channel.patch
@@ -0,0 +1,372 @@
+--- a/net/mac80211/cfg.c
++++ b/net/mac80211/cfg.c
+@@ -3473,6 +3473,27 @@ static int ieee80211_set_bitrate_mask(st
+ 	return 0;
+ }
+ 
++bool ieee80211_scanning_busy(struct ieee80211_local *local,
++			     struct cfg80211_chan_def *chandef)
++{
++	struct cfg80211_scan_request *scan_req;
++	struct wiphy *wiphy = local->hw.wiphy;
++	u32 mask;
++
++	if (list_empty(&local->roc_list) && !local->scanning)
++		return false;
++
++	if (!wiphy->n_radio)
++		return true;
++
++	mask = ieee80211_offchannel_radio_mask(local);
++	scan_req = wiphy_dereference(wiphy, local->scan_req);
++	if (scan_req)
++		mask |= ieee80211_scan_req_radio_mask(local, scan_req);
++
++	return mask & ieee80211_chandef_radio_mask(local, chandef);
++}
++
+ static int ieee80211_start_radar_detection(struct wiphy *wiphy,
+ 					   struct net_device *dev,
+ 					   struct cfg80211_chan_def *chandef,
+@@ -3486,7 +3507,7 @@ static int ieee80211_start_radar_detecti
+ 
+ 	lockdep_assert_wiphy(local->hw.wiphy);
+ 
+-	if (!list_empty(&local->roc_list) || local->scanning)
++	if (ieee80211_scanning_busy(local, chandef))
+ 		return -EBUSY;
+ 
+ 	link_data = sdata_dereference(sdata->link[link_id], sdata);
+@@ -3978,7 +3999,7 @@ __ieee80211_channel_switch(struct wiphy
+ 
+ 	lockdep_assert_wiphy(local->hw.wiphy);
+ 
+-	if (!list_empty(&local->roc_list) || local->scanning)
++	if (ieee80211_scanning_busy(local, &params->chandef))
+ 		return -EBUSY;
+ 
+ 	if (sdata->wdev.links[link_id].cac_started)
+--- a/net/mac80211/chan.c
++++ b/net/mac80211/chan.c
+@@ -629,14 +629,24 @@ ieee80211_find_chanctx(struct ieee80211_
+ 	return NULL;
+ }
+ 
+-bool ieee80211_is_radar_required(struct ieee80211_local *local)
++bool ieee80211_is_radar_required(struct ieee80211_local *local, u32 radio_mask)
+ {
++	struct ieee80211_chanctx_conf *conf;
+ 	struct ieee80211_link_data *link;
+ 
+ 	lockdep_assert_wiphy(local->hw.wiphy);
+ 
+ 	for_each_sdata_link(local, link) {
+-		if (link->radar_required)
++		if (!link->radar_required)
++			continue;
++		if (!local->hw.wiphy->n_radio)
++			return true;
++
++		conf = wiphy_dereference(local->hw.wiphy, link->conf->chanctx_conf);
++		if (!conf)
++			continue;
++
++		if (conf->radio_idx >= 0 && (radio_mask & BIT(conf->radio_idx)))
+ 			return true;
+ 	}
+ 
+--- a/net/mac80211/ieee80211_i.h
++++ b/net/mac80211/ieee80211_i.h
+@@ -1947,6 +1947,12 @@ int ieee80211_mesh_finish_csa(struct iee
+ 			      u64 *changed);
+ 
+ /* scan/BSS handling */
++u32 ieee80211_scan_req_radio_mask(struct ieee80211_local *local,
++				  struct cfg80211_scan_request *req);
++bool ieee80211_scanning_busy(struct ieee80211_local *local,
++			     struct cfg80211_chan_def *chandef);
++u32 ieee80211_can_leave_ch(struct ieee80211_sub_if_data *sdata,
++			   u32 radio_mask);
+ void ieee80211_scan_work(struct wiphy *wiphy, struct wiphy_work *work);
+ int ieee80211_request_ibss_scan(struct ieee80211_sub_if_data *sdata,
+ 				const u8 *ssid, u8 ssid_len,
+@@ -1985,6 +1991,7 @@ void ieee80211_sched_scan_stopped_work(s
+ /* off-channel/mgmt-tx */
+ void ieee80211_offchannel_stop_vifs(struct ieee80211_local *local);
+ void ieee80211_offchannel_return(struct ieee80211_local *local);
++u32 ieee80211_offchannel_radio_mask(struct ieee80211_local *local);
+ void ieee80211_roc_setup(struct ieee80211_local *local);
+ void ieee80211_start_next_roc(struct ieee80211_local *local);
+ void ieee80211_reconfig_roc(struct ieee80211_local *local);
+@@ -2629,6 +2636,8 @@ bool ieee80211_chandef_s1g_oper(const st
+ 				struct cfg80211_chan_def *chandef);
+ void ieee80211_chandef_downgrade(struct cfg80211_chan_def *chandef,
+ 				 struct ieee80211_conn_settings *conn);
++u32 ieee80211_chandef_radio_mask(struct ieee80211_local *local,
++				 struct cfg80211_chan_def *chandef);
+ static inline void
+ ieee80211_chanreq_downgrade(struct ieee80211_chan_req *chanreq,
+ 			    struct ieee80211_conn_settings *conn)
+@@ -2685,7 +2694,7 @@ void ieee80211_recalc_chanctx_min_def(st
+ 				      struct ieee80211_chanctx *ctx,
+ 				      struct ieee80211_link_data *rsvd_for,
+ 				      bool check_reserved);
+-bool ieee80211_is_radar_required(struct ieee80211_local *local);
++bool ieee80211_is_radar_required(struct ieee80211_local *local, u32 radio_mask);
+ 
+ void ieee80211_dfs_cac_timer_work(struct wiphy *wiphy, struct wiphy_work *work);
+ void ieee80211_dfs_cac_cancel(struct ieee80211_local *local,
+--- a/net/mac80211/offchannel.c
++++ b/net/mac80211/offchannel.c
+@@ -168,6 +168,35 @@ void ieee80211_offchannel_return(struct
+ 					false);
+ }
+ 
++u32 ieee80211_offchannel_radio_mask(struct ieee80211_local *local)
++{
++	const struct wiphy_radio *radio;
++	struct ieee80211_roc_work *roc;
++	u32 mask = 0;
++	int r;
++
++	for (r = 0; r < local->hw.wiphy->n_radio; r++) {
++		radio = &local->hw.wiphy->radio[r];
++
++		list_for_each_entry(roc, &local->roc_list, list) {
++			struct cfg80211_chan_def chandef = {};
++
++			if (!roc->started)
++				continue;
++
++			cfg80211_chandef_create(&chandef, roc->chan,
++						NL80211_CHAN_NO_HT);
++			if (!cfg80211_radio_chandef_valid(radio, &chandef))
++				continue;
++
++			mask |= BIT(r);
++			break;
++		}
++	}
++
++	return mask;
++}
++
+ static void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc)
+ {
+ 	/* was never transmitted */
+@@ -566,7 +595,9 @@ static int ieee80211_start_roc_work(stru
+ 				    enum ieee80211_roc_type type)
+ {
+ 	struct ieee80211_roc_work *roc, *tmp;
++	struct cfg80211_chan_def chandef = {};
+ 	bool queued = false, combine_started = true;
++	u32 radio_mask;
+ 	int ret;
+ 
+ 	lockdep_assert_wiphy(local->hw.wiphy);
+@@ -578,6 +609,12 @@ static int ieee80211_start_roc_work(stru
+ 	if (!local->emulate_chanctx && !local->ops->remain_on_channel)
+ 		return -EOPNOTSUPP;
+ 
++	cfg80211_chandef_create(&chandef, channel, NL80211_CHAN_NO_HT);
++	radio_mask = ieee80211_chandef_radio_mask(local, &chandef);
++	if (!ieee80211_can_leave_ch(sdata, radio_mask) &&
++	    !ieee80211_scanning_busy(local, &chandef))
++		return -EBUSY;
++
+ 	roc = kzalloc(sizeof(*roc), GFP_KERNEL);
+ 	if (!roc)
+ 		return -ENOMEM;
+@@ -613,8 +650,7 @@ static int ieee80211_start_roc_work(stru
+ 	}
+ 
+ 	/* if there's no need to queue, handle it immediately */
+-	if (list_empty(&local->roc_list) &&
+-	    !local->scanning && !ieee80211_is_radar_required(local)) {
++	if (list_empty(&local->roc_list) && !local->scanning) {
+ 		/* if not HW assist, just queue & schedule work */
+ 		if (!local->ops->remain_on_channel) {
+ 			list_add_tail(&roc->list, &local->roc_list);
+--- a/net/mac80211/scan.c
++++ b/net/mac80211/scan.c
+@@ -571,36 +571,83 @@ static int ieee80211_start_sw_scan(struc
+ 	return 0;
+ }
+ 
+-static bool __ieee80211_can_leave_ch(struct ieee80211_sub_if_data *sdata)
++u32 ieee80211_scan_req_radio_mask(struct ieee80211_local *local,
++				  struct cfg80211_scan_request *req)
++{
++	const struct wiphy_radio *radio;
++	u32 mask = 0;
++	int i, r;
++
++	for (r = 0; r < local->hw.wiphy->n_radio; r++) {
++		radio = &local->hw.wiphy->radio[r];
++
++		for (i = 0; i < req->n_channels; i++) {
++			struct cfg80211_chan_def chandef = {};
++
++			chandef.chan = req->channels[i];
++			cfg80211_chandef_create(&chandef, req->channels[i],
++						NL80211_CHAN_NO_HT);
++			if (!cfg80211_radio_chandef_valid(radio, &chandef))
++				continue;
++
++			mask |= BIT(r);
++			break;
++		}
++	}
++
++	return mask;
++}
++
++u32 ieee80211_can_leave_ch(struct ieee80211_sub_if_data *sdata,
++			   u32 radio_mask)
+ {
+ 	struct ieee80211_local *local = sdata->local;
+ 	struct ieee80211_sub_if_data *sdata_iter;
++	struct wiphy *wiphy = local->hw.wiphy;
++	struct ieee80211_chanctx_conf *conf;
++	struct ieee80211_link_data *link;
+ 	unsigned int link_id;
+ 
+ 	lockdep_assert_wiphy(local->hw.wiphy);
+ 
+-	if (!ieee80211_is_radar_required(local))
++	if (!ieee80211_is_radar_required(local, radio_mask))
+ 		return true;
+ 
+ 	if (!regulatory_pre_cac_allowed(local->hw.wiphy))
+ 		return false;
+ 
+ 	list_for_each_entry(sdata_iter, &local->interfaces, list) {
+-		for_each_valid_link(&sdata_iter->wdev, link_id)
+-			if (sdata_iter->wdev.links[link_id].cac_started)
++		for_each_valid_link(&sdata_iter->wdev, link_id) {
++			if (!sdata_iter->wdev.links[link_id].cac_started)
++				continue;
++
++			if (!wiphy->n_radio)
+ 				return false;
++
++			link = sdata_dereference(sdata->link[link_id], sdata);
++			if (!link)
++				continue;
++
++			conf = wiphy_dereference(wiphy, link->conf->chanctx_conf);
++			if (!conf)
++				continue;
++
++			if (conf->radio_idx >= 0 &&
++			    (radio_mask & BIT(conf->radio_idx)))
++				return false;
++		}
+ 	}
+ 
+ 	return true;
+ }
+ 
+ static bool ieee80211_can_scan(struct ieee80211_local *local,
+-			       struct ieee80211_sub_if_data *sdata)
++			       struct ieee80211_sub_if_data *sdata,
++			       u32 radio_mask)
+ {
+-	if (!__ieee80211_can_leave_ch(sdata))
+-		return false;
+-
+-	if (!list_empty(&local->roc_list))
++	if (!list_empty(&local->roc_list) &&
++	    (!local->hw.wiphy->n_radio ||
++	     (radio_mask & ieee80211_offchannel_radio_mask(local))))
+ 		return false;
+ 
+ 	if (sdata->vif.type == NL80211_IFTYPE_STATION &&
+@@ -612,15 +659,22 @@ static bool ieee80211_can_scan(struct ie
+ 
+ void ieee80211_run_deferred_scan(struct ieee80211_local *local)
+ {
++	struct ieee80211_sub_if_data *sdata;
++	struct cfg80211_scan_request *req;
++	u32 radio_mask;
++
+ 	lockdep_assert_wiphy(local->hw.wiphy);
+ 
+-	if (!local->scan_req || local->scanning)
++	req = wiphy_dereference(local->hw.wiphy, local->scan_req);
++	if (!req || local->scanning)
++		return;
++
++	radio_mask = ieee80211_scan_req_radio_mask(local, req);
++	sdata = wiphy_dereference(local->hw.wiphy, local->scan_sdata);
++	if (!ieee80211_can_leave_ch(sdata, radio_mask))
+ 		return;
+ 
+-	if (!ieee80211_can_scan(local,
+-				rcu_dereference_protected(
+-					local->scan_sdata,
+-					lockdep_is_held(&local->hw.wiphy->mtx))))
++	if (!ieee80211_can_scan(local, sdata, radio_mask))
+ 		return;
+ 
+ 	wiphy_delayed_work_queue(local->hw.wiphy, &local->scan_work,
+@@ -703,6 +757,7 @@ static int __ieee80211_start_scan(struct
+ {
+ 	struct ieee80211_local *local = sdata->local;
+ 	bool hw_scan = local->ops->hw_scan;
++	u32 radio_mask;
+ 	int rc;
+ 
+ 	lockdep_assert_wiphy(local->hw.wiphy);
+@@ -717,10 +772,11 @@ static int __ieee80211_start_scan(struct
+ 	    !(sdata->vif.active_links & BIT(req->tsf_report_link_id)))
+ 		return -EINVAL;
+ 
+-	if (!__ieee80211_can_leave_ch(sdata))
++	radio_mask = ieee80211_scan_req_radio_mask(local, req);
++	if (!ieee80211_can_leave_ch(sdata, radio_mask))
+ 		return -EBUSY;
+ 
+-	if (!ieee80211_can_scan(local, sdata)) {
++	if (!ieee80211_can_scan(local, sdata, radio_mask)) {
+ 		/* wait for the work to finish/time out */
+ 		rcu_assign_pointer(local->scan_req, req);
+ 		rcu_assign_pointer(local->scan_sdata, sdata);
+--- a/net/mac80211/util.c
++++ b/net/mac80211/util.c
+@@ -3644,6 +3644,23 @@ again:
+ 	WARN_ON_ONCE(!cfg80211_chandef_valid(c));
+ }
+ 
++u32 ieee80211_chandef_radio_mask(struct ieee80211_local *local,
++				 struct cfg80211_chan_def *chandef)
++{
++	struct wiphy *wiphy = local->hw.wiphy;
++	const struct wiphy_radio *radio;
++	u32 mask = 0;
++	int i;
++
++	for (i = 0; i < wiphy->n_radio; i++) {
++		radio = &wiphy->radio[i];
++		if (cfg80211_radio_chandef_valid(radio, chandef))
++			mask |= BIT(i);
++	}
++
++	return mask;
++}
++
+ /*
+  * Returns true if smps_mode_new is strictly more restrictive than
+  * smps_mode_old.
+--- a/net/wireless/util.c
++++ b/net/wireless/util.c
+@@ -2911,6 +2911,9 @@ bool cfg80211_radio_chandef_valid(const
+ {
+ 	u32 freq, width;
+ 
++	if (!cfg80211_chandef_valid(chandef))
++		return false;
++
+ 	freq = ieee80211_chandef_to_khz(chandef);
+ 	width = nl80211_chan_width_to_mhz(chandef->width);
+ 	if (!ieee80211_radio_freq_range_valid(radio, freq, width))




More information about the lede-commits mailing list