iw: add multi-radio support
authorFelix Fietkau <nbd@nbd.name>
Sat, 29 Jun 2024 11:03:30 +0000 (13:03 +0200)
committerFelix Fietkau <nbd@nbd.name>
Tue, 22 Oct 2024 12:40:43 +0000 (14:40 +0200)
Signed-off-by: Felix Fietkau <nbd@nbd.name>
package/network/utils/iw/patches/001-nl80211_h_sync.patch [new file with mode: 0644]
package/network/utils/iw/patches/300-wiphy_radios.patch [new file with mode: 0644]
package/network/utils/iw/patches/310-vif_radio_mask.patch [new file with mode: 0644]

diff --git a/package/network/utils/iw/patches/001-nl80211_h_sync.patch b/package/network/utils/iw/patches/001-nl80211_h_sync.patch
new file mode 100644 (file)
index 0000000..f48c083
--- /dev/null
@@ -0,0 +1,98 @@
+--- a/nl80211.h
++++ b/nl80211.h
+@@ -2061,6 +2061,10 @@ enum nl80211_commands {
+  * @NL80211_ATTR_INTERFACE_COMBINATIONS: Nested attribute listing the supported
+  *    interface combinations. In each nested item, it contains attributes
+  *    defined in &enum nl80211_if_combination_attrs.
++ *    If the wiphy uses multiple radios (@NL80211_ATTR_WIPHY_RADIOS is set),
++ *    this attribute contains the interface combinations of the first radio.
++ *    See @NL80211_ATTR_WIPHY_INTERFACE_COMBINATIONS for the global wiphy
++ *    combinations for the sum of all radios.
+  * @NL80211_ATTR_SOFTWARE_IFTYPES: Nested attribute (just like
+  *    %NL80211_ATTR_SUPPORTED_IFTYPES) containing the interface types that
+  *    are managed in software: interfaces of these types aren't subject to
+@@ -2856,6 +2860,17 @@ enum nl80211_commands {
+  *    %NL80211_CMD_ASSOCIATE indicating the SPP A-MSDUs
+  *    are used on this connection
+  *
++ * @NL80211_ATTR_WIPHY_RADIOS: Nested attribute describing physical radios
++ *    belonging to this wiphy. See &enum nl80211_wiphy_radio_attrs.
++ *
++ * @NL80211_ATTR_WIPHY_INTERFACE_COMBINATIONS: Nested attribute listing the
++ *    supported interface combinations for all radios combined. In each
++ *    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
+@@ -3401,6 +3416,11 @@ enum nl80211_attrs {
+       NL80211_ATTR_ASSOC_SPP_AMSDU,
++      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,
+@@ -7987,4 +8007,54 @@ enum nl80211_ap_settings_flags {
+       NL80211_AP_SETTINGS_SA_QUERY_OFFLOAD_SUPPORT    = 1 << 1,
+ };
++/**
++ * enum nl80211_wiphy_radio_attrs - wiphy radio attributes
++ *
++ * @__NL80211_WIPHY_RADIO_ATTR_INVALID: Invalid
++ *
++ * @NL80211_WIPHY_RADIO_ATTR_INDEX: Index of this radio (u32)
++ * @NL80211_WIPHY_RADIO_ATTR_FREQ_RANGE: Frequency range supported by this
++ *    radio. Attribute may be present multiple times.
++ * @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_LAST: Internal
++ * @NL80211_WIPHY_RADIO_ATTR_MAX: Highest attribute
++ */
++enum nl80211_wiphy_radio_attrs {
++      __NL80211_WIPHY_RADIO_ATTR_INVALID,
++
++      NL80211_WIPHY_RADIO_ATTR_INDEX,
++      NL80211_WIPHY_RADIO_ATTR_FREQ_RANGE,
++      NL80211_WIPHY_RADIO_ATTR_INTERFACE_COMBINATION,
++
++      /* keep last */
++      __NL80211_WIPHY_RADIO_ATTR_LAST,
++      NL80211_WIPHY_RADIO_ATTR_MAX = __NL80211_WIPHY_RADIO_ATTR_LAST - 1,
++};
++
++/**
++ * enum nl80211_wiphy_radio_freq_range - wiphy radio frequency range
++ *
++ * @__NL80211_WIPHY_RADIO_FREQ_ATTR_INVALID: Invalid
++ *
++ * @NL80211_WIPHY_RADIO_FREQ_ATTR_START: Frequency range start (u32).
++ *    The unit is kHz.
++ * @NL80211_WIPHY_RADIO_FREQ_ATTR_END: Frequency range end (u32).
++ *    The unit is kHz.
++ *
++ * @__NL80211_WIPHY_RADIO_FREQ_ATTR_LAST: Internal
++ * @NL80211_WIPHY_RADIO_FREQ_ATTR_MAX: Highest attribute
++ */
++enum nl80211_wiphy_radio_freq_range {
++      __NL80211_WIPHY_RADIO_FREQ_ATTR_INVALID,
++
++      NL80211_WIPHY_RADIO_FREQ_ATTR_START,
++      NL80211_WIPHY_RADIO_FREQ_ATTR_END,
++
++      __NL80211_WIPHY_RADIO_FREQ_ATTR_LAST,
++      NL80211_WIPHY_RADIO_FREQ_ATTR_MAX = __NL80211_WIPHY_RADIO_FREQ_ATTR_LAST - 1,
++};
++
+ #endif /* __LINUX_NL80211_H */
diff --git a/package/network/utils/iw/patches/300-wiphy_radios.patch b/package/network/utils/iw/patches/300-wiphy_radios.patch
new file mode 100644 (file)
index 0000000..534addf
--- /dev/null
@@ -0,0 +1,252 @@
+--- a/info.c
++++ b/info.c
+@@ -295,6 +295,151 @@ static void print_pmsr_capabilities(stru
+       }
+ }
++static void print_interface_combinations(struct nlattr *ifcomb, bool radio)
++{
++      const char *indent = radio ? "\t" : "";
++      struct nlattr *nl_combi;
++      bool have_combinations = false;
++      int rem;
++
++      nla_for_each_nested(nl_combi, ifcomb, rem) {
++              static struct nla_policy iface_combination_policy[NUM_NL80211_IFACE_COMB] = {
++                      [NL80211_IFACE_COMB_LIMITS] = { .type = NLA_NESTED },
++                      [NL80211_IFACE_COMB_MAXNUM] = { .type = NLA_U32 },
++                      [NL80211_IFACE_COMB_STA_AP_BI_MATCH] = { .type = NLA_FLAG },
++                      [NL80211_IFACE_COMB_NUM_CHANNELS] = { .type = NLA_U32 },
++                      [NL80211_IFACE_COMB_RADAR_DETECT_WIDTHS] = { .type = NLA_U32 },
++              };
++              struct nlattr *tb_comb[NUM_NL80211_IFACE_COMB];
++              static struct nla_policy iface_limit_policy[NUM_NL80211_IFACE_LIMIT] = {
++                      [NL80211_IFACE_LIMIT_TYPES] = { .type = NLA_NESTED },
++                      [NL80211_IFACE_LIMIT_MAX] = { .type = NLA_U32 },
++              };
++              struct nlattr *tb_limit[NUM_NL80211_IFACE_LIMIT];
++              struct nlattr *nl_limit;
++              int err, rem_limit;
++              bool comma = false;
++
++              if (radio && nla_type(nl_combi) !=
++                           NL80211_WIPHY_RADIO_ATTR_INTERFACE_COMBINATION)
++                      continue;
++
++              if (!have_combinations) {
++                      printf("\t%svalid interface combinations:\n", indent);
++                      have_combinations = true;
++              }
++
++              printf("\t\t%s * ", indent);
++
++              err = nla_parse_nested(tb_comb, MAX_NL80211_IFACE_COMB,
++                                         nl_combi, iface_combination_policy);
++              if (err || !tb_comb[NL80211_IFACE_COMB_LIMITS] ||
++                      !tb_comb[NL80211_IFACE_COMB_MAXNUM] ||
++                      !tb_comb[NL80211_IFACE_COMB_NUM_CHANNELS]) {
++                      printf(" <failed to parse>\n");
++                      goto broken_combination;
++              }
++
++              nla_for_each_nested(nl_limit, tb_comb[NL80211_IFACE_COMB_LIMITS], rem_limit) {
++                      err = nla_parse_nested(tb_limit, MAX_NL80211_IFACE_LIMIT,
++                                                 nl_limit, iface_limit_policy);
++                      if (err || !tb_limit[NL80211_IFACE_LIMIT_TYPES]) {
++                              printf("<failed to parse>\n");
++                              goto broken_combination;
++                      }
++
++                      if (comma)
++                              printf(", ");
++                      comma = true;
++                      printf("#{ ");
++                      print_iftype_line(tb_limit[NL80211_IFACE_LIMIT_TYPES]);
++                      printf(" } <= %u", nla_get_u32(tb_limit[NL80211_IFACE_LIMIT_MAX]));
++              }
++              printf(",\n\t\t%s   ", indent);
++
++              printf("total <= %d, #channels <= %d%s",
++                      nla_get_u32(tb_comb[NL80211_IFACE_COMB_MAXNUM]),
++                      nla_get_u32(tb_comb[NL80211_IFACE_COMB_NUM_CHANNELS]),
++                      tb_comb[NL80211_IFACE_COMB_STA_AP_BI_MATCH] ?
++                              ", STA/AP BI must match" : "");
++              if (tb_comb[NL80211_IFACE_COMB_RADAR_DETECT_WIDTHS]) {
++                      unsigned long widths = nla_get_u32(tb_comb[NL80211_IFACE_COMB_RADAR_DETECT_WIDTHS]);
++
++                      if (widths) {
++                              int width;
++                              bool first = true;
++
++                              printf(", radar detect widths: {");
++                              for (width = 0; width < 32; width++)
++                                      if (widths & (1 << width)) {
++                                              printf("%s %s",
++                                                         first ? "":",",
++                                                         channel_width_name(width));
++                                              first = false;
++                                      }
++                              printf(" }\n");
++                      }
++              }
++              printf("\n");
++broken_combination:
++              ;
++      }
++
++      if (!have_combinations)
++              printf("\t%sinterface combinations are not supported\n", indent);
++}
++
++static void print_radio_freq(struct nlattr *freqs)
++{
++      struct nlattr *freq;
++      int rem;
++
++      nla_for_each_nested(freq, freqs, rem) {
++              static struct nla_policy freq_policy[NL80211_WIPHY_RADIO_FREQ_ATTR_MAX + 1] = {
++                      [NL80211_WIPHY_RADIO_FREQ_ATTR_START] = { .type = NLA_U32 },
++                      [NL80211_WIPHY_RADIO_FREQ_ATTR_END] = { .type = NLA_U32 },
++              };
++              struct nlattr *tb[NL80211_WIPHY_RADIO_FREQ_ATTR_MAX + 1];
++              uint32_t start, end;
++
++              if (nla_type(freq) != NL80211_WIPHY_RADIO_ATTR_FREQ_RANGE)
++                      continue;
++
++              if (nla_parse_nested(tb, NL80211_WIPHY_RADIO_ATTR_MAX + 1,
++                                   freq, freq_policy) ||
++                  !tb[NL80211_WIPHY_RADIO_FREQ_ATTR_START] ||
++                  !tb[NL80211_WIPHY_RADIO_FREQ_ATTR_END])
++                      continue;
++
++              start = nla_get_u32(tb[NL80211_WIPHY_RADIO_FREQ_ATTR_START]);
++              end = nla_get_u32(tb[NL80211_WIPHY_RADIO_FREQ_ATTR_END]);
++
++              printf("\t\tfreq range: %.1f MHz - %.1f MHz\n", (float)start / 1000, (float)end / 1000);
++      }
++}
++
++static void print_radios(struct nlattr *radios)
++{
++      struct nlattr *radio;
++      int rem, idx = 0;
++
++      nla_for_each_nested(radio, radios, rem) {
++              static struct nla_policy radio_policy[NL80211_WIPHY_RADIO_ATTR_MAX + 1] = {
++                      [NL80211_WIPHY_RADIO_ATTR_INDEX] = { .type = NLA_U32 },
++              };
++              struct nlattr *tb[NL80211_WIPHY_RADIO_ATTR_MAX + 1];
++
++              if (nla_parse_nested(tb, NL80211_WIPHY_RADIO_ATTR_MAX + 1,
++                                   radio, radio_policy) ||
++                  !tb[NL80211_WIPHY_RADIO_ATTR_INDEX])
++                      continue;
++
++              printf("\twiphy radio %d:\n", nla_get_u32(tb[NL80211_WIPHY_RADIO_ATTR_INDEX]));
++              print_radio_freq(radio);
++              print_interface_combinations(radio, true);
++      }
++}
++
+ static int print_phy_handler(struct nl_msg *msg, void *arg)
+ {
+       struct nlattr *tb_msg[NL80211_ATTR_MAX + 1];
+@@ -565,93 +710,11 @@ next:
+                                 "\t\t", tb_msg[NL80211_ATTR_SOFTWARE_IFTYPES]);
+ #endif
+-      if (tb_msg[NL80211_ATTR_INTERFACE_COMBINATIONS]) {
+-              struct nlattr *nl_combi;
+-              int rem_combi;
+-              bool have_combinations = false;
+-
+-              nla_for_each_nested(nl_combi, tb_msg[NL80211_ATTR_INTERFACE_COMBINATIONS], rem_combi) {
+-                      static struct nla_policy iface_combination_policy[NUM_NL80211_IFACE_COMB] = {
+-                              [NL80211_IFACE_COMB_LIMITS] = { .type = NLA_NESTED },
+-                              [NL80211_IFACE_COMB_MAXNUM] = { .type = NLA_U32 },
+-                              [NL80211_IFACE_COMB_STA_AP_BI_MATCH] = { .type = NLA_FLAG },
+-                              [NL80211_IFACE_COMB_NUM_CHANNELS] = { .type = NLA_U32 },
+-                              [NL80211_IFACE_COMB_RADAR_DETECT_WIDTHS] = { .type = NLA_U32 },
+-                      };
+-                      struct nlattr *tb_comb[NUM_NL80211_IFACE_COMB];
+-                      static struct nla_policy iface_limit_policy[NUM_NL80211_IFACE_LIMIT] = {
+-                              [NL80211_IFACE_LIMIT_TYPES] = { .type = NLA_NESTED },
+-                              [NL80211_IFACE_LIMIT_MAX] = { .type = NLA_U32 },
+-                      };
+-                      struct nlattr *tb_limit[NUM_NL80211_IFACE_LIMIT];
+-                      struct nlattr *nl_limit;
+-                      int err, rem_limit;
+-                      bool comma = false;
+-
+-                      if (!have_combinations) {
+-                              printf("\tvalid interface combinations:\n");
+-                              have_combinations = true;
+-                      }
+-
+-                      printf("\t\t * ");
++      if (tb_msg[NL80211_ATTR_INTERFACE_COMBINATIONS])
++              print_interface_combinations(tb_msg[NL80211_ATTR_INTERFACE_COMBINATIONS], false);
+-                      err = nla_parse_nested(tb_comb, MAX_NL80211_IFACE_COMB,
+-                                             nl_combi, iface_combination_policy);
+-                      if (err || !tb_comb[NL80211_IFACE_COMB_LIMITS] ||
+-                          !tb_comb[NL80211_IFACE_COMB_MAXNUM] ||
+-                          !tb_comb[NL80211_IFACE_COMB_NUM_CHANNELS]) {
+-                              printf(" <failed to parse>\n");
+-                              goto broken_combination;
+-                      }
+-
+-                      nla_for_each_nested(nl_limit, tb_comb[NL80211_IFACE_COMB_LIMITS], rem_limit) {
+-                              err = nla_parse_nested(tb_limit, MAX_NL80211_IFACE_LIMIT,
+-                                                     nl_limit, iface_limit_policy);
+-                              if (err || !tb_limit[NL80211_IFACE_LIMIT_TYPES]) {
+-                                      printf("<failed to parse>\n");
+-                                      goto broken_combination;
+-                              }
+-
+-                              if (comma)
+-                                      printf(", ");
+-                              comma = true;
+-                              printf("#{ ");
+-                              print_iftype_line(tb_limit[NL80211_IFACE_LIMIT_TYPES]);
+-                              printf(" } <= %u", nla_get_u32(tb_limit[NL80211_IFACE_LIMIT_MAX]));
+-                      }
+-                      printf(",\n\t\t   ");
+-
+-                      printf("total <= %d, #channels <= %d%s",
+-                              nla_get_u32(tb_comb[NL80211_IFACE_COMB_MAXNUM]),
+-                              nla_get_u32(tb_comb[NL80211_IFACE_COMB_NUM_CHANNELS]),
+-                              tb_comb[NL80211_IFACE_COMB_STA_AP_BI_MATCH] ?
+-                                      ", STA/AP BI must match" : "");
+-                      if (tb_comb[NL80211_IFACE_COMB_RADAR_DETECT_WIDTHS]) {
+-                              unsigned long widths = nla_get_u32(tb_comb[NL80211_IFACE_COMB_RADAR_DETECT_WIDTHS]);
+-
+-                              if (widths) {
+-                                      int width;
+-                                      bool first = true;
+-
+-                                      printf(", radar detect widths: {");
+-                                      for (width = 0; width < 32; width++)
+-                                              if (widths & (1 << width)) {
+-                                                      printf("%s %s",
+-                                                             first ? "":",",
+-                                                             channel_width_name(width));
+-                                                      first = false;
+-                                              }
+-                                      printf(" }\n");
+-                              }
+-                      }
+-                      printf("\n");
+-broken_combination:
+-                      ;
+-              }
+-
+-              if (!have_combinations)
+-                      printf("\tinterface combinations are not supported\n");
+-      }
++      if (tb_msg[NL80211_ATTR_WIPHY_RADIOS])
++              print_radios(tb_msg[NL80211_ATTR_WIPHY_RADIOS]);
+ #ifdef IW_FULL
+       if (tb_msg[NL80211_ATTR_SUPPORTED_COMMANDS]) {
diff --git a/package/network/utils/iw/patches/310-vif_radio_mask.patch b/package/network/utils/iw/patches/310-vif_radio_mask.patch
new file mode 100644 (file)
index 0000000..724f71c
--- /dev/null
@@ -0,0 +1,99 @@
+--- a/interface.c
++++ b/interface.c
+@@ -226,6 +226,43 @@ nla_put_failure:
+       return 1;
+ }
++static int parse_radio_list(char *str, struct nl_msg *msg)
++{
++      unsigned int mask = 0;
++      unsigned long id;
++      char *end;
++
++      if (!str)
++              return 1;
++
++      if (!strcmp(str, "all"))
++              goto out;
++
++      while (1) {
++              if (!*str)
++                      return 1;
++
++              id = strtoul(str, &end, 0);
++              if (id > 31)
++                      return 1;
++
++              mask |= 1 << id;
++              if (!*end)
++                      break;
++
++              if (end == str || *end != ',')
++                      return 1;
++
++              str = end + 1;
++      }
++
++out:
++      NLA_PUT_U32(msg, NL80211_ATTR_VIF_RADIO_MASK, mask);
++      return 0;
++nla_put_failure:
++      return 1;
++}
++
+ static int handle_interface_add(struct nl80211_state *state,
+                               struct nl_msg *msg,
+                               int argc, char **argv,
+@@ -287,6 +324,15 @@ try_another:
+                               fprintf(stderr, "flags error\n");
+                               return 2;
+                       }
++              } else if (strcmp(argv[0], "radios") == 0) {
++                      argc--;
++                      argv++;
++                      if (parse_radio_list(argv[0], msg)) {
++                              fprintf(stderr, "Invalid radio list\n");
++                              return 2;
++                      }
++                      argc--;
++                      argv++;
+               } else {
+                       return 1;
+               }
+@@ -306,14 +352,14 @@ try_another:
+  nla_put_failure:
+       return -ENOBUFS;
+ }
+-COMMAND(interface, add, "<name> type <type> [mesh_id <meshid>] [4addr on|off] [flags <flag>*] [addr <mac-addr>]",
++COMMAND(interface, add, "<name> type <type> [mesh_id <meshid>] [4addr on|off] [flags <flag>*] [addr <mac-addr>] [radios all|<id>[,<id>...]]",
+       NL80211_CMD_NEW_INTERFACE, 0, CIB_PHY, handle_interface_add,
+       "Add a new virtual interface with the given configuration.\n"
+       IFACE_TYPES "\n\n"
+       "The flags are only used for monitor interfaces, valid flags are:\n"
+       VALID_FLAGS "\n\n"
+       "The mesh_id is used only for mesh mode.");
+-COMMAND(interface, add, "<name> type <type> [mesh_id <meshid>] [4addr on|off] [flags <flag>*] [addr <mac-addr>]",
++COMMAND(interface, add, "<name> type <type> [mesh_id <meshid>] [4addr on|off] [flags <flag>*] [addr <mac-addr>] [radios all|<id>[,<id>...]]",
+       NL80211_CMD_NEW_INTERFACE, 0, CIB_NETDEV, handle_interface_add, NULL);
+ static int handle_interface_del(struct nl80211_state *state,
+@@ -493,6 +539,19 @@ static int print_iface_handler(struct nl
+                       printf("\n");
+               }
+       }
++
++      if (tb_msg[NL80211_ATTR_VIF_RADIO_MASK]) {
++              uint32_t mask = nla_get_u32(tb_msg[NL80211_ATTR_VIF_RADIO_MASK]);
++              int i;
++
++              if (mask) {
++                      printf("%s\tRadios:", indent);
++                      for (i = 0; mask; i++, mask >>= 1)
++                              if (mask & 1)
++                                      printf(" %d", i);
++                      printf("\n");
++              }
++      }
+       return NL_SKIP;
+ }