hostapd: Allow ACS to be offloaded to the driver
[mech_eap.git] / src / ap / acs.c
index 05d0fa5..97cf26f 100644 (file)
@@ -13,6 +13,7 @@
 #include "utils/common.h"
 #include "utils/list.h"
 #include "common/ieee802_11_defs.h"
+#include "common/wpa_ctrl.h"
 #include "drivers/driver.h"
 #include "hostapd.h"
 #include "ap_drv_ops.h"
@@ -279,10 +280,11 @@ static void acs_cleanup(struct hostapd_iface *iface)
 }
 
 
-void acs_fail(struct hostapd_iface *iface)
+static void acs_fail(struct hostapd_iface *iface)
 {
        wpa_printf(MSG_ERROR, "ACS: Failed to start");
        acs_cleanup(iface);
+       hostapd_disable_iface(iface);
 }
 
 
@@ -358,7 +360,20 @@ static int acs_usable_ht40_chan(struct hostapd_channel_data *chan)
                                157, 184, 192 };
        unsigned int i;
 
-       for (i = 0; i < sizeof(allowed) / sizeof(allowed[0]); i++)
+       for (i = 0; i < ARRAY_SIZE(allowed); i++)
+               if (chan->chan == allowed[i])
+                       return 1;
+
+       return 0;
+}
+
+
+static int acs_usable_vht80_chan(struct hostapd_channel_data *chan)
+{
+       const int allowed[] = { 36, 52, 100, 116, 132, 149 };
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(allowed); i++)
                if (chan->chan == allowed[i])
                        return 1;
 
@@ -472,7 +487,7 @@ static struct hostapd_channel_data *acs_find_chan(struct hostapd_iface *iface,
        for (i = 0; i < iface->current_mode->num_channels; i++) {
                chan = &iface->current_mode->channels[i];
 
-               if (!acs_usable_chan(chan))
+               if (chan->flag & HOSTAPD_CHAN_DISABLED)
                        continue;
 
                if (chan->freq == freq)
@@ -492,7 +507,8 @@ static struct hostapd_channel_data *acs_find_chan(struct hostapd_iface *iface,
 static struct hostapd_channel_data *
 acs_find_ideal_chan(struct hostapd_iface *iface)
 {
-       struct hostapd_channel_data *chan, *adj_chan, *ideal_chan = NULL;
+       struct hostapd_channel_data *chan, *adj_chan, *ideal_chan = NULL,
+               *rand_chan = NULL;
        long double factor, ideal_factor = 0;
        int i, j;
        int n_chans = 1;
@@ -524,9 +540,10 @@ acs_find_ideal_chan(struct hostapd_iface *iface)
        for (i = 0; i < iface->current_mode->num_channels; i++) {
                chan = &iface->current_mode->channels[i];
 
-               if (!acs_usable_chan(chan))
+               if (chan->flag & HOSTAPD_CHAN_DISABLED)
                        continue;
 
+
                /* HT40 on 5 GHz has a limited set of primary channels as per
                 * 11n Annex J */
                if (iface->current_mode->mode == HOSTAPD_MODE_IEEE80211A &&
@@ -538,14 +555,26 @@ acs_find_ideal_chan(struct hostapd_iface *iface)
                        continue;
                }
 
-               factor = chan->interference_factor;
+               if (iface->current_mode->mode == HOSTAPD_MODE_IEEE80211A &&
+                   iface->conf->ieee80211ac &&
+                   iface->conf->vht_oper_chwidth == 1 &&
+                   !acs_usable_vht80_chan(chan)) {
+                       wpa_printf(MSG_DEBUG, "ACS: Channel %d: not allowed as primary channel for VHT80",
+                                  chan->chan);
+                       continue;
+               }
+
+               factor = 0;
+               if (acs_usable_chan(chan))
+                       factor = chan->interference_factor;
 
                for (j = 1; j < n_chans; j++) {
                        adj_chan = acs_find_chan(iface, chan->freq + (j * 20));
                        if (!adj_chan)
                                break;
 
-                       factor += adj_chan->interference_factor;
+                       if (acs_usable_chan(adj_chan))
+                               factor += adj_chan->interference_factor;
                }
 
                if (j != n_chans) {
@@ -564,22 +593,22 @@ acs_find_ideal_chan(struct hostapd_iface *iface)
 
                                adj_chan = acs_find_chan(iface, chan->freq +
                                                         (j * 20) - 5);
-                               if (adj_chan)
+                               if (adj_chan && acs_usable_chan(adj_chan))
                                        factor += adj_chan->interference_factor;
 
                                adj_chan = acs_find_chan(iface, chan->freq +
                                                         (j * 20) - 10);
-                               if (adj_chan)
+                               if (adj_chan && acs_usable_chan(adj_chan))
                                        factor += adj_chan->interference_factor;
 
                                adj_chan = acs_find_chan(iface, chan->freq +
                                                         (j * 20) + 5);
-                               if (adj_chan)
+                               if (adj_chan && acs_usable_chan(adj_chan))
                                        factor += adj_chan->interference_factor;
 
                                adj_chan = acs_find_chan(iface, chan->freq +
                                                         (j * 20) + 10);
-                               if (adj_chan)
+                               if (adj_chan && acs_usable_chan(adj_chan))
                                        factor += adj_chan->interference_factor;
                        }
                }
@@ -587,39 +616,49 @@ acs_find_ideal_chan(struct hostapd_iface *iface)
                wpa_printf(MSG_DEBUG, "ACS:  * channel %d: total interference = %Lg",
                           chan->chan, factor);
 
-               if (!ideal_chan || factor < ideal_factor) {
+               if (acs_usable_chan(chan) &&
+                   (!ideal_chan || factor < ideal_factor)) {
                        ideal_factor = factor;
                        ideal_chan = chan;
                }
+
+               /* This channel would at least be usable */
+               if (!rand_chan)
+                       rand_chan = chan;
        }
 
-       if (ideal_chan)
+       if (ideal_chan) {
                wpa_printf(MSG_DEBUG, "ACS: Ideal channel is %d (%d MHz) with total interference factor of %Lg",
                           ideal_chan->chan, ideal_chan->freq, ideal_factor);
+               return ideal_chan;
+       }
 
-       return ideal_chan;
+       return rand_chan;
 }
 
 
 static void acs_adjust_vht_center_freq(struct hostapd_iface *iface)
 {
+       int offset;
+
        wpa_printf(MSG_DEBUG, "ACS: Adjusting VHT center frequency");
 
        switch (iface->conf->vht_oper_chwidth) {
        case VHT_CHANWIDTH_USE_HT:
-               iface->conf->vht_oper_centr_freq_seg0_idx =
-                       iface->conf->channel + 2;
+               offset = 2 * iface->conf->secondary_channel;
                break;
        case VHT_CHANWIDTH_80MHZ:
-               iface->conf->vht_oper_centr_freq_seg0_idx =
-                       iface->conf->channel + 6;
+               offset = 6;
                break;
        default:
                /* TODO: How can this be calculated? Adjust
                 * acs_find_ideal_chan() */
                wpa_printf(MSG_INFO, "ACS: Only VHT20/40/80 is supported now");
-               break;
+               return;
        }
+
+       iface->conf->vht_oper_centr_freq_seg0_idx =
+               iface->conf->channel + offset;
 }
 
 
@@ -710,21 +749,24 @@ static void acs_scan_complete(struct hostapd_iface *iface)
        err = hostapd_drv_get_survey(iface->bss[0], 0);
        if (err) {
                wpa_printf(MSG_ERROR, "ACS: Failed to get survey data");
-               acs_fail(iface);
+               goto fail;
        }
 
        if (++iface->acs_num_completed_scans < iface->conf->acs_num_scans) {
                err = acs_request_scan(iface);
                if (err) {
                        wpa_printf(MSG_ERROR, "ACS: Failed to request scan");
-                       acs_fail(iface);
-                       return;
+                       goto fail;
                }
 
                return;
        }
 
        acs_study(iface);
+       return;
+fail:
+       hostapd_acs_completed(iface, 1);
+       acs_fail(iface);
 }
 
 
@@ -759,6 +801,7 @@ static int acs_request_scan(struct hostapd_iface *iface)
        if (hostapd_driver_scan(iface->bss[0], &params) < 0) {
                wpa_printf(MSG_ERROR, "ACS: Failed to request initial scan");
                acs_cleanup(iface);
+               os_free(params.freqs);
                return -1;
        }
 
@@ -773,11 +816,22 @@ enum hostapd_chan_status acs_init(struct hostapd_iface *iface)
 
        wpa_printf(MSG_INFO, "ACS: Automatic channel selection started, this may take a bit");
 
+       if (iface->drv_flags & WPA_DRIVER_FLAGS_ACS_OFFLOAD) {
+               wpa_printf(MSG_INFO, "ACS: Offloading to driver");
+               err = hostapd_drv_do_acs(iface->bss[0]);
+               if (err)
+                       return HOSTAPD_CHAN_INVALID;
+               return HOSTAPD_CHAN_ACS;
+       }
+
        acs_cleanup(iface);
 
        err = acs_request_scan(iface);
        if (err < 0)
                return HOSTAPD_CHAN_INVALID;
 
+       hostapd_set_state(iface, HAPD_IFACE_ACS);
+       wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, ACS_EVENT_STARTED);
+
        return HOSTAPD_CHAN_ACS;
 }