]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
Determine NSS and bandwidth for connection on association event
authorHenry Yen <henryyen@google.com>
Thu, 16 Oct 2025 16:11:03 +0000 (16:11 +0000)
committerJouni Malinen <j@w1.fi>
Tue, 9 Dec 2025 11:02:33 +0000 (13:02 +0200)
Parse HT/VHT/HE/EHT capabilities and operation elements from the
(Re)Association Request and Response frames. This is used to get the
max_nss_rx, max_nss_tx, and channel_bandwidth capabilities of the
current association.

Signed-off-by: sunilravi <sunilravi@google.com>
Signed-off-by: Henry Yen <henryyen@google.com>
src/common/ieee802_11_common.c
src/common/ieee802_11_common.h
src/common/ieee802_11_defs.h
wpa_supplicant/events.c
wpa_supplicant/wpa_supplicant_i.h

index b77a6b16a384505e3a49229f2363a1453ed1287c..d257bbd9f8e4e2858b5b9ba8297ba5d48266966e 100644 (file)
@@ -3650,3 +3650,372 @@ int get_basic_mle_link_id(const u8 *buf, size_t len)
 
        return buf[link_id_pos] & BASIC_MLE_STA_CTRL_LINK_ID_MASK;
 }
+
+
+/* Parse HT capabilities to get maximum number of supported spatial streams */
+static int
+parse_ht_mcs_set_for_max_nss(const struct ieee80211_ht_capabilities *htcaps,
+                            bool parse_for_rx)
+{
+       int i, max_nss_rx = 1;
+       u8 supported_tx_mcs_set, tx_mcs_set_defined, tx_rx_mcs_set_not_equal;
+
+       if (!htcaps)
+               return max_nss_rx;
+
+       for (i = 4; i >= 1; i--) {
+               if (htcaps->supported_mcs_set[i - 1] > 0) {
+                       max_nss_rx = i;
+                       break;
+               }
+       }
+       if (parse_for_rx)
+               return max_nss_rx;
+
+       supported_tx_mcs_set = htcaps->supported_mcs_set[12];
+       tx_mcs_set_defined = supported_tx_mcs_set & 0x1;
+       tx_rx_mcs_set_not_equal = (supported_tx_mcs_set >> 1) & 0x1;
+       if (tx_mcs_set_defined && tx_rx_mcs_set_not_equal) {
+               u8 max_nss_tx_field_value = (supported_tx_mcs_set >> 2) & 0x3;
+
+               /*
+                * The maximum number of Tx streams is 1 more than the field
+                * value.
+                */
+               return max_nss_tx_field_value + 1;
+       }
+
+       return max_nss_rx;
+}
+
+
+/* Parse MCS map to get maximum number of supported spatial streams */
+static unsigned int parse_mcs_map_for_max_nss(u16 mcs_map,
+                                             unsigned int max_streams_allowed)
+{
+       unsigned int i, max_nss = 1;
+
+       for (i = max_streams_allowed; i >= 1; i--) {
+               unsigned int stream_map = (mcs_map >> ((i - 1) * 2)) & 0x3;
+
+               /* 3 means unsupported */
+               if (stream_map != 3) {
+                       max_nss = i;
+                       break;
+               }
+       }
+
+       return max_nss;
+}
+
+
+/* Parse capabilities elements to get maximum number of supported spatial
+ * streams */
+unsigned int get_max_nss_capability(struct ieee802_11_elems *elems,
+                                   bool parse_for_rx)
+{
+       unsigned int max_nss = 1;
+       struct ieee80211_ht_capabilities *htcaps =
+               (struct ieee80211_ht_capabilities *) elems->ht_capabilities;
+       struct ieee80211_vht_capabilities *vhtcaps =
+               (struct ieee80211_vht_capabilities *) elems->vht_capabilities;
+       struct ieee80211_he_capabilities *hecaps =
+               (struct ieee80211_he_capabilities *) elems->he_capabilities;
+       le16 mcs_map;
+
+       if (htcaps) {
+               unsigned int max_nss_ht;
+
+               max_nss_ht = parse_ht_mcs_set_for_max_nss(htcaps, parse_for_rx);
+               if (max_nss_ht > max_nss)
+                       max_nss = max_nss_ht;
+       }
+
+       if (vhtcaps) {
+               unsigned int max_nss_vht;
+
+               mcs_map = parse_for_rx ?
+                       vhtcaps->vht_supported_mcs_set.rx_map :
+                       vhtcaps->vht_supported_mcs_set.tx_map;
+               max_nss_vht = parse_mcs_map_for_max_nss(
+                       le_to_host16(mcs_map), VHT_RX_NSS_MAX_STREAMS);
+               if (max_nss_vht > max_nss)
+                       max_nss = max_nss_vht;
+       }
+
+       if (hecaps) {
+               unsigned int max_nss_he;
+
+               mcs_map = parse_for_rx ?
+                       hecaps->he_basic_supported_mcs_set.rx_map :
+                       hecaps->he_basic_supported_mcs_set.tx_map;
+               max_nss_he = parse_mcs_map_for_max_nss(
+                       le_to_host16(mcs_map), HE_NSS_MAX_STREAMS);
+               if (max_nss_he > max_nss)
+                       max_nss = max_nss_he;
+       }
+
+       return max_nss;
+}
+
+
+/* Parse VHT/HE capabilities elements to get supported channel width */
+struct supported_chan_width
+get_supported_channel_width(struct ieee802_11_elems *elems)
+{
+       struct supported_chan_width supported_width;
+       struct ieee80211_vht_capabilities *vhtcaps;
+       struct ieee80211_he_capabilities *hecaps;
+       struct ieee80211_eht_capabilities *ehtcaps;
+
+       supported_width.is_160_supported = false;
+       supported_width.is_80p80_supported = false;
+       supported_width.is_320_supported = false;
+       if (!elems)
+               return supported_width;
+
+       vhtcaps = (struct ieee80211_vht_capabilities *) elems->vht_capabilities;
+       hecaps = (struct ieee80211_he_capabilities *) elems->he_capabilities;
+       ehtcaps = (struct ieee80211_eht_capabilities *) elems->eht_capabilities;
+
+       if (vhtcaps) {
+               u32 vht_capabilities_info =
+                       le_to_host32(vhtcaps->vht_capabilities_info);
+
+               if (vht_capabilities_info & VHT_CAP_SUPP_CHAN_WIDTH_160MHZ)
+                       supported_width.is_160_supported = true;
+               if (vht_capabilities_info &
+                   VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ) {
+                       supported_width.is_160_supported = true;
+                       supported_width.is_80p80_supported = true;
+               }
+       }
+
+       if (hecaps) {
+               u8 channel_width_set = hecaps->he_phy_capab_info[
+                       HE_PHYCAP_CHANNEL_WIDTH_SET_IDX];
+
+               if (channel_width_set &
+                   HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G)
+                       supported_width.is_160_supported = true;
+               if (channel_width_set &
+                   HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G)
+                       supported_width.is_80p80_supported = true;
+       }
+
+       if (ehtcaps) {
+               if (ehtcaps->phy_cap[EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_IDX] &
+                   EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_MASK)
+                       supported_width.is_320_supported = true;
+       }
+
+       return supported_width;
+}
+
+
+/*
+ * Parse VHT operation info fields to get operation channel width
+ * note that VHT operation info fields could come from the VHT Operation element
+ * or from the HE Operation element.
+ */
+static enum chan_width get_vht_operation_channel_width(
+       const struct ieee80211_vht_operation *vht_oper_info)
+{
+       enum chan_width channel_width = CHAN_WIDTH_UNKNOWN;
+       u8 seg0, seg1;
+
+       switch (vht_oper_info->vht_op_info_chwidth) {
+       case 1:
+               seg0 = vht_oper_info->vht_op_info_chan_center_freq_seg0_idx;
+               seg1 = vht_oper_info->vht_op_info_chan_center_freq_seg1_idx;
+               if (seg1 && abs(seg1 - seg0) == 8)
+                       channel_width = CHAN_WIDTH_160;
+               else if (seg1)
+                       channel_width = CHAN_WIDTH_80P80;
+               else
+                       channel_width = CHAN_WIDTH_80;
+               break;
+       case 2:
+               channel_width = CHAN_WIDTH_160;
+               break;
+       case 3:
+               channel_width = CHAN_WIDTH_80P80;
+               break;
+       }
+
+       return channel_width;
+}
+
+
+/* Parse 6 GHz operation info fields to get operation channel width */
+static enum chan_width get_6ghz_operation_channel_width(
+       const struct ieee80211_he_6ghz_oper_info *six_ghz_oper_info)
+{
+       enum chan_width channel_width = CHAN_WIDTH_UNKNOWN;
+       u8 seg0, seg1;
+
+       switch (six_ghz_oper_info->control &
+               HE_6GHZ_OPER_INFO_CTRL_CHAN_WIDTH_MASK) {
+       case 0:
+               channel_width = CHAN_WIDTH_20;
+               break;
+       case 1:
+               channel_width = CHAN_WIDTH_40;
+               break;
+       case 2:
+               channel_width = CHAN_WIDTH_80;
+               break;
+       case 3:
+               seg0 = six_ghz_oper_info->chan_center_freq_seg0;
+               seg1 = six_ghz_oper_info->chan_center_freq_seg1;
+               if (abs(seg1 - seg0) == 8)
+                       channel_width = CHAN_WIDTH_160;
+               else
+                       channel_width = CHAN_WIDTH_80P80;
+               break;
+       }
+
+       return channel_width;
+}
+
+
+/* Parse HE Operation element to get HE operation channel width */
+static enum chan_width get_he_operation_channel_width(
+       const struct ieee80211_he_operation *he_oper, size_t he_oper_len)
+{
+       enum chan_width channel_width = CHAN_WIDTH_UNKNOWN;
+       const u8 *he_oper_u8 = (const u8 *) he_oper;
+       bool is_6ghz_info_present, is_vht_info_present, is_cohosted_bss_present;
+       size_t expected_len;
+
+       if (he_oper_len < HE_OPERATION_ELEM_MIN_LEN)
+               return channel_width;
+
+       is_6ghz_info_present =
+               he_oper->he_oper_params & HE_OPERATION_6GHZ_OPER_INFO;
+       is_vht_info_present =
+               he_oper->he_oper_params & HE_OPERATION_VHT_OPER_INFO;
+       is_cohosted_bss_present =
+               he_oper->he_oper_params & HE_OPERATION_COHOSTED_BSS;
+       expected_len = HE_OPERATION_ELEM_MIN_LEN +
+               (is_6ghz_info_present ? HE_OPERATION_6GHZ_OPER_INFO_LEN : 0) +
+               (is_vht_info_present ? HE_OPERATION_VHT_OPER_INFO_LEN : 0) +
+               (is_cohosted_bss_present ?
+                HE_OPERATION_COHOSTED_BSSID_INDICATOR_LEN : 0);
+
+       if (he_oper_len < expected_len)
+               return channel_width;
+
+       if (is_6ghz_info_present) {
+               struct ieee80211_he_6ghz_oper_info *six_ghz_oper_info =
+                       (struct ieee80211_he_6ghz_oper_info *)
+                       (he_oper_u8 + HE_OPERATION_ELEM_MIN_LEN +
+                        (is_vht_info_present ?
+                         HE_OPERATION_VHT_OPER_INFO_LEN : 0) +
+                        (is_cohosted_bss_present ?
+                         HE_OPERATION_COHOSTED_BSSID_INDICATOR_LEN : 0));
+
+               channel_width =
+                       get_6ghz_operation_channel_width(six_ghz_oper_info);
+       }
+
+       if (channel_width == CHAN_WIDTH_UNKNOWN && is_vht_info_present) {
+               struct ieee80211_vht_operation *vht_oper_info =
+                       (struct ieee80211_vht_operation *)
+                       (he_oper_u8 + HE_OPERATION_ELEM_MIN_LEN);
+
+               channel_width = get_vht_operation_channel_width(vht_oper_info);
+       }
+
+       return channel_width;
+}
+
+
+/* Parse EHT Operation element to get EHT operation channel width */
+static enum chan_width get_eht_operation_channel_width(
+       const struct ieee80211_eht_operation *eht_oper, size_t eht_oper_len)
+{
+       if (eht_oper_len < EHT_OPERATION_ELEM_MIN_LEN + EHT_OPER_INFO_MIN_LEN ||
+           !(eht_oper->oper_params & EHT_OPER_INFO_PRESENT))
+               return CHAN_WIDTH_UNKNOWN;
+
+       switch (eht_oper->oper_info.control & EHT_OPER_CHANNEL_WIDTH_MASK) {
+       case EHT_OPER_CHANNEL_WIDTH_20MHZ:
+               return CHAN_WIDTH_20;
+       case EHT_OPER_CHANNEL_WIDTH_40MHZ:
+               return CHAN_WIDTH_40;
+       case EHT_OPER_CHANNEL_WIDTH_80MHZ:
+               return CHAN_WIDTH_80;
+       case EHT_OPER_CHANNEL_WIDTH_160MHZ:
+               return CHAN_WIDTH_160;
+       case EHT_OPER_CHANNEL_WIDTH_320MHZ:
+               return CHAN_WIDTH_320;
+       default:
+               return CHAN_WIDTH_UNKNOWN;
+       }
+}
+
+
+/* Parse HT/VHT/HE operation elements to get operation channel width */
+enum chan_width get_operation_channel_width(struct ieee802_11_elems *elems)
+{
+       enum chan_width channel_width = CHAN_WIDTH_UNKNOWN;
+       struct ieee80211_ht_operation *ht_oper;
+       struct ieee80211_vht_operation *vht_oper_info;
+       struct ieee80211_he_operation *he_oper;
+       struct ieee80211_eht_operation *eht_oper;
+
+       if (!elems)
+               return channel_width;
+
+       ht_oper = (struct ieee80211_ht_operation *) elems->ht_operation;
+       vht_oper_info = (struct ieee80211_vht_operation *) elems->vht_operation;
+       he_oper = (struct ieee80211_he_operation *) elems->he_operation;
+       eht_oper = (struct ieee80211_eht_operation *) elems->eht_operation;
+
+       if (eht_oper)
+               channel_width = get_eht_operation_channel_width(
+                       eht_oper, elems->eht_operation_len);
+
+       if (channel_width == CHAN_WIDTH_UNKNOWN && he_oper)
+               channel_width = get_he_operation_channel_width(
+                       he_oper, elems->he_operation_len);
+
+       if (channel_width == CHAN_WIDTH_UNKNOWN && vht_oper_info)
+               channel_width = get_vht_operation_channel_width(vht_oper_info);
+
+       if (channel_width == CHAN_WIDTH_UNKNOWN && ht_oper) {
+               u8 sec_chan_offset = ht_oper->ht_param &
+                       HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK;
+
+               channel_width = sec_chan_offset == 0 ?
+                       CHAN_WIDTH_20 : CHAN_WIDTH_40;
+       }
+
+       return channel_width;
+}
+
+
+/*
+ * Get STA operation channel width from AP's operation channel width and
+ * STA's supported channel width
+ */
+enum chan_width get_sta_operation_chan_width(
+       enum chan_width ap_operation_chan_width,
+       struct supported_chan_width sta_supported_chan_width)
+{
+       if (ap_operation_chan_width == CHAN_WIDTH_320 &&
+           sta_supported_chan_width.is_320_supported)
+               return CHAN_WIDTH_320;
+
+       if (ap_operation_chan_width == CHAN_WIDTH_160 ||
+           ap_operation_chan_width == CHAN_WIDTH_320)
+               return sta_supported_chan_width.is_160_supported ?
+                       CHAN_WIDTH_160 : CHAN_WIDTH_80;
+
+       if (ap_operation_chan_width == CHAN_WIDTH_80P80)
+               return sta_supported_chan_width.is_80p80_supported ?
+                       CHAN_WIDTH_80P80 : CHAN_WIDTH_80;
+
+       return ap_operation_chan_width;
+}
index af011a35719777726c8465b81a0e091fa1920382..6a108ccf7ee5475d40913e93b6e61e58e186753d 100644 (file)
@@ -390,4 +390,22 @@ const u8 * get_basic_mle_mld_addr(const u8 *buf, size_t len);
 const u8 * get_basic_mle_eml_capa(const u8 *buf, size_t len);
 int get_basic_mle_link_id(const u8 *buf, size_t len);
 
+unsigned int get_max_nss_capability(struct ieee802_11_elems *elems,
+                                   bool parse_for_rx);
+
+struct supported_chan_width {
+       bool is_160_supported;
+       bool is_80p80_supported;
+       bool is_320_supported;
+};
+
+struct supported_chan_width
+get_supported_channel_width(struct ieee802_11_elems *elems);
+
+enum chan_width get_operation_channel_width(struct ieee802_11_elems *elems);
+
+enum chan_width get_sta_operation_chan_width(
+       enum chan_width ap_operation_chan_width,
+       struct supported_chan_width sta_supported_width);
+
 #endif /* IEEE802_11_COMMON_H */
index c9377d2074ed7fe7032a5d3ee21878ac39bf7b80..9ecd8bc4931544c4c7bfbec4c3a256c4d9f5f222 100644 (file)
@@ -2597,6 +2597,9 @@ struct ieee80211_spatial_reuse {
 
 /* HE operation fields length */
 #define HE_OPERATION_ELEM_MIN_LEN                              6
+#define HE_OPERATION_VHT_OPER_INFO_LEN                         3
+#define HE_OPERATION_COHOSTED_BSSID_INDICATOR_LEN              1
+#define HE_OPERATION_6GHZ_OPER_INFO_LEN                                5
 
 /**
  * enum he_reg_info_6ghz_ap_type - Allowed Access Point types for 6 GHz Band
@@ -2713,6 +2716,7 @@ struct ieee80211_he_mu_edca_parameter_set {
 #define EHT_OPER_DISABLED_SUBCHAN_BITMAP_SIZE          2
 
 /* Control subfield: Channel Width subfield; see Table 9-401b */
+#define EHT_OPER_CHANNEL_WIDTH_MASK                    0x7
 #define EHT_OPER_CHANNEL_WIDTH_20MHZ                   0
 #define EHT_OPER_CHANNEL_WIDTH_40MHZ                   1
 #define EHT_OPER_CHANNEL_WIDTH_80MHZ                   2
@@ -2720,6 +2724,8 @@ struct ieee80211_he_mu_edca_parameter_set {
 #define EHT_OPER_CHANNEL_WIDTH_320MHZ                  4
 
 /* Figure 9-1002c: EHT Operation Information field format */
+#define EHT_OPER_INFO_MIN_LEN                          3
+
 struct ieee80211_eht_oper_info {
        u8 control; /* B0..B2: Channel Width */
        u8 ccfs0;
index e2d57452d7b7a00a3b70a21b0bebab19b367ed6f..8fe08ee3d9cdf74eec9a4d2e745864189479a765 100644 (file)
@@ -3450,6 +3450,9 @@ static void wpas_parse_connection_info(struct wpa_supplicant *wpa_s,
                                       const u8 *resp_ies, size_t resp_ies_len)
 {
        struct ieee802_11_elems req_elems, resp_elems;
+       int max_nss_rx_req, max_nss_rx_resp, max_nss_tx_req, max_nss_tx_resp;
+       struct supported_chan_width sta_supported_chan_width;
+       enum chan_width ap_operation_chan_width;
 
        wpa_s->connection_set = 0;
 
@@ -3475,6 +3478,29 @@ static void wpas_parse_connection_info(struct wpa_supplicant *wpa_s,
                resp_elems.eht_capabilities;
        if (req_elems.rrm_enabled)
                wpa_s->rrm.rrm_used = 1;
+
+       max_nss_rx_req = get_max_nss_capability(&req_elems, 1);
+       max_nss_rx_resp = get_max_nss_capability(&resp_elems, 1);
+       wpa_s->connection_max_nss_rx = MIN(max_nss_rx_resp, max_nss_rx_req);
+
+       max_nss_tx_req = get_max_nss_capability(&req_elems, 0);
+       max_nss_tx_resp = get_max_nss_capability(&resp_elems, 0);
+       wpa_s->connection_max_nss_tx = MIN(max_nss_tx_resp, max_nss_tx_req);
+
+       sta_supported_chan_width = get_supported_channel_width(&req_elems);
+       ap_operation_chan_width = get_operation_channel_width(&resp_elems);
+       if (wpa_s->connection_vht || wpa_s->connection_he ||
+           wpa_s->connection_eht) {
+               wpa_s->connection_channel_bandwidth =
+                       get_sta_operation_chan_width(ap_operation_chan_width,
+                                                    sta_supported_chan_width);
+       } else if (wpa_s->connection_ht) {
+               wpa_s->connection_channel_bandwidth =
+                       ap_operation_chan_width == CHAN_WIDTH_40 ?
+                       CHAN_WIDTH_40 : CHAN_WIDTH_20;
+       } else {
+               wpa_s->connection_channel_bandwidth = CHAN_WIDTH_20;
+       }
 }
 
 
index a2497fc5b0d3aa9cf89c55bdc153c78a72130f82..f6cdf4df335a070e84adece78365e8ddb2ee692d 100644 (file)
@@ -995,6 +995,9 @@ struct wpa_supplicant {
        unsigned int connection_he:1;
        unsigned int connection_eht:1;
        unsigned int disable_mbo_oce:1;
+       u8 connection_max_nss_rx;
+       u8 connection_max_nss_tx;
+       enum chan_width connection_channel_bandwidth;
 
        struct os_reltime last_mac_addr_change;
        enum wpas_mac_addr_style last_mac_addr_style;