This series adds ACS support to hostapd. I've completed
a few of my own TODOs and splitted up the patches as
best as possible. I'm pretty satisfied with this code now
as an initial start for ACS code. At this point we need
to start finding ideal values for the metrics for offchannel
operation so that we can consistently always find the same
channel, even if it means surveying longer or doing more
scans.
Other algorithms for ACS shoudl be easy to plug in, for example,
spectral scan ;)
Anyway, to enable and test this thing just set CONFIG_ACS=1
on your build config and then something like this on your
hostapd.conf:
channel=acs_survey
acs_num_req_surveys=10
acs_roc_duration_ms=100
Test results, rants are appreciated.
Luis R. Rodriguez (4):
hostapd: split up channel checking into helpers
hostapd: add offchannel support
hostapd: add survey dump support
hostapd: add Automatic Channel Selection (ACS) support
hostapd/Makefile | 5 +
hostapd/config_file.c | 47 ++++-
hostapd/defconfig | 25 +++
hostapd/hostapd.conf | 17 ++
src/ap/acs.c | 464 ++++++++++++++++++++++++++++++++++++++++++
src/ap/acs.h | 40 ++++
src/ap/ap_config.c | 15 ++
src/ap/ap_config.h | 4 +
src/ap/ap_drv_ops.h | 27 +++
src/ap/drv_callbacks.c | 139 +++++++++++++-
src/ap/hostapd.c | 2 +
src/ap/hostapd.h | 24 +++
src/ap/hw_features.c | 188 +++++++++++------
src/drivers/driver.h | 171 +++++++++++++++-
src/drivers/driver_nl80211.c | 193 ++++++++++++++++++
15 files changed, 1290 insertions(+), 71 deletions(-)
create mode 100644 src/ap/acs.c
create mode 100644 src/ap/acs.h
--
1.7.4.15.g7811d
This splits up the channel checking upon initialization
into a few helpers. This should make this a bit easier
to follow. This also paves the way for some initial
ACS entry code.
Signed-off-by: Luis R. Rodriguez <[email protected]>
---
src/ap/hostapd.h | 6 ++
src/ap/hw_features.c | 148 +++++++++++++++++++++++++++-----------------------
2 files changed, 86 insertions(+), 68 deletions(-)
diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
index d4501a1..6e4cc4f 100644
--- a/src/ap/hostapd.h
+++ b/src/ap/hostapd.h
@@ -30,6 +30,12 @@ struct full_dynamic_vlan;
enum wps_event;
union wps_event_data;
+enum hostapd_chan_status {
+ HOSTAPD_CHAN_VALID = 0, /* channel is ready */
+ HOSTAPD_CHAN_INVALID = 1, /* no usable channel found */
+ HOSTAPD_CHAN_ACS = 2, /* ACS work being performed */
+};
+
struct hostapd_probereq_cb {
int (*cb)(void *ctx, const u8 *sa, const u8 *ie, size_t ie_len);
void *ctx;
diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c
index 86f6811..887744b 100644
--- a/src/ap/hw_features.c
+++ b/src/ap/hw_features.c
@@ -599,6 +599,77 @@ int hostapd_check_ht_capab(struct hostapd_iface *iface)
return 0;
}
+static int hostapd_is_usable_chan(struct hostapd_iface *iface,
+ int channel,
+ int primary)
+{
+ unsigned int i;
+ struct hostapd_channel_data *chan;
+
+ for (i = 0; i < iface->current_mode->num_channels; i++) {
+ chan = &iface->current_mode->channels[i];
+ if (chan->chan == channel) {
+ if (chan->flag & HOSTAPD_CHAN_DISABLED) {
+ wpa_printf(MSG_ERROR,
+ "%schannel [%i] (%i) is disabled for "
+ "use in AP mode, flags: 0x%x",
+ primary ? "" : "Configured HT40 secondary ",
+ i, chan->chan, chan->flag);
+ } else {
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int hostapd_is_usable_chans(struct hostapd_iface *iface)
+{
+ if (!hostapd_is_usable_chan(iface, iface->conf->channel, 1))
+ return 0;
+
+ if (!iface->conf->secondary_channel)
+ return 1;
+
+ if (!hostapd_is_usable_chan(iface, iface->conf->secondary_channel, 0))
+ return 0;
+
+ return 1;
+}
+
+static enum hostapd_chan_status hostapd_check_chans(struct hostapd_iface *iface)
+{
+ if (iface->conf->channel) {
+ if (hostapd_is_usable_chans(iface))
+ return HOSTAPD_CHAN_VALID;
+ else
+ return HOSTAPD_CHAN_INVALID;
+ }
+
+ /*
+ * The user set channel=0 which is used to trigger ACS,
+ * which we do not yet support.
+ */
+ return HOSTAPD_CHAN_INVALID;
+}
+
+static void hostapd_notify_bad_chans(struct hostapd_iface *iface)
+{
+ iface->current_mode = NULL;
+
+ hostapd_logger(iface->bss[0], NULL,
+ HOSTAPD_MODULE_IEEE80211,
+ HOSTAPD_LEVEL_WARNING,
+ "Configured channel (%d) not found from the "
+ "channel list of current mode (%d) %s",
+ iface->conf->channel,
+ iface->current_mode->mode,
+ hostapd_hw_mode_txt(iface->current_mode->mode));
+ hostapd_logger(iface->bss[0], NULL, HOSTAPD_MODULE_IEEE80211,
+ HOSTAPD_LEVEL_WARNING,
+ "Hardware does not support configured channel");
+}
/**
* hostapd_select_hw_mode - Select the hardware mode
@@ -610,7 +681,7 @@ int hostapd_check_ht_capab(struct hostapd_iface *iface)
*/
int hostapd_select_hw_mode(struct hostapd_iface *iface)
{
- int i, j, ok;
+ int i;
if (iface->num_hw_features < 1)
return -1;
@@ -634,75 +705,16 @@ int hostapd_select_hw_mode(struct hostapd_iface *iface)
return -2;
}
- ok = 0;
- for (j = 0; j < iface->current_mode->num_channels; j++) {
- struct hostapd_channel_data *chan =
- &iface->current_mode->channels[j];
- if (chan->chan == iface->conf->channel) {
- if (chan->flag & HOSTAPD_CHAN_DISABLED) {
- wpa_printf(MSG_ERROR,
- "channel [%i] (%i) is disabled for "
- "use in AP mode, flags: 0x%x",
- j, chan->chan, chan->flag);
- } else {
- ok = 1;
- break;
- }
- }
- }
- if (ok && iface->conf->secondary_channel) {
- int sec_ok = 0;
- int sec_chan = iface->conf->channel +
- iface->conf->secondary_channel * 4;
- for (j = 0; j < iface->current_mode->num_channels; j++) {
- struct hostapd_channel_data *chan =
- &iface->current_mode->channels[j];
- if (!(chan->flag & HOSTAPD_CHAN_DISABLED) &&
- (chan->chan == sec_chan)) {
- sec_ok = 1;
- break;
- }
- }
- if (!sec_ok) {
- hostapd_logger(iface->bss[0], NULL,
- HOSTAPD_MODULE_IEEE80211,
- HOSTAPD_LEVEL_WARNING,
- "Configured HT40 secondary channel "
- "(%d) not found from the channel list "
- "of current mode (%d) %s",
- sec_chan, iface->current_mode->mode,
- hostapd_hw_mode_txt(
- iface->current_mode->mode));
- ok = 0;
- }
- }
- if (iface->conf->channel == 0) {
- /* TODO: could request a scan of neighboring BSSes and select
- * the channel automatically */
- wpa_printf(MSG_ERROR, "Channel not configured "
- "(hw_mode/channel in hostapd.conf)");
- return -3;
- }
- if (ok == 0 && iface->conf->channel != 0) {
- hostapd_logger(iface->bss[0], NULL,
- HOSTAPD_MODULE_IEEE80211,
- HOSTAPD_LEVEL_WARNING,
- "Configured channel (%d) not found from the "
- "channel list of current mode (%d) %s",
- iface->conf->channel,
- iface->current_mode->mode,
- hostapd_hw_mode_txt(iface->current_mode->mode));
- iface->current_mode = NULL;
- }
-
- if (iface->current_mode == NULL) {
- hostapd_logger(iface->bss[0], NULL, HOSTAPD_MODULE_IEEE80211,
- HOSTAPD_LEVEL_WARNING,
- "Hardware does not support configured channel");
+ switch (hostapd_check_chans(iface)) {
+ case HOSTAPD_CHAN_VALID:
+ return 0;
+ case HOSTAPD_CHAN_ACS: /* ACS not supported yet */
+ case HOSTAPD_CHAN_INVALID:
+ default:
+ hostapd_notify_bad_chans(iface);
return -4;
+ return HOSTAPD_CHAN_INVALID;
}
-
- return 0;
}
--
1.7.4.15.g7811d
This adds survey dump support for all frequencies
and for specific desired frequencies. This will later
be used by ACS code for spectrum heuristics.
Signed-off-by: Luis R. Rodriguez <[email protected]>
---
src/ap/ap_drv_ops.h | 15 +++
src/ap/drv_callbacks.c | 72 +++++++++++++++-
src/ap/hostapd.h | 8 ++
src/drivers/driver.h | 89 +++++++++++++++++++-
src/drivers/driver_nl80211.c | 193 ++++++++++++++++++++++++++++++++++++++++++
5 files changed, 375 insertions(+), 2 deletions(-)
diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h
index a62e1e5..24c7e95 100644
--- a/src/ap/ap_drv_ops.h
+++ b/src/ap/ap_drv_ops.h
@@ -213,4 +213,19 @@ static inline int hostapd_drv_remain_on_channel(struct hostapd_data *hapd,
return hapd->driver->remain_on_channel(hapd->drv_priv, freq, duration);
}
+static inline int hostapd_drv_survey_freq(struct hostapd_data *hapd,
+ unsigned int freq)
+{
+ if (hapd->driver == NULL)
+ return -1;
+ if (!hapd->driver->get_survey)
+ return -1;
+ return hapd->driver->get_survey(hapd->drv_priv, freq);
+}
+
+static inline int hostapd_drv_survey(struct hostapd_data *hapd)
+{
+ return hostapd_drv_survey_freq(hapd, 0);
+}
+
#endif /* AP_DRV_OPS */
diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c
index 676fac1..6953b71 100644
--- a/src/ap/drv_callbacks.c
+++ b/src/ap/drv_callbacks.c
@@ -545,6 +545,73 @@ static void hostapd_event_roc_cancel(struct hostapd_data *hapd,
/* XXX: pass err to listeners, no one yet */
}
+struct hostapd_channel_data *hostapd_get_mode_channel(struct hostapd_iface *iface,
+ unsigned int freq)
+{
+ unsigned int i;
+ struct hostapd_channel_data *chan;
+
+ for (i = 0; i < iface->current_mode->num_channels; i++) {
+ chan = &iface->current_mode->channels[i];
+ if (!chan)
+ return NULL;
+ if (chan->freq == freq)
+ return chan;
+ }
+
+ return NULL;
+}
+
+static void hostapd_update_nf(struct hostapd_iface *iface,
+ struct hostapd_channel_data *chan,
+ struct freq_survey *survey)
+{
+ if (!iface->chans_surveyed) {
+ chan->min_nf = survey->nf;
+ iface->lowest_nf = survey->nf;
+ } else {
+ if (!chan->survey_count)
+ chan->min_nf = survey->nf;
+ else if (survey->nf < chan->min_nf)
+ chan->min_nf = survey->nf;
+ if (survey->nf < iface->lowest_nf)
+ iface->lowest_nf = survey->nf;
+ }
+}
+
+static void hostapd_event_get_survey(struct hostapd_data *hapd,
+ struct survey_results *survey_results)
+{
+ struct hostapd_iface *iface = hapd->iface;
+ struct freq_survey *survey, *tmp;
+ struct hostapd_channel_data *chan;
+
+ if (dl_list_empty(&survey_results->survey_list)) {
+ wpa_printf(MSG_DEBUG, "ACS: no survey data received");
+ return;
+ }
+
+ dl_list_for_each_safe(survey, tmp, &survey_results->survey_list, struct freq_survey, list_member) {
+ chan = hostapd_get_mode_channel(iface, survey->freq);
+ if (!chan)
+ goto fail;
+ if (chan->flag & HOSTAPD_CHAN_DISABLED)
+ continue;
+
+ dl_list_del(&survey->list_member);
+ dl_list_add_tail(&chan->survey_list, &survey->list_member);
+
+ hostapd_update_nf(iface, chan, survey);
+
+ chan->survey_count++;
+ iface->chans_surveyed++;
+ }
+
+ return;
+fail:
+ iface->chans_surveyed = 0;
+}
+
void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
union wpa_event_data *data)
{
@@ -633,6 +700,7 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
data->rx_action.bssid == NULL)
break;
hostapd_rx_action(hapd, &data->rx_action);
+#endif /* NEED_AP_MLME */
case EVENT_REMAIN_ON_CHANNEL:
hostapd_event_roc(hapd,
data->remain_on_channel.freq,
@@ -643,7 +711,9 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
data->remain_on_channel.freq,
data->remain_on_channel.duration);
break;
-#endif /* NEED_AP_MLME */
+ case EVENT_SURVEY:
+ hostapd_event_get_survey(hapd, &data->survey_results);
+ break;
default:
wpa_printf(MSG_DEBUG, "Unknown event %d", event);
break;
diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
index d8318ee..1095d2b 100644
--- a/src/ap/hostapd.h
+++ b/src/ap/hostapd.h
@@ -231,6 +231,14 @@ struct hostapd_iface {
/* Offchannel operation helper */
unsigned int off_channel_freq_idx;
+ /* surveying helpers */
+
+ /* number of channels surveyed */
+ unsigned int chans_surveyed;
+
+ /* lowest observed noise floor in dBm */
+ s8 lowest_nf;
+
void (*scan_cb)(struct hostapd_iface *iface);
int (*ctrl_iface_init)(struct hostapd_data *hapd);
diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index 513580a..0e1c5c1 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -26,6 +26,7 @@
#define WPA_SUPPLICANT_DRIVER_VERSION 4
#include "common/defs.h"
+#include "list.h"
#define HOSTAPD_CHAN_DISABLED 0x00000001
#define HOSTAPD_CHAN_PASSIVE_SCAN 0x00000002
@@ -58,6 +59,22 @@ struct hostapd_channel_data {
* max_tx_power - maximum transmit power in dBm
*/
u8 max_tx_power;
+
+ /*
+ * survey_count - number of surveys XXX: can we remove this and use the count of the linked list?
+ */
+ unsigned int survey_count;
+
+ /*
+ * survey_list - linked list of surveys
+ */
+ struct dl_list survey_list;
+
+ /**
+ * min_nf - minimum observed noise floor, in dBm, based on all surveyed channel data
+ */
+ s8 min_nf;
+
};
/**
@@ -2253,6 +2270,30 @@ struct wpa_driver_ops {
* implementation, there is no need to implement this function.
*/
int (*set_authmode)(void *priv, int authmode);
+
+ /**
+ * get_survey - Retrieve survey data
+ * @priv: Private driver interface data
+ * @freq: if set survey data for the specified frequency is only
+ * being requested. If not set all survey data is requested.
+ * Returns: 0 on success, -1 on failure
+ *
+ * Use this to retreieve:
+ *
+ * - the observed channel noise floor
+ * - the amount of time we have spent on the channel
+ * - the amount of time during which we have spent on the channel that
+ * the radio has determined the the medium is busy and we cannot transmit
+ * - the amount of time we have spent RX'ing data
+ * - the amount of time we have spent TX'ing data
+ *
+ * This data can be use for spectrum heuristics, one example is
+ * Automatic Channel Selection (ACS). The channel survey data is
+ * kept on a linked list on the channel data, one entry is added
+ * for each survey. The min_nf of the channel is updated for
+ * each survey.
+ */
+ int (*get_survey)(void *priv, unsigned int freq);
};
@@ -2655,9 +2696,44 @@ enum wpa_event_type {
/**
* EVENT_IBSS_PEER_LOST - IBSS peer not reachable anymore
*/
- EVENT_IBSS_PEER_LOST
+ EVENT_IBSS_PEER_LOST,
+
+ /**
+ * EVENT_SURVEY - Received survey data
+ *
+ * This event gets triggered when a driver query is issued for survey
+ * data and its returned. The returned data is stored in
+ * struct survey_results. The results provide at most one survey
+ * entry for each frequency and at minimum will provide survey
+ * one survey entry for one frequency. The survey data can be
+ * os_malloc()'d and then os_free()'d, so the event callback must
+ * only copy data.
+ */
+ EVENT_SURVEY,
};
+/**
+ * struct survey_info - channel survey info
+ *
+ * @ifidx: interface index in which this survey was observed
+ * @freq: center of frequency of the surveyed channel
+ * @nf: channel noise floor in dBm
+ * @channel_time: amount of time in ms the radio spent on the channel
+ * @channel_time_busy: amount of time in ms the radio detected some signal
+ * that indicated to the radio the channel was not clear
+ * @channel_time_rx: amount of time the radio spent receiving data
+ * @channel_time_tx: amount of time the radio spent transmitting data
+ */
+struct freq_survey {
+ u32 ifidx;
+ unsigned int freq;
+ s8 nf;
+ u64 channel_time;
+ u64 channel_time_busy;
+ u64 channel_time_rx;
+ u64 channel_time_tx;
+ struct dl_list list_member;
+};
/**
* union wpa_event_data - Additional data for wpa_supplicant_event() calls
@@ -3187,6 +3263,17 @@ union wpa_event_data {
struct ibss_peer_lost {
u8 peer[ETH_ALEN];
} ibss_peer_lost;
+
+ /**
+ * survey_results - survey result data for EVENT_SURVEY
+ * @freq_filter: requested frequency survey filter, 0 if request
+ * was for all survey data
+ * @survey_list: linked list of survey data
+ */
+ struct survey_results {
+ unsigned int freq_filter;
+ struct dl_list survey_list;
+ } survey_results;
};
/**
diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index cfc9372..e5c4962 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -6603,6 +6603,198 @@ static int nl80211_flush_pmkid(void *priv)
return nl80211_pmkid(bss, NL80211_CMD_FLUSH_PMKSA, NULL, NULL);
}
+static void clean_survey_results(struct survey_results *survey_results)
+{
+ struct freq_survey *survey, *tmp;
+
+ if (dl_list_empty(&survey_results->survey_list))
+ return;
+
+ dl_list_for_each_safe(survey, tmp, &survey_results->survey_list, struct freq_survey, list_member) {
+ dl_list_del(&survey->list_member);
+ os_free(survey);
+ }
+}
+
+static void add_survey(struct nlattr **sinfo, u32 ifidx, struct dl_list *survey_list)
+{
+ struct freq_survey *survey;
+
+ survey = (struct freq_survey*) os_malloc(sizeof(struct freq_survey));
+ if (!survey)
+ return;
+ os_memset(survey, 0, sizeof(struct freq_survey));
+
+ survey->ifidx = ifidx;
+ survey->freq = nla_get_u32(sinfo[NL80211_SURVEY_INFO_FREQUENCY]);
+ survey->nf = (int8_t) nla_get_u8(sinfo[NL80211_SURVEY_INFO_NOISE]);
+ survey->channel_time = nla_get_u64(sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME]);
+ survey->channel_time_busy = nla_get_u64(sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY]);
+ survey->channel_time_rx = nla_get_u64(sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_RX]);
+ survey->channel_time_tx = nla_get_u64(sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_TX]);
+
+ wpa_printf(MSG_DEBUG, "nl80211: Freq survey dump event (freq=%d MHz noise=%d "
+ "channel_time=%ld busy_time=%ld tx_time=%ld rx_time=%ld)",
+ survey->freq,
+ survey->nf,
+ survey->channel_time,
+ survey->channel_time_busy,
+ survey->channel_time_rx,
+ survey->channel_time_tx);
+
+ dl_list_add_tail(survey_list, &survey->list_member);
+}
+
+static int survey_check(struct nlattr **sinfo)
+{
+ if (!sinfo[NL80211_SURVEY_INFO_NOISE] ||
+ !sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME] ||
+ !sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY] ||
+ !sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_TX])
+ return 0;
+ return 1;
+}
+
+static void survey_fail_reason(struct nlattr **sinfo, u32 surveyed_freq)
+{
+ wpa_printf(MSG_ERROR,
+ "nl80211: Following survey data missing "
+ "for freq %d MHz:",
+ surveyed_freq);
+
+ if (!sinfo[NL80211_SURVEY_INFO_NOISE])
+ wpa_printf(MSG_ERROR, "\tnoise");
+
+ if (!sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME])
+ wpa_printf(MSG_ERROR, "\tchannel time");
+
+ if (!sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY])
+ wpa_printf(MSG_ERROR, "\tchannel busy time");
+
+ if (!sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_TX])
+ wpa_printf(MSG_ERROR, "\tchannel tx time");
+}
+
+static int check_survey_ok(struct nlattr **sinfo,
+ u32 surveyed_freq,
+ unsigned int freq_filter)
+{
+ int good_survey = survey_check(sinfo);
+
+ /* If no filter is specified we just drop data for any bogus survey */
+ if (!freq_filter) {
+ if (good_survey)
+ return 1;
+ /* No filter used but no usable survey data found */
+ survey_fail_reason(sinfo, surveyed_freq);
+ return 0;
+ }
+
+ if (freq_filter != surveyed_freq)
+ return 0;
+
+ if (good_survey)
+ return 1;
+ /* Filter matches now, lets be verbose why this is a bad survey */
+
+ survey_fail_reason(sinfo, surveyed_freq);
+ return 0;
+}
+
+static int survey_handler(struct nl_msg *msg, void *arg)
+{
+ struct nlattr *tb[NL80211_ATTR_MAX + 1];
+ struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+ struct nlattr *sinfo[NL80211_SURVEY_INFO_MAX + 1];
+ struct survey_results *survey_results;
+ u32 surveyed_freq = 0;
+ u32 ifidx;
+
+ static struct nla_policy survey_policy[NL80211_SURVEY_INFO_MAX + 1] = {
+ [NL80211_SURVEY_INFO_FREQUENCY] = { .type = NLA_U32 },
+ [NL80211_SURVEY_INFO_NOISE] = { .type = NLA_U8 },
+ };
+
+ survey_results = (struct survey_results *) arg;
+
+ nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
+ genlmsg_attrlen(gnlh, 0), NULL);
+
+ ifidx = nla_get_u32(tb[NL80211_ATTR_IFINDEX]);
+
+ if (!tb[NL80211_ATTR_SURVEY_INFO])
+ return NL_SKIP;
+
+ if (nla_parse_nested(sinfo, NL80211_SURVEY_INFO_MAX,
+ tb[NL80211_ATTR_SURVEY_INFO],
+ survey_policy))
+ return NL_SKIP;
+
+ if (!sinfo[NL80211_SURVEY_INFO_FREQUENCY]) {
+ wpa_printf(MSG_ERROR, "nl80211: invalid survey data");
+ return NL_SKIP;
+ }
+
+ surveyed_freq = nla_get_u32(sinfo[NL80211_SURVEY_INFO_FREQUENCY]);
+
+ if (!check_survey_ok(sinfo, surveyed_freq, survey_results->freq_filter))
+ return NL_SKIP;
+
+ if (survey_results->freq_filter &&
+ survey_results->freq_filter != surveyed_freq) {
+ wpa_printf(MSG_EXCESSIVE, "nl80211: ignoring survey data "
+ "for freq %d MHz", surveyed_freq);
+ return NL_SKIP;
+ }
+
+ add_survey(sinfo, ifidx, &survey_results->survey_list);
+
+ return NL_SKIP;
+}
+
+static int wpa_driver_nl80211_get_survey(void *priv, unsigned int freq)
+{
+ struct i802_bss *bss = priv;
+ struct wpa_driver_nl80211_data *drv = bss->drv;
+ struct nl_msg *msg;
+ int err = -ENOBUFS;
+ union wpa_event_data data;
+ struct survey_results *survey_results;
+
+ os_memset(&data, 0, sizeof(data));
+ survey_results = &data.survey_results;
+
+ dl_list_init(&survey_results->survey_list);
+
+ msg = nlmsg_alloc();
+ if (!msg)
+ goto nla_put_failure;
+
+ genlmsg_put(msg, 0, 0, genl_family_get_id(drv->nl80211), 0, NLM_F_DUMP,
+ NL80211_CMD_GET_SURVEY, 0);
+
+ NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->ifindex);
+
+ if (freq)
+ data.survey_results.freq_filter = freq;
+
+ do {
+ err = send_and_recv_msgs(drv, msg, survey_handler, survey_results);
+ } while (err > 0);
+
+ if (err) {
+ wpa_printf(MSG_ERROR, "nl80211: failed to process survey data");
+ goto out_clean;
+ }
+
+ wpa_supplicant_event(drv->ctx, EVENT_SURVEY, &data);
+
+out_clean:
+ clean_survey_results(survey_results);
+nla_put_failure:
+ return err;
+}
+
const struct wpa_driver_ops wpa_driver_nl80211_ops = {
.name = "nl80211",
@@ -6676,4 +6868,5 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = {
.add_pmkid = nl80211_add_pmkid,
.remove_pmkid = nl80211_remove_pmkid,
.flush_pmkid = nl80211_flush_pmkid,
+ .get_survey = wpa_driver_nl80211_get_survey,
};
--
1.7.4.15.g7811d
This adds Automatic Channel Selection (ACS) support to hostapd
using the Survery based ACS algorithm [1]. You can enable this
with CONFIG_ACS and can use it by setting the channel to "0" or
"acs_survey". Other configurable parameters are:
* acs_num_req_surveys - must be > 1
* acs_roc_duration_ms - 1..5000
TODO:
- The current algorithm is heavily based on the amount of time
we are willing to spend offchannel configurable via acs_roc_duration_ms,
and acs_num_req_surveys, this will work for the period of time we do
the analysis, so if these values are too low you'd use an ideal channel
only based on the short bursts of traffic on the channel. We can also take
into consideration other data to help us further make a better analysis and
speed out our decision:
* Do a scan and count the number of BSSes on each channel:
* Assign an HT40 primary channel a high interference aggregate value
* Assign an HT40 secondary channel a lower interference aggregate value
* Use a frequency broker to collect other PHY RF interference:
* BT devices, etc, assign interference value aggregates to these
- An ideal result would continue surveying the channels and collect a
histogram, the ideal channel then will remain ideal for most of the
collected history.
- Add wpa_supplicant support for ACS, ideal for P2P
- Randomize channel study
- Enable the use passive scan instead of offchannel operation to enable
drivers that do not support offchannel support
- Get more drivers / firmware to implement / export survey dump
- Any other OSes interested?
[1] http://wireless.kernel.org/en/users/Documentation/acs#Survey_based_algorithm
Signed-off-by: Luis R. Rodriguez <[email protected]>
---
hostapd/Makefile | 5 +
hostapd/config_file.c | 47 +++++-
hostapd/defconfig | 25 +++
hostapd/hostapd.conf | 17 ++
src/ap/acs.c | 464 ++++++++++++++++++++++++++++++++++++++++++++++++
src/ap/acs.h | 40 ++++
src/ap/ap_config.c | 15 ++
src/ap/ap_config.h | 4 +
src/ap/ap_drv_ops.h | 1 +
src/ap/drv_callbacks.c | 5 +-
src/ap/hostapd.c | 2 +
src/ap/hostapd.h | 6 +
src/ap/hw_features.c | 48 +++++-
src/drivers/driver.h | 82 +++++++++
14 files changed, 754 insertions(+), 7 deletions(-)
create mode 100644 src/ap/acs.c
create mode 100644 src/ap/acs.h
diff --git a/hostapd/Makefile b/hostapd/Makefile
index d05975b..5ff51be 100644
--- a/hostapd/Makefile
+++ b/hostapd/Makefile
@@ -736,6 +736,11 @@ CFLAGS += -DCONFIG_P2P_MANAGER
OBJS += ../src/ap/p2p_hostapd.o
endif
+ifdef CONFIG_ACS
+CFLAGS += -DCONFIG_ACS
+OBJS += ../src/ap/acs.o
+endif
+
ifdef CONFIG_NO_STDOUT_DEBUG
CFLAGS += -DCONFIG_NO_STDOUT_DEBUG
endif
diff --git a/hostapd/config_file.c b/hostapd/config_file.c
index 835f050..6294e53 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -1736,7 +1736,18 @@ struct hostapd_config * hostapd_config_read(const char *fname)
errors++;
}
} else if (os_strcmp(buf, "channel") == 0) {
- conf->channel = atoi(pos);
+ if (os_strcmp(pos, "acs_survey") == 0) {
+ conf->channel = 0;
+ } else
+ conf->channel = atoi(pos);
+#ifndef CONFIG_ACS
+ if (!conf->channel) {
+ wpa_printf(MSG_ERROR, "Line %d: set as '%s' "
+ "tries to enable ACS but "
+ "CONFIG_ACS disabled", line, pos);
+ errors++;
+ }
+#endif
} else if (os_strcmp(buf, "beacon_int") == 0) {
int val = atoi(pos);
/* MIB defines range as 1..65535, but very small values
@@ -1751,6 +1762,40 @@ struct hostapd_config * hostapd_config_read(const char *fname)
errors++;
} else
conf->beacon_int = val;
+#ifdef CONFIG_ACS
+ } else if (os_strcmp(buf, "acs_num_req_surveys") == 0) {
+ int val = atoi(pos);
+ if (val <= 0) {
+ wpa_printf(MSG_ERROR, "Line %d: invalid "
+ "acs_num_req_surveys %d (expected > 1)",
+ line, val);
+ errors++;
+ } else
+ conf->acs_num_req_surveys = val;
+ } else if (os_strcmp(buf, "acs_roc_duration_ms") == 0) {
+ int val = atoi(pos);
+ /* Well only Linux supports offchannel ops right now ;) */
+ if (val <= 0 || val > 5000) {
+ errors++;
+ wpa_printf(MSG_ERROR, "Line %d: invalid "
+ "acs_roc_duration_ms %d (expected "
+ "1..5000)", line, val);
+ } else
+ conf->acs_roc_duration_ms = val;
+#else
+ } else if (os_strcmp(buf, "acs_num_req_surveys") == 0) {
+ int val = atoi(pos);
+ wpa_printf(MSG_ERROR, "Line %d: invalid "
+ "acs_num_req_surveys %d (CONFIG_ACS disabled)",
+ line, val);
+ errors++;
+ } else if (os_strcmp(buf, "acs_roc_duration_ms") == 0) {
+ int val = atoi(pos);
+ wpa_printf(MSG_ERROR, "Line %d: invalid "
+ "acs_roc_duration_ms %d (CONFIG_ACS disabled)"
+ "1..5000)", line, val);
+ errors++;
+#endif /* CONFIG_ACS */
} else if (os_strcmp(buf, "dtim_period") == 0) {
bss->dtim_period = atoi(pos);
if (bss->dtim_period < 1 || bss->dtim_period > 255) {
diff --git a/hostapd/defconfig b/hostapd/defconfig
index 26be2a8..4e2ec1b 100644
--- a/hostapd/defconfig
+++ b/hostapd/defconfig
@@ -208,3 +208,28 @@ CONFIG_IPV6=y
# considered for builds that are known to be used on devices that meet the
# requirements described above.
#CONFIG_NO_RANDOM_POOL=y
+
+# Enable this to get Automatic Channel Selection support. This will allow
+# hostapd to pick the channel automatically for you when you see the channel to
+# "acs_survey" or "0". Eventually other ACS algorithms can be added in similar way.
+# Automatic selection is currently only done through initialization, later on we
+# hope to do background checks to keep us moving to more ideal channels as time
+# goes by. ACS is currently only supported through the nl80211 driver, and
+# your 802.11 driver must support offchannel TX support and survey dump
+# capablities.
+#
+# You can customize the ACS survey algorithm with the hostapd.conf variables:
+#
+# acs_num_req_surveys requirement is > 1 - number of surverys required per channel
+# acs_roc_duration_ms requirement is 1..5000 - offchannel time
+#
+# Supported ACS drivers:
+#
+# * ath9k
+# * ath5k
+#
+# For more details refer to:
+#
+# http://wireless.kernel.org/en/users/Documentation/acs
+#
+#CONFIG_ACS=y
diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
index e0525e4..cfdbc8e 100644
--- a/hostapd/hostapd.conf
+++ b/hostapd/hostapd.conf
@@ -106,8 +106,25 @@ hw_mode=a
# Please note that some drivers (e.g., madwifi) do not use this value from
# hostapd and the channel will need to be configuration separately with
# iwconfig.
+#
+# If you enable CONFIG_ACS you can automatically get an ideal channel
+# by setting channel=acs_survey or channel=0, both of these options will
+# trigger the ACS survey based algorithm.
channel=60
+# ACS tuning - Automatic Channel Selection
+# See: http://wireless.kernel.org/en/users/Documentation/acs
+#
+# You can customize the ACS survey algorithm with the hostapd.conf variables:
+#
+# acs_num_req_surveys requirement is > 1 - number of surverys required per channel
+# acs_roc_duration_ms requirement is 1..5000 - offchannel time
+#
+# Defaults:
+#
+#acs_num_req_surveys=10
+#acs_roc_duration_ms=5
+
# Beacon interval in kus (1.024 ms) (default: 100; range 15..65535)
beacon_int=100
diff --git a/src/ap/acs.c b/src/ap/acs.c
new file mode 100644
index 0000000..45e1e2c
--- /dev/null
+++ b/src/ap/acs.c
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2011 Qualcomm Atheros
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * ACS Maintainer: Luis R. Rodriguez <[email protected]>
+ */
+
+#include "includes.h"
+#include "acs.h"
+#include "drivers/driver.h"
+#include "ap/ap_drv_ops.h"
+#include "ap/ap_config.h"
+#include "ap/hw_features.h"
+
+/*
+ * Until we get really bored and implement OS support for
+ * others on os.[ch]
+ */
+#include <math.h>
+
+/*
+ * Automatic Channel Selection
+ *
+ * http://wireless.kernel.org/en/users/Documentation/acs
+ *
+ * This is the hostapd AP ACS implementation
+ *
+ * You get automatic channel selection when you configure hostapd
+ * with a channel=acs_survey or channel=0 on the hostapd.conf file.
+ *
+ * TODO:
+ *
+ * - The current algorithm is heavily based on the amount of time
+ * we are willing to spend offchannel configurable via acs_roc_duration_ms,
+ * and acs_num_req_surveys, this will work for the period of time we do
+ * the analysis, so if these values are too low you'd use an ideal channel
+ * only based on the short bursts of traffic on the channel. We can also take
+ * into consideration other data to help us further make a better analysis and
+ * speed out our decision:
+ *
+ * * Do a scan and count the number of BSSes on each channel:
+ * * Assign an HT40 primary channel a high interference aggregate value
+ * * Assign an HT40 secondary channel a lower interference aggregate value
+ * * Use a frequency broker to collect other PHY RF interference:
+ * * BT devices, etc, assign interference value aggregates to these
+ *
+ * - An ideal result would continue surveying the channels and collect a
+ * histogram, the ideal channel then will remain ideal for most of the
+ * collected history.
+ *
+ * - Add wpa_supplicant support for ACS, ideal for P2P
+ *
+ * - Randomize channel study
+ *
+ * - Enable the use passive scan instead of offchannel operation to enable
+ * drivers that do not support offchannel support
+ *
+ * - Get more drivers / firmware to implement / export survey dump
+ *
+ * - Any other OSes interested?
+ */
+
+static void acs_clean_chan_surveys(struct hostapd_channel_data *chan)
+{
+ struct freq_survey *survey, *tmp;
+
+ if (dl_list_empty(&chan->survey_list))
+ return;
+
+ dl_list_for_each_safe(survey, tmp, &chan->survey_list, struct freq_survey, list_member) {
+ dl_list_del(&survey->list_member);
+ os_free(survey);
+ }
+}
+
+static void acs_cleanup(struct hostapd_iface *iface)
+{
+ unsigned int i;
+ struct hostapd_channel_data *chan;
+
+ for (i = 0; i < iface->current_mode->num_channels; i++) {
+ chan = &iface->current_mode->channels[i];
+
+ if (chan->survey_count)
+ acs_clean_chan_surveys(chan);
+
+ dl_list_init(&chan->survey_list);
+ chan->min_nf = 0;
+ chan->survey_count = 0;
+ }
+
+ iface->chans_surveyed = 0;
+ iface->off_channel_freq_idx = 0;
+ iface->acs_num_completed_surveys = 0;
+}
+
+void acs_fail(struct hostapd_iface *iface)
+{
+ wpa_printf(MSG_ERROR, "ACS: failed to start");
+ acs_cleanup(iface);
+}
+
+/*
+ * XXX: Use libm's pow thingy? If we can implement our own log2() then
+ * we can keep this here and remove libm support from hostapd, not
+ * sure if its worth it.
+ */
+static u64 base_to_power(u64 base, u64 pow)
+{
+ u64 result = base;
+
+ if (pow == 0)
+ return 1;
+
+ pow--;
+ while (pow--)
+ result *= base;
+
+ return result;
+}
+
+static long double acs_survey_interference_factor(struct freq_survey *survey, s8 min_nf)
+{
+ long double factor;
+
+ factor = survey->channel_time_busy - survey->channel_time_tx;
+ factor /= (survey->channel_time - survey->channel_time_tx);
+ factor *= (base_to_power(2, survey->nf - min_nf));
+ factor = log2(factor);
+
+ return factor;
+}
+
+static void acs_chan_interference_factor(struct hostapd_iface *iface,
+ struct hostapd_channel_data *chan)
+{
+ struct freq_survey *survey;
+ unsigned int i = 0;
+ long double int_factor = 0;
+
+ if (dl_list_empty(&chan->survey_list) || chan->flag & HOSTAPD_CHAN_DISABLED)
+ return;
+
+ dl_list_for_each(survey, &chan->survey_list, struct freq_survey, list_member) {
+ int_factor = acs_survey_interference_factor(survey, iface->lowest_nf);
+ chan->survey_interference_factor += int_factor;
+ wpa_printf(MSG_DEBUG, "\tsurvey_id: %d"
+ "\tchan_min_nf: %d\tsurvey_interference_factor: %Lf",
+ ++i, chan->min_nf, int_factor);
+ }
+
+ /* XXX: remove survey count */
+
+ chan->survey_interference_factor = chan->survey_interference_factor / chan->survey_count;
+}
+
+static int acs_usable_chan(struct hostapd_channel_data *chan)
+{
+ if (!chan->survey_count)
+ return 0;
+ if (dl_list_empty(&chan->survey_list))
+ return 0;
+ if (chan->flag & HOSTAPD_CHAN_DISABLED)
+ return 0;
+ return 1;
+}
+
+/*
+ * At this point its assumed we have the iface->lowest_nf
+ * and all chan->min_nf values
+ */
+struct hostapd_channel_data *acs_find_ideal_chan(struct hostapd_iface *iface)
+{
+ unsigned int i;
+ struct hostapd_channel_data *chan, *ideal_chan = NULL;
+
+ for (i = 0; i < iface->current_mode->num_channels; i++) {
+ chan = &iface->current_mode->channels[i];
+
+ if (!acs_usable_chan(chan))
+ continue;
+
+ wpa_printf(MSG_DEBUG, "------------------------- "
+ "Survey analysis for channel %d (%d MHz) "
+ "--------------------------------",
+ chan->chan,
+ chan->freq);
+
+ acs_chan_interference_factor(iface, chan);
+
+ wpa_printf(MSG_DEBUG, "\tChannel survey interference factor average: %Lf",
+ chan->survey_interference_factor);
+
+ if (!ideal_chan)
+ ideal_chan = chan;
+ else {
+ if (chan->survey_interference_factor < ideal_chan->survey_interference_factor)
+ ideal_chan = chan;
+ }
+ }
+
+ return ideal_chan;
+}
+
+static enum hostapd_chan_status acs_study_next_freq(struct hostapd_iface *iface)
+{
+ int err;
+ unsigned int i;
+ struct hostapd_channel_data *chan;
+ struct hostapd_data *hapd = iface->bss[0];
+
+ if (iface->off_channel_freq_idx > iface->current_mode->num_channels) {
+ wpa_printf(MSG_ERROR, "ACS: channel index out of bounds");
+ return HOSTAPD_CHAN_INVALID;
+ }
+
+ for (i = iface->off_channel_freq_idx; i < iface->current_mode->num_channels; i++) {
+ chan = &iface->current_mode->channels[i];
+ if (chan->flag & HOSTAPD_CHAN_DISABLED)
+ continue;
+
+ err = hostapd_drv_remain_on_channel(hapd, chan->freq, iface->conf->acs_roc_duration_ms);
+ if (err < 0) {
+ wpa_printf(MSG_ERROR, "ACS: request to go offchannel "
+ "on freq %d MHz failed",
+ chan->freq);
+ return HOSTAPD_CHAN_INVALID;
+ }
+
+ iface->off_channel_freq_idx = i;
+
+ return HOSTAPD_CHAN_ACS;
+ }
+
+ if (!iface->chans_surveyed) {
+ wpa_printf(MSG_ERROR, "ACS: unable to survey any channel");
+ return HOSTAPD_CHAN_INVALID;
+ }
+
+ return HOSTAPD_CHAN_VALID;
+}
+
+static void acs_study_complete(struct hostapd_iface *iface)
+{
+ struct hostapd_channel_data *ideal_chan;
+
+ iface->acs_num_completed_surveys++;
+
+ if (iface->acs_num_completed_surveys < iface->conf->acs_num_req_surveys) {
+ iface->off_channel_freq_idx = 0;
+
+ switch (acs_study_next_freq(iface)) {
+ case HOSTAPD_CHAN_ACS:
+ return;
+ case HOSTAPD_CHAN_VALID:
+ /*
+ * Bullshit, we were expected to do some more bg work
+ * due to the acs_num_req_surveys.
+ */
+ wpa_printf(MSG_ERROR, "ACS: odd loop bug, report this...");
+ case HOSTAPD_CHAN_INVALID:
+ default:
+ goto fail;
+ }
+ }
+
+ if (!iface->chans_surveyed) {
+ wpa_printf(MSG_ERROR, "ACS: unable to collect any "
+ "useful survey data");
+ goto fail;
+ }
+
+ ideal_chan = acs_find_ideal_chan(iface);
+ if (!ideal_chan) {
+ wpa_printf(MSG_ERROR, "ACS: although survey data was collected we "
+ "were unable to compute an ideal channel");
+ goto fail;
+ }
+
+ wpa_printf(MSG_DEBUG, "-------------------------------------------------------------------------");
+ wpa_printf(MSG_INFO, "ACS: Ideal chan: %d (%d MHz) Average interference factor: %Lf",
+ ideal_chan->chan,
+ ideal_chan->freq,
+ ideal_chan->survey_interference_factor);
+ wpa_printf(MSG_DEBUG, "-------------------------------------------------------------------------");
+
+ iface->conf->channel = ideal_chan->chan;
+ /*
+ * Note that iface->conf->secondary_channel will be left as
+ * per your preference for enabling HT40+, HT40- or not using
+ * HT40 at all.
+ */
+
+ /*
+ * hostapd_setup_interface_complete() will return -1 on failure,
+ * 0 on success and 0 is HOSTAPD_CHAN_VALID :)
+ */
+ switch (hostapd_acs_completed(iface)) {
+ case HOSTAPD_CHAN_VALID:
+ acs_cleanup(iface);
+ return;
+ case HOSTAPD_CHAN_INVALID:
+ case HOSTAPD_CHAN_ACS:
+ default:
+ wpa_printf(MSG_ERROR, "ACS: although things seemed "
+ "fine we failed in the end");
+ goto fail;
+ }
+
+fail:
+ acs_fail(iface);
+}
+
+
+static void acs_roc_next(struct hostapd_iface *iface,
+ unsigned int freq,
+ unsigned int duration)
+{
+ struct hostapd_channel_data *chan;
+ struct hostapd_data *hapd = iface->bss[0];
+ int err;
+
+ chan = &iface->current_mode->channels[iface->off_channel_freq_idx];
+
+ wpa_printf(MSG_EXCESSIVE, "ACS: offchannel on freq %d MHz", freq);
+
+ err = hostapd_drv_survey_freq(hapd, freq);
+ if (err) {
+ /* XXX: figure out why we are not getting out of here */
+ wpa_printf(MSG_ERROR, "ACS: failed to get any survey "
+ "data for freq %d MHz", freq);
+ goto fail;
+ }
+
+ wpa_printf(MSG_EXCESSIVE, "ACS: going to next channel...");
+
+ iface->off_channel_freq_idx++;
+
+ switch (acs_study_next_freq(iface)) {
+ case HOSTAPD_CHAN_VALID:
+ acs_study_complete(iface);
+ return;
+ case HOSTAPD_CHAN_ACS:
+ return;
+ case HOSTAPD_CHAN_INVALID:
+ default:
+ goto fail;
+ }
+
+fail:
+ acs_fail(iface);
+}
+
+void hostapd_notify_acs_roc(struct hostapd_iface *iface,
+ unsigned int freq,
+ unsigned int duration,
+ int roc_status)
+{
+ if (roc_status) {
+ acs_fail(iface);
+ return;
+ }
+
+ /* We'll get an event once completed or cancelled */
+}
+
+void hostapd_notify_acs_roc_cancel(struct hostapd_iface *iface,
+ unsigned int freq,
+ unsigned int duration,
+ int roc_status)
+{
+ if (roc_status) {
+ acs_fail(iface);
+ return;
+ }
+
+ acs_roc_next(iface, freq, duration);
+}
+
+static void acs_init_scan_complete(struct hostapd_iface *iface)
+{
+ wpa_printf(MSG_DEBUG, "ACS: using survey based algorithm "
+ "(acs_num_req_surveys=%d acs_roc_duration_ms=%d)",
+ iface->conf->acs_num_req_surveys,
+ iface->conf->acs_roc_duration_ms);
+
+ acs_cleanup(iface);
+
+ iface->acs_num_completed_surveys = 0;
+
+ switch (acs_study_next_freq(iface)) {
+ case HOSTAPD_CHAN_ACS:
+ return;
+ case HOSTAPD_CHAN_VALID:
+ case HOSTAPD_CHAN_INVALID:
+ default:
+ acs_fail(iface);
+ }
+}
+
+static int acs_init_scan(struct hostapd_iface *iface)
+{
+ struct wpa_driver_scan_params params;
+
+ wpa_printf(MSG_DEBUG, "ACS: initial scan just to kick off the hw a bit...");
+ os_memset(¶ms, 0, sizeof(params));
+
+ if (hostapd_driver_scan(iface->bss[0], ¶ms) < 0) {
+ wpa_printf(MSG_ERROR, "ACS: Failed to request initial scan");
+ return -1;
+ }
+
+ iface->scan_cb = acs_init_scan_complete;
+ return 1;
+}
+
+static int acs_sanity_check(struct hostapd_iface *iface)
+{
+
+ /* Meh? */
+ if (iface->chans_surveyed) {
+ wpa_printf(MSG_ERROR, "ACS: no usable channels found");
+ return -1;
+ }
+
+ if (!(iface->drv_flags & WPA_DRIVER_FLAGS_OFFCHANNEL_TX)) {
+ wpa_printf(MSG_ERROR, "ACS: offchannel TX support required");
+ return -1;
+ }
+
+ return 0;
+}
+
+enum hostapd_chan_status acs_init(struct hostapd_iface *iface)
+{
+ int err;
+
+ wpa_printf(MSG_INFO, "ACS: automatic channel selection started, this may take a bit");
+
+ err = acs_sanity_check(iface);
+ if (err < 0)
+ return HOSTAPD_CHAN_INVALID;
+
+ err = acs_init_scan(iface);
+ if (err < 0)
+ return HOSTAPD_CHAN_INVALID;
+ else if (err == 1)
+ return HOSTAPD_CHAN_ACS;
+ else
+ return HOSTAPD_CHAN_INVALID;
+}
diff --git a/src/ap/acs.h b/src/ap/acs.h
new file mode 100644
index 0000000..1397160
--- /dev/null
+++ b/src/ap/acs.h
@@ -0,0 +1,40 @@
+#ifndef __ACS_H
+#define __ACS_H
+
+#include "utils/common.h"
+#include "ap/hostapd.h"
+#include "list.h"
+
+#ifdef CONFIG_ACS
+enum hostapd_chan_status acs_init(struct hostapd_iface *iface);
+void hostapd_notify_acs_roc(struct hostapd_iface *iface,
+ unsigned int freq,
+ unsigned int duration,
+ int roc_status);
+void hostapd_notify_acs_roc_cancel(struct hostapd_iface *iface,
+ unsigned int freq,
+ unsigned int duration,
+ int roc_status);
+int hostapd_acs_completed(struct hostapd_iface *iface);
+#else
+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=1");
+ return HOSTAPD_CHAN_INVALID;
+}
+static inline void hostapd_notify_acs_roc(struct hostapd_iface *iface,
+ unsigned int freq,
+ unsigned int duration,
+ int roc_status)
+{
+}
+static inline void hostapd_notify_acs_roc_cancel(struct hostapd_iface *iface,
+ unsigned int freq,
+ unsigned int duration,
+ int roc_status)
+{
+}
+#endif /* CONFIG_ACS */
+
+#endif /* __ACS_H */
diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
index e77716b..10751d3 100644
--- a/src/ap/ap_config.c
+++ b/src/ap/ap_config.c
@@ -96,6 +96,19 @@ void hostapd_config_defaults_bss(struct hostapd_bss_config *bss)
}
+#ifdef CONFIG_ACS
+static void hostapd_config_acs_defaults(struct hostapd_config *conf)
+{
+ conf->acs_num_req_surveys = 10;
+ conf->acs_roc_duration_ms = 5;
+}
+#else
+static void hostapd_config_acs_defaults(struct hostapd_config *conf)
+{
+}
+#endif
+
+
struct hostapd_config * hostapd_config_defaults(void)
{
#define ecw2cw(ecw) ((1 << (ecw)) - 1)
@@ -162,6 +175,8 @@ struct hostapd_config * hostapd_config_defaults(void)
conf->ht_capab = HT_CAP_INFO_SMPS_DISABLED;
+ hostapd_config_acs_defaults(conf);
+
return conf;
}
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index 0a3e76e..30babdf 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -393,6 +393,10 @@ struct hostapd_config {
int ieee80211n;
int secondary_channel;
int require_ht;
+#ifdef CONFIG_ACS
+ unsigned int acs_num_req_surveys;
+ unsigned int acs_roc_duration_ms;
+#endif
};
diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h
index 24c7e95..c9ff723 100644
--- a/src/ap/ap_drv_ops.h
+++ b/src/ap/ap_drv_ops.h
@@ -228,4 +228,5 @@ static inline int hostapd_drv_survey(struct hostapd_data *hapd)
return hostapd_drv_survey_freq(hapd, 0);
}
+
#endif /* AP_DRV_OPS */
diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c
index 6953b71..d94ac62 100644
--- a/src/ap/drv_callbacks.c
+++ b/src/ap/drv_callbacks.c
@@ -35,6 +35,7 @@
#include "wps_hostapd.h"
#include "ap_drv_ops.h"
#include "ap_config.h"
+#include "acs.h"
int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr,
@@ -531,7 +532,7 @@ static void hostapd_event_roc(struct hostapd_data *hapd,
int err;
err = hostapd_roc_channel_check(iface);
- /* XXX: pass err to listeners, no one yet */
+ hostapd_notify_acs_roc(iface, freq, duration, err);
}
static void hostapd_event_roc_cancel(struct hostapd_data *hapd,
@@ -542,7 +543,7 @@ static void hostapd_event_roc_cancel(struct hostapd_data *hapd,
int err;
err = hostapd_roc_channel_check(iface);
- /* XXX: pass err to listeners, no one yet */
+ hostapd_notify_acs_roc_cancel(iface, freq, duration, err);
}
struct hostapd_channel_data *hostapd_get_mode_channel(struct hostapd_iface *iface,
diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c
index d8af571..79f9b66 100644
--- a/src/ap/hostapd.c
+++ b/src/ap/hostapd.c
@@ -700,6 +700,8 @@ static int setup_interface(struct hostapd_iface *iface)
"channel. (%d)", ret);
return -1;
}
+ if (ret == 1)
+ return 0;
ret = hostapd_check_ht_capab(iface);
if (ret < 0)
return -1;
diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
index 1095d2b..184e221 100644
--- a/src/ap/hostapd.h
+++ b/src/ap/hostapd.h
@@ -239,6 +239,12 @@ struct hostapd_iface {
/* lowest observed noise floor in dBm */
s8 lowest_nf;
+#ifdef CONFIG_ACS
+ /* ACS helpers */
+ unsigned int acs_num_completed_surveys;
+ unsigned int acs_num_req_surveys;
+#endif
+
void (*scan_cb)(struct hostapd_iface *iface);
int (*ctrl_iface_init)(struct hostapd_data *hapd);
diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c
index 887744b..4596681 100644
--- a/src/ap/hw_features.c
+++ b/src/ap/hw_features.c
@@ -25,6 +25,7 @@
#include "ap_config.h"
#include "ap_drv_ops.h"
#include "hw_features.h"
+#include "acs.h"
void hostapd_free_hw_features(struct hostapd_hw_modes *hw_features,
@@ -648,10 +649,18 @@ static enum hostapd_chan_status hostapd_check_chans(struct hostapd_iface *iface)
}
/*
- * The user set channel=0 which is used to trigger ACS,
- * which we do not yet support.
+ * The user set channel=0 or channel=acs_survey
+ * which is used to trigger ACS.
*/
- return HOSTAPD_CHAN_INVALID;
+
+ switch (acs_init(iface)) {
+ case HOSTAPD_CHAN_ACS:
+ return HOSTAPD_CHAN_ACS;
+ case HOSTAPD_CHAN_VALID:
+ case HOSTAPD_CHAN_INVALID:
+ default:
+ return HOSTAPD_CHAN_INVALID;
+ }
}
static void hostapd_notify_bad_chans(struct hostapd_iface *iface)
@@ -671,6 +680,36 @@ static void hostapd_notify_bad_chans(struct hostapd_iface *iface)
"Hardware does not support configured channel");
}
+int hostapd_acs_completed(struct hostapd_iface *iface)
+{
+ int ret;
+
+ switch (hostapd_check_chans(iface)) {
+ case HOSTAPD_CHAN_VALID:
+ break;
+ case HOSTAPD_CHAN_ACS: /* WTF */
+ wpa_printf(MSG_ERROR, "ACS complete but not.. odd report this..");
+ hostapd_notify_bad_chans(iface);
+ return -1;
+ case HOSTAPD_CHAN_INVALID:
+ default:
+ wpa_printf(MSG_ERROR, "ACS picked unusable channels");
+ hostapd_notify_bad_chans(iface);
+ return -1;
+ }
+
+ ret = hostapd_check_ht_capab(iface);
+ if (ret < 0)
+ return -1;
+ if (ret == 1) {
+ wpa_printf(MSG_DEBUG, "Interface initialization will "
+ "be completed in a callback");
+ return 0;
+ }
+
+ return hostapd_setup_interface_complete(iface, 0);
+}
+
/**
* hostapd_select_hw_mode - Select the hardware mode
* @iface: Pointer to interface data.
@@ -708,7 +747,8 @@ int hostapd_select_hw_mode(struct hostapd_iface *iface)
switch (hostapd_check_chans(iface)) {
case HOSTAPD_CHAN_VALID:
return 0;
- case HOSTAPD_CHAN_ACS: /* ACS not supported yet */
+ case HOSTAPD_CHAN_ACS: /* ACS will run and later complete */
+ return 1;
case HOSTAPD_CHAN_INVALID:
default:
hostapd_notify_bad_chans(iface);
diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index 0e1c5c1..fd55f11 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -75,6 +75,88 @@ struct hostapd_channel_data {
*/
s8 min_nf;
+#ifdef CONFIG_ACS
+ /**
+ * survey_interference_factor - computed interference factor on this channel
+ * The survey interference factor is defined as the ratio of the observed busy time
+ * over the time we spent on the channel, this value is then amplified by the observed
+ * noise floor on the channel in comparison to the lowest noise floor observed on the
+ * entire band.
+ *
+ * This corresponds to:
+ * ---
+ * (busy time - tx time) / (active time - tx time) * 2^(chan_nf + band_min_nf)
+ * ---
+ *
+ * The coefficient of of 2 reflects the way power in "far-field"
+ * radiation decreases as the square of distance from the antenna [1].
+ * What this does is it decreases the observed busy time ratio if the
+ * noise observed was low but increases it if the noise was high,
+ * proportionally to the way "far field" radiation changes over
+ * distance. Since the values obtained here can vary from fractional to
+ * millions the sane thing to do here is to use log2() to reflect the
+ * observed interference factor. log2() values less than 0 then
+ * represent fractional results, while > 1 values non-fractional
+ * results. The computation of the interference factor then becomes:
+ *
+ * ---
+ * log2( (busy time - tx time) / (active time - tx time) * 2^(chan_nf + band_min_nf))
+ * --- or due to logarithm identities:
+ * log2(busy time - tx time) - log2(active time - tx time) + log2(2^(chan_nf + band_min_nf))
+ * ---
+ *
+ * All this is "interference factor" is purely subjective and ony time
+ * will tell how usable this is. By using the minimum noise floor we
+ * remove any possible issues due to card calibration. The computation
+ * of the interference factor then is dependent on what the card itself
+ * picks up as the minimum noise, not an actual real possible card
+ * noise value.
+ *
+ * Example values:
+ *
+ * 2412 MHz: 7.429173
+ * 2417 MHz: 10.460830
+ * 2422 MHz: 12.671070
+ * 2427 MHz: 13.583892
+ * 2432 MHz: 13.405357
+ * 2442 MHz: 13.566887
+ * 2447 MHz: 15.630824
+ * 2452 MHz: 14.639748
+ * 2457 MHz: 14.139193
+ * 2467 MHz: 11.914643
+ * 2472 MHz: 16.996074
+ * 2484 MHz: 15.175455
+ * 5180 MHz: -0.218548
+ * 5200 MHz: -2.204059
+ * 5220 MHz: -1.762898
+ * 5240 MHz: -1.314665
+ * 5260 MHz: -3.100989
+ * 5280 MHz: -2.157037
+ * 5300 MHz: -1.842629
+ * 5320 MHz: -1.498928
+ * 5500 MHz: 3.304770
+ * 5520 MHz: 2.345992
+ * 5540 MHz: 2.749775
+ * 5560 MHz: 2.390887
+ * 5580 MHz: 2.592958
+ * 5600 MHz: 2.420149
+ * 5620 MHz: 2.650282
+ * 5640 MHz: 2.954027
+ * 5660 MHz: 2.991007
+ * 5680 MHz: 2.955472
+ * 5700 MHz: 2.280499
+ * 5745 MHz: 2.388630
+ * 5765 MHz: 2.332542
+ * 5785 MHz: 0.955708
+ * 5805 MHz: 1.025377
+ * 5825 MHz: 0.843392
+ *
+ * In this example the ideal channel then is 5260 MHz
+ *
+ * [1] http://en.wikipedia.org/wiki/Near_and_far_field
+ */
+ long double survey_interference_factor;
+#endif
};
/**
--
1.7.4.15.g7811d
On Fri, Jul 15, 2011 at 2:38 PM, Swaminathan Vasanth
<[email protected]> wrote:
>> Other algorithms for ACS shoudl be easy to plug in, for example,
>> spectral scan ;)
>>
>> Anyway, to enable and test this thing just set CONFIG_ACS=1
>> on your build config and then something like this on your
>> hostapd.conf:
>>
>> channel=acs_survey
>> acs_num_req_surveys=10
>> acs_roc_duration_ms=100
>>
>> Test results, rants are appreciated.
>>
>
> Hi,
>
> I am trying to enable the Auto Channel Selection in hostapd with ath5k and
> ath9k drivers. I cloned the source of hostap from the git and applied the patch
> (
> http://www.kernel.org/pub/linux/kernel/people/mcgrof/patches/hostapd/acs-all-in-one-v2.patch
> ). I then went to the hostapd directory of hostap and did make and make install.
> Followed instruction from (
> http://wireless.kernel.org/en/users/Documentation/hostapd#Download_and_compile_hostapd
> ).
>
> While building the software i enabled CONFIG_ACS=y in the defconfig file
> present in /hostap/hostapd/ to .config and performed the build. I then did a
> make install and configured the hostapd.conf file.
>
> Hostapd is working fine for me without ACS but when i enable channel=0 or
> channel=acs_survey and uncomment the
>
> acs_num_req_surveys=10
> acs_roc_duration_ms=5
>
> options for ACS to be enabled it throws this error when starting hostapd,
>
>
>> sudo hostapd hostapd.conf
>
> Configuration file: hostapd.conf
> Line 113: set as '0' tries to enable ACS but CONFIG_ACS disabled
> Line 125: invalid acs_num_req_surveys 10 (CONFIG_ACS disabled)
> Line 126: invalid acs_roc_duration_ms 5 (CONFIG_ACS disabled)1..5000)
> 3 errors found in configuration file 'hostapd.conf'
>
> Infact i enabled CONFIG_ACS when building.
Hmm, try:
make V=1 | grep -i ACS
> Any pointers on what to do to enable ACS please.
>
> Awaiting your kind reply.
I'm about to respin these patches to apply to the latest hostapd.git
with 1 fix. Try that.
Luis
> Other algorithms for ACS shoudl be easy to plug in, for example,
> spectral scan ;)
>
> Anyway, to enable and test this thing just set CONFIG_ACS=1
> on your build config and then something like this on your
> hostapd.conf:
>
> channel=acs_survey
> acs_num_req_surveys=10
> acs_roc_duration_ms=100
>
> Test results, rants are appreciated.
>
Hi,
I am trying to enable the Auto Channel Selection in hostapd with ath5k and
ath9k drivers. I cloned the source of hostap from the git and applied the patch
(
http://www.kernel.org/pub/linux/kernel/people/mcgrof/patches/hostapd/acs-all-in-one-v2.patch
). I then went to the hostapd directory of hostap and did make and make install.
Followed instruction from (
http://wireless.kernel.org/en/users/Documentation/hostapd#Download_and_compile_hostapd
).
While building the software i enabled CONFIG_ACS=y in the defconfig file
present in /hostap/hostapd/ to .config and performed the build. I then did a
make install and configured the hostapd.conf file.
Hostapd is working fine for me without ACS but when i enable channel=0 or
channel=acs_survey and uncomment the
acs_num_req_surveys=10
acs_roc_duration_ms=5
options for ACS to be enabled it throws this error when starting hostapd,
> sudo hostapd hostapd.conf
Configuration file: hostapd.conf
Line 113: set as '0' tries to enable ACS but CONFIG_ACS disabled
Line 125: invalid acs_num_req_surveys 10 (CONFIG_ACS disabled)
Line 126: invalid acs_roc_duration_ms 5 (CONFIG_ACS disabled)1..5000)
3 errors found in configuration file 'hostapd.conf'
Infact i enabled CONFIG_ACS when building.
Any pointers on what to do to enable ACS please.
Awaiting your kind reply.
Thanks,
Luis R. Rodriguez <lrodriguez@...> writes:
> Other algorithms for ACS shoudl be easy to plug in, for example,
> spectral scan ;)
>
> Anyway, to enable and test this thing just set CONFIG_ACS=1
> on your build config and then something like this on your
> hostapd.conf:
>
> channel=acs_survey
> acs_num_req_surveys=10
> acs_roc_duration_ms=100
>
> Test results, rants are appreciated.
I am willing to test the hostapd auto channel selection implementation by
integrating it with Openwrt firmware with Netgear WNDR 3700 Router. Currently i
got the patch (acs-all-in-one-v1.patch) and integrating it with the stable
release of hostapd 0.6.10.
Hope it will be useful for you.
BR,
Vasanth
>
> Luis R. Rodriguez (4):
> hostapd: split up channel checking into helpers
> hostapd: add offchannel support
> hostapd: add survey dump support
> hostapd: add Automatic Channel Selection (ACS) support