--- /dev/null
+From: Karthikeyan Periyasamy <quic_periyasa@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@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);
--- /dev/null
+From: Felix Fietkau <nbd@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@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(¶ms, 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, ¶ms);
+ 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)
--- /dev/null
+From: Felix Fietkau <nbd@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@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)
--- /dev/null
+From: Felix Fietkau <nbd@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@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,
--- /dev/null
+From: Felix Fietkau <nbd@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@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;
+ }
+
--- /dev/null
+From: Felix Fietkau <nbd@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@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);
--- /dev/null
+From: Felix Fietkau <nbd@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@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;
+ }
--- /dev/null
+From: Felix Fietkau <nbd@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@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)
--- /dev/null
+From: Felix Fietkau <nbd@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@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
--- /dev/null
+From: Felix Fietkau <nbd@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@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;
+
--- /dev/null
+From: Felix Fietkau <nbd@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@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;
--- /dev/null
+From: Felix Fietkau <nbd@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@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];
+