[PATCH 4/6] nl80211: propagate S1G widths and freq offsets

James Ewing james at teledatics.com
Wed Oct 1 09:49:59 PDT 2025


Teach the nl80211 backend to recognise the new S1G channel widths and to
forward the kHz-level frequency metadata reported by the kernel. This
includes advertising the right width in STA opmode notifications,
packaging centre-frequency offsets for hostapd, and populating scan
results with freq_khz/freq_offset so higher layers can keep sub-MHz
precision.

Tested: tests/hwsim/run_s1g_smoke.sh
Signed-off-by: James Ewing <james at teledatics.com>
---
 hostapd/ctrl_iface.c               | 15 +++++
 src/common/hw_features_common.c    |  5 +-
 src/drivers/driver.h               | 12 ++++
 src/drivers/driver_nl80211.c       | 90 +++++++++++++++++++++++++++++-
 src/drivers/driver_nl80211_event.c | 20 +++++++
 src/drivers/driver_nl80211_scan.c  | 22 ++++++++
 src/utils/common.h                 | 33 +++++++++++
 7 files changed, 193 insertions(+), 4 deletions(-)

diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c
index e6ea1dc3a..747630de4 100644
--- a/hostapd/ctrl_iface.c
+++ b/hostapd/ctrl_iface.c
@@ -2533,6 +2533,21 @@ static int hostapd_ctrl_check_freq_params(struct hostapd_freq_params *params,
 		if (params->center_freq2 || params->sec_channel_offset)
 			return -1;
 
+		if (punct_bitmap)
+			return -1;
+		break;
+	case S1G_BANDWIDTH_16MHZ:
+	case S1G_BANDWIDTH_8MHZ:
+	case S1G_BANDWIDTH_4MHZ:
+	case S1G_BANDWIDTH_2MHZ:
+	case S1G_BANDWIDTH_1MHZ:
+		if (params->center_freq1 &&
+		    params->center_freq1 != params->freq)
+			return -1;
+
+		if (params->center_freq2 || params->sec_channel_offset)
+			return -1;
+
 		if (punct_bitmap)
 			return -1;
 		break;
diff --git a/src/common/hw_features_common.c b/src/common/hw_features_common.c
index d9276b935..48280cf2b 100644
--- a/src/common/hw_features_common.c
+++ b/src/common/hw_features_common.c
@@ -497,6 +497,7 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data,
 	os_memset(data, 0, sizeof(*data));
 	data->mode = mode;
 	data->freq = freq;
+	data->freq_offset = 0;
 	data->channel = channel;
 	data->ht_enabled = ht_enabled;
 	data->vht_enabled = vht_enabled;
@@ -504,6 +505,7 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data,
 	data->eht_enabled = eht_enabled;
 	data->sec_channel_offset = sec_channel_offset;
 	data->center_freq1 = freq + sec_channel_offset * 10;
+	data->center_freq1_offset = 0;
 	data->center_freq2 = 0;
 	data->punct_bitmap = punct_bitmap;
 	if (oper_chwidth == CONF_OPER_CHWIDTH_80MHZ)
@@ -589,7 +591,7 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data,
 		data->ht_enabled = 0;
 		data->vht_enabled = 0;
 
-		return 0;
+		goto out;
 	}
 
 	if (data->eht_enabled) switch (oper_chwidth) {
@@ -832,6 +834,7 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data,
 		break;
 	}
 
+out:
 	return 0;
 }
 
diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index d943062fe..c8dcdeadb 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -372,6 +372,8 @@ struct hostapd_multi_hw_info {
  * @flags: information flags about the BSS/IBSS (WPA_SCAN_*)
  * @bssid: BSSID
  * @freq: frequency of the channel in MHz (e.g., 2412 = channel 1)
+ * @freq_offset: frequency offset in kHz relative to @freq (legacy drivers
+ *	that do not report this leave it zero)
  * @max_cw: the max channel width of the connection (calculated during scan
  * result processing)
  * @beacon_int: beacon interval in TUs (host byte order)
@@ -410,6 +412,7 @@ struct wpa_scan_res {
 	unsigned int flags;
 	u8 bssid[ETH_ALEN];
 	int freq;
+	unsigned int freq_offset;
 	enum chan_width max_cw;
 	u16 beacon_int;
 	u16 caps;
@@ -833,6 +836,10 @@ struct hostapd_freq_params {
 	 * freq - Primary channel center frequency in MHz
 	 */
 	int freq;
+	/**
+	 * freq_offset - Primary channel frequency offset in kHz relative to @freq
+	 */
+	unsigned int freq_offset;
 
 	/**
 	 * channel - Channel number
@@ -869,6 +876,11 @@ struct hostapd_freq_params {
 	 * Valid for both HT and VHT.
 	 */
 	int center_freq1;
+	/**
+	 * center_freq1_offset - Segment 0 frequency offset in kHz relative to
+	 *	@center_freq1
+	 */
+	unsigned int center_freq1_offset;
 
 	/**
 	 * center_freq2 - Segment 1 center frequency in MHz
diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index c5bbe119c..2e80cce80 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -217,6 +217,16 @@ enum chan_width convert2width(int width)
 		return CHAN_WIDTH_80P80;
 	case NL80211_CHAN_WIDTH_160:
 		return CHAN_WIDTH_160;
+	case NL80211_CHAN_WIDTH_16:
+		return CHAN_WIDTH_16;
+	case NL80211_CHAN_WIDTH_8:
+		return CHAN_WIDTH_8;
+	case NL80211_CHAN_WIDTH_4:
+		return CHAN_WIDTH_4;
+	case NL80211_CHAN_WIDTH_2:
+		return CHAN_WIDTH_2;
+	case NL80211_CHAN_WIDTH_1:
+		return CHAN_WIDTH_1;
 	case NL80211_CHAN_WIDTH_320:
 		return CHAN_WIDTH_320;
 	default:
@@ -5115,12 +5125,58 @@ err:
 #endif /* CONFIG_DRIVER_NL80211_QCA */
 
 
+static bool nl80211_is_s1g_bandwidth(int bandwidth)
+{
+	switch (bandwidth) {
+	case S1G_BANDWIDTH_16MHZ:
+	case S1G_BANDWIDTH_8MHZ:
+	case S1G_BANDWIDTH_4MHZ:
+	case S1G_BANDWIDTH_2MHZ:
+	case S1G_BANDWIDTH_1MHZ:
+		return true;
+	default:
+		return false;
+	}
+}
+
+
+static int nl80211_s1g_bandwidth_to_width(int bandwidth,
+			 enum nl80211_chan_width *width)
+{
+	int ret = 0;
+
+	switch (bandwidth) {
+	case S1G_BANDWIDTH_16MHZ:
+		*width = NL80211_CHAN_WIDTH_16;
+		break;
+	case S1G_BANDWIDTH_8MHZ:
+		*width = NL80211_CHAN_WIDTH_8;
+		break;
+	case S1G_BANDWIDTH_4MHZ:
+		*width = NL80211_CHAN_WIDTH_4;
+		break;
+	case S1G_BANDWIDTH_2MHZ:
+		*width = NL80211_CHAN_WIDTH_2;
+		break;
+	case S1G_BANDWIDTH_1MHZ:
+		*width = NL80211_CHAN_WIDTH_1;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
 static int nl80211_put_freq_params(struct nl_msg *msg,
 				   const struct hostapd_freq_params *freq)
 {
 	enum hostapd_hw_mode hw_mode;
 	int is_24ghz;
 	u8 channel;
+	bool channel_type_set = false;
+	enum nl80211_chan_width cw = NL80211_CHAN_WIDTH_20_NOHT;
 
 	wpa_printf(MSG_DEBUG, "  * freq=%d", freq->freq);
 	if (nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ, freq->freq))
@@ -5137,10 +5193,16 @@ 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 ||
-	    ((freq->he_enabled || freq->eht_enabled) && !is_24ghz)) {
-		enum nl80211_chan_width cw;
+	if (nl80211_is_s1g_bandwidth(freq->bandwidth)) {
+		wpa_printf(MSG_DEBUG, "  * bandwidth=%d", freq->bandwidth);
+		if (nl80211_s1g_bandwidth_to_width(freq->bandwidth, &cw) < 0)
+			return -EINVAL;
 
+		wpa_printf(MSG_DEBUG, "  * channel_width=%d", cw);
+		if (nla_put_u32(msg, NL80211_ATTR_CHANNEL_WIDTH, cw))
+			return -ENOBUFS;
+	} else if (freq->vht_enabled ||
+	    ((freq->he_enabled || freq->eht_enabled) && !is_24ghz)) {
 		wpa_printf(MSG_DEBUG, "  * bandwidth=%d", freq->bandwidth);
 		switch (freq->bandwidth) {
 		case 20:
@@ -5197,6 +5259,7 @@ static int nl80211_put_freq_params(struct nl_msg *msg,
 		wpa_printf(MSG_DEBUG, "  * channel_type=%d", ct);
 		if (nla_put_u32(msg, NL80211_ATTR_WIPHY_CHANNEL_TYPE, ct))
 			return -ENOBUFS;
+		channel_type_set = true;
 	} else if (freq->edmg.channels && freq->edmg.bw_config) {
 		wpa_printf(MSG_DEBUG,
 			   "  * EDMG configuration: channels=0x%x bw_config=%d",
@@ -5207,12 +5270,33 @@ static int nl80211_put_freq_params(struct nl_msg *msg,
 			       freq->edmg.bw_config))
 			return -1;
 	} else {
+		/* nothing to do */
+	}
+
+	if (!channel_type_set) {
 		wpa_printf(MSG_DEBUG, "  * channel_type=%d",
 			   NL80211_CHAN_NO_HT);
 		if (nla_put_u32(msg, NL80211_ATTR_WIPHY_CHANNEL_TYPE,
 				NL80211_CHAN_NO_HT))
 			return -ENOBUFS;
+		channel_type_set = true;
+	}
+
+	if (freq->freq_offset) {
+		wpa_printf(MSG_DEBUG, "  * freq_offset=%u", freq->freq_offset);
+		if (nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ_OFFSET,
+				freq->freq_offset))
+			return -ENOBUFS;
 	}
+
+	if (freq->center_freq1 && freq->center_freq1_offset) {
+		wpa_printf(MSG_DEBUG, "  * center_freq1_offset=%u",
+			   freq->center_freq1_offset);
+		if (nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ1_OFFSET,
+				freq->center_freq1_offset))
+			return -ENOBUFS;
+	}
+
 	if (freq->radar_background &&
 	    nla_put_flag(msg, NL80211_ATTR_RADAR_BACKGROUND))
 		return -ENOBUFS;
diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c
index 9e4a3e6e1..65137d84d 100644
--- a/src/drivers/driver_nl80211_event.c
+++ b/src/drivers/driver_nl80211_event.c
@@ -1219,6 +1219,11 @@ static int calculate_chan_offset(int width, int freq, int cf1, int cf2)
 	switch (convert2width(width)) {
 	case CHAN_WIDTH_20_NOHT:
 	case CHAN_WIDTH_20:
+	case CHAN_WIDTH_16:
+	case CHAN_WIDTH_8:
+	case CHAN_WIDTH_4:
+	case CHAN_WIDTH_2:
+	case CHAN_WIDTH_1:
 		return 0;
 	case CHAN_WIDTH_40:
 		freq1 = cf1 - 10;
@@ -3933,6 +3938,21 @@ static void nl80211_sta_opmode_change_event(struct wpa_driver_nl80211_data *drv,
 		case NL80211_CHAN_WIDTH_320:
 			ed.sta_opmode.chan_width = CHAN_WIDTH_320;
 			break;
+		case NL80211_CHAN_WIDTH_16:
+			ed.sta_opmode.chan_width = CHAN_WIDTH_16;
+			break;
+		case NL80211_CHAN_WIDTH_8:
+			ed.sta_opmode.chan_width = CHAN_WIDTH_8;
+			break;
+		case NL80211_CHAN_WIDTH_4:
+			ed.sta_opmode.chan_width = CHAN_WIDTH_4;
+			break;
+		case NL80211_CHAN_WIDTH_2:
+			ed.sta_opmode.chan_width = CHAN_WIDTH_2;
+			break;
+		case NL80211_CHAN_WIDTH_1:
+			ed.sta_opmode.chan_width = CHAN_WIDTH_1;
+			break;
 		default:
 			ed.sta_opmode.chan_width = CHAN_WIDTH_UNKNOWN;
 			break;
diff --git a/src/drivers/driver_nl80211_scan.c b/src/drivers/driver_nl80211_scan.c
index f0313c1af..328a10f1e 100644
--- a/src/drivers/driver_nl80211_scan.c
+++ b/src/drivers/driver_nl80211_scan.c
@@ -806,6 +806,8 @@ nl80211_parse_bss_info(struct wpa_driver_nl80211_data *drv,
 		[NL80211_BSS_PARENT_TSF] = { .type = NLA_U64 },
 		[NL80211_BSS_PARENT_BSSID] = { .type = NLA_UNSPEC },
 		[NL80211_BSS_LAST_SEEN_BOOTTIME] = { .type = NLA_U64 },
+		[NL80211_BSS_FREQUENCY_OFFSET] = { .type = NLA_U32 },
+		[NL80211_BSS_CHAN_WIDTH] = { .type = NLA_U32 },
 	};
 	struct wpa_scan_res *r;
 	const u8 *ie, *beacon_ie;
@@ -844,15 +846,35 @@ nl80211_parse_bss_info(struct wpa_driver_nl80211_data *drv,
 	r = os_zalloc(sizeof(*r) + ie_len + beacon_ie_len);
 	if (r == NULL)
 		return NULL;
+	r->max_cw = CHAN_WIDTH_UNKNOWN;
 	if (bss[NL80211_BSS_BSSID])
 		os_memcpy(r->bssid, nla_data(bss[NL80211_BSS_BSSID]),
 			  ETH_ALEN);
 	if (bss[NL80211_BSS_FREQUENCY])
 		r->freq = nla_get_u32(bss[NL80211_BSS_FREQUENCY]);
+	if (bss[NL80211_BSS_FREQUENCY_OFFSET])
+		r->freq_offset = nla_get_u32(
+			bss[NL80211_BSS_FREQUENCY_OFFSET]);
 	if (bss[NL80211_BSS_BEACON_INTERVAL])
 		r->beacon_int = nla_get_u16(bss[NL80211_BSS_BEACON_INTERVAL]);
 	if (bss[NL80211_BSS_CAPABILITY])
 		r->caps = nla_get_u16(bss[NL80211_BSS_CAPABILITY]);
+	if (bss[NL80211_BSS_CHAN_WIDTH]) {
+		switch (nla_get_u32(bss[NL80211_BSS_CHAN_WIDTH])) {
+		case NL80211_BSS_CHAN_WIDTH_20:
+			r->max_cw = CHAN_WIDTH_20;
+			break;
+		case NL80211_BSS_CHAN_WIDTH_2:
+			r->max_cw = CHAN_WIDTH_2;
+			break;
+		case NL80211_BSS_CHAN_WIDTH_1:
+			r->max_cw = CHAN_WIDTH_1;
+			break;
+		default:
+			r->max_cw = CHAN_WIDTH_UNKNOWN;
+			break;
+		}
+	}
 	r->flags |= WPA_SCAN_NOISE_INVALID;
 	if (bss[NL80211_BSS_SIGNAL_MBM]) {
 		r->level = nla_get_u32(bss[NL80211_BSS_SIGNAL_MBM]);
diff --git a/src/utils/common.h b/src/utils/common.h
index d7b3600f2..8a3f12cc2 100644
--- a/src/utils/common.h
+++ b/src/utils/common.h
@@ -127,6 +127,39 @@ typedef int8_t s8;
 #endif /* !WPA_TYPES_DEFINED */
 
 
+#ifndef MHZ_TO_KHZ
+#define MHZ_TO_KHZ(freq) ((freq) * 1000)
+#endif
+
+#ifndef KHZ_TO_MHZ
+#define KHZ_TO_MHZ(freq) ((freq) / 1000)
+#endif
+
+static inline unsigned int mhz_to_khz_with_offset(int freq_mhz,
+					    unsigned int offset_khz)
+{
+	if (freq_mhz <= 0)
+		return 0;
+
+	return MHZ_TO_KHZ(freq_mhz) + offset_khz;
+}
+
+static inline unsigned int freq_offset_from_khz(unsigned int freq_khz,
+					 int freq_mhz)
+{
+	unsigned int base;
+
+	if (freq_mhz <= 0)
+		return 0;
+
+	base = MHZ_TO_KHZ(freq_mhz);
+	if (freq_khz <= base)
+		return 0;
+
+	return freq_khz - base;
+}
+
+
 /* Define platform specific byte swapping macros */
 
 #if defined(__CYGWIN__) || defined(CONFIG_NATIVE_WINDOWS)
-- 
2.43.0




More information about the Hostap mailing list