From 79c0730a7b773b713524140af372d00c9312faa5 Mon Sep 17 00:00:00 2001 From: tinnci Date: Mon, 19 Jan 2026 18:27:40 +0800 Subject: [PATCH] wifi: parse NL80211_ATTR_INTERFACE_COMBINATIONS from kernel Parse the interface combination capabilities reported by the kernel via NL80211_ATTR_INTERFACE_COMBINATIONS in nl80211_wiphy_info_handler(). This information is essential for determining whether the hardware supports running multiple interface types concurrently (e.g., station mode + P2P client). The parsed data is stored in new data structures: - NMWifiIfaceCombLimit: stores max interfaces and type bitmask per limit - NMWifiIfaceCombination: stores limits, max_num, num_channels per combo - NMWifiIfaceCombinations: stores all valid combinations Also add nm_wifi_utils_can_concurrent() API to check if two interface types can operate concurrently based on the parsed capabilities. This is the first step towards supporting Wi-Fi P2P concurrent connections without disconnecting the main infrastructure connection. --- .../wifi/nm-wifi-utils-nl80211.c | 94 +++++++++++++++++++ .../wifi/nm-wifi-utils-private.h | 33 +++++++ src/libnm-platform/wifi/nm-wifi-utils.c | 90 ++++++++++++++++++ src/libnm-platform/wifi/nm-wifi-utils.h | 17 ++++ 4 files changed, 234 insertions(+) diff --git a/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c b/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c index 020c054b17..005694fad6 100644 --- a/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c +++ b/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c @@ -575,8 +575,18 @@ struct nl80211_device_info { gboolean supported; gboolean success; gboolean can_wowlan; + + /* Interface combinations from NL80211_ATTR_INTERFACE_COMBINATIONS */ + GArray *iface_combinations; }; +static void +_nm_wifi_iface_combination_clear(NMWifiIfaceCombination *comb) +{ + if (comb->limits) + g_array_unref(comb->limits); +} + #define WLAN_CIPHER_SUITE_USE_GROUP 0x000FAC00 #define WLAN_CIPHER_SUITE_WEP40 0x000FAC01 #define WLAN_CIPHER_SUITE_TKIP 0x000FAC02 @@ -765,6 +775,87 @@ nl80211_wiphy_info_handler(const struct nl_msg *msg, void *arg) if (tb[NL80211_ATTR_SUPPORT_IBSS_RSN]) info->caps |= _NM_WIFI_DEVICE_CAP_IBSS_RSN; + /* Parse interface combinations for concurrent mode support */ + if (tb[NL80211_ATTR_INTERFACE_COMBINATIONS]) { + struct nlattr *nl_comb; + int rem_comb; + int n_combinations = 0; + + /* First pass: count combinations */ + nla_for_each_nested (nl_comb, tb[NL80211_ATTR_INTERFACE_COMBINATIONS], rem_comb) { + n_combinations++; + } + + if (n_combinations > 0) { + GArray *combs; + + combs = g_array_sized_new(FALSE, TRUE, sizeof(NMWifiIfaceCombination), n_combinations); + g_array_set_clear_func(combs, (GDestroyNotify) _nm_wifi_iface_combination_clear); + + /* Second pass: parse each combination */ + nla_for_each_nested (nl_comb, tb[NL80211_ATTR_INTERFACE_COMBINATIONS], rem_comb) { + struct nlattr *tb_comb[MAX_NL80211_IFACE_COMB + 1]; + NMWifiIfaceCombination comb = {0}; + + if (nla_parse_nested_arr(tb_comb, nl_comb, NULL) < 0) + continue; + + if (tb_comb[NL80211_IFACE_COMB_MAXNUM]) + comb.max_num = nla_get_u32(tb_comb[NL80211_IFACE_COMB_MAXNUM]); + + if (tb_comb[NL80211_IFACE_COMB_NUM_CHANNELS]) + comb.num_channels = nla_get_u32(tb_comb[NL80211_IFACE_COMB_NUM_CHANNELS]); + + /* Parse limits within this combination */ + if (tb_comb[NL80211_IFACE_COMB_LIMITS]) { + struct nlattr *nl_limit; + int rem_limit; + int n_limits = 0; + + /* Count limits */ + nla_for_each_nested (nl_limit, tb_comb[NL80211_IFACE_COMB_LIMITS], rem_limit) { + n_limits++; + } + + if (n_limits > 0) { + comb.limits = + g_array_sized_new(FALSE, TRUE, sizeof(NMWifiIfaceCombLimit), n_limits); + + nla_for_each_nested (nl_limit, + tb_comb[NL80211_IFACE_COMB_LIMITS], + rem_limit) { + struct nlattr *tb_limit[MAX_NL80211_IFACE_LIMIT + 1]; + NMWifiIfaceCombLimit limit = {0}; + + if (nla_parse_nested_arr(tb_limit, nl_limit, NULL) < 0) + continue; + + if (tb_limit[NL80211_IFACE_LIMIT_MAX]) + limit.max = nla_get_u32(tb_limit[NL80211_IFACE_LIMIT_MAX]); + + if (tb_limit[NL80211_IFACE_LIMIT_TYPES]) { + struct nlattr *nl_type; + int rem_type; + + nla_for_each_nested (nl_type, + tb_limit[NL80211_IFACE_LIMIT_TYPES], + rem_type) { + limit.types |= (1 << nla_type(nl_type)); + } + } + g_array_append_val(comb.limits, limit); + } + } + } + g_array_append_val(combs, comb); + } + + info->iface_combinations = combs; + + _LOGD("parsed %d interface combinations from kernel", n_combinations); + } + } + info->success = TRUE; return NL_SKIP; @@ -920,6 +1011,9 @@ nm_wifi_utils_nl80211_new(struct nl_sock *genl, guint16 genl_family_id, int ifin self->parent.caps = device_info.caps; self->can_wowlan = device_info.can_wowlan; + /* Store interface combination capabilities for concurrent mode support */ + self->parent.iface_combinations = device_info.iface_combinations; + _LOGD("using nl80211 for Wi-Fi device control"); return (NMWifiUtils *) g_steal_pointer(&self); diff --git a/src/libnm-platform/wifi/nm-wifi-utils-private.h b/src/libnm-platform/wifi/nm-wifi-utils-private.h index abec38e21d..da1f999d53 100644 --- a/src/libnm-platform/wifi/nm-wifi-utils-private.h +++ b/src/libnm-platform/wifi/nm-wifi-utils-private.h @@ -8,6 +8,36 @@ #include "nm-wifi-utils.h" +/** + * NMWifiIfaceCombLimit: + * @max: Maximum number of interfaces in this limit set + * @types: Bitmask of interface types (NL80211_IFTYPE_*) + * + * Represents a single interface limit within a combination. + */ +typedef struct { + guint16 max; + guint16 types; +} NMWifiIfaceCombLimit; + +/** + * NMWifiIfaceCombination: + * @limits: Array of interface limits + * @n_limits: Number of limits + * @max_num: Maximum total number of interfaces + * @num_channels: Number of different channels that may be used + * @sta_ap_bi_match: Whether beacon intervals must match + * + * Represents a valid interface combination from the kernel. + */ +typedef struct { + GArray *limits; + guint32 max_num; + guint32 num_channels; +} NMWifiIfaceCombination; + + + typedef struct { GObjectClass parent; @@ -61,6 +91,9 @@ struct NMWifiUtils { int ifindex; _NMDeviceWifiCapabilities caps; + + /* Interface combination capabilities from NL80211_ATTR_INTERFACE_COMBINATIONS */ + GArray *iface_combinations; }; #endif /* __WIFI_UTILS_PRIVATE_H__ */ diff --git a/src/libnm-platform/wifi/nm-wifi-utils.c b/src/libnm-platform/wifi/nm-wifi-utils.c index 596ff64477..3c0ac8b224 100644 --- a/src/libnm-platform/wifi/nm-wifi-utils.c +++ b/src/libnm-platform/wifi/nm-wifi-utils.c @@ -210,3 +210,93 @@ nm_wifi_utils_indicate_addressing_running(NMWifiUtils *data, gboolean running) return klass->indicate_addressing_running ? klass->indicate_addressing_running(data, running) : FALSE; } + +/** + * nm_wifi_utils_can_concurrent: + * @data: The NMWifiUtils instance + * @iftype1: First interface type (NL80211_IFTYPE_*) + * @iftype2: Second interface type (NL80211_IFTYPE_*) + * @out_num_channels: (out) (optional): Number of different channels allowed + * + * Check if two interface types can operate concurrently based on + * the hardware's interface combination capabilities parsed from + * NL80211_ATTR_INTERFACE_COMBINATIONS. + * + * The algorithm tries to find a valid allocation: place iftype1 in one Limit + * and iftype2 in another Limit (or the same Limit if its max >= 2). + * + * Returns: %TRUE if the combination is allowed, %FALSE otherwise. + */ +gboolean +nm_wifi_utils_can_concurrent(NMWifiUtils *data, + guint32 iftype1, + guint32 iftype2, + guint8 *out_num_channels) +{ + GArray *combs; + guint i, j, k; + + g_return_val_if_fail(data != NULL, FALSE); + + combs = data->iface_combinations; + if (!combs || combs->len == 0) + return FALSE; + + /* Check each combination to find a valid allocation for both interface types */ + for (i = 0; i < combs->len; i++) { + NMWifiIfaceCombination *comb = &g_array_index(combs, NMWifiIfaceCombination, i); + + /* Quick check: we need at least 2 interfaces total for concurrent operation */ + if (comb->max_num < 2) + continue; + + if (!comb->limits || comb->limits->len == 0) + continue; + + /* Try to allocate iftype1 to limit[j] and iftype2 to limit[k]. + * They can be the same limit (j == k) or different limits (j != k). + */ + for (j = 0; j < comb->limits->len; j++) { + NMWifiIfaceCombLimit *limit_a = &g_array_index(comb->limits, NMWifiIfaceCombLimit, j); + + /* Check if limit_a supports iftype1 */ + if (!(limit_a->types & (1 << iftype1))) + continue; + + for (k = 0; k < comb->limits->len; k++) { + NMWifiIfaceCombLimit *limit_b = + &g_array_index(comb->limits, NMWifiIfaceCombLimit, k); + + /* Check if limit_b supports iftype2 */ + if (!(limit_b->types & (1 << iftype2))) + continue; + + /* Found two limits that support the requested types. + * Now verify the constraints. + */ + + if (j == k) { + /* Case 1: Both types in the same limit. + * The limit must allow at least 2 interfaces. + */ + if (limit_a->max >= 2) { + if (out_num_channels) + *out_num_channels = comb->num_channels; + return TRUE; + } + } else { + /* Case 2: Types in different limits. + * Each limit only needs to support 1 interface (which is + * guaranteed since we're here), and we already verified + * comb->max_num >= 2. + */ + if (out_num_channels) + *out_num_channels = comb->num_channels; + return TRUE; + } + } + } + } + + return FALSE; +} diff --git a/src/libnm-platform/wifi/nm-wifi-utils.h b/src/libnm-platform/wifi/nm-wifi-utils.h index 0532817eb5..dc06eec474 100644 --- a/src/libnm-platform/wifi/nm-wifi-utils.h +++ b/src/libnm-platform/wifi/nm-wifi-utils.h @@ -69,4 +69,21 @@ gboolean nm_wifi_utils_set_mesh_channel(NMWifiUtils *data, guint32 channel); gboolean nm_wifi_utils_set_mesh_ssid(NMWifiUtils *data, const guint8 *ssid, gsize len); +/** + * nm_wifi_utils_can_concurrent: + * @data: The NMWifiUtils instance + * @iftype1: First interface type (NL80211_IFTYPE_*) + * @iftype2: Second interface type (NL80211_IFTYPE_*) + * @out_num_channels: (out) (optional): Number of different channels allowed + * + * Check if two interface types can operate concurrently based on + * the hardware's interface combination capabilities. + * + * Returns: %TRUE if the combination is allowed, %FALSE otherwise. + */ +gboolean nm_wifi_utils_can_concurrent(NMWifiUtils *data, + guint32 iftype1, + guint32 iftype2, + guint8 *out_num_channels); + #endif /* __WIFI_UTILS_H__ */