nl80211: Support vendor scan together with normal scan
[mech_eap.git] / src / drivers / driver_nl80211_event.c
index a5b3230..5f2811c 100644 (file)
@@ -271,6 +271,7 @@ static void mlme_event_connect(struct wpa_driver_nl80211_data *drv,
                               struct nlattr *ptk_kek)
 {
        union wpa_event_data event;
+       const u8 *ssid;
        u16 status_code;
 
        if (drv->capa.flags & WPA_DRIVER_FLAGS_SME) {
@@ -331,6 +332,16 @@ static void mlme_event_connect(struct wpa_driver_nl80211_data *drv,
        if (req_ie) {
                event.assoc_info.req_ies = nla_data(req_ie);
                event.assoc_info.req_ies_len = nla_len(req_ie);
+
+               if (cmd == NL80211_CMD_ROAM) {
+                       ssid = nl80211_get_ie(event.assoc_info.req_ies,
+                                             event.assoc_info.req_ies_len,
+                                             WLAN_EID_SSID);
+                       if (ssid && ssid[1] > 0 && ssid[1] <= 32) {
+                               drv->ssid_len = ssid[1];
+                               os_memcpy(drv->ssid, ssid + 2, ssid[1]);
+                       }
+               }
        }
        if (resp_ie) {
                event.assoc_info.resp_ies = nla_data(resp_ie);
@@ -957,7 +968,7 @@ static void mlme_event_ft_event(struct wpa_driver_nl80211_data *drv,
 
 
 static void send_scan_event(struct wpa_driver_nl80211_data *drv, int aborted,
-                           struct nlattr *tb[])
+                           struct nlattr *tb[], int external_scan)
 {
        union wpa_event_data event;
        struct nlattr *nl;
@@ -967,7 +978,7 @@ static void send_scan_event(struct wpa_driver_nl80211_data *drv, int aborted,
        int freqs[MAX_REPORT_FREQS];
        int num_freqs = 0;
 
-       if (drv->scan_for_auth) {
+       if (!external_scan && drv->scan_for_auth) {
                drv->scan_for_auth = 0;
                wpa_printf(MSG_DEBUG, "nl80211: Scan results for missing "
                           "cfg80211 BSS entry");
@@ -978,6 +989,8 @@ static void send_scan_event(struct wpa_driver_nl80211_data *drv, int aborted,
        os_memset(&event, 0, sizeof(event));
        info = &event.scan_info;
        info->aborted = aborted;
+       info->external_scan = external_scan;
+       info->nl_scan_event = 1;
 
        if (tb[NL80211_ATTR_SCAN_SSIDS]) {
                nla_for_each_nested(nl, tb[NL80211_ATTR_SCAN_SSIDS], rem) {
@@ -1480,6 +1493,25 @@ static void qca_nl80211_avoid_freq(struct wpa_driver_nl80211_data *drv,
 }
 
 
+static enum hostapd_hw_mode get_qca_hw_mode(u8 hw_mode)
+{
+       switch (hw_mode) {
+       case QCA_ACS_MODE_IEEE80211B:
+               return HOSTAPD_MODE_IEEE80211B;
+       case QCA_ACS_MODE_IEEE80211G:
+               return HOSTAPD_MODE_IEEE80211G;
+       case QCA_ACS_MODE_IEEE80211A:
+               return HOSTAPD_MODE_IEEE80211A;
+       case QCA_ACS_MODE_IEEE80211AD:
+               return HOSTAPD_MODE_IEEE80211AD;
+       case QCA_ACS_MODE_IEEE80211ANY:
+               return HOSTAPD_MODE_IEEE80211ANY;
+       default:
+               return NUM_HOSTAPD_MODES;
+       }
+}
+
+
 static void qca_nl80211_acs_select_ch(struct wpa_driver_nl80211_data *drv,
                                   const u8 *data, size_t len)
 {
@@ -1509,14 +1541,28 @@ static void qca_nl80211_acs_select_ch(struct wpa_driver_nl80211_data *drv,
        if (tb[QCA_WLAN_VENDOR_ATTR_ACS_CHWIDTH])
                event.acs_selected_channels.ch_width =
                        nla_get_u16(tb[QCA_WLAN_VENDOR_ATTR_ACS_CHWIDTH]);
+       if (tb[QCA_WLAN_VENDOR_ATTR_ACS_HW_MODE]) {
+               u8 hw_mode = nla_get_u8(tb[QCA_WLAN_VENDOR_ATTR_ACS_HW_MODE]);
+
+               event.acs_selected_channels.hw_mode = get_qca_hw_mode(hw_mode);
+               if (event.acs_selected_channels.hw_mode == NUM_HOSTAPD_MODES ||
+                   event.acs_selected_channels.hw_mode ==
+                   HOSTAPD_MODE_IEEE80211ANY) {
+                       wpa_printf(MSG_DEBUG,
+                                  "nl80211: Invalid hw_mode %d in ACS selection event",
+                                  hw_mode);
+                       return;
+               }
+       }
 
        wpa_printf(MSG_INFO,
-                  "nl80211: ACS Results: PCH: %d SCH: %d BW: %d VHT0: %d VHT1: %d",
+                  "nl80211: ACS Results: PCH: %d SCH: %d BW: %d VHT0: %d VHT1: %d HW_MODE: %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);
+                  event.acs_selected_channels.vht_seg1_center_ch,
+                  event.acs_selected_channels.hw_mode);
 
        /* Ignore ACS channel list check for backwards compatibility */
 
@@ -1642,6 +1688,138 @@ static void qca_nl80211_dfs_offload_radar_event(
 }
 
 
+static void qca_nl80211_scan_trigger_event(struct wpa_driver_nl80211_data *drv,
+                                          u8 *data, size_t len)
+{
+       struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_SCAN_MAX + 1];
+       u64 cookie = 0;
+       union wpa_event_data event;
+       struct scan_info *info;
+
+       if (nla_parse(tb, QCA_WLAN_VENDOR_ATTR_SCAN_MAX,
+                     (struct nlattr *) data, len, NULL) ||
+           !tb[QCA_WLAN_VENDOR_ATTR_SCAN_COOKIE])
+               return;
+
+       cookie = nla_get_u64(tb[QCA_WLAN_VENDOR_ATTR_SCAN_COOKIE]);
+       if (cookie != drv->vendor_scan_cookie) {
+               /* External scan trigger event, ignore */
+               return;
+       }
+
+       /* Cookie match, own scan */
+       os_memset(&event, 0, sizeof(event));
+       info = &event.scan_info;
+       info->external_scan = 0;
+       info->nl_scan_event = 0;
+
+       drv->scan_state = SCAN_STARTED;
+       wpa_supplicant_event(drv->ctx, EVENT_SCAN_STARTED, &event);
+}
+
+
+static void send_vendor_scan_event(struct wpa_driver_nl80211_data *drv,
+                                  int aborted, struct nlattr *tb[],
+                                  int external_scan)
+{
+       union wpa_event_data event;
+       struct nlattr *nl;
+       int rem;
+       struct scan_info *info;
+       int freqs[MAX_REPORT_FREQS];
+       int num_freqs = 0;
+
+       os_memset(&event, 0, sizeof(event));
+       info = &event.scan_info;
+       info->aborted = aborted;
+       info->external_scan = external_scan;
+
+       if (tb[QCA_WLAN_VENDOR_ATTR_SCAN_SSIDS]) {
+               nla_for_each_nested(nl,
+                                   tb[QCA_WLAN_VENDOR_ATTR_SCAN_SSIDS], rem) {
+                       struct wpa_driver_scan_ssid *s =
+                               &info->ssids[info->num_ssids];
+                       s->ssid = nla_data(nl);
+                       s->ssid_len = nla_len(nl);
+                       wpa_printf(MSG_DEBUG,
+                                  "nl80211: Scan probed for SSID '%s'",
+                                  wpa_ssid_txt(s->ssid, s->ssid_len));
+                       info->num_ssids++;
+                       if (info->num_ssids == WPAS_MAX_SCAN_SSIDS)
+                               break;
+               }
+       }
+
+       if (tb[QCA_WLAN_VENDOR_ATTR_SCAN_FREQUENCIES]) {
+               char msg[200], *pos, *end;
+               int res;
+
+               pos = msg;
+               end = pos + sizeof(msg);
+               *pos = '\0';
+
+               nla_for_each_nested(nl,
+                                   tb[QCA_WLAN_VENDOR_ATTR_SCAN_FREQUENCIES],
+                                   rem) {
+                       freqs[num_freqs] = nla_get_u32(nl);
+                       res = os_snprintf(pos, end - pos, " %d",
+                                         freqs[num_freqs]);
+                       if (!os_snprintf_error(end - pos, res))
+                               pos += res;
+                       num_freqs++;
+                       if (num_freqs == MAX_REPORT_FREQS - 1)
+                               break;
+               }
+
+               info->freqs = freqs;
+               info->num_freqs = num_freqs;
+               wpa_printf(MSG_DEBUG, "nl80211: Scan included frequencies:%s",
+                          msg);
+       }
+       wpa_supplicant_event(drv->ctx, EVENT_SCAN_RESULTS, &event);
+}
+
+
+static void qca_nl80211_scan_done_event(struct wpa_driver_nl80211_data *drv,
+                                       u8 *data, size_t len)
+{
+       struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_SCAN_MAX + 1];
+       u64 cookie = 0;
+       enum scan_status status;
+       int external_scan;
+
+       if (nla_parse(tb, QCA_WLAN_VENDOR_ATTR_SCAN_MAX,
+                     (struct nlattr *) data, len, NULL) ||
+           !tb[QCA_WLAN_VENDOR_ATTR_SCAN_STATUS] ||
+           !tb[QCA_WLAN_VENDOR_ATTR_SCAN_COOKIE])
+               return;
+
+       status = nla_get_u8(tb[QCA_WLAN_VENDOR_ATTR_SCAN_STATUS]);
+       if (status >= VENDOR_SCAN_STATUS_MAX)
+               return; /* invalid status */
+
+       cookie = nla_get_u64(tb[QCA_WLAN_VENDOR_ATTR_SCAN_COOKIE]);
+       if (cookie != drv->vendor_scan_cookie) {
+               /* Event from an external scan, get scan results */
+               external_scan = 1;
+       } else {
+               external_scan = 0;
+               if (status == VENDOR_SCAN_STATUS_NEW_RESULTS)
+                       drv->scan_state = SCAN_COMPLETED;
+               else
+                       drv->scan_state = SCAN_ABORTED;
+
+               eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout, drv,
+                                    drv->ctx);
+               drv->vendor_scan_cookie = 0;
+               drv->last_scan_cmd = 0;
+       }
+
+       send_vendor_scan_event(drv, (status == VENDOR_SCAN_STATUS_ABORTED), tb,
+                              external_scan);
+}
+
+
 static void nl80211_vendor_event_qca(struct wpa_driver_nl80211_data *drv,
                                     u32 subcmd, u8 *data, size_t len)
 {
@@ -1665,6 +1843,12 @@ static void nl80211_vendor_event_qca(struct wpa_driver_nl80211_data *drv,
        case QCA_NL80211_VENDOR_SUBCMD_DFS_OFFLOAD_RADAR_DETECTED:
                qca_nl80211_dfs_offload_radar_event(drv, subcmd, data, len);
                break;
+       case QCA_NL80211_VENDOR_SUBCMD_TRIGGER_SCAN:
+               qca_nl80211_scan_trigger_event(drv, data, len);
+               break;
+       case QCA_NL80211_VENDOR_SUBCMD_SCAN_DONE:
+               qca_nl80211_scan_done_event(drv, data, len);
+               break;
        default:
                wpa_printf(MSG_DEBUG,
                           "nl80211: Ignore unsupported QCA vendor event %u",
@@ -1787,6 +1971,7 @@ static void do_process_drv_event(struct i802_bss *bss, int cmd,
 {
        struct wpa_driver_nl80211_data *drv = bss->drv;
        union wpa_event_data data;
+       int external_scan_event = 0;
 
        wpa_printf(MSG_DEBUG, "nl80211: Drv Event %d (%s) received for %s",
                   cmd, nl80211_command_to_string(cmd), bss->ifname);
@@ -1839,28 +2024,38 @@ static void do_process_drv_event(struct i802_bss *bss, int cmd,
        case NL80211_CMD_NEW_SCAN_RESULTS:
                wpa_dbg(drv->ctx, MSG_DEBUG,
                        "nl80211: New scan results available");
-               drv->scan_state = SCAN_COMPLETED;
                drv->scan_complete_events = 1;
-               eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout, drv,
-                                    drv->ctx);
-               send_scan_event(drv, 0, tb);
+               if (drv->last_scan_cmd == NL80211_CMD_TRIGGER_SCAN) {
+                       drv->scan_state = SCAN_COMPLETED;
+                       eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout,
+                                            drv, drv->ctx);
+                       drv->last_scan_cmd = 0;
+               } else {
+                       external_scan_event = 1;
+               }
+               send_scan_event(drv, 0, tb, external_scan_event);
                break;
        case NL80211_CMD_SCHED_SCAN_RESULTS:
                wpa_dbg(drv->ctx, MSG_DEBUG,
                        "nl80211: New sched scan results available");
                drv->scan_state = SCHED_SCAN_RESULTS;
-               send_scan_event(drv, 0, tb);
+               send_scan_event(drv, 0, tb, 0);
                break;
        case NL80211_CMD_SCAN_ABORTED:
                wpa_dbg(drv->ctx, MSG_DEBUG, "nl80211: Scan aborted");
-               drv->scan_state = SCAN_ABORTED;
-               /*
-                * Need to indicate that scan results are available in order
-                * not to make wpa_supplicant stop its scanning.
-                */
-               eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout, drv,
-                                    drv->ctx);
-               send_scan_event(drv, 1, tb);
+               if (drv->last_scan_cmd == NL80211_CMD_TRIGGER_SCAN) {
+                       drv->scan_state = SCAN_ABORTED;
+                       /*
+                        * Need to indicate that scan results are available in
+                        * order not to make wpa_supplicant stop its scanning.
+                        */
+                       eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout,
+                                            drv, drv->ctx);
+                       drv->last_scan_cmd = 0;
+               } else {
+                       external_scan_event = 1;
+               }
+               send_scan_event(drv, 1, tb, external_scan_event);
                break;
        case NL80211_CMD_AUTHENTICATE:
        case NL80211_CMD_ASSOCIATE: