Update to new version 0.8.16 from BRCM

Sync with main tree commit b8349523e460493fa0b4de36c689595109e45e91
Author: Neeraj Kumar Garg <neerajkg@broadcom.com>
Date:   Tue Dec 27 23:21:45 2011 +0200
    P2P: Reject p2p_group_add if forced frequency is not acceptable

Change-Id: Icb4541a371b05c270e80440d7a7fdea7f33ff61e
Signed-off-by: Dmitry Shmidt <dimitrysh@google.com>
diff --git a/wpa_supplicant/scan.c b/wpa_supplicant/scan.c
index bb64e9a..e453cfc 100644
--- a/wpa_supplicant/scan.c
+++ b/wpa_supplicant/scan.c
@@ -20,7 +20,6 @@
 #include "config.h"
 #include "wpa_supplicant_i.h"
 #include "driver_i.h"
-#include "mlme.h"
 #include "wps_supplicant.h"
 #include "p2p_supplicant.h"
 #include "p2p/p2p.h"
@@ -52,13 +51,13 @@
 
 
 #ifdef CONFIG_WPS
-static int wpas_wps_in_use(struct wpa_config *conf,
+static int wpas_wps_in_use(struct wpa_supplicant *wpa_s,
 			   enum wps_request_type *req_type)
 {
 	struct wpa_ssid *ssid;
 	int wps = 0;
 
-	for (ssid = conf->ssid; ssid; ssid = ssid->next) {
+	for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) {
 		if (!(ssid->key_mgmt & WPA_KEY_MGMT_WPS))
 			continue;
 
@@ -71,6 +70,14 @@
 			return 2;
 	}
 
+#ifdef CONFIG_P2P
+	wpa_s->wps->dev.p2p = 1;
+	if (!wps) {
+		wps = 1;
+		*req_type = WPS_REQ_ENROLLEE_INFO;
+	}
+#endif /* CONFIG_P2P */
+
 	return wps;
 }
 #endif /* CONFIG_WPS */
@@ -192,16 +199,71 @@
 
 	wpa_supplicant_notify_scanning(wpa_s, 1);
 
-	if (wpa_s->drv_flags & WPA_DRIVER_FLAGS_USER_SPACE_MLME)
-		ret = ieee80211_sta_req_scan(wpa_s, params);
-	else
-		ret = wpa_drv_scan(wpa_s, params);
-
+	ret = wpa_drv_scan(wpa_s, params);
 	if (ret) {
 		wpa_supplicant_notify_scanning(wpa_s, 0);
 		wpas_notify_scan_done(wpa_s, 0);
-	} else
+	} else {
 		wpa_s->scan_runs++;
+		wpa_s->normal_scans++;
+	}
+
+	return ret;
+}
+
+
+static void
+wpa_supplicant_delayed_sched_scan_timeout(void *eloop_ctx, void *timeout_ctx)
+{
+	struct wpa_supplicant *wpa_s = eloop_ctx;
+
+	wpa_dbg(wpa_s, MSG_DEBUG, "Starting delayed sched scan");
+
+	if (wpa_supplicant_req_sched_scan(wpa_s))
+		wpa_supplicant_req_scan(wpa_s, 0, 0);
+}
+
+
+static void
+wpa_supplicant_sched_scan_timeout(void *eloop_ctx, void *timeout_ctx)
+{
+	struct wpa_supplicant *wpa_s = eloop_ctx;
+
+	wpa_dbg(wpa_s, MSG_DEBUG, "Sched scan timeout - stopping it");
+
+	wpa_s->sched_scan_timed_out = 1;
+	wpa_supplicant_cancel_sched_scan(wpa_s);
+}
+
+
+static int
+wpa_supplicant_start_sched_scan(struct wpa_supplicant *wpa_s,
+				struct wpa_driver_scan_params *params,
+				int interval)
+{
+	int ret;
+
+	wpa_supplicant_notify_scanning(wpa_s, 1);
+	ret = wpa_drv_sched_scan(wpa_s, params, interval * 1000);
+	if (ret)
+		wpa_supplicant_notify_scanning(wpa_s, 0);
+	else
+		wpa_s->sched_scanning = 1;
+
+	return ret;
+}
+
+
+static int wpa_supplicant_stop_sched_scan(struct wpa_supplicant *wpa_s)
+{
+	int ret;
+
+	ret = wpa_drv_stop_sched_scan(wpa_s);
+	if (ret) {
+		wpa_dbg(wpa_s, MSG_DEBUG, "stopping sched_scan failed!");
+		/* TODO: what to do if stopping fails? */
+		return -1;
+	}
 
 	return ret;
 }
@@ -240,16 +302,128 @@
 }
 
 
+static void wpa_supplicant_optimize_freqs(
+	struct wpa_supplicant *wpa_s, struct wpa_driver_scan_params *params)
+{
+#ifdef CONFIG_P2P
+	if (params->freqs == NULL && wpa_s->p2p_in_provisioning &&
+	    wpa_s->go_params) {
+		/* Optimize provisioning state scan based on GO information */
+		if (wpa_s->p2p_in_provisioning < 5 &&
+		    wpa_s->go_params->freq > 0) {
+			wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Scan only GO "
+				"preferred frequency %d MHz",
+				wpa_s->go_params->freq);
+			params->freqs = os_zalloc(2 * sizeof(int));
+			if (params->freqs)
+				params->freqs[0] = wpa_s->go_params->freq;
+		} else if (wpa_s->p2p_in_provisioning < 8 &&
+			   wpa_s->go_params->freq_list[0]) {
+			wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Scan only common "
+				"channels");
+			int_array_concat(&params->freqs,
+					 wpa_s->go_params->freq_list);
+			if (params->freqs)
+				int_array_sort_unique(params->freqs);
+		}
+		wpa_s->p2p_in_provisioning++;
+	}
+#endif /* CONFIG_P2P */
+
+#ifdef CONFIG_WPS
+	if (params->freqs == NULL && wpa_s->after_wps && wpa_s->wps_freq) {
+		/*
+		 * Optimize post-provisioning scan based on channel used
+		 * during provisioning.
+		 */
+		wpa_dbg(wpa_s, MSG_DEBUG, "WPS: Scan only frequency %u MHz "
+			"that was used during provisioning", wpa_s->wps_freq);
+		params->freqs = os_zalloc(2 * sizeof(int));
+		if (params->freqs)
+			params->freqs[0] = wpa_s->wps_freq;
+		wpa_s->after_wps--;
+	}
+
+#endif /* CONFIG_WPS */
+}
+
+
+#ifdef CONFIG_INTERWORKING
+static void wpas_add_interworking_elements(struct wpa_supplicant *wpa_s,
+					   struct wpabuf *buf)
+{
+	if (wpa_s->conf->interworking == 0)
+		return;
+
+	wpabuf_put_u8(buf, WLAN_EID_EXT_CAPAB);
+	wpabuf_put_u8(buf, 4);
+	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, WLAN_EID_INTERWORKING);
+	wpabuf_put_u8(buf, is_zero_ether_addr(wpa_s->conf->hessid) ? 1 :
+		      1 + ETH_ALEN);
+	wpabuf_put_u8(buf, wpa_s->conf->access_network_type);
+	/* No Venue Info */
+	if (!is_zero_ether_addr(wpa_s->conf->hessid))
+		wpabuf_put_data(buf, wpa_s->conf->hessid, ETH_ALEN);
+}
+#endif /* CONFIG_INTERWORKING */
+
+
+static struct wpabuf *
+wpa_supplicant_extra_ies(struct wpa_supplicant *wpa_s,
+			 struct wpa_driver_scan_params *params)
+{
+	struct wpabuf *extra_ie = NULL;
+#ifdef CONFIG_WPS
+	int wps = 0;
+	enum wps_request_type req_type = WPS_REQ_ENROLLEE_INFO;
+#endif /* CONFIG_WPS */
+
+#ifdef CONFIG_INTERWORKING
+	if (wpa_s->conf->interworking &&
+	    wpabuf_resize(&extra_ie, 100) == 0)
+		wpas_add_interworking_elements(wpa_s, extra_ie);
+#endif /* CONFIG_INTERWORKING */
+
+#ifdef CONFIG_WPS
+	wps = wpas_wps_in_use(wpa_s, &req_type);
+
+	if (wps) {
+		struct wpabuf *wps_ie;
+		wps_ie = wps_build_probe_req_ie(wps == 2, &wpa_s->wps->dev,
+						wpa_s->wps->uuid, req_type,
+						0, NULL);
+		if (wps_ie) {
+			if (wpabuf_resize(&extra_ie, wpabuf_len(wps_ie)) == 0)
+				wpabuf_put_buf(extra_ie, wps_ie);
+			wpabuf_free(wps_ie);
+		}
+	}
+
+#ifdef CONFIG_P2P
+	if (wps) {
+		size_t ielen = p2p_scan_ie_buf_len(wpa_s->global->p2p);
+		if (wpabuf_resize(&extra_ie, ielen) == 0)
+			wpas_p2p_scan_ie(wpa_s, extra_ie);
+	}
+#endif /* CONFIG_P2P */
+
+#endif /* CONFIG_WPS */
+
+	return extra_ie;
+}
+
+
 static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
 {
 	struct wpa_supplicant *wpa_s = eloop_ctx;
 	struct wpa_ssid *ssid;
 	int scan_req = 0, ret;
-	struct wpabuf *wps_ie = NULL;
-#ifdef CONFIG_WPS
-	int wps = 0;
-	enum wps_request_type req_type = WPS_REQ_ENROLLEE_INFO;
-#endif /* CONFIG_WPS */
+	struct wpabuf *extra_ie;
 	struct wpa_driver_scan_params params;
 	size_t max_ssids;
 	enum wpa_states prev_state;
@@ -284,8 +458,21 @@
 		return;
 	}
 
-	if ((wpa_s->drv_flags & WPA_DRIVER_FLAGS_USER_SPACE_MLME) ||
-	    wpa_s->conf->ap_scan == 2)
+#ifdef CONFIG_P2P
+	if (wpas_p2p_in_progress(wpa_s)) {
+		if (wpa_s->wpa_state == WPA_SCANNING) {
+			wpa_dbg(wpa_s, MSG_DEBUG, "Delay station mode scan "
+				"while P2P operation is in progress");
+			wpa_supplicant_req_scan(wpa_s, 5, 0);
+		} else {
+			wpa_dbg(wpa_s, MSG_DEBUG, "Do not request scan while "
+				"P2P operation is in progress");
+		}
+		return;
+	}
+#endif /* CONFIG_P2P */
+
+	if (wpa_s->conf->ap_scan == 2)
 		max_ssids = 1;
 	else {
 		max_ssids = wpa_s->max_scan_ssids;
@@ -293,10 +480,6 @@
 			max_ssids = WPAS_MAX_SCAN_SSIDS;
 	}
 
-#ifdef CONFIG_WPS
-	wps = wpas_wps_in_use(wpa_s->conf, &req_type);
-#endif /* CONFIG_WPS */
-
 	scan_req = wpa_s->scan_req;
 	wpa_s->scan_req = 0;
 
@@ -401,71 +584,8 @@
 			"SSID");
 	}
 
-#ifdef CONFIG_P2P
-	wpa_s->wps->dev.p2p = 1;
-	if (!wps) {
-		wps = 1;
-		req_type = WPS_REQ_ENROLLEE_INFO;
-	}
-
-	if (params.freqs == NULL && wpa_s->p2p_in_provisioning &&
-	    wpa_s->go_params) {
-		/* Optimize provisioning state scan based on GO information */
-		if (wpa_s->p2p_in_provisioning < 5 &&
-		    wpa_s->go_params->freq > 0) {
-			wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Scan only GO "
-				"preferred frequency %d MHz",
-				wpa_s->go_params->freq);
-			params.freqs = os_zalloc(2 * sizeof(int));
-			if (params.freqs)
-				params.freqs[0] = wpa_s->go_params->freq;
-		} else if (wpa_s->p2p_in_provisioning < 8 &&
-			   wpa_s->go_params->freq_list[0]) {
-			wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Scan only common "
-				"channels");
-			int_array_concat(&params.freqs,
-					 wpa_s->go_params->freq_list);
-			if (params.freqs)
-				int_array_sort_unique(params.freqs);
-		}
-		wpa_s->p2p_in_provisioning++;
-	}
-#endif /* CONFIG_P2P */
-
-#ifdef CONFIG_WPS
-	if (params.freqs == NULL && wpa_s->after_wps && wpa_s->wps_freq) {
-		/*
-		 * Optimize post-provisioning scan based on channel used
-		 * during provisioning.
-		 */
-		wpa_dbg(wpa_s, MSG_DEBUG, "WPS: Scan only frequency %u MHz "
-			"that was used during provisioning", wpa_s->wps_freq);
-		params.freqs = os_zalloc(2 * sizeof(int));
-		if (params.freqs)
-			params.freqs[0] = wpa_s->wps_freq;
-		wpa_s->after_wps--;
-	}
-
-	if (wps) {
-		wps_ie = wps_build_probe_req_ie(wps == 2, &wpa_s->wps->dev,
-						wpa_s->wps->uuid, req_type,
-						0, NULL);
-		if (wps_ie) {
-			params.extra_ies = wpabuf_head(wps_ie);
-			params.extra_ies_len = wpabuf_len(wps_ie);
-		}
-	}
-#endif /* CONFIG_WPS */
-
-#ifdef CONFIG_P2P
-	if (wps_ie) {
-		if (wpabuf_resize(&wps_ie, 100) == 0) {
-			wpas_p2p_scan_ie(wpa_s, wps_ie);
-			params.extra_ies = wpabuf_head(wps_ie);
-			params.extra_ies_len = wpabuf_len(wps_ie);
-		}
-	}
-#endif /* CONFIG_P2P */
+	wpa_supplicant_optimize_freqs(wpa_s, &params);
+	extra_ie = wpa_supplicant_extra_ies(wpa_s, &params);
 
 	if (params.freqs == NULL && wpa_s->next_scan_freqs) {
 		wpa_dbg(wpa_s, MSG_DEBUG, "Optimize scan based on previously "
@@ -477,10 +597,24 @@
 
 	params.filter_ssids = wpa_supplicant_build_filter_ssids(
 		wpa_s->conf, &params.num_filter_ssids);
+	if (extra_ie) {
+		params.extra_ies = wpabuf_head(extra_ie);
+		params.extra_ies_len = wpabuf_len(extra_ie);
+	}
+
+#ifdef CONFIG_P2P
+	if (wpa_s->p2p_in_provisioning) {
+		/*
+		 * The interface may not yet be in P2P mode, so we have to
+		 * explicitly request P2P probe to disable CCK rates.
+		 */
+		params.p2p_probe = 1;
+	}
+#endif /* CONFIG_P2P */
 
 	ret = wpa_supplicant_trigger_scan(wpa_s, &params);
 
-	wpabuf_free(wps_ie);
+	wpabuf_free(extra_ie);
 	os_free(params.freqs);
 	os_free(params.filter_ssids);
 
@@ -535,6 +669,215 @@
 
 
 /**
+ * wpa_supplicant_delayed_sched_scan - Request a delayed scheduled scan
+ * @wpa_s: Pointer to wpa_supplicant data
+ * @sec: Number of seconds after which to scan
+ * @usec: Number of microseconds after which to scan
+ *
+ * This function is used to schedule periodic scans for neighboring
+ * access points after the specified time.
+ */
+int wpa_supplicant_delayed_sched_scan(struct wpa_supplicant *wpa_s,
+				      int sec, int usec)
+{
+	if (!wpa_s->sched_scan_supported)
+		return -1;
+
+	eloop_register_timeout(sec, usec,
+			       wpa_supplicant_delayed_sched_scan_timeout,
+			       wpa_s, NULL);
+
+	return 0;
+}
+
+
+/**
+ * wpa_supplicant_req_sched_scan - Start a periodic scheduled scan
+ * @wpa_s: Pointer to wpa_supplicant data
+ *
+ * This function is used to schedule periodic scans for neighboring
+ * access points repeating the scan continuously.
+ */
+int wpa_supplicant_req_sched_scan(struct wpa_supplicant *wpa_s)
+{
+	struct wpa_driver_scan_params params;
+	enum wpa_states prev_state;
+	struct wpa_ssid *ssid;
+	struct wpabuf *wps_ie = NULL;
+	int ret;
+	unsigned int max_sched_scan_ssids;
+	int wildcard = 0;
+	int need_ssids;
+
+	if (!wpa_s->sched_scan_supported)
+		return -1;
+
+	if (wpa_s->max_sched_scan_ssids > WPAS_MAX_SCAN_SSIDS)
+		max_sched_scan_ssids = WPAS_MAX_SCAN_SSIDS;
+	else
+		max_sched_scan_ssids = wpa_s->max_sched_scan_ssids;
+	if (max_sched_scan_ssids < 1)
+		return -1;
+
+	if (wpa_s->sched_scanning) {
+		wpa_dbg(wpa_s, MSG_DEBUG, "Already sched scanning");
+		return 0;
+	}
+
+	need_ssids = 0;
+	for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) {
+		if (!ssid->disabled && !ssid->scan_ssid) {
+			/* Use wildcard SSID to find this network */
+			wildcard = 1;
+		} else if (!ssid->disabled && ssid->ssid_len)
+			need_ssids++;
+	}
+	if (wildcard)
+		need_ssids++;
+
+	if (wpa_s->normal_scans < 3 &&
+	    (need_ssids <= wpa_s->max_scan_ssids ||
+	     wpa_s->max_scan_ssids >= (int) max_sched_scan_ssids)) {
+		/*
+		 * When normal scan can speed up operations, use that for the
+		 * first operations before starting the sched_scan to allow
+		 * user space sleep more. We do this only if the normal scan
+		 * has functionality that is suitable for this or if the
+		 * sched_scan does not have better support for multiple SSIDs.
+		 */
+		wpa_dbg(wpa_s, MSG_DEBUG, "Use normal scan instead of "
+			"sched_scan for initial scans (normal_scans=%d)",
+			wpa_s->normal_scans);
+		return -1;
+	}
+
+	os_memset(&params, 0, sizeof(params));
+
+	/* If we can't allocate space for the filters, we just don't filter */
+	params.filter_ssids = os_zalloc(wpa_s->max_match_sets *
+					sizeof(struct wpa_driver_scan_filter));
+
+	prev_state = wpa_s->wpa_state;
+	if (wpa_s->wpa_state == WPA_DISCONNECTED ||
+	    wpa_s->wpa_state == WPA_INACTIVE)
+		wpa_supplicant_set_state(wpa_s, WPA_SCANNING);
+
+	/* Find the starting point from which to continue scanning */
+	ssid = wpa_s->conf->ssid;
+	if (wpa_s->prev_sched_ssid) {
+		while (ssid) {
+			if (ssid == wpa_s->prev_sched_ssid) {
+				ssid = ssid->next;
+				break;
+			}
+			ssid = ssid->next;
+		}
+	}
+
+	if (!ssid || !wpa_s->prev_sched_ssid) {
+		wpa_dbg(wpa_s, MSG_DEBUG, "Beginning of SSID list");
+
+		wpa_s->sched_scan_interval = 10;
+		wpa_s->sched_scan_timeout = max_sched_scan_ssids * 2;
+		wpa_s->first_sched_scan = 1;
+		ssid = wpa_s->conf->ssid;
+		wpa_s->prev_sched_ssid = ssid;
+	}
+
+	if (wildcard) {
+		wpa_dbg(wpa_s, MSG_DEBUG, "Add wildcard SSID to sched_scan");
+		params.num_ssids++;
+	}
+
+	while (ssid) {
+		if (ssid->disabled)
+			goto next;
+
+		if (params.num_filter_ssids < wpa_s->max_match_sets &&
+		    params.filter_ssids && ssid->ssid && ssid->ssid_len) {
+			wpa_dbg(wpa_s, MSG_DEBUG, "add to filter ssid: %s",
+				wpa_ssid_txt(ssid->ssid, ssid->ssid_len));
+			os_memcpy(params.filter_ssids[params.num_filter_ssids].ssid,
+				  ssid->ssid, ssid->ssid_len);
+			params.filter_ssids[params.num_filter_ssids].ssid_len =
+				ssid->ssid_len;
+			params.num_filter_ssids++;
+		} else if (params.filter_ssids && ssid->ssid && ssid->ssid_len)
+		{
+			wpa_dbg(wpa_s, MSG_DEBUG, "Not enough room for SSID "
+				"filter for sched_scan - drop filter");
+			os_free(params.filter_ssids);
+			params.filter_ssids = NULL;
+			params.num_filter_ssids = 0;
+		}
+
+		if (ssid->scan_ssid && ssid->ssid && ssid->ssid_len) {
+			if (params.num_ssids == max_sched_scan_ssids)
+				break; /* only room for broadcast SSID */
+			wpa_dbg(wpa_s, MSG_DEBUG,
+				"add to active scan ssid: %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++;
+			if (params.num_ssids >= max_sched_scan_ssids) {
+				wpa_s->prev_sched_ssid = ssid;
+				break;
+			}
+		}
+
+	next:
+		wpa_s->prev_sched_ssid = ssid;
+		ssid = ssid->next;
+	}
+
+	if (params.num_filter_ssids == 0) {
+		os_free(params.filter_ssids);
+		params.filter_ssids = NULL;
+	}
+
+	if (wpa_s->wps)
+		wps_ie = wpa_supplicant_extra_ies(wpa_s, &params);
+
+	if (ssid || !wpa_s->first_sched_scan) {
+		wpa_dbg(wpa_s, MSG_DEBUG,
+			"Starting sched scan: interval %d (no timeout)",
+			wpa_s->sched_scan_interval);
+	} else {
+		wpa_dbg(wpa_s, MSG_DEBUG,
+			"Starting sched scan: interval %d timeout %d",
+			wpa_s->sched_scan_interval, wpa_s->sched_scan_timeout);
+	}
+
+	ret = wpa_supplicant_start_sched_scan(wpa_s, &params,
+					      wpa_s->sched_scan_interval);
+	wpabuf_free(wps_ie);
+	os_free(params.filter_ssids);
+	if (ret) {
+		wpa_msg(wpa_s, MSG_WARNING, "Failed to initiate sched scan");
+		if (prev_state != wpa_s->wpa_state)
+			wpa_supplicant_set_state(wpa_s, prev_state);
+		return ret;
+	}
+
+	/* If we have more SSIDs to scan, add a timeout so we scan them too */
+	if (ssid || !wpa_s->first_sched_scan) {
+		wpa_s->sched_scan_timed_out = 0;
+		eloop_register_timeout(wpa_s->sched_scan_timeout, 0,
+				       wpa_supplicant_sched_scan_timeout,
+				       wpa_s, NULL);
+		wpa_s->first_sched_scan = 0;
+		wpa_s->sched_scan_timeout /= 2;
+		wpa_s->sched_scan_interval *= 2;
+	}
+
+	return 0;
+}
+
+
+/**
  * wpa_supplicant_cancel_scan - Cancel a scheduled scan request
  * @wpa_s: Pointer to wpa_supplicant data
  *
@@ -549,6 +892,23 @@
 }
 
 
+/**
+ * wpa_supplicant_cancel_sched_scan - Stop running scheduled scans
+ * @wpa_s: Pointer to wpa_supplicant data
+ *
+ * This function is used to stop a periodic scheduled scan.
+ */
+void wpa_supplicant_cancel_sched_scan(struct wpa_supplicant *wpa_s)
+{
+	if (!wpa_s->sched_scanning)
+		return;
+
+	wpa_dbg(wpa_s, MSG_DEBUG, "Cancelling sched scan");
+	eloop_cancel_timeout(wpa_supplicant_sched_scan_timeout, wpa_s, NULL);
+	wpa_supplicant_stop_sched_scan(wpa_s);
+}
+
+
 void wpa_supplicant_notify_scanning(struct wpa_supplicant *wpa_s,
 				    int scanning)
 {
@@ -686,15 +1046,28 @@
 }
 
 
+/*
+ * Channels with a great SNR can operate at full rate. What is a great SNR?
+ * This doc https://supportforums.cisco.com/docs/DOC-12954 says, "the general
+ * rule of thumb is that any SNR above 20 is good." This one
+ * http://www.cisco.com/en/US/tech/tk722/tk809/technologies_q_and_a_item09186a00805e9a96.shtml#qa23
+ * recommends 25 as a minimum SNR for 54 Mbps data rate. 30 is chosen here as a
+ * conservative value.
+ */
+#define GREAT_SNR 30
+
 /* Compare function for sorting scan results. Return >0 if @b is considered
  * better. */
 static int wpa_scan_result_compar(const void *a, const void *b)
 {
+#define IS_5GHZ(n) (n > 4000)
+#define MIN(a,b) a < b ? a : b
 	struct wpa_scan_res **_wa = (void *) a;
 	struct wpa_scan_res **_wb = (void *) b;
 	struct wpa_scan_res *wa = *_wa;
 	struct wpa_scan_res *wb = *_wb;
 	int wpa_a, wpa_b, maxrate_a, maxrate_b;
+	int snr_a, snr_b;
 
 	/* WPA/WPA2 support preferred */
 	wpa_a = wpa_scan_get_vendor_ie(wa, WPA_IE_VENDOR_TYPE) != NULL ||
@@ -715,23 +1088,37 @@
 	    (wb->caps & IEEE80211_CAP_PRIVACY) == 0)
 		return -1;
 
-	/* best/max rate preferred if signal level close enough XXX */
-	if ((wa->level && wb->level && abs(wb->level - wa->level) < 5) ||
+	if ((wa->flags & wb->flags & WPA_SCAN_LEVEL_DBM) &&
+	    !((wa->flags | wb->flags) & WPA_SCAN_NOISE_INVALID)) {
+		snr_a = MIN(wa->level - wa->noise, GREAT_SNR);
+		snr_b = MIN(wb->level - wb->noise, GREAT_SNR);
+	} else {
+		/* Not suitable information to calculate SNR, so use level */
+		snr_a = wa->level;
+		snr_b = wb->level;
+	}
+
+	/* best/max rate preferred if SNR close enough */
+        if ((snr_a && snr_b && abs(snr_b - snr_a) < 5) ||
 	    (wa->qual && wb->qual && abs(wb->qual - wa->qual) < 10)) {
 		maxrate_a = wpa_scan_get_max_rate(wa);
 		maxrate_b = wpa_scan_get_max_rate(wb);
 		if (maxrate_a != maxrate_b)
 			return maxrate_b - maxrate_a;
+		if (IS_5GHZ(wa->freq) ^ IS_5GHZ(wb->freq))
+			return IS_5GHZ(wa->freq) ? -1 : 1;
 	}
 
 	/* use freq for channel preference */
 
-	/* all things being equal, use signal level; if signal levels are
+	/* all things being equal, use SNR; if SNRs are
 	 * identical, use quality values since some drivers may only report
 	 * that value and leave the signal level zero */
-	if (wb->level == wa->level)
+	if (snr_b == snr_a)
 		return wb->qual - wa->qual;
-	return wb->level - wa->level;
+	return snr_b - snr_a;
+#undef MIN
+#undef IS_5GHZ
 }
 
 
@@ -783,6 +1170,37 @@
 #endif /* CONFIG_WPS */
 
 
+static void dump_scan_res(struct wpa_scan_results *scan_res)
+{
+#ifndef CONFIG_NO_STDOUT_DEBUG
+	size_t i;
+
+	if (scan_res->res == NULL || scan_res->num == 0)
+		return;
+
+	wpa_printf(MSG_EXCESSIVE, "Sorted scan results");
+
+	for (i = 0; i < scan_res->num; i++) {
+		struct wpa_scan_res *r = scan_res->res[i];
+		if ((r->flags & (WPA_SCAN_LEVEL_DBM | WPA_SCAN_NOISE_INVALID))
+		    == WPA_SCAN_LEVEL_DBM) {
+			int snr = r->level - r->noise;
+			wpa_printf(MSG_EXCESSIVE, MACSTR " freq=%d qual=%d "
+				   "noise=%d level=%d snr=%d%s flags=0x%x",
+				   MAC2STR(r->bssid), r->freq, r->qual,
+				   r->noise, r->level, snr,
+				   snr >= GREAT_SNR ? "*" : "", r->flags);
+		} else {
+			wpa_printf(MSG_EXCESSIVE, MACSTR " freq=%d qual=%d "
+				   "noise=%d level=%d flags=0x%x",
+				   MAC2STR(r->bssid), r->freq, r->qual,
+				   r->noise, r->level, r->flags);
+		}
+	}
+#endif /* CONFIG_NO_STDOUT_DEBUG */
+}
+
+
 /**
  * wpa_supplicant_get_scan_results - Get scan results
  * @wpa_s: Pointer to wpa_supplicant data
@@ -802,10 +1220,7 @@
 	size_t i;
 	int (*compar)(const void *, const void *) = wpa_scan_result_compar;
 
-	if (wpa_s->drv_flags & WPA_DRIVER_FLAGS_USER_SPACE_MLME)
-		scan_res = ieee80211_sta_get_scan_results(wpa_s);
-	else
-		scan_res = wpa_drv_get_scan_results2(wpa_s);
+	scan_res = wpa_drv_get_scan_results2(wpa_s);
 	if (scan_res == NULL) {
 		wpa_dbg(wpa_s, MSG_DEBUG, "Failed to get scan results");
 		return NULL;
@@ -821,6 +1236,7 @@
 
 	qsort(scan_res->res, scan_res->num, sizeof(struct wpa_scan_res *),
 	      compar);
+	dump_scan_res(scan_res);
 
 	wpa_bss_update_start(wpa_s);
 	for (i = 0; i < scan_res->num; i++)
@@ -841,17 +1257,3 @@
 
 	return 0;
 }
-
-
-void wpa_scan_results_free(struct wpa_scan_results *res)
-{
-	size_t i;
-
-	if (res == NULL)
-		return;
-
-	for (i = 0; i < res->num; i++)
-		os_free(res->res[i]);
-	os_free(res->res);
-	os_free(res);
-}