[PATCH 3/7] ap: support BW changes via CLI
Benjamin Berg
benjamin at sipsolutions.net
Thu Jul 31 06:56:59 PDT 2025
From: Johannes Berg <johannes.berg at intel.com>
At least for testing, it's useful to be able to change the bandwidth
on the fly without doing a full channel switch announcement, by just
rebuilding the HT/VHT operation elements. Add support for a command
("SET_BW") to do just that.
Co-developed-by: Ilan Peer <ilan.peer at intel.com>
Signed-off-by: Ilan Peer <ilan.peer at intel.com>
Signed-off-by: Johannes Berg <johannes.berg at intel.com>
Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
---
hostapd/ctrl_iface.c | 53 +++++++++++++++++++++++++++++++
hostapd/hostapd_cli.c | 36 +++++++++++++++++++++
src/ap/ctrl_iface_ap.c | 2 ++
src/ap/hostapd.c | 8 ++---
src/ap/hostapd.h | 4 +++
src/drivers/driver.h | 3 ++
src/drivers/driver_nl80211_capa.c | 4 ++-
tests/hwsim/test_ap_ht.py | 41 ++++++++++++++++++++++++
tests/hwsim/test_ap_vht.py | 38 ++++++++++++++++++++++
9 files changed, 184 insertions(+), 5 deletions(-)
diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c
index 3b410ac77a..db6e44a61b 100644
--- a/hostapd/ctrl_iface.c
+++ b/hostapd/ctrl_iface.c
@@ -3046,6 +3046,55 @@ static char hostapd_ctrl_iface_notify_cw_change(struct hostapd_data *hapd,
}
+static int hostapd_ctrl_iface_set_bw(struct hostapd_iface *iface, char *pos)
+{
+#ifdef NEED_AP_MLME
+ struct hostapd_freq_params freq_params;
+ int ret;
+ enum oper_chan_width chanwidth;
+ u8 chan, oper_class;
+
+ if (!(iface->drv_flags2 & WPA_DRIVER_FLAGS2_AP_CHANWIDTH_CHANGE))
+ return -1;
+
+ ret = hostapd_parse_freq_params(pos, &freq_params, iface->freq);
+ if (ret)
+ return ret;
+
+ chanwidth = hostapd_chan_width_from_freq_params(&freq_params);
+
+ if (ieee80211_freq_to_channel_ext(
+ freq_params.freq,
+ freq_params.sec_channel_offset,
+ chanwidth, &oper_class,
+ &chan) == NUM_HOSTAPD_MODES) {
+ wpa_printf(MSG_DEBUG,
+ "invalid channel: (freq=%d, sec_channel_offset=%d, vht_enabled=%d, he_enabled=%d)",
+ freq_params.freq,
+ freq_params.sec_channel_offset,
+ freq_params.vht_enabled,
+ freq_params.he_enabled);
+ return -1;
+ }
+
+ freq_params.channel = chan;
+
+ /* FIXME: what if the newly extended channel overlaps radar ranges? */
+
+ ret = hostapd_change_config_freq(iface->bss[0], iface->conf,
+ &freq_params, NULL);
+ if (ret)
+ return ret;
+
+ ieee802_11_set_beacons(iface);
+ return 0;
+
+#else /* NEED_AP_MLME */
+ return -1;
+#endif /* NEED_AP_MLME */
+}
+
+
static int hostapd_ctrl_iface_mib(struct hostapd_data *hapd, char *reply,
int reply_size, const char *param)
{
@@ -4346,6 +4395,10 @@ static int hostapd_ctrl_iface_receive_process(struct hostapd_data *hapd,
} else if (os_strncmp(buf, "REGISTER_FRAME ", 15) == 0) {
if (hostapd_ctrl_register_frame(hapd, buf + 16) < 0)
reply_len = -1;
+ } else if (os_strncmp(buf, "SET_BW ", 7) == 0) {
+ /* note: preserve the space for hostapd_parse_freq_params() */
+ if (hostapd_ctrl_iface_set_bw(hapd->iface, buf + 6))
+ reply_len = -1;
#endif /* CONFIG_TESTING_OPTIONS */
} else if (os_strncmp(buf, "CHAN_SWITCH ", 12) == 0) {
if (hostapd_ctrl_iface_chan_switch(hapd->iface, buf + 12))
diff --git a/hostapd/hostapd_cli.c b/hostapd/hostapd_cli.c
index 57702d93b2..0f6ce5c8b1 100644
--- a/hostapd/hostapd_cli.c
+++ b/hostapd/hostapd_cli.c
@@ -1231,6 +1231,36 @@ static int hostapd_cli_cmd_notify_cw_change(struct wpa_ctrl *ctrl,
}
+static int hostapd_cli_cmd_set_bw(struct wpa_ctrl *ctrl,
+ int argc, char *argv[])
+{
+ char cmd[256];
+ int res;
+ int i;
+ char *tmp;
+ int total;
+
+ res = os_snprintf(cmd, sizeof(cmd), "SET_BW");
+ if (os_snprintf_error(sizeof(cmd), res)) {
+ printf("Too long CHAN_SWITCH command.\n");
+ return -1;
+ }
+
+ total = res;
+ for (i = 0; i < argc; i++) {
+ tmp = cmd + total;
+ res = os_snprintf(tmp, sizeof(cmd) - total, " %s", argv[i]);
+ if (os_snprintf_error(sizeof(cmd) - total, res)) {
+ printf("Too long SET_BW command.\n");
+ return -1;
+ }
+ total += res;
+ }
+
+ return wpa_ctrl_command(ctrl, cmd);
+}
+
+
static int hostapd_cli_cmd_enable(struct wpa_ctrl *ctrl, int argc,
char *argv[])
{
@@ -1770,6 +1800,12 @@ static const struct hostapd_cli_cmd hostapd_cli_commands[] = {
#endif /* CONFIG_IEEE80211AX */
{ "notify_cw_change", hostapd_cli_cmd_notify_cw_change, NULL,
"<channel_width> = 0 - 20 MHz, 1 - 40 MHz, 2 - 80 MHz, 3 - 160 MHz" },
+#ifdef CONFIG_TESTING_OPTIONS
+ { "set_bw", hostapd_cli_cmd_set_bw, NULL,
+ "[sec_channel_offset=] [center_freq1=]\n"
+ " [center_freq2=] [bandwidth=] [ht|vht]\n"
+ " = change channel bandwidth" },
+#endif
{ "hs20_wnm_notif", hostapd_cli_cmd_hs20_wnm_notif, NULL,
"<addr> <url>\n"
" = send WNM-Notification Subscription Remediation Request" },
diff --git a/src/ap/ctrl_iface_ap.c b/src/ap/ctrl_iface_ap.c
index 002e7b4938..8072710957 100644
--- a/src/ap/ctrl_iface_ap.c
+++ b/src/ap/ctrl_iface_ap.c
@@ -1131,6 +1131,8 @@ int hostapd_parse_freq_params(const char *pos,
struct hostapd_freq_params *params,
unsigned int freq)
{
+ memset(params, 0, sizeof(*params));
+
if (freq)
params->freq = freq;
else
diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c
index cb7b199366..65df420a06 100644
--- a/src/ap/hostapd.c
+++ b/src/ap/hostapd.c
@@ -4448,10 +4448,10 @@ free_ap_params:
* the same hw_mode. Any other changes to MAC parameters or provided settings
* are not supported.
*/
-static int hostapd_change_config_freq(struct hostapd_data *hapd,
- struct hostapd_config *conf,
- struct hostapd_freq_params *params,
- struct hostapd_freq_params *old_params)
+int hostapd_change_config_freq(struct hostapd_data *hapd,
+ struct hostapd_config *conf,
+ struct hostapd_freq_params *params,
+ struct hostapd_freq_params *old_params)
{
int channel;
u8 seg0 = 0, seg1 = 0;
diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
index 129e0b5f79..1f5a21cbd7 100644
--- a/src/ap/hostapd.h
+++ b/src/ap/hostapd.h
@@ -845,6 +845,10 @@ void hostapd_event_sta_opmode_changed(struct hostapd_data *hapd, const u8 *addr,
enum smps_mode smps_mode,
enum chan_width chan_width, u8 rx_nss);
+int hostapd_change_config_freq(struct hostapd_data *hapd,
+ struct hostapd_config *conf,
+ struct hostapd_freq_params *params,
+ struct hostapd_freq_params *old_params);
#ifdef CONFIG_FST
void fst_hostapd_fill_iface_obj(struct hostapd_data *hapd,
struct fst_wpa_obj *iface_obj);
diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index 0a05323532..bea48af7d7 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -2417,6 +2417,9 @@ struct wpa_driver_capa {
#define WPA_DRIVER_FLAGS2_P2P_FEATURE_V2 0x0000000002000000ULL
/** Driver supports P2P PCC mode */
#define WPA_DRIVER_FLAGS2_P2P_FEATURE_PCC_MODE 0x0000000004000000ULL
+/** Driver supports arbitrary channel width changes in AP mode */
+#define WPA_DRIVER_FLAGS2_AP_CHANWIDTH_CHANGE 0x0000000008000000ULL
+
u64 flags2;
#define FULL_AP_CLIENT_STATE_SUPP(drv_flags) \
diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c
index 88ee9e724a..2aba1f9a42 100644
--- a/src/drivers/driver_nl80211_capa.c
+++ b/src/drivers/driver_nl80211_capa.c
@@ -747,8 +747,10 @@ static void wiphy_info_feature_flags(struct wiphy_info_data *info,
if (flags & NL80211_FEATURE_NEED_OBSS_SCAN)
capa->flags |= WPA_DRIVER_FLAGS_OBSS_SCAN;
- if (flags & NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE)
+ if (flags & NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE) {
capa->flags |= WPA_DRIVER_FLAGS_HT_2040_COEX;
+ capa->flags2 |= WPA_DRIVER_FLAGS2_AP_CHANWIDTH_CHANGE;
+ }
if (flags & NL80211_FEATURE_TDLS_CHANNEL_SWITCH) {
wpa_printf(MSG_DEBUG, "nl80211: TDLS channel switch");
diff --git a/tests/hwsim/test_ap_ht.py b/tests/hwsim/test_ap_ht.py
index 01e874fc8d..5d8e39ad6b 100644
--- a/tests/hwsim/test_ap_ht.py
+++ b/tests/hwsim/test_ap_ht.py
@@ -972,6 +972,47 @@ def test_ap_ht_40mhz_intolerant_sta(dev, apdev):
if hapd.get_status_field("secondary_channel") != "-1":
raise Exception("Unexpected secondary_channel (did not re-enable 40 MHz)")
+def test_ap_ht_20_40_switch(dev, apdev):
+ """Do a bandwidth switch from 20 to 40 MHz without doing a CSA"""
+ params = {"ssid": "ht_20_40",
+ "channel": "6",
+ "ht_capab": "[HT40-]"}
+ hapd = hostapd.add_ap(apdev[0], params)
+ if hapd.get_status_field("secondary_channel") != "-1":
+ raise Exception("Unexpected secondary_channel")
+
+ dev[0].connect("ht_20_40", key_mgmt="NONE", scan_freq="2437")
+ if hapd.get_status_field("secondary_channel") != "-1":
+ raise Exception("Unexpected secondary_channel")
+
+ hapd.request("SET_BW bandwidth=20 ht")
+ time.sleep(1)
+ if hapd.get_status_field("secondary_channel") != "0":
+ raise Exception("Unexpected secondary_channel")
+ sig = dev[0].request('SIGNAL_POLL').splitlines()
+ expected = (
+ 'FREQUENCY=2437',
+ 'WIDTH=20 MHz',
+ 'CENTER_FRQ1=2437',
+ )
+ for e in expected:
+ if not e in sig:
+ raise Exception("%s not found in %r" % (e, sig))
+
+ hapd.request("SET_BW bandwidth=40 sec_channel_offset=-1 ht")
+ time.sleep(1)
+ if hapd.get_status_field("secondary_channel") != "-1":
+ raise Exception("Unexpected secondary_channel")
+ sig = dev[0].request('SIGNAL_POLL').splitlines()
+ expected = (
+ 'FREQUENCY=2437',
+ 'WIDTH=40 MHz',
+ 'CENTER_FRQ1=2427',
+ )
+ for e in expected:
+ if not e in sig:
+ raise Exception("%s not found in %r" % (e, sig))
+
def test_ap_ht_40mhz_intolerant_sta_deinit(dev, apdev):
"""Associated STA indicating 40 MHz intolerant and hostapd deinit"""
clear_scan_cache(apdev[0])
diff --git a/tests/hwsim/test_ap_vht.py b/tests/hwsim/test_ap_vht.py
index d0bd6468ef..3149d51177 100644
--- a/tests/hwsim/test_ap_vht.py
+++ b/tests/hwsim/test_ap_vht.py
@@ -1339,3 +1339,41 @@ def test_ap_vht_csa_invalid(dev, apdev):
time.sleep(1)
finally:
clear_regdom(hapd, dev)
+
+def test_ap_vht_bwswitch(dev, apdev):
+ """Do a bandwidth switch without a CSA"""
+ try:
+ params = {"ssid": "vht",
+ "country_code": "FI",
+ "hw_mode": "a",
+ "channel": "36",
+ "ht_capab": "[HT40+]",
+ "ieee80211n": "1",
+ "ieee80211ac": "1",
+ "vht_oper_chwidth": "1",
+ "vht_capab": "[MAX-MPDU-11454]",
+ "vht_oper_centr_freq_seg0_idx": "42"}
+ hapd = hostapd.add_ap(apdev[0], params)
+
+ dev[0].connect("vht", key_mgmt="NONE", scan_freq='5180')
+
+ for request, expected in [
+ (None,
+ ('FREQUENCY=5180', 'WIDTH=80 MHz', 'CENTER_FRQ1=5210')),
+ ('center_freq1=5210 sec_channel_offset=1 bandwidth=40 ht vht',
+ ('FREQUENCY=5180', 'WIDTH=40 MHz', 'CENTER_FRQ1=5190')),
+ ('center_freq1=5210 sec_channel_offset=0 bandwidth=20 ht vht',
+ ('FREQUENCY=5180', 'WIDTH=20 MHz', 'CENTER_FRQ1=5180')),
+ ('bandwidth=80 sec_channel_offset=1 center_freq1=5210 ht vht',
+ ('FREQUENCY=5180', 'WIDTH=80 MHz', 'CENTER_FRQ1=5210')),
+ ]:
+ if request is not None:
+ if 'OK' not in hapd.request("SET_BW " + request):
+ raise Exception("SET_BW request failed")
+ time.sleep(1)
+ sig = dev[0].request('SIGNAL_POLL').splitlines()
+ for e in expected:
+ if not e in sig:
+ raise Exception("%s not found in %r" % (e, sig))
+ finally:
+ clear_regdom(hapd, dev)
--
2.50.1
More information about the Hostap
mailing list