f4096e7cd5
IEEE P802.11be/D2.0 added a 4-octet Basic EHT-MCS And Nss Set field into the EHT Operation element. cfg80211 is now verifying that the EHT Operation element has large enough payload and that check is failing with the previous version. This commit does not really set the correct Basic EHT-MCS And Nss Set values, but the IE length check is now passing to allow initial mac80211_hwsim testing to succeed. Signed-off-by: Jouni Malinen <j@w1.fi>
410 lines
10 KiB
C
410 lines
10 KiB
C
/*
|
|
* 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);
|
|
}
|