From 5c1ee08e2dfa41bb887b9d33eeebfde5e5da574e Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sat, 29 Jun 2024 13:03:30 +0200 Subject: [PATCH] iw: add multi-radio support Signed-off-by: Felix Fietkau --- .../utils/iw/patches/001-nl80211_h_sync.patch | 98 +++++++ .../utils/iw/patches/300-wiphy_radios.patch | 252 ++++++++++++++++++ .../utils/iw/patches/310-vif_radio_mask.patch | 99 +++++++ 3 files changed, 449 insertions(+) create mode 100644 package/network/utils/iw/patches/001-nl80211_h_sync.patch create mode 100644 package/network/utils/iw/patches/300-wiphy_radios.patch create mode 100644 package/network/utils/iw/patches/310-vif_radio_mask.patch 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 index 0000000000..f48c083575 --- /dev/null +++ b/package/network/utils/iw/patches/001-nl80211_h_sync.patch @@ -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 index 0000000000..534addf897 --- /dev/null +++ b/package/network/utils/iw/patches/300-wiphy_radios.patch @@ -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(" \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("\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(" \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("\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 index 0000000000..724f71cdad --- /dev/null +++ b/package/network/utils/iw/patches/310-vif_radio_mask.patch @@ -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, " type [mesh_id ] [4addr on|off] [flags *] [addr ]", ++COMMAND(interface, add, " type [mesh_id ] [4addr on|off] [flags *] [addr ] [radios all|[,...]]", + 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, " type [mesh_id ] [4addr on|off] [flags *] [addr ]", ++COMMAND(interface, add, " type [mesh_id ] [4addr on|off] [flags *] [addr ] [radios all|[,...]]", + 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; + } -- 2.30.2