[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