diff --git a/hostapd/config_file.c b/hostapd/config_file.c index 53143f76c..cae9fd30d 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -775,6 +775,24 @@ static int hostapd_config_read_wep(struct hostapd_wep_keys *wep, int keyidx, } +static int hostapd_parse_chanlist(struct hostapd_config *conf, char *val) +{ + char *pos; + + /* for backwards compatibility, translate ' ' in conf str to ',' */ + pos = val; + while (pos) { + pos = os_strchr(pos, ' '); + if (pos) + *pos++ = ','; + } + if (freq_range_list_parse(&conf->acs_ch_list, val)) + return -1; + + return 0; +} + + static int hostapd_parse_intlist(int **int_list, char *val) { int *list; @@ -2542,12 +2560,15 @@ static int hostapd_config_fill(struct hostapd_config *conf, line); return 1; #else /* CONFIG_ACS */ + conf->acs = 1; conf->channel = 0; #endif /* CONFIG_ACS */ - } else + } else { conf->channel = atoi(pos); + conf->acs = conf->channel == 0; + } } else if (os_strcmp(buf, "chanlist") == 0) { - if (hostapd_parse_intlist(&conf->chanlist, pos)) { + if (hostapd_parse_chanlist(conf, pos)) { wpa_printf(MSG_ERROR, "Line %d: invalid channel list", line); return 1; diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index 9e81e9e98..90d15232b 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -170,8 +170,11 @@ channel=1 # Channel list restriction. This option allows hostapd to select one of the # provided channels when a channel should be automatically selected. -# Default: not set (allow any enabled channel to be selected) +# Channel list can be provided as range using hyphen ('-') or individual +# channels can be specified by space (' ') seperated values +# Default: all channels allowed in selected hw_mode #chanlist=100 104 108 112 116 +#chanlist=1 6 11-13 # Beacon interval in kus (1.024 ms) (default: 100; range 15..65535) beacon_int=100 diff --git a/src/ap/acs.c b/src/ap/acs.c index ae7f6c309..652d020d5 100644 --- a/src/ap/acs.c +++ b/src/ap/acs.c @@ -479,16 +479,10 @@ static int acs_usable_chan(struct hostapd_channel_data *chan) static int is_in_chanlist(struct hostapd_iface *iface, struct hostapd_channel_data *chan) { - int *entry; - - if (!iface->conf->chanlist) + if (!iface->conf->acs_ch_list.num) return 1; - for (entry = iface->conf->chanlist; *entry != -1; entry++) { - if (*entry == chan->chan) - return 1; - } - return 0; + return freq_range_list_includes(&iface->conf->acs_ch_list, chan->chan); } diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c index 76011dc07..cccbfabb7 100644 --- a/src/ap/ap_config.c +++ b/src/ap/ap_config.c @@ -181,6 +181,8 @@ struct hostapd_config * hostapd_config_defaults(void) conf->corrupt_gtk_rekey_mic_probability = 0.0; #endif /* CONFIG_TESTING_OPTIONS */ + conf->acs = 0; + conf->acs_ch_list.num = 0; #ifdef CONFIG_ACS conf->acs_num_scans = 5; #endif /* CONFIG_ACS */ @@ -579,7 +581,7 @@ void hostapd_config_free(struct hostapd_config *conf) os_free(conf->bss); os_free(conf->supported_rates); os_free(conf->basic_rates); - os_free(conf->chanlist); + os_free(conf->acs_ch_list.range); os_free(conf->driver_params); #ifdef CONFIG_ACS os_free(conf->acs_chan_bias); diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index 961d2dd38..b9d68321c 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -568,7 +568,8 @@ struct hostapd_config { int fragm_threshold; u8 send_probe_response; u8 channel; - int *chanlist; + u8 acs; + struct wpa_freq_range_list acs_ch_list; enum hostapd_hw_mode hw_mode; /* HOSTAPD_MODE_IEEE80211A, .. */ enum { LONG_PREAMBLE = 0, diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c index e16306c4e..aae544fc8 100644 --- a/src/ap/ap_drv_ops.c +++ b/src/ap/ap_drv_ops.c @@ -715,13 +715,66 @@ int hostapd_drv_set_qos_map(struct hostapd_data *hapd, int hostapd_drv_do_acs(struct hostapd_data *hapd) { struct drv_acs_params params; + int ret, i, acs_ch_list_all = 0; + u8 *channels = NULL; + unsigned int num_channels = 0; + struct hostapd_hw_modes *mode; if (hapd->driver == NULL || hapd->driver->do_acs == NULL) return 0; + os_memset(¶ms, 0, sizeof(params)); params.hw_mode = hapd->iface->conf->hw_mode; + + /* + * If no chanlist config parameter is provided, include all enabled + * channels of the selected hw_mode. + */ + if (!hapd->iface->conf->acs_ch_list.num) + acs_ch_list_all = 1; + + mode = hapd->iface->current_mode; + if (mode == NULL) + return -1; + channels = os_malloc(mode->num_channels); + if (channels == NULL) + return -1; + + for (i = 0; i < mode->num_channels; i++) { + struct hostapd_channel_data *chan = &mode->channels[i]; + if (!acs_ch_list_all && + !freq_range_list_includes(&hapd->iface->conf->acs_ch_list, + chan->chan)) + continue; + if (!(chan->flag & HOSTAPD_CHAN_DISABLED)) + channels[num_channels++] = chan->chan; + } + + params.ch_list = channels; + params.ch_list_len = num_channels; + params.ht_enabled = !!(hapd->iface->conf->ieee80211n); params.ht40_enabled = !!(hapd->iface->conf->ht_capab & HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET); - return hapd->driver->do_acs(hapd->drv_priv, ¶ms); + params.vht_enabled = !!(hapd->iface->conf->ieee80211ac); + params.ch_width = 20; + if (hapd->iface->conf->ieee80211n && params.ht40_enabled) + params.ch_width = 40; + + /* Note: VHT20 is defined by combination of ht_capab & vht_oper_chwidth + */ + if (hapd->iface->conf->ieee80211ac && params.ht40_enabled) { + if (hapd->iface->conf->vht_oper_chwidth == VHT_CHANWIDTH_80MHZ) + params.ch_width = 80; + else if (hapd->iface->conf->vht_oper_chwidth == + VHT_CHANWIDTH_160MHZ || + hapd->iface->conf->vht_oper_chwidth == + VHT_CHANWIDTH_80P80MHZ) + params.ch_width = 160; + } + + ret = hapd->driver->do_acs(hapd->drv_priv, ¶ms); + os_free(channels); + + return ret; } diff --git a/src/ap/dfs.c b/src/ap/dfs.c index d458a4275..715f19b6a 100644 --- a/src/ap/dfs.c +++ b/src/ap/dfs.c @@ -165,16 +165,10 @@ static int dfs_chan_range_available(struct hostapd_hw_modes *mode, static int is_in_chanlist(struct hostapd_iface *iface, struct hostapd_channel_data *chan) { - int *entry; - - if (!iface->conf->chanlist) + if (!iface->conf->acs_ch_list.num) return 1; - for (entry = iface->conf->chanlist; *entry != -1; entry++) { - if (*entry == chan->chan) - return 1; - } - return 0; + return freq_range_list_includes(&iface->conf->acs_ch_list, chan->chan); } diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c index a0adc67db..507053eaa 100644 --- a/src/ap/drv_callbacks.c +++ b/src/ap/drv_callbacks.c @@ -532,9 +532,8 @@ void hostapd_event_connect_failed_reason(struct hostapd_data *hapd, #ifdef CONFIG_ACS static void hostapd_acs_channel_selected(struct hostapd_data *hapd, - u8 pri_channel, u8 sec_channel) + struct acs_selected_channels *acs_res) { - int channel; int ret; if (hapd->iconf->channel) { @@ -543,29 +542,55 @@ static void hostapd_acs_channel_selected(struct hostapd_data *hapd, return; } - hapd->iface->freq = hostapd_hw_get_freq(hapd, pri_channel); + hapd->iface->freq = hostapd_hw_get_freq(hapd, acs_res->pri_channel); - channel = pri_channel; - if (!channel) { + if (!acs_res->pri_channel) { hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_WARNING, "driver switched to bad channel"); return; } - hapd->iconf->channel = channel; + hapd->iconf->channel = acs_res->pri_channel; + hapd->iconf->acs = 1; - if (sec_channel == 0) + if (acs_res->sec_channel == 0) hapd->iconf->secondary_channel = 0; - else if (sec_channel < pri_channel) + else if (acs_res->sec_channel < acs_res->pri_channel) hapd->iconf->secondary_channel = -1; - else if (sec_channel > pri_channel) + else if (acs_res->sec_channel > acs_res->pri_channel) hapd->iconf->secondary_channel = 1; else { wpa_printf(MSG_ERROR, "Invalid secondary channel!"); return; } + if (hapd->iface->conf->ieee80211ac) { + /* set defaults for backwards compatibility */ + hapd->iconf->vht_oper_centr_freq_seg1_idx = 0; + hapd->iconf->vht_oper_centr_freq_seg0_idx = 0; + hapd->iconf->vht_oper_chwidth = VHT_CHANWIDTH_USE_HT; + if (acs_res->ch_width == 80) { + hapd->iconf->vht_oper_centr_freq_seg0_idx = + acs_res->vht_seg0_center_ch; + hapd->iconf->vht_oper_chwidth = VHT_CHANWIDTH_80MHZ; + } else if (acs_res->ch_width == 160) { + if (acs_res->vht_seg1_center_ch == 0) { + hapd->iconf->vht_oper_centr_freq_seg0_idx = + acs_res->vht_seg0_center_ch; + hapd->iconf->vht_oper_chwidth = + VHT_CHANWIDTH_160MHZ; + } else { + hapd->iconf->vht_oper_centr_freq_seg0_idx = + acs_res->vht_seg0_center_ch; + hapd->iconf->vht_oper_centr_freq_seg1_idx = + acs_res->vht_seg1_center_ch; + hapd->iconf->vht_oper_chwidth = + VHT_CHANWIDTH_80P80MHZ; + } + } + } + ret = hostapd_acs_completed(hapd->iface, 0); if (ret) { wpa_printf(MSG_ERROR, @@ -1248,9 +1273,8 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event, break; #ifdef CONFIG_ACS case EVENT_ACS_CHANNEL_SELECTED: - hostapd_acs_channel_selected( - hapd, data->acs_selected_channels.pri_channel, - data->acs_selected_channels.sec_channel); + hostapd_acs_channel_selected(hapd, + &data->acs_selected_channels); break; #endif /* CONFIG_ACS */ default: diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c index 3e4e16b4f..6cdb6d37f 100644 --- a/src/ap/hostapd.c +++ b/src/ap/hostapd.c @@ -179,6 +179,7 @@ int hostapd_reload_config(struct hostapd_iface *iface) hapd = iface->bss[j]; hapd->iconf = newconf; hapd->iconf->channel = oldconf->channel; + hapd->iconf->acs = oldconf->acs; hapd->iconf->secondary_channel = oldconf->secondary_channel; hapd->iconf->ieee80211n = oldconf->ieee80211n; hapd->iconf->ieee80211ac = oldconf->ieee80211ac; diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c index 05431d32c..96744c4f3 100644 --- a/src/ap/hw_features.c +++ b/src/ap/hw_features.c @@ -510,7 +510,11 @@ static int ieee80211n_supported_ht_capab(struct hostapd_iface *iface) return 0; } - if ((conf & HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET) && + /* + * Driver ACS chosen channel may not be HT40 due to internal driver + * restrictions. + */ + if (!iface->conf->acs && (conf & HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET) && !(hw & HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET)) { wpa_printf(MSG_ERROR, "Driver does not support configured " "HT capability [HT40*]"); diff --git a/src/common/hw_features_common.c b/src/common/hw_features_common.c index 309215e55..8d83de65d 100644 --- a/src/common/hw_features_common.c +++ b/src/common/hw_features_common.c @@ -363,8 +363,6 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data, int vht_oper_chwidth, int center_segment0, int center_segment1, u32 vht_caps) { - int tmp; - os_memset(data, 0, sizeof(*data)); data->mode = mode; data->freq = freq; @@ -404,13 +402,34 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data, return -1; if (!sec_channel_offset) return -1; - /* primary 40 part must match the HT configuration */ - tmp = (30 + freq - 5000 - center_segment0 * 5) / 20; - tmp /= 2; - if (data->center_freq1 != 5000 + - center_segment0 * 5 - 20 + 40 * tmp) - return -1; - data->center_freq1 = 5000 + center_segment0 * 5; + if (!center_segment0) { + if (channel <= 48) + center_segment0 = 42; + else if (channel <= 64) + center_segment0 = 58; + else if (channel <= 112) + center_segment0 = 106; + else if (channel <= 128) + center_segment0 = 122; + else if (channel <= 144) + center_segment0 = 138; + else if (channel <= 161) + center_segment0 = 155; + data->center_freq1 = 5000 + center_segment0 * 5; + } else { + /* + * Note: HT/VHT config and params are coupled. Check if + * HT40 channel band is in VHT80 Pri channel band + * configuration. + */ + if (center_segment0 == channel + 6 || + center_segment0 == channel + 2 || + center_segment0 == channel - 2 || + center_segment0 == channel - 6) + data->center_freq1 = 5000 + center_segment0 * 5; + else + return -1; + } break; case VHT_CHANWIDTH_160MHZ: data->bandwidth = 160; @@ -424,13 +443,21 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data, return -1; if (!sec_channel_offset) return -1; - /* primary 40 part must match the HT configuration */ - tmp = (70 + freq - 5000 - center_segment0 * 5) / 20; - tmp /= 2; - if (data->center_freq1 != 5000 + - center_segment0 * 5 - 60 + 40 * tmp) + /* + * Note: HT/VHT config and params are coupled. Check if + * HT40 channel band is in VHT160 channel band configuration. + */ + if (center_segment0 == channel + 14 || + center_segment0 == channel + 10 || + center_segment0 == channel + 6 || + center_segment0 == channel + 2 || + center_segment0 == channel - 2 || + center_segment0 == channel - 6 || + center_segment0 == channel - 10 || + center_segment0 == channel - 14) + data->center_freq1 = 5000 + center_segment0 * 5; + else return -1; - data->center_freq1 = 5000 + center_segment0 * 5; break; } diff --git a/src/common/qca-vendor.h b/src/common/qca-vendor.h index 2117ee702..5ff681788 100644 --- a/src/common/qca-vendor.h +++ b/src/common/qca-vendor.h @@ -195,6 +195,11 @@ enum qca_wlan_vendor_attr_acs_offload { QCA_WLAN_VENDOR_ATTR_ACS_HW_MODE, QCA_WLAN_VENDOR_ATTR_ACS_HT_ENABLED, QCA_WLAN_VENDOR_ATTR_ACS_HT40_ENABLED, + QCA_WLAN_VENDOR_ATTR_ACS_VHT_ENABLED, + QCA_WLAN_VENDOR_ATTR_ACS_CHWIDTH, + QCA_WLAN_VENDOR_ATTR_ACS_CH_LIST, + QCA_WLAN_VENDOR_ATTR_ACS_VHT_SEG0_CENTER_CHANNEL, + QCA_WLAN_VENDOR_ATTR_ACS_VHT_SEG1_CENTER_CHANNEL, /* keep last */ QCA_WLAN_VENDOR_ATTR_ACS_AFTER_LAST, QCA_WLAN_VENDOR_ATTR_ACS_MAX = diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 556620140..6a9cd74a2 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -1588,6 +1588,16 @@ struct drv_acs_params { /* Indicates whether HT40 is enabled */ int ht40_enabled; + + /* Indicates whether VHT is enabled */ + int vht_enabled; + + /* Configured ACS channel width */ + u16 ch_width; + + /* ACS channel list info */ + unsigned int ch_list_len; + const u8 *ch_list; }; @@ -4546,10 +4556,18 @@ union wpa_event_data { * struct acs_selected_channels - Data for EVENT_ACS_CHANNEL_SELECTED * @pri_channel: Selected primary channel * @sec_channel: Selected secondary channel + * @vht_seg0_center_ch: VHT mode Segment0 center channel + * @vht_seg1_center_ch: VHT mode Segment1 center channel + * @ch_width: Selected Channel width by driver. Driver may choose to + * change hostapd configured ACS channel width due driver internal + * channel restrictions. */ struct acs_selected_channels { u8 pri_channel; u8 sec_channel; + u8 vht_seg0_center_ch; + u8 vht_seg1_center_ch; + u16 ch_width; } acs_selected_channels; }; diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 023823f33..7b3dc515f 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -8382,12 +8382,24 @@ static int wpa_driver_do_acs(void *priv, struct drv_acs_params *params) (params->ht_enabled && nla_put_flag(msg, QCA_WLAN_VENDOR_ATTR_ACS_HT_ENABLED)) || (params->ht40_enabled && - nla_put_flag(msg, QCA_WLAN_VENDOR_ATTR_ACS_HT40_ENABLED))) { + nla_put_flag(msg, QCA_WLAN_VENDOR_ATTR_ACS_HT40_ENABLED)) || + (params->vht_enabled && + nla_put_flag(msg, QCA_WLAN_VENDOR_ATTR_ACS_VHT_ENABLED)) || + nla_put_u16(msg, QCA_WLAN_VENDOR_ATTR_ACS_CHWIDTH, + params->ch_width) || + (params->ch_list_len && + nla_put(msg, QCA_WLAN_VENDOR_ATTR_ACS_CH_LIST, params->ch_list_len, + params->ch_list))) { nlmsg_free(msg); return -ENOBUFS; } nla_nest_end(msg, data); + wpa_printf(MSG_DEBUG, + "nl80211: ACS Params: HW_MODE: %d HT: %d HT40: %d VHT: %d BW: %d CH_LIST_LEN: %u", + params->hw_mode, params->ht_enabled, params->ht40_enabled, + params->vht_enabled, params->ch_width, params->ch_list_len); + ret = send_and_recv_msgs(drv, msg, NULL, NULL); if (ret) { wpa_printf(MSG_DEBUG, diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c index 87e412dc5..a5b323026 100644 --- a/src/drivers/driver_nl80211_event.c +++ b/src/drivers/driver_nl80211_event.c @@ -1500,6 +1500,25 @@ static void qca_nl80211_acs_select_ch(struct wpa_driver_nl80211_data *drv, nla_get_u8(tb[QCA_WLAN_VENDOR_ATTR_ACS_PRIMARY_CHANNEL]); event.acs_selected_channels.sec_channel = nla_get_u8(tb[QCA_WLAN_VENDOR_ATTR_ACS_SECONDARY_CHANNEL]); + if (tb[QCA_WLAN_VENDOR_ATTR_ACS_VHT_SEG0_CENTER_CHANNEL]) + event.acs_selected_channels.vht_seg0_center_ch = + nla_get_u8(tb[QCA_WLAN_VENDOR_ATTR_ACS_VHT_SEG0_CENTER_CHANNEL]); + if (tb[QCA_WLAN_VENDOR_ATTR_ACS_VHT_SEG0_CENTER_CHANNEL]) + event.acs_selected_channels.vht_seg1_center_ch = + nla_get_u8(tb[QCA_WLAN_VENDOR_ATTR_ACS_VHT_SEG1_CENTER_CHANNEL]); + if (tb[QCA_WLAN_VENDOR_ATTR_ACS_CHWIDTH]) + event.acs_selected_channels.ch_width = + nla_get_u16(tb[QCA_WLAN_VENDOR_ATTR_ACS_CHWIDTH]); + + wpa_printf(MSG_INFO, + "nl80211: ACS Results: PCH: %d SCH: %d BW: %d VHT0: %d VHT1: %d", + event.acs_selected_channels.pri_channel, + event.acs_selected_channels.sec_channel, + event.acs_selected_channels.ch_width, + event.acs_selected_channels.vht_seg0_center_ch, + event.acs_selected_channels.vht_seg1_center_ch); + + /* Ignore ACS channel list check for backwards compatibility */ wpa_supplicant_event(drv->ctx, EVENT_ACS_CHANNEL_SELECTED, &event); }