[RFC 7/8] nl80211: add S1G support

Lachlan Hodges lachlan.hodges at morsemicro.com
Wed May 27 23:38:56 PDT 2026


Introduce the ability to configure an S1G interface via nl80211.

Signed-off-by: Lachlan Hodges <lachlan.hodges at morsemicro.com>
---
 src/drivers/driver_nl80211.c       | 51 ++++++++++++++++++++++++++++--
 src/drivers/driver_nl80211_capa.c  | 36 +++++++++++++++++++--
 src/drivers/driver_nl80211_event.c | 10 ++++--
 3 files changed, 88 insertions(+), 9 deletions(-)

diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index df82c1b670e3..0cdeb9bd5668 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -5666,6 +5666,7 @@ static int nl80211_put_freq_params(struct nl_msg *msg,
 	wpa_printf(MSG_DEBUG, "  * he_enabled=%d", freq->he_enabled);
 	wpa_printf(MSG_DEBUG, "  * vht_enabled=%d", freq->vht_enabled);
 	wpa_printf(MSG_DEBUG, "  * ht_enabled=%d", freq->ht_enabled);
+	wpa_printf(MSG_DEBUG, "  * s1g_enabled=%d", freq->s1g_enabled);
 	wpa_printf(MSG_DEBUG, "  * radar_background=%d",
 		   freq->radar_background);
 
@@ -5673,7 +5674,43 @@ static int nl80211_put_freq_params(struct nl_msg *msg,
 	is_24ghz = hw_mode == HOSTAPD_MODE_IEEE80211G ||
 		hw_mode == HOSTAPD_MODE_IEEE80211B;
 
-	if (freq->vht_enabled ||
+	if (freq->s1g_enabled) {
+		enum nl80211_chan_width cw;
+
+		wpa_printf(MSG_DEBUG, "  * channel_width=%d", freq->bandwidth);
+		wpa_printf(MSG_DEBUG, "  * center_freq1=%d", freq->center_freq1);
+		wpa_printf(MSG_DEBUG, "  * center_freq1_offset=%d", freq->center_freq1_offset);
+		wpa_printf(MSG_DEBUG, "  * s1g_primary_2mhz=%d", freq->s1g_primary_2mhz);
+
+		switch (freq->bandwidth) {
+		case 1:
+			cw = NL80211_CHAN_WIDTH_1;
+			break;
+		case 2:
+			cw = NL80211_CHAN_WIDTH_2;
+			break;
+		case 4:
+			cw = NL80211_CHAN_WIDTH_4;
+			break;
+		case 8:
+			cw = NL80211_CHAN_WIDTH_8;
+			break;
+		case 16:
+			cw = NL80211_CHAN_WIDTH_16;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		if (nla_put_u32(msg, NL80211_ATTR_CHANNEL_WIDTH, cw))
+			return -ENOBUFS;
+		if (nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ1, freq->center_freq1))
+			return -ENOBUFS;
+		if (nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ1_OFFSET, freq->center_freq1_offset))
+			return -ENOBUFS;
+		if (freq->s1g_primary_2mhz && nla_put_flag(msg, NL80211_ATTR_S1G_PRIMARY_2MHZ))
+			return -ENOBUFS;
+	} else if (freq->vht_enabled ||
 	    ((freq->he_enabled || freq->eht_enabled) && !is_24ghz)) {
 		enum nl80211_chan_width cw;
 
@@ -6193,9 +6230,9 @@ static int nl80211_set_channel(struct i802_bss *bss,
 	int ret;
 
 	wpa_printf(MSG_DEBUG,
-		   "nl80211: Set freq %d offset %d (ht_enabled=%d, vht_enabled=%d, he_enabled=%d, eht_enabled=%d, , bandwidth=%d MHz, cf1=%d MHz, cf1_offset=%d KHz cf2=%d MHz)",
+		   "nl80211: Set freq %d offset %d (ht_enabled=%d, vht_enabled=%d, he_enabled=%d, eht_enabled=%d, s1g_enabled=%d, bandwidth=%d MHz, cf1=%d MHz, cf1_offset=%d KHz cf2=%d MHz)",
 		   freq->freq, freq->freq_offset, freq->ht_enabled, freq->vht_enabled,
-		   freq->he_enabled, freq->eht_enabled, freq->bandwidth,
+		   freq->he_enabled, freq->eht_enabled, freq->s1g_enabled, freq->bandwidth,
 		   freq->center_freq1, freq->center_freq1_offset, freq->center_freq2);
 
 	msg = nl80211_bss_msg(bss, 0, set_chan ? NL80211_CMD_SET_CHANNEL :
@@ -6381,6 +6418,14 @@ static int wpa_driver_nl80211_sta_add(void *priv,
 				goto fail;
 		}
 
+		if (params->s1g_capab) {
+			wpa_hexdump(MSG_DEBUG, "  * s1g_capab",
+				    params->s1g_capab, sizeof(*params->s1g_capab));
+			if (nla_put(msg, NL80211_ATTR_S1G_CAPABILITY,
+				    sizeof(*params->s1g_capab), params->s1g_capab))
+				goto fail;
+		}
+
 		if (params->ext_capab) {
 			wpa_hexdump(MSG_DEBUG, "  * ext_capab",
 				    params->ext_capab, params->ext_capab_len);
diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c
index e423eeeb4528..2b96721c8d49 100644
--- a/src/drivers/driver_nl80211_capa.c
+++ b/src/drivers/driver_nl80211_capa.c
@@ -2132,6 +2132,24 @@ static void phy_info_vht_capa(struct hostapd_hw_modes *mode,
 }
 
 
+static void phy_info_s1g_capa(struct hostapd_hw_modes *mode,
+			      struct nlattr *capability,
+			      struct nlattr *mcs_nss_set)
+{
+	if (capability && nla_len(capability) >= 10) {
+		u8 *capa;
+		capa = nla_data(capability);
+		os_memcpy(&mode->s1g_capab.capab_info, capa, 10);
+	}
+
+	if (mcs_nss_set && nla_len(mcs_nss_set) >= 5) {
+		u8 *mcs_nss;
+		mcs_nss = nla_data(mcs_nss_set);
+		os_memcpy(&mode->s1g_capab.supp_mcs_nss, mcs_nss, 5);
+	}
+}
+
+
 static int phy_info_edmg_capa(struct hostapd_hw_modes *mode,
 			      struct nlattr *bw_config,
 			      struct nlattr *channels)
@@ -2208,6 +2226,13 @@ static void phy_info_freq(struct hostapd_hw_modes *mode,
 	if (tb_freq[NL80211_FREQUENCY_ATTR_NO_320MHZ])
 		chan->allowed_bw &= ~HOSTAPD_CHAN_WIDTH_320;
 
+	if (tb_freq[NL80211_FREQUENCY_ATTR_NO_4MHZ])
+		chan->allowed_bw &= ~HOSTAPD_CHAN_WIDTH_4;
+	if (tb_freq[NL80211_FREQUENCY_ATTR_8MHZ])
+		chan->allowed_bw &= ~HOSTAPD_CHAN_WIDTH_8;
+	if (tb_freq[NL80211_FREQUENCY_ATTR_16MHZ])
+		chan->allowed_bw &= ~HOSTAPD_CHAN_WIDTH_16;
+
 	if (tb_freq[NL80211_FREQUENCY_ATTR_DFS_STATE]) {
 		enum nl80211_dfs_state state =
 			nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_DFS_STATE]);
@@ -2589,6 +2614,8 @@ static int phy_info_band(struct phy_info_arg *phy_info, struct nlattr *nl_band)
 			 tb_band[NL80211_BAND_ATTR_HT_MCS_SET]);
 	phy_info_vht_capa(mode, tb_band[NL80211_BAND_ATTR_VHT_CAPA],
 			  tb_band[NL80211_BAND_ATTR_VHT_MCS_SET]);
+	phy_info_s1g_capa(mode, tb_band[NL80211_BAND_ATTR_S1G_CAPA],
+			  tb_band[NL80211_BAND_ATTR_S1G_MCS_NSS_SET]);
 	ret = phy_info_edmg_capa(mode,
 				 tb_band[NL80211_BAND_ATTR_EDMG_BW_CONFIG],
 				 tb_band[NL80211_BAND_ATTR_EDMG_CHANNELS]);
@@ -2658,8 +2685,9 @@ wpa_driver_nl80211_postprocess_modes(struct hostapd_hw_modes *modes,
 			continue;
 
 		modes[m].is_6ghz = false;
-
-		if (modes[m].channels[0].freq < 2000) {
+		if (is_s1ghz_freq(modes[m].channels[0].freq)) {
+			modes[m].mode = HOSTAPD_MODE_IEEE80211AH;
+		} else if (modes[m].channels[0].freq < 2000) {
 			modes[m].num_channels = 0;
 			continue;
 		} else if (modes[m].channels[0].freq < 4000) {
@@ -3124,6 +3152,8 @@ static const char * modestr(enum hostapd_hw_mode mode)
 		return "802.11a";
 	case HOSTAPD_MODE_IEEE80211AD:
 		return "802.11ad";
+	case HOSTAPD_MODE_IEEE80211AH:
+		return "802.11ah";
 	default:
 		return "?";
 	}
@@ -3151,7 +3181,7 @@ static void nl80211_dump_chan_list(struct wpa_driver_nl80211_data *drv,
 
 			if (is_6ghz_freq(chan->freq))
 				drv->uses_6ghz = true;
-			if (chan->freq >= 900 && chan->freq < 1000)
+			if (is_s1ghz_freq(chan->freq))
 				drv->uses_s1g = true;
 
 			if (drv->uses_s1g) {
diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c
index 28e8a7123af6..14c6f27fbebf 100644
--- a/src/drivers/driver_nl80211_event.c
+++ b/src/drivers/driver_nl80211_event.c
@@ -327,9 +327,13 @@ static void mlme_event_assoc(struct wpa_driver_nl80211_data *drv,
 	event.assoc_info.resp_frame = frame;
 	event.assoc_info.resp_frame_len = len;
 	if (len > 24 + sizeof(mgmt->u.assoc_resp)) {
-		event.assoc_info.resp_ies = (u8 *) mgmt->u.assoc_resp.variable;
-		event.assoc_info.resp_ies_len =
-			len - 24 - sizeof(mgmt->u.assoc_resp);
+		if (is_s1ghz_freq(drv->assoc_freq)) {
+			event.assoc_info.resp_ies = (u8 *) mgmt->u.s1g_assoc_resp.variable;
+			event.assoc_info.resp_ies_len = len - 24 - sizeof(mgmt->u.s1g_assoc_resp);
+		} else {
+			event.assoc_info.resp_ies = (u8 *) mgmt->u.assoc_resp.variable;
+			event.assoc_info.resp_ies_len = len - 24 - sizeof(mgmt->u.assoc_resp);
+		}
 	}
 
 	if (req_ie) {
-- 
2.43.0




More information about the Hostap mailing list