Extend offloaded ACS QCA vendor command to support VHT

Update ACS driver offload feature for VHT configuration. In addition,
this allows the chanlist parameter to be used to specify which channels
are included as options for the offloaded ACS case.

Signed-off-by: Jouni Malinen <jouni@qca.qualcomm.com>
This commit is contained in:
Manikandan Mohan 2015-03-11 13:03:58 -07:00 committed by Jouni Malinen
parent 0fd52a612a
commit 857d94225a
15 changed files with 229 additions and 51 deletions

View file

@ -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) static int hostapd_parse_intlist(int **int_list, char *val)
{ {
int *list; int *list;
@ -2542,12 +2560,15 @@ static int hostapd_config_fill(struct hostapd_config *conf,
line); line);
return 1; return 1;
#else /* CONFIG_ACS */ #else /* CONFIG_ACS */
conf->acs = 1;
conf->channel = 0; conf->channel = 0;
#endif /* CONFIG_ACS */ #endif /* CONFIG_ACS */
} else } else {
conf->channel = atoi(pos); conf->channel = atoi(pos);
conf->acs = conf->channel == 0;
}
} else if (os_strcmp(buf, "chanlist") == 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", wpa_printf(MSG_ERROR, "Line %d: invalid channel list",
line); line);
return 1; return 1;

View file

@ -170,8 +170,11 @@ channel=1
# Channel list restriction. This option allows hostapd to select one of the # Channel list restriction. This option allows hostapd to select one of the
# provided channels when a channel should be automatically selected. # 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=100 104 108 112 116
#chanlist=1 6 11-13
# Beacon interval in kus (1.024 ms) (default: 100; range 15..65535) # Beacon interval in kus (1.024 ms) (default: 100; range 15..65535)
beacon_int=100 beacon_int=100

View file

@ -479,16 +479,10 @@ static int acs_usable_chan(struct hostapd_channel_data *chan)
static int is_in_chanlist(struct hostapd_iface *iface, static int is_in_chanlist(struct hostapd_iface *iface,
struct hostapd_channel_data *chan) struct hostapd_channel_data *chan)
{ {
int *entry; if (!iface->conf->acs_ch_list.num)
if (!iface->conf->chanlist)
return 1; return 1;
for (entry = iface->conf->chanlist; *entry != -1; entry++) { return freq_range_list_includes(&iface->conf->acs_ch_list, chan->chan);
if (*entry == chan->chan)
return 1;
}
return 0;
} }

View file

@ -181,6 +181,8 @@ struct hostapd_config * hostapd_config_defaults(void)
conf->corrupt_gtk_rekey_mic_probability = 0.0; conf->corrupt_gtk_rekey_mic_probability = 0.0;
#endif /* CONFIG_TESTING_OPTIONS */ #endif /* CONFIG_TESTING_OPTIONS */
conf->acs = 0;
conf->acs_ch_list.num = 0;
#ifdef CONFIG_ACS #ifdef CONFIG_ACS
conf->acs_num_scans = 5; conf->acs_num_scans = 5;
#endif /* CONFIG_ACS */ #endif /* CONFIG_ACS */
@ -579,7 +581,7 @@ void hostapd_config_free(struct hostapd_config *conf)
os_free(conf->bss); os_free(conf->bss);
os_free(conf->supported_rates); os_free(conf->supported_rates);
os_free(conf->basic_rates); os_free(conf->basic_rates);
os_free(conf->chanlist); os_free(conf->acs_ch_list.range);
os_free(conf->driver_params); os_free(conf->driver_params);
#ifdef CONFIG_ACS #ifdef CONFIG_ACS
os_free(conf->acs_chan_bias); os_free(conf->acs_chan_bias);

View file

@ -568,7 +568,8 @@ struct hostapd_config {
int fragm_threshold; int fragm_threshold;
u8 send_probe_response; u8 send_probe_response;
u8 channel; u8 channel;
int *chanlist; u8 acs;
struct wpa_freq_range_list acs_ch_list;
enum hostapd_hw_mode hw_mode; /* HOSTAPD_MODE_IEEE80211A, .. */ enum hostapd_hw_mode hw_mode; /* HOSTAPD_MODE_IEEE80211A, .. */
enum { enum {
LONG_PREAMBLE = 0, LONG_PREAMBLE = 0,

View file

@ -715,13 +715,66 @@ int hostapd_drv_set_qos_map(struct hostapd_data *hapd,
int hostapd_drv_do_acs(struct hostapd_data *hapd) int hostapd_drv_do_acs(struct hostapd_data *hapd)
{ {
struct drv_acs_params params; 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) if (hapd->driver == NULL || hapd->driver->do_acs == NULL)
return 0; return 0;
os_memset(&params, 0, sizeof(params)); os_memset(&params, 0, sizeof(params));
params.hw_mode = hapd->iface->conf->hw_mode; 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.ht_enabled = !!(hapd->iface->conf->ieee80211n);
params.ht40_enabled = !!(hapd->iface->conf->ht_capab & params.ht40_enabled = !!(hapd->iface->conf->ht_capab &
HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET); HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET);
return hapd->driver->do_acs(hapd->drv_priv, &params); 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, &params);
os_free(channels);
return ret;
} }

View file

@ -165,16 +165,10 @@ static int dfs_chan_range_available(struct hostapd_hw_modes *mode,
static int is_in_chanlist(struct hostapd_iface *iface, static int is_in_chanlist(struct hostapd_iface *iface,
struct hostapd_channel_data *chan) struct hostapd_channel_data *chan)
{ {
int *entry; if (!iface->conf->acs_ch_list.num)
if (!iface->conf->chanlist)
return 1; return 1;
for (entry = iface->conf->chanlist; *entry != -1; entry++) { return freq_range_list_includes(&iface->conf->acs_ch_list, chan->chan);
if (*entry == chan->chan)
return 1;
}
return 0;
} }

View file

@ -532,9 +532,8 @@ void hostapd_event_connect_failed_reason(struct hostapd_data *hapd,
#ifdef CONFIG_ACS #ifdef CONFIG_ACS
static void hostapd_acs_channel_selected(struct hostapd_data *hapd, 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; int ret;
if (hapd->iconf->channel) { if (hapd->iconf->channel) {
@ -543,29 +542,55 @@ static void hostapd_acs_channel_selected(struct hostapd_data *hapd,
return; 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 (!acs_res->pri_channel) {
if (!channel) {
hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211, hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211,
HOSTAPD_LEVEL_WARNING, HOSTAPD_LEVEL_WARNING,
"driver switched to bad channel"); "driver switched to bad channel");
return; 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; 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; 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; hapd->iconf->secondary_channel = 1;
else { else {
wpa_printf(MSG_ERROR, "Invalid secondary channel!"); wpa_printf(MSG_ERROR, "Invalid secondary channel!");
return; 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); ret = hostapd_acs_completed(hapd->iface, 0);
if (ret) { if (ret) {
wpa_printf(MSG_ERROR, wpa_printf(MSG_ERROR,
@ -1248,9 +1273,8 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
break; break;
#ifdef CONFIG_ACS #ifdef CONFIG_ACS
case EVENT_ACS_CHANNEL_SELECTED: case EVENT_ACS_CHANNEL_SELECTED:
hostapd_acs_channel_selected( hostapd_acs_channel_selected(hapd,
hapd, data->acs_selected_channels.pri_channel, &data->acs_selected_channels);
data->acs_selected_channels.sec_channel);
break; break;
#endif /* CONFIG_ACS */ #endif /* CONFIG_ACS */
default: default:

View file

@ -179,6 +179,7 @@ int hostapd_reload_config(struct hostapd_iface *iface)
hapd = iface->bss[j]; hapd = iface->bss[j];
hapd->iconf = newconf; hapd->iconf = newconf;
hapd->iconf->channel = oldconf->channel; hapd->iconf->channel = oldconf->channel;
hapd->iconf->acs = oldconf->acs;
hapd->iconf->secondary_channel = oldconf->secondary_channel; hapd->iconf->secondary_channel = oldconf->secondary_channel;
hapd->iconf->ieee80211n = oldconf->ieee80211n; hapd->iconf->ieee80211n = oldconf->ieee80211n;
hapd->iconf->ieee80211ac = oldconf->ieee80211ac; hapd->iconf->ieee80211ac = oldconf->ieee80211ac;

View file

@ -510,7 +510,11 @@ static int ieee80211n_supported_ht_capab(struct hostapd_iface *iface)
return 0; 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)) { !(hw & HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET)) {
wpa_printf(MSG_ERROR, "Driver does not support configured " wpa_printf(MSG_ERROR, "Driver does not support configured "
"HT capability [HT40*]"); "HT capability [HT40*]");

View file

@ -363,8 +363,6 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data,
int vht_oper_chwidth, int center_segment0, int vht_oper_chwidth, int center_segment0,
int center_segment1, u32 vht_caps) int center_segment1, u32 vht_caps)
{ {
int tmp;
os_memset(data, 0, sizeof(*data)); os_memset(data, 0, sizeof(*data));
data->mode = mode; data->mode = mode;
data->freq = freq; data->freq = freq;
@ -404,13 +402,34 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data,
return -1; return -1;
if (!sec_channel_offset) if (!sec_channel_offset)
return -1; return -1;
/* primary 40 part must match the HT configuration */ if (!center_segment0) {
tmp = (30 + freq - 5000 - center_segment0 * 5) / 20; if (channel <= 48)
tmp /= 2; center_segment0 = 42;
if (data->center_freq1 != 5000 + else if (channel <= 64)
center_segment0 * 5 - 20 + 40 * tmp) center_segment0 = 58;
return -1; else if (channel <= 112)
data->center_freq1 = 5000 + center_segment0 * 5; 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; break;
case VHT_CHANWIDTH_160MHZ: case VHT_CHANWIDTH_160MHZ:
data->bandwidth = 160; data->bandwidth = 160;
@ -424,13 +443,21 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data,
return -1; return -1;
if (!sec_channel_offset) if (!sec_channel_offset)
return -1; return -1;
/* primary 40 part must match the HT configuration */ /*
tmp = (70 + freq - 5000 - center_segment0 * 5) / 20; * Note: HT/VHT config and params are coupled. Check if
tmp /= 2; * HT40 channel band is in VHT160 channel band configuration.
if (data->center_freq1 != 5000 + */
center_segment0 * 5 - 60 + 40 * tmp) 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; return -1;
data->center_freq1 = 5000 + center_segment0 * 5;
break; break;
} }

View file

@ -195,6 +195,11 @@ enum qca_wlan_vendor_attr_acs_offload {
QCA_WLAN_VENDOR_ATTR_ACS_HW_MODE, QCA_WLAN_VENDOR_ATTR_ACS_HW_MODE,
QCA_WLAN_VENDOR_ATTR_ACS_HT_ENABLED, QCA_WLAN_VENDOR_ATTR_ACS_HT_ENABLED,
QCA_WLAN_VENDOR_ATTR_ACS_HT40_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 */ /* keep last */
QCA_WLAN_VENDOR_ATTR_ACS_AFTER_LAST, QCA_WLAN_VENDOR_ATTR_ACS_AFTER_LAST,
QCA_WLAN_VENDOR_ATTR_ACS_MAX = QCA_WLAN_VENDOR_ATTR_ACS_MAX =

View file

@ -1588,6 +1588,16 @@ struct drv_acs_params {
/* Indicates whether HT40 is enabled */ /* Indicates whether HT40 is enabled */
int ht40_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 * struct acs_selected_channels - Data for EVENT_ACS_CHANNEL_SELECTED
* @pri_channel: Selected primary channel * @pri_channel: Selected primary channel
* @sec_channel: Selected secondary 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 { struct acs_selected_channels {
u8 pri_channel; u8 pri_channel;
u8 sec_channel; u8 sec_channel;
u8 vht_seg0_center_ch;
u8 vht_seg1_center_ch;
u16 ch_width;
} acs_selected_channels; } acs_selected_channels;
}; };

View file

@ -8382,12 +8382,24 @@ static int wpa_driver_do_acs(void *priv, struct drv_acs_params *params)
(params->ht_enabled && (params->ht_enabled &&
nla_put_flag(msg, QCA_WLAN_VENDOR_ATTR_ACS_HT_ENABLED)) || nla_put_flag(msg, QCA_WLAN_VENDOR_ATTR_ACS_HT_ENABLED)) ||
(params->ht40_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); nlmsg_free(msg);
return -ENOBUFS; return -ENOBUFS;
} }
nla_nest_end(msg, data); 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); ret = send_and_recv_msgs(drv, msg, NULL, NULL);
if (ret) { if (ret) {
wpa_printf(MSG_DEBUG, wpa_printf(MSG_DEBUG,

View file

@ -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]); nla_get_u8(tb[QCA_WLAN_VENDOR_ATTR_ACS_PRIMARY_CHANNEL]);
event.acs_selected_channels.sec_channel = event.acs_selected_channels.sec_channel =
nla_get_u8(tb[QCA_WLAN_VENDOR_ATTR_ACS_SECONDARY_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); wpa_supplicant_event(drv->ctx, EVENT_ACS_CHANNEL_SELECTED, &event);
} }