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.
This commit is contained in:
tinnci 2026-01-19 18:27:40 +08:00
parent 748be9a3e7
commit 79c0730a7b
4 changed files with 234 additions and 0 deletions

View file

@ -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);

View file

@ -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__ */

View file

@ -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;
}

View file

@ -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__ */