From 84802c7f25def1eaf84e6db221afdf14a316ee7c Mon Sep 17 00:00:00 2001
From: Guy Mishol <guym@ti.com>
Date: Tue, 1 Apr 2014 15:07:16 +0300
Subject: [PATCH] acs: Porting legacy ACS solution [ACS]

Porting the legacy ACS implementation.

fix: remove redundant ieee80211n_check_40mhz call
The 40mhz check should be done only if no scan_results
are available (as being checked in the following lines).

The redundant scan finally results in a try to reconfigure
the already-configured channel.

Signed-off-by: Eliad Peller <eliad@wizery.com>
---
 hostapd/config_file.c |  12 ++
 src/ap/acs.h          |   4 +-
 src/ap/ap_config.h    |   3 +
 src/ap/hostapd.c      |   4 +-
 src/ap/hw_features.c  | 360 +++++++++++++++++++++++++++++++++++++++++++++++---
 src/ap/hw_features.h  |   6 +-
 6 files changed, 364 insertions(+), 25 deletions(-)

diff --git a/hostapd/config_file.c b/hostapd/config_file.c
index ee3ae65..1b3245c 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -3261,6 +3261,18 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 			conf->rate_type = BEACON_RATE_LEGACY;
 			conf->beacon_rate = val;
 		}
+	} else if (os_strcmp(buf, "acs_blacklist") == 0) {
+		if (hostapd_parse_intlist(&conf->acs_blacklist, pos)) {
+			wpa_printf(MSG_ERROR, "Line %d: invalid acs "
+				   "black list", line);
+			return 1;
+		}
+	} else if (os_strcmp(buf, "acs_whitelist") == 0) {
+		if (hostapd_parse_intlist(&conf->acs_whitelist, pos)) {
+			wpa_printf(MSG_ERROR, "Line %d: invalid acs "
+				   "white list", line);
+			return 1;
+		}
 	} else if (os_strcmp(buf, "preamble") == 0) {
 		if (atoi(pos))
 			conf->preamble = SHORT_PREAMBLE;
diff --git a/src/ap/acs.h b/src/ap/acs.h
index ec84f0e..f29ae3f 100644
--- a/src/ap/acs.h
+++ b/src/ap/acs.h
@@ -19,8 +19,8 @@ void acs_cleanup(struct hostapd_iface *iface);
 
 static inline enum hostapd_chan_status acs_init(struct hostapd_iface *iface)
 {
-	wpa_printf(MSG_ERROR, "ACS was disabled on your build, rebuild hostapd with CONFIG_ACS=y or set channel");
-	return HOSTAPD_CHAN_INVALID;
+	wpa_printf(MSG_ERROR, "CONFIG_ACS was not set. use custom ACS");
+	return HOSTAPD_CHAN_ACS;
 }
 
 static inline void acs_cleanup(struct hostapd_iface *iface)
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index 509677a..ee579e7 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -821,6 +821,9 @@ struct hostapd_config {
 	struct fst_iface_cfg fst_cfg;
 #endif /* CONFIG_FST */
 
+	int *acs_blacklist;
+	int *acs_whitelist;
+
 #ifdef CONFIG_P2P
 	u8 p2p_go_ctwindow;
 #endif /* CONFIG_P2P */
diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c
index 77742f4..c8769a4 100644
--- a/src/ap/hostapd.c
+++ b/src/ap/hostapd.c
@@ -1532,11 +1532,13 @@ static int setup_interface2(struct hostapd_iface *iface)
 				   "channel. (%d)", ret);
 			goto fail;
 		}
+		/* we don't need hostapd_check_ht_capab if the channel is
+		 * selected automatically */
 		if (ret == 1) {
 			wpa_printf(MSG_DEBUG, "Interface initialization will be completed in a callback (ACS)");
 			return 0;
 		}
-		ret = hostapd_check_ht_capab(iface);
+		ret = hostapd_check_ht_capab(iface, NULL);
 		if (ret < 0)
 			goto fail;
 		if (ret == 1) {
diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c
index 9d3d990..4a4e892 100644
--- a/src/ap/hw_features.c
+++ b/src/ap/hw_features.c
@@ -286,28 +286,16 @@ static int ieee80211n_check_40mhz_2g4(struct hostapd_iface *iface,
 }
 
 
-static void ieee80211n_check_scan(struct hostapd_iface *iface)
+static int ieee80211n_check_scan(struct hostapd_iface *iface,
+				 struct wpa_scan_results *scan_res)
 {
-	struct wpa_scan_results *scan_res;
 	int oper40;
 	int res;
 
-	/* Check list of neighboring BSSes (from scan) to see whether 40 MHz is
-	 * allowed per IEEE Std 802.11-2012, 10.15.3.2 */
-
-	iface->scan_cb = NULL;
-
-	scan_res = hostapd_driver_get_scan_results(iface->bss[0]);
-	if (scan_res == NULL) {
-		hostapd_setup_interface_complete(iface, 1);
-		return;
-	}
-
 	if (iface->current_mode->mode == HOSTAPD_MODE_IEEE80211A)
 		oper40 = ieee80211n_check_40mhz_5g(iface, scan_res);
 	else
 		oper40 = ieee80211n_check_40mhz_2g4(iface, scan_res);
-	wpa_scan_results_free(scan_res);
 
 	iface->secondary_ch = iface->conf->secondary_channel;
 	if (!oper40) {
@@ -337,6 +325,28 @@ static void ieee80211n_check_scan(struct hostapd_iface *iface)
 	}
 
 	hostapd_setup_interface_complete(iface, !res);
+	return 1;
+}
+
+
+static void ieee80211n_get_res_and_check_scan(struct hostapd_iface *iface)
+{
+	struct wpa_scan_results *scan_res;
+
+	/* Check list of neighboring BSSes (from scan) to see whether 40 MHz is
+	 * allowed per IEEE Std 802.11-2012, 10.15.3.2 */
+
+	iface->scan_cb = NULL;
+
+	scan_res = hostapd_driver_get_scan_results(iface->bss[0]);
+
+	if (scan_res == NULL) {
+		hostapd_setup_interface_complete(iface, 1);
+		return;
+	}
+
+	ieee80211n_check_scan(iface, scan_res);
+	wpa_scan_results_free(scan_res);
 }
 
 
@@ -453,7 +463,7 @@ static void ap_ht40_scan_retry(void *eloop_data, void *user_data)
 	}
 
 	if (ret == 0) {
-		iface->scan_cb = ieee80211n_check_scan;
+		iface->scan_cb = ieee80211n_get_res_and_check_scan;
 		return;
 	}
 
@@ -509,7 +519,7 @@ static int ieee80211n_check_40mhz(struct hostapd_iface *iface)
 		return -1;
 	}
 
-	iface->scan_cb = ieee80211n_check_scan;
+	iface->scan_cb = ieee80211n_get_res_and_check_scan;
 	return 1;
 }
 
@@ -658,12 +668,13 @@ static int ieee80211ac_supported_vht_capab(struct hostapd_iface *iface)
 #endif /* CONFIG_IEEE80211N */
 
 
-int hostapd_check_ht_capab(struct hostapd_iface *iface)
+int hostapd_check_ht_capab(struct hostapd_iface *iface,
+			   struct wpa_scan_results *scan_res)
 {
 #ifdef CONFIG_IEEE80211N
 	int ret;
 	if (!iface->conf->ieee80211n)
-		return 0;
+                goto out;
 
 	if (iface->current_mode->mode != HOSTAPD_MODE_IEEE80211B &&
 	    iface->current_mode->mode != HOSTAPD_MODE_IEEE80211G &&
@@ -680,13 +691,21 @@ int hostapd_check_ht_capab(struct hostapd_iface *iface)
 	    !ieee80211ac_supported_vht_capab(iface))
 		return -1;
 #endif /* CONFIG_IEEE80211AC */
-	ret = ieee80211n_check_40mhz(iface);
+	if (scan_res)
+		ret = ieee80211n_check_scan(iface, scan_res);
+	else
+		ret = ieee80211n_check_40mhz(iface);
+	/* sometimes the init should proceed async or fail */
 	if (ret)
 		return ret;
 	if (!ieee80211n_allowed_ht40_channel_pair(iface))
 		return -1;
+out:
 #endif /* CONFIG_IEEE80211N */
 
+	/* we are in an async callback if we have scan results */
+	if (scan_res)
+		hostapd_setup_interface_complete(iface, 0);
 	return 0;
 }
 
@@ -833,7 +852,7 @@ int hostapd_acs_completed(struct hostapd_iface *iface, int err)
 		goto out;
 	}
 
-	ret = hostapd_check_ht_capab(iface);
+	ret = hostapd_check_ht_capab(iface,NULL);
 	if (ret < 0)
 		goto out;
 	if (ret == 1) {
@@ -847,6 +866,292 @@ out:
 }
 
 
+static int valid_ap_channel(struct hostapd_iface *iface, int chan)
+{
+	int j;
+	struct hostapd_channel_data *c;
+	int *list;
+
+	/* don't allow AP on channel 14 - only JP 11b rates */
+	if (chan == 14)
+		return 0;
+
+	/* don't allow channels on the the ACS blacklist */
+	if (iface->conf->acs_blacklist) {
+		list = iface->conf->acs_blacklist;
+		for (j = 0; list[j] >= 0; j++)
+			if (chan == list[j])
+				return 0;
+	}
+
+	/* only allow channels from the ACS whitelist */
+	if (iface->conf->acs_whitelist) {
+		list = iface->conf->acs_whitelist;
+		for (j = 0; list[j] >= 0; j++)
+			if (chan == list[j])
+				break;
+
+		/* channel not found */
+		if (list[j] != chan)
+			return 0;
+	}
+
+	for (j = 0; j < iface->current_mode->num_channels; j++) {
+		c = &iface->current_mode->channels[j];
+		if (c->chan == chan)
+			return (c->flag & HOSTAPD_CHAN_DISABLED) ? 0 : 1;
+	}
+
+	/* channel not found */
+	return 0;
+}
+
+
+/* this is a duplication of the table in p2p_supplicant.c.
+ * all changes here must be propagated there and vice versa */
+static struct oper_class_map op_class[] = {
+#if 0 /* diallow HT40 on 2.4Ghz on purpose */
+	{ HOSTAPD_MODE_IEEE80211G, 83, 1, 9, 1, BW40PLUS },
+	{ HOSTAPD_MODE_IEEE80211G, 84, 5, 13, 1, BW40MINUS },
+#endif
+	{ HOSTAPD_MODE_IEEE80211A, 116, 36, 44, 8, BW40PLUS },
+	{ HOSTAPD_MODE_IEEE80211A, 117, 40, 48, 8, BW40MINUS },
+	{ HOSTAPD_MODE_IEEE80211A, 126, 149, 157, 8, BW40PLUS },
+	{ HOSTAPD_MODE_IEEE80211A, 127, 153, 161, 8, BW40MINUS },
+	{ -1, 0, 0, 0, 0, BW40PLUS } /* terminator */
+};
+
+
+
+static int channel_distance(struct hostapd_iface *iface)
+{
+	switch (iface->current_mode->mode) {
+	case HOSTAPD_MODE_IEEE80211A:
+		return 4;
+	case HOSTAPD_MODE_IEEE80211B:
+	case HOSTAPD_MODE_IEEE80211G:
+		return 1;
+	default:
+		break;
+	}
+
+	wpa_printf(MSG_ERROR, "Invalid HW mode for channel distance");
+	return 0;
+}
+
+
+
+/* Returns secondary channel (-1, 1), if possible considering the
+ * user preferred secondary channel. If no HT40 operation is possible,
+ * returns 0 */
+static int select_secondary_channel(struct hostapd_iface *iface,
+				    int primary_chan, int pref_sec_chan)
+{
+	int i;
+	int up_ok = 0, down_ok = 0;
+
+	for (i = 0; op_class[i].op_class; i++) {
+		struct oper_class_map *o = &op_class[i];
+		u8 ch;
+
+		if (o->mode != iface->current_mode->mode)
+			continue;
+
+		if (primary_chan < o->min_chan || primary_chan > o->max_chan)
+			continue;
+
+		for (ch = o->min_chan; ch <= o->max_chan; ch += o->inc) {
+			if (ch == primary_chan) {
+				if (o->bw == BW40PLUS)
+					up_ok = 1;
+				else if (o->bw == BW40MINUS)
+					down_ok = 1;
+
+				break;
+			}
+		}
+	}
+
+	if (up_ok && !valid_ap_channel(iface,
+				primary_chan + channel_distance(iface)))
+		up_ok = 0;
+	if (down_ok && !valid_ap_channel(iface,
+				primary_chan - channel_distance(iface)))
+		down_ok = 0;
+
+	if ((pref_sec_chan == 1 && up_ok) || (pref_sec_chan == -1 && down_ok))
+		return pref_sec_chan;
+
+	if (up_ok)
+		return 1;
+	else if (down_ok)
+		return -1;
+
+	/* no secondary channel possible */
+	return 0;
+}
+
+/* unreasonable number of APs to find on a channel. */
+#define MAX_AP_COUNT 10000
+
+void set_prim_sec_chan(struct hostapd_iface *iface, int *channel_cnt,
+		       int min_cnt, int default_prim_chan)
+{
+	int j, i;
+	int min_sec_cnt = MAX_AP_COUNT, min_sec_prim_chan = -1,
+		min_sec_chan_dir = -1;
+	int prim_chan, sec_chan_dir, sec_chan;
+
+	if (!iface->conf->secondary_channel)
+		goto set;
+
+	/* if a secondary channel is requested, try to select a channel that
+	 * allows HT40 from the minimal AP ones */
+	for (j = 0; j < iface->current_mode->num_channels; j++) {
+		prim_chan = iface->current_mode->channels[j].chan;
+		if (channel_cnt[j] != min_cnt)
+			continue;
+
+		sec_chan_dir = select_secondary_channel(iface, prim_chan,
+					iface->conf->secondary_channel);
+
+		if (!sec_chan_dir)
+			continue;
+
+		/* see if this secondary channel has minimal APs count */
+		sec_chan = prim_chan + sec_chan_dir * channel_distance(iface);
+		for (i = 0; i < iface->current_mode->num_channels; i++) {
+			if (iface->current_mode->channels[i].chan == sec_chan)
+				break;
+		}
+
+		if (i < iface->current_mode->num_channels &&
+		    channel_cnt[i] < min_sec_cnt) {
+			min_sec_cnt = channel_cnt[i];
+			min_sec_prim_chan = prim_chan;
+			min_sec_chan_dir = sec_chan_dir;
+		}
+	}
+
+	/* found some sec chan with minimal APs */
+	if (min_sec_cnt != MAX_AP_COUNT) {
+		iface->conf->channel = min_sec_prim_chan;
+		iface->conf->secondary_channel = min_sec_chan_dir;
+		return;
+	}
+
+	wpa_printf(MSG_DEBUG, "Could not auto-select secondary channel");
+
+set:
+	iface->conf->channel = default_prim_chan;
+	iface->conf->secondary_channel = 0;
+	iface->conf->ht_capab &= ~HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET;
+}
+
+
+static void hostapd_auto_select_scan_cb(struct hostapd_iface *iface)
+{
+	struct wpa_scan_results *scan_res;
+	size_t i, j;
+	int *channel_cnt;
+	int min_cnt, min_idx;
+	struct hostapd_channel_data *chan;
+
+	iface->scan_cb = NULL;
+
+	/* init all channel counters to 0 */
+	channel_cnt = os_calloc(iface->current_mode->num_channels, sizeof(int));
+	if (channel_cnt == NULL) {
+		hostapd_setup_interface_complete(iface, 1);
+		return;
+	}
+
+	scan_res = hostapd_driver_get_scan_results(iface->bss[0]);
+	if (scan_res == NULL) {
+		hostapd_setup_interface_complete(iface, 1);
+		goto free_chans;
+	}
+
+	/* increment channel counters according to scan results */
+	for (i = 0; i < scan_res->num; i++) {
+		struct wpa_scan_res *bss = scan_res->res[i];
+		for (j = 0; j < iface->current_mode->num_channels; j++) {
+			chan = &iface->current_mode->channels[j];
+
+			if (bss->freq == chan->freq) {
+				channel_cnt[j]++;
+				wpa_printf(MSG_MSGDUMP, "%d BSSes on ch %d",
+					   channel_cnt[j], chan->chan);
+				break;
+			}
+		}
+	}
+
+	min_idx = -1;
+	min_cnt = MAX_AP_COUNT;
+	for (j = 0; j < iface->current_mode->num_channels; j++) {
+		chan = &iface->current_mode->channels[j];
+		if (!valid_ap_channel(iface, chan->chan)) {
+			channel_cnt[j] = MAX_AP_COUNT;
+			continue;
+		}
+
+		if (channel_cnt[j] >= min_cnt)
+			continue;
+
+		min_cnt = channel_cnt[j];
+		min_idx = j;
+	}
+
+	if (min_idx == -1) {
+		wpa_printf(MSG_ERROR,
+			   "Could not select channel automatically");
+		hostapd_setup_interface_complete(iface, 1);
+		goto free_scan;
+	}
+
+	chan = &iface->current_mode->channels[min_idx];
+	wpa_printf(MSG_DEBUG, "Min APs found in channel %d (AP count %d)",
+		   chan->chan, min_cnt);
+
+	/* Select a secondary channel and fine tune the primary one.
+	 * Basically we try to start HT40, without increasing the number
+	 * of APs on the primary channel. */
+	set_prim_sec_chan(iface, channel_cnt, min_cnt, chan->chan);
+
+	wpa_printf(MSG_DEBUG, "Auto-selected channel: %d secondary: %d",
+		   iface->conf->channel, iface->conf->secondary_channel);
+
+	/* will complete interface setup */
+	hostapd_check_ht_capab(iface, scan_res);
+
+free_scan:
+	wpa_scan_results_free(scan_res);
+
+free_chans:
+	os_free(channel_cnt);
+}
+
+
+static int hostapd_auto_select_channel(struct hostapd_iface *iface)
+{
+
+	struct wpa_driver_scan_params params;
+
+	/* TODO: we can scan only the current HW mode */
+	wpa_printf(MSG_DEBUG, "Scan for neighboring BSSes to select channel");
+	os_memset(&params, 0, sizeof(params));
+	if (hostapd_driver_scan(iface->bss[0], &params) < 0) {
+		wpa_printf(MSG_ERROR, "Failed to request a scan of "
+			   "neighboring BSSes");
+		return -1;
+	}
+
+	iface->scan_cb = hostapd_auto_select_scan_cb;
+	return 0;
+}
+
+
 /**
  * hostapd_select_hw_mode - Select the hardware mode
  * @iface: Pointer to interface data.
@@ -895,6 +1200,21 @@ int hostapd_select_hw_mode(struct hostapd_iface *iface)
 		}
 	}
 
+	/*
+	 * request a scan of neighboring BSSes and select the
+	 * channel automatically
+	 */
+
+	if (iface->conf->channel == 0) {
+		if (hostapd_auto_select_channel(iface)) {
+			wpa_printf(MSG_ERROR, "Channel not configured "
+				   "(hw_mode/channel in hostapd.conf) and "
+				   "automatic channel selection failed");
+
+			return -3;
+		}
+	}
+
 	switch (hostapd_check_chans(iface)) {
 	case HOSTAPD_CHAN_VALID:
 		return 0;
diff --git a/src/ap/hw_features.h b/src/ap/hw_features.h
index ca7f22b..4e655c8 100644
--- a/src/ap/hw_features.h
+++ b/src/ap/hw_features.h
@@ -20,7 +20,8 @@ int hostapd_select_hw_mode(struct hostapd_iface *iface);
 const char * hostapd_hw_mode_txt(int mode);
 int hostapd_hw_get_freq(struct hostapd_data *hapd, int chan);
 int hostapd_hw_get_channel(struct hostapd_data *hapd, int freq);
-int hostapd_check_ht_capab(struct hostapd_iface *iface);
+int hostapd_check_ht_capab(struct hostapd_iface *iface,
+			   struct wpa_scan_results *scan_res);
 int hostapd_prepare_rates(struct hostapd_iface *iface,
 			  struct hostapd_hw_modes *mode);
 void hostapd_stop_setup_timers(struct hostapd_iface *iface);
@@ -56,7 +57,8 @@ static inline int hostapd_hw_get_freq(struct hostapd_data *hapd, int chan)
 	return -1;
 }
 
-static inline int hostapd_check_ht_capab(struct hostapd_iface *iface)
+static inline int hostapd_check_ht_capab(struct hostapd_iface *iface,
+					 struct wpa_scan_results *scan_res)
 {
 	return 0;
 }
-- 
2.7.4

