[PATCH 4/6] nl80211: propagate S1G widths and freq offsets
James Ewing
james at teledatics.com
Tue Sep 30 12:13:19 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 | 17 ++++++
src/common/hw_features_common.c | 16 +++++-
src/drivers/driver.h | 21 +++++++
src/drivers/driver_nl80211.c | 90 +++++++++++++++++++++++++++++-
src/drivers/driver_nl80211_event.c | 28 ++++++++++
src/drivers/driver_nl80211_scan.c | 32 +++++++++++
6 files changed, 200 insertions(+), 4 deletions(-)
diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c
index e6ea1dc3a..26e5c191e 100644
--- a/hostapd/ctrl_iface.c
+++ b/hostapd/ctrl_iface.c
@@ -2533,6 +2533,23 @@ 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 16:
+ case 10:
+ case 8:
+ case 5:
+ case 4:
+ case 2:
+ case 1:
+ 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..1f484b5b1 100644
--- a/src/common/hw_features_common.c
+++ b/src/common/hw_features_common.c
@@ -497,6 +497,9 @@ int hostapd_set_freq_params(struct
hostapd_freq_params *data,
os_memset(data, 0, sizeof(*data));
data->mode = mode;
data->freq = freq;
+ if (freq > 0)
+ data->freq_khz = freq * 1000;
+ data->freq_offset = 0;
data->channel = channel;
data->ht_enabled = ht_enabled;
data->vht_enabled = vht_enabled;
@@ -589,7 +592,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 +835,17 @@ int hostapd_set_freq_params(struct
hostapd_freq_params *data,
break;
}
+out:
+ if (!data->freq_khz && data->freq > 0)
+ data->freq_khz = data->freq * 1000;
+ if (data->center_freq1)
+ data->center_freq1_khz = data->center_freq1 * 1000;
+ else
+ data->center_freq1_khz = 0;
+ if (data->center_freq2)
+ data->center_freq2_khz = data->center_freq2 * 1000;
+ else
+ data->center_freq2_khz = 0;
return 0;
}
diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index d943062fe..e788db37f 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -372,6 +372,9 @@ 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_khz: frequency in kHz when provided by the driver (0 if unknown)
+ * @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 +413,8 @@ struct wpa_scan_res {
unsigned int flags;
u8 bssid[ETH_ALEN];
int freq;
+ unsigned int freq_khz;
+ unsigned int freq_offset;
enum chan_width max_cw;
u16 beacon_int;
u16 caps;
@@ -833,6 +838,14 @@ struct hostapd_freq_params {
* freq - Primary channel center frequency in MHz
*/
int freq;
+ /**
+ * freq_khz - Primary channel center frequency in kHz (0 if unknown)
+ */
+ unsigned int freq_khz;
+ /**
+ * freq_offset - Primary channel frequency offset in kHz relative
to @freq
+ */
+ unsigned int freq_offset;
/**
* channel - Channel number
@@ -869,6 +882,10 @@ struct hostapd_freq_params {
* Valid for both HT and VHT.
*/
int center_freq1;
+ /**
+ * center_freq1_khz - Segment 0 center frequency in kHz (0 if unknown)
+ */
+ unsigned int center_freq1_khz;
/**
* center_freq2 - Segment 1 center frequency in MHz
@@ -876,6 +893,10 @@ struct hostapd_freq_params {
* Non-zero only for bandwidth 80 and an 80+80 channel
*/
int center_freq2;
+ /**
+ * center_freq2_khz - Segment 1 center frequency in kHz (0 if unknown)
+ */
+ unsigned int center_freq2_khz;
/**
* bandwidth - Channel bandwidth in MHz (20, 40, 80, 160)
diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index c5bbe119c..2edad2173 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -217,6 +217,20 @@ enum chan_width convert2width(int width)
return CHAN_WIDTH_80P80;
case NL80211_CHAN_WIDTH_160:
return CHAN_WIDTH_160;
+ case NL80211_CHAN_WIDTH_10:
+ return CHAN_WIDTH_10;
+ case NL80211_CHAN_WIDTH_5:
+ return CHAN_WIDTH_5;
+ 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:
@@ -5121,6 +5135,8 @@ static int nl80211_put_freq_params(struct nl_msg *msg,
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 +5153,42 @@ 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 (freq->bandwidth == 16 || freq->bandwidth == 10 ||
+ freq->bandwidth == 8 || freq->bandwidth == 5 ||
+ freq->bandwidth == 4 || freq->bandwidth == 2 ||
+ freq->bandwidth == 1) {
+ wpa_printf(MSG_DEBUG, " * bandwidth=%d", freq->bandwidth);
+ switch (freq->bandwidth) {
+ case 16:
+ cw = NL80211_CHAN_WIDTH_16;
+ break;
+ case 10:
+ cw = NL80211_CHAN_WIDTH_10;
+ break;
+ case 8:
+ cw = NL80211_CHAN_WIDTH_8;
+ break;
+ case 5:
+ cw = NL80211_CHAN_WIDTH_5;
+ break;
+ case 4:
+ cw = NL80211_CHAN_WIDTH_4;
+ break;
+ case 2:
+ cw = NL80211_CHAN_WIDTH_2;
+ break;
+ case 1:
+ cw = NL80211_CHAN_WIDTH_1;
+ break;
+ default:
+ 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 +5245,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 +5256,47 @@ 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_khz) {
+ unsigned int freq_offset = freq->freq_offset;
+
+ if (!freq_offset && freq->freq > 0 &&
+ freq->freq_khz >= (unsigned int) freq->freq * 1000)
+ freq_offset = freq->freq_khz - freq->freq * 1000;
+ if (freq_offset) {
+ wpa_printf(MSG_DEBUG, " * freq_offset=%u", freq_offset);
+ if (nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ_OFFSET,
+ freq_offset))
+ return -ENOBUFS;
+ }
}
+
+ if (freq->center_freq1 && freq->center_freq1_khz &&
+ freq->center_freq1_khz >= (unsigned int) freq->center_freq1 *
1000) {
+ unsigned int cf1_offset =
+ freq->center_freq1_khz -
+ freq->center_freq1 * 1000;
+
+ if (cf1_offset) {
+ wpa_printf(MSG_DEBUG, " * center_freq1_offset=%u",
+ cf1_offset);
+ if (nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ1_OFFSET,
+ cf1_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..4b24935c8 100644
--- a/src/drivers/driver_nl80211_event.c
+++ b/src/drivers/driver_nl80211_event.c
@@ -1219,6 +1219,13 @@ 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_10:
+ case CHAN_WIDTH_8:
+ case CHAN_WIDTH_5:
+ case CHAN_WIDTH_4:
+ case CHAN_WIDTH_2:
+ case CHAN_WIDTH_1:
return 0;
case CHAN_WIDTH_40:
freq1 = cf1 - 10;
@@ -3933,6 +3940,27 @@ 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_10:
+ ed.sta_opmode.chan_width = CHAN_WIDTH_10;
+ break;
+ case NL80211_CHAN_WIDTH_5:
+ ed.sta_opmode.chan_width = CHAN_WIDTH_5;
+ 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..7024a2b2b 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,45 @@ 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 (r->freq > 0)
+ r->freq_khz = r->freq * 1000;
+ if (r->freq_khz && r->freq_offset)
+ r->freq_khz += r->freq_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_10:
+ r->max_cw = CHAN_WIDTH_10;
+ break;
+ case NL80211_BSS_CHAN_WIDTH_5:
+ r->max_cw = CHAN_WIDTH_5;
+ 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]);
--
2.43.0
More information about the Hostap
mailing list