]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
AP: Support bandwidth changes without CSA through control interface
authorJohannes Berg <johannes.berg@intel.com>
Thu, 31 Jul 2025 13:56:59 +0000 (15:56 +0200)
committerJouni Malinen <j@w1.fi>
Mon, 6 Oct 2025 11:00:47 +0000 (14:00 +0300)
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@intel.com>
Signed-off-by: Ilan Peer <ilan.peer@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
hostapd/ctrl_iface.c
hostapd/hostapd_cli.c
src/ap/hostapd.c
src/ap/hostapd.h
src/drivers/driver.h
src/drivers/driver_nl80211_capa.c
tests/hwsim/test_ap_ht.py
tests/hwsim/test_ap_vht.py

index e6ea1dc3a6d295dc695c026ff0bed7a56b5a9e6f..e20609c367f1e441762ff15f3a4c1bccbe030e2d 100644 (file)
@@ -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)
 {
@@ -4365,6 +4414,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))
index 95f7fd2d8e051152b67a8440e8f3d0f7719109d1..b787cc1978608591ecc917cbacfafa79048939bd 100644 (file)
@@ -1231,6 +1231,13 @@ 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[])
+{
+       return hostapd_cli_cmd(ctrl, "SET_BW", 0, argc, argv);
+}
+
+
 static int hostapd_cli_cmd_enable(struct wpa_ctrl *ctrl, int argc,
                                  char *argv[])
 {
@@ -1770,6 +1777,10 @@ 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" },
+       { "set_bw", hostapd_cli_cmd_set_bw, NULL,
+         "[sec_channel_offset=] [center_freq1=]\n"
+         "  [center_freq2=] [bandwidth=] [ht|vht]\n"
+         "  = change channel bandwidth" },
        { "hs20_wnm_notif", hostapd_cli_cmd_hs20_wnm_notif, NULL,
          "<addr> <url>\n"
          "  = send WNM-Notification Subscription Remediation Request" },
index 9918c270aaf9c2a85fe2924ca55c56d4a57d48e0..fe815119f434aa347cec6b66000a9fa483dfb872 100644 (file)
@@ -4466,10 +4466,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;
index 6e04212786ebeab906ef69eaf84e4506fda8d5f2..3f58225e8184e62a83f9d0fffd9fc819a93c01f1 100644 (file)
@@ -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);
index ada274e43c80db18aca2adca81a5cd5d6fbde0bb..95bb2557e7886b3f1223fa0153aed3c7a8d0d6e7 100644 (file)
@@ -2422,6 +2422,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) \
index 88ee9e724a028d24f3295f3434360c87b1be9d90..2aba1f9a42f33216c8e2e3fe2a8ffe997c3fe28f 100644 (file)
@@ -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");
index 01e874fc8d90637a72c00e9aeee8ce9b496a0948..516b09e2efe8f2fdcf2d392943c34080d417b36e 100644 (file)
@@ -972,6 +972,48 @@ 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"""
+    clear_scan_cache(apdev[0])
+    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])
index d0bd6468ef3f1492e56601ef535676bf60abcc95..3149d511778c59dd9282bd6d33facd94f0dca658 100644 (file)
@@ -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)