[PATCH 01/15] hostapd: Allow the primary/secondary channel switch to be disabled
Andrei-Alexandru Bleortu
me at andrei-z.com
Sun Oct 4 03:41:54 PDT 2026
Before enabling a 40 MHz or wider channel, hostapd scans for neighboring
BSSs and swaps its primary and secondary 20 MHz channels when another BSS
has its primary channel on this BSS's secondary channel. The scan can
already be skipped with no_pri_sec_switch, but only P2P GO and mesh set
it, so an operator with a coordinated channel plan cannot keep the
configured primary channel.
Expose no_pri_sec_switch as a configuration parameter, documenting that
it skips the whole scan, which IEEE Std 802.11-2024, 11.15.3.2 otherwise
requires. Since the scan is what records the secondary channel to return
to after a 20/40 MHz coexistence fallback, record it when skipping too.
Assisted-by: LLM
Signed-off-by: Andrei-Alexandru Bleortu <me at andrei-z.com>
---
hostapd/config_file.c | 2 ++
hostapd/hostapd.conf | 10 ++++++++++
src/ap/hw_features.c | 7 ++++++-
tests/hwsim/test_ap_ht.py | 36 ++++++++++++++++++++++++++++++++++++
4 files changed, 54 insertions(+), 1 deletion(-)
diff --git a/hostapd/config_file.c b/hostapd/config_file.c
index 59827966e..d5ab87403 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -3700,6 +3700,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
conf->ht_vht_twt_responder = atoi(pos);
} else if (os_strcmp(buf, "obss_interval") == 0) {
conf->obss_interval = atoi(pos);
+ } else if (os_strcmp(buf, "no_pri_sec_switch") == 0) {
+ conf->no_pri_sec_switch = atoi(pos);
#ifdef CONFIG_IEEE80211AC
} else if (os_strcmp(buf, "ieee80211ac") == 0) {
conf->ieee80211ac = atoi(pos);
diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
index 0e9001221..6918591f9 100644
--- a/hostapd/hostapd.conf
+++ b/hostapd/hostapd.conf
@@ -673,6 +673,16 @@ wmm_ac_vo_acm=0
# no co-existence issues with neighboring devices are found.
#obss_interval=0
+# Keep the configured primary and secondary 40 MHz channels
+# Before enabling a 40 MHz (or wider) channel, hostapd scans for neighboring
+# BSSs and, if another BSS has its primary channel on this BSS's secondary
+# channel, swaps the two. Setting this to 1 skips that scan altogether (also
+# the 2.4 GHz 20/40 MHz coexistence check) and keeps the channels as
+# configured. IEEE Std 802.11-2024, 11.15.3.2 requires the scan and, in the
+# 5 GHz band, the primary channel of an existing 20/40 MHz BSS on the same two
+# channels, so use this only where the channel plan is coordinated otherwise.
+#no_pri_sec_switch=0
+
# ht_vht_twt_responder: Whether TWT responder is enabled in HT and VHT modes
# 0 = disable; Disable TWT responder support in HT and VHT modes (default).
# 1 = enable; Enable TWT responder support in HT and VHT modes if supported by
diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c
index 9fe84f70a..8555fe754 100644
--- a/src/ap/hw_features.c
+++ b/src/ap/hw_features.c
@@ -621,8 +621,13 @@ static int ieee80211n_check_40mhz(struct hostapd_iface *iface)
int ret;
/* Check that HT40 is used and PRI / SEC switch is allowed */
- if (!iface->conf->secondary_channel || iface->conf->no_pri_sec_switch)
+ if (!iface->conf->secondary_channel)
+ return 0;
+ if (iface->conf->no_pri_sec_switch) {
+ /* Restored after a 20/40 MHz coexistence fallback */
+ iface->secondary_ch = iface->conf->secondary_channel;
return 0;
+ }
hostapd_set_state(iface, HAPD_IFACE_HT_SCAN);
wpa_printf(MSG_DEBUG, "Scan for neighboring BSSes prior to enabling "
diff --git a/tests/hwsim/test_ap_ht.py b/tests/hwsim/test_ap_ht.py
index 516b09e2e..f00bacdf7 100644
--- a/tests/hwsim/test_ap_ht.py
+++ b/tests/hwsim/test_ap_ht.py
@@ -490,6 +490,42 @@ def test_ap_ht40_5ghz_switch(dev, apdev):
hapd2.request("DISABLE")
set_world_reg(apdev[0], apdev[1], dev[0])
+def test_ap_ht40_5ghz_no_pri_sec_switch(dev, apdev):
+ """HT40 on 5 GHz keeps the configured channels with no_pri_sec_switch"""
+ clear_scan_cache(apdev[0])
+ try:
+ hapd = None
+ hapd2 = None
+ params = {"ssid": "test-ht40",
+ "hw_mode": "a",
+ "channel": "36",
+ "country_code": "US",
+ "ht_capab": "[HT40+]"}
+ hapd2 = hostapd.add_ap(apdev[1], params)
+
+ # Without the option, the scan switches this to primary 36
+ params = {"ssid": "test-ht40",
+ "hw_mode": "a",
+ "channel": "40",
+ "ht_capab": "[HT40-]",
+ "no_pri_sec_switch": "1"}
+ hapd = hostapd.add_ap(apdev[0], params)
+
+ pri = hapd.get_status_field("channel")
+ if pri != "40":
+ raise Exception("Unexpected primary channel: " + pri)
+ sec = hapd.get_status_field("secondary_channel")
+ if sec != "-1":
+ raise Exception("Unexpected secondary channel: " + sec)
+ dev[0].connect("test-ht40", key_mgmt="NONE", scan_freq="5200")
+ finally:
+ dev[0].request("DISCONNECT")
+ if hapd:
+ hapd.request("DISABLE")
+ if hapd2:
+ hapd2.request("DISABLE")
+ set_world_reg(apdev[0], apdev[1], dev[0])
+
def test_ap_ht40_5ghz_switch2(dev, apdev):
"""HT40 co-ex scan on 5 GHz switching pri/sec channel (2)"""
clear_scan_cache(apdev[0])
--
2.47.3
More information about the Hostap
mailing list