wifi: ath12k: advertise multi device interface combination
authorKarthikeyan Periyasamy <quic_periyasa@quicinc.com>
Mon, 9 Dec 2024 18:54:18 +0000 (20:54 +0200)
committerJeff Johnson <jeff.johnson@oss.qualcomm.com>
Mon, 16 Dec 2024 20:46:58 +0000 (12:46 -0800)
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>
drivers/net/wireless/ath/ath12k/mac.c

index 504ad36caca9ef5be9fe81b8504ec41459060a23..705e0b673435851f8f272888215825a9247b3452 100644 (file)
@@ -10336,14 +10336,20 @@ static bool ath12k_mac_is_iface_mode_enable(struct ath12k_hw *ah,
 {
        struct ath12k *ar;
        int i;
-       u16 interface_modes, mode;
-       bool is_enable = true;
+       u16 interface_modes, mode = 0;
+       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) {
                interface_modes = ar->ab->hw_params->interface_modes;
-               if (!(interface_modes & mode)) {
-                       is_enable = false;
+               if (interface_modes & mode) {
+                       is_enable = true;
                        break;
                }
        }
@@ -10351,31 +10357,20 @@ static bool ath12k_mac_is_iface_mode_enable(struct ath12k_hw *ah,
        return is_enable;
 }
 
-static void ath12k_mac_cleanup_iface_combinations(struct ath12k_hw *ah)
-{
-       struct wiphy *wiphy = ah->hw->wiphy;
-
-       kfree(wiphy->iface_combinations[0].limits);
-       kfree(wiphy->iface_combinations);
-}
-
-static int ath12k_mac_setup_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;
-       struct ieee80211_iface_combination *combinations;
+       u16 interface_modes = ar->ab->hw_params->interface_modes;
        struct ieee80211_iface_limit *limits;
        int n_limits, max_interfaces;
        bool ap, mesh, p2p;
 
-       ap = ath12k_mac_is_iface_mode_enable(ah, NL80211_IFTYPE_AP);
-       p2p = ath12k_mac_is_iface_mode_enable(ah, NL80211_IFTYPE_P2P_DEVICE);
+       ap = interface_modes & BIT(NL80211_IFTYPE_AP);
+       p2p = interface_modes & BIT(NL80211_IFTYPE_P2P_DEVICE);
 
        mesh = IS_ENABLED(CONFIG_MAC80211_MESH) &&
-               ath12k_mac_is_iface_mode_enable(ah, NL80211_IFTYPE_MESH_POINT);
-
-       combinations = kzalloc(sizeof(*combinations), GFP_KERNEL);
-       if (!combinations)
-               return -ENOMEM;
+              (interface_modes & BIT(NL80211_IFTYPE_MESH_POINT));
 
        if ((ap || mesh) && !p2p) {
                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);
-       if (!limits) {
-               kfree(combinations);
+       if (!limits)
                return -ENOMEM;
-       }
 
        limits[0].max = 1;
        limits[0].types |= BIT(NL80211_IFTYPE_STATION);
@@ -10411,26 +10404,181 @@ static int ath12k_mac_setup_iface_combinations(struct ath12k_hw *ah)
 
        if (p2p) {
                limits[1].types |= BIT(NL80211_IFTYPE_P2P_CLIENT) |
-                                  BIT(NL80211_IFTYPE_P2P_GO);
+                                       BIT(NL80211_IFTYPE_P2P_GO);
                limits[2].max = 1;
                limits[2].types |= BIT(NL80211_IFTYPE_P2P_DEVICE);
        }
 
-       combinations[0].limits = limits;
-       combinations[0].n_limits = n_limits;
-       combinations[0].max_interfaces = max_interfaces;
-       combinations[0].num_different_channels = 1;
-       combinations[0].beacon_int_infra_match = true;
-       combinations[0].beacon_int_min_gcd = 100;
-       combinations[0].radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) |
-                                               BIT(NL80211_CHAN_WIDTH_20) |
-                                               BIT(NL80211_CHAN_WIDTH_40) |
-                                               BIT(NL80211_CHAN_WIDTH_80);
+       comb[0].limits = limits;
+       comb[0].n_limits = n_limits;
+       comb[0].max_interfaces = max_interfaces;
+       comb[0].num_different_channels = 1;
+       comb[0].beacon_int_infra_match = true;
+       comb[0].beacon_int_min_gcd = 100;
+       comb[0].radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) |
+                                       BIT(NL80211_CHAN_WIDTH_20) |
+                                       BIT(NL80211_CHAN_WIDTH_40) |
+                                       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->n_iface_combinations = 1;
 
        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[] = {