Add Nss and BW parsing after association.
Parse HT/VHT/HE capabilities and operation IE of association request
and response frames to get max_nss_rx, max_nss_tx and channel_bandwidth
capabilities of the current network.
This CL only includes changes to be upstreamed to wpa_supplicant and
doesn't include hidl change.
Bug: 147390882
Test: VTS test
Test: manual test with various APs.
Change-Id: Ie43aca62f7f02cb3c0862f7a36bc66fa5899191b
diff --git a/src/common/ieee802_11_common.c b/src/common/ieee802_11_common.c
index 0d37674..b330454 100644
--- a/src/common/ieee802_11_common.c
+++ b/src/common/ieee802_11_common.c
@@ -271,10 +271,14 @@
elems->password_id_len = elen;
break;
case WLAN_EID_EXT_HE_CAPABILITIES:
+ if (elen < HE_CAPABILITIES_IE_MIN_LEN)
+ break;
elems->he_capabilities = pos;
elems->he_capabilities_len = elen;
break;
case WLAN_EID_EXT_HE_OPERATION:
+ if (elen < HE_OPERATION_IE_MIN_LEN)
+ break;
elems->he_operation = pos;
elems->he_operation_len = elen;
break;
@@ -2389,3 +2393,273 @@
}
return CHANWIDTH_USE_HT;
}
+
+
+/* Parse HT capabilities to get maximum number of supported spatial streams */
+static int parse_ht_mcs_set_for_max_nss(
+ struct ieee80211_ht_capabilities *htcaps,
+ u8 parse_for_rx)
+{
+ int max_nss_rx = 1;
+ if (htcaps == NULL)
+ return max_nss_rx;
+ int i;
+ 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;
+ u8 supported_tx_mcs_set = htcaps->supported_mcs_set[12];
+ u8 tx_mcs_set_defined = supported_tx_mcs_set & 0x1;
+ u8 tx_rx_mcs_set_not_equal = (supported_tx_mcs_set >> 1) & 0x1;
+ if (tx_mcs_set_defined && tx_rx_mcs_set_not_equal) {
+ int 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 int parse_mcs_map_for_max_nss (u16 mcs_map, int max_streams_allowed)
+{
+ int max_nss = 1;
+ int i;
+ for (i = max_streams_allowed; i >= 1; i--) {
+ 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 IEs to get maximum number of supported spatial streams */
+int get_max_nss_capability(struct ieee802_11_elems *elems, int parse_for_rx)
+{
+ 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;
+ if (htcaps) {
+ int 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;
+ }
+ le16 mcs_map;
+ if (vhtcaps) {
+ mcs_map = (parse_for_rx) ? vhtcaps->vht_supported_mcs_set.rx_map :
+ vhtcaps->vht_supported_mcs_set.tx_map;
+ int 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) {
+ mcs_map = (parse_for_rx) ? hecaps->he_basic_supported_mcs_set.rx_map :
+ hecaps->he_basic_supported_mcs_set.tx_map;
+ int 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 IEs to get supported channel width */
+struct supported_chan_width get_supported_channel_width(
+ struct ieee802_11_elems *elems)
+{
+ struct supported_chan_width supported_width;
+ supported_width.is_160_supported = 0;
+ supported_width.is_80p80_supported = 0;
+ if (elems == NULL)
+ return supported_width;
+
+ struct ieee80211_vht_capabilities *vhtcaps =
+ (struct ieee80211_vht_capabilities *) elems->vht_capabilities;
+ struct ieee80211_he_capabilities *hecaps =
+ (struct ieee80211_he_capabilities *) elems->he_capabilities;
+
+ if (vhtcaps) {
+ le32 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 = 1;
+ if (vht_capabilities_info & VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ)
+ supported_width.is_80p80_supported = 1;
+ }
+ 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 = 1;
+ if (channel_width_set & HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G)
+ supported_width.is_80p80_supported = 1;
+ }
+ wpa_printf(MSG_DEBUG, " IE indicate 160 supported: %u, 80+80 supported: %u",
+ supported_width.is_160_supported, supported_width.is_80p80_supported);
+ return supported_width;
+}
+
+/*
+ * Parse VHT operation info fields to get operation channel width
+ * note that VHT operation info fields could come from VHT operation IE
+ * or from HE operation IE
+ */
+static enum chan_width get_vht_operation_channel_width(
+ struct ieee80211_vht_operation_info *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;
+ default:
+ break;
+ }
+ wpa_printf(MSG_DEBUG, " VHT operation CBW: %u", channel_width);
+ return channel_width;
+}
+
+/* Parse 6GHz operation info fields to get operation channel width */
+static enum chan_width get_6ghz_operation_channel_width(
+ struct ieee80211_6ghz_operation_info * six_ghz_oper_info)
+{
+ enum chan_width channel_width = CHAN_WIDTH_UNKNOWN;
+ u8 seg0, seg1;
+ switch (six_ghz_oper_info->control & SIX_GHZ_CONTROL_CHANNEL_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_idx;
+ seg1 = six_ghz_oper_info->chan_center_freq_seg1_idx;
+ if (abs(seg1 - seg0) == 8)
+ channel_width = CHAN_WIDTH_160;
+ else
+ channel_width = CHAN_WIDTH_80P80;
+ break;
+ default:
+ break;
+ }
+ wpa_printf(MSG_DEBUG, " 6GHz operation CBW: %u", channel_width);
+ return channel_width;
+}
+
+
+/* Parse HE operation IE to get HE operation channel width */
+static enum chan_width get_he_operation_channel_width(
+ struct ieee80211_he_operation *he_oper,
+ int he_oper_len)
+{
+ enum chan_width channel_width = CHAN_WIDTH_UNKNOWN;
+ u8 is_6ghz_info_present =
+ (he_oper->he_oper_params & HE_OPERATION_6GHZ_OPER_INFO) ? 1 : 0;
+ u8 is_vht_info_present =
+ (he_oper->he_oper_params & HE_OPERATION_VHT_OPER_INFO) ? 1 : 0;
+ u8 is_cohosted_bss_present =
+ (he_oper->he_oper_params & HE_OPERATION_COHOSTED_BSS) ? 1 : 0;
+ int expected_len = HE_OPERATION_IE_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_6ghz_operation_info *six_ghz_oper_info =
+ (struct ieee80211_6ghz_operation_info *)
+ (he_oper + HE_OPERATION_IE_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_info *vht_oper_info =
+ (struct ieee80211_vht_operation_info *)
+ (he_oper + HE_OPERATION_IE_MIN_LEN);
+ channel_width = get_vht_operation_channel_width(vht_oper_info);
+ }
+ wpa_printf(MSG_DEBUG, " HE operation CBW: %u", channel_width);
+ return channel_width;
+}
+
+/* Parse HT/VHT/HE operation IEs 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;
+ if (elems == NULL)
+ return channel_width;
+
+ struct ieee80211_ht_operation *ht_oper =
+ (struct ieee80211_ht_operation *) elems->ht_operation;
+ struct ieee80211_vht_operation_info *vht_oper_info =
+ (struct ieee80211_vht_operation_info *) elems->vht_operation;
+ struct ieee80211_he_operation *he_oper =
+ (struct ieee80211_he_operation *) elems->he_operation;
+ if (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;
+ }
+ wpa_printf(MSG_DEBUG, " overall operation CBW: %u", channel_width);
+ 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_160)
+ 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;
+}
diff --git a/src/common/ieee802_11_common.h b/src/common/ieee802_11_common.h
index fbda10c..2c66507 100644
--- a/src/common/ieee802_11_common.h
+++ b/src/common/ieee802_11_common.h
@@ -291,4 +291,18 @@
int ieee802_edmg_is_allowed(struct ieee80211_edmg_config allowed,
struct ieee80211_edmg_config requested);
+int get_max_nss_capability(struct ieee802_11_elems *elems, int parse_for_rx);
+
+struct supported_chan_width {
+ u8 is_160_supported;
+ u8 is_80p80_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 */
diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h
index e3c2ed3..fdc51dc 100644
--- a/src/common/ieee802_11_defs.h
+++ b/src/common/ieee802_11_defs.h
@@ -1102,6 +1102,12 @@
le16 vht_basic_mcs_set;
} STRUCT_PACKED;
+struct ieee80211_vht_operation_info {
+ u8 vht_op_info_chwidth;
+ u8 vht_op_info_chan_center_freq_seg0_idx;
+ u8 vht_op_info_chan_center_freq_seg1_idx;
+} STRUCT_PACKED;
+
struct ieee80211_ampe_ie {
u8 selected_pairwise_suite[4];
u8 local_nonce[32];
@@ -2122,9 +2128,13 @@
struct ieee80211_he_capabilities {
u8 he_mac_capab_info[6];
u8 he_phy_capab_info[11];
- /* Followed by 4, 8, or 12 octets of Supported HE-MCS And NSS Set field
+ struct {
+ le16 rx_map;
+ le16 tx_map;
+ } he_basic_supported_mcs_set;
+ /* Followed by 0, 4, or 8 octets of optional supported HE-MCS And NSS Set field
* and optional variable length PPE Thresholds field. */
- u8 optional[37];
+ u8 optional[33];
} STRUCT_PACKED;
struct ieee80211_he_operation {
@@ -2152,8 +2162,17 @@
u8 params[19];
} STRUCT_PACKED;
-/* HE Capabilities Information defines */
+struct ieee80211_6ghz_operation_info {
+ u8 primary_chan;
+ u8 control;
+ u8 chan_center_freq_seg0_idx;
+ u8 chan_center_freq_seg1_idx;
+ u8 minimum_rate;
+} STRUCT_PACKED;
+#define HE_CAPABILITIES_IE_MIN_LEN 21
+
+/* HE Capabilities Information defines */
#define HE_PHYCAP_CHANNEL_WIDTH_SET_IDX 0
#define HE_PHYCAP_CHANNEL_WIDTH_MASK ((u8) (BIT(1) | BIT(2) | \
BIT(3) | BIT(4)))
@@ -2200,6 +2219,12 @@
#define HE_OPERATION_BSS_COLOR_DISABLED ((u32) BIT(31))
#define HE_OPERATION_BSS_COLOR_OFFSET 24
+/* HE operation fields length*/
+#define HE_OPERATION_IE_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
+
/* Spatial Reuse defines */
#define SPATIAL_REUSE_SRP_DISALLOWED BIT(0)
#define SPATIAL_REUSE_NON_SRG_OBSS_PD_SR_DISALLOWED BIT(1)
@@ -2207,6 +2232,10 @@
#define SPATIAL_REUSE_SRG_INFORMATION_PRESENT BIT(3)
#define SPATIAL_REUSE_HESIGA_SR_VAL15_ALLOWED BIT(4)
+/* 6GHz operation control field defines*/
+#define SIX_GHZ_CONTROL_CHANNEL_WIDTH_MASK ((u8) BIT(0) | BIT(1))
+#define SIX_GHZ_CONTROL_DUPLICATE_BEACON BIT(2)
+
struct ieee80211_he_mu_edca_parameter_set {
u8 he_qos_info;
u8 he_mu_ac_be_param[3];
diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index 15ef0f7..4c459cf 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -2476,6 +2476,30 @@
resp_elems.vht_capabilities;
wpa_s->connection_he = req_elems.he_capabilities &&
resp_elems.he_capabilities;
+
+ int max_nss_rx_req = get_max_nss_capability(&req_elems, 1);
+ int max_nss_rx_resp = get_max_nss_capability(&resp_elems, 1);
+ wpa_s->connection_max_nss_rx = (max_nss_rx_resp > max_nss_rx_req) ?
+ max_nss_rx_req : max_nss_rx_resp;
+ int max_nss_tx_req = get_max_nss_capability(&req_elems, 0);
+ int max_nss_tx_resp = get_max_nss_capability(&resp_elems, 0);
+ wpa_s->connection_max_nss_tx = (max_nss_tx_resp > max_nss_tx_req) ?
+ max_nss_tx_req : max_nss_tx_resp;
+
+ struct supported_chan_width sta_supported_chan_width =
+ get_supported_channel_width(&req_elems);
+ enum chan_width ap_operation_chan_width =
+ get_operation_channel_width(&resp_elems);
+ if (wpa_s->connection_vht || wpa_s->connection_he) {
+ 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;
+ }
}
}
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index dcc2a6d..967298b 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -759,6 +759,9 @@
unsigned int connection_ht:1;
unsigned int connection_vht:1;
unsigned int connection_he:1;
+ unsigned int connection_max_nss_rx:4;
+ unsigned int connection_max_nss_tx:4;
+ unsigned int connection_channel_bandwidth:5;
unsigned int disable_mbo_oce:1;
struct os_reltime last_mac_addr_change;