hostapd: Allow ACS to be offloaded to the driver

Using QCA vendor command, allow ACS function to be offloaded to the
driver. Once channels are selected, hostapd is notified to perform OBSS
operation.

Signed-off-by: Jouni Malinen <jouni@qca.qualcomm.com>
This commit is contained in:
Peng Xu 2014-11-18 20:11:09 +02:00 committed by Jouni Malinen
parent f34891a3af
commit 16689c7cfc
10 changed files with 242 additions and 1 deletions

View File

@ -816,6 +816,14 @@ enum hostapd_chan_status acs_init(struct hostapd_iface *iface)
wpa_printf(MSG_INFO, "ACS: Automatic channel selection started, this may take a bit");
if (iface->drv_flags & WPA_DRIVER_FLAGS_ACS_OFFLOAD) {
wpa_printf(MSG_INFO, "ACS: Offloading to driver");
err = hostapd_drv_do_acs(iface->bss[0]);
if (err)
return HOSTAPD_CHAN_INVALID;
return HOSTAPD_CHAN_ACS;
}
acs_cleanup(iface);
err = acs_request_scan(iface);

View File

@ -793,3 +793,18 @@ int hostapd_drv_set_qos_map(struct hostapd_data *hapd,
return hapd->driver->set_qos_map(hapd->drv_priv, qos_map_set,
qos_map_set_len);
}
int hostapd_drv_do_acs(struct hostapd_data *hapd)
{
struct drv_acs_params params;
if (hapd->driver == NULL || hapd->driver->do_acs == NULL)
return 0;
os_memset(&params, 0, sizeof(params));
params.hw_mode = hapd->iface->conf->hw_mode;
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, &params);
}

View File

@ -111,6 +111,7 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data, int mode,
int vht_enabled, int sec_channel_offset,
int vht_oper_chwidth, int center_segment0,
int center_segment1, u32 vht_caps);
int hostapd_drv_do_acs(struct hostapd_data *hapd);
#include "drivers/driver.h"

View File

@ -525,6 +525,51 @@ 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)
{
int channel;
int ret;
if (hapd->iconf->channel) {
wpa_printf(MSG_INFO, "ACS: Channel was already set to %d",
hapd->iconf->channel);
return;
}
hapd->iface->freq = hostapd_hw_get_freq(hapd, pri_channel);
channel = pri_channel;
if (!channel) {
hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211,
HOSTAPD_LEVEL_WARNING,
"driver switched to bad channel");
return;
}
hapd->iconf->channel = channel;
if (sec_channel == 0)
hapd->iconf->secondary_channel = 0;
else if (sec_channel < pri_channel)
hapd->iconf->secondary_channel = -1;
else if (sec_channel > pri_channel)
hapd->iconf->secondary_channel = 1;
else {
wpa_printf(MSG_ERROR, "Invalid secondary channel!");
return;
}
ret = hostapd_acs_completed(hapd->iface, 0);
if (ret) {
wpa_printf(MSG_ERROR,
"ACS: Possibly channel configuration is invalid");
}
}
#endif /* CONFIG_ACS */
int hostapd_probe_req_rx(struct hostapd_data *hapd, const u8 *sa, const u8 *da,
const u8 *bssid, const u8 *ie, size_t ie_len,
int ssi_signal)
@ -1169,6 +1214,13 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
case EVENT_INTERFACE_DISABLED:
wpa_msg(hapd->msg_ctx, MSG_INFO, INTERFACE_DISABLED);
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);
break;
#endif /* CONFIG_ACS */
default:
wpa_printf(MSG_DEBUG, "Unknown event %d", event);
break;

View File

@ -58,6 +58,10 @@ enum qca_radiotap_vendor_ids {
* NL80211_CMD_ROAM event with optional attributes including information
* from offloaded key management operation. Uses
* enum qca_wlan_vendor_attr_roam_auth attributes.
*
* @QCA_NL80211_VENDOR_SUBCMD_DO_ACS: ACS command/event which is used to
* invoke the ACS function in device and pass selected channels to
* hostapd.
*/
enum qca_nl80211_vendor_subcmds {
QCA_NL80211_VENDOR_SUBCMD_UNSPEC = 0,
@ -102,6 +106,7 @@ enum qca_nl80211_vendor_subcmds {
QCA_NL80211_VENDOR_SUBCMD_KEY_MGMT_ROAM_AUTH = 51,
QCA_NL80211_VENDOR_SUBCMD_APFIND = 52,
/* 53 - reserved for QCA */
QCA_NL80211_VENDOR_SUBCMD_DO_ACS = 54,
};
@ -145,4 +150,24 @@ enum qca_wlan_vendor_attr_roam_auth {
QCA_WLAN_VENDOR_ATTR_ROAM_AUTH_AFTER_LAST - 1
};
enum qca_wlan_vendor_attr_acs_offload {
QCA_WLAN_VENDOR_ATTR_ACS_CHANNEL_INVALID = 0,
QCA_WLAN_VENDOR_ATTR_ACS_PRIMARY_CHANNEL,
QCA_WLAN_VENDOR_ATTR_ACS_SECONDARY_CHANNEL,
QCA_WLAN_VENDOR_ATTR_ACS_HW_MODE,
QCA_WLAN_VENDOR_ATTR_ACS_HT_ENABLED,
QCA_WLAN_VENDOR_ATTR_ACS_HT40_ENABLED,
/* keep last */
QCA_WLAN_VENDOR_ATTR_ACS_AFTER_LAST,
QCA_WLAN_VENDOR_ATTR_ACS_MAX =
QCA_WLAN_VENDOR_ATTR_ACS_AFTER_LAST - 1
};
enum qca_wlan_vendor_acs_hw_mode {
QCA_ACS_MODE_IEEE80211B,
QCA_ACS_MODE_IEEE80211G,
QCA_ACS_MODE_IEEE80211A,
QCA_ACS_MODE_IEEE80211AD,
};
#endif /* QCA_VENDOR_H */

View File

@ -1073,6 +1073,8 @@ struct wpa_driver_capa {
#define WPA_DRIVER_FLAGS_AP_CSA 0x80000000
/* Driver supports mesh */
#define WPA_DRIVER_FLAGS_MESH 0x0000000100000000ULL
/* Driver support ACS offload */
#define WPA_DRIVER_FLAGS_ACS_OFFLOAD 0x0000000200000000ULL
u64 flags;
#define WPA_DRIVER_SMPS_MODE_STATIC 0x00000001
@ -1425,6 +1427,17 @@ enum drv_br_net_param {
DRV_BR_NET_PARAM_GARP_ACCEPT,
};
struct drv_acs_params {
/* Selected mode (HOSTAPD_MODE_*) */
enum hostapd_hw_mode hw_mode;
/* Indicates whether HT is enabled */
int ht_enabled;
/* Indicates whether HT40 is enabled */
int ht40_enabled;
};
/**
* struct wpa_driver_ops - Driver interface API definition
@ -3213,6 +3226,17 @@ struct wpa_driver_ops {
* Returns 0 on success, -1 on failure
*/
int (*leave_mesh)(void *priv);
/**
* do_acs - Automatically select channel
* @priv: Private driver interface data
* @params: Parameters for ACS
* Returns 0 on success, -1 on failure
*
* This command can be used to offload ACS to the driver if the driver
* indicates support for such offloading (WPA_DRIVER_FLAGS_ACS_OFFLOAD).
*/
int (*do_acs)(void *priv, struct drv_acs_params *params);
};
@ -3681,8 +3705,15 @@ enum wpa_event_type {
/**
* EVENT_NEW_PEER_CANDIDATE - new (unknown) mesh peer notification
*/
EVENT_NEW_PEER_CANDIDATE
EVENT_NEW_PEER_CANDIDATE,
/**
* EVENT_ACS_CHANNEL_SELECTED - Received selected channels by ACS
*
* Indicates a pair of primary and secondary channels chosen by ACS
* in device.
*/
EVENT_ACS_CHANNEL_SELECTED,
};
@ -4370,6 +4401,15 @@ union wpa_event_data {
size_t ie_len;
} mesh_peer;
/**
* struct acs_selected_channels - Data for EVENT_ACS_CHANNEL_SELECTED
* @pri_channel: Selected primary channel
* @sec_channel: Selected secondary channel
*/
struct acs_selected_channels {
u8 pri_channel;
u8 sec_channel;
} acs_selected_channels;
};
/**

View File

@ -78,6 +78,7 @@ const char * event_to_string(enum wpa_event_type event)
E2S(SCAN_STARTED);
E2S(AVOID_FREQUENCIES);
E2S(NEW_PEER_CANDIDATE);
E2S(ACS_CHANNEL_SELECTED);
}
return "UNKNOWN";

View File

@ -9249,6 +9249,71 @@ static int wpa_driver_br_set_net_param(void *priv, enum drv_br_net_param param,
}
static int hw_mode_to_qca_acs(enum hostapd_hw_mode hw_mode)
{
switch (hw_mode) {
case HOSTAPD_MODE_IEEE80211B:
return QCA_ACS_MODE_IEEE80211B;
case HOSTAPD_MODE_IEEE80211G:
return QCA_ACS_MODE_IEEE80211G;
case HOSTAPD_MODE_IEEE80211A:
return QCA_ACS_MODE_IEEE80211A;
case HOSTAPD_MODE_IEEE80211AD:
return QCA_ACS_MODE_IEEE80211AD;
default:
return -1;
}
}
static int wpa_driver_do_acs(void *priv, struct drv_acs_params *params)
{
struct i802_bss *bss = priv;
struct wpa_driver_nl80211_data *drv = bss->drv;
struct nl_msg *msg;
struct nlattr *data;
int ret = -ENOBUFS;
int mode;
mode = hw_mode_to_qca_acs(params->hw_mode);
if (mode < 0)
return -1;
msg = nlmsg_alloc();
if (!msg)
return -1;
nl80211_cmd(drv, msg, 0, NL80211_CMD_VENDOR);
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->ifindex);
NLA_PUT_U32(msg, NL80211_ATTR_VENDOR_ID, OUI_QCA);
NLA_PUT_U32(msg, NL80211_ATTR_VENDOR_SUBCMD,
QCA_NL80211_VENDOR_SUBCMD_DO_ACS);
data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA);
if (!data)
goto nla_put_failure;
NLA_PUT_U8(msg, QCA_WLAN_VENDOR_ATTR_ACS_HW_MODE, mode);
if (params->ht_enabled)
NLA_PUT_FLAG(msg, QCA_WLAN_VENDOR_ATTR_ACS_HT_ENABLED);
if (params->ht40_enabled)
NLA_PUT_FLAG(msg, QCA_WLAN_VENDOR_ATTR_ACS_HT40_ENABLED);
nla_nest_end(msg, data);
ret = send_and_recv_msgs(drv, msg, NULL, NULL);
msg = NULL;
if (ret) {
wpa_printf(MSG_DEBUG,
"nl80211: Failed to invoke driver ACS function: %s",
strerror(errno));
}
nla_put_failure:
nlmsg_free(msg);
return ret;
}
const struct wpa_driver_ops wpa_driver_nl80211_ops = {
.name = "nl80211",
.desc = "Linux nl80211/cfg80211",
@ -9353,4 +9418,5 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = {
.br_set_net_param = wpa_driver_br_set_net_param,
.add_tx_ts = nl80211_add_ts,
.del_tx_ts = nl80211_del_ts,
.do_acs = wpa_driver_do_acs,
};

View File

@ -536,6 +536,9 @@ static int wiphy_info_handler(struct nl_msg *msg, void *arg)
case QCA_NL80211_VENDOR_SUBCMD_KEY_MGMT_SET_KEY:
drv->key_mgmt_set_key_vendor_cmd_avail = 1;
break;
case QCA_NL80211_VENDOR_SUBCMD_DO_ACS:
drv->capa.flags |= WPA_DRIVER_FLAGS_ACS_OFFLOAD;
break;
}
wpa_printf(MSG_DEBUG, "nl80211: Supported vendor command: vendor_id=0x%x subcmd=%u",

View File

@ -1469,6 +1469,33 @@ static void qca_nl80211_avoid_freq(struct wpa_driver_nl80211_data *drv,
}
static void qca_nl80211_acs_select_ch(struct wpa_driver_nl80211_data *drv,
const u8 *data, size_t len)
{
struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_ACS_MAX + 1];
union wpa_event_data event;
wpa_printf(MSG_DEBUG,
"nl80211: ACS channel selection vendor event received");
if (nla_parse(tb, QCA_WLAN_VENDOR_ATTR_ACS_MAX,
(struct nlattr *) data, len, NULL))
return;
if (!tb[QCA_WLAN_VENDOR_ATTR_ACS_PRIMARY_CHANNEL] ||
!tb[QCA_WLAN_VENDOR_ATTR_ACS_SECONDARY_CHANNEL])
return;
os_memset(&event, 0, sizeof(event));
event.acs_selected_channels.pri_channel =
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]);
wpa_supplicant_event(drv->ctx, EVENT_ACS_CHANNEL_SELECTED, &event);
}
static void qca_nl80211_key_mgmt_auth(struct wpa_driver_nl80211_data *drv,
const u8 *data, size_t len)
{
@ -1512,6 +1539,9 @@ static void nl80211_vendor_event_qca(struct wpa_driver_nl80211_data *drv,
case QCA_NL80211_VENDOR_SUBCMD_KEY_MGMT_ROAM_AUTH:
qca_nl80211_key_mgmt_auth(drv, data, len);
break;
case QCA_NL80211_VENDOR_SUBCMD_DO_ACS:
qca_nl80211_acs_select_ch(drv, data, len);
break;
default:
wpa_printf(MSG_DEBUG,
"nl80211: Ignore unsupported QCA vendor event %u",