hostapd: Allow ACS to be offloaded to the driver
authorPeng Xu <pxu@qca.qualcomm.com>
Tue, 18 Nov 2014 18:11:09 +0000 (20:11 +0200)
committerJouni Malinen <j@w1.fi>
Wed, 3 Dec 2014 20:31:53 +0000 (22:31 +0200)
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>
src/ap/acs.c
src/ap/ap_drv_ops.c
src/ap/ap_drv_ops.h
src/ap/drv_callbacks.c
src/common/qca-vendor.h
src/drivers/driver.h
src/drivers/driver_common.c
src/drivers/driver_nl80211.c
src/drivers/driver_nl80211_capa.c
src/drivers/driver_nl80211_event.c

index b94b8a4..97cf26f 100644 (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);
index 262fdcd..9ebcf8f 100644 (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);
+}
index 65061c9..7ad3ed7 100644 (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"
index 94df019..40a2a9c 100644 (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;
index 9c1863b..2f1bdce 100644 (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 */
index aaac0b1..2c6c4ec 100644 (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;
 };
 
 /**
index e0a7ebb..f897c11 100644 (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";
index ce5639a..fccae11 100644 (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,
 };
index 9ee49e1..99c0027 100644 (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",
index bb19a20..9835e28 100644 (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",