[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