[PATCH RFC 8/9] nl80211: add S1G support

Lachlan Hodges lachlan.hodges at morsemicro.com
Thu Oct 8 20:32:05 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       | 54 ++++++++++++++++++++++++++++--
 src/drivers/driver_nl80211_capa.c  | 38 ++++++++++++++++++---
 src/drivers/driver_nl80211_event.c | 24 +++++++------
 3 files changed, 99 insertions(+), 17 deletions(-)

diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index 86e0e2b55b8d..9690fcc5fb5b 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -5691,6 +5691,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);
 
@@ -5698,7 +5699,46 @@ 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;
 
@@ -6234,9 +6274,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 :
@@ -6430,6 +6470,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 7dc33e42544c..3297572ea9ff 100644
--- a/src/drivers/driver_nl80211_capa.c
+++ b/src/drivers/driver_nl80211_capa.c
@@ -2150,6 +2150,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)
@@ -2228,6 +2246,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_NO_8MHZ])
+		chan->allowed_bw &= ~HOSTAPD_CHAN_WIDTH_8;
+	if (tb_freq[NL80211_FREQUENCY_ATTR_NO_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]);
@@ -2632,6 +2657,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]);
@@ -2701,8 +2728,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) {
@@ -3172,6 +3200,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 "?";
 	}
@@ -3199,10 +3229,10 @@ 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) {
+			if (is_s1ghz_freq(chan->freq)) {
 				/* For S1G, print frequency in KHz */
 				res = os_snprintf(pos, end - pos, " %d%s%s",
 						MHZ_TO_KHZ(chan->freq) + chan->freq_offset,
diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c
index cd08b7193e7e..3ffaa70d4bdc 100644
--- a/src/drivers/driver_nl80211_event.c
+++ b/src/drivers/driver_nl80211_event.c
@@ -280,6 +280,7 @@ static void mlme_event_assoc(struct wpa_driver_nl80211_data *drv,
 	union wpa_event_data event;
 	u16 status;
 	int ssid_len;
+	size_t fixed_len;
 
 	if (!(drv->capa.flags & WPA_DRIVER_FLAGS_SME) &&
 	    drv->force_connect_cmd) {
@@ -294,7 +295,13 @@ static void mlme_event_assoc(struct wpa_driver_nl80211_data *drv,
 
 	wpa_printf(MSG_DEBUG, "nl80211: Associate event");
 	mgmt = (const struct ieee80211_mgmt *) frame;
-	if (len < 24 + sizeof(mgmt->u.assoc_resp)) {
+
+	if (is_s1ghz_freq(drv->assoc_freq))
+		fixed_len = 24 + sizeof(mgmt->u.s1g_assoc_resp);
+	else
+		fixed_len = 24 + sizeof(mgmt->u.assoc_resp);
+
+	if (len < fixed_len) {
 		wpa_printf(MSG_DEBUG, "nl80211: Too short association event "
 			   "frame");
 		return;
@@ -308,11 +315,9 @@ static void mlme_event_assoc(struct wpa_driver_nl80211_data *drv,
 	if (status != WLAN_STATUS_SUCCESS) {
 		os_memset(&event, 0, sizeof(event));
 		event.assoc_reject.bssid = mgmt->bssid;
-		if (len > 24 + sizeof(mgmt->u.assoc_resp)) {
-			event.assoc_reject.resp_ies =
-				(u8 *) mgmt->u.assoc_resp.variable;
-			event.assoc_reject.resp_ies_len =
-				len - 24 - sizeof(mgmt->u.assoc_resp);
+		if (len > fixed_len) {
+			event.assoc_reject.resp_ies = frame + fixed_len;
+			event.assoc_reject.resp_ies_len = len - fixed_len;
 		}
 		event.assoc_reject.status_code = status;
 
@@ -327,10 +332,9 @@ static void mlme_event_assoc(struct wpa_driver_nl80211_data *drv,
 	os_memset(&event, 0, sizeof(event));
 	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 (len > fixed_len) {
+		event.assoc_info.resp_ies = frame + fixed_len;
+		event.assoc_info.resp_ies_len = len - fixed_len;
 	}
 
 	if (req_ie) {
-- 
2.43.0




More information about the Hostap mailing list