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",