[PATCH] bgscan: Add centralized signal_monitor

npoojary at chromium.org npoojary at chromium.org
Tue Apr 9 16:56:10 PDT 2019


From: Neeraj Poojary <npoojary at chromium.org>

The signal strength number tells only half of the picture with
respect to signal quality.  The other half is the prevailing
noise on the channel.  The two together indicate how easily the
interface is able to pull data out of the background noise.

This change takes noise into account, by calculating a CQM
threshold based both on the RSSI and noise.  This noise value is
available in the CQM events as well as polled data.  Since most
background scan algorithms will probably want to do this the same
way, I have added this as routines in bgscan.c that can be
utilized by individual background scan mechanisms.

Additionally, I've modified bgscan_simple to use this mechanism,
and scheduled noisefloor updates to happen at the same rate as
background scans (the noisefloor should be a slowly changing
parameter).

This patch also includes changes to the background scan procedure
to make background scans low priority and resume aborted scans from
frequencies that haven't been scanned yet.

Signed-off-by: Neeraj Poojary <npoojary at chromium.org>
---
 src/drivers/driver.h           |   2 +
 wpa_supplicant/bgscan.c        |  85 ++++++++++++
 wpa_supplicant/bgscan_i.h      |  66 ++++++++++
 wpa_supplicant/bgscan_simple.c | 227 +++++++++++++++++++++++++++++----
 wpa_supplicant/scan.c          |   1 +
 5 files changed, 355 insertions(+), 26 deletions(-)
 create mode 100644 wpa_supplicant/bgscan_i.h

diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index 4ac9f16a0..b4ac6dbdd 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -314,11 +314,13 @@ struct wpa_scan_res {
  * @res: Array of pointers to allocated variable length scan result entries
  * @num: Number of entries in the scan result array
  * @fetch_time: Time when the results were fetched from the driver
+ * @aborted: Whether the scan was aborted
  */
 struct wpa_scan_results {
 	struct wpa_scan_res **res;
 	size_t num;
 	struct os_reltime fetch_time;
+	int aborted;
 };
 
 /**
diff --git a/wpa_supplicant/bgscan.c b/wpa_supplicant/bgscan.c
index 1ea640114..544f62512 100644
--- a/wpa_supplicant/bgscan.c
+++ b/wpa_supplicant/bgscan.c
@@ -9,9 +9,12 @@
 #include "includes.h"
 
 #include "common.h"
+#include "eloop.h"
 #include "wpa_supplicant_i.h"
 #include "config_ssid.h"
+#include "driver_i.h"
 #include "bgscan.h"
+#include "bgscan_i.h"
 
 
 static const struct bgscan_ops * bgscan_modules[] = {
@@ -107,3 +110,85 @@ void bgscan_notify_signal_change(struct wpa_supplicant *wpa_s, int above,
 						    current_noise,
 						    current_txrate);
 }
+
+static void bgscan_apply_signal_monitor(void *eloop_ctx, void *timeout_ctx)
+{
+	struct bgscan_signal_monitor_state *sm_state = eloop_ctx;
+
+	wpa_drv_signal_monitor(sm_state->wpa_s, sm_state->calc_threshold,
+			       sm_state->hysteresis);
+}
+
+
+void bgscan_update_signal_monitor(struct bgscan_signal_monitor_state *sm_state,
+				 int current_signal, int current_noise)
+{
+	int threshold = current_noise + sm_state->headroom;
+
+	if (current_noise >= 0)
+		return;
+
+	if (threshold >= sm_state->calc_threshold -
+	    BGSCAN_NOISEFLOOR_TOLERANCE &&
+	    threshold <= sm_state->calc_threshold +
+	    BGSCAN_NOISEFLOOR_TOLERANCE)
+		return;
+
+	wpa_printf(MSG_DEBUG, "%s: noisefloor update: %d -> %d",
+		   __func__, sm_state->calc_threshold - sm_state->headroom,
+		   current_noise);
+
+	sm_state->calc_threshold = threshold;
+
+	/*
+	 * Schedule a noisefloor adjustment.  Do this as a timeout callback,
+	 * so it is implicitly throttled.
+	 */
+	eloop_cancel_timeout(bgscan_apply_signal_monitor, sm_state, NULL);
+	eloop_register_timeout(BGSCAN_NOISEFLOOR_UPDATE_DELAY, 0,
+			       bgscan_apply_signal_monitor, sm_state, NULL);
+}
+
+int bgscan_poll_signal_monitor(struct bgscan_signal_monitor_state *sm_state,
+			       struct wpa_signal_info *siginfo_ret)
+{
+	struct wpa_signal_info siginfo;
+	int ret;
+
+	ret = wpa_drv_signal_poll(sm_state->wpa_s, &siginfo);
+	if (ret != 0)
+		return ret;
+
+	wpa_printf(MSG_DEBUG, "%s: bgscan poll noisefloor: %d ",
+		   __func__, siginfo.current_noise);
+
+	bgscan_update_signal_monitor(sm_state, siginfo.current_signal,
+				     siginfo.current_noise);
+
+	if (siginfo_ret != 0)
+		memcpy(siginfo_ret, &siginfo, sizeof(siginfo));
+
+	return 0;
+}
+
+void bgscan_init_signal_monitor(struct bgscan_signal_monitor_state *sm_state,
+				struct wpa_supplicant *wpa_s,
+				int signal_threshold,
+				int hysteresis) {
+
+	sm_state->wpa_s = wpa_s;
+	sm_state->calc_threshold = signal_threshold;
+	sm_state->hysteresis = hysteresis;
+	sm_state->headroom = signal_threshold - BGSCAN_DEFAULT_NOISE_FLOOR;
+
+	if (wpa_drv_signal_monitor(wpa_s, signal_threshold, hysteresis) < 0)
+		wpa_printf(MSG_ERROR, "bgscan simple: Failed to enable "
+			   "signal strength monitoring");
+}
+
+void bgscan_deinit_signal_monitor(struct bgscan_signal_monitor_state *sm_state)
+{
+	wpa_drv_signal_monitor(sm_state->wpa_s, 0, 0);
+	eloop_cancel_timeout(bgscan_apply_signal_monitor, sm_state, NULL);
+}
+
diff --git a/wpa_supplicant/bgscan_i.h b/wpa_supplicant/bgscan_i.h
new file mode 100644
index 000000000..42b14df20
--- /dev/null
+++ b/wpa_supplicant/bgscan_i.h
@@ -0,0 +1,66 @@
+/*
+ * WPA Supplicant - background scan and roaming interface
+ * Copyright (c) 2009-2010, Jouni Malinen <j at w1.fi>
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef BGSCAN_I_H
+#define BGSCAN_I_H
+
+/*
+ * The signal monitoring code is an optional facility for bgscan algorithms
+ * that want to track both signal strength and noise floor (e.g. so they can
+ * make decisions based on received signal strength relative to noise floor).
+ *
+ * calc_threshold: Signal strength threshold for generating CQM events.
+ *     When signal strength passes above or below this value CQM events
+ *     are generated.  rssi_threshold is initialized from user-specified
+ *     options to the algorithm and then recalculated based on the current
+ *     noise floor.
+ * headroom: The the threshold for signal above the noisefloor for
+ *     generating CQM events.  headroom is calculated at initialization
+ *     from the user-specified signal strength and then used to calculate
+ *     calc_threshold using the current noise floor.
+ * hysteresis: Hysterisis value passed into the driver CQM to indicate
+ *     how large a delta in received signal (in dBm) from the last CQM
+ *     event should trigger another CQM event.
+ */
+struct bgscan_signal_monitor_state {
+	struct wpa_supplicant *wpa_s;
+	int calc_threshold;
+	int headroom;
+	int hysteresis;
+};
+
+void bgscan_init_signal_monitor(struct bgscan_signal_monitor_state *sm_state,
+				struct wpa_supplicant *wpa_s,
+				int signal_threshold,
+				int hysteresis);
+void bgscan_deinit_signal_monitor(struct bgscan_signal_monitor_state *sm_state);
+void bgscan_update_signal_monitor(struct bgscan_signal_monitor_state *sm_state,
+				  int current_signal, int current_noise);
+int bgscan_poll_signal_monitor(struct bgscan_signal_monitor_state *sm_state,
+			       struct wpa_signal_info *siginfo_ret);
+
+
+/*
+ * The time (secs) to delay updates to the CQM monitoring parameters.  This is
+ * done to collapse rapid changes into a single request.
+ */
+#define BGSCAN_NOISEFLOOR_UPDATE_DELAY 10
+
+/*
+ * The starting/default noise floor for the channel (dBm).  This also
+ * serves as the reference noise floor for user-specified signal strength
+ * values in bgscan algorithms that use these facilities.
+ */
+#define BGSCAN_DEFAULT_NOISE_FLOOR -95
+
+/*
+ * Range [+/-] for determining if the noise floor has changed enough for
+ * us to adjust the RSSI threshold.
+ */
+#define BGSCAN_NOISEFLOOR_TOLERANCE 1
+#endif /* BGSCAN_I_H */
diff --git a/wpa_supplicant/bgscan_simple.c b/wpa_supplicant/bgscan_simple.c
index 41a26df0d..ae7927aba 100644
--- a/wpa_supplicant/bgscan_simple.c
+++ b/wpa_supplicant/bgscan_simple.c
@@ -16,6 +16,7 @@
 #include "driver_i.h"
 #include "scan.h"
 #include "bgscan.h"
+#include "bgscan_i.h"
 
 struct bgscan_simple_data {
 	struct wpa_supplicant *wpa_s;
@@ -27,9 +28,56 @@ struct bgscan_simple_data {
 	int short_interval; /* use if signal < threshold */
 	int long_interval; /* use if signal > threshold */
 	struct os_reltime last_bgscan;
+	int *supp_freqs;
+	int n_supp_freqs;
+	int *scan_freqs;
+	int freq_idx;
+	struct bgscan_signal_monitor_state signal_monitor;
 };
 
 
+static int * bgscan_simple_get_freqs(struct bgscan_simple_data *data)
+{
+	int *freqs = data->scan_freqs;
+	int i, j;
+
+	if (data->supp_freqs == NULL)
+		return NULL;
+
+	if (freqs == NULL)
+		return NULL;
+
+	j = 0;
+	for (i = data->freq_idx; i < data->n_supp_freqs; i++)
+		freqs[j++] = data->supp_freqs[i];
+	for (i = 0; i < data->freq_idx; i++)
+		freqs[j++] = data->supp_freqs[i];
+	freqs[j] = 0;		/* NB: terminator expected elsewhere */
+
+	return freqs;
+}
+
+
+#define BGSCAN_FREQ_LIST_MAX_STRLEN 1000
+static void log_freqs(const char *tag, const int freqs[])
+{
+	char msg[BGSCAN_FREQ_LIST_MAX_STRLEN];
+	int i, pos;
+
+	msg[0] = '\0';
+	for (i = 0, pos = 0; freqs[i] != 0 && pos < BGSCAN_FREQ_LIST_MAX_STRLEN; i++) {
+		int ret;
+		ret = os_snprintf(&msg[pos], BGSCAN_FREQ_LIST_MAX_STRLEN - pos, " %d",
+				  freqs[i]);
+		if (ret < 0 || ret >= BGSCAN_FREQ_LIST_MAX_STRLEN - pos)
+			break;
+		pos += ret;
+	}
+	msg[pos] = '\0';
+	wpa_printf(MSG_DEBUG, "bgscan simple: %s frequencies:%s", tag, msg);
+}
+
+
 static void bgscan_simple_timeout(void *eloop_ctx, void *timeout_ctx)
 {
 	struct bgscan_simple_data *data = eloop_ctx;
@@ -40,19 +88,26 @@ static void bgscan_simple_timeout(void *eloop_ctx, void *timeout_ctx)
 	params.num_ssids = 1;
 	params.ssids[0].ssid = data->ssid->ssid;
 	params.ssids[0].ssid_len = data->ssid->ssid_len;
-	params.freqs = data->ssid->scan_freq;
 
-	/*
-	 * A more advanced bgscan module would learn about most like channels
-	 * over time and request scans only for some channels (probing others
-	 * every now and then) to reduce effect on the data connection.
-	 */
+	if (data->ssid->scan_freq == NULL)
+		params.freqs = bgscan_simple_get_freqs(data);
+	else
+		params.freqs = data->ssid->scan_freq;
 
-	wpa_printf(MSG_DEBUG, "bgscan simple: Request a background scan");
+	params.low_priority = 1;
+
+	wpa_printf(MSG_DEBUG, "bgscan simple: Request a background scan "
+		   "with reason %s",
+		   timeout_ctx ? (char *)timeout_ctx : "None");
+	if (params.freqs != NULL)
+		log_freqs("Scanning", params.freqs);
 	if (wpa_supplicant_trigger_scan(wpa_s, &params)) {
 		wpa_printf(MSG_DEBUG, "bgscan simple: Failed to trigger scan");
+		eloop_cancel_timeout(bgscan_simple_timeout, data,
+				     ELOOP_ALL_CTX);
 		eloop_register_timeout(data->scan_interval, 0,
-				       bgscan_simple_timeout, data, NULL);
+				       bgscan_simple_timeout, data,
+				       "failed-scan");
 	} else {
 		if (data->scan_interval == data->short_interval) {
 			data->short_scan_count++;
@@ -100,6 +155,70 @@ static int bgscan_simple_get_params(struct bgscan_simple_data *data,
 }
 
 
+static int in_array(const int *array, int v, int arraylen)
+{
+	int i;
+
+	if (array == NULL)
+		return 0;
+
+	for (i = 0; i < arraylen; i++)
+		if (array[i] == v)
+			return 1;
+	return 0;
+}
+
+
+static void bgscan_simple_setup_freqs(struct wpa_supplicant *wpa_s,
+				      struct bgscan_simple_data *data)
+{
+	struct hostapd_hw_modes *modes;
+	const struct hostapd_hw_modes *infra;
+	u16 num_modes, flags;
+	int i, j, *freqs;
+	size_t count;
+	u8 dfs_domain;
+
+	data->supp_freqs = NULL;
+	data->freq_idx = 0;
+
+	modes = wpa_drv_get_hw_feature_data(wpa_s, &num_modes, &flags, &dfs_domain);
+	if (!modes)
+		return;
+
+	count = 0;
+	freqs = NULL;
+	for (i = 0; i < num_modes; i++) {
+		for (j = 0; j < modes[i].num_channels; j++) {
+			int freq, *n;
+
+			if (modes[i].channels[j].flag & HOSTAPD_CHAN_DISABLED)
+				continue;
+			freq = modes[i].channels[j].freq;
+			if (in_array(freqs, freq, count))	/* NB: de-dup list */
+				continue;
+			n = os_realloc(freqs, (count + 2) * sizeof(int));
+			if (n != NULL) {
+				freqs = n;
+				freqs[count++] = freq;
+				freqs[count] = 0;
+			}
+		}
+		os_free(modes[i].channels);
+		os_free(modes[i].rates);
+	}
+	os_free(modes);
+
+	if (freqs != NULL) {
+		data->supp_freqs = freqs;
+		data->n_supp_freqs = count;
+		data->scan_freqs = os_malloc((count + 1) * sizeof(int));
+
+		log_freqs("Supported", freqs);
+	}
+}
+
+
 static void * bgscan_simple_init(struct wpa_supplicant *wpa_s,
 				 const char *params,
 				 const struct wpa_ssid *ssid)
@@ -125,23 +244,25 @@ static void * bgscan_simple_init(struct wpa_supplicant *wpa_s,
 		   data->signal_threshold, data->short_interval,
 		   data->long_interval);
 
-	if (data->signal_threshold &&
-	    wpa_drv_signal_monitor(wpa_s, data->signal_threshold, 4) < 0) {
-		wpa_printf(MSG_ERROR, "bgscan simple: Failed to enable "
-			   "signal strength monitoring");
-	}
-
 	data->scan_interval = data->short_interval;
 	data->max_short_scans = data->long_interval / data->short_interval + 1;
 	if (data->signal_threshold) {
-		/* Poll for signal info to set initial scan interval */
 		struct wpa_signal_info siginfo;
-		if (wpa_drv_signal_poll(wpa_s, &siginfo) == 0 &&
+
+		bgscan_init_signal_monitor(&data->signal_monitor, wpa_s,
+					   data->signal_threshold, 4);
+
+		/* Poll for signal info to set initial scan interval */
+		if (bgscan_poll_signal_monitor(&data->signal_monitor,
+					       &siginfo) == 0 &&
 		    siginfo.current_signal >= data->signal_threshold)
 			data->scan_interval = data->long_interval;
 	}
 	wpa_printf(MSG_DEBUG, "bgscan simple: Init scan interval: %d",
 		   data->scan_interval);
+
+	bgscan_simple_setup_freqs(wpa_s, data);
+
 	eloop_register_timeout(data->scan_interval, 0, bgscan_simple_timeout,
 			       data, NULL);
 
@@ -160,23 +281,70 @@ static void * bgscan_simple_init(struct wpa_supplicant *wpa_s,
 static void bgscan_simple_deinit(void *priv)
 {
 	struct bgscan_simple_data *data = priv;
-	eloop_cancel_timeout(bgscan_simple_timeout, data, NULL);
+	eloop_cancel_timeout(bgscan_simple_timeout, data, ELOOP_ALL_CTX);
 	if (data->signal_threshold)
-		wpa_drv_signal_monitor(data->wpa_s, 0, 0);
+		bgscan_deinit_signal_monitor(&data->signal_monitor);
+	os_free(data->supp_freqs);
+	os_free(data->scan_freqs);
 	os_free(data);
 }
 
 
+static int find_freq_index(const struct bgscan_simple_data *data, int freq)
+{
+	int ix;
+
+	for (ix = data->freq_idx; ix < data->n_supp_freqs; ix++)
+		if (freq == data->supp_freqs[ix])
+			return ix;
+	for (ix = 0; ix < data->freq_idx; ix++)
+		if (freq == data->supp_freqs[ix])
+			return ix;
+	return -1;
+}
+
 static int bgscan_simple_notify_scan(void *priv,
 				     struct wpa_scan_results *scan_res)
 {
 	struct bgscan_simple_data *data = priv;
+	char *bgscan_reason = NULL;
 
 	wpa_printf(MSG_DEBUG, "bgscan simple: scan result notification");
 
-	eloop_cancel_timeout(bgscan_simple_timeout, data, NULL);
+	if (scan_res->aborted && data->supp_freqs != NULL) {
+		int last_freq, i, idx;
+		/*
+		 * Scan was aborted; advance the rotor past known
+		 * channels visited.  This does not take into account
+		 * channels that were visited but had no scan results.
+		 * This should be ok as we always supply a complete
+		 * frequency list when we scan.
+		 *
+		 * NB: can't depend on scan results order matching our
+		 * channel list as the upper layers sort results
+		 */
+		last_freq = 0;
+		for (i = 0; i < scan_res->num; i++) {
+			if (scan_res->res[i]->freq == last_freq)
+				continue;
+			last_freq = scan_res->res[i]->freq;
+			idx = find_freq_index(data, last_freq) + 1;
+			if (idx != -1)
+				data->freq_idx = (idx + 1) % data->n_supp_freqs;
+		}
+	} else
+		data->freq_idx = 0;
+	wpa_printf(MSG_DEBUG, "bgscan simple: freq_idx %d", data->freq_idx);
+
+	if (data->signal_threshold)
+		bgscan_poll_signal_monitor(&data->signal_monitor, NULL);
+	if (data->scan_interval == data->short_interval)
+		bgscan_reason = "short-interval-scan";
+	else
+		bgscan_reason = "long-interval-scan";
+	eloop_cancel_timeout(bgscan_simple_timeout, data, ELOOP_ALL_CTX);
 	eloop_register_timeout(data->scan_interval, 0, bgscan_simple_timeout,
-			       data, NULL);
+			       data, bgscan_reason);
 
 	/*
 	 * A more advanced bgscan could process scan results internally, select
@@ -213,6 +381,10 @@ static void bgscan_simple_notify_signal_change(void *priv, int above,
 		   "(above=%d current_signal=%d current_noise=%d "
 		   "current_txrate=%d))", above, current_signal,
 		   current_noise, current_txrate);
+
+	bgscan_update_signal_monitor(&data->signal_monitor, current_signal,
+				     current_noise);
+
 	if (data->scan_interval == data->long_interval && !above) {
 		wpa_printf(MSG_DEBUG, "bgscan simple: Start using short "
 			   "bgscan interval");
@@ -234,18 +406,20 @@ static void bgscan_simple_notify_signal_change(void *priv, int above,
 			 * scan is too far in the future.
 			 */
 			eloop_cancel_timeout(bgscan_simple_timeout, data,
-					     NULL);
+					     ELOOP_ALL_CTX);
 			eloop_register_timeout(data->scan_interval, 0,
 					       bgscan_simple_timeout, data,
-					       NULL);
+					       "signal-below-short-interval-scan");
 		}
 	} else if (data->scan_interval == data->short_interval && above) {
 		wpa_printf(MSG_DEBUG, "bgscan simple: Start using long bgscan "
 			   "interval");
 		data->scan_interval = data->long_interval;
-		eloop_cancel_timeout(bgscan_simple_timeout, data, NULL);
+		eloop_cancel_timeout(bgscan_simple_timeout, data,
+				     ELOOP_ALL_CTX);
 		eloop_register_timeout(data->scan_interval, 0,
-				       bgscan_simple_timeout, data, NULL);
+				       bgscan_simple_timeout, data,
+				       "signal-above-long-interval-scan");
 	} else if (!above) {
 		/*
 		 * Signal dropped further 4 dB. Request a new scan if we have
@@ -258,9 +432,10 @@ static void bgscan_simple_notify_signal_change(void *priv, int above,
 
 	if (scan) {
 		wpa_printf(MSG_DEBUG, "bgscan simple: Trigger immediate scan");
-		eloop_cancel_timeout(bgscan_simple_timeout, data, NULL);
+		eloop_cancel_timeout(bgscan_simple_timeout, data,
+				     ELOOP_ALL_CTX);
 		eloop_register_timeout(0, 0, bgscan_simple_timeout, data,
-				       NULL);
+				       "signal-below-immediate-scan");
 	}
 }
 
diff --git a/wpa_supplicant/scan.c b/wpa_supplicant/scan.c
index ee39e0c92..517a87cc6 100644
--- a/wpa_supplicant/scan.c
+++ b/wpa_supplicant/scan.c
@@ -2399,6 +2399,7 @@ wpa_supplicant_get_scan_results(struct wpa_supplicant *wpa_s,
 		wpa_s->ignore_post_flush_scan_res = 0;
 		return scan_res;
 	}
+	scan_res->aborted = (info && info->aborted);
 
 	wpa_bss_update_start(wpa_s);
 	for (i = 0; i < scan_res->num; i++)
-- 
2.20.1




More information about the Hostap mailing list