[PATCH 6/6] tests: add S1G association coverage
James Ewing
james at teledatics.com
Tue Sep 30 12:13:38 PDT 2025
Introduce the S1G association hwsim test that verifies the
freq_khz/freq_offset plumbing end-to-end. The test integrates with the
existing run-tests.py harness and gracefully skips when the running
kernel does not expose NL80211_BAND_S1GHZ.
Tested: ./tests/hwsim/run-tests.py -q test_s1g_assoc (SKIP: iw phy did
not report an S1G channel index)
Signed-off-by: James Ewing <james at teledatics.com>
---
tests/hwsim/test_s1g_assoc.py | 101 ++++++++++++++++++++++++++++++++++
1 file changed, 101 insertions(+)
create mode 100644 tests/hwsim/test_s1g_assoc.py
diff --git a/tests/hwsim/test_s1g_assoc.py b/tests/hwsim/test_s1g_assoc.py
new file mode 100644
index 000000000..80ca3be31
--- /dev/null
+++ b/tests/hwsim/test_s1g_assoc.py
@@ -0,0 +1,101 @@
+# S1G association path coverage
+#
+# Validates that the supplicant can associate with an S1G-capable AP while
+# preserving kHz-level channel metadata inside the BSS cache.
+
+import logging
+
+import hostapd
+
+from utils import HwsimSkip, list_s1g_capable_channels
+
+
+logger = logging.getLogger()
+
+
+def _pick_s1g_channel():
+ channels = list_s1g_capable_channels()
+ if not channels:
+ raise HwsimSkip("mac80211_hwsim does not expose any S1G channels")
+
+ for entry in channels:
+ if entry.get("channel"):
+ return entry
+
+ # Fall back to the first entry even if iw does not report a channel
number.
+ return channels[0]
+
+
+def test_sta_assoc_s1g_channel(dev, apdev):
+ """Exercise STA association on an S1G channel and observe freq_khz
plumbing"""
+
+ channel_info = _pick_s1g_channel()
+ channel = channel_info.get("channel")
+ if channel is None:
+ raise HwsimSkip("iw phy did not report an S1G channel index")
+
+ freq_khz = int(round(channel_info["freq_mhz"] * 1000))
+ primary_freq_mhz = freq_khz // 1000
+ freq_offset = freq_khz - primary_freq_mhz * 1000
+
+ logger.info(
+ "Using S1G channel %d at %.3f MHz (primary %d MHz, offset %d kHz)",
+ channel,
+ channel_info["freq_mhz"],
+ primary_freq_mhz,
+ freq_offset,
+ )
+
+ params = {
+ "ssid": "s1g-assoc",
+ "hw_mode": "ah",
+ "channel": str(channel),
+ "ieee80211n": "0",
+ "ieee80211ac": "0",
+ "ieee80211ax": "0",
+ }
+
+ hapd = hostapd.add_ap(apdev[0], params, set_channel=False)
+
+ dev[0].connect(
+ ssid="s1g-assoc",
+ key_mgmt="NONE",
+ scan_freq=str(primary_freq_mhz),
+ )
+
+ status = dev[0].get_status()
+ if status.get("freq") != str(primary_freq_mhz):
+ raise Exception(
+ "Supplicant reported freq %s, expected %d MHz" %
+ (status.get("freq"), primary_freq_mhz))
+
+ bss = dev[0].get_bss(hapd.own_addr())
+ if not bss:
+ raise Exception("Failed to fetch BSS entry for associated S1G AP")
+
+ if bss.get("freq") != str(primary_freq_mhz):
+ raise Exception(
+ "BSS table freq %s does not match primary %d MHz" %
+ (bss.get("freq"), primary_freq_mhz))
+
+ freq_khz_str = bss.get("freq_khz")
+ if freq_khz_str is None:
+ raise Exception("freq_khz missing from BSS table entry")
+ if int(freq_khz_str) != freq_khz:
+ raise Exception(
+ "freq_khz %s did not match expected %d" %
+ (freq_khz_str, freq_khz))
+
+ freq_offset_str = bss.get("freq_offset")
+ if freq_offset:
+ if freq_offset_str is None:
+ raise Exception("freq_offset missing from BSS table entry")
+ if int(freq_offset_str) != freq_offset:
+ raise Exception(
+ "freq_offset %s did not match expected %d" %
+ (freq_offset_str, freq_offset))
+ elif freq_offset_str is not None and int(freq_offset_str) != 0:
+ raise Exception("Expected zero freq_offset, saw %s" %
freq_offset_str)
+
+ dev[0].request("DISCONNECT")
+
--
2.43.0
More information about the Hostap
mailing list