[RFC 5/8] AP: add S1G support
Lachlan Hodges
lachlan.hodges at morsemicro.com
Wed May 27 23:38:54 PDT 2026
Introduce the ability to bring up an S1G AP. Currently only US 2024
channelisation is supported. A sample config is as follows:
```
interface=wlan0
country_code=US
driver=nl80211
ssid=wifi_halow
channel=43
ieee80211ah=1
op_class=71
beacon_int=100
dtim_period=10
s1g_oper_centr_freq_idx=44
s1g_primary_2mhz=1
hw_mode=ah
wpa=2
wpa_key_mgmt=SAE
rsn_pairwise=CCMP
sae_password=12345678
ieee80211w=2
```
Where we are configuring an operating channel of 44, primary 1MHz
channel of 43, whilst utilising a 2MHz primary. There is no need to
include sae_pwe=1 as H2E will automatically be enabled for S1G.
ieee80211w=2 is required, and will error if not set correctly during
config validation.
Signed-off-by: Lachlan Hodges <lachlan.hodges at morsemicro.com>
---
hostapd/Makefile | 5 +
hostapd/config_file.c | 10 ++
src/ap/Makefile | 1 +
src/ap/ap_config.c | 34 ++++++
src/ap/ap_config.h | 23 ++++
src/ap/ap_drv_ops.c | 18 ++-
src/ap/ap_drv_ops.h | 4 +-
src/ap/beacon.c | 97 ++++++++++++++--
src/ap/ctrl_iface_ap.c | 8 +-
src/ap/dfs.c | 8 +-
src/ap/hostapd.c | 13 ++-
src/ap/hw_features.c | 32 +++++-
src/ap/ieee802_11.c | 60 ++++++++--
src/ap/ieee802_11.h | 10 ++
src/ap/ieee802_11_s1g.c | 196 ++++++++++++++++++++++++++++++++
src/ap/ieee802_11_shared.c | 14 +++
src/ap/interference.c | 1 +
src/ap/sta_info.c | 5 +-
src/ap/sta_info.h | 4 +-
src/common/defs.h | 6 +
src/common/hw_features_common.c | 49 +++++++-
src/common/hw_features_common.h | 6 +-
src/common/ieee802_11_common.c | 55 ++++++++-
src/common/ieee802_11_common.h | 6 +-
src/common/ieee802_11_defs.h | 55 +++++++++
wlantest/bss.c | 2 +-
26 files changed, 677 insertions(+), 45 deletions(-)
create mode 100644 src/ap/ieee802_11_s1g.c
diff --git a/hostapd/Makefile b/hostapd/Makefile
index b2420e8474be..89f041720ace 100644
--- a/hostapd/Makefile
+++ b/hostapd/Makefile
@@ -355,6 +355,11 @@ CFLAGS += -DCONFIG_IEEE80211AX
OBJS += ../src/ap/ieee802_11_he.o
endif
+ifdef CONFIG_IEEE80211AH
+CFLAGS += -DCONFIG_IEEE80211AH
+OBJS += ../src/ap/ieee802_11_s1g.o
+endif
+
ifdef CONFIG_MBO
CFLAGS += -DCONFIG_MBO
OBJS += ../src/ap/mbo_ap.o
diff --git a/hostapd/config_file.c b/hostapd/config_file.c
index d34b4aa83950..790e8dcc1811 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -3177,6 +3177,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
conf->hw_mode = HOSTAPD_MODE_IEEE80211G;
else if (os_strcmp(pos, "ad") == 0)
conf->hw_mode = HOSTAPD_MODE_IEEE80211AD;
+ else if (os_strcmp(pos, "ah") == 0)
+ conf->hw_mode = HOSTAPD_MODE_IEEE80211AH;
else if (os_strcmp(pos, "any") == 0)
conf->hw_mode = HOSTAPD_MODE_IEEE80211ANY;
else {
@@ -3752,6 +3754,14 @@ static int hostapd_config_fill(struct hostapd_config *conf,
} else if (os_strcmp(buf, "mbssid_max") == 0) {
conf->mbssid_max = atoi(pos);
#endif /* CONFIG_IEEE80211AX */
+#ifdef CONFIG_IEEE80211AH
+ } else if (os_strcmp(buf, "ieee80211ah") == 0) {
+ conf->ieee80211ah = atoi(pos);
+ } else if (os_strcmp(buf, "s1g_oper_centr_freq_idx") == 0) {
+ conf->s1g_oper_centr_freq_idx = atoi(pos);
+ } else if (os_strcmp(buf, "s1g_primary_2mhz") == 0) {
+ conf->s1g_primary_2mhz = atoi(pos);
+#endif /* CONFIG_IEEE80211AH */
} else if (os_strcmp(buf, "max_listen_interval") == 0) {
bss->max_listen_interval = atoi(pos);
} else if (os_strcmp(buf, "disable_pmksa_caching") == 0) {
diff --git a/src/ap/Makefile b/src/ap/Makefile
index 22a21d31eda6..790deabc2886 100644
--- a/src/ap/Makefile
+++ b/src/ap/Makefile
@@ -35,6 +35,7 @@ LIB_OBJS= \
ieee802_11_ht.o \
ieee802_11_shared.o \
ieee802_11_vht.o \
+ ieee802_11_s1g.o \
ieee802_1x.o \
interference.o \
neighbor_db.o \
diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
index 36a4dad65626..7e14f3efd168 100644
--- a/src/ap/ap_config.c
+++ b/src/ap/ap_config.c
@@ -1199,6 +1199,26 @@ static bool hostapd_sae_pk_password_without_pk(struct hostapd_bss_config *bss)
}
#endif /* CONFIG_SAE_PK */
+#ifdef CONFIG_IEEE80211AH
+bool hostapd_config_check_bss_s1g(struct hostapd_bss_config *bss)
+{
+ if (bss->ieee80211w != MGMT_FRAME_PROTECTION_REQUIRED) {
+ wpa_printf(MSG_ERROR,
+ "Management frame protection is required in S1GHz (ieee80211w=2)");
+ return false;
+ }
+
+#ifdef CONFIG_SAE
+ if (wpa_key_mgmt_sae(bss->wpa_key_mgmt) &&
+ bss->sae_pwe != SAE_PWE_HASH_TO_ELEMENT) {
+ wpa_printf(MSG_INFO, "SAE: Forcing SAE H2E on S1GHz");
+ bss->sae_pwe = SAE_PWE_HASH_TO_ELEMENT;
+ }
+#endif /* CONFIG_SAE */
+
+ return true;
+}
+#endif /* CONFIG_IEEE80211AH */
bool hostapd_config_check_bss_6g(struct hostapd_bss_config *bss)
{
@@ -1256,6 +1276,12 @@ static int hostapd_config_check_bss(struct hostapd_bss_config *bss,
!hostapd_config_check_bss_6g(bss))
return -1;
+#ifdef CONFIG_IEEE80211AH
+ if (full_config && conf->hw_mode == HOSTAPD_MODE_IEEE80211AH &&
+ !hostapd_config_check_bss_s1g(bss))
+ return -1;
+#endif /* CONFIG_IEEE80211AH */
+
if (full_config && bss->ieee802_1x && !bss->eap_server &&
!bss->radius->auth_servers) {
wpa_printf(MSG_ERROR, "Invalid IEEE 802.1X configuration (no "
@@ -1629,6 +1655,14 @@ int hostapd_config_check(struct hostapd_config *conf, int full_config)
return -1;
}
+#ifdef CONFIG_IEEE80211AH
+ if (full_config && conf->hw_mode == HOSTAPD_MODE_IEEE80211AH &&
+ !is_supported_s1g_op_class(conf->op_class)) {
+ wpa_printf(MSG_ERROR, "Unsupported global op_class for S1G operation");
+ return -1;
+ }
+#endif /* CONFIG_IEEE80211AH */
+
for (i = 0; i < conf->num_bss; i++) {
if (hostapd_config_check_bss(conf->bss[i], conf, full_config))
return -1;
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index 3fe2206b9133..9e1d9c9e925f 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -1068,6 +1068,7 @@ struct hostapd_config {
u8 channel;
int enable_edmg;
u8 edmg_channel;
+ bool s1g_primary_2mhz;
u8 acs;
struct wpa_freq_range_list acs_ch_list;
struct wpa_freq_range_list acs_freq_list;
@@ -1207,6 +1208,12 @@ struct hostapd_config {
bool require_he;
#endif /* CONFIG_IEEE80211AX */
+ int ieee80211ah;
+#ifdef CONFIG_IEEE80211AH
+ int s1g_oper_centr_freq_idx;
+ enum oper_chan_width s1g_oper_chwidth;
+#endif /* CONFIG_IEEE80211AH */
+
/* VHT enable/disable config from CHAN_SWITCH */
#define CH_SWITCH_VHT_ENABLED BIT(0)
#define CH_SWITCH_VHT_DISABLED BIT(1)
@@ -1274,6 +1281,10 @@ struct hostapd_config {
static inline enum oper_chan_width
hostapd_get_oper_chwidth(struct hostapd_config *conf)
{
+#ifdef CONFIG_IEEE80211AH
+ if (conf->ieee80211ah)
+ return conf->s1g_oper_chwidth;
+#endif
#ifdef CONFIG_IEEE80211BE
if (conf->ieee80211be)
return conf->eht_oper_chwidth;
@@ -1289,6 +1300,10 @@ static inline void
hostapd_set_oper_chwidth(struct hostapd_config *conf,
enum oper_chan_width oper_chwidth)
{
+#ifdef CONFIG_IEEE80211AH
+ if (conf->ieee80211ah)
+ conf->s1g_oper_chwidth = oper_chwidth;
+#endif /* CONFIG_IEEE80211AH */
#ifdef CONFIG_IEEE80211BE
if (conf->ieee80211be)
conf->eht_oper_chwidth = oper_chwidth;
@@ -1305,6 +1320,10 @@ hostapd_set_oper_chwidth(struct hostapd_config *conf,
static inline u8
hostapd_get_oper_centr_freq_seg0_idx(struct hostapd_config *conf)
{
+#ifdef CONFIG_IEEE80211AH
+ if (conf->ieee80211ah)
+ return conf->s1g_oper_centr_freq_idx;
+#endif /* CONFIG_IEEE80211AH */
#ifdef CONFIG_IEEE80211BE
if (conf->ieee80211be)
return conf->eht_oper_centr_freq_seg0_idx;
@@ -1320,6 +1339,10 @@ static inline void
hostapd_set_oper_centr_freq_seg0_idx(struct hostapd_config *conf,
u8 oper_centr_freq_seg0_idx)
{
+#ifdef CONFIG_IEEE80211AH
+ if (conf->ieee80211ah)
+ conf->s1g_oper_centr_freq_idx = oper_centr_freq_seg0_idx;
+#endif /* CONFIG_IEEE80211AH */
#ifdef CONFIG_IEEE80211BE
if (conf->ieee80211be)
conf->eht_oper_centr_freq_seg0_idx = oper_centr_freq_seg0_idx;
diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
index 5862bedff238..a10472155068 100644
--- a/src/ap/ap_drv_ops.c
+++ b/src/ap/ap_drv_ops.c
@@ -478,6 +478,7 @@ int hostapd_sta_add(struct hostapd_data *hapd,
const struct ieee80211_eht_capabilities *eht_capab,
size_t eht_capab_len,
const struct ieee80211_he_6ghz_band_cap *he_6ghz_capab,
+ const struct ieee80211_s1g_capabilities *s1g_capab,
u32 flags, u8 qosinfo, u8 vht_opmode, int supp_p2p_ps,
int set, const u8 *link_addr, bool mld_link_sta,
u16 eml_cap, bool epp_sta)
@@ -503,6 +504,7 @@ int hostapd_sta_add(struct hostapd_data *hapd,
params.eht_capab = eht_capab;
params.eht_capab_len = eht_capab_len;
params.he_6ghz_capab = he_6ghz_capab;
+ params.s1g_capab = s1g_capab;
params.vht_opmode_enabled = !!(flags & WLAN_STA_VHT_OPMODE_ENABLED);
params.vht_opmode = vht_opmode;
params.flags = hostapd_sta_flags_to_drv(flags);
@@ -650,6 +652,7 @@ int hostapd_set_freq(struct hostapd_data *hapd, enum hostapd_hw_mode mode,
int edmg, u8 edmg_channel,
int ht_enabled, int vht_enabled,
int he_enabled, bool eht_enabled,
+ int s1g_enabled,
int sec_channel_offset, int oper_chwidth,
int center_segment0, int center_segment1)
{
@@ -659,7 +662,8 @@ int hostapd_set_freq(struct hostapd_data *hapd, enum hostapd_hw_mode mode,
if (hostapd_set_freq_params(&data, mode, freq, freq_offset,
channel, edmg,
edmg_channel, ht_enabled,
- vht_enabled, he_enabled, eht_enabled,
+ vht_enabled, he_enabled,
+ eht_enabled, s1g_enabled,
sec_channel_offset, oper_chwidth,
center_segment0, center_segment1,
cmode ? cmode->vht_capab : 0,
@@ -667,7 +671,9 @@ int hostapd_set_freq(struct hostapd_data *hapd, enum hostapd_hw_mode mode,
&cmode->he_capab[IEEE80211_MODE_AP] : NULL,
cmode ?
&cmode->eht_capab[IEEE80211_MODE_AP] :
- NULL, hostapd_get_punct_bitmap(hapd)))
+ NULL, hostapd_get_punct_bitmap(hapd),
+ hapd->iconf->op_class,
+ hapd->iconf->s1g_primary_2mhz))
return -1;
if (hapd->driver == NULL)
@@ -1098,14 +1104,16 @@ int hostapd_start_dfs_cac(struct hostapd_iface *iface,
if (hostapd_set_freq_params(&data, mode, freq, 0, channel, 0, 0,
ht_enabled,
- vht_enabled, he_enabled, eht_enabled,
+ vht_enabled, he_enabled,
+ eht_enabled, 0,
sec_channel_offset,
oper_chwidth, center_segment0,
center_segment1,
cmode->vht_capab,
&cmode->he_capab[IEEE80211_MODE_AP],
&cmode->eht_capab[IEEE80211_MODE_AP],
- hostapd_get_punct_bitmap(hapd))) {
+ hostapd_get_punct_bitmap(hapd),
+ hapd->iconf->op_class, false)) {
wpa_printf(MSG_ERROR, "Can't set freq params");
return -1;
}
@@ -1255,6 +1263,8 @@ int hostapd_drv_do_acs(struct hostapd_data *hapd)
for (i = 0; i < hapd->iface->num_hw_features; i++) {
mode = &hapd->iface->hw_features[i];
+ if (mode->mode == HOSTAPD_MODE_IEEE80211AH)
+ continue;
if (selected_mode != HOSTAPD_MODE_IEEE80211ANY &&
selected_mode != mode->mode)
continue;
diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h
index fa84f709de91..76e2f875fd50 100644
--- a/src/ap/ap_drv_ops.h
+++ b/src/ap/ap_drv_ops.h
@@ -48,6 +48,7 @@ int hostapd_sta_add(struct hostapd_data *hapd,
const struct ieee80211_eht_capabilities *eht_capab,
size_t eht_capab_len,
const struct ieee80211_he_6ghz_band_cap *he_6ghz_capab,
+ const struct ieee80211_s1g_capabilities *s1g_capab,
u32 flags, u8 qosinfo, u8 vht_opmode, int supp_p2p_ps,
int set, const u8 *link_addr, bool mld_link_sta,
u16 eml_cap, bool epp_sta);
@@ -74,7 +75,8 @@ int hostapd_set_freq(struct hostapd_data *hapd, enum hostapd_hw_mode mode,
int freq, int freq_offset,
int channel, int edmg, u8 edmg_channel,
int ht_enabled, int vht_enabled, int he_enabled,
- bool eht_enabled, int sec_channel_offset, int oper_chwidth,
+ bool eht_enabled, int s1g_enabled,
+ int sec_channel_offset, int oper_chwidth,
int center_segment0, int center_segment1);
int hostapd_set_rts(struct hostapd_data *hapd, int rts);
int hostapd_set_frag(struct hostapd_data *hapd, int frag);
diff --git a/src/ap/beacon.c b/src/ap/beacon.c
index 2610106871a6..0fd541868306 100644
--- a/src/ap/beacon.c
+++ b/src/ap/beacon.c
@@ -809,8 +809,15 @@ static size_t hostapd_probe_resp_elems_len(struct hostapd_data *hapd,
}
#endif /* CONFIG_IEEE80211BE */
- buflen += hostapd_eid_mbssid_len(hapd_probed, WLAN_FC_STYPE_PROBE_RESP,
- NULL,
+
+#ifdef CONFIG_IEEE80211AH
+ if (hapd->iconf->ieee80211ah) {
+ buflen += 2 + sizeof(struct ieee80211_s1g_capabilities) +
+ 2 + sizeof(struct ieee80211_s1g_operation);
+ }
+#endif /* CONFIG_IEEE80211AH */
+
+ buflen += hostapd_eid_mbssid_len(hapd_probed, WLAN_FC_STYPE_PROBE_RESP, NULL,
params->known_bss,
params->known_bss_len, NULL);
buflen += hostapd_eid_rnr_len(hapd, WLAN_FC_STYPE_PROBE_RESP, true);
@@ -919,6 +926,13 @@ static u8 * hostapd_probe_resp_fill_elems(struct hostapd_data *hapd,
}
#endif /* CONFIG_IEEE80211AC */
+#ifdef CONFIG_IEEE80211AH
+ if (hapd->iconf->ieee80211ah) {
+ pos = hostapd_eid_s1g_capab(hapd, pos);
+ pos = hostapd_eid_s1g_oper(hapd, pos);
+ }
+#endif /* CONFIG_IEEE80211AH */
+
#ifdef CONFIG_IEEE80211AX
if (hostapd_is_he_enabled(hapd) &&
is_6ghz_op_class(hapd->iconf->op_class))
@@ -1504,7 +1518,11 @@ void handle_probe_req(struct hostapd_data *hapd,
return;
}
- if ((!elems.ssid || !elems.supp_rates)) {
+ /*
+ * S1G does not support legacy rates. The equivalent information is
+ * advertised in the S1G Capabilities element.
+ */
+ if ((!elems.ssid || (!elems.supp_rates && !elems.s1g_capabilities))) {
wpa_printf(MSG_DEBUG, "STA " MACSTR " sent probe request "
"without SSID or supported rates element",
MAC2STR(mgmt->sa));
@@ -2234,6 +2252,9 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd,
struct wpa_driver_ap_params *params)
{
struct ieee80211_mgmt *head = NULL;
+#ifdef CONFIG_IEEE80211AH
+ struct ieee80211_ext *ext_head = NULL;
+#endif /* CONFIG_IEEE80211AH */
u8 *tail = NULL;
size_t head_len = 0, tail_len = 0;
u8 *resp = NULL;
@@ -2299,6 +2320,14 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd,
}
#endif /* CONFIG_IEEE80211BE */
+#ifdef CONFIG_IEEE80211AH
+ if (hapd->iconf->ieee80211ah) {
+ /* +2 for element type + length respectively */
+ tail_len += 2 + sizeof(struct ieee80211_s1g_operation) +
+ 2 + sizeof(struct ieee80211_s1g_capabilities);
+ }
+#endif /* CONFIG_IEEE80211AH */
+
if (hapd->iconf->mbssid == ENHANCED_MBSSID_ENABLED &&
hapd == hostapd_mbssid_get_tx_bss(hapd))
tail_len += 5; /* Multiple BSSID Configuration element */
@@ -2317,12 +2346,25 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd,
}
tailend = tail + tail_len;
- head->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
- WLAN_FC_STYPE_BEACON);
+ if (hapd->iconf->ieee80211ah) {
+#ifdef CONFIG_IEEE80211AH
+ ext_head = (struct ieee80211_ext *)head;
+ ext_head->duration = host_to_le16(0);
+ ext_head->frame_control = IEEE80211_FC(WLAN_FC_TYPE_EXT, WLAN_FC_STYPE_S1G_BEACON);
+ ext_head->frame_control |= WLAN_S1G_FC_BSS_BW(WLAN_S1G_BSS_BW_MIN_1_MAX_16);
+#endif /* CONFIG_IEEE80211AH */
+ } else {
+ head->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, WLAN_FC_STYPE_BEACON);
+ }
+
head->duration = host_to_le16(0);
os_memset(head->da, 0xff, ETH_ALEN);
os_memcpy(head->sa, hapd->own_addr, ETH_ALEN);
+#ifdef CONFIG_IEEE80211AH
+ if (hapd->iconf->ieee80211ah)
+ os_memcpy(ext_head->sa, hapd->own_addr, ETH_ALEN);
+#endif /* CONFIG_IEEE80211AH */
os_memcpy(head->bssid, hapd->own_addr, ETH_ALEN);
head->u.beacon.beacon_int =
host_to_le16(hapd->iconf->beacon_int);
@@ -2330,7 +2372,30 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd,
/* hardware or low-level driver will setup seq_ctrl and timestamp */
capab_info = hostapd_own_capab_info(hapd);
head->u.beacon.capab_info = host_to_le16(capab_info);
+
+#ifdef CONFIG_IEEE80211AH
+ if (hapd->iconf->ieee80211ah)
+ pos = &ext_head->u.beacon.variable[0];
+ else
+ pos = &head->u.beacon.variable[0];
+#else
pos = &head->u.beacon.variable[0];
+#endif /* CONFIG_IEEE80211AH */
+
+ /*
+ * As per IEEE80211-2024 11.1.3.10.1 the first element in a beacon
+ * transmitted at a TBTT that is not a TSBTT (i.e a long beacon) shall
+ * contain the Beacon Compatibility element as the first element in a
+ * beacon. Therefore insert this at the head before the SSID element.
+ * Other elements can be inserted in the tail as normal.
+ */
+#ifdef CONFIG_IEEE80211AH
+ if (hapd->iconf->ieee80211ah) {
+ pos = hostapd_eid_s1g_beacon_compat(hapd, pos);
+ tailpos = hostapd_eid_s1g_capab(hapd, tailpos);
+ tailpos = hostapd_eid_s1g_oper(hapd, tailpos);
+ }
+#endif /* CONFIG_IEEE80211AH */
/* SSID */
*pos++ = WLAN_EID_SSID;
@@ -2354,7 +2419,14 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd,
/* DS Params */
pos = hostapd_eid_ds_params(hapd, pos);
+#ifdef CONFIG_IEEE80211AH
+ if (hapd->iconf->ieee80211ah)
+ head_len = pos - (u8 *) ext_head;
+ else
+ head_len = pos - (u8 *) head;
+#else
head_len = pos - (u8 *) head;
+#endif /* CONFIG_IEEE80211AH */
tailpos = hostapd_eid_country(hapd, tailpos, tailend - tailpos);
@@ -2590,7 +2662,14 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd,
}
#endif /* CONFIG_SAE */
+ #ifdef CONFIG_IEEE80211AH
+ if (hapd->iconf->ieee80211ah)
+ params->head = (u8 *) ext_head;
+ else
+ params->head = (u8 *) head;
+ #else
params->head = (u8 *) head;
+ #endif /* CONFIG_IEEE80211AH */
params->head_len = head_len;
params->tail = tail;
params->tail_len = tail_len;
@@ -2812,7 +2891,7 @@ static int __ieee802_11_set_beacon(struct hostapd_data *hapd)
iconf->channel, iconf->enable_edmg,
iconf->edmg_channel, iconf->ieee80211n,
iconf->ieee80211ac, iconf->ieee80211ax,
- iconf->ieee80211be,
+ iconf->ieee80211be, iconf->ieee80211ah,
iconf->secondary_channel,
hostapd_get_oper_chwidth(iconf),
hostapd_get_oper_centr_freq_seg0_idx(iconf),
@@ -2820,7 +2899,9 @@ static int __ieee802_11_set_beacon(struct hostapd_data *hapd)
cmode->vht_capab,
&cmode->he_capab[IEEE80211_MODE_AP],
&cmode->eht_capab[IEEE80211_MODE_AP],
- hostapd_get_punct_bitmap(hapd)) == 0) {
+ hostapd_get_punct_bitmap(hapd),
+ hapd->iconf->op_class,
+ hapd->iconf->s1g_primary_2mhz) == 0) {
freq.link_id = -1;
#ifdef CONFIG_IEEE80211BE
if (hapd->conf->mld_ap)
@@ -2832,6 +2913,8 @@ static int __ieee802_11_set_beacon(struct hostapd_data *hapd)
for (i = 0; i < hapd->iface->num_hw_features; i++) {
mode = &hapd->iface->hw_features[i];
+ if (mode->mode == HOSTAPD_MODE_IEEE80211AH)
+ continue;
if (iconf->hw_mode != HOSTAPD_MODE_IEEE80211ANY &&
iconf->hw_mode != mode->mode)
continue;
diff --git a/src/ap/ctrl_iface_ap.c b/src/ap/ctrl_iface_ap.c
index 90a0a49363e3..50bc35665768 100644
--- a/src/ap/ctrl_iface_ap.c
+++ b/src/ap/ctrl_iface_ap.c
@@ -224,6 +224,8 @@ static const char * hw_mode_str(enum hostapd_hw_mode mode)
return "a";
case HOSTAPD_MODE_IEEE80211AD:
return "ad";
+ case HOSTAPD_MODE_IEEE80211AH:
+ return "ah";
case HOSTAPD_MODE_IEEE80211ANY:
return "any";
case NUM_HOSTAPD_MODES:
@@ -885,6 +887,7 @@ int hostapd_ctrl_iface_status(struct hostapd_data *hapd, char *buf,
"ieee80211ac=%d\n"
"ieee80211ax=%d\n"
"ieee80211be=%d\n"
+ "ieee80211ah=%d\n"
"beacon_int=%u\n"
"dtim_period=%d\n",
iface->conf->channel,
@@ -896,6 +899,7 @@ int hostapd_ctrl_iface_status(struct hostapd_data *hapd, char *buf,
hostapd_is_vht_enabled(hapd),
hostapd_is_he_enabled(hapd),
hostapd_is_eht_enabled(hapd),
+ iface->conf->ieee80211ah,
iface->conf->beacon_int,
hapd->conf->dtim_period);
if (os_snprintf_error(buflen - len, ret))
@@ -1173,7 +1177,9 @@ static struct hostapd_hw_modes * get_target_hw_mode(struct hostapd_iface *iface,
enum hostapd_hw_mode target_mode;
bool is_6ghz = is_6ghz_freq(freq);
- if (freq < 4000)
+ if (freq < 1000)
+ target_mode = HOSTAPD_MODE_IEEE80211AH;
+ else if (freq < 4000)
target_mode = HOSTAPD_MODE_IEEE80211G;
else if (freq > 50000)
target_mode = HOSTAPD_MODE_IEEE80211AD;
diff --git a/src/ap/dfs.c b/src/ap/dfs.c
index 2758454ff033..683485f85c6b 100644
--- a/src/ap/dfs.c
+++ b/src/ap/dfs.c
@@ -839,7 +839,7 @@ int hostapd_handle_dfs(struct hostapd_iface *iface)
int res, n_chans, n_chans1, start_chan_idx, start_chan_idx1;
int skip_radar = 0;
- if (is_6ghz_freq(iface->freq))
+ if (is_6ghz_freq(iface->freq) || is_s1ghz_freq(iface->freq))
return 1;
if (!iface->current_mode) {
@@ -1021,6 +1021,7 @@ static int hostapd_dfs_request_channel_switch(struct hostapd_iface *iface,
iface->conf->ieee80211ac,
iface->conf->ieee80211ax,
iface->conf->ieee80211be,
+ 0, /* No DFS for S1G */
secondary_channel,
new_vht_oper_chwidth,
oper_centr_freq_seg0_idx,
@@ -1028,7 +1029,8 @@ static int hostapd_dfs_request_channel_switch(struct hostapd_iface *iface,
cmode->vht_capab,
&cmode->he_capab[ieee80211_mode],
&cmode->eht_capab[ieee80211_mode],
- hostapd_get_punct_bitmap(iface->bss[0]));
+ hostapd_get_punct_bitmap(iface->bss[0]),
+ iface->conf->op_class, false);
if (err) {
wpa_printf(MSG_ERROR,
@@ -1599,6 +1601,7 @@ int hostapd_is_dfs_required(struct hostapd_iface *iface)
if ((!(iface->drv_flags & WPA_DRIVER_FLAGS_DFS_OFFLOAD) &&
!iface->conf->ieee80211h) ||
!iface->current_mode ||
+ iface->current_mode->mode == HOSTAPD_MODE_IEEE80211AH ||
iface->current_mode->mode != HOSTAPD_MODE_IEEE80211A)
return 0;
@@ -1715,6 +1718,7 @@ int hostapd_is_dfs_overlap(struct hostapd_iface *iface, enum chan_width width,
int i;
if (!iface->conf->ieee80211h || !mode ||
+ mode->mode == HOSTAPD_MODE_IEEE80211AH ||
mode->mode != HOSTAPD_MODE_IEEE80211A)
return 0;
diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c
index a15e17919b3f..6f0b34b55de5 100644
--- a/src/ap/hostapd.c
+++ b/src/ap/hostapd.c
@@ -133,7 +133,8 @@ static void hostapd_reload_bss(struct hostapd_data *hapd)
if (hapd->conf->wmm_enabled < 0)
hapd->conf->wmm_enabled = hapd->iconf->ieee80211n |
- hapd->iconf->ieee80211ax;
+ hapd->iconf->ieee80211ax |
+ hapd->iconf->ieee80211ah;
#ifndef CONFIG_NO_RADIUS
radius_client_reconfig(hapd->radius, hapd->conf->radius);
@@ -1541,7 +1542,8 @@ setup_mld:
if (conf->wmm_enabled < 0)
conf->wmm_enabled = hapd->iconf->ieee80211n |
- hapd->iconf->ieee80211ax;
+ hapd->iconf->ieee80211ax |
+ hapd->iconf->ieee80211ah;
#ifdef CONFIG_IEEE80211R_AP
if (is_zero_ether_addr(conf->r1_key_holder))
@@ -2654,6 +2656,7 @@ static int hostapd_setup_interface_complete_sync(struct hostapd_iface *iface,
hapd->iconf->ieee80211ac,
hapd->iconf->ieee80211ax,
hapd->iconf->ieee80211be,
+ hapd->iconf->ieee80211ah,
hapd->iconf->secondary_channel,
hostapd_get_oper_chwidth(hapd->iconf),
hostapd_get_oper_centr_freq_seg0_idx(
@@ -4532,7 +4535,8 @@ int hostapd_change_config_freq(struct hostapd_data *hapd,
conf->channel, conf->enable_edmg,
conf->edmg_channel, conf->ieee80211n,
conf->ieee80211ac, conf->ieee80211ax,
- conf->ieee80211be, conf->secondary_channel,
+ conf->ieee80211be, 0,
+ conf->secondary_channel,
hostapd_get_oper_chwidth(conf),
hostapd_get_oper_centr_freq_seg0_idx(conf),
hostapd_get_oper_centr_freq_seg1_idx(conf),
@@ -4541,7 +4545,8 @@ int hostapd_change_config_freq(struct hostapd_data *hapd,
NULL,
mode ? &mode->eht_capab[IEEE80211_MODE_AP] :
NULL,
- hostapd_get_punct_bitmap(hapd)))
+ hostapd_get_punct_bitmap(hapd),
+ hapd->iconf->op_class, false))
return -1;
switch (params->bandwidth) {
diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c
index 6ac7cd9d8837..b55c0a62cc75 100644
--- a/src/ap/hw_features.c
+++ b/src/ap/hw_features.c
@@ -261,6 +261,12 @@ int hostapd_prepare_rates(struct hostapd_data *hapd,
int basic_rates_g[] = { 10, 20, 55, 110, 0 };
const int *basic_rates;
+#ifdef CONFIG_IEEE80211AH
+ /* S1G does not support basic rates */
+ if (mode->mode == HOSTAPD_MODE_IEEE80211AH)
+ return 0;
+#endif
+
if (conf->basic_rates)
basic_rates = conf->basic_rates;
else switch (mode->mode) {
@@ -1114,6 +1120,12 @@ static int hostapd_is_usable_chans(struct hostapd_iface *iface)
wpa_printf(MSG_ERROR, "Primary frequency not allowed");
return err;
}
+
+#ifdef CONFIG_IEEE80211AH
+ if (iface->conf->ieee80211ah)
+ return 1;
+#endif
+
err = hostapd_is_usable_edmg(iface);
if (err <= 0)
return err;
@@ -1181,7 +1193,9 @@ int hostapd_determine_mode(struct hostapd_iface *iface)
iface->conf->hw_mode != HOSTAPD_MODE_IEEE80211ANY)
return 0;
- if (iface->freq < 4000)
+ if (iface->freq < 1000)
+ target_mode = HOSTAPD_MODE_IEEE80211AH;
+ else if (iface->freq < 4000)
target_mode = HOSTAPD_MODE_IEEE80211G;
else if (iface->freq > 50000)
target_mode = HOSTAPD_MODE_IEEE80211AD;
@@ -1357,6 +1371,14 @@ int hostapd_select_hw_mode(struct hostapd_iface *iface)
iface->conf->ieee80211be = 0;
}
+ if (iface->conf->hw_mode == HOSTAPD_MODE_IEEE80211AH) {
+ wpa_printf(MSG_INFO, "Disabling HT/VHT/HE/EHT for S1G mode");
+ iface->conf->ieee80211n = 0;
+ iface->conf->ieee80211ac = 0;
+ iface->conf->ieee80211ax = 0;
+ iface->conf->ieee80211be = 0;
+ }
+
iface->current_mode = NULL;
for (i = 0; i < iface->num_hw_features; i++) {
struct hostapd_hw_modes *mode = &iface->hw_features[i];
@@ -1419,6 +1441,8 @@ const char * hostapd_hw_mode_txt(int mode)
return "IEEE 802.11g";
case HOSTAPD_MODE_IEEE80211AD:
return "IEEE 802.11ad";
+ case HOSTAPD_MODE_IEEE80211AH:
+ return "IEEE 802.11ah";
default:
return "UNKNOWN";
}
@@ -1466,6 +1490,12 @@ int hostapd_hw_skip_mode(struct hostapd_iface *iface,
{
int i;
+ if (mode->mode == HOSTAPD_MODE_IEEE80211AH && !iface->current_mode)
+ return 1;
+ if (iface->current_mode &&
+ iface->current_mode->mode != HOSTAPD_MODE_IEEE80211AH &&
+ mode->mode == HOSTAPD_MODE_IEEE80211AH)
+ return 1;
if (iface->current_mode)
return mode != iface->current_mode;
if (mode->mode != HOSTAPD_MODE_IEEE80211B)
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index 50f7e2a35f2e..fbb5f83f7e90 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -173,6 +173,12 @@ u8 * hostapd_eid_supp_rates(struct hostapd_data *hapd, u8 *eid)
u8 buf[100];
size_t len;
+#ifdef CONFIG_IEEE80211AH
+ /* No supp rates for S1G */
+ if (hapd->iconf->ieee80211ah)
+ return eid;
+#endif /* CONFIG_IEEE80211AH */
+
len = hostapd_supp_rates(hapd, buf);
if (len == 0)
return eid;
@@ -4971,9 +4977,10 @@ static u16 check_multi_ap(struct hostapd_data *hapd, struct sta_info *sta,
static u16 copy_supp_rates(struct hostapd_data *hapd, struct sta_info *sta,
struct ieee802_11_elems *elems)
{
- /* Supported rates not used in IEEE 802.11ad/DMG */
- if (hapd->iface->current_mode &&
- hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211AD)
+ /* Supported rates not used in IEEE 802.11ad/DMG/ah */
+ if ((hapd->iface->current_mode &&
+ hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211AD) ||
+ hapd->iconf->ieee80211ah)
return WLAN_STATUS_SUCCESS;
if (!elems->supp_rates) {
@@ -5543,6 +5550,15 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
}
#endif /* CONFIG_IEEE80211BE */
+#ifdef CONFIG_IEEE80211AH
+ if (hapd->iconf->ieee80211ah) {
+ resp = copy_sta_s1g_capab(hapd, sta, IEEE80211_MODE_AP,
+ elems->s1g_capabilities);
+ if (resp != WLAN_STATUS_SUCCESS)
+ return resp;
+ }
+#endif /* CONFIG_IEEE80211AH */
+
#ifdef CONFIG_P2P
if (elems->p2p && ies && ies_len) {
wpabuf_free(sta->p2p_ie);
@@ -6471,6 +6487,9 @@ static int add_associated_sta(struct hostapd_data *hapd,
struct ieee80211_vht_capabilities vht_cap;
struct ieee80211_he_capabilities he_cap;
struct ieee80211_eht_capabilities eht_cap;
+#ifdef CONFIG_IEEE80211AH
+ struct ieee80211_s1g_capabilities s1g_cap;
+#endif /* CONFIG_IEEE80211AH */
int set = 1;
const u8 *mld_link_addr = NULL;
bool mld_link_sta = false, epp_sta = false;
@@ -6553,6 +6572,12 @@ static int add_associated_sta(struct hostapd_data *hapd,
sta->eht_capab_len);
#endif /* CONFIG_IEEE80211BE */
+#ifdef CONFIG_IEEE80211AH
+ if (sta->flags & WLAN_STA_S1G)
+ hostapd_get_s1g_capab(hapd, sta->s1g_capab, &s1g_cap,
+ sizeof(s1g_cap));
+#endif /* CONFIG_IEEE80211AH */
+
/*
* Add the station with forced WLAN_STA_ASSOC flag. The sta->flags
* will be set when the ACK frame for the (Re)Association Response frame
@@ -6567,7 +6592,7 @@ static int add_associated_sta(struct hostapd_data *hapd,
sta->flags & WLAN_STA_HE ? sta->he_capab_len : 0,
sta->flags & WLAN_STA_EHT ? &eht_cap : NULL,
sta->flags & WLAN_STA_EHT ? sta->eht_capab_len : 0,
- sta->he_6ghz_capab,
+ sta->he_6ghz_capab, sta->s1g_capab,
sta->flags | WLAN_STA_ASSOC, sta->qosinfo,
sta->vht_opmode, sta->p2p_ie ? 1 : 0,
set, mld_link_addr, mld_link_sta, eml_cap,
@@ -6602,6 +6627,7 @@ static u16 send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta,
struct ieee80211_mgmt *reply;
u8 *p;
u16 res = WLAN_STATUS_SUCCESS;
+ le16 aid;
buflen = sizeof(struct ieee80211_mgmt) + 1024;
#ifdef CONFIG_FILS
@@ -6645,15 +6671,23 @@ static u16 send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta,
os_memcpy(reply->bssid, hapd->own_addr, ETH_ALEN);
send_len = IEEE80211_HDRLEN;
- send_len += sizeof(reply->u.assoc_resp);
reply->u.assoc_resp.capab_info =
host_to_le16(hostapd_own_capab_info(hapd));
reply->u.assoc_resp.status_code = host_to_le16(status_code);
- reply->u.assoc_resp.aid = host_to_le16((sta ? sta->aid : 0) |
- BIT(14) | BIT(15));
+ aid = host_to_le16((sta ? sta->aid : 0) | BIT(14) | BIT(15));
+ if (hapd->iconf->ieee80211ah) {
+ /* S1G AID is carried in the AID Response element */
+ send_len += sizeof(reply->u.s1g_assoc_resp);
+ p = reply->u.s1g_assoc_resp.variable;
+ } else {
+ send_len += sizeof(reply->u.assoc_resp);
+ reply->u.assoc_resp.aid = aid;
+ p = reply->u.assoc_resp.variable;
+ }
+
/* Supported rates */
- p = hostapd_eid_supp_rates(hapd, reply->u.assoc_resp.variable);
+ p = hostapd_eid_supp_rates(hapd, p);
/* Extended supported rates */
p = hostapd_eid_ext_supp_rates(hapd, p);
@@ -6747,6 +6781,14 @@ static u16 send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta,
}
#endif /* CONFIG_IEEE80211AX */
+#ifdef CONFIG_IEEE80211AH
+ if (hapd->iconf->ieee80211ah) {
+ p = hostapd_eid_s1g_capab(hapd, p);
+ p = hostapd_eid_s1g_oper(hapd, p);
+ p = hostapd_eid_aid_response(hapd, p, aid);
+ }
+#endif /* CONFIG_IEEE80211AH */
+
p = hostapd_eid_ext_capab(hapd, p, false);
p = hostapd_eid_bss_max_idle_period(hapd, p,
sta ? sta->max_idle_period : 0);
@@ -7339,7 +7381,7 @@ static void handle_assoc(struct hostapd_data *hapd,
hostapd_logger(hapd, mgmt->sa,
HOSTAPD_MODULE_IEEE80211,
HOSTAPD_LEVEL_INFO,
- "Station tried to associate before authentication (aid=%d flags=0x%x)",
+ "Station tried to associate before authentication (aid=%d flags=0x%lx)",
sta ? sta->aid : -1,
sta ? sta->flags : 0);
send_deauth(hapd, mgmt->sa,
diff --git a/src/ap/ieee802_11.h b/src/ap/ieee802_11.h
index 664d585b994c..38cb181aa850 100644
--- a/src/ap/ieee802_11.h
+++ b/src/ap/ieee802_11.h
@@ -103,6 +103,10 @@ u8 * hostapd_eid_he_operation(struct hostapd_data *hapd, u8 *eid);
u8 * hostapd_eid_he_mu_edca_parameter_set(struct hostapd_data *hapd, u8 *eid);
u8 * hostapd_eid_spatial_reuse(struct hostapd_data *hapd, u8 *eid);
u8 * hostapd_eid_he_6ghz_band_cap(struct hostapd_data *hapd, u8 *eid);
+u8 * hostapd_eid_s1g_oper(struct hostapd_data *hapd, u8 *eid);
+u8 * hostapd_eid_s1g_capab(struct hostapd_data *hapd, u8 *eid);
+u8 * hostapd_eid_s1g_beacon_compat(struct hostapd_data *hapd, u8 *eid);
+u8 * hostapd_eid_aid_response(struct hostapd_data *hapd, u8 *eid, le16 aid);
int hostapd_ht_operation_update(struct hostapd_iface *iface);
void ieee802_11_send_sa_query_req(struct hostapd_data *hapd,
@@ -189,6 +193,12 @@ int hostapd_update_time_adv(struct hostapd_data *hapd);
void hostapd_client_poll_ok(struct hostapd_data *hapd, const u8 *addr);
u8 * hostapd_eid_bss_max_idle_period(struct hostapd_data *hapd, u8 *eid,
u16 value);
+u16 copy_sta_s1g_capab(struct hostapd_data *hapd, struct sta_info *sta,
+ enum ieee80211_op_mode opmode, const u8 *s1g_capab);
+void hostapd_get_s1g_capab(struct hostapd_data *hapd,
+ const struct ieee80211_s1g_capabilities *src,
+ struct ieee80211_s1g_capabilities *dest,
+ size_t len);
int auth_sae_init_committed(struct hostapd_data *hapd, struct sta_info *sta);
#ifdef CONFIG_SAE
diff --git a/src/ap/ieee802_11_s1g.c b/src/ap/ieee802_11_s1g.c
new file mode 100644
index 000000000000..b79de44bd4f2
--- /dev/null
+++ b/src/ap/ieee802_11_s1g.c
@@ -0,0 +1,196 @@
+/*
+ * hostapd / IEEE 802.11ah S1G
+ * Copyright (c) 2022, Morse Micro
+ *
+ * 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 "common/ieee802_11_defs.h"
+#include "hostapd.h"
+#include "ap_config.h"
+#include "sta_info.h"
+#include "beacon.h"
+#include "ieee802_11.h"
+
+/* 1SS, MCS9. TODO set via config */
+#define S1G_BASIC_MCS_NSS 0xCCC4
+
+static u8 hostapd_global_op_class_to_s1g_op_class(u8 global)
+{
+ switch(global) {
+ /* US */
+ case 68: return 1;
+ case 69: return 2;
+ case 70: return 3;
+ case 71: return 4;
+ case 72: return 5;
+ default: return 0;
+ }
+}
+
+static u8 hostapd_s1g_calc_pri_1mhz_loc(struct hostapd_data *hapd)
+{
+ u8 low_chan;
+ u8 pri_1mhz_chan = hapd->iconf->channel;
+ u8 oper_ch = hapd->iconf->s1g_oper_centr_freq_idx ?
+ hapd->iconf->s1g_oper_centr_freq_idx :
+ hapd->iconf->channel;
+
+ if (hapd->iconf->s1g_oper_chwidth == CONF_OPER_CHWIDTH_1MHZ)
+ return 0;
+
+ switch (hapd->iconf->s1g_oper_chwidth) {
+ case CONF_OPER_CHWIDTH_4MHZ:
+ low_chan = oper_ch - 3;
+ break;
+ case CONF_OPER_CHWIDTH_8MHZ:
+ low_chan = oper_ch - 7;
+ break;
+ case CONF_OPER_CHWIDTH_16MHZ:
+ low_chan = oper_ch - 15;
+ break;
+ default:
+ return 0;
+ }
+
+ return ((pri_1mhz_chan - low_chan) / 2) & 1;
+}
+
+u8 *hostapd_eid_s1g_oper(struct hostapd_data *hapd, u8 *eid)
+{
+ struct ieee80211_s1g_operation *cap;
+ u8 *pos = eid;
+ u8 ch_width = 0;
+ u8 bss_width;
+ u8 s1g_pri_loc = hostapd_s1g_calc_pri_1mhz_loc(hapd);
+
+ *pos++ = WLAN_EID_S1G_OPERATION;
+ *pos++ = sizeof(*cap);
+
+ cap = (struct ieee80211_s1g_operation *) pos;
+ os_memset(cap, 0, sizeof(*cap));
+
+ /* IEEE80211-2024 Table 10-39 */
+ switch (hapd->iconf->s1g_oper_chwidth) {
+ case CONF_OPER_CHWIDTH_1MHZ:
+ bss_width = 0x0;
+ break;
+ case CONF_OPER_CHWIDTH_2MHZ:
+ bss_width = 0x1;
+ break;
+ case CONF_OPER_CHWIDTH_4MHZ:
+ bss_width = 0x3;
+ break;
+ case CONF_OPER_CHWIDTH_8MHZ:
+ bss_width = 0x7;
+ break;
+ case CONF_OPER_CHWIDTH_16MHZ:
+ bss_width = 0xF;
+ break;
+ default:
+ return NULL;
+ }
+
+ if (!hapd->iconf->s1g_primary_2mhz)
+ ch_width |= S1G_OPER_IE_CHANWIDTH_PRIM_CH_MASK;
+
+ ch_width |= (bss_width << 1) & S1G_OPER_IE_CHANWIDTH_OPER_CH_MASK;
+
+ if (hapd->iconf->s1g_primary_2mhz && s1g_pri_loc)
+ ch_width |= S1G_OPER_IE_CHANWIDTH_PRIM_OFFSET;
+
+ cap->ch_width = ch_width;
+ cap->oper_class = hostapd_global_op_class_to_s1g_op_class(hapd->iconf->op_class);
+ cap->basic_mcs_nss = host_to_le16(S1G_BASIC_MCS_NSS);
+
+ if (hapd->iconf->s1g_primary_2mhz) {
+ if (s1g_pri_loc)
+ cap->primary_ch = hapd->iconf->channel - 1;
+ else
+ cap->primary_ch = hapd->iconf->channel + 1;
+ } else {
+ cap->primary_ch = hapd->iconf->channel;
+ }
+
+ cap->oper_ch = hapd->iconf->s1g_oper_centr_freq_idx ?
+ hapd->iconf->s1g_oper_centr_freq_idx :
+ cap->primary_ch;
+
+ pos += sizeof(*cap);
+ return pos;
+}
+
+u8 * hostapd_eid_s1g_capab(struct hostapd_data *hapd, u8 *eid)
+{
+ struct ieee80211_s1g_capabilities *cap;
+ u8 *pos = eid;
+
+ *pos++ = WLAN_EID_S1G_CAPABILITIES;
+ *pos++ = sizeof(*cap);
+
+ cap = (struct ieee80211_s1g_capabilities *) pos;
+ os_memset(cap, 0, sizeof(*cap));
+
+ os_memcpy(cap, &hapd->iface->current_mode->s1g_capab, sizeof(*cap));
+
+ pos += sizeof(*cap);
+ return pos;
+}
+
+
+u8 * hostapd_eid_s1g_beacon_compat(struct hostapd_data *hapd, u8 *eid)
+{
+ u8 *pos = eid;
+ u16 beacon_int;
+
+ beacon_int = hapd->iconf->beacon_int;
+
+ *pos++ = WLAN_EID_S1G_BCN_COMPAT;
+ *pos++ = 8;
+ *pos++ = WLAN_CAPABILITY_ESS | WLAN_CAPABILITY_PRIVACY;;
+ *pos++ = 0;
+
+ *pos++ = beacon_int & 0xff;
+ *pos++ = (beacon_int >> 8) & 0xff;
+
+ /*
+ * Last 4 octets will be filled by the driver/fw to contain the 4
+ * MSB of the TSF timer at time of generation.
+ */
+ *pos++ = 0;
+ *pos++ = 0;
+ *pos++ = 0;
+ *pos++ = 0;
+ return pos;
+}
+
+u16 copy_sta_s1g_capab(struct hostapd_data *hapd, struct sta_info *sta,
+ enum ieee80211_op_mode opmode, const u8 *s1g_capab)
+{
+ sta->s1g_capab = os_memdup(s1g_capab, sizeof(struct ieee80211_s1g_capabilities));
+ if (!sta->s1g_capab)
+ return WLAN_STATUS_UNSPECIFIED_FAILURE;
+
+ sta->flags |= WLAN_STA_S1G;
+
+ return WLAN_STATUS_SUCCESS;
+}
+
+void hostapd_get_s1g_capab(struct hostapd_data *hapd,
+ const struct ieee80211_s1g_capabilities *src,
+ struct ieee80211_s1g_capabilities *dest,
+ size_t len)
+{
+ if (!src || !dest)
+ return;
+
+ if (len > sizeof(*dest))
+ len = sizeof(*dest);
+
+ os_memset(dest, 0, sizeof(*dest));
+ os_memcpy(dest, src, len);
+}
\ No newline at end of file
diff --git a/src/ap/ieee802_11_shared.c b/src/ap/ieee802_11_shared.c
index a6decab8d97b..42b1efe471aa 100644
--- a/src/ap/ieee802_11_shared.c
+++ b/src/ap/ieee802_11_shared.c
@@ -496,6 +496,20 @@ static void hostapd_ext_capab_byte(struct hostapd_data *hapd, u8 *pos, int idx,
}
}
+u8 * hostapd_eid_aid_response(struct hostapd_data *hapd, u8 *eid, le16 aid)
+{
+ u8 *pos = eid;
+ const u8 len = 5;
+
+ *pos++ = WLAN_EID_AID_RESPONSE;
+ *pos++ = len;
+
+ memset(pos, 0, len);
+ u8 *end = pos + len;
+ *pos++ = aid;
+
+ return end;
+}
u8 * hostapd_eid_ext_capab(struct hostapd_data *hapd, u8 *eid,
bool mbssid_complete)
diff --git a/src/ap/interference.c b/src/ap/interference.c
index 44aa6c0019ef..12ab133c32d6 100644
--- a/src/ap/interference.c
+++ b/src/ap/interference.c
@@ -558,6 +558,7 @@ int hostapd_incumbt_sig_intf_detected(struct hostapd_iface *iface, int freq,
iface->conf->ieee80211ac,
iface->conf->ieee80211ax,
iface->conf->ieee80211be,
+ iface->conf->ieee80211ah,
iface->conf->secondary_channel,
hostapd_get_oper_chwidth(iface->conf),
hostapd_get_oper_centr_freq_seg0_idx(iface->conf),
diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c
index 2bb7e1235cb0..246996054e44 100644
--- a/src/ap/sta_info.c
+++ b/src/ap/sta_info.c
@@ -474,6 +474,7 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta)
os_free(sta->he_capab);
os_free(sta->he_6ghz_capab);
os_free(sta->eht_capab);
+ os_free(sta->s1g_capab);
hostapd_free_psk_list(sta->psk);
os_free(sta->identity);
os_free(sta->radius_cui);
@@ -602,7 +603,7 @@ void ap_handle_timer(void *eloop_ctx, void *timeout_ctx)
int reason;
int max_inactivity = hapd->conf->ap_max_inactivity;
- wpa_printf(MSG_DEBUG, "%s: %s: " MACSTR " flags=0x%x timeout_next=%d",
+ wpa_printf(MSG_DEBUG, "%s: %s: " MACSTR " flags=0x%lx timeout_next=%d",
hapd->conf->iface, __func__, MAC2STR(sta->addr), sta->flags,
sta->timeout_next);
if (sta->timeout_next == STA_REMOVE) {
@@ -2115,7 +2116,7 @@ int ap_sta_re_add(struct hostapd_data *hapd, struct sta_info *sta)
if (hostapd_sta_add(hapd, sta->addr, 0, 0,
sta->supported_rates,
sta->supported_rates_len,
- 0, NULL, NULL, NULL, 0, NULL, 0, NULL,
+ 0, NULL, NULL, NULL, 0, NULL, 0, NULL, NULL,
sta->flags, 0, 0, 0, 0,
mld_link_addr, mld_link_sta, eml_cap, epp_sta)) {
hostapd_logger(hapd, sta->addr,
diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h
index 9810eb22f9a4..10dfdb935892 100644
--- a/src/ap/sta_info.h
+++ b/src/ap/sta_info.h
@@ -49,6 +49,7 @@
#define WLAN_STA_PENDING_DISASSOC_CB BIT(29)
#define WLAN_STA_PENDING_DEAUTH_CB BIT(30)
#define WLAN_STA_NONERP BIT(31)
+#define WLAN_STA_S1G 1ull << 32
/* Maximum number of supported rates (from both Supported Rates and Extended
* Supported Rates IEs). */
@@ -112,7 +113,7 @@ struct sta_info {
struct dl_list ip6addr; /* list head for struct ip6addr */
u16 aid; /* STA's unique AID (1 .. 2007) or 0 if not yet assigned */
u16 disconnect_reason_code; /* RADIUS server override */
- u32 flags; /* Bitfield of WLAN_STA_* */
+ u64 flags; /* Bitfield of WLAN_STA_* */
u16 capability;
u16 listen_interval; /* or beacon_int for APs */
u8 supported_rates[WLAN_SUPP_RATES_MAX];
@@ -226,6 +227,7 @@ struct sta_info {
struct ieee80211_he_6ghz_band_cap *he_6ghz_capab;
struct ieee80211_eht_capabilities *eht_capab;
size_t eht_capab_len;
+ struct ieee80211_s1g_capabilities *s1g_capab;
int sa_query_count; /* number of pending SA Query requests;
* 0 = no SA Query in progress */
diff --git a/src/common/defs.h b/src/common/defs.h
index ec602d660ace..0ba6f5ae5a59 100644
--- a/src/common/defs.h
+++ b/src/common/defs.h
@@ -418,6 +418,7 @@ enum hostapd_hw_mode {
HOSTAPD_MODE_IEEE80211G,
HOSTAPD_MODE_IEEE80211A,
HOSTAPD_MODE_IEEE80211AD,
+ HOSTAPD_MODE_IEEE80211AH,
HOSTAPD_MODE_IEEE80211ANY,
NUM_HOSTAPD_MODES
};
@@ -510,6 +511,11 @@ enum oper_chan_width {
CONF_OPER_CHWIDTH_8640MHZ,
CONF_OPER_CHWIDTH_40MHZ_6GHZ,
CONF_OPER_CHWIDTH_320MHZ,
+ CONF_OPER_CHWIDTH_1MHZ,
+ CONF_OPER_CHWIDTH_2MHZ,
+ CONF_OPER_CHWIDTH_4MHZ,
+ CONF_OPER_CHWIDTH_8MHZ,
+ CONF_OPER_CHWIDTH_16MHZ,
};
enum key_flag {
diff --git a/src/common/hw_features_common.c b/src/common/hw_features_common.c
index 32471ce36651..cd17bb6d5546 100644
--- a/src/common/hw_features_common.c
+++ b/src/common/hw_features_common.c
@@ -485,13 +485,15 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data,
int channel, int enable_edmg,
u8 edmg_channel, int ht_enabled,
int vht_enabled, int he_enabled,
- bool eht_enabled, int sec_channel_offset,
+ bool eht_enabled, int s1g_enabled,
+ int sec_channel_offset,
enum oper_chan_width oper_chwidth,
int center_segment0,
int center_segment1, u32 vht_caps,
struct he_capabilities *he_cap,
struct eht_capabilities *eht_cap,
- u16 punct_bitmap)
+ u16 punct_bitmap, int op_class,
+ bool s1g_primary_2mhz)
{
enum oper_chan_width oper_chwidth_legacy;
u8 seg0_legacy, seg1_legacy;
@@ -509,10 +511,12 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data,
data->vht_enabled = vht_enabled;
data->he_enabled = he_enabled;
data->eht_enabled = eht_enabled;
+ data->s1g_enabled = s1g_enabled;
data->sec_channel_offset = sec_channel_offset;
data->center_freq1 = freq + sec_channel_offset * 10;
data->center_freq2 = 0;
data->punct_bitmap = punct_bitmap;
+ data->s1g_primary_2mhz = s1g_primary_2mhz;
if (oper_chwidth == CONF_OPER_CHWIDTH_80MHZ)
data->bandwidth = 80;
else if (oper_chwidth == CONF_OPER_CHWIDTH_160MHZ ||
@@ -529,7 +533,40 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data,
hostapd_encode_edmg_chan(enable_edmg, edmg_channel, channel,
&data->edmg);
- if (is_6ghz_freq(freq)) {
+ if (is_s1ghz_freq(freq)) {
+ if (oper_chwidth == CONF_OPER_CHWIDTH_16MHZ)
+ data->bandwidth = 16;
+ else if (oper_chwidth == CONF_OPER_CHWIDTH_8MHZ)
+ data->bandwidth = 8;
+ else if (oper_chwidth == CONF_OPER_CHWIDTH_4MHZ)
+ data->bandwidth = 4;
+ else if (oper_chwidth == CONF_OPER_CHWIDTH_2MHZ)
+ data->bandwidth = 2;
+ else
+ data->bandwidth = 1;
+
+ if (!center_segment0) {
+ data->center_freq1 = data->freq;
+ data->center_freq1_offset = data->freq_offset;
+ } else {
+ int freq1 = ieee80211_chan_to_freq_khz(NULL, op_class, center_segment0);
+ if (freq1 < 0) {
+ wpa_printf(MSG_ERROR,
+ "Invalid segment 0 center frequency for S1G");
+ return -1;
+ }
+
+ data->center_freq1 = KHZ_TO_MHZ(freq1);
+ data->center_freq1_offset = KHZ_TO_S1G_OFFSET(freq1);
+ }
+
+ data->ht_enabled = 0;
+ data->vht_enabled = 0;
+ data->he_enabled = 0;
+ data->eht_enabled = 0;
+ data->center_freq2 = 0;
+ return 0;
+ } else if (is_6ghz_freq(freq)) {
if (!data->he_enabled && !data->eht_enabled) {
wpa_printf(MSG_ERROR,
"Can't set 6 GHz mode - HE or EHT aren't enabled");
@@ -962,6 +999,12 @@ int chan_bw_allowed(const struct hostapd_channel_data *chan, u32 bw,
u32 bw_mask;
switch (bw) {
+ case 4:
+ return chan->allowed_bw & HOSTAPD_CHAN_WIDTH_4;
+ case 8:
+ return chan->allowed_bw & HOSTAPD_CHAN_WIDTH_8;
+ case 16:
+ return chan->allowed_bw & HOSTAPD_CHAN_WIDTH_16;
case 20:
bw_mask = HOSTAPD_CHAN_WIDTH_20;
break;
diff --git a/src/common/hw_features_common.h b/src/common/hw_features_common.h
index 63c0893e2c2b..58138c49fe42 100644
--- a/src/common/hw_features_common.h
+++ b/src/common/hw_features_common.h
@@ -45,13 +45,15 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data,
int channel, int edmg, u8 edmg_channel,
int ht_enabled,
int vht_enabled, int he_enabled,
- bool eht_enabled, int sec_channel_offset,
+ bool eht_enabled, int s1g_enabled,
+ int sec_channel_offset,
enum oper_chan_width oper_chwidth,
int center_segment0,
int center_segment1, u32 vht_caps,
struct he_capabilities *he_caps,
struct eht_capabilities *eht_cap,
- u16 punct_bitmap);
+ u16 punct_bitmap, int op_class,
+ bool s1g_primary_2mhz);
void set_disable_ht40(struct ieee80211_ht_capabilities *htcaps,
int disabled);
int ieee80211ac_cap_check(u32 hw, u32 conf);
diff --git a/src/common/ieee802_11_common.c b/src/common/ieee802_11_common.c
index a3b9592e23bc..88dbe0fc83ba 100644
--- a/src/common/ieee802_11_common.c
+++ b/src/common/ieee802_11_common.c
@@ -702,7 +702,7 @@ static ParseRes __ieee802_11_parse_elems(const u8 *start, size_t len,
case WLAN_EID_S1G_CAPABILITIES:
if (elen < 15)
break;
- elems->s1g_capab = pos;
+ elems->s1g_capabilities = pos;
break;
case WLAN_EID_FRAGMENT:
wpa_printf(MSG_MSGDUMP,
@@ -714,6 +714,16 @@ static ParseRes __ieee802_11_parse_elems(const u8 *start, size_t len,
len, show_errors))
unknown++;
break;
+ case WLAN_EID_S1G_OPERATION:
+ if (elen < sizeof(struct ieee80211_s1g_operation))
+ break;
+ elems->s1g_operation = pos;
+ break;
+ case WLAN_EID_AID_RESPONSE:
+ if (elen < sizeof(struct ieee80211_aid_response))
+ break;
+ elems->aid_response = pos;
+ break;
default:
unknown++;
if (!show_errors)
@@ -917,7 +927,10 @@ void ieee802_11_elems_clear_ids(struct ieee802_11_elems *elems,
elems->dils_len = 0;
break;
case WLAN_EID_S1G_CAPABILITIES:
- elems->s1g_capab = NULL;
+ elems->s1g_capabilities = NULL;
+ break;
+ case WLAN_EID_S1G_OPERATION:
+ elems->s1g_operation = NULL;
break;
}
}
@@ -1515,6 +1528,9 @@ ieee80211_freq_to_channel_ext(unsigned int freq, int sec_channel,
/* TODO: more operating classes */
+ if (is_s1ghz_freq(freq))
+ return HOSTAPD_MODE_IEEE80211AH;
+
if (sec_channel > 1 || sec_channel < -1)
return NUM_HOSTAPD_MODES;
@@ -2022,10 +2038,6 @@ static int ieee80211_chan_to_freq_khz_global(u8 op_class, u8 chan)
{
/* Table E-4 in IEEE Std 802.11-2020 - Global operating classes */
switch (op_class) {
- case 50:
- case 51:
- case 52:
- case 53:
case 68:
case 69:
case 70:
@@ -3165,6 +3177,12 @@ bool is_same_band(int freq1, int freq2)
}
+bool is_s1ghz_freq(int freq)
+{
+ return freq < 1000;
+}
+
+
int ieee802_11_parse_candidate_list(const char *pos, u8 *nei_rep,
size_t nei_rep_len)
{
@@ -3453,6 +3471,16 @@ int op_class_to_bandwidth(u8 op_class)
enum oper_chan_width op_class_to_ch_width(u8 op_class)
{
switch (op_class) {
+ case 68: /* S1G 2024 US 1MHz */
+ return CONF_OPER_CHWIDTH_1MHZ;
+ case 69: /* S1G 2024 US 2MHz */
+ return CONF_OPER_CHWIDTH_2MHZ;
+ case 70: /* S1G 2024 US 4MHz */
+ return CONF_OPER_CHWIDTH_4MHZ;
+ case 71: /* S1G 2024 US 8MHz */
+ return CONF_OPER_CHWIDTH_8MHZ;
+ case 72: /* S1G 2024 US 16MHz */
+ return CONF_OPER_CHWIDTH_16MHZ;
case 81:
case 82:
return CONF_OPER_CHWIDTH_USE_HT;
@@ -4311,3 +4339,18 @@ int ieee80211_get_center_freq(int ctrl_freq, u32 bw)
return -1;
}
}
+
+bool is_supported_s1g_op_class(u8 op_class)
+{
+ switch (op_class) {
+ /* IEEE80211-2024 US */
+ case 68:
+ case 69:
+ case 70:
+ case 71:
+ case 72:
+ return true;
+ default:
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/common/ieee802_11_common.h b/src/common/ieee802_11_common.h
index ed0147812fde..12a0eb6ca252 100644
--- a/src/common/ieee802_11_common.h
+++ b/src/common/ieee802_11_common.h
@@ -111,7 +111,7 @@ struct ieee802_11_elems {
const u8 *short_ssid_list;
const u8 *he_6ghz_band_cap;
const u8 *sae_pk;
- const u8 *s1g_capab;
+ const u8 *s1g_capabilities;
const u8 *pasn_params;
const u8 *eht_capabilities;
const u8 *eht_operation;
@@ -131,6 +131,8 @@ struct ieee802_11_elems {
const u8 *akm_suite_selector;
const u8 *supported_groups;
const u8 *nan_ie;
+ const u8 *s1g_operation;
+ const u8 *aid_response;
u8 ssid_len;
u8 supp_rates_len;
@@ -331,6 +333,8 @@ int get_6ghz_sec_channel(int channel);
bool is_same_band(int freq1, int freq2);
#define IS_2P4GHZ(n) (n >= 2412 && n <= 2484)
#define IS_5GHZ(n) (n > 4000 && n < 5895)
+bool is_s1ghz_freq(int freq);
+bool is_supported_s1g_op_class(u8 op_class);
u8 op_class_idx_to_chan(const struct oper_class_map *op, u8 idx);
int op_class_chan_to_idx(const struct oper_class_map *op, u8 chan);
diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h
index febaab8f1be3..992585859dd8 100644
--- a/src/common/ieee802_11_defs.h
+++ b/src/common/ieee802_11_defs.h
@@ -89,6 +89,12 @@
#define WLAN_FC_STYPE_DMG_BEACON 0
#define WLAN_FC_STYPE_S1G_BEACON 1
+#define WLAN_S1G_BSS_BW_MIN_1_MAX_16 7
+
+#define WLAN_S1G_FC_BSS_BW_SHIFT 11
+#define WLAN_S1G_FC_BSS_BW_MASK (0x7 << WLAN_S1G_FC_BSS_BW_SHIFT)
+#define WLAN_S1G_FC_BSS_BW(bw) (((bw) & 0x7) << WLAN_S1G_FC_BSS_BW_SHIFT)
+
/* Authentication algorithms */
#define WLAN_AUTH_OPEN 0
#define WLAN_AUTH_SHARED_KEY 1
@@ -482,7 +488,10 @@
#define WLAN_EID_DEVICE_LOCATION 204
#define WLAN_EID_WHITE_SPACE_MAP 205
#define WLAN_EID_FTM_PARAMETERS 206
+#define WLAN_EID_AID_REQUEST 210
+#define WLAN_EID_AID_RESPONSE 211
#define WLAN_EID_S1G_BCN_COMPAT 213
+#define WLAN_EID_S1G_SHORT_BCN_INTERVAL 214
#define WLAN_EID_TWT 216
#define WLAN_EID_S1G_CAPABILITIES 217
#define WLAN_EID_VENDOR_SPECIFIC 221
@@ -1009,6 +1018,7 @@ struct ieee80211_hdr {
#define IEEE80211_HDRLEN (sizeof(struct ieee80211_hdr))
+/* TODO this needs to be removed */
struct ieee80211_hdr_s1g_beacon {
le16 frame_control;
le16 duration_id;
@@ -1060,6 +1070,11 @@ struct ieee80211_mgmt {
/* followed by Supported rates */
u8 variable[];
} STRUCT_PACKED assoc_resp, reassoc_resp;
+ struct {
+ le16 capab_info;
+ le16 status_code;
+ u8 variable[];
+ } STRUCT_PACKED s1g_assoc_resp, s1g_reassoc_resp;
struct {
le16 capab_info;
le16 listen_interval;
@@ -1231,6 +1246,18 @@ struct ieee80211_mgmt {
} u;
} STRUCT_PACKED;
+struct ieee80211_ext {
+ le16 frame_control;
+ le16 duration;
+ u8 sa[6];
+ union {
+ struct {
+ u8 timestamp[4];
+ u8 change_seq;
+ u8 variable[];
+ } STRUCT_PACKED beacon;
+ } u;
+} STRUCT_PACKED;
#define IEEE80211_MIN_ACTION_LEN(type) \
(offsetof(struct ieee80211_mgmt, u.action.u.type) + \
@@ -2691,6 +2718,34 @@ struct ieee80211_he_mu_edca_parameter_set {
/* B7: Reserved if sent by an AP; More Data Ack if sent by a non-AP STA */
#define HE_QOS_INFO_MORE_DATA_ACK ((u8) (BIT(7)))
+struct ieee80211_s1g_capabilities {
+ u8 capab_info[10];
+ u8 supp_mcs_nss[5];
+} STRUCT_PACKED;
+
+struct ieee80211_s1g_operation {
+ u8 ch_width;
+ u8 oper_class;
+ u8 primary_ch;
+ u8 oper_ch;
+ le16 basic_mcs_nss;
+} STRUCT_PACKED;
+
+struct ieee80211_aid_response {
+ u16 aid;
+ u8 switch_count;
+ u16 response_interval;
+} STRUCT_PACKED;
+
+#define S1G_OPER_IE_CHANWIDTH_PRIM_CH_MASK (BIT(0))
+#define S1G_OPER_IE_CHANWIDTH_OPER_CH_MASK (BIT(1) | BIT(2) | \
+ BIT(3) | BIT(4))
+#define S1G_OPER_IE_CHANWIDTH_PRIM_OFFSET (BIT(5))
+#define S1G_OPER_IE_MAX_MCS_1_NSS_SHIFT (2)
+#define S1G_OPER_IE_MAX_MCS_2_NSS_SHIFT (6)
+#define S1G_OPER_IE_MAX_MCS_3_NSS_SHIFT (10)
+#define S1G_OPER_IE_MAX_MCS_4_NSS_SHIFT (14)
+
/*
* IEEE Std 802.11-2020 and IEEE Std 802.11ax-2021
* 9.4.2.170 Reduced Neighbor Report element
diff --git a/wlantest/bss.c b/wlantest/bss.c
index 0e94ab1cc240..5879b8b0ba70 100644
--- a/wlantest/bss.c
+++ b/wlantest/bss.c
@@ -178,7 +178,7 @@ void bss_update(struct wlantest *wt, struct wlantest_bss *bss,
* Probe Response frames. Note this assumes short beacons were dropped
* due to missing SSID above.
*/
- if (!elems->rsn_ie && (!elems->s1g_capab || beacon != 1)) {
+ if (!elems->rsn_ie && (!elems->s1g_capabilities || beacon != 1)) {
if (bss->rsnie[0]) {
add_note(wt, MSG_INFO, "BSS " MACSTR
" - RSN IE removed", MAC2STR(bss->bssid));
--
2.43.0
More information about the Hostap
mailing list