hostapd: Add survey dump support
authorMichal Kazior <michal.kazior@tieto.com>
Wed, 24 Jul 2013 06:59:13 +0000 (08:59 +0200)
committerJouni Malinen <j@w1.fi>
Sun, 25 Aug 2013 15:35:25 +0000 (18:35 +0300)
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-hostap: Michal Kazior <michal.kazior@tieto.com>

src/ap/ap_drv_ops.h
src/ap/drv_callbacks.c
src/ap/hostapd.h
src/drivers/driver.h
src/drivers/driver_common.c
src/drivers/driver_nl80211.c

index cfc30ce..23277b6 100644 (file)
@@ -225,4 +225,14 @@ static inline void hostapd_drv_poll_client(struct hostapd_data *hapd,
        hapd->driver->poll_client(hapd->drv_priv, own_addr, addr, qos);
 }
 
+static inline int hostapd_drv_get_survey(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);
+}
+
 #endif /* AP_DRV_OPS */
index fa4b5e4..e3ba224 100644 (file)
@@ -715,6 +715,72 @@ static void hostapd_event_eapol_rx(struct hostapd_data *hapd, const u8 *src,
 }
 
 
+static struct hostapd_channel_data * hostapd_get_mode_channel(
+       struct hostapd_iface *iface, unsigned int freq)
+{
+       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 ((unsigned int) 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 (dl_list_empty(&chan->survey_list))
+                       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, "No survey data received");
+               return;
+       }
+
+       dl_list_for_each_safe(survey, tmp, &survey_results->survey_list,
+                             struct freq_survey, list) {
+               chan = hostapd_get_mode_channel(iface, survey->freq);
+               if (!chan)
+                       continue;
+               if (chan->flag & HOSTAPD_CHAN_DISABLED)
+                       continue;
+
+               dl_list_del(&survey->list);
+               dl_list_add_tail(&chan->survey_list, &survey->list);
+
+               hostapd_update_nf(iface, chan, survey);
+
+               iface->chans_surveyed++;
+       }
+}
+
+
 void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
                          union wpa_event_data *data)
 {
@@ -856,6 +922,9 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
                        hapd, data->connect_failed_reason.addr,
                        data->connect_failed_reason.code);
                break;
+       case EVENT_SURVEY:
+               hostapd_event_get_survey(hapd, &data->survey_results);
+               break;
        default:
                wpa_printf(MSG_DEBUG, "Unknown event %d", event);
                break;
index df16a18..673dd34 100644 (file)
@@ -301,6 +301,15 @@ struct hostapd_iface {
        int olbc_ht;
 
        u16 ht_op_mode;
+
+       /* 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);
 };
 
index cb46238..6fb05ac 100644 (file)
@@ -20,6 +20,7 @@
 #define WPA_SUPPLICANT_DRIVER_VERSION 4
 
 #include "common/defs.h"
+#include "utils/list.h"
 
 #define HOSTAPD_CHAN_DISABLED 0x00000001
 #define HOSTAPD_CHAN_PASSIVE_SCAN 0x00000002
@@ -55,9 +56,20 @@ struct hostapd_channel_data {
        int flag;
 
        /**
-        * max_tx_power - maximum transmit power in dBm
+        * max_tx_power - Maximum transmit power in dBm
         */
        u8 max_tx_power;
+
+       /*
+        * 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;
 };
 
 #define HOSTAPD_MODE_FLAG_HT_INFO_KNOWN BIT(0)
@@ -2728,6 +2740,31 @@ struct wpa_driver_ops {
         * mode.
         */
        int (*stop_ap)(void *priv);
+
+       /**
+        * 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 retrieve:
+        *
+        * - 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 medium is busy and we cannot
+        *   transmit
+        * - the amount of time we have spent receiving data
+        * - the amount of time we have spent transmitting data
+        *
+        * This data can be used 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);
 };
 
 
@@ -3212,11 +3249,57 @@ enum wpa_event_type {
         *
         * The channel which was previously unavailable is now available again.
         */
-       EVENT_DFS_NOP_FINISHED
+       EVENT_DFS_NOP_FINISHED,
+
+       /*
+       * EVENT_SURVEY - Received survey data
+       *
+       * This event gets triggered when a driver query is issued for survey
+       * data and the requested data becomes available. 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 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 freq_survey - 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
+ * @filled: bitmask indicating which fields have been reported, see
+ *     SURVEY_HAS_* defines.
+ * @list: Internal list pointers
+ */
+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;
+       unsigned int filled;
+       struct dl_list list;
+};
+
+#define SURVEY_HAS_NF BIT(0)
+#define SURVEY_HAS_CHAN_TIME BIT(1)
+#define SURVEY_HAS_CHAN_TIME_BUSY BIT(2)
+#define SURVEY_HAS_CHAN_TIME_RX BIT(3)
+#define SURVEY_HAS_CHAN_TIME_TX BIT(4)
+
+
+/**
  * union wpa_event_data - Additional data for wpa_supplicant_event() calls
  */
 union wpa_event_data {
@@ -3858,6 +3941,17 @@ union wpa_event_data {
        struct dfs_event {
                int freq;
        } dfs_event;
+
+       /**
+        * 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; /* struct freq_survey */
+       } survey_results;
 };
 
 /**
index 12ccc14..8d1d22e 100644 (file)
@@ -84,6 +84,7 @@ const char * event_to_string(enum wpa_event_type event)
        E2S(DFS_CAC_FINISHED);
        E2S(DFS_CAC_ABORTED);
        E2S(DFS_NOP_FINISHED);
+       E2S(SURVEY);
        }
 
        return "UNKNOWN";
index 2dd548a..6792e04 100644 (file)
@@ -9971,6 +9971,185 @@ static int nl80211_flush_pmkid(void *priv)
 }
 
 
+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) {
+               dl_list_del(&survey->list);
+               os_free(survey);
+       }
+}
+
+
+static void add_survey(struct nlattr **sinfo, u32 ifidx,
+                      struct dl_list *survey_list)
+{
+       struct freq_survey *survey;
+
+       survey = os_zalloc(sizeof(struct freq_survey));
+       if  (!survey)
+               return;
+
+       survey->ifidx = ifidx;
+       survey->freq = nla_get_u32(sinfo[NL80211_SURVEY_INFO_FREQUENCY]);
+       survey->filled = 0;
+
+       if (sinfo[NL80211_SURVEY_INFO_NOISE]) {
+               survey->nf = (int8_t)
+                       nla_get_u8(sinfo[NL80211_SURVEY_INFO_NOISE]);
+               survey->filled |= SURVEY_HAS_NF;
+       }
+
+       if (sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME]) {
+               survey->channel_time =
+                       nla_get_u64(sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME]);
+               survey->filled |= SURVEY_HAS_CHAN_TIME;
+       }
+
+       if (sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY]) {
+               survey->channel_time_busy =
+                       nla_get_u64(sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY]);
+               survey->filled |= SURVEY_HAS_CHAN_TIME_BUSY;
+       }
+
+       if (sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_RX]) {
+               survey->channel_time_rx =
+                       nla_get_u64(sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_RX]);
+               survey->filled |= SURVEY_HAS_CHAN_TIME_RX;
+       }
+
+       if (sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_TX]) {
+               survey->channel_time_tx =
+                       nla_get_u64(sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_TX]);
+               survey->filled |= SURVEY_HAS_CHAN_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 filled=%04x)",
+                  survey->freq,
+                  survey->nf,
+                  (unsigned long int) survey->channel_time,
+                  (unsigned long int) survey->channel_time_busy,
+                  (unsigned long int) survey->channel_time_tx,
+                  (unsigned long int) survey->channel_time_rx,
+                  survey->filled);
+
+       dl_list_add_tail(survey_list, &survey->list);
+}
+
+
+static int check_survey_ok(struct nlattr **sinfo, u32 surveyed_freq,
+                          unsigned int freq_filter)
+{
+       if (!freq_filter)
+               return 1;
+
+       return freq_filter == surveyed_freq;
+}
+
+
+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;
+
+       nl80211_cmd(drv, msg, NLM_F_DUMP, NL80211_CMD_GET_SURVEY);
+
+       NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->ifindex);
+
+       if (freq)
+               data.survey_results.freq_filter = freq;
+
+       do {
+               wpa_printf(MSG_DEBUG, "nl80211: Fetch survey data");
+               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;
+}
+
+
 static void nl80211_set_rekey_info(void *priv, const u8 *kek, const u8 *kck,
                                   const u8 *replay_ctr)
 {
@@ -10597,4 +10776,5 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = {
 #endif /* CONFIG_TDLS */
        .update_ft_ies = wpa_driver_nl80211_update_ft_ies,
        .get_mac_addr = wpa_driver_nl80211_get_macaddr,
+       .get_survey = wpa_driver_nl80211_get_survey,
 };