[openwrt/openwrt] mac80211: improve single-wiphy multi-radio support

LEDE Commits lede-commits at lists.infradead.org
Tue Oct 22 05:49:06 PDT 2024


nbd pushed a commit to openwrt/openwrt.git, branch main:
https://git.openwrt.org/87033c3a0b22376a043fe5eb5f3690524efd271d

commit 87033c3a0b22376a043fe5eb5f3690524efd271d
Author: Felix Fietkau <nbd at nbd.name>
AuthorDate: Thu Sep 26 14:15:14 2024 +0200

    mac80211: improve single-wiphy multi-radio support
    
    - add support for configuring allowed radios for a vif
    - add support for monitor mode on multiple channels
    
    Signed-off-by: Felix Fietkau <nbd at nbd.name>
---
 ...11-check-radio-iface-combination-for-mult.patch | 122 ++++++++
 ...fg80211-add-option-for-vif-allowed-radios.patch | 309 +++++++++++++++++++
 ...11-use-vif-radio-mask-to-limit-ibss-scan-.patch |  79 +++++
 ...11-use-vif-radio-mask-to-limit-chanctx-an.patch |  52 ++++
 ...ac80211-remove-status-ampdu_delimiter_crc.patch |  67 ++++
 ...1-pass-net_device-to-.set_monitor_channel.patch | 165 ++++++++++
 ...11-add-flag-to-opt-out-of-virtual-monitor.patch | 337 +++++++++++++++++++++
 ...56-wifi-cfg80211-add-monitor-SKIP_TX-flag.patch |  56 ++++
 ...11-add-support-for-the-monitor-SKIP_TX-fl.patch |  54 ++++
 ...fi-mac80211-refactor-ieee80211_rx_monitor.patch |  94 ++++++
 ...11-filter-on-monitor-interfaces-based-on-.patch |  29 ++
 ...80211-report-per-wiphy-radio-antenna-mask.patch |  64 ++++
 12 files changed, 1428 insertions(+)

diff --git a/package/kernel/mac80211/patches/subsys/331-wifi-cfg80211-check-radio-iface-combination-for-mult.patch b/package/kernel/mac80211/patches/subsys/331-wifi-cfg80211-check-radio-iface-combination-for-mult.patch
new file mode 100644
index 0000000000..76c351aa83
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/331-wifi-cfg80211-check-radio-iface-combination-for-mult.patch
@@ -0,0 +1,122 @@
+From: Karthikeyan Periyasamy <quic_periyasa at quicinc.com>
+Date: Tue, 17 Sep 2024 19:32:39 +0530
+Subject: [PATCH] wifi: cfg80211: check radio iface combination for multi radio
+ per wiphy
+
+Currently, wiphy_verify_combinations() fails for the multi-radio per wiphy
+due to the condition check on new global interface combination that DFS
+only works on one channel. In a multi-radio scenario, new global interface
+combination encompasses the capabilities of all radio combinations, so it
+supports more than one channel with DFS. For multi-radio per wiphy,
+interface combination verification needs to be performed for radio specific
+interface combinations. This is necessary as the new global interface
+combination combines the capabilities of all radio combinations.
+
+Fixes: a01b1e9f9955 ("wifi: mac80211: add support for DFS with multiple radios")
+Signed-off-by: Karthikeyan Periyasamy <quic_periyasa at quicinc.com>
+---
+
+--- a/net/wireless/core.c
++++ b/net/wireless/core.c
+@@ -599,16 +599,20 @@ use_default_name:
+ }
+ EXPORT_SYMBOL(wiphy_new_nm);
+ 
+-static int wiphy_verify_combinations(struct wiphy *wiphy)
++static
++int wiphy_verify_iface_combinations(struct wiphy *wiphy,
++				    const struct ieee80211_iface_combination *iface_comb,
++				    int n_iface_comb,
++				    bool combined_radio)
+ {
+ 	const struct ieee80211_iface_combination *c;
+ 	int i, j;
+ 
+-	for (i = 0; i < wiphy->n_iface_combinations; i++) {
++	for (i = 0; i < n_iface_comb; i++) {
+ 		u32 cnt = 0;
+ 		u16 all_iftypes = 0;
+ 
+-		c = &wiphy->iface_combinations[i];
++		c = &iface_comb[i];
+ 
+ 		/*
+ 		 * Combinations with just one interface aren't real,
+@@ -621,9 +625,13 @@ static int wiphy_verify_combinations(str
+ 		if (WARN_ON(!c->num_different_channels))
+ 			return -EINVAL;
+ 
+-		/* DFS only works on one channel. */
+-		if (WARN_ON(c->radar_detect_widths &&
+-			    (c->num_different_channels > 1)))
++		/* DFS only works on one channel. Avoid this check
++		 * for multi-radio global combination, since it hold
++		 * the capabilities of all radio combinations.
++		 */
++		if (!combined_radio &&
++		    WARN_ON(c->radar_detect_widths &&
++			    c->num_different_channels > 1))
+ 			return -EINVAL;
+ 
+ 		if (WARN_ON(!c->n_limits))
+@@ -644,13 +652,21 @@ static int wiphy_verify_combinations(str
+ 			if (WARN_ON(wiphy->software_iftypes & types))
+ 				return -EINVAL;
+ 
+-			/* Only a single P2P_DEVICE can be allowed */
+-			if (WARN_ON(types & BIT(NL80211_IFTYPE_P2P_DEVICE) &&
++			/* Only a single P2P_DEVICE can be allowed, avoid this
++			 * check for multi-radio global combination, since it
++			 * hold the capabilities of all radio combinations.
++			 */
++			if (!combined_radio &&
++			    WARN_ON(types & BIT(NL80211_IFTYPE_P2P_DEVICE) &&
+ 				    c->limits[j].max > 1))
+ 				return -EINVAL;
+ 
+-			/* Only a single NAN can be allowed */
+-			if (WARN_ON(types & BIT(NL80211_IFTYPE_NAN) &&
++			/* Only a single NAN can be allowed, avoid this
++			 * check for multi-radio global combination, since it
++			 * hold the capabilities of all radio combinations.
++			 */
++			if (!combined_radio &&
++			    WARN_ON(types & BIT(NL80211_IFTYPE_NAN) &&
+ 				    c->limits[j].max > 1))
+ 				return -EINVAL;
+ 
+@@ -674,6 +690,34 @@ static int wiphy_verify_combinations(str
+ 	return 0;
+ }
+ 
++static int wiphy_verify_combinations(struct wiphy *wiphy)
++{
++	int i, ret;
++	bool combined_radio = false;
++
++	if (wiphy->n_radio) {
++		for (i = 0; i < wiphy->n_radio; i++) {
++			const struct wiphy_radio *radio = &wiphy->radio[i];
++
++			ret = wiphy_verify_iface_combinations(wiphy,
++							      radio->iface_combinations,
++							      radio->n_iface_combinations,
++							      false);
++			if (ret)
++				return ret;
++		}
++
++		combined_radio = true;
++	}
++
++	ret = wiphy_verify_iface_combinations(wiphy,
++					      wiphy->iface_combinations,
++					      wiphy->n_iface_combinations,
++					      combined_radio);
++
++	return ret;
++}
++
+ int wiphy_register(struct wiphy *wiphy)
+ {
+ 	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
diff --git a/package/kernel/mac80211/patches/subsys/350-wifi-cfg80211-add-option-for-vif-allowed-radios.patch b/package/kernel/mac80211/patches/subsys/350-wifi-cfg80211-add-option-for-vif-allowed-radios.patch
new file mode 100644
index 0000000000..d5b42f3a3a
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/350-wifi-cfg80211-add-option-for-vif-allowed-radios.patch
@@ -0,0 +1,309 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Wed, 17 Jul 2024 15:43:52 +0200
+Subject: [PATCH] wifi: cfg80211: add option for vif allowed radios
+
+This allows users to prevent a vif from affecting radios other than the
+configured ones. This can be useful in cases where e.g. an AP is running
+on one radio, and triggering a scan on another radio should not disturb it.
+
+Changing the allowed radios list for a vif is supported, but only while
+it is down.
+
+While it is possible to achieve the same by always explicitly specifying
+a frequency list for scan requests and ensuring that the wrong channel/band
+is never accidentally set on an unrelated interface, this change makes
+multi-radio wiphy setups a lot easier to deal with for CLI users.
+
+By itself, this patch only enforces the radio mask for scanning requests
+and remain-on-channel. Follow-up changes build on this to limit configured
+frequencies.
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/include/net/cfg80211.h
++++ b/include/net/cfg80211.h
+@@ -6227,6 +6227,7 @@ enum ieee80211_ap_reg_power {
+  *	entered.
+  * @links[].cac_time_ms: CAC time in ms
+  * @valid_links: bitmap describing what elements of @links are valid
++ * @radio_mask: Bitmask of radios that this interface is allowed to operate on.
+  */
+ struct wireless_dev {
+ 	struct wiphy *wiphy;
+@@ -6339,6 +6340,8 @@ struct wireless_dev {
+ 		unsigned int cac_time_ms;
+ 	} links[IEEE80211_MLD_MAX_NUM_LINKS];
+ 	u16 valid_links;
++
++	u32 radio_mask;
+ };
+ 
+ static inline const u8 *wdev_address(struct wireless_dev *wdev)
+@@ -6525,6 +6528,17 @@ bool cfg80211_radio_chandef_valid(const
+ 				  const struct cfg80211_chan_def *chandef);
+ 
+ /**
++ * cfg80211_wdev_channel_allowed - Check if the wdev may use the channel
++ *
++ * @wdev: the wireless device
++ * @chan: channel to check
++ *
++ * Return: whether or not the wdev may use the channel
++ */
++bool cfg80211_wdev_channel_allowed(struct wireless_dev *wdev,
++				   struct ieee80211_channel *chan);
++
++/**
+  * ieee80211_get_response_rate - get basic rate for a given rate
+  *
+  * @sband: the band to look for rates in
+--- a/include/uapi/linux/nl80211.h
++++ b/include/uapi/linux/nl80211.h
+@@ -2868,6 +2868,9 @@ enum nl80211_commands {
+  *	nested item, it contains attributes defined in
+  *	&enum nl80211_if_combination_attrs.
+  *
++ * @NL80211_ATTR_VIF_RADIO_MASK: Bitmask of allowed radios (u32).
++ *	A value of 0 means all radios.
++ *
+  * @NUM_NL80211_ATTR: total number of nl80211_attrs available
+  * @NL80211_ATTR_MAX: highest attribute number currently defined
+  * @__NL80211_ATTR_AFTER_LAST: internal use
+@@ -3416,6 +3419,8 @@ enum nl80211_attrs {
+ 	NL80211_ATTR_WIPHY_RADIOS,
+ 	NL80211_ATTR_WIPHY_INTERFACE_COMBINATIONS,
+ 
++	NL80211_ATTR_VIF_RADIO_MASK,
++
+ 	/* add attributes here, update the policy in nl80211.c */
+ 
+ 	__NL80211_ATTR_AFTER_LAST,
+--- a/net/wireless/nl80211.c
++++ b/net/wireless/nl80211.c
+@@ -829,6 +829,7 @@ static const struct nla_policy nl80211_p
+ 	[NL80211_ATTR_MLO_TTLM_DLINK] = NLA_POLICY_EXACT_LEN(sizeof(u16) * 8),
+ 	[NL80211_ATTR_MLO_TTLM_ULINK] = NLA_POLICY_EXACT_LEN(sizeof(u16) * 8),
+ 	[NL80211_ATTR_ASSOC_SPP_AMSDU] = { .type = NLA_FLAG },
++	[NL80211_ATTR_VIF_RADIO_MASK] = { .type = NLA_U32 },
+ };
+ 
+ /* policy for the key attributes */
+@@ -3996,7 +3997,8 @@ static int nl80211_send_iface(struct sk_
+ 	    nla_put_u32(msg, NL80211_ATTR_GENERATION,
+ 			rdev->devlist_generation ^
+ 			(cfg80211_rdev_list_generation << 2)) ||
+-	    nla_put_u8(msg, NL80211_ATTR_4ADDR, wdev->use_4addr))
++	    nla_put_u8(msg, NL80211_ATTR_4ADDR, wdev->use_4addr) ||
++	    nla_put_u32(msg, NL80211_ATTR_VIF_RADIO_MASK, wdev->radio_mask))
+ 		goto nla_put_failure;
+ 
+ 	if (rdev->ops->get_channel && !wdev->valid_links) {
+@@ -4312,6 +4314,29 @@ static int nl80211_valid_4addr(struct cf
+ 	return -EOPNOTSUPP;
+ }
+ 
++static int nl80211_parse_vif_radio_mask(struct genl_info *info,
++					u32 *radio_mask)
++{
++	struct cfg80211_registered_device *rdev = info->user_ptr[0];
++	struct nlattr *attr = info->attrs[NL80211_ATTR_VIF_RADIO_MASK];
++	u32 mask, allowed;
++
++	if (!attr) {
++		*radio_mask = 0;
++		return 0;
++	}
++
++	allowed = BIT(rdev->wiphy.n_radio) - 1;
++	mask = nla_get_u32(attr);
++	if (mask & ~allowed)
++		return -EINVAL;
++	if (!mask)
++		mask = allowed;
++	*radio_mask = mask;
++
++	return 1;
++}
++
+ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
+ {
+ 	struct cfg80211_registered_device *rdev = info->user_ptr[0];
+@@ -4319,6 +4344,8 @@ static int nl80211_set_interface(struct
+ 	int err;
+ 	enum nl80211_iftype otype, ntype;
+ 	struct net_device *dev = info->user_ptr[1];
++	struct wireless_dev *wdev = dev->ieee80211_ptr;
++	u32 radio_mask = 0;
+ 	bool change = false;
+ 
+ 	memset(&params, 0, sizeof(params));
+@@ -4332,8 +4359,6 @@ static int nl80211_set_interface(struct
+ 	}
+ 
+ 	if (info->attrs[NL80211_ATTR_MESH_ID]) {
+-		struct wireless_dev *wdev = dev->ieee80211_ptr;
+-
+ 		if (ntype != NL80211_IFTYPE_MESH_POINT)
+ 			return -EINVAL;
+ 		if (otype != NL80211_IFTYPE_MESH_POINT)
+@@ -4364,6 +4389,12 @@ static int nl80211_set_interface(struct
+ 	if (err > 0)
+ 		change = true;
+ 
++	err = nl80211_parse_vif_radio_mask(info, &radio_mask);
++	if (err < 0)
++		return err;
++	if (err && netif_running(dev))
++		return -EBUSY;
++
+ 	if (change)
+ 		err = cfg80211_change_iface(rdev, dev, ntype, &params);
+ 	else
+@@ -4372,11 +4403,11 @@ static int nl80211_set_interface(struct
+ 	if (!err && params.use_4addr != -1)
+ 		dev->ieee80211_ptr->use_4addr = params.use_4addr;
+ 
+-	if (change && !err) {
+-		struct wireless_dev *wdev = dev->ieee80211_ptr;
++	if (radio_mask)
++		wdev->radio_mask = radio_mask;
+ 
++	if (change && !err)
+ 		nl80211_notify_iface(rdev, wdev, NL80211_CMD_SET_INTERFACE);
+-	}
+ 
+ 	return err;
+ }
+@@ -4387,6 +4418,7 @@ static int _nl80211_new_interface(struct
+ 	struct vif_params params;
+ 	struct wireless_dev *wdev;
+ 	struct sk_buff *msg;
++	u32 radio_mask;
+ 	int err;
+ 	enum nl80211_iftype type = NL80211_IFTYPE_UNSPECIFIED;
+ 
+@@ -4424,6 +4456,10 @@ static int _nl80211_new_interface(struct
+ 	if (err < 0)
+ 		return err;
+ 
++	err = nl80211_parse_vif_radio_mask(info, &radio_mask);
++	if (err < 0)
++		return err;
++
+ 	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ 	if (!msg)
+ 		return -ENOMEM;
+@@ -4465,6 +4501,9 @@ static int _nl80211_new_interface(struct
+ 		break;
+ 	}
+ 
++	if (radio_mask)
++		wdev->radio_mask = radio_mask;
++
+ 	if (nl80211_send_iface(msg, info->snd_portid, info->snd_seq, 0,
+ 			       rdev, wdev, NL80211_CMD_NEW_INTERFACE) < 0) {
+ 		nlmsg_free(msg);
+@@ -9180,6 +9219,9 @@ static bool cfg80211_off_channel_oper_al
+ 
+ 	lockdep_assert_wiphy(wdev->wiphy);
+ 
++	if (!cfg80211_wdev_channel_allowed(wdev, chan))
++		return false;
++
+ 	if (!cfg80211_beaconing_iface_active(wdev))
+ 		return true;
+ 
+@@ -9392,7 +9434,8 @@ static int nl80211_trigger_scan(struct s
+ 			}
+ 
+ 			/* ignore disabled channels */
+-			if (chan->flags & IEEE80211_CHAN_DISABLED)
++			if (chan->flags & IEEE80211_CHAN_DISABLED ||
++			    !cfg80211_wdev_channel_allowed(wdev, chan))
+ 				continue;
+ 
+ 			request->channels[i] = chan;
+@@ -9412,7 +9455,8 @@ static int nl80211_trigger_scan(struct s
+ 
+ 				chan = &wiphy->bands[band]->channels[j];
+ 
+-				if (chan->flags & IEEE80211_CHAN_DISABLED)
++				if (chan->flags & IEEE80211_CHAN_DISABLED ||
++				    !cfg80211_wdev_channel_allowed(wdev, chan))
+ 					continue;
+ 
+ 				request->channels[i] = chan;
+--- a/net/wireless/scan.c
++++ b/net/wireless/scan.c
+@@ -956,7 +956,8 @@ static int cfg80211_scan_6ghz(struct cfg
+ 		struct ieee80211_channel *chan =
+ 			ieee80211_get_channel(&rdev->wiphy, ap->center_freq);
+ 
+-		if (!chan || chan->flags & IEEE80211_CHAN_DISABLED)
++		if (!chan || chan->flags & IEEE80211_CHAN_DISABLED ||
++		    !cfg80211_wdev_channel_allowed(rdev_req->wdev, chan))
+ 			continue;
+ 
+ 		for (i = 0; i < rdev_req->n_channels; i++) {
+@@ -3490,9 +3491,12 @@ int cfg80211_wext_siwscan(struct net_dev
+ 			continue;
+ 
+ 		for (j = 0; j < wiphy->bands[band]->n_channels; j++) {
++			struct ieee80211_channel *chan;
++
+ 			/* ignore disabled channels */
+-			if (wiphy->bands[band]->channels[j].flags &
+-						IEEE80211_CHAN_DISABLED)
++			chan = &wiphy->bands[band]->channels[j];
++			if (chan->flags & IEEE80211_CHAN_DISABLED ||
++			    !cfg80211_wdev_channel_allowed(creq->wdev, chan))
+ 				continue;
+ 
+ 			/* If we have a wireless request structure and the
+--- a/net/wireless/util.c
++++ b/net/wireless/util.c
+@@ -2923,3 +2923,32 @@ bool cfg80211_radio_chandef_valid(const
+ 	return true;
+ }
+ EXPORT_SYMBOL(cfg80211_radio_chandef_valid);
++
++bool cfg80211_wdev_channel_allowed(struct wireless_dev *wdev,
++				   struct ieee80211_channel *chan)
++{
++	struct wiphy *wiphy = wdev->wiphy;
++	const struct wiphy_radio *radio;
++	struct cfg80211_chan_def chandef;
++	u32 radio_mask;
++	int i;
++
++	radio_mask = wdev->radio_mask;
++	if (!wiphy->n_radio || radio_mask == BIT(wiphy->n_radio) - 1)
++		return true;
++
++	cfg80211_chandef_create(&chandef, chan, NL80211_CHAN_HT20);
++	for (i = 0; i < wiphy->n_radio; i++) {
++		if (!(radio_mask & BIT(i)))
++			continue;
++
++		radio = &wiphy->radio[i];
++		if (!cfg80211_radio_chandef_valid(radio, &chandef))
++			continue;
++
++		return true;
++	}
++
++	return false;
++}
++EXPORT_SYMBOL(cfg80211_wdev_channel_allowed);
+--- a/net/wireless/core.c
++++ b/net/wireless/core.c
+@@ -1415,6 +1415,8 @@ void cfg80211_init_wdev(struct wireless_
+ 	/* allow mac80211 to determine the timeout */
+ 	wdev->ps_timeout = -1;
+ 
++	wdev->radio_mask = BIT(wdev->wiphy->n_radio) - 1;
++
+ 	if ((wdev->iftype == NL80211_IFTYPE_STATION ||
+ 	     wdev->iftype == NL80211_IFTYPE_P2P_CLIENT ||
+ 	     wdev->iftype == NL80211_IFTYPE_ADHOC) && !wdev->use_4addr)
diff --git a/package/kernel/mac80211/patches/subsys/351-wifi-mac80211-use-vif-radio-mask-to-limit-ibss-scan-.patch b/package/kernel/mac80211/patches/subsys/351-wifi-mac80211-use-vif-radio-mask-to-limit-ibss-scan-.patch
new file mode 100644
index 0000000000..7363c3873f
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/351-wifi-mac80211-use-vif-radio-mask-to-limit-ibss-scan-.patch
@@ -0,0 +1,79 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Thu, 26 Sep 2024 14:06:11 +0200
+Subject: [PATCH] wifi: mac80211: use vif radio mask to limit ibss scan
+ frequencies
+
+Reject frequencies not supported by any radio that the vif is allowed to use.
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/net/mac80211/scan.c
++++ b/net/mac80211/scan.c
+@@ -1178,14 +1178,14 @@ int ieee80211_request_ibss_scan(struct i
+ 				unsigned int n_channels)
+ {
+ 	struct ieee80211_local *local = sdata->local;
+-	int ret = -EBUSY, i, n_ch = 0;
++	int i, n_ch = 0;
+ 	enum nl80211_band band;
+ 
+ 	lockdep_assert_wiphy(local->hw.wiphy);
+ 
+ 	/* busy scanning */
+ 	if (local->scan_req)
+-		goto unlock;
++		return -EBUSY;
+ 
+ 	/* fill internal scan request */
+ 	if (!channels) {
+@@ -1202,7 +1202,9 @@ int ieee80211_request_ibss_scan(struct i
+ 				    &local->hw.wiphy->bands[band]->channels[i];
+ 
+ 				if (tmp_ch->flags & (IEEE80211_CHAN_NO_IR |
+-						     IEEE80211_CHAN_DISABLED))
++						     IEEE80211_CHAN_DISABLED) ||
++				    !cfg80211_wdev_channel_allowed(&sdata->wdev,
++								   tmp_ch))
+ 					continue;
+ 
+ 				local->int_scan_req->channels[n_ch] = tmp_ch;
+@@ -1211,21 +1213,23 @@ int ieee80211_request_ibss_scan(struct i
+ 		}
+ 
+ 		if (WARN_ON_ONCE(n_ch == 0))
+-			goto unlock;
++			return -EINVAL;
+ 
+ 		local->int_scan_req->n_channels = n_ch;
+ 	} else {
+ 		for (i = 0; i < n_channels; i++) {
+ 			if (channels[i]->flags & (IEEE80211_CHAN_NO_IR |
+-						  IEEE80211_CHAN_DISABLED))
++						  IEEE80211_CHAN_DISABLED) ||
++			    !cfg80211_wdev_channel_allowed(&sdata->wdev,
++							   channels[i]))
+ 				continue;
+ 
+ 			local->int_scan_req->channels[n_ch] = channels[i];
+ 			n_ch++;
+ 		}
+ 
+-		if (WARN_ON_ONCE(n_ch == 0))
+-			goto unlock;
++		if (n_ch == 0)
++			return -EINVAL;
+ 
+ 		local->int_scan_req->n_channels = n_ch;
+ 	}
+@@ -1235,9 +1239,7 @@ int ieee80211_request_ibss_scan(struct i
+ 	memcpy(local->int_scan_req->ssids[0].ssid, ssid, IEEE80211_MAX_SSID_LEN);
+ 	local->int_scan_req->ssids[0].ssid_len = ssid_len;
+ 
+-	ret = __ieee80211_start_scan(sdata, sdata->local->int_scan_req);
+- unlock:
+-	return ret;
++	return __ieee80211_start_scan(sdata, sdata->local->int_scan_req);
+ }
+ 
+ void ieee80211_scan_cancel(struct ieee80211_local *local)
diff --git a/package/kernel/mac80211/patches/subsys/352-wifi-mac80211-use-vif-radio-mask-to-limit-chanctx-an.patch b/package/kernel/mac80211/patches/subsys/352-wifi-mac80211-use-vif-radio-mask-to-limit-chanctx-an.patch
new file mode 100644
index 0000000000..ac3d101148
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/352-wifi-mac80211-use-vif-radio-mask-to-limit-chanctx-an.patch
@@ -0,0 +1,52 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Thu, 26 Sep 2024 14:07:50 +0200
+Subject: [PATCH] wifi: mac80211: use vif radio mask to limit creating chanctx
+
+Reject frequencies not supported by any radio that the vif is allowed to use.
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/net/mac80211/chan.c
++++ b/net/mac80211/chan.c
+@@ -1167,7 +1167,7 @@ ieee80211_replace_chanctx(struct ieee802
+ static bool
+ ieee80211_find_available_radio(struct ieee80211_local *local,
+ 			       const struct ieee80211_chan_req *chanreq,
+-			       int *radio_idx)
++			       u32 radio_mask, int *radio_idx)
+ {
+ 	struct wiphy *wiphy = local->hw.wiphy;
+ 	const struct wiphy_radio *radio;
+@@ -1178,6 +1178,9 @@ ieee80211_find_available_radio(struct ie
+ 		return true;
+ 
+ 	for (i = 0; i < wiphy->n_radio; i++) {
++		if (!(radio_mask & BIT(i)))
++			continue;
++
+ 		radio = &wiphy->radio[i];
+ 		if (!cfg80211_radio_chandef_valid(radio, &chanreq->oper))
+ 			continue;
+@@ -1211,7 +1214,9 @@ int ieee80211_link_reserve_chanctx(struc
+ 	new_ctx = ieee80211_find_reservation_chanctx(local, chanreq, mode);
+ 	if (!new_ctx) {
+ 		if (ieee80211_can_create_new_chanctx(local, -1) &&
+-		    ieee80211_find_available_radio(local, chanreq, &radio_idx))
++		    ieee80211_find_available_radio(local, chanreq,
++						   sdata->wdev.radio_mask,
++						   &radio_idx))
+ 			new_ctx = ieee80211_new_chanctx(local, chanreq, mode,
+ 							false, radio_idx);
+ 		else
+@@ -1881,7 +1886,9 @@ int _ieee80211_link_use_channel(struct i
+ 	/* Note: context is now reserved */
+ 	if (ctx)
+ 		reserved = true;
+-	else if (!ieee80211_find_available_radio(local, chanreq, &radio_idx))
++	else if (!ieee80211_find_available_radio(local, chanreq,
++						 sdata->wdev.radio_mask,
++						 &radio_idx))
+ 		ctx = ERR_PTR(-EBUSY);
+ 	else
+ 		ctx = ieee80211_new_chanctx(local, chanreq, mode,
diff --git a/package/kernel/mac80211/patches/subsys/353-wifi-mac80211-remove-status-ampdu_delimiter_crc.patch b/package/kernel/mac80211/patches/subsys/353-wifi-mac80211-remove-status-ampdu_delimiter_crc.patch
new file mode 100644
index 0000000000..c0cdcdd6bc
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/353-wifi-mac80211-remove-status-ampdu_delimiter_crc.patch
@@ -0,0 +1,67 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Wed, 17 Jul 2024 22:49:16 +0200
+Subject: [PATCH] wifi: mac80211: remove status->ampdu_delimiter_crc
+
+This was never used by any driver, so remove it to free up some space.
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/include/net/mac80211.h
++++ b/include/net/mac80211.h
+@@ -1448,8 +1448,6 @@ ieee80211_tx_info_clear_status(struct ie
+  * @RX_FLAG_AMPDU_IS_LAST: this subframe is the last subframe of the A-MPDU
+  * @RX_FLAG_AMPDU_DELIM_CRC_ERROR: A delimiter CRC error has been detected
+  *	on this subframe
+- * @RX_FLAG_AMPDU_DELIM_CRC_KNOWN: The delimiter CRC field is known (the CRC
+- *	is stored in the @ampdu_delimiter_crc field)
+  * @RX_FLAG_MIC_STRIPPED: The mic was stripped of this packet. Decryption was
+  *	done by the hardware
+  * @RX_FLAG_ONLY_MONITOR: Report frame only to monitor interfaces without
+@@ -1521,7 +1519,7 @@ enum mac80211_rx_flags {
+ 	RX_FLAG_AMPDU_LAST_KNOWN	= BIT(12),
+ 	RX_FLAG_AMPDU_IS_LAST		= BIT(13),
+ 	RX_FLAG_AMPDU_DELIM_CRC_ERROR	= BIT(14),
+-	RX_FLAG_AMPDU_DELIM_CRC_KNOWN	= BIT(15),
++	/* one free bit at 15 */
+ 	RX_FLAG_MACTIME			= BIT(16) | BIT(17),
+ 	RX_FLAG_MACTIME_PLCP_START	= 1 << 16,
+ 	RX_FLAG_MACTIME_START		= 2 << 16,
+@@ -1618,7 +1616,6 @@ enum mac80211_rx_encoding {
+  * @rx_flags: internal RX flags for mac80211
+  * @ampdu_reference: A-MPDU reference number, must be a different value for
+  *	each A-MPDU but the same for each subframe within one A-MPDU
+- * @ampdu_delimiter_crc: A-MPDU delimiter CRC
+  * @zero_length_psdu_type: radiotap type of the 0-length PSDU
+  * @link_valid: if the link which is identified by @link_id is valid. This flag
+  *	is set only when connection is MLO.
+@@ -1656,7 +1653,6 @@ struct ieee80211_rx_status {
+ 	s8 signal;
+ 	u8 chains;
+ 	s8 chain_signal[IEEE80211_MAX_CHAINS];
+-	u8 ampdu_delimiter_crc;
+ 	u8 zero_length_psdu_type;
+ 	u8 link_valid:1, link_id:4;
+ };
+--- a/net/mac80211/rx.c
++++ b/net/mac80211/rx.c
+@@ -508,18 +508,13 @@ ieee80211_add_rx_radiotap_header(struct
+ 			flags |= IEEE80211_RADIOTAP_AMPDU_IS_LAST;
+ 		if (status->flag & RX_FLAG_AMPDU_DELIM_CRC_ERROR)
+ 			flags |= IEEE80211_RADIOTAP_AMPDU_DELIM_CRC_ERR;
+-		if (status->flag & RX_FLAG_AMPDU_DELIM_CRC_KNOWN)
+-			flags |= IEEE80211_RADIOTAP_AMPDU_DELIM_CRC_KNOWN;
+ 		if (status->flag & RX_FLAG_AMPDU_EOF_BIT_KNOWN)
+ 			flags |= IEEE80211_RADIOTAP_AMPDU_EOF_KNOWN;
+ 		if (status->flag & RX_FLAG_AMPDU_EOF_BIT)
+ 			flags |= IEEE80211_RADIOTAP_AMPDU_EOF;
+ 		put_unaligned_le16(flags, pos);
+ 		pos += 2;
+-		if (status->flag & RX_FLAG_AMPDU_DELIM_CRC_KNOWN)
+-			*pos++ = status->ampdu_delimiter_crc;
+-		else
+-			*pos++ = 0;
++		*pos++ = 0;
+ 		*pos++ = 0;
+ 	}
+ 
diff --git a/package/kernel/mac80211/patches/subsys/354-wifi-cfg80211-pass-net_device-to-.set_monitor_channel.patch b/package/kernel/mac80211/patches/subsys/354-wifi-cfg80211-pass-net_device-to-.set_monitor_channel.patch
new file mode 100644
index 0000000000..c2a915963c
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/354-wifi-cfg80211-pass-net_device-to-.set_monitor_channel.patch
@@ -0,0 +1,165 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Thu, 26 Sep 2024 19:52:30 +0200
+Subject: [PATCH] wifi: cfg80211: pass net_device to .set_monitor_channel
+
+Preparation for allowing multiple monitor interfaces with different channels
+on a multi-radio wiphy.
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/drivers/net/wireless/ath/wil6210/cfg80211.c
++++ b/drivers/net/wireless/ath/wil6210/cfg80211.c
+@@ -1493,6 +1493,7 @@ out:
+ }
+ 
+ static int wil_cfg80211_set_channel(struct wiphy *wiphy,
++				    struct net_device *dev,
+ 				    struct cfg80211_chan_def *chandef)
+ {
+ 	struct wil6210_priv *wil = wiphy_to_wil(wiphy);
+--- a/drivers/net/wireless/marvell/libertas/cfg.c
++++ b/drivers/net/wireless/marvell/libertas/cfg.c
+@@ -486,6 +486,7 @@ static int lbs_add_wps_enrollee_tlv(u8 *
+  */
+ 
+ static int lbs_cfg_set_monitor_channel(struct wiphy *wiphy,
++				       struct net_device *dev,
+ 				       struct cfg80211_chan_def *chandef)
+ {
+ 	struct lbs_private *priv = wiphy_priv(wiphy);
+--- a/drivers/net/wireless/microchip/wilc1000/cfg80211.c
++++ b/drivers/net/wireless/microchip/wilc1000/cfg80211.c
+@@ -231,6 +231,7 @@ struct wilc_vif *wilc_get_wl_to_vif(stru
+ }
+ 
+ static int set_channel(struct wiphy *wiphy,
++		       struct net_device *dev,
+ 		       struct cfg80211_chan_def *chandef)
+ {
+ 	struct wilc *wl = wiphy_priv(wiphy);
+@@ -1424,7 +1425,7 @@ static int start_ap(struct wiphy *wiphy,
+ 	struct wilc_vif *vif = netdev_priv(dev);
+ 	int ret;
+ 
+-	ret = set_channel(wiphy, &settings->chandef);
++	ret = set_channel(wiphy, dev, &settings->chandef);
+ 	if (ret != 0)
+ 		netdev_err(dev, "Error in setting channel\n");
+ 
+--- a/include/net/cfg80211.h
++++ b/include/net/cfg80211.h
+@@ -4700,6 +4700,7 @@ struct cfg80211_ops {
+ 					     struct ieee80211_channel *chan);
+ 
+ 	int	(*set_monitor_channel)(struct wiphy *wiphy,
++				       struct net_device *dev,
+ 				       struct cfg80211_chan_def *chandef);
+ 
+ 	int	(*scan)(struct wiphy *wiphy,
+--- a/net/mac80211/cfg.c
++++ b/net/mac80211/cfg.c
+@@ -879,6 +879,7 @@ static int ieee80211_get_station(struct
+ }
+ 
+ static int ieee80211_set_monitor_channel(struct wiphy *wiphy,
++					 struct net_device *dev,
+ 					 struct cfg80211_chan_def *chandef)
+ {
+ 	struct ieee80211_local *local = wiphy_priv(wiphy);
+--- a/net/wireless/chan.c
++++ b/net/wireless/chan.c
+@@ -1673,6 +1673,7 @@ bool cfg80211_reg_check_beaconing(struct
+ EXPORT_SYMBOL(cfg80211_reg_check_beaconing);
+ 
+ int cfg80211_set_monitor_channel(struct cfg80211_registered_device *rdev,
++				 struct net_device *dev,
+ 				 struct cfg80211_chan_def *chandef)
+ {
+ 	if (!rdev->ops->set_monitor_channel)
+@@ -1680,7 +1681,7 @@ int cfg80211_set_monitor_channel(struct
+ 	if (!cfg80211_has_monitors_only(rdev))
+ 		return -EBUSY;
+ 
+-	return rdev_set_monitor_channel(rdev, chandef);
++	return rdev_set_monitor_channel(rdev, dev, chandef);
+ }
+ 
+ bool cfg80211_any_usable_channels(struct wiphy *wiphy,
+--- a/net/wireless/core.h
++++ b/net/wireless/core.h
+@@ -510,6 +510,7 @@ static inline unsigned int elapsed_jiffi
+ }
+ 
+ int cfg80211_set_monitor_channel(struct cfg80211_registered_device *rdev,
++				 struct net_device *dev,
+ 				 struct cfg80211_chan_def *chandef);
+ 
+ int ieee80211_get_ratemask(struct ieee80211_supported_band *sband,
+--- a/net/wireless/nl80211.c
++++ b/net/wireless/nl80211.c
+@@ -3562,7 +3562,7 @@ static int __nl80211_set_channel(struct
+ 	case NL80211_IFTYPE_MESH_POINT:
+ 		return cfg80211_set_mesh_channel(rdev, wdev, &chandef);
+ 	case NL80211_IFTYPE_MONITOR:
+-		return cfg80211_set_monitor_channel(rdev, &chandef);
++		return cfg80211_set_monitor_channel(rdev, dev, &chandef);
+ 	default:
+ 		break;
+ 	}
+--- a/net/wireless/rdev-ops.h
++++ b/net/wireless/rdev-ops.h
+@@ -445,11 +445,12 @@ rdev_libertas_set_mesh_channel(struct cf
+ 
+ static inline int
+ rdev_set_monitor_channel(struct cfg80211_registered_device *rdev,
++			 struct net_device *dev,
+ 			 struct cfg80211_chan_def *chandef)
+ {
+ 	int ret;
+-	trace_rdev_set_monitor_channel(&rdev->wiphy, chandef);
+-	ret = rdev->ops->set_monitor_channel(&rdev->wiphy, chandef);
++	trace_rdev_set_monitor_channel(&rdev->wiphy, dev, chandef);
++	ret = rdev->ops->set_monitor_channel(&rdev->wiphy, dev, chandef);
+ 	trace_rdev_return_int(&rdev->wiphy, ret);
+ 	return ret;
+ }
+--- a/net/wireless/trace.h
++++ b/net/wireless/trace.h
+@@ -1318,19 +1318,21 @@ TRACE_EVENT(rdev_libertas_set_mesh_chann
+ );
+ 
+ TRACE_EVENT(rdev_set_monitor_channel,
+-	TP_PROTO(struct wiphy *wiphy,
++	TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
+ 		 struct cfg80211_chan_def *chandef),
+-	TP_ARGS(wiphy, chandef),
++	TP_ARGS(wiphy, netdev, chandef),
+ 	TP_STRUCT__entry(
+ 		WIPHY_ENTRY
++		NETDEV_ENTRY
+ 		CHAN_DEF_ENTRY
+ 	),
+ 	TP_fast_assign(
+ 		WIPHY_ASSIGN;
++		NETDEV_ASSIGN;
+ 		CHAN_DEF_ASSIGN(chandef);
+ 	),
+-	TP_printk(WIPHY_PR_FMT ", " CHAN_DEF_PR_FMT,
+-		  WIPHY_PR_ARG, CHAN_DEF_PR_ARG)
++	TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", " CHAN_DEF_PR_FMT,
++		  WIPHY_PR_ARG, NETDEV_PR_ARG, CHAN_DEF_PR_ARG)
+ );
+ 
+ TRACE_EVENT(rdev_auth,
+--- a/net/wireless/wext-compat.c
++++ b/net/wireless/wext-compat.c
+@@ -830,7 +830,7 @@ static int cfg80211_wext_siwfreq(struct
+ 			ret = -EINVAL;
+ 			break;
+ 		}
+-		ret = cfg80211_set_monitor_channel(rdev, &chandef);
++		ret = cfg80211_set_monitor_channel(rdev, dev, &chandef);
+ 		break;
+ 	case NL80211_IFTYPE_MESH_POINT:
+ 		freq = cfg80211_wext_freq(wextfreq);
diff --git a/package/kernel/mac80211/patches/subsys/355-wifi-mac80211-add-flag-to-opt-out-of-virtual-monitor.patch b/package/kernel/mac80211/patches/subsys/355-wifi-mac80211-add-flag-to-opt-out-of-virtual-monitor.patch
new file mode 100644
index 0000000000..2510cb0dbe
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/355-wifi-mac80211-add-flag-to-opt-out-of-virtual-monitor.patch
@@ -0,0 +1,337 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Mon, 30 Sep 2024 15:09:45 +0200
+Subject: [PATCH] wifi: mac80211: add flag to opt out of virtual monitor
+ support
+
+This is useful for multi-radio devices that are capable of monitoring on
+multiple channels simultanenously. When this flag is set, each monitor
+interface is passed to the driver individually and can have a configured
+channel.
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/include/net/mac80211.h
++++ b/include/net/mac80211.h
+@@ -2679,6 +2679,11 @@ struct ieee80211_txq {
+  *	a virtual monitor interface when monitor interfaces are the only
+  *	active interfaces.
+  *
++ * @IEEE80211_HW_NO_VIRTUAL_MONITOR: The driver would like to be informed
++ *	of any monitor interface, as well as their configured channel.
++ *	This is useful for supporting multiple monitor interfaces on different
++ *	channels.
++ *
+  * @IEEE80211_HW_NO_AUTO_VIF: The driver would like for no wlanX to
+  *	be created.  It is expected user-space will create vifs as
+  *	desired (and thus have them named as desired).
+@@ -2838,6 +2843,7 @@ enum ieee80211_hw_flags {
+ 	IEEE80211_HW_SUPPORTS_DYNAMIC_PS,
+ 	IEEE80211_HW_MFP_CAPABLE,
+ 	IEEE80211_HW_WANT_MONITOR_VIF,
++	IEEE80211_HW_NO_VIRTUAL_MONITOR,
+ 	IEEE80211_HW_NO_AUTO_VIF,
+ 	IEEE80211_HW_SW_CRYPTO_CONTROL,
+ 	IEEE80211_HW_SUPPORT_FAST_XMIT,
+--- a/net/mac80211/cfg.c
++++ b/net/mac80211/cfg.c
+@@ -105,8 +105,11 @@ static int ieee80211_set_mon_options(str
+ 	}
+ 
+ 	/* also validate MU-MIMO change */
+-	monitor_sdata = wiphy_dereference(local->hw.wiphy,
+-					  local->monitor_sdata);
++	if (ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR))
++		monitor_sdata = sdata;
++	else
++		monitor_sdata = wiphy_dereference(local->hw.wiphy,
++						  local->monitor_sdata);
+ 
+ 	if (!monitor_sdata &&
+ 	    (params->vht_mumimo_groups || params->vht_mumimo_follow_addr))
+@@ -114,7 +117,9 @@ static int ieee80211_set_mon_options(str
+ 
+ 	/* apply all changes now - no failures allowed */
+ 
+-	if (monitor_sdata && ieee80211_hw_check(&local->hw, WANT_MONITOR_VIF))
++	if (monitor_sdata &&
++		(ieee80211_hw_check(&local->hw, WANT_MONITOR_VIF) ||
++		 ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR)))
+ 		ieee80211_set_mu_mimo_follow(monitor_sdata, params);
+ 
+ 	if (params->flags) {
+@@ -889,22 +894,25 @@ static int ieee80211_set_monitor_channel
+ 
+ 	lockdep_assert_wiphy(local->hw.wiphy);
+ 
+-	if (cfg80211_chandef_identical(&local->monitor_chanreq.oper,
+-				       &chanreq.oper))
+-		return 0;
++	sdata = IEEE80211_DEV_TO_SUB_IF(dev);
++	if (!ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR)) {
++		if (cfg80211_chandef_identical(&local->monitor_chanreq.oper,
++						   &chanreq.oper))
++			return 0;
+ 
+-	sdata = wiphy_dereference(local->hw.wiphy,
+-				  local->monitor_sdata);
+-	if (!sdata)
+-		goto done;
++		sdata = wiphy_dereference(wiphy, local->monitor_sdata);
++		if (!sdata)
++			goto done;
++	}
+ 
+-	if (cfg80211_chandef_identical(&sdata->vif.bss_conf.chanreq.oper,
++	if (rcu_access_pointer(sdata->deflink.conf->chanctx_conf) &&
++		cfg80211_chandef_identical(&sdata->vif.bss_conf.chanreq.oper,
+ 				       &chanreq.oper))
+ 		return 0;
+ 
+ 	ieee80211_link_release_channel(&sdata->deflink);
+ 	ret = ieee80211_link_use_channel(&sdata->deflink, &chanreq,
+-					 IEEE80211_CHANCTX_EXCLUSIVE);
++					 IEEE80211_CHANCTX_SHARED);
+ 	if (ret)
+ 		return ret;
+ done:
+@@ -3049,7 +3057,8 @@ static int ieee80211_set_tx_power(struct
+ 	if (wdev) {
+ 		sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
+ 
+-		if (sdata->vif.type == NL80211_IFTYPE_MONITOR) {
++		if (sdata->vif.type == NL80211_IFTYPE_MONITOR &&
++		    !ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR)) {
+ 			if (!ieee80211_hw_check(&local->hw, WANT_MONITOR_VIF))
+ 				return -EOPNOTSUPP;
+ 
+@@ -3097,7 +3106,8 @@ static int ieee80211_set_tx_power(struct
+ 	}
+ 
+ 	list_for_each_entry(sdata, &local->interfaces, list) {
+-		if (sdata->vif.type == NL80211_IFTYPE_MONITOR) {
++		if (sdata->vif.type == NL80211_IFTYPE_MONITOR &&
++		    !ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR)) {
+ 			has_monitor = true;
+ 			continue;
+ 		}
+@@ -3107,7 +3117,8 @@ static int ieee80211_set_tx_power(struct
+ 		sdata->vif.bss_conf.txpower_type = txp_type;
+ 	}
+ 	list_for_each_entry(sdata, &local->interfaces, list) {
+-		if (sdata->vif.type == NL80211_IFTYPE_MONITOR)
++		if (sdata->vif.type == NL80211_IFTYPE_MONITOR &&
++		    !ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR))
+ 			continue;
+ 		ieee80211_recalc_txpower(sdata, update_txp_type);
+ 	}
+@@ -4299,7 +4310,8 @@ static int ieee80211_cfg_get_channel(str
+ 	if (chanctx_conf) {
+ 		*chandef = link->conf->chanreq.oper;
+ 		ret = 0;
+-	} else if (local->open_count > 0 &&
++	} else if (!ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR) &&
++		   local->open_count > 0 &&
+ 		   local->open_count == local->monitors &&
+ 		   sdata->vif.type == NL80211_IFTYPE_MONITOR) {
+ 		*chandef = local->monitor_chanreq.oper;
+--- a/net/mac80211/chan.c
++++ b/net/mac80211/chan.c
+@@ -337,6 +337,10 @@ ieee80211_get_chanctx_max_required_bw(st
+ 		case NL80211_IFTYPE_P2P_DEVICE:
+ 		case NL80211_IFTYPE_NAN:
+ 			continue;
++		case NL80211_IFTYPE_MONITOR:
++			WARN_ON_ONCE(!ieee80211_hw_check(&local->hw,
++							 NO_VIRTUAL_MONITOR));
++			fallthrough;
+ 		case NL80211_IFTYPE_ADHOC:
+ 		case NL80211_IFTYPE_MESH_POINT:
+ 		case NL80211_IFTYPE_OCB:
+@@ -345,7 +349,6 @@ ieee80211_get_chanctx_max_required_bw(st
+ 		case NL80211_IFTYPE_WDS:
+ 		case NL80211_IFTYPE_UNSPECIFIED:
+ 		case NUM_NL80211_IFTYPES:
+-		case NL80211_IFTYPE_MONITOR:
+ 		case NL80211_IFTYPE_P2P_CLIENT:
+ 		case NL80211_IFTYPE_P2P_GO:
+ 			WARN_ON_ONCE(1);
+@@ -954,6 +957,10 @@ void ieee80211_recalc_smps_chanctx(struc
+ 			if (!link->sdata->u.mgd.associated)
+ 				continue;
+ 			break;
++		case NL80211_IFTYPE_MONITOR:
++			if (!ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR))
++				continue;
++			break;
+ 		case NL80211_IFTYPE_AP:
+ 		case NL80211_IFTYPE_ADHOC:
+ 		case NL80211_IFTYPE_MESH_POINT:
+@@ -966,6 +973,11 @@ void ieee80211_recalc_smps_chanctx(struc
+ 		if (rcu_access_pointer(link->conf->chanctx_conf) != &chanctx->conf)
+ 			continue;
+ 
++		if (link->sdata->vif.type == NL80211_IFTYPE_MONITOR) {
++			rx_chains_dynamic = rx_chains_static = local->rx_chains;
++			break;
++		}
++
+ 		switch (link->smps_mode) {
+ 		default:
+ 			WARN_ONCE(1, "Invalid SMPS mode %d\n",
+--- a/net/mac80211/debugfs.c
++++ b/net/mac80211/debugfs.c
+@@ -465,6 +465,7 @@ static const char *hw_flag_names[] = {
+ 	FLAG(SUPPORTS_DYNAMIC_PS),
+ 	FLAG(MFP_CAPABLE),
+ 	FLAG(WANT_MONITOR_VIF),
++	FLAG(NO_VIRTUAL_MONITOR),
+ 	FLAG(NO_AUTO_VIF),
+ 	FLAG(SW_CRYPTO_CONTROL),
+ 	FLAG(SUPPORT_FAST_XMIT),
+--- a/net/mac80211/driver-ops.c
++++ b/net/mac80211/driver-ops.c
+@@ -65,6 +65,7 @@ int drv_add_interface(struct ieee80211_l
+ 	if (WARN_ON(sdata->vif.type == NL80211_IFTYPE_AP_VLAN ||
+ 		    (sdata->vif.type == NL80211_IFTYPE_MONITOR &&
+ 		     !ieee80211_hw_check(&local->hw, WANT_MONITOR_VIF) &&
++		     !ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR) &&
+ 		     !(sdata->u.mntr.flags & MONITOR_FLAG_ACTIVE))))
+ 		return -EINVAL;
+ 
+--- a/net/mac80211/iface.c
++++ b/net/mac80211/iface.c
+@@ -279,8 +279,13 @@ static int _ieee80211_change_mac(struct
+ 	ret = eth_mac_addr(sdata->dev, sa);
+ 
+ 	if (ret == 0) {
+-		memcpy(sdata->vif.addr, sa->sa_data, ETH_ALEN);
+-		ether_addr_copy(sdata->vif.bss_conf.addr, sdata->vif.addr);
++		if (check_dup) {
++			memcpy(sdata->vif.addr, sa->sa_data, ETH_ALEN);
++			ether_addr_copy(sdata->vif.bss_conf.addr, sdata->vif.addr);
++		} else {
++			memset(sdata->vif.addr, 0, ETH_ALEN);
++			memset(sdata->vif.bss_conf.addr, 0, ETH_ALEN);
++		}
+ 	}
+ 
+ 	/* Regardless of eth_mac_addr() return we still want to add the
+@@ -699,9 +704,11 @@ static void ieee80211_do_stop(struct iee
+ 		ieee80211_recalc_idle(local);
+ 		ieee80211_recalc_offload(local);
+ 
+-		if (!(sdata->u.mntr.flags & MONITOR_FLAG_ACTIVE))
++		if (!(sdata->u.mntr.flags & MONITOR_FLAG_ACTIVE) &&
++		    !ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR))
+ 			break;
+ 
++		ieee80211_link_release_channel(&sdata->deflink);
+ 		fallthrough;
+ 	default:
+ 		if (!going_down)
+@@ -1131,7 +1138,8 @@ int ieee80211_add_virtual_monitor(struct
+ 	ASSERT_RTNL();
+ 	lockdep_assert_wiphy(local->hw.wiphy);
+ 
+-	if (local->monitor_sdata)
++	if (local->monitor_sdata ||
++	    ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR))
+ 		return 0;
+ 
+ 	sdata = kzalloc(sizeof(*sdata) + local->hw.vif_data_size, GFP_KERNEL);
+@@ -1193,6 +1201,9 @@ void ieee80211_del_virtual_monitor(struc
+ {
+ 	struct ieee80211_sub_if_data *sdata;
+ 
++	if (ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR))
++		return;
++
+ 	ASSERT_RTNL();
+ 	lockdep_assert_wiphy(local->hw.wiphy);
+ 
+@@ -1328,7 +1339,8 @@ int ieee80211_do_open(struct wireless_de
+ 			break;
+ 		}
+ 
+-		if (sdata->u.mntr.flags & MONITOR_FLAG_ACTIVE) {
++		if ((sdata->u.mntr.flags & MONITOR_FLAG_ACTIVE) ||
++		    ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR)) {
+ 			res = drv_add_interface(local, sdata);
+ 			if (res)
+ 				goto err_stop;
+--- a/net/mac80211/rx.c
++++ b/net/mac80211/rx.c
+@@ -840,6 +840,9 @@ ieee80211_rx_monitor(struct ieee80211_lo
+ 		bool last_monitor = list_is_last(&sdata->u.mntr.list,
+ 						 &local->mon_list);
+ 
++		if (ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR))
++			ieee80211_handle_mu_mimo_mon(sdata, origskb, rtap_space);
++
+ 		if (!monskb)
+ 			monskb = ieee80211_make_monitor_skb(local, &origskb,
+ 							    rate, rtap_space,
+--- a/net/mac80211/tx.c
++++ b/net/mac80211/tx.c
+@@ -1763,7 +1763,8 @@ static bool __ieee80211_tx(struct ieee80
+ 
+ 	switch (sdata->vif.type) {
+ 	case NL80211_IFTYPE_MONITOR:
+-		if (sdata->u.mntr.flags & MONITOR_FLAG_ACTIVE) {
++		if ((sdata->u.mntr.flags & MONITOR_FLAG_ACTIVE) ||
++		    ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR)) {
+ 			vif = &sdata->vif;
+ 			break;
+ 		}
+@@ -3952,7 +3953,8 @@ begin:
+ 
+ 	switch (tx.sdata->vif.type) {
+ 	case NL80211_IFTYPE_MONITOR:
+-		if (tx.sdata->u.mntr.flags & MONITOR_FLAG_ACTIVE) {
++		if ((tx.sdata->u.mntr.flags & MONITOR_FLAG_ACTIVE) ||
++		    ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR)) {
+ 			vif = &tx.sdata->vif;
+ 			break;
+ 		}
+--- a/net/mac80211/util.c
++++ b/net/mac80211/util.c
+@@ -754,7 +754,8 @@ static void __iterate_interfaces(struct
+ 	list_for_each_entry_rcu(sdata, &local->interfaces, list) {
+ 		switch (sdata->vif.type) {
+ 		case NL80211_IFTYPE_MONITOR:
+-			if (!(sdata->u.mntr.flags & MONITOR_FLAG_ACTIVE))
++			if (!(sdata->u.mntr.flags & MONITOR_FLAG_ACTIVE) &&
++			    !ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR))
+ 				continue;
+ 			break;
+ 		case NL80211_IFTYPE_AP_VLAN:
+@@ -1857,8 +1858,10 @@ int ieee80211_reconfig(struct ieee80211_
+ 	}
+ 
+ 	list_for_each_entry(sdata, &local->interfaces, list) {
++		if (sdata->vif.type == NL80211_IFTYPE_MONITOR &&
++		    !ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR))
++			continue;
+ 		if (sdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
+-		    sdata->vif.type != NL80211_IFTYPE_MONITOR &&
+ 		    ieee80211_sdata_running(sdata)) {
+ 			res = drv_add_interface(local, sdata);
+ 			if (WARN_ON(res))
+@@ -1871,11 +1874,14 @@ int ieee80211_reconfig(struct ieee80211_
+ 	 */
+ 	if (res) {
+ 		list_for_each_entry_continue_reverse(sdata, &local->interfaces,
+-						     list)
++						     list) {
++			if (sdata->vif.type == NL80211_IFTYPE_MONITOR &&
++			    !ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR))
++				continue;
+ 			if (sdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
+-			    sdata->vif.type != NL80211_IFTYPE_MONITOR &&
+ 			    ieee80211_sdata_running(sdata))
+ 				drv_remove_interface(local, sdata);
++		}
+ 		ieee80211_handle_reconfig_failure(local);
+ 		return res;
+ 	}
diff --git a/package/kernel/mac80211/patches/subsys/356-wifi-cfg80211-add-monitor-SKIP_TX-flag.patch b/package/kernel/mac80211/patches/subsys/356-wifi-cfg80211-add-monitor-SKIP_TX-flag.patch
new file mode 100644
index 0000000000..dfc01c6c9b
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/356-wifi-cfg80211-add-monitor-SKIP_TX-flag.patch
@@ -0,0 +1,56 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Mon, 30 Sep 2024 17:04:09 +0200
+Subject: [PATCH] wifi: cfg80211: add monitor SKIP_TX flag
+
+This can be used to indicate that the user is not interested in receiving
+locally sent packets on the monitor interface.
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/include/net/cfg80211.h
++++ b/include/net/cfg80211.h
+@@ -2272,6 +2272,7 @@ static inline int cfg80211_get_station(s
+  * @MONITOR_FLAG_OTHER_BSS: disable BSSID filtering
+  * @MONITOR_FLAG_COOK_FRAMES: report frames after processing
+  * @MONITOR_FLAG_ACTIVE: active monitor, ACKs frames on its MAC address
++ * @MONITOR_FLAG_SKIP_TX: do not pass locally transmitted frames
+  */
+ enum monitor_flags {
+ 	MONITOR_FLAG_CHANGED		= BIT(__NL80211_MNTR_FLAG_INVALID),
+@@ -2281,6 +2282,7 @@ enum monitor_flags {
+ 	MONITOR_FLAG_OTHER_BSS		= BIT(NL80211_MNTR_FLAG_OTHER_BSS),
+ 	MONITOR_FLAG_COOK_FRAMES	= BIT(NL80211_MNTR_FLAG_COOK_FRAMES),
+ 	MONITOR_FLAG_ACTIVE		= BIT(NL80211_MNTR_FLAG_ACTIVE),
++	MONITOR_FLAG_SKIP_TX		= BIT(NL80211_MNTR_FLAG_SKIP_TX),
+ };
+ 
+ /**
+--- a/include/uapi/linux/nl80211.h
++++ b/include/uapi/linux/nl80211.h
+@@ -4703,6 +4703,7 @@ enum nl80211_survey_info {
+  *	overrides all other flags.
+  * @NL80211_MNTR_FLAG_ACTIVE: use the configured MAC address
+  *	and ACK incoming unicast packets.
++ * @NL80211_MNTR_FLAG_SKIP_TX: do not pass local tx packets
+  *
+  * @__NL80211_MNTR_FLAG_AFTER_LAST: internal use
+  * @NL80211_MNTR_FLAG_MAX: highest possible monitor flag
+@@ -4715,6 +4716,7 @@ enum nl80211_mntr_flags {
+ 	NL80211_MNTR_FLAG_OTHER_BSS,
+ 	NL80211_MNTR_FLAG_COOK_FRAMES,
+ 	NL80211_MNTR_FLAG_ACTIVE,
++	NL80211_MNTR_FLAG_SKIP_TX,
+ 
+ 	/* keep last */
+ 	__NL80211_MNTR_FLAG_AFTER_LAST,
+--- a/net/wireless/nl80211.c
++++ b/net/wireless/nl80211.c
+@@ -4201,6 +4201,7 @@ static const struct nla_policy mntr_flag
+ 	[NL80211_MNTR_FLAG_OTHER_BSS] = { .type = NLA_FLAG },
+ 	[NL80211_MNTR_FLAG_COOK_FRAMES] = { .type = NLA_FLAG },
+ 	[NL80211_MNTR_FLAG_ACTIVE] = { .type = NLA_FLAG },
++	[NL80211_MNTR_FLAG_SKIP_TX] = { .type = NLA_FLAG },
+ };
+ 
+ static int parse_monitor_flags(struct nlattr *nla, u32 *mntrflags)
diff --git a/package/kernel/mac80211/patches/subsys/357-wifi-mac80211-add-support-for-the-monitor-SKIP_TX-fl.patch b/package/kernel/mac80211/patches/subsys/357-wifi-mac80211-add-support-for-the-monitor-SKIP_TX-fl.patch
new file mode 100644
index 0000000000..e62c15c19d
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/357-wifi-mac80211-add-support-for-the-monitor-SKIP_TX-fl.patch
@@ -0,0 +1,54 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Mon, 30 Sep 2024 17:05:18 +0200
+Subject: [PATCH] wifi: mac80211: add support for the monitor SKIP_TX flag
+
+Do not pass locally sent packets to monitor interfaces with this flag set.
+Skip processing tx packets on the status call entirely if no monitor
+interfaces without this flag are present.
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/net/mac80211/ieee80211_i.h
++++ b/net/mac80211/ieee80211_i.h
+@@ -1374,7 +1374,7 @@ struct ieee80211_local {
+ 	spinlock_t queue_stop_reason_lock;
+ 
+ 	int open_count;
+-	int monitors, cooked_mntrs;
++	int monitors, cooked_mntrs, tx_mntrs;
+ 	/* number of interfaces with corresponding FIF_ flags */
+ 	int fif_fcsfail, fif_plcpfail, fif_control, fif_other_bss, fif_pspoll,
+ 	    fif_probe_req;
+--- a/net/mac80211/iface.c
++++ b/net/mac80211/iface.c
+@@ -1094,6 +1094,8 @@ void ieee80211_adjust_monitor_flags(stru
+ 	ADJUST(CONTROL, control);
+ 	ADJUST(CONTROL, pspoll);
+ 	ADJUST(OTHER_BSS, other_bss);
++	if (!(flags & MONITOR_FLAG_SKIP_TX))
++		local->tx_mntrs += offset;
+ 
+ #undef ADJUST
+ }
+--- a/net/mac80211/status.c
++++ b/net/mac80211/status.c
+@@ -927,6 +927,9 @@ void ieee80211_tx_monitor(struct ieee802
+ 			if (!ieee80211_sdata_running(sdata))
+ 				continue;
+ 
++			if (sdata->u.mntr.flags & MONITOR_FLAG_SKIP_TX)
++				continue;
++
+ 			if ((sdata->u.mntr.flags & MONITOR_FLAG_COOK_FRAMES) &&
+ 			    !send_to_cooked)
+ 				continue;
+@@ -1099,7 +1102,7 @@ static void __ieee80211_tx_status(struct
+ 	 * This is a bit racy but we can avoid a lot of work
+ 	 * with this test...
+ 	 */
+-	if (!local->monitors && (!send_to_cooked || !local->cooked_mntrs)) {
++	if (!local->tx_mntrs && (!send_to_cooked || !local->cooked_mntrs)) {
+ 		if (status->free_list)
+ 			list_add_tail(&skb->list, status->free_list);
+ 		else
diff --git a/package/kernel/mac80211/patches/subsys/358-wifi-mac80211-refactor-ieee80211_rx_monitor.patch b/package/kernel/mac80211/patches/subsys/358-wifi-mac80211-refactor-ieee80211_rx_monitor.patch
new file mode 100644
index 0000000000..cc976060d4
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/358-wifi-mac80211-refactor-ieee80211_rx_monitor.patch
@@ -0,0 +1,94 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Wed, 2 Oct 2024 12:31:22 +0200
+Subject: [PATCH] wifi: mac80211: refactor ieee80211_rx_monitor
+
+Rework the monitor mode interface iteration to get rid of the last_monitor
+condition. Preparation for further filtering received monitor packets.
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/net/mac80211/rx.c
++++ b/net/mac80211/rx.c
+@@ -762,8 +762,8 @@ ieee80211_rx_monitor(struct ieee80211_lo
+ 		     struct ieee80211_rate *rate)
+ {
+ 	struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(origskb);
+-	struct ieee80211_sub_if_data *sdata;
+-	struct sk_buff *monskb = NULL;
++	struct ieee80211_sub_if_data *sdata, *prev_sdata = NULL;
++	struct sk_buff *skb, *monskb = NULL;
+ 	int present_fcs_len = 0;
+ 	unsigned int rtap_space = 0;
+ 	struct ieee80211_sub_if_data *monitor_sdata =
+@@ -837,8 +837,10 @@ ieee80211_rx_monitor(struct ieee80211_lo
+ 	ieee80211_handle_mu_mimo_mon(monitor_sdata, origskb, rtap_space);
+ 
+ 	list_for_each_entry_rcu(sdata, &local->mon_list, u.mntr.list) {
+-		bool last_monitor = list_is_last(&sdata->u.mntr.list,
+-						 &local->mon_list);
++		if (!prev_sdata) {
++			prev_sdata = sdata;
++			continue;
++		}
+ 
+ 		if (ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR))
+ 			ieee80211_handle_mu_mimo_mon(sdata, origskb, rtap_space);
+@@ -846,34 +848,34 @@ ieee80211_rx_monitor(struct ieee80211_lo
+ 		if (!monskb)
+ 			monskb = ieee80211_make_monitor_skb(local, &origskb,
+ 							    rate, rtap_space,
+-							    only_monitor &&
+-							    last_monitor);
+-
+-		if (monskb) {
+-			struct sk_buff *skb;
++							    false);
++		if (!monskb)
++			continue;
+ 
+-			if (last_monitor) {
+-				skb = monskb;
+-				monskb = NULL;
+-			} else {
+-				skb = skb_clone(monskb, GFP_ATOMIC);
+-			}
++		skb = skb_clone(monskb, GFP_ATOMIC);
++		if (!skb)
++			continue;
++
++		skb->dev = prev_sdata->dev;
++		dev_sw_netstats_rx_add(skb->dev, skb->len);
++		netif_receive_skb(skb);
++		prev_sdata = sdata;
++	}
+ 
+-			if (skb) {
+-				skb->dev = sdata->dev;
+-				dev_sw_netstats_rx_add(skb->dev, skb->len);
+-				netif_receive_skb(skb);
+-			}
++	if (prev_sdata) {
++		if (monskb)
++			skb = monskb;
++		else
++			skb = ieee80211_make_monitor_skb(local, &origskb,
++							 rate, rtap_space,
++							 only_monitor);
++		if (skb) {
++			skb->dev = prev_sdata->dev;
++			dev_sw_netstats_rx_add(skb->dev, skb->len);
++			netif_receive_skb(skb);
+ 		}
+-
+-		if (last_monitor)
+-			break;
+ 	}
+ 
+-	/* this happens if last_monitor was erroneously false */
+-	dev_kfree_skb(monskb);
+-
+-	/* ditto */
+ 	if (!origskb)
+ 		return NULL;
+ 
diff --git a/package/kernel/mac80211/patches/subsys/359-wifi-mac80211-filter-on-monitor-interfaces-based-on-.patch b/package/kernel/mac80211/patches/subsys/359-wifi-mac80211-filter-on-monitor-interfaces-based-on-.patch
new file mode 100644
index 0000000000..4b2c67aeba
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/359-wifi-mac80211-filter-on-monitor-interfaces-based-on-.patch
@@ -0,0 +1,29 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Wed, 2 Oct 2024 12:35:13 +0200
+Subject: [PATCH] wifi: mac80211: filter on monitor interfaces based on
+ configured channel
+
+When a monitor interface has an assigned channel (only happens with the
+NO_VIRTUAL_MONITOR feature), only pass packets received on that channel.
+This is useful for monitoring on multiple channels at the same time using
+multiple monitor interfaces.
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/net/mac80211/rx.c
++++ b/net/mac80211/rx.c
+@@ -837,6 +837,13 @@ ieee80211_rx_monitor(struct ieee80211_lo
+ 	ieee80211_handle_mu_mimo_mon(monitor_sdata, origskb, rtap_space);
+ 
+ 	list_for_each_entry_rcu(sdata, &local->mon_list, u.mntr.list) {
++		struct cfg80211_chan_def *chandef;
++
++		chandef = &sdata->vif.bss_conf.chanreq.oper;
++		if (chandef->chan &&
++		    chandef->chan->center_freq != status->freq)
++			continue;
++
+ 		if (!prev_sdata) {
+ 			prev_sdata = sdata;
+ 			continue;
diff --git a/package/kernel/mac80211/patches/subsys/360-wifi-cfg80211-report-per-wiphy-radio-antenna-mask.patch b/package/kernel/mac80211/patches/subsys/360-wifi-cfg80211-report-per-wiphy-radio-antenna-mask.patch
new file mode 100644
index 0000000000..178e01dc16
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/360-wifi-cfg80211-report-per-wiphy-radio-antenna-mask.patch
@@ -0,0 +1,64 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Wed, 7 Aug 2024 13:31:07 +0200
+Subject: [PATCH] wifi: cfg80211: report per wiphy radio antenna mask
+
+With multi-radio devices, each radio typically gets a fixed set of antennas.
+In order to be able to disable specific antennas for some radios, user space
+needs to know which antenna mask bits are assigned to which radio.
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/include/net/cfg80211.h
++++ b/include/net/cfg80211.h
+@@ -5443,6 +5443,8 @@ struct wiphy_radio_freq_range {
+  * @iface_combinations: Valid interface combinations array, should not
+  *	list single interface types.
+  * @n_iface_combinations: number of entries in @iface_combinations array.
++ *
++ * @antenna_mask: bitmask of antennas connected to this radio.
+  */
+ struct wiphy_radio {
+ 	const struct wiphy_radio_freq_range *freq_range;
+@@ -5450,6 +5452,8 @@ struct wiphy_radio {
+ 
+ 	const struct ieee80211_iface_combination *iface_combinations;
+ 	int n_iface_combinations;
++
++	u32 antenna_mask;
+ };
+ 
+ #define CFG80211_HW_TIMESTAMP_ALL_PEERS	0xffff
+--- a/include/uapi/linux/nl80211.h
++++ b/include/uapi/linux/nl80211.h
+@@ -8038,6 +8038,8 @@ enum nl80211_ap_settings_flags {
+  * @NL80211_WIPHY_RADIO_ATTR_INTERFACE_COMBINATION: Supported interface
+  *	combination for this radio. Attribute may be present multiple times
+  *	and contains attributes defined in &enum nl80211_if_combination_attrs.
++ * @NL80211_WIPHY_RADIO_ATTR_ANTENNA_MASK: bitmask (u32) of antennas
++ *	connected to this radio.
+  *
+  * @__NL80211_WIPHY_RADIO_ATTR_LAST: Internal
+  * @NL80211_WIPHY_RADIO_ATTR_MAX: Highest attribute
+@@ -8048,6 +8050,7 @@ enum nl80211_wiphy_radio_attrs {
+ 	NL80211_WIPHY_RADIO_ATTR_INDEX,
+ 	NL80211_WIPHY_RADIO_ATTR_FREQ_RANGE,
+ 	NL80211_WIPHY_RADIO_ATTR_INTERFACE_COMBINATION,
++	NL80211_WIPHY_RADIO_ATTR_ANTENNA_MASK,
+ 
+ 	/* keep last */
+ 	__NL80211_WIPHY_RADIO_ATTR_LAST,
+--- a/net/wireless/nl80211.c
++++ b/net/wireless/nl80211.c
+@@ -2431,6 +2431,11 @@ static int nl80211_put_radio(struct wiph
+ 	if (nla_put_u32(msg, NL80211_WIPHY_RADIO_ATTR_INDEX, idx))
+ 		goto nla_put_failure;
+ 
++	if (r->antenna_mask &&
++	    nla_put_u32(msg, NL80211_WIPHY_RADIO_ATTR_ANTENNA_MASK,
++			r->antenna_mask))
++		goto nla_put_failure;
++
+ 	for (i = 0; i < r->n_freq_range; i++) {
+ 		const struct wiphy_radio_freq_range *range = &r->freq_range[i];
+ 




More information about the lede-commits mailing list