/* * hostapd / IEEE 802.11be EHT * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. * * This software may be distributed under the terms of the BSD license. * See README for more details. */ #include "utils/includes.h" #include "utils/common.h" #include "hostapd.h" #include "sta_info.h" #include "ieee802_11.h" static u16 ieee80211_eht_ppet_size(u16 ppe_thres_hdr, const u8 *phy_cap_info) { u8 ru; u16 sz = 0; if ((phy_cap_info[EHT_PHYCAP_PPE_THRESHOLD_PRESENT_IDX] & EHT_PHYCAP_PPE_THRESHOLD_PRESENT) == 0) return 0; ru = (ppe_thres_hdr & EHT_PPE_THRES_RU_INDEX_MASK) >> EHT_PPE_THRES_RU_INDEX_SHIFT; while (ru) { if (ru & 0x1) sz++; ru >>= 1; } sz = sz * (1 + ((ppe_thres_hdr & EHT_PPE_THRES_NSS_MASK) >> EHT_PPE_THRES_NSS_SHIFT)); sz = (sz * 6) + 9; if (sz % 8) sz += 8; sz /= 8; return sz; } static u8 ieee80211_eht_mcs_set_size(enum hostapd_hw_mode mode, u8 opclass, u8 he_oper_chwidth, const u8 *he_phy_cap, const u8 *eht_phy_cap) { u8 sz = EHT_PHYCAP_MCS_NSS_LEN_20MHZ_PLUS; bool band24, band5, band6; u8 he_phy_cap_chwidth = ~HE_PHYCAP_CHANNEL_WIDTH_MASK; switch (he_oper_chwidth) { case CONF_OPER_CHWIDTH_80P80MHZ: he_phy_cap_chwidth |= HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G; /* fall through */ case CONF_OPER_CHWIDTH_160MHZ: he_phy_cap_chwidth |= HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G; /* fall through */ case CONF_OPER_CHWIDTH_80MHZ: case CONF_OPER_CHWIDTH_USE_HT: he_phy_cap_chwidth |= HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_IN_2G | HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G; break; } he_phy_cap_chwidth &= he_phy_cap[HE_PHYCAP_CHANNEL_WIDTH_SET_IDX]; band24 = mode == HOSTAPD_MODE_IEEE80211B || mode == HOSTAPD_MODE_IEEE80211G || mode == NUM_HOSTAPD_MODES; band5 = mode == HOSTAPD_MODE_IEEE80211A || mode == NUM_HOSTAPD_MODES; band6 = is_6ghz_op_class(opclass); if (band24 && (he_phy_cap_chwidth & HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_IN_2G) == 0) return EHT_PHYCAP_MCS_NSS_LEN_20MHZ_ONLY; if (band5 && (he_phy_cap_chwidth & (HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G | HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G | HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G)) == 0) return EHT_PHYCAP_MCS_NSS_LEN_20MHZ_ONLY; if (band5 && (he_phy_cap_chwidth & (HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G | HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G))) sz += EHT_PHYCAP_MCS_NSS_LEN_20MHZ_PLUS; if (band6 && (eht_phy_cap[EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_IDX] & EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_MASK)) sz += EHT_PHYCAP_MCS_NSS_LEN_20MHZ_PLUS; return sz; } size_t hostapd_eid_eht_capab_len(struct hostapd_data *hapd, enum ieee80211_op_mode opmode) { struct hostapd_hw_modes *mode; struct eht_capabilities *eht_cap; size_t len = 3 + 2 + EHT_PHY_CAPAB_LEN; mode = hapd->iface->current_mode; if (!mode) return 0; eht_cap = &mode->eht_capab[opmode]; if (!eht_cap->eht_supported) return 0; len += ieee80211_eht_mcs_set_size(mode->mode, hapd->iconf->op_class, hapd->iconf->he_oper_chwidth, mode->he_capab[opmode].phy_cap, eht_cap->phy_cap); len += ieee80211_eht_ppet_size(WPA_GET_LE16(&eht_cap->ppet[0]), eht_cap->phy_cap); return len; } u8 * hostapd_eid_eht_capab(struct hostapd_data *hapd, u8 *eid, enum ieee80211_op_mode opmode) { struct hostapd_hw_modes *mode; struct eht_capabilities *eht_cap; struct ieee80211_eht_capabilities *cap; size_t mcs_nss_len, ppe_thresh_len; u8 *pos = eid, *length_pos; mode = hapd->iface->current_mode; if (!mode) return eid; eht_cap = &mode->eht_capab[opmode]; if (!eht_cap->eht_supported) return eid; *pos++ = WLAN_EID_EXTENSION; length_pos = pos++; *pos++ = WLAN_EID_EXT_EHT_CAPABILITIES; cap = (struct ieee80211_eht_capabilities *) pos; os_memset(cap, 0, sizeof(*cap)); cap->mac_cap = host_to_le16(eht_cap->mac_cap); os_memcpy(cap->phy_cap, eht_cap->phy_cap, EHT_PHY_CAPAB_LEN); if (!is_6ghz_op_class(hapd->iconf->op_class)) cap->phy_cap[EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_IDX] &= ~EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_MASK; if (!hapd->iface->conf->eht_phy_capab.su_beamformer) cap->phy_cap[EHT_PHYCAP_SU_BEAMFORMER_IDX] &= ~EHT_PHYCAP_SU_BEAMFORMER; if (!hapd->iface->conf->eht_phy_capab.su_beamformee) cap->phy_cap[EHT_PHYCAP_SU_BEAMFORMEE_IDX] &= ~EHT_PHYCAP_SU_BEAMFORMEE; if (!hapd->iface->conf->eht_phy_capab.mu_beamformer) cap->phy_cap[EHT_PHYCAP_MU_BEAMFORMER_IDX] &= ~EHT_PHYCAP_MU_BEAMFORMER_MASK; pos = cap->optional; mcs_nss_len = ieee80211_eht_mcs_set_size(mode->mode, hapd->iconf->op_class, hapd->iconf->he_oper_chwidth, mode->he_capab[opmode].phy_cap, eht_cap->phy_cap); if (mcs_nss_len) { os_memcpy(pos, eht_cap->mcs, mcs_nss_len); pos += mcs_nss_len; } ppe_thresh_len = ieee80211_eht_ppet_size( WPA_GET_LE16(&eht_cap->ppet[0]), eht_cap->phy_cap); if (ppe_thresh_len) { os_memcpy(pos, eht_cap->ppet, ppe_thresh_len); pos += ppe_thresh_len; } *length_pos = pos - (eid + 2); return pos; } u8 * hostapd_eid_eht_operation(struct hostapd_data *hapd, u8 *eid) { struct hostapd_config *conf = hapd->iconf; struct ieee80211_eht_operation *oper; u8 *pos = eid, seg0 = 0, seg1 = 0; enum oper_chan_width chwidth; size_t elen = 1 + 4 + 3; if (!hapd->iface->current_mode) return eid; *pos++ = WLAN_EID_EXTENSION; *pos++ = 1 + elen; *pos++ = WLAN_EID_EXT_EHT_OPERATION; oper = (struct ieee80211_eht_operation *) pos; oper->oper_params = EHT_OPER_INFO_PRESENT; /* TODO: Fill in appropriate EHT-MCS max Nss information */ oper->basic_eht_mcs_nss_set[0] = 0x11; oper->basic_eht_mcs_nss_set[1] = 0x00; oper->basic_eht_mcs_nss_set[2] = 0x00; oper->basic_eht_mcs_nss_set[3] = 0x00; if (is_6ghz_op_class(conf->op_class)) chwidth = op_class_to_ch_width(conf->op_class); else chwidth = conf->eht_oper_chwidth; seg0 = hostapd_get_oper_centr_freq_seg0_idx(conf); switch (chwidth) { case CONF_OPER_CHWIDTH_320MHZ: oper->oper_info.control |= EHT_OPER_CHANNEL_WIDTH_320MHZ; seg1 = seg0; if (hapd->iconf->channel < seg0) seg0 -= 16; else seg0 += 16; break; case CONF_OPER_CHWIDTH_160MHZ: oper->oper_info.control |= EHT_OPER_CHANNEL_WIDTH_160MHZ; seg1 = seg0; if (hapd->iconf->channel < seg0) seg0 -= 8; else seg0 += 8; break; case CONF_OPER_CHWIDTH_80MHZ: oper->oper_info.control |= EHT_OPER_CHANNEL_WIDTH_80MHZ; break; case CONF_OPER_CHWIDTH_USE_HT: if (seg0) oper->oper_info.control |= EHT_OPER_CHANNEL_WIDTH_40MHZ; break; default: return eid; } oper->oper_info.ccfs0 = seg0 ? seg0 : hapd->iconf->channel; oper->oper_info.ccfs1 = seg1; return pos + elen; } static bool check_valid_eht_mcs_nss(struct hostapd_data *hapd, const u8 *ap_mcs, const u8 *sta_mcs, u8 mcs_count, u8 map_len) { unsigned int i, j; for (i = 0; i < mcs_count; i++) { ap_mcs += i * 3; sta_mcs += i * 3; for (j = 0; j < map_len; j++) { if (((ap_mcs[j] >> 4) & 0xFF) == 0) continue; if ((sta_mcs[j] & 0xFF) == 0) continue; return true; } } wpa_printf(MSG_DEBUG, "No matching EHT MCS found between AP TX and STA RX"); return false; } static bool check_valid_eht_mcs(struct hostapd_data *hapd, const u8 *sta_eht_capab, enum ieee80211_op_mode opmode) { struct hostapd_hw_modes *mode; const struct ieee80211_eht_capabilities *capab; const u8 *ap_mcs, *sta_mcs; u8 mcs_count = 1; mode = hapd->iface->current_mode; if (!mode) return true; ap_mcs = mode->eht_capab[opmode].mcs; capab = (const struct ieee80211_eht_capabilities *) sta_eht_capab; sta_mcs = capab->optional; if (ieee80211_eht_mcs_set_size(mode->mode, hapd->iconf->op_class, hapd->iconf->he_oper_chwidth, mode->he_capab[opmode].phy_cap, mode->eht_capab[opmode].phy_cap) == EHT_PHYCAP_MCS_NSS_LEN_20MHZ_ONLY) return check_valid_eht_mcs_nss( hapd, ap_mcs, sta_mcs, 1, EHT_PHYCAP_MCS_NSS_LEN_20MHZ_ONLY); switch (hapd->iface->conf->eht_oper_chwidth) { case CONF_OPER_CHWIDTH_320MHZ: mcs_count++; /* fall through */ case CONF_OPER_CHWIDTH_80P80MHZ: case CONF_OPER_CHWIDTH_160MHZ: mcs_count++; break; default: break; } return check_valid_eht_mcs_nss(hapd, ap_mcs, sta_mcs, mcs_count, EHT_PHYCAP_MCS_NSS_LEN_20MHZ_PLUS); } static bool ieee80211_invalid_eht_cap_size(enum hostapd_hw_mode mode, u8 opclass, u8 he_oper_chwidth, const u8 *he_cap, const u8 *eht_cap, size_t len) { const struct ieee80211_he_capabilities *he_capab; struct ieee80211_eht_capabilities *cap; const u8 *he_phy_cap; size_t cap_len; u16 ppe_thres_hdr; he_capab = (const struct ieee80211_he_capabilities *) he_cap; he_phy_cap = he_capab->he_phy_capab_info; cap = (struct ieee80211_eht_capabilities *) eht_cap; cap_len = sizeof(*cap) - sizeof(cap->optional); if (len < cap_len) return true; cap_len += ieee80211_eht_mcs_set_size(mode, opclass, he_oper_chwidth, he_phy_cap, cap->phy_cap); if (len < cap_len) return true; ppe_thres_hdr = len > cap_len + 1 ? WPA_GET_LE16(&eht_cap[cap_len]) : 0x01ff; cap_len += ieee80211_eht_ppet_size(ppe_thres_hdr, cap->phy_cap); return len < cap_len; } u16 copy_sta_eht_capab(struct hostapd_data *hapd, struct sta_info *sta, enum ieee80211_op_mode opmode, const u8 *he_capab, size_t he_capab_len, const u8 *eht_capab, size_t eht_capab_len) { struct hostapd_hw_modes *c_mode = hapd->iface->current_mode; enum hostapd_hw_mode mode = c_mode ? c_mode->mode : NUM_HOSTAPD_MODES; if (!hapd->iconf->ieee80211be || hapd->conf->disable_11be || !he_capab || he_capab_len < IEEE80211_HE_CAPAB_MIN_LEN || !eht_capab || ieee80211_invalid_eht_cap_size(mode, hapd->iconf->op_class, hapd->iconf->he_oper_chwidth, he_capab, eht_capab, eht_capab_len) || !check_valid_eht_mcs(hapd, eht_capab, opmode)) { sta->flags &= ~WLAN_STA_EHT; os_free(sta->eht_capab); sta->eht_capab = NULL; return WLAN_STATUS_SUCCESS; } os_free(sta->eht_capab); sta->eht_capab = os_memdup(eht_capab, eht_capab_len); if (!sta->eht_capab) { sta->eht_capab_len = 0; return WLAN_STATUS_UNSPECIFIED_FAILURE; } sta->flags |= WLAN_STA_EHT; sta->eht_capab_len = eht_capab_len; return WLAN_STATUS_SUCCESS; } void hostapd_get_eht_capab(struct hostapd_data *hapd, const struct ieee80211_eht_capabilities *src, struct ieee80211_eht_capabilities *dest, size_t len) { if (!src || !dest) return; if (len > sizeof(*dest)) len = sizeof(*dest); /* TODO: mask out unsupported features */ os_memset(dest, 0, sizeof(*dest)); os_memcpy(dest, src, len); }