wifi: ath12k: advertise multi device interface combination

The prerequisite for MLO support in cfg80211/mac80211 requires that all the
links participating in MLO belong to the same wiphy/struct ieee80211_hw. The
driver needs to group multiple discrete hardware components, each acting as a
link in MLO, under one wiphy. Consequently, the driver advertises
multi-hardware device interface combination capabilities specific to the radio,
including supported frequencies. The global interface combination represent the
combined interface capabilities.

Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.3.1-00173-QCAHKSWPL_SILICONZ-1
Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3

Signed-off-by: Karthikeyan Periyasamy <quic_periyasa@quicinc.com>
Signed-off-by: Kalle Valo <quic_kvalo@quicinc.com>
Link: https://patch.msgid.link/20241209185421.376381-7-kvalo@kernel.org
Signed-off-by: Jeff Johnson <jeff.johnson@oss.qualcomm.com>
This commit is contained in:
Karthikeyan Periyasamy 2024-12-09 20:54:18 +02:00 committed by Jeff Johnson
parent 3c9bc818b8
commit ae6b065282

View File

@ -10336,14 +10336,20 @@ static bool ath12k_mac_is_iface_mode_enable(struct ath12k_hw *ah,
{ {
struct ath12k *ar; struct ath12k *ar;
int i; int i;
u16 interface_modes, mode; u16 interface_modes, mode = 0;
bool is_enable = true; bool is_enable = false;
if (type == NL80211_IFTYPE_MESH_POINT) {
if (IS_ENABLED(CONFIG_MAC80211_MESH))
mode = BIT(type);
} else {
mode = BIT(type);
}
mode = BIT(type);
for_each_ar(ah, ar, i) { for_each_ar(ah, ar, i) {
interface_modes = ar->ab->hw_params->interface_modes; interface_modes = ar->ab->hw_params->interface_modes;
if (!(interface_modes & mode)) { if (interface_modes & mode) {
is_enable = false; is_enable = true;
break; break;
} }
} }
@ -10351,31 +10357,20 @@ static bool ath12k_mac_is_iface_mode_enable(struct ath12k_hw *ah,
return is_enable; return is_enable;
} }
static void ath12k_mac_cleanup_iface_combinations(struct ath12k_hw *ah) static int
ath12k_mac_setup_radio_iface_comb(struct ath12k *ar,
struct ieee80211_iface_combination *comb)
{ {
struct wiphy *wiphy = ah->hw->wiphy; u16 interface_modes = ar->ab->hw_params->interface_modes;
kfree(wiphy->iface_combinations[0].limits);
kfree(wiphy->iface_combinations);
}
static int ath12k_mac_setup_iface_combinations(struct ath12k_hw *ah)
{
struct wiphy *wiphy = ah->hw->wiphy;
struct ieee80211_iface_combination *combinations;
struct ieee80211_iface_limit *limits; struct ieee80211_iface_limit *limits;
int n_limits, max_interfaces; int n_limits, max_interfaces;
bool ap, mesh, p2p; bool ap, mesh, p2p;
ap = ath12k_mac_is_iface_mode_enable(ah, NL80211_IFTYPE_AP); ap = interface_modes & BIT(NL80211_IFTYPE_AP);
p2p = ath12k_mac_is_iface_mode_enable(ah, NL80211_IFTYPE_P2P_DEVICE); p2p = interface_modes & BIT(NL80211_IFTYPE_P2P_DEVICE);
mesh = IS_ENABLED(CONFIG_MAC80211_MESH) && mesh = IS_ENABLED(CONFIG_MAC80211_MESH) &&
ath12k_mac_is_iface_mode_enable(ah, NL80211_IFTYPE_MESH_POINT); (interface_modes & BIT(NL80211_IFTYPE_MESH_POINT));
combinations = kzalloc(sizeof(*combinations), GFP_KERNEL);
if (!combinations)
return -ENOMEM;
if ((ap || mesh) && !p2p) { if ((ap || mesh) && !p2p) {
n_limits = 2; n_limits = 2;
@ -10392,10 +10387,8 @@ static int ath12k_mac_setup_iface_combinations(struct ath12k_hw *ah)
} }
limits = kcalloc(n_limits, sizeof(*limits), GFP_KERNEL); limits = kcalloc(n_limits, sizeof(*limits), GFP_KERNEL);
if (!limits) { if (!limits)
kfree(combinations);
return -ENOMEM; return -ENOMEM;
}
limits[0].max = 1; limits[0].max = 1;
limits[0].types |= BIT(NL80211_IFTYPE_STATION); limits[0].types |= BIT(NL80211_IFTYPE_STATION);
@ -10411,26 +10404,181 @@ static int ath12k_mac_setup_iface_combinations(struct ath12k_hw *ah)
if (p2p) { if (p2p) {
limits[1].types |= BIT(NL80211_IFTYPE_P2P_CLIENT) | limits[1].types |= BIT(NL80211_IFTYPE_P2P_CLIENT) |
BIT(NL80211_IFTYPE_P2P_GO); BIT(NL80211_IFTYPE_P2P_GO);
limits[2].max = 1; limits[2].max = 1;
limits[2].types |= BIT(NL80211_IFTYPE_P2P_DEVICE); limits[2].types |= BIT(NL80211_IFTYPE_P2P_DEVICE);
} }
combinations[0].limits = limits; comb[0].limits = limits;
combinations[0].n_limits = n_limits; comb[0].n_limits = n_limits;
combinations[0].max_interfaces = max_interfaces; comb[0].max_interfaces = max_interfaces;
combinations[0].num_different_channels = 1; comb[0].num_different_channels = 1;
combinations[0].beacon_int_infra_match = true; comb[0].beacon_int_infra_match = true;
combinations[0].beacon_int_min_gcd = 100; comb[0].beacon_int_min_gcd = 100;
combinations[0].radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) | comb[0].radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) |
BIT(NL80211_CHAN_WIDTH_20) | BIT(NL80211_CHAN_WIDTH_20) |
BIT(NL80211_CHAN_WIDTH_40) | BIT(NL80211_CHAN_WIDTH_40) |
BIT(NL80211_CHAN_WIDTH_80); BIT(NL80211_CHAN_WIDTH_80);
return 0;
}
static int
ath12k_mac_setup_global_iface_comb(struct ath12k_hw *ah,
struct wiphy_radio *radio,
u8 n_radio,
struct ieee80211_iface_combination *comb)
{
const struct ieee80211_iface_combination *iter_comb;
struct ieee80211_iface_limit *limits;
int i, j, n_limits;
bool ap, mesh, p2p;
if (!n_radio)
return 0;
ap = ath12k_mac_is_iface_mode_enable(ah, NL80211_IFTYPE_AP);
p2p = ath12k_mac_is_iface_mode_enable(ah, NL80211_IFTYPE_P2P_DEVICE);
mesh = ath12k_mac_is_iface_mode_enable(ah, NL80211_IFTYPE_MESH_POINT);
if ((ap || mesh) && !p2p)
n_limits = 2;
else if (p2p)
n_limits = 3;
else
n_limits = 1;
limits = kcalloc(n_limits, sizeof(*limits), GFP_KERNEL);
if (!limits)
return -ENOMEM;
for (i = 0; i < n_radio; i++) {
iter_comb = radio[i].iface_combinations;
for (j = 0; j < iter_comb->n_limits && j < n_limits; j++) {
limits[j].types |= iter_comb->limits[j].types;
limits[j].max += iter_comb->limits[j].max;
}
comb->max_interfaces += iter_comb->max_interfaces;
comb->num_different_channels += iter_comb->num_different_channels;
comb->radar_detect_widths |= iter_comb->radar_detect_widths;
}
comb->limits = limits;
comb->n_limits = n_limits;
comb->beacon_int_infra_match = true;
comb->beacon_int_min_gcd = 100;
return 0;
}
static
void ath12k_mac_cleanup_iface_comb(const struct ieee80211_iface_combination *iface_comb)
{
kfree(iface_comb[0].limits);
kfree(iface_comb);
}
static void ath12k_mac_cleanup_iface_combinations(struct ath12k_hw *ah)
{
struct wiphy *wiphy = ah->hw->wiphy;
const struct wiphy_radio *radio;
int i;
if (wiphy->n_radio > 0) {
radio = wiphy->radio;
for (i = 0; i < wiphy->n_radio; i++)
ath12k_mac_cleanup_iface_comb(radio[i].iface_combinations);
kfree(wiphy->radio);
}
ath12k_mac_cleanup_iface_comb(wiphy->iface_combinations);
}
static int ath12k_mac_setup_iface_combinations(struct ath12k_hw *ah)
{
struct ieee80211_iface_combination *combinations, *comb;
struct wiphy *wiphy = ah->hw->wiphy;
struct wiphy_radio *radio;
struct ath12k *ar;
int i, ret;
combinations = kzalloc(sizeof(*combinations), GFP_KERNEL);
if (!combinations)
return -ENOMEM;
if (ah->num_radio == 1) {
ret = ath12k_mac_setup_radio_iface_comb(&ah->radio[0],
combinations);
if (ret) {
ath12k_hw_warn(ah, "failed to setup radio interface combinations for one radio: %d",
ret);
goto err_free_combinations;
}
goto out;
}
/* there are multiple radios */
radio = kcalloc(ah->num_radio, sizeof(*radio), GFP_KERNEL);
if (!radio) {
ret = -ENOMEM;
goto err_free_combinations;
}
for_each_ar(ah, ar, i) {
comb = kzalloc(sizeof(*comb), GFP_KERNEL);
if (!comb) {
ret = -ENOMEM;
goto err_free_radios;
}
ret = ath12k_mac_setup_radio_iface_comb(ar, comb);
if (ret) {
ath12k_hw_warn(ah, "failed to setup radio interface combinations for radio %d: %d",
i, ret);
kfree(comb);
goto err_free_radios;
}
radio[i].freq_range = &ar->freq_range;
radio[i].n_freq_range = 1;
radio[i].iface_combinations = comb;
radio[i].n_iface_combinations = 1;
}
ret = ath12k_mac_setup_global_iface_comb(ah, radio, ah->num_radio, combinations);
if (ret) {
ath12k_hw_warn(ah, "failed to setup global interface combinations: %d",
ret);
goto err_free_all_radios;
}
wiphy->radio = radio;
wiphy->n_radio = ah->num_radio;
out:
wiphy->iface_combinations = combinations; wiphy->iface_combinations = combinations;
wiphy->n_iface_combinations = 1; wiphy->n_iface_combinations = 1;
return 0; return 0;
err_free_all_radios:
i = ah->num_radio;
err_free_radios:
while (i--)
ath12k_mac_cleanup_iface_comb(radio[i].iface_combinations);
kfree(radio);
err_free_combinations:
kfree(combinations);
return ret;
} }
static const u8 ath12k_if_types_ext_capa[] = { static const u8 ath12k_if_types_ext_capa[] = {