Add initial changes to support 320 MHz channel width. Signed-off-by: Veerendranath Jakkam <quic_vjakkam@quicinc.com> Signed-off-by: Karthikeyan Periyasamy <quic_periyasa@quicinc.com>
382 lines
9.5 KiB
C
382 lines
9.5 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,
|
|
const u8 *he_phy_cap,
|
|
const u8 *eht_phy_cap)
|
|
{
|
|
u8 sz = EHT_PHYCAP_MCS_NSS_LEN_20MHZ_PLUS;
|
|
bool band24, band5, band6;
|
|
|
|
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[HE_PHYCAP_CHANNEL_WIDTH_SET_IDX] &
|
|
HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_IN_2G) == 0)
|
|
return EHT_PHYCAP_MCS_NSS_LEN_20MHZ_ONLY;
|
|
|
|
if (band5 &&
|
|
(he_phy_cap[HE_PHYCAP_CHANNEL_WIDTH_SET_IDX] &
|
|
(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[HE_PHYCAP_CHANNEL_WIDTH_SET_IDX] &
|
|
(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,
|
|
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,
|
|
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;
|
|
|
|
if (!hapd->iface->current_mode)
|
|
return eid;
|
|
|
|
*pos++ = WLAN_EID_EXTENSION;
|
|
*pos++ = 5;
|
|
*pos++ = WLAN_EID_EXT_EHT_OPERATION;
|
|
|
|
oper = (struct ieee80211_eht_operation *) pos;
|
|
oper->oper_params = EHT_OPER_INFO_PRESENT;
|
|
|
|
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 + 4;
|
|
}
|
|
|
|
|
|
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,
|
|
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,
|
|
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_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,
|
|
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);
|
|
}
|