mac80211: improve single-wiphy multi-radio support
authorFelix Fietkau <nbd@nbd.name>
Thu, 26 Sep 2024 12:15:14 +0000 (14:15 +0200)
committerFelix Fietkau <nbd@nbd.name>
Tue, 22 Oct 2024 12:40:42 +0000 (14:40 +0200)
- add support for configuring allowed radios for a vif
- add support for monitor mode on multiple channels

Signed-off-by: Felix Fietkau <nbd@nbd.name>
12 files changed:
package/kernel/mac80211/patches/subsys/331-wifi-cfg80211-check-radio-iface-combination-for-mult.patch [new file with mode: 0644]
package/kernel/mac80211/patches/subsys/350-wifi-cfg80211-add-option-for-vif-allowed-radios.patch [new file with mode: 0644]
package/kernel/mac80211/patches/subsys/351-wifi-mac80211-use-vif-radio-mask-to-limit-ibss-scan-.patch [new file with mode: 0644]
package/kernel/mac80211/patches/subsys/352-wifi-mac80211-use-vif-radio-mask-to-limit-chanctx-an.patch [new file with mode: 0644]
package/kernel/mac80211/patches/subsys/353-wifi-mac80211-remove-status-ampdu_delimiter_crc.patch [new file with mode: 0644]
package/kernel/mac80211/patches/subsys/354-wifi-cfg80211-pass-net_device-to-.set_monitor_channel.patch [new file with mode: 0644]
package/kernel/mac80211/patches/subsys/355-wifi-mac80211-add-flag-to-opt-out-of-virtual-monitor.patch [new file with mode: 0644]
package/kernel/mac80211/patches/subsys/356-wifi-cfg80211-add-monitor-SKIP_TX-flag.patch [new file with mode: 0644]
package/kernel/mac80211/patches/subsys/357-wifi-mac80211-add-support-for-the-monitor-SKIP_TX-fl.patch [new file with mode: 0644]
package/kernel/mac80211/patches/subsys/358-wifi-mac80211-refactor-ieee80211_rx_monitor.patch [new file with mode: 0644]
package/kernel/mac80211/patches/subsys/359-wifi-mac80211-filter-on-monitor-interfaces-based-on-.patch [new file with mode: 0644]
package/kernel/mac80211/patches/subsys/360-wifi-cfg80211-report-per-wiphy-radio-antenna-mask.patch [new file with mode: 0644]

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 (file)
index 0000000..76c351a
--- /dev/null
@@ -0,0 +1,122 @@
+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);
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 (file)
index 0000000..d5b42f3
--- /dev/null
@@ -0,0 +1,309 @@
+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(&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 (file)
index 0000000..7363c38
--- /dev/null
@@ -0,0 +1,79 @@
+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)
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 (file)
index 0000000..ac3d101
--- /dev/null
@@ -0,0 +1,52 @@
+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,
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 (file)
index 0000000..c0cdcdd
--- /dev/null
@@ -0,0 +1,67 @@
+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;
+       }
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 (file)
index 0000000..c2a9159
--- /dev/null
@@ -0,0 +1,165 @@
+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);
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 (file)
index 0000000..2510cb0
--- /dev/null
@@ -0,0 +1,337 @@
+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;
+       }
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 (file)
index 0000000..dfc01c6
--- /dev/null
@@ -0,0 +1,56 @@
+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)
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 (file)
index 0000000..e62c15c
--- /dev/null
@@ -0,0 +1,54 @@
+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
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 (file)
index 0000000..cc97606
--- /dev/null
@@ -0,0 +1,94 @@
+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;
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 (file)
index 0000000..4b2c67a
--- /dev/null
@@ -0,0 +1,29 @@
+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;
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 (file)
index 0000000..178e01d
--- /dev/null
@@ -0,0 +1,64 @@
+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];