Parallelize distinct radio work operations
authorKanchanapally, Vidyullatha <vkanchan@qti.qualcomm.com>
Fri, 25 Sep 2015 11:36:27 +0000 (17:06 +0530)
committerJouni Malinen <j@w1.fi>
Wed, 30 Sep 2015 22:41:46 +0000 (01:41 +0300)
This commit contains the necessary changes to parallelize
distinct radio work operations which are different in type and
the band used, only when the underlying driver is capable of
supporting such simultaneous offchannel operations.

Signed-off-by: Jouni Malinen <jouni@qca.qualcomm.com>
wpa_supplicant/wpa_supplicant.c
wpa_supplicant/wpa_supplicant_i.h

index ef55fdc..9400927 100644 (file)
@@ -3870,6 +3870,56 @@ static int wpas_set_wowlan_triggers(struct wpa_supplicant *wpa_s,
 }
 
 
+static enum wpa_radio_work_band wpas_freq_to_band(int freq)
+{
+       if (freq < 3000)
+               return BAND_2_4_GHZ;
+       if (freq > 50000)
+               return BAND_60_GHZ;
+       return BAND_5_GHZ;
+}
+
+
+static unsigned int wpas_get_bands(struct wpa_supplicant *wpa_s,
+                                  const int *freqs)
+{
+       int i;
+       unsigned int band = 0;
+
+       if (freqs) {
+               /* freqs are specified for the radio work */
+               for (i = 0; freqs[i]; i++)
+                       band |= wpas_freq_to_band(freqs[i]);
+       } else {
+               /*
+                * freqs are not specified, implies all
+                * the supported freqs by HW
+                */
+               for (i = 0; i < wpa_s->hw.num_modes; i++) {
+                       if (wpa_s->hw.modes[i].num_channels != 0) {
+                               if (wpa_s->hw.modes[i].mode ==
+                                   HOSTAPD_MODE_IEEE80211B ||
+                                   wpa_s->hw.modes[i].mode ==
+                                   HOSTAPD_MODE_IEEE80211G)
+                                       band |= BAND_2_4_GHZ;
+                               else if (wpa_s->hw.modes[i].mode ==
+                                        HOSTAPD_MODE_IEEE80211A)
+                                       band |= BAND_5_GHZ;
+                               else if (wpa_s->hw.modes[i].mode ==
+                                        HOSTAPD_MODE_IEEE80211AD)
+                                       band |= BAND_60_GHZ;
+                               else if (wpa_s->hw.modes[i].mode ==
+                                        HOSTAPD_MODE_IEEE80211ANY)
+                                       band = BAND_2_4_GHZ | BAND_5_GHZ |
+                                               BAND_60_GHZ;
+                       }
+               }
+       }
+
+       return band;
+}
+
+
 static struct wpa_radio * radio_add_interface(struct wpa_supplicant *wpa_s,
                                              const char *rn)
 {
@@ -3922,11 +3972,103 @@ static void radio_work_free(struct wpa_radio_work *work)
        }
 #endif /* CONFIG_P2P */
 
+       if (work->started) {
+               work->wpa_s->radio->num_active_works--;
+               wpa_dbg(work->wpa_s, MSG_DEBUG,
+                       "radio_work_free('%s'@%p: num_active_works --> %u",
+                       work->type, work,
+                       work->wpa_s->radio->num_active_works);
+       }
+
        dl_list_del(&work->list);
        os_free(work);
 }
 
 
+static struct wpa_radio_work * radio_work_get_next_work(struct wpa_radio *radio)
+{
+       struct wpa_radio_work *active_work = NULL;
+       struct wpa_radio_work *tmp;
+
+       /* Get the active work to know the type and band. */
+       dl_list_for_each(tmp, &radio->work, struct wpa_radio_work, list) {
+               if (tmp->started) {
+                       active_work = tmp;
+                       break;
+               }
+       }
+
+       if (!active_work) {
+               /* No active work, start one */
+               radio->num_active_works = 0;
+               dl_list_for_each(tmp, &radio->work, struct wpa_radio_work,
+                                list) {
+                       if (os_strcmp(tmp->type, "scan") == 0 &&
+                           radio->external_scan_running &&
+                           (((struct wpa_driver_scan_params *)
+                             tmp->ctx)->only_new_results ||
+                            tmp->wpa_s->clear_driver_scan_cache))
+                               continue;
+                       return tmp;
+               }
+               return NULL;
+       }
+
+       if (os_strcmp(active_work->type, "sme-connect") == 0 ||
+           os_strcmp(active_work->type, "connect") == 0) {
+               /*
+                * If the active work is either connect or sme-connect,
+                * do not parallelize them with other radio works.
+                */
+               wpa_dbg(active_work->wpa_s, MSG_DEBUG,
+                       "Do not parallelize radio work with %s",
+                       active_work->type);
+               return NULL;
+       }
+
+       dl_list_for_each(tmp, &radio->work, struct wpa_radio_work, list) {
+               if (tmp->started)
+                       continue;
+
+               /*
+                * If connect or sme-connect are enqueued, parallelize only
+                * those operations ahead of them in the queue.
+                */
+               if (os_strcmp(tmp->type, "connect") == 0 ||
+                   os_strcmp(tmp->type, "sme-connect") == 0)
+                       break;
+
+               /*
+                * Check that the radio works are distinct and
+                * on different bands.
+                */
+               if (os_strcmp(active_work->type, tmp->type) != 0 &&
+                   (active_work->bands != tmp->bands)) {
+                       /*
+                        * If a scan has to be scheduled through nl80211 scan
+                        * interface and if an external scan is already running,
+                        * do not schedule the scan since it is likely to get
+                        * rejected by kernel.
+                        */
+                       if (os_strcmp(tmp->type, "scan") == 0 &&
+                           radio->external_scan_running &&
+                           (((struct wpa_driver_scan_params *)
+                             tmp->ctx)->only_new_results ||
+                            tmp->wpa_s->clear_driver_scan_cache))
+                               continue;
+
+                       wpa_dbg(active_work->wpa_s, MSG_DEBUG,
+                               "active_work:%s new_work:%s",
+                               active_work->type, tmp->type);
+                       return tmp;
+               }
+       }
+
+       /* Did not find a radio work to schedule in parallel. */
+       return NULL;
+}
+
+
 static void radio_start_next_work(void *eloop_ctx, void *timeout_ctx)
 {
        struct wpa_radio *radio = eloop_ctx;
@@ -3935,26 +4077,48 @@ static void radio_start_next_work(void *eloop_ctx, void *timeout_ctx)
        struct wpa_supplicant *wpa_s;
 
        work = dl_list_first(&radio->work, struct wpa_radio_work, list);
-       if (work == NULL)
+       if (work == NULL) {
+               radio->num_active_works = 0;
                return;
-
-       if (work->started)
-               return; /* already started and still in progress */
+       }
 
        wpa_s = dl_list_first(&radio->ifaces, struct wpa_supplicant,
                              radio_list);
-       if (wpa_s && wpa_s->radio->external_scan_running) {
-               wpa_printf(MSG_DEBUG, "Delay radio work start until externally triggered scan completes");
-               return;
+
+       if (!(wpa_s &&
+             wpa_s->drv_flags & WPA_DRIVER_FLAGS_OFFCHANNEL_SIMULTANEOUS)) {
+               if (work->started)
+                       return; /* already started and still in progress */
+
+               if (wpa_s && wpa_s->radio->external_scan_running) {
+                       wpa_printf(MSG_DEBUG, "Delay radio work start until externally triggered scan completes");
+                       return;
+               }
+       } else {
+               work = NULL;
+               if (radio->num_active_works < MAX_ACTIVE_WORKS) {
+                       /* get the work to schedule next */
+                       work = radio_work_get_next_work(radio);
+               }
+               if (!work)
+                       return;
        }
 
+       wpa_s = work->wpa_s;
        os_get_reltime(&now);
        os_reltime_sub(&now, &work->time, &diff);
-       wpa_dbg(work->wpa_s, MSG_DEBUG, "Starting radio work '%s'@%p after %ld.%06ld second wait",
+       wpa_dbg(wpa_s, MSG_DEBUG,
+               "Starting radio work '%s'@%p after %ld.%06ld second wait",
                work->type, work, diff.sec, diff.usec);
        work->started = 1;
        work->time = now;
+       radio->num_active_works++;
+
        work->cb(work, 0);
+
+       if ((wpa_s->drv_flags & WPA_DRIVER_FLAGS_OFFCHANNEL_SIMULTANEOUS) &&
+           radio->num_active_works < MAX_ACTIVE_WORKS)
+               radio_work_check_next(wpa_s);
 }
 
 
@@ -4062,6 +4226,7 @@ int radio_add_work(struct wpa_supplicant *wpa_s, unsigned int freq,
                   void (*cb)(struct wpa_radio_work *work, int deinit),
                   void *ctx)
 {
+       struct wpa_radio *radio = wpa_s->radio;
        struct wpa_radio_work *work;
        int was_empty;
 
@@ -4076,6 +4241,16 @@ int radio_add_work(struct wpa_supplicant *wpa_s, unsigned int freq,
        work->cb = cb;
        work->ctx = ctx;
 
+       if (freq)
+               work->bands = wpas_freq_to_band(freq);
+       else if (os_strcmp(type, "scan") == 0 ||
+                os_strcmp(type, "p2p-scan") == 0)
+               work->bands = wpas_get_bands(wpa_s,
+                                            ((struct wpa_driver_scan_params *)
+                                             ctx)->freqs);
+       else
+               work->bands = wpas_get_bands(wpa_s, NULL);
+
        was_empty = dl_list_empty(&wpa_s->radio->work);
        if (next)
                dl_list_add(&wpa_s->radio->work, &work->list);
@@ -4084,6 +4259,12 @@ int radio_add_work(struct wpa_supplicant *wpa_s, unsigned int freq,
        if (was_empty) {
                wpa_dbg(wpa_s, MSG_DEBUG, "First radio work item in the queue - schedule start immediately");
                radio_work_check_next(wpa_s);
+       } else if ((wpa_s->drv_flags & WPA_DRIVER_FLAGS_OFFCHANNEL_SIMULTANEOUS)
+                  && radio->num_active_works < MAX_ACTIVE_WORKS) {
+               wpa_dbg(wpa_s, MSG_DEBUG,
+                       "Try to schedule a radio work (num_active_works=%u)",
+                       radio->num_active_works);
+               radio_work_check_next(wpa_s);
        }
 
        return 0;
index 58df48c..52402cd 100644 (file)
@@ -300,10 +300,19 @@ struct wpa_radio {
        char name[16]; /* from driver_ops get_radio_name() or empty if not
                        * available */
        unsigned int external_scan_running:1;
+       unsigned int num_active_works;
        struct dl_list ifaces; /* struct wpa_supplicant::radio_list entries */
        struct dl_list work; /* struct wpa_radio_work::list entries */
 };
 
+#define MAX_ACTIVE_WORKS 2
+
+enum wpa_radio_work_band {
+       BAND_2_4_GHZ = BIT(0),
+       BAND_5_GHZ = BIT(1),
+       BAND_60_GHZ = BIT(2),
+};
+
 /**
  * struct wpa_radio_work - Radio work item
  */
@@ -316,6 +325,7 @@ struct wpa_radio_work {
        void *ctx;
        unsigned int started:1;
        struct os_reltime time;
+       unsigned int bands;
 };
 
 int radio_add_work(struct wpa_supplicant *wpa_s, unsigned int freq,