From 16689c7cfc99c66aecbf16eb2f4a8bc941cb5d0f Mon Sep 17 00:00:00 2001 From: Peng Xu Date: Tue, 18 Nov 2014 20:11:09 +0200 Subject: [PATCH] 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 --- src/ap/acs.c | 8 ++++ src/ap/ap_drv_ops.c | 15 +++++++ src/ap/ap_drv_ops.h | 1 + src/ap/drv_callbacks.c | 52 +++++++++++++++++++++++ src/common/qca-vendor.h | 25 +++++++++++ src/drivers/driver.h | 42 ++++++++++++++++++- src/drivers/driver_common.c | 1 + src/drivers/driver_nl80211.c | 66 ++++++++++++++++++++++++++++++ src/drivers/driver_nl80211_capa.c | 3 ++ src/drivers/driver_nl80211_event.c | 30 ++++++++++++++ 10 files changed, 242 insertions(+), 1 deletion(-) diff --git a/src/ap/acs.c b/src/ap/acs.c index b94b8a438..97cf26fbe 100644 --- a/src/ap/acs.c +++ b/src/ap/acs.c @@ -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); diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c index 262fdcd94..9ebcf8f2e 100644 --- a/src/ap/ap_drv_ops.c +++ b/src/ap/ap_drv_ops.c @@ -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(¶ms, 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, ¶ms); +} diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h index 65061c93f..7ad3ed77c 100644 --- a/src/ap/ap_drv_ops.h +++ b/src/ap/ap_drv_ops.h @@ -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" diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c index 94df01931..40a2a9c70 100644 --- a/src/ap/drv_callbacks.c +++ b/src/ap/drv_callbacks.c @@ -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; diff --git a/src/common/qca-vendor.h b/src/common/qca-vendor.h index 9c1863b7d..2f1bdcefc 100644 --- a/src/common/qca-vendor.h +++ b/src/common/qca-vendor.h @@ -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 */ diff --git a/src/drivers/driver.h b/src/drivers/driver.h index aaac0b156..2c6c4ec14 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -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; }; /** diff --git a/src/drivers/driver_common.c b/src/drivers/driver_common.c index e0a7ebbfe..f897c1147 100644 --- a/src/drivers/driver_common.c +++ b/src/drivers/driver_common.c @@ -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"; diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index ce5639ace..fccae1177 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -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, }; diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c index 9ee49e16d..99c0027c0 100644 --- a/src/drivers/driver_nl80211_capa.c +++ b/src/drivers/driver_nl80211_capa.c @@ -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", diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c index bb19a203f..9835e2871 100644 --- a/src/drivers/driver_nl80211_event.c +++ b/src/drivers/driver_nl80211_event.c @@ -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",