mesh: Implement mesh scanning
[mech_eap.git] / wpa_supplicant / scan.c
index a40c5b6..b95182e 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * WPA Supplicant - Scanning
- * Copyright (c) 2003-2012, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2003-2014, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -21,8 +21,8 @@
 #include "hs20_supplicant.h"
 #include "notify.h"
 #include "bss.h"
-#include "gas_query.h"
 #include "scan.h"
+#include "mesh.h"
 
 
 static void wpa_supplicant_gen_assoc_event(struct wpa_supplicant *wpa_s)
@@ -142,6 +142,53 @@ static void wpa_supplicant_assoc_try(struct wpa_supplicant *wpa_s,
 }
 
 
+static void wpas_trigger_scan_cb(struct wpa_radio_work *work, int deinit)
+{
+       struct wpa_supplicant *wpa_s = work->wpa_s;
+       struct wpa_driver_scan_params *params = work->ctx;
+       int ret;
+
+       if (deinit) {
+               if (!work->started) {
+                       wpa_scan_free_params(params);
+                       return;
+               }
+               wpa_supplicant_notify_scanning(wpa_s, 0);
+               wpas_notify_scan_done(wpa_s, 0);
+               wpa_s->scan_work = NULL;
+               return;
+       }
+
+       if (wpas_update_random_addr_disassoc(wpa_s) < 0) {
+               wpa_msg(wpa_s, MSG_INFO,
+                       "Failed to assign random MAC address for a scan");
+               radio_work_done(work);
+               return;
+       }
+
+       wpa_supplicant_notify_scanning(wpa_s, 1);
+
+       if (wpa_s->clear_driver_scan_cache)
+               params->only_new_results = 1;
+       ret = wpa_drv_scan(wpa_s, params);
+       wpa_scan_free_params(params);
+       work->ctx = NULL;
+       if (ret) {
+               wpa_supplicant_notify_scanning(wpa_s, 0);
+               wpas_notify_scan_done(wpa_s, 0);
+               radio_work_done(work);
+               return;
+       }
+
+       os_get_reltime(&wpa_s->scan_trigger_time);
+       wpa_s->scan_runs++;
+       wpa_s->normal_scans++;
+       wpa_s->own_scan_requested = 1;
+       wpa_s->clear_driver_scan_cache = 0;
+       wpa_s->scan_work = work;
+}
+
+
 /**
  * wpa_supplicant_trigger_scan - Request driver to start a scan
  * @wpa_s: Pointer to wpa_supplicant data
@@ -151,22 +198,24 @@ static void wpa_supplicant_assoc_try(struct wpa_supplicant *wpa_s,
 int wpa_supplicant_trigger_scan(struct wpa_supplicant *wpa_s,
                                struct wpa_driver_scan_params *params)
 {
-       int ret;
+       struct wpa_driver_scan_params *ctx;
 
-       wpa_supplicant_notify_scanning(wpa_s, 1);
+       if (wpa_s->scan_work) {
+               wpa_dbg(wpa_s, MSG_INFO, "Reject scan trigger since one is already pending");
+               return -1;
+       }
 
-       ret = wpa_drv_scan(wpa_s, params);
-       if (ret) {
-               wpa_supplicant_notify_scanning(wpa_s, 0);
-               wpas_notify_scan_done(wpa_s, 0);
-       } else {
-               os_get_reltime(&wpa_s->scan_trigger_time);
-               wpa_s->scan_runs++;
-               wpa_s->normal_scans++;
-               wpa_s->own_scan_requested = 1;
+       ctx = wpa_scan_clone_params(params);
+       if (ctx == NULL)
+               return -1;
+
+       if (radio_add_work(wpa_s, 0, "scan", 0, wpas_trigger_scan_cb, ctx) < 0)
+       {
+               wpa_scan_free_params(ctx);
+               return -1;
        }
 
-       return ret;
+       return 0;
 }
 
 
@@ -285,6 +334,32 @@ static void wpa_supplicant_optimize_freqs(
                }
                wpa_s->p2p_in_provisioning++;
        }
+
+       if (params->freqs == NULL && wpa_s->p2p_in_invitation) {
+               /*
+                * Optimize scan based on GO information during persistent
+                * group reinvocation
+                */
+               if (wpa_s->p2p_in_invitation < 5 &&
+                   wpa_s->p2p_invite_go_freq > 0) {
+                       wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Scan only GO preferred frequency %d MHz during invitation",
+                               wpa_s->p2p_invite_go_freq);
+                       params->freqs = os_zalloc(2 * sizeof(int));
+                       if (params->freqs)
+                               params->freqs[0] = wpa_s->p2p_invite_go_freq;
+               }
+               wpa_s->p2p_in_invitation++;
+               if (wpa_s->p2p_in_invitation > 20) {
+                       /*
+                        * This should not really happen since the variable is
+                        * cleared on group removal, but if it does happen, make
+                        * sure we do not get stuck in special invitation scan
+                        * mode.
+                        */
+                       wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Clear p2p_in_invitation");
+                       wpa_s->p2p_in_invitation = 0;
+               }
+       }
 #endif /* CONFIG_P2P */
 
 #ifdef CONFIG_WPS
@@ -299,7 +374,8 @@ static void wpa_supplicant_optimize_freqs(
                if (params->freqs)
                        params->freqs[0] = wpa_s->wps_freq;
                wpa_s->after_wps--;
-       }
+       } else if (wpa_s->after_wps)
+               wpa_s->after_wps--;
 
        if (params->freqs == NULL && wpa_s->known_wps_freq && wpa_s->wps_freq)
        {
@@ -323,11 +399,17 @@ static void wpas_add_interworking_elements(struct wpa_supplicant *wpa_s,
                return;
 
        wpabuf_put_u8(buf, WLAN_EID_EXT_CAPAB);
-       wpabuf_put_u8(buf, 4);
+       wpabuf_put_u8(buf, 6);
        wpabuf_put_u8(buf, 0x00);
        wpabuf_put_u8(buf, 0x00);
        wpabuf_put_u8(buf, 0x00);
        wpabuf_put_u8(buf, 0x80); /* Bit 31 - Interworking */
+       wpabuf_put_u8(buf, 0x00);
+#ifdef CONFIG_HS20
+       wpabuf_put_u8(buf, 0x40); /* Bit 46 - WNM-Notification */
+#else /* CONFIG_HS20 */
+       wpabuf_put_u8(buf, 0x00);
+#endif /* CONFIG_HS20 */
 
        wpabuf_put_u8(buf, WLAN_EID_INTERWORKING);
        wpabuf_put_u8(buf, is_zero_ether_addr(wpa_s->conf->hessid) ? 1 :
@@ -379,11 +461,13 @@ static struct wpabuf * wpa_supplicant_extra_ies(struct wpa_supplicant *wpa_s)
        }
 #endif /* CONFIG_P2P */
 
+       wpa_supplicant_mesh_add_scan_ie(wpa_s, &extra_ie);
+
 #endif /* CONFIG_WPS */
 
 #ifdef CONFIG_HS20
        if (wpa_s->conf->hs20 && wpabuf_resize(&extra_ie, 7) == 0)
-               wpas_hs20_add_indication(extra_ie);
+               wpas_hs20_add_indication(extra_ie, -1);
 #endif /* CONFIG_HS20 */
 
        return extra_ie;
@@ -474,6 +558,47 @@ static void wpa_setband_scan_freqs(struct wpa_supplicant *wpa_s,
 }
 
 
+static void wpa_set_scan_ssids(struct wpa_supplicant *wpa_s,
+                              struct wpa_driver_scan_params *params,
+                              size_t max_ssids)
+{
+       unsigned int i;
+       struct wpa_ssid *ssid;
+
+       for (i = 0; i < wpa_s->scan_id_count; i++) {
+               unsigned int j;
+
+               ssid = wpa_config_get_network(wpa_s->conf, wpa_s->scan_id[i]);
+               if (!ssid || !ssid->scan_ssid)
+                       continue;
+
+               for (j = 0; j < params->num_ssids; j++) {
+                       if (params->ssids[j].ssid_len == ssid->ssid_len &&
+                           params->ssids[j].ssid &&
+                           os_memcmp(params->ssids[j].ssid, ssid->ssid,
+                                     ssid->ssid_len) == 0)
+                               break;
+               }
+               if (j < params->num_ssids)
+                       continue; /* already in the list */
+
+               if (params->num_ssids + 1 > max_ssids) {
+                       wpa_printf(MSG_DEBUG,
+                                  "Over max scan SSIDs for manual request");
+                       break;
+               }
+
+               wpa_printf(MSG_DEBUG, "Scan SSID (manual request): %s",
+                          wpa_ssid_txt(ssid->ssid, ssid->ssid_len));
+               params->ssids[params->num_ssids].ssid = ssid->ssid;
+               params->ssids[params->num_ssids].ssid_len = ssid->ssid_len;
+               params->num_ssids++;
+       }
+
+       wpa_s->scan_id_count = 0;
+}
+
+
 static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
 {
        struct wpa_supplicant *wpa_s = eloop_ctx;
@@ -485,16 +610,19 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
        size_t max_ssids;
        enum wpa_states prev_state;
 
+       if (wpa_s->pno || wpa_s->pno_sched_pending) {
+               wpa_dbg(wpa_s, MSG_DEBUG, "Skip scan - PNO is in progress");
+               return;
+       }
+
        if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) {
                wpa_dbg(wpa_s, MSG_DEBUG, "Skip scan - interface disabled");
-               wpas_p2p_continue_after_scan(wpa_s);
                return;
        }
 
        if (wpa_s->disconnected && wpa_s->scan_req == NORMAL_SCAN_REQ) {
                wpa_dbg(wpa_s, MSG_DEBUG, "Disconnected - do not scan");
                wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED);
-               wpas_p2p_continue_after_scan(wpa_s);
                return;
        }
 
@@ -512,7 +640,6 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
            wpa_s->scan_req == NORMAL_SCAN_REQ) {
                wpa_dbg(wpa_s, MSG_DEBUG, "No enabled networks - do not scan");
                wpa_supplicant_set_state(wpa_s, WPA_INACTIVE);
-               wpas_p2p_continue_after_scan(wpa_s);
                return;
        }
 
@@ -529,30 +656,11 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
                return;
        }
 
-#ifdef CONFIG_P2P
-       if (wpas_p2p_in_progress(wpa_s) || wpas_wpa_is_in_progress(wpa_s, 0)) {
-               if (wpa_s->sta_scan_pending &&
-                   wpas_p2p_in_progress(wpa_s) == 2 &&
-                   wpa_s->global->p2p_cb_on_scan_complete) {
-                       wpa_dbg(wpa_s, MSG_DEBUG, "Process pending station "
-                               "mode scan during P2P search");
-               } else {
-                       wpa_dbg(wpa_s, MSG_DEBUG, "Delay station mode scan "
-                               "while P2P operation is in progress");
-                       wpa_s->sta_scan_pending = 1;
-                       wpa_supplicant_req_scan(wpa_s, 5, 0);
-                       return;
-               }
-       }
-#endif /* CONFIG_P2P */
-
-#ifdef CONFIG_GAS
-       if (gas_query_in_progress(wpa_s->gas)) {
-               wpa_dbg(wpa_s, MSG_DEBUG, "Delay scan while GAS query is in progress");
-               wpa_supplicant_req_scan(wpa_s, 1, 0);
+       if (wpas_p2p_in_progress(wpa_s)) {
+               wpa_dbg(wpa_s, MSG_DEBUG, "Delay station mode scan while P2P operation is in progress");
+               wpa_supplicant_req_scan(wpa_s, 5, 0);
                return;
        }
-#endif /* CONFIG_GAS */
 
        if (wpa_s->conf->ap_scan == 2)
                max_ssids = 1;
@@ -606,6 +714,19 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
                params.num_ssids = 1;
                goto ssid_list_set;
        }
+
+       if (wpa_s->p2p_in_invitation) {
+               if (wpa_s->current_ssid) {
+                       wpa_printf(MSG_DEBUG, "P2P: Use specific SSID for scan during invitation");
+                       params.ssids[0].ssid = wpa_s->current_ssid->ssid;
+                       params.ssids[0].ssid_len =
+                               wpa_s->current_ssid->ssid_len;
+                       params.num_ssids = 1;
+               } else {
+                       wpa_printf(MSG_DEBUG, "P2P: No specific SSID known for scan during invitation");
+               }
+               goto ssid_list_set;
+       }
 #endif /* CONFIG_P2P */
 
        /* Find the starting point from which to continue scanning */
@@ -632,6 +753,36 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
                 * wildcard SSID.
                 */
                ssid = NULL;
+       } else if (wpa_s->reattach && wpa_s->current_ssid != NULL) {
+               /*
+                * Perform single-channel single-SSID scan for
+                * reassociate-to-same-BSS operation.
+                */
+               /* Setup SSID */
+               ssid = wpa_s->current_ssid;
+               wpa_hexdump_ascii(MSG_DEBUG, "Scan SSID",
+                                 ssid->ssid, ssid->ssid_len);
+               params.ssids[0].ssid = ssid->ssid;
+               params.ssids[0].ssid_len = ssid->ssid_len;
+               params.num_ssids = 1;
+
+               /*
+                * Allocate memory for frequency array, allocate one extra
+                * slot for the zero-terminator.
+                */
+               params.freqs = os_malloc(sizeof(int) * 2);
+               if (params.freqs == NULL) {
+                       wpa_dbg(wpa_s, MSG_ERROR, "Memory allocation failed");
+                       return;
+               }
+               params.freqs[0] = wpa_s->assoc_freq;
+               params.freqs[1] = 0;
+
+               /*
+                * Reset the reattach flag so that we fall back to full scan if
+                * this scan fails.
+                */
+               wpa_s->reattach = 0;
        } else {
                struct wpa_ssid *start = ssid, *tssid;
                int freqs_set = 0;
@@ -658,6 +809,10 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
                                ssid = wpa_s->conf->ssid;
                }
 
+               if (wpa_s->scan_id_count &&
+                   wpa_s->last_scan_req == MANUAL_SCAN_REQ)
+                       wpa_set_scan_ssids(wpa_s, &params, max_ssids);
+
                for (tssid = wpa_s->conf->ssid; tssid; tssid = tssid->next) {
                        if (wpas_network_disabled(wpa_s, tssid))
                                continue;
@@ -715,6 +870,10 @@ ssid_list_set:
        wpa_supplicant_optimize_freqs(wpa_s, &params);
        extra_ie = wpa_supplicant_extra_ies(wpa_s);
 
+       if (wpa_s->last_scan_req == MANUAL_SCAN_REQ &&
+           wpa_s->manual_scan_only_new)
+               params.only_new_results = 1;
+
        if (wpa_s->last_scan_req == MANUAL_SCAN_REQ && params.freqs == NULL &&
            wpa_s->manual_scan_freqs) {
                wpa_dbg(wpa_s, MSG_DEBUG, "Limit manual scan to specified channels");
@@ -764,7 +923,7 @@ ssid_list_set:
        }
 
 #ifdef CONFIG_P2P
-       if (wpa_s->p2p_in_provisioning ||
+       if (wpa_s->p2p_in_provisioning || wpa_s->p2p_in_invitation ||
            (wpa_s->show_group_started && wpa_s->go_params)) {
                /*
                 * The interface may not yet be in P2P mode, so we have to
@@ -868,17 +1027,19 @@ void wpa_supplicant_update_scan_int(struct wpa_supplicant *wpa_s, int sec)
  */
 void wpa_supplicant_req_scan(struct wpa_supplicant *wpa_s, int sec, int usec)
 {
-       if (eloop_deplete_timeout(sec, usec, wpa_supplicant_scan, wpa_s, NULL))
-       {
-               wpa_dbg(wpa_s, MSG_DEBUG, "Rescheduling scan request: %d sec %d usec",
+       int res = eloop_deplete_timeout(sec, usec, wpa_supplicant_scan, wpa_s,
+                                       NULL);
+       if (res == 1) {
+               wpa_dbg(wpa_s, MSG_DEBUG, "Rescheduling scan request: %d.%06d sec",
                        sec, usec);
-               return;
+       } else if (res == 0) {
+               wpa_dbg(wpa_s, MSG_DEBUG, "Ignore new scan request for %d.%06d sec since an earlier request is scheduled to trigger sooner",
+                       sec, usec);
+       } else {
+               wpa_dbg(wpa_s, MSG_DEBUG, "Setting scan request: %d.%06d sec",
+                       sec, usec);
+               eloop_register_timeout(sec, usec, wpa_supplicant_scan, wpa_s, NULL);
        }
-
-       wpa_dbg(wpa_s, MSG_DEBUG, "Setting scan request: %d sec %d usec",
-               sec, usec);
-       eloop_cancel_timeout(wpa_supplicant_scan, wpa_s, NULL);
-       eloop_register_timeout(sec, usec, wpa_supplicant_scan, wpa_s, NULL);
 }
 
 
@@ -1090,6 +1251,16 @@ int wpa_supplicant_req_sched_scan(struct wpa_supplicant *wpa_s)
                params.extra_ies_len = wpabuf_len(extra_ie);
        }
 
+       if (wpa_s->conf->filter_rssi)
+               params.filter_rssi = wpa_s->conf->filter_rssi;
+
+       /* See if user specified frequencies. If so, scan only those. */
+       if (wpa_s->conf->freq_list && !params.freqs) {
+               wpa_dbg(wpa_s, MSG_DEBUG,
+                       "Optimize scan based on conf->freq_list");
+               int_array_concat(&params.freqs, wpa_s->conf->freq_list);
+       }
+
        scan_params = &params;
 
 scan:
@@ -1150,7 +1321,6 @@ void wpa_supplicant_cancel_scan(struct wpa_supplicant *wpa_s)
 {
        wpa_dbg(wpa_s, MSG_DEBUG, "Cancelling scan request");
        eloop_cancel_timeout(wpa_supplicant_scan, wpa_s, NULL);
-       wpas_p2p_continue_after_scan(wpa_s);
 }
 
 
@@ -1618,7 +1788,7 @@ wpa_supplicant_get_scan_results(struct wpa_supplicant *wpa_s,
        filter_scan_res(wpa_s, scan_res);
 
 #ifdef CONFIG_WPS
-       if (wpas_wps_in_progress(wpa_s)) {
+       if (wpas_wps_searching(wpa_s)) {
                wpa_dbg(wpa_s, MSG_DEBUG, "WPS: Order scan results with WPS "
                        "provisioning rules");
                compar = wpa_scan_result_wps_compar;
@@ -1680,6 +1850,11 @@ void scan_only_handler(struct wpa_supplicant *wpa_s,
        }
        wpas_notify_scan_results(wpa_s);
        wpas_notify_scan_done(wpa_s, 1);
+       if (wpa_s->scan_work) {
+               struct wpa_radio_work *work = wpa_s->scan_work;
+               wpa_s->scan_work = NULL;
+               radio_work_done(work);
+       }
 }
 
 
@@ -1687,3 +1862,213 @@ int wpas_scan_scheduled(struct wpa_supplicant *wpa_s)
 {
        return eloop_is_timeout_registered(wpa_supplicant_scan, wpa_s, NULL);
 }
+
+
+struct wpa_driver_scan_params *
+wpa_scan_clone_params(const struct wpa_driver_scan_params *src)
+{
+       struct wpa_driver_scan_params *params;
+       size_t i;
+       u8 *n;
+
+       params = os_zalloc(sizeof(*params));
+       if (params == NULL)
+               return NULL;
+
+       for (i = 0; i < src->num_ssids; i++) {
+               if (src->ssids[i].ssid) {
+                       n = os_malloc(src->ssids[i].ssid_len);
+                       if (n == NULL)
+                               goto failed;
+                       os_memcpy(n, src->ssids[i].ssid,
+                                 src->ssids[i].ssid_len);
+                       params->ssids[i].ssid = n;
+                       params->ssids[i].ssid_len = src->ssids[i].ssid_len;
+               }
+       }
+       params->num_ssids = src->num_ssids;
+
+       if (src->extra_ies) {
+               n = os_malloc(src->extra_ies_len);
+               if (n == NULL)
+                       goto failed;
+               os_memcpy(n, src->extra_ies, src->extra_ies_len);
+               params->extra_ies = n;
+               params->extra_ies_len = src->extra_ies_len;
+       }
+
+       if (src->freqs) {
+               int len = int_array_len(src->freqs);
+               params->freqs = os_malloc((len + 1) * sizeof(int));
+               if (params->freqs == NULL)
+                       goto failed;
+               os_memcpy(params->freqs, src->freqs, (len + 1) * sizeof(int));
+       }
+
+       if (src->filter_ssids) {
+               params->filter_ssids = os_malloc(sizeof(*params->filter_ssids) *
+                                                src->num_filter_ssids);
+               if (params->filter_ssids == NULL)
+                       goto failed;
+               os_memcpy(params->filter_ssids, src->filter_ssids,
+                         sizeof(*params->filter_ssids) *
+                         src->num_filter_ssids);
+               params->num_filter_ssids = src->num_filter_ssids;
+       }
+
+       params->filter_rssi = src->filter_rssi;
+       params->p2p_probe = src->p2p_probe;
+       params->only_new_results = src->only_new_results;
+       params->low_priority = src->low_priority;
+
+       return params;
+
+failed:
+       wpa_scan_free_params(params);
+       return NULL;
+}
+
+
+void wpa_scan_free_params(struct wpa_driver_scan_params *params)
+{
+       size_t i;
+
+       if (params == NULL)
+               return;
+
+       for (i = 0; i < params->num_ssids; i++)
+               os_free((u8 *) params->ssids[i].ssid);
+       os_free((u8 *) params->extra_ies);
+       os_free(params->freqs);
+       os_free(params->filter_ssids);
+       os_free(params);
+}
+
+
+int wpas_start_pno(struct wpa_supplicant *wpa_s)
+{
+       int ret, interval;
+       size_t i, num_ssid, num_match_ssid;
+       struct wpa_ssid *ssid;
+       struct wpa_driver_scan_params params;
+
+       if (!wpa_s->sched_scan_supported)
+               return -1;
+
+       if (wpa_s->pno || wpa_s->pno_sched_pending)
+               return 0;
+
+       if ((wpa_s->wpa_state > WPA_SCANNING) &&
+           (wpa_s->wpa_state <= WPA_COMPLETED)) {
+               wpa_printf(MSG_ERROR, "PNO: In assoc process");
+               return -EAGAIN;
+       }
+
+       if (wpa_s->wpa_state == WPA_SCANNING) {
+               wpa_supplicant_cancel_scan(wpa_s);
+               if (wpa_s->sched_scanning) {
+                       wpa_printf(MSG_DEBUG, "Schedule PNO on completion of "
+                                  "ongoing sched scan");
+                       wpa_supplicant_cancel_sched_scan(wpa_s);
+                       wpa_s->pno_sched_pending = 1;
+                       return 0;
+               }
+       }
+
+       os_memset(&params, 0, sizeof(params));
+
+       num_ssid = num_match_ssid = 0;
+       ssid = wpa_s->conf->ssid;
+       while (ssid) {
+               if (!wpas_network_disabled(wpa_s, ssid)) {
+                       num_match_ssid++;
+                       if (ssid->scan_ssid)
+                               num_ssid++;
+               }
+               ssid = ssid->next;
+       }
+
+       if (num_match_ssid == 0) {
+               wpa_printf(MSG_DEBUG, "PNO: No configured SSIDs");
+               return -1;
+       }
+
+       if (num_match_ssid > num_ssid) {
+               params.num_ssids++; /* wildcard */
+               num_ssid++;
+       }
+
+       if (num_ssid > WPAS_MAX_SCAN_SSIDS) {
+               wpa_printf(MSG_DEBUG, "PNO: Use only the first %u SSIDs from "
+                          "%u", WPAS_MAX_SCAN_SSIDS, (unsigned int) num_ssid);
+               num_ssid = WPAS_MAX_SCAN_SSIDS;
+       }
+
+       if (num_match_ssid > wpa_s->max_match_sets) {
+               num_match_ssid = wpa_s->max_match_sets;
+               wpa_dbg(wpa_s, MSG_DEBUG, "PNO: Too many SSIDs to match");
+       }
+       params.filter_ssids = os_calloc(num_match_ssid,
+                                       sizeof(struct wpa_driver_scan_filter));
+       if (params.filter_ssids == NULL)
+               return -1;
+       i = 0;
+       ssid = wpa_s->conf->ssid;
+       while (ssid) {
+               if (!wpas_network_disabled(wpa_s, ssid)) {
+                       if (ssid->scan_ssid && params.num_ssids < num_ssid) {
+                               params.ssids[params.num_ssids].ssid =
+                                       ssid->ssid;
+                               params.ssids[params.num_ssids].ssid_len =
+                                        ssid->ssid_len;
+                               params.num_ssids++;
+                       }
+                       os_memcpy(params.filter_ssids[i].ssid, ssid->ssid,
+                                 ssid->ssid_len);
+                       params.filter_ssids[i].ssid_len = ssid->ssid_len;
+                       params.num_filter_ssids++;
+                       i++;
+                       if (i == num_match_ssid)
+                               break;
+               }
+               ssid = ssid->next;
+       }
+
+       if (wpa_s->conf->filter_rssi)
+               params.filter_rssi = wpa_s->conf->filter_rssi;
+
+       interval = wpa_s->conf->sched_scan_interval ?
+               wpa_s->conf->sched_scan_interval : 10;
+
+       if (params.freqs == NULL && wpa_s->manual_sched_scan_freqs) {
+               wpa_dbg(wpa_s, MSG_DEBUG, "Limit sched scan to specified channels");
+               params.freqs = wpa_s->manual_sched_scan_freqs;
+       }
+
+       ret = wpa_supplicant_start_sched_scan(wpa_s, &params, interval);
+       os_free(params.filter_ssids);
+       if (ret == 0)
+               wpa_s->pno = 1;
+       else
+               wpa_msg(wpa_s, MSG_ERROR, "Failed to schedule PNO");
+       return ret;
+}
+
+
+int wpas_stop_pno(struct wpa_supplicant *wpa_s)
+{
+       int ret = 0;
+
+       if (!wpa_s->pno)
+               return 0;
+
+       ret = wpa_supplicant_stop_sched_scan(wpa_s);
+
+       wpa_s->pno = 0;
+       wpa_s->pno_sched_pending = 0;
+
+       if (wpa_s->wpa_state == WPA_SCANNING)
+               wpa_supplicant_req_scan(wpa_s, 0, 0);
+
+       return ret;
+}