]> git.ipfire.org Git - thirdparty/hostap.git/blobdiff - wpa_supplicant/wpa_supplicant.c
Use radio work for P2P scan requests
[thirdparty/hostap.git] / wpa_supplicant / wpa_supplicant.c
index d999e06a1dffd53c81c50d561af72f6dfcc9dbb7..138e975a8ddcd9aafec583f1ace64dd52a920377 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * WPA Supplicant
- * Copyright (c) 2003-2012, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2003-2014, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -54,7 +54,7 @@
 
 const char *wpa_supplicant_version =
 "wpa_supplicant v" VERSION_STR "\n"
-"Copyright (c) 2003-2013, Jouni Malinen <j@w1.fi> and contributors";
+"Copyright (c) 2003-2014, Jouni Malinen <j@w1.fi> and contributors";
 
 const char *wpa_supplicant_license =
 "This software may be distributed under the terms of the BSD license.\n"
@@ -104,11 +104,6 @@ const char *wpa_supplicant_full_license5 =
 "\n";
 #endif /* CONFIG_NO_STDOUT_DEBUG */
 
-extern int wpa_debug_level;
-extern int wpa_debug_show_keys;
-extern int wpa_debug_timestamp;
-extern struct wpa_driver_ops *wpa_drivers[];
-
 /* Configure default/group WEP keys for static WEP */
 int wpa_set_wep_keys(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid)
 {
@@ -200,8 +195,6 @@ static void wpa_supplicant_timeout(void *eloop_ctx, void *timeout_ctx)
         * So, wait a second until scanning again.
         */
        wpa_supplicant_req_scan(wpa_s, 1, 0);
-
-       wpas_p2p_continue_after_scan(wpa_s);
 }
 
 
@@ -500,29 +493,23 @@ static void wpa_supplicant_cleanup(struct wpa_supplicant *wpa_s)
  */
 void wpa_clear_keys(struct wpa_supplicant *wpa_s, const u8 *addr)
 {
-       if (wpa_s->keys_cleared) {
-               /* Some drivers (e.g., ndiswrapper & NDIS drivers) seem to have
-                * timing issues with keys being cleared just before new keys
-                * are set or just after association or something similar. This
-                * shows up in group key handshake failing often because of the
-                * client not receiving the first encrypted packets correctly.
-                * Skipping some of the extra key clearing steps seems to help
-                * in completing group key handshake more reliably. */
-               wpa_dbg(wpa_s, MSG_DEBUG, "No keys have been configured - "
-                       "skip key clearing");
-               return;
-       }
+       int i, max;
 
-       /* MLME-DELETEKEYS.request */
-       wpa_drv_set_key(wpa_s, WPA_ALG_NONE, NULL, 0, 0, NULL, 0, NULL, 0);
-       wpa_drv_set_key(wpa_s, WPA_ALG_NONE, NULL, 1, 0, NULL, 0, NULL, 0);
-       wpa_drv_set_key(wpa_s, WPA_ALG_NONE, NULL, 2, 0, NULL, 0, NULL, 0);
-       wpa_drv_set_key(wpa_s, WPA_ALG_NONE, NULL, 3, 0, NULL, 0, NULL, 0);
 #ifdef CONFIG_IEEE80211W
-       wpa_drv_set_key(wpa_s, WPA_ALG_NONE, NULL, 4, 0, NULL, 0, NULL, 0);
-       wpa_drv_set_key(wpa_s, WPA_ALG_NONE, NULL, 5, 0, NULL, 0, NULL, 0);
+       max = 6;
+#else /* CONFIG_IEEE80211W */
+       max = 4;
 #endif /* CONFIG_IEEE80211W */
-       if (addr) {
+
+       /* MLME-DELETEKEYS.request */
+       for (i = 0; i < max; i++) {
+               if (wpa_s->keys_cleared & BIT(i))
+                       continue;
+               wpa_drv_set_key(wpa_s, WPA_ALG_NONE, NULL, i, 0, NULL, 0,
+                               NULL, 0);
+       }
+       if (!(wpa_s->keys_cleared & BIT(0)) && addr &&
+           !is_zero_ether_addr(addr)) {
                wpa_drv_set_key(wpa_s, WPA_ALG_NONE, addr, 0, 0, NULL, 0, NULL,
                                0);
                /* MLME-SETPROTECTION.request(None) */
@@ -531,7 +518,7 @@ void wpa_clear_keys(struct wpa_supplicant *wpa_s, const u8 *addr)
                        MLME_SETPROTECTION_PROTECT_TYPE_NONE,
                        MLME_SETPROTECTION_KEY_TYPE_PAIRWISE);
        }
-       wpa_s->keys_cleared = 1;
+       wpa_s->keys_cleared = (u32) -1;
 }
 
 
@@ -2905,12 +2892,81 @@ static struct wpa_radio * radio_add_interface(struct wpa_supplicant *wpa_s,
        if (rn)
                os_strlcpy(radio->name, rn, sizeof(radio->name));
        dl_list_init(&radio->ifaces);
+       dl_list_init(&radio->work);
        dl_list_add(&radio->ifaces, &wpa_s->radio_list);
 
        return radio;
 }
 
 
+static void radio_work_free(struct wpa_radio_work *work)
+{
+       if (work->wpa_s->scan_work == work) {
+               /* This should not really happen. */
+               wpa_dbg(work->wpa_s, MSG_INFO, "Freeing radio work '%s'@%p (started=%d) that is marked as scan_work",
+                       work->type, work, work->started);
+               work->wpa_s->scan_work = NULL;
+       }
+
+#ifdef CONFIG_P2P
+       if (work->wpa_s->p2p_scan_work == work) {
+               /* This should not really happen. */
+               wpa_dbg(work->wpa_s, MSG_INFO, "Freeing radio work '%s'@%p (started=%d) that is marked as p2p_scan_work",
+                       work->type, work, work->started);
+               work->wpa_s->p2p_scan_work = NULL;
+       }
+#endif /* CONFIG_P2P */
+
+       dl_list_del(&work->list);
+       os_free(work);
+}
+
+
+static void radio_start_next_work(void *eloop_ctx, void *timeout_ctx)
+{
+       struct wpa_radio *radio = eloop_ctx;
+       struct wpa_radio_work *work;
+       struct os_reltime now, diff;
+
+       work = dl_list_first(&radio->work, struct wpa_radio_work, list);
+       if (work == NULL)
+               return;
+
+       if (work->started)
+               return; /* already started and still in progress */
+
+       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",
+               work->type, work, diff.sec, diff.usec);
+       work->started = 1;
+       work->time = now;
+       work->cb(work, 0);
+}
+
+
+void radio_remove_unstarted_work(struct wpa_supplicant *wpa_s, const char *type)
+{
+       struct wpa_radio_work *work, *tmp;
+       struct wpa_radio *radio = wpa_s->radio;
+
+       dl_list_for_each_safe(work, tmp, &radio->work, struct wpa_radio_work,
+                             list) {
+               if (type && (work->started || os_strcmp(type, work->type) != 0))
+                       continue;
+               if (work->started) {
+                       wpa_dbg(wpa_s, MSG_DEBUG, "Leaving started radio work '%s'@%p in the list",
+                               work->type, work);
+                       continue;
+               }
+               wpa_dbg(wpa_s, MSG_DEBUG, "Remove unstarted radio work '%s'@%p",
+                       work->type, work);
+               work->cb(work, 1);
+               radio_work_free(work);
+       }
+}
+
+
 static void radio_remove_interface(struct wpa_supplicant *wpa_s)
 {
        struct wpa_radio *radio = wpa_s->radio;
@@ -2921,16 +2977,109 @@ static void radio_remove_interface(struct wpa_supplicant *wpa_s)
        wpa_printf(MSG_DEBUG, "Remove interface %s from radio %s",
                   wpa_s->ifname, radio->name);
        dl_list_del(&wpa_s->radio_list);
-       wpa_s->radio = NULL;
-
-       if (!dl_list_empty(&radio->ifaces))
+       if (!dl_list_empty(&radio->ifaces)) {
+               wpa_s->radio = NULL;
                return; /* Interfaces remain for this radio */
+       }
 
        wpa_printf(MSG_DEBUG, "Remove radio %s", radio->name);
+       radio_remove_unstarted_work(wpa_s, NULL);
+       eloop_cancel_timeout(radio_start_next_work, radio, NULL);
+       wpa_s->radio = NULL;
        os_free(radio);
 }
 
 
+static void radio_work_check_next(struct wpa_supplicant *wpa_s)
+{
+       struct wpa_radio *radio = wpa_s->radio;
+
+       if (dl_list_empty(&radio->work))
+               return;
+       eloop_cancel_timeout(radio_start_next_work, radio, NULL);
+       eloop_register_timeout(0, 0, radio_start_next_work, radio, NULL);
+}
+
+
+/**
+ * radio_add_work - Add a radio work item
+ * @wpa_s: Pointer to wpa_supplicant data
+ * @freq: Frequency of the offchannel operation in MHz or 0
+ * @type: Unique identifier for each type of work
+ * @next: Force as the next work to be executed
+ * @cb: Callback function for indicating when radio is available
+ * @ctx: Context pointer for the work (work->ctx in cb())
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function is used to request time for an operation that requires
+ * exclusive radio control. Once the radio is available, the registered callback
+ * function will be called. radio_work_done() must be called once the exclusive
+ * radio operation has been completed, so that the radio is freed for other
+ * operations. The special case of deinit=1 is used to free the context data
+ * during interface removal. That does not allow the callback function to start
+ * the radio operation, i.e., it must free any resources allocated for the radio
+ * work and return.
+ *
+ * The @freq parameter can be used to indicate a single channel on which the
+ * offchannel operation will occur. This may allow multiple radio work
+ * operations to be performed in parallel if they apply for the same channel.
+ * Setting this to 0 indicates that the work item may use multiple channels or
+ * requires exclusive control of the radio.
+ */
+int radio_add_work(struct wpa_supplicant *wpa_s, unsigned int freq,
+                  const char *type, int next,
+                  void (*cb)(struct wpa_radio_work *work, int deinit),
+                  void *ctx)
+{
+       struct wpa_radio_work *work;
+       int was_empty;
+
+       work = os_zalloc(sizeof(*work));
+       if (work == NULL)
+               return -1;
+       wpa_dbg(wpa_s, MSG_DEBUG, "Add radio work '%s'@%p", type, work);
+       os_get_reltime(&work->time);
+       work->freq = freq;
+       work->type = type;
+       work->wpa_s = wpa_s;
+       work->cb = cb;
+       work->ctx = ctx;
+
+       was_empty = dl_list_empty(&wpa_s->radio->work);
+       if (next)
+               dl_list_add(&wpa_s->radio->work, &work->list);
+       else
+               dl_list_add_tail(&wpa_s->radio->work, &work->list);
+       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);
+       }
+
+       return 0;
+}
+
+
+/**
+ * radio_work_done - Indicate that a radio work item has been completed
+ * @work: Completed work
+ *
+ * This function is called once the callback function registered with
+ * radio_add_work() has completed its work.
+ */
+void radio_work_done(struct wpa_radio_work *work)
+{
+       struct wpa_supplicant *wpa_s = work->wpa_s;
+       struct os_reltime now, diff;
+
+       os_get_reltime(&now);
+       os_reltime_sub(&now, &work->time, &diff);
+       wpa_dbg(wpa_s, MSG_DEBUG, "Radio work '%s'@%p done in %ld.%06ld seconds",
+               work->type, work, diff.sec, diff.usec);
+       radio_work_free(work);
+       radio_work_check_next(wpa_s);
+}
+
+
 static int wpas_init_driver(struct wpa_supplicant *wpa_s,
                            struct wpa_interface *iface)
 {
@@ -3824,8 +3973,6 @@ void wpas_connection_failed(struct wpa_supplicant *wpa_s, const u8 *bssid)
         */
        wpa_supplicant_req_scan(wpa_s, timeout / 1000,
                                1000 * (timeout % 1000));
-
-       wpas_p2p_continue_after_scan(wpa_s);
 }