[OpenWrt-Devel,5/5] hostapd: update switch_channel methods

Message ID 20181008124520.30040-5-yshvedov@wimarksystems.com
State New
Headers show
Series
  • [OpenWrt-Devel,1/5] hostapd: add ubus hostapd_iface object
Related show

Commit Message

Yury Shvedov Oct. 8, 2018, 12:45 p.m.
This patch implements more complex procedure of channel switching. It
can accept the list of channel features witch can contain the list of
channel numbers or frequencies. There two type of operation - fast and
not fast.

To be more, accurate, there two operations now - switch_chan and
switch_chan_list. The first accepts nearly the same arguments as old,
except next:
    * Instead of freq and chan parameters there could be one of four:
      freq - the same as old, channel - the same as old, freqs - the
      list of frequencies and channels - the list of channels. Only one
      of them will be used and translates to the list of channels. The
      priority order is the same as listed.
    * There no segment 0 and 1 indexes variables. They will be predicted
      automatically by vht_oper_cwidth.
    * The variable vht_oper_chwidth could be one of VHT_CHANWIDTH_
      values, or 20, 40, 80, 160 and 8080 to be more user-friendly.
    * New variables fast and allow_acs will be described later.
The switch_can_list has bcn_count, block_tx, fast and allow_acs
variables with the same meanings as in switch_chan. The list variable is a
list of object with switch_chan arguments. Let's call this list the
features list.

The fast (defined by boolean variable fast)  will try only to perform the
channel_switch operation via the driver. With the most drivers and channel
(except the DFS) this will successfully works. The operation will roll over
features and channels lists until successful driver's switch_chan.

The non-fast operation will take the first feature, assemble the
chanlist configuration and restarts ap. The full configuring operation occurs
including acs (if allow_acs) and DFS. The result channel will be chosen
from the channel list. In case when all listed channels are prohibited
by DFS the hostapd will stay switched off.

Signed-off-by: Yury Shvedov <yshvedov@wimarksystems.com>
---
 .../services/hostapd/src/src/ap/ubus.c        | 510 ++++++++++++++++--
 1 file changed, 473 insertions(+), 37 deletions(-)

Patch

diff --git a/package/network/services/hostapd/src/src/ap/ubus.c b/package/network/services/hostapd/src/src/ap/ubus.c
index abbe3fac88..a3e2c00777 100644
--- a/package/network/services/hostapd/src/src/ap/ubus.c
+++ b/package/network/services/hostapd/src/src/ap/ubus.c
@@ -23,6 +23,8 @@ 
 #include "rrm.h"
 #include "wnm_ap.h"
 #include "taxonomy.h"
+#include "hw_features.h"
+#include "common/hw_features_common.h"
 
 #define ACCT_DEFAULT_UPDATE_INTERVAL 300
 
@@ -153,63 +155,405 @@  hostapd_iface_get_bss(struct ubus_context *ctx, struct ubus_object *obj,
 #ifdef NEED_AP_MLME
 enum {
 	CSA_FREQ,
-	CSA_BCN_COUNT,
-	CSA_CENTER_FREQ1,
-	CSA_CENTER_FREQ2,
-	CSA_BANDWIDTH,
-	CSA_SEC_CHANNEL_OFFSET,
+	CSA_CHANNEL,
+	CSA_FREQS,
+	CSA_CHANNELS,
 	CSA_HT,
 	CSA_VHT,
+	CSA_SECONDARY_CHANNEL,
+	CSA_VHT_OPER_CWIDTH,
+	CSA_BCN_COUNT,
 	CSA_BLOCK_TX,
-	__CSA_MAX
+	CSA_FAST,
+	CSA_ALLOW_ACS,
+	__CSA_MAX,
 };
 
 static const struct blobmsg_policy csa_policy[__CSA_MAX] = {
 	[CSA_FREQ] = { "freq", BLOBMSG_TYPE_INT32 },
-	[CSA_BCN_COUNT] = { "bcn_count", BLOBMSG_TYPE_INT32 },
-	[CSA_CENTER_FREQ1] = { "center_freq1", BLOBMSG_TYPE_INT32 },
-	[CSA_CENTER_FREQ2] = { "center_freq2", BLOBMSG_TYPE_INT32 },
-	[CSA_BANDWIDTH] = { "bandwidth", BLOBMSG_TYPE_INT32 },
-	[CSA_SEC_CHANNEL_OFFSET] = { "sec_channel_offset", BLOBMSG_TYPE_INT32 },
+	[CSA_CHANNEL] = { "channel", BLOBMSG_TYPE_INT32 },
+	[CSA_FREQS] = { "freqs", BLOBMSG_TYPE_ARRAY },
+	[CSA_CHANNELS] = { "channels", BLOBMSG_TYPE_ARRAY },
 	[CSA_HT] = { "ht", BLOBMSG_TYPE_BOOL },
 	[CSA_VHT] = { "vht", BLOBMSG_TYPE_BOOL },
+	[CSA_SECONDARY_CHANNEL] = { "secondary_channel", BLOBMSG_TYPE_INT32 },
+	[CSA_VHT_OPER_CWIDTH] = { "vht_oper_chwidth", BLOBMSG_TYPE_INT32 },
+	[CSA_BCN_COUNT] = { "bcn_count", BLOBMSG_TYPE_INT32 },
 	[CSA_BLOCK_TX] = { "block_tx", BLOBMSG_TYPE_BOOL },
+	[CSA_FAST] = { "fast", BLOBMSG_TYPE_BOOL },
+	[CSA_ALLOW_ACS] = { "allow_acs", BLOBMSG_TYPE_BOOL },
 };
+static void ubus_adjust_vht_center_freq(struct hostapd_iface *iface,
+				       int chan, int secondary_channel, int *vht_oper_chwidth,
+					   int ht, int vht,
+				       u8 *vht_oper_centr_freq_seg0_idx,
+				       u8 *vht_oper_centr_freq_seg1_idx)
+{
+	*vht_oper_centr_freq_seg1_idx = 0;
+	if (!chan)
+		return;
+	if (!ht && !vht) {
+		*vht_oper_chwidth = VHT_CHANWIDTH_USE_HT;
+		*vht_oper_centr_freq_seg0_idx = chan;
+		return;
+	}
+
+	switch (*vht_oper_chwidth) {
+	case VHT_CHANWIDTH_USE_HT:
+	case 40:
+		*vht_oper_chwidth = VHT_CHANWIDTH_USE_HT;
+		if (secondary_channel == 1)
+			*vht_oper_centr_freq_seg0_idx = chan + 2;
+		else if (secondary_channel == -1)
+			*vht_oper_centr_freq_seg0_idx = chan - 2;
+		else
+			*vht_oper_centr_freq_seg0_idx = chan;
+		break;
+	case VHT_CHANWIDTH_80MHZ:
+	case 80:
+		*vht_oper_chwidth = VHT_CHANWIDTH_80MHZ;
+		*vht_oper_centr_freq_seg0_idx = chan + 6;
+		break;
+	case VHT_CHANWIDTH_160MHZ:
+	case 160:
+		*vht_oper_chwidth = VHT_CHANWIDTH_160MHZ;
+		*vht_oper_centr_freq_seg0_idx = chan + 14;
+		break;
+	case 20:
+	default:
+		*vht_oper_chwidth = VHT_CHANWIDTH_USE_HT;
+		*vht_oper_centr_freq_seg0_idx = chan;
+		break;
+	}
+}
+static int ubus_bandwidth_to_vht_oper_chwidth(int bandwidth, int center_freq1,
+			int center_freq2)
+{
+	if (bandwidth < 80) {
+		return VHT_CHANWIDTH_USE_HT;
+	} else if (bandwidth < 160) {
+		return center_freq2 ? VHT_CHANWIDTH_80P80MHZ : VHT_CHANWIDTH_80MHZ;
+	} else {
+		return VHT_CHANWIDTH_160MHZ;
+	}
+}
 
 static int
-hostapd_switch_chan(struct hostapd_data *hapd, struct blob_attr *msg)
+hostapd_iface_try_channel_fallback_helper(struct hostapd_iface *iface,
+		struct wpa_freq_range_list *ch_list, int ht, int vht, int sec_channel,
+		int vht_oper_chwidth, int center_idx0, int center_idx1, int acs,
+		int *disabled, int do_switch)
+{
+	iface->conf->ieee80211n = ht;
+	iface->conf->ieee80211ac = vht;
+    iface->conf->secondary_channel = sec_channel;
+	//iface->freq = ch_list->range->min;
+	iface->conf->channel = ch_list->range->min;
+	iface->conf->vht_oper_centr_freq_seg0_idx = center_idx0;
+	iface->conf->vht_oper_centr_freq_seg1_idx = center_idx1;
+	iface->conf->vht_oper_chwidth = vht_oper_chwidth;
+	iface->conf->acs = acs;
+	if (acs)
+		iface->conf->channel = 0;
+	wpa_printf(MSG_DEBUG, "ubus: Fallback channel switch selected %d - %s, "
+			"list: %s, ht - %d, vht - %d sec_channel - %d, seg0_idx - %d, "
+			"seg1_idx - %d, chwidth - %d", iface->conf->channel,
+			acs ? "with acs" : "without acs", freq_range_list_str(ch_list), ht,
+			vht, sec_channel, center_idx0, center_idx1, vht_oper_chwidth);
+	iface->conf->acs_ch_list.range =
+		os_realloc_array(iface->conf->acs_ch_list.range, ch_list->num,
+			sizeof(struct wpa_freq_range));
+	os_memcpy (iface->conf->acs_ch_list.range, ch_list->range,
+			ch_list->num * 	sizeof(struct wpa_freq_range));
+	iface->conf->acs_ch_list.num = ch_list->num;
+
+	if (!*disabled) {
+		if (!do_switch) {
+			return 0;
+		}
+		hostapd_disable_iface(iface);
+		*disabled = 1;
+	}
+	if (!hostapd_enable_iface(iface)) {
+		*disabled = 0;
+		return 0;
+	}
+	return -1;
+}
+static int
+hostapd_iface_try_channel_fallback(struct hostapd_iface *iface,
+		struct wpa_freq_range_list *ch_list, int ht, int vht, int sec_channel,
+		int vht_oper_chwidth, int acs, int *disabled, int do_switch)
+{
+	u8 center_idx0, center_idx1;
+	ubus_adjust_vht_center_freq(iface, ch_list->range->min, sec_channel,
+			&vht_oper_chwidth, ht, vht, &center_idx0,  &center_idx1);
+
+	return hostapd_iface_try_channel_fallback_helper(iface, ch_list, ht, vht,
+			sec_channel, vht_oper_chwidth, center_idx0, center_idx1, acs,
+			disabled, do_switch);
+}
+static int
+hostapd_iface_try_channel_fallback_fp(struct hostapd_iface *iface,
+		struct hostapd_freq_params *data, int acs,
+		struct wpa_freq_range_list *ch_list, int *disabled, int do_switch)
+{
+	int center_idx0 = hostapd_hw_get_channel(iface->bss[0], data->center_freq1);
+	int center_idx1 = hostapd_hw_get_channel(iface->bss[0], data->center_freq2);
+
+	return hostapd_iface_try_channel_fallback_helper(iface, ch_list,
+			data->ht_enabled, data->vht_enabled,
+			data->sec_channel_offset,
+			ubus_bandwidth_to_vht_oper_chwidth(data->bandwidth,
+				center_idx0, center_idx1), center_idx0, center_idx1,
+			acs, disabled, do_switch);
+}
+static int hostapd_set_curr_freq_params(struct hostapd_iface *iface,
+		struct hostapd_freq_params *params, int *acs,
+		struct wpa_freq_range_list *ch_list)
+{
+	struct hostapd_config *conf = iface->conf;
+	memset(params, 0, sizeof(*params));
+	memset(ch_list, 0, sizeof(*ch_list));
+
+	if (hostapd_set_freq_params(params, conf->hw_mode,
+				hostapd_hw_get_freq(*iface->bss, conf->channel),
+				conf->channel, conf->ieee80211n,
+				conf->ieee80211ac,
+				conf->secondary_channel,
+				conf->vht_oper_chwidth,
+				conf->vht_oper_centr_freq_seg0_idx,
+				conf->vht_oper_centr_freq_seg1_idx,
+				conf->vht_capab))
+    {
+            params->channel = 0;
+			return -1;
+    }
+	*acs = conf->acs;
+	if (!*acs)
+		return 0;
+	ch_list->range = os_calloc(conf->acs_ch_list.num, sizeof(*ch_list->range));
+	ch_list->num = conf->acs_ch_list.num;
+	os_memcpy(ch_list->range, conf->acs_ch_list.range,
+			conf->acs_ch_list.num * sizeof(*ch_list->range));
+    return 0;
+}
+static int
+hostapd_freq_params_compare(struct hostapd_freq_params *a,
+		struct hostapd_freq_params *b)
+{
+	/* Compiler could choose different size of enum than int. Other values are
+	 * the same size, so there should not be any padding */
+	return a->mode == b->mode &&
+		os_memcmp(&a->freq, &b->freq, sizeof(*a) - ((void *)&a->freq - (void *)a)) == 0;
+}
+static int
+hostapd_iface_try_channel(struct hostapd_iface *iface, struct hostapd_freq_params *old,
+		struct wpa_freq_range_list *ch_list, int ht, int vht, int sec_channel,
+		int vht_oper_chwidth, int cs_count, int block_tx, int fast,
+		int acs, int *disabled)
 {
-	struct blob_attr *tb[__CSA_MAX];
 	struct csa_settings css;
+	struct hostapd_freq_params *freq_params = &css.freq_params;
+	unsigned int i, k;
+	int err = 1;
+	int chan, freq;
+	u8 center_idx0, center_idx1;
+
+	memset(&css, 0, sizeof (css));
+	css.cs_count = cs_count;
+	css.block_tx = block_tx;
+
+	if (*disabled) {
+		return hostapd_iface_try_channel_fallback(iface, ch_list, ht, vht,
+				sec_channel, vht_oper_chwidth, acs, disabled, 1);
+	}
+	if (hostapd_csa_in_progress(iface) || iface->cac_started)
+		return -1;
+	if (iface->state != HAPD_IFACE_ENABLED) {
+		return hostapd_iface_try_channel_fallback(iface, ch_list, ht, vht,
+				sec_channel, vht_oper_chwidth, acs, disabled, 1);
+	}
 
-	blobmsg_parse(csa_policy, __CSA_MAX, tb, blob_data(msg), blob_len(msg));
+	for (i=0; i < ch_list->num; ++i) {
+		/*
+		 * Allow selection of DFS channel in ETSI to comply with
+		 * uniform spreading.
+		 */
+		chan = ch_list->range[i].min;
+		freq = hostapd_hw_get_freq(iface->bss[0], chan);
+		ubus_adjust_vht_center_freq(iface, chan, sec_channel, &vht_oper_chwidth,
+				ht, vht, &center_idx0, &center_idx1);
+		if (hostapd_set_freq_params(freq_params, iface->conf->hw_mode,
+					freq, chan,	ht, vht, sec_channel, vht_oper_chwidth,
+					center_idx0, center_idx1,
+					iface->current_mode->vht_capab))
+		{
+			wpa_printf(MSG_WARNING, "Can not build channel freq=%d, chan=%d, "
+					"ht=%d, vht=%d, sec_channel=%d, vht_cwidth=%d, center0=%d, "
+					"center1=%d", freq, chan,
+					ht, vht, sec_channel, vht_oper_chwidth,
+					center_idx0, center_idx1);
+			return UBUS_STATUS_INVALID_ARGUMENT;
+		}
+		if (hostapd_freq_params_compare(old, freq_params)) {
+			return 0;
+		}
 
-	if (!tb[CSA_FREQ])
-		return UBUS_STATUS_INVALID_ARGUMENT;
+		/* Perform channel switch/CSA */
+		for (k = 0; k < iface->num_bss; k++) {
+			err = hostapd_switch_channel(iface->bss[k], &css);
+			if (err)
+				break;
+		}
+		if (!err) {
+			/* Set configuration to selected channel */
+			ch_list->range[i].min = ch_list->range[i].max = ch_list->range->min;
+			ch_list->range->min = ch_list->range->max = chan;
+			return hostapd_iface_try_channel_fallback(iface, ch_list, ht, vht,
+				sec_channel, vht_oper_chwidth, acs, disabled, 0);
+		} else if (!fast) {
+			/* In slow mode try only first channel for switch. Others - for
+			 * ACS and DFS procedures */
+			break;
+		}
+	}
+	if (fast)
+		return -1;
+	wpa_printf(MSG_DEBUG, "Can not perform fast switch. Use fallback method");
+	return hostapd_iface_try_channel_fallback(iface, ch_list, ht, vht,
+			sec_channel, vht_oper_chwidth, acs, disabled, 1);
+}
+static int ubus_check_channel(struct hostapd_iface *iface, int n)
+{
+	struct hostapd_channel_data *chan =
+		hw_get_channel_chan(iface->current_mode, n, NULL);
 
-	memset(&css, 0, sizeof(css));
-	css.freq_params.freq = blobmsg_get_u32(tb[CSA_FREQ]);
+	if (!chan) {
+		return UBUS_STATUS_INVALID_ARGUMENT;
+	}
+	if (chan->flag & HOSTAPD_CHAN_DISABLED) {
+		return UBUS_STATUS_NOT_SUPPORTED;
+	}
+	return 0;
+}
+static int blobmsg_get_ch_list(struct blob_attr *attr, struct hostapd_iface *iface,
+		struct wpa_freq_range_list *ch_list, int is_array, int is_channel)
+{
+	int val;
+	struct blob_attr *cur;
+	int rem;
+	int res = 0;
+
+	memset(ch_list, 0, sizeof(*ch_list));
+	if (!is_array) {
+		ch_list->range = os_calloc(1, sizeof(ch_list->range[0]));
+		val = blobmsg_get_u32(attr);
+		if (!is_channel)
+			val = hostapd_hw_get_channel(iface->bss[0], val);
+		ch_list->range->min = ch_list->range->max = val;
+		ch_list->num=1;
+		res = ubus_check_channel(iface, val);
+	} else blobmsg_for_each_attr(cur, attr, rem) {
+		ch_list->range = os_realloc_array(ch_list->range, ch_list->num + 1,
+				sizeof(ch_list->range[0]));
+		val = blobmsg_get_u32(cur);
+		ch_list->range[ch_list->num].min = ch_list->range[ch_list->num].max =
+			is_channel ? val : hostapd_hw_get_channel(iface->bss[0], val);
+		++ch_list->num;
+		res = ubus_check_channel(iface, val);
+		if (res)
+			break;
+	}
+	if (!res)
+		return 0;
 
-#define SET_CSA_SETTING(name, field, type) \
-	do { \
-		if (tb[name]) \
-			css.field = blobmsg_get_ ## type(tb[name]); \
-	} while(0)
+	os_free(ch_list->range);
+	ch_list->range = NULL;
+	ch_list->num=0;
+	return res;
+}
+static int
+hostapd_do_switch_chan(struct hostapd_iface *iface, struct blob_attr *tb[],
+		struct hostapd_freq_params *old, int cs_count, int block_tx, int fast,
+		int acs, int *disabled)
+{
+	struct wpa_freq_range_list ch_list;
+	int ht = old->ht_enabled;
+	int vht = old->vht_enabled;
+	int sec_channel = old->sec_channel_offset;
+	int vht_oper_chwidth = ubus_bandwidth_to_vht_oper_chwidth(old->bandwidth,
+			old->center_freq1, old->center_freq2);
+	int res = 1;
+
+	if (tb[CSA_FREQ]) {
+		res = blobmsg_get_ch_list(tb[CSA_FREQ], iface, &ch_list, 0, 0);
+	} else if (tb[CSA_CHANNEL]) {
+		res = blobmsg_get_ch_list(tb[CSA_CHANNEL], iface, &ch_list, 0, 1);
+	} else if (tb[CSA_FREQS]) {
+		res = blobmsg_get_ch_list(tb[CSA_FREQS], iface, &ch_list, 1, 0);
+	} else if (tb[CSA_CHANNELS]) {
+		res = blobmsg_get_ch_list(tb[CSA_CHANNELS], iface, &ch_list, 1, 1);
+	}
+	if (res)
+		return res;
 
-	SET_CSA_SETTING(CSA_BCN_COUNT, cs_count, u32);
-	SET_CSA_SETTING(CSA_CENTER_FREQ1, freq_params.center_freq1, u32);
-	SET_CSA_SETTING(CSA_CENTER_FREQ2, freq_params.center_freq2, u32);
-	SET_CSA_SETTING(CSA_BANDWIDTH, freq_params.bandwidth, u32);
-	SET_CSA_SETTING(CSA_SEC_CHANNEL_OFFSET, freq_params.sec_channel_offset, u32);
-	SET_CSA_SETTING(CSA_HT, freq_params.ht_enabled, bool);
-	SET_CSA_SETTING(CSA_VHT, freq_params.vht_enabled, bool);
-	SET_CSA_SETTING(CSA_BLOCK_TX, block_tx, bool);
+	if (tb[CSA_HT]) {
+		ht = blobmsg_get_bool(tb[CSA_HT]);
+	}
+	if (tb[CSA_VHT]) {
+		vht = blobmsg_get_bool(tb[CSA_VHT]);
+	}
+	if (tb[CSA_SECONDARY_CHANNEL]) {
+		sec_channel = blobmsg_get_u32(tb[CSA_SECONDARY_CHANNEL]);
+	}
+	if (tb[CSA_VHT_OPER_CWIDTH]) {
+		vht_oper_chwidth = blobmsg_get_u32(tb[CSA_VHT_OPER_CWIDTH]);
+	}
+	return hostapd_iface_try_channel(iface, old, &ch_list, ht, vht, sec_channel,
+			vht_oper_chwidth, cs_count, block_tx, fast, acs, disabled);
+}
+static int
+hostapd_switch_chan(struct hostapd_iface *iface, struct blob_attr *msg)
+{
+	struct blob_attr *tb[__CSA_MAX];
+	struct hostapd_freq_params old;
+	struct wpa_freq_range_list old_ch_list;
+	int old_acs;
+	int cs_count = 5, block_tx = 0, fast = 0, acs = 0;
+	int disabled = iface->state == HAPD_IFACE_DISABLED;
+	int err = UBUS_STATUS_OK;
 
+	blobmsg_parse(csa_policy, __CSA_MAX, tb, blob_data(msg), blob_len(msg));
 
-	if (hostapd_switch_channel(hapd, &css) != 0)
-		return UBUS_STATUS_NOT_SUPPORTED;
-	return UBUS_STATUS_OK;
-#undef SET_CSA_SETTING
+	memset(&old, 0, sizeof (old));
+	hostapd_set_curr_freq_params(iface, &old, &old_acs, &old_ch_list);
+
+	if (tb[CSA_BCN_COUNT])
+		cs_count = blobmsg_get_u32(tb[CSA_BCN_COUNT]);
+	if (tb[CSA_BLOCK_TX])
+		block_tx = blobmsg_get_bool(tb[CSA_BCN_COUNT]);
+	if (tb[CSA_FAST])
+		fast = blobmsg_get_bool(tb[CSA_FAST]);
+	if (tb[CSA_ALLOW_ACS])
+		acs = blobmsg_get_bool(tb[CSA_ALLOW_ACS]);
+
+	err = hostapd_do_switch_chan(iface, tb, &old, cs_count, block_tx, fast,
+			acs, &disabled);
+	if (!err)
+		goto result;
+	if (err < 0)
+		err = UBUS_STATUS_NOT_SUPPORTED;
+
+	if (!disabled)
+		goto result;
+
+	hostapd_iface_try_channel_fallback_fp(iface, &old, old_acs, &old_ch_list,
+			&disabled, 1);
+
+result:
+	os_free(old_ch_list.range);
+	return err;
 }
 
 static int
@@ -220,7 +564,86 @@  hostapd_iface_switch_chan(struct ubus_context *ctx, struct ubus_object *obj,
 	struct hostapd_iface *iface = container_of(obj, struct hostapd_iface,
 			ubus.obj);
 	if (iface && iface->bss[0])
-		return hostapd_switch_chan(iface->bss[0], msg);
+		return hostapd_switch_chan(iface, msg);
+	return UBUS_STATUS_INVALID_ARGUMENT;
+}
+
+enum {
+	CSA_LIST,
+	CSA_LIST_BCN_COUNT,
+	CSA_LIST_BLOCK_TX,
+	CSA_LIST_FAST,
+	CSA_LIST_ALLOW_ACS,
+	__CSA_LIST_MAX,
+};
+static const struct blobmsg_policy csa_list_policy[__CSA_LIST_MAX] = {
+	[CSA_LIST] = { "list", BLOBMSG_TYPE_ARRAY },
+	[CSA_LIST_BCN_COUNT] = { "bcn_count", BLOBMSG_TYPE_INT32 },
+	[CSA_LIST_BLOCK_TX] = { "block_tx", BLOBMSG_TYPE_BOOL },
+	[CSA_LIST_FAST] = { "fast", BLOBMSG_TYPE_BOOL },
+	[CSA_LIST_ALLOW_ACS] = { "allow_acs", BLOBMSG_TYPE_BOOL },
+};
+
+static int
+hostapd_switch_chan_list(struct hostapd_iface *iface, struct blob_attr *msg)
+{
+	struct blob_attr *tb_l[__CSA_LIST_MAX];
+	struct blob_attr *tb[__CSA_MAX];
+	struct blob_attr *cur;
+	struct hostapd_freq_params old;
+	struct wpa_freq_range_list old_ch_list;
+	int old_acs;
+	int cs_count = 5, block_tx = 0, fast = 0, acs = 0;
+	int disabled = iface->state == HAPD_IFACE_DISABLED;
+	int rem;
+	int err = UBUS_STATUS_OK;
+
+	blobmsg_parse(csa_list_policy, __CSA_LIST_MAX, tb_l,
+			blob_data(msg), blob_len(msg));
+
+	if (!tb_l[CSA_LIST])
+		return UBUS_STATUS_INVALID_ARGUMENT;
+
+	hostapd_set_curr_freq_params(iface, &old, &old_acs, &old_ch_list);
+
+	if (tb_l[CSA_LIST_BCN_COUNT])
+		cs_count = blobmsg_get_u32(tb_l[CSA_LIST_BCN_COUNT]);
+	if (tb_l[CSA_LIST_BLOCK_TX])
+		block_tx = blobmsg_get_bool(tb_l[CSA_LIST_BCN_COUNT]);
+	if (tb_l[CSA_LIST_FAST])
+		fast = blobmsg_get_bool(tb_l[CSA_LIST_FAST]);
+	if (tb_l[CSA_LIST_ALLOW_ACS])
+		acs = blobmsg_get_bool(tb_l[CSA_LIST_ALLOW_ACS]);
+
+	blobmsg_for_each_attr(cur, tb_l[CSA_LIST], rem) {
+		blobmsg_parse(csa_policy, __CSA_MAX, tb, blobmsg_data(cur),
+				blobmsg_data_len(cur));
+		err = hostapd_do_switch_chan(iface, tb, &old, cs_count, block_tx, fast,
+				acs, &disabled);
+		if (!err)
+			goto result;
+	}
+
+	if (err < 0)
+		err = UBUS_STATUS_NOT_SUPPORTED;
+	if (!disabled)
+		goto result;
+
+	hostapd_iface_try_channel_fallback_fp(iface, &old, old_acs, &old_ch_list,
+			&disabled, 1);
+result:
+	os_free(old_ch_list.range);
+	return err;
+}
+static int
+hostapd_iface_switch_chan_list(struct ubus_context *ctx, struct ubus_object *obj,
+			struct ubus_request_data *req, const char *method,
+			struct blob_attr *msg)
+{
+	struct hostapd_iface *iface = container_of(obj, struct hostapd_iface,
+			ubus.obj);
+	if (iface && iface->bss[0])
+		return hostapd_switch_chan_list(iface, msg);
 	return UBUS_STATUS_INVALID_ARGUMENT;
 }
 #endif
@@ -230,6 +653,8 @@  static const struct ubus_method iface_methods[] = {
 	UBUS_METHOD_NOARG("get_bss", hostapd_iface_get_bss),
 #ifdef NEED_AP_MLME
 	UBUS_METHOD("switch_chan", hostapd_iface_switch_chan, csa_policy),
+	UBUS_METHOD("switch_chan_list", hostapd_iface_switch_chan_list,
+			csa_list_policy),
 #endif
 };
 static struct ubus_object_type iface_object_type =
@@ -564,7 +989,17 @@  hostapd_bss_switch_chan(struct ubus_context *ctx, struct ubus_object *obj,
 {
 	struct hostapd_data *hapd = get_hapd_from_object(obj);
 	if (hapd)
-		return hostapd_switch_chan(hapd, msg);
+		return hostapd_switch_chan(hapd->iface, msg);
+	return UBUS_STATUS_INVALID_ARGUMENT;
+}
+static int
+hostapd_bss_switch_chan_list(struct ubus_context *ctx, struct ubus_object *obj,
+			struct ubus_request_data *req, const char *method,
+			struct blob_attr *msg)
+{
+	struct hostapd_data *hapd = get_hapd_from_object(obj);
+	if (hapd)
+		return hostapd_switch_chan_list(hapd->iface, msg);
 	return UBUS_STATUS_INVALID_ARGUMENT;
 }
 #endif
@@ -1082,6 +1517,7 @@  static const struct ubus_method bss_methods[] = {
 	UBUS_METHOD_NOARG("get_features", hostapd_bss_get_features),
 #ifdef NEED_AP_MLME
 	UBUS_METHOD("switch_chan", hostapd_bss_switch_chan, csa_policy),
+	UBUS_METHOD("switch_chan_list", hostapd_bss_switch_chan_list, csa_list_policy),
 #endif
 	UBUS_METHOD("set_vendor_elements", hostapd_vendor_elements, ve_policy),
 	UBUS_METHOD("notify_response", hostapd_notify_response, notify_policy),