]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
Allow external programs to request wpa_radio work items
authorJouni Malinen <j@w1.fi>
Sat, 4 Jan 2014 11:10:41 +0000 (13:10 +0200)
committerJouni Malinen <j@w1.fi>
Tue, 7 Jan 2014 08:45:10 +0000 (10:45 +0200)
The new control interface command RADIO_WORK can be used by external
programs to request radio allocation slots from wpa_supplicant if
exclusive radio control is needed, e.g., for offchannel operations. If
such operations are done directly to the driver, wpa_supplicant may not
have enough information to avoid conflicting operations. This new
command can be used to provide enough information and radio scheduling
to avoid issues with such cases.

Signed-hostap: Jouni Malinen <j@w1.fi>

src/common/wpa_ctrl.h
wpa_supplicant/README
wpa_supplicant/ctrl_iface.c
wpa_supplicant/ctrl_iface.h
wpa_supplicant/wpa_cli.c
wpa_supplicant/wpa_supplicant.c
wpa_supplicant/wpa_supplicant_i.h

index 030df130da63ba27f2f8e41f45ca6b3fc5b13f95..9bd59a9c931391a7e649d61281ae08502a1a4aed 100644 (file)
@@ -162,6 +162,9 @@ extern "C" {
 /* parameters: <addr> <dialog_token> <freq> <status_code> <result> */
 #define GAS_QUERY_DONE "GAS-QUERY-DONE "
 
+#define EXT_RADIO_WORK_START "EXT-RADIO-WORK-START "
+#define EXT_RADIO_WORK_TIMEOUT "EXT-RADIO-WORK-TIMEOUT "
+
 /* hostapd control interface - fixed message prefixes */
 #define WPS_EVENT_PIN_NEEDED "WPS-PIN-NEEDED "
 #define WPS_EVENT_NEW_AP_SETTINGS "WPS-NEW-AP-SETTINGS "
index 8e9cc45f3439152dc75400f4c3e0122150d4c0ed..3e566b7730ff3b5f81a068bcd43ebffeb1007031 100644 (file)
@@ -983,3 +983,71 @@ directory could be created before starting the wpa_supplicant and set to
 suitable mode to allow wpa_supplicant to create sockets
 there. Alternatively, other directory or abstract socket namespace could
 be used for the control interface.
+
+
+External requests for radio control
+-----------------------------------
+
+External programs can request wpa_supplicant to not start offchannel
+operations during other tasks that may need exclusive control of the
+radio. The RADIO_WORK control interface command can be used for this.
+
+"RADIO_WORK add <name> [freq=<MHz>] [timeout=<seconds>]" command can be
+used to reserve a slot for radio access. If freq is specified, other
+radio work items on the same channel may be completed in
+parallel. Otherwise, all other radio work items are blocked during
+execution. Timeout is set to 10 seconds by default to avoid blocking
+wpa_supplicant operations for excessive time. If a longer (or shorter)
+safety timeout is needed, that can be specified with the optional
+timeout parameter. This command returns an identifier for the radio work
+item.
+
+Once the radio work item has been started, "EXT-RADIO-WORK-START <id>"
+event message is indicated that the external processing can start. Once
+the operation has been completed, "RADIO_WORK done <id>" is used to
+indicate that to wpa_supplicant. This allows other radio works to be
+performed. If this command is forgotten (e.g., due to the external
+program terminating), wpa_supplicant will time out the radio owrk item
+and send "EXT-RADIO-WORK-TIMEOUT <id>" event ot indicate that this has
+happened. "RADIO_WORK done <id>" can also be used to cancel items that
+have not yet been started.
+
+For example, in wpa_cli interactive mode:
+
+> radio_work add test
+1
+<3>EXT-RADIO-WORK-START 1
+> radio_work show
+ext:test@wlan0:0:1:2.487797
+> radio_work done 1
+OK
+> radio_work show
+
+
+> radio_work done 3
+OK
+> radio_work show
+ext:test freq=2412 timeout=30@wlan0:2412:1:28.583483
+<3>EXT-RADIO-WORK-TIMEOUT 2
+
+
+> radio_work add test2 freq=2412 timeout=60
+5
+<3>EXT-RADIO-WORK-START 5
+> radio_work add test3
+6
+> radio_work add test4
+7
+> radio_work show
+ext:test2 freq=2412 timeout=60@wlan0:2412:1:9.751844
+ext:test3@wlan0:0:0:5.071812
+ext:test4@wlan0:0:0:3.143870
+> radio_work done 6
+OK
+> radio_work show
+ext:test2 freq=2412 timeout=60@wlan0:2412:1:16.287869
+ext:test4@wlan0:0:0:9.679895
+> radio_work done 5
+OK
+<3>EXT-RADIO-WORK-START 7
+<3>EXT-RADIO-WORK-TIMEOUT 7
index 9e553f202957adf5b5ccf30a530987fc35504a1c..8e3931ffee079649d96aa6b86e17ff81ab525f56 100644 (file)
@@ -5221,6 +5221,186 @@ static void wpa_supplicant_ctrl_iface_flush(struct wpa_supplicant *wpa_s)
 }
 
 
+static int wpas_ctrl_radio_work_show(struct wpa_supplicant *wpa_s,
+                                    char *buf, size_t buflen)
+{
+       struct wpa_radio_work *work;
+       char *pos, *end;
+       struct os_reltime now, diff;
+
+       pos = buf;
+       end = buf + buflen;
+
+       os_get_reltime(&now);
+
+       dl_list_for_each(work, &wpa_s->radio->work, struct wpa_radio_work, list)
+       {
+               int ret;
+
+               os_reltime_sub(&now, &work->time, &diff);
+               ret = os_snprintf(pos, end - pos, "%s@%s:%u:%u:%ld.%06ld\n",
+                                 work->type, work->wpa_s->ifname, work->freq,
+                                 work->started, diff.sec, diff.usec);
+               if (ret < 0 || ret >= end - pos)
+                       break;
+               pos += ret;
+       }
+
+       return pos - buf;
+}
+
+
+static void wpas_ctrl_radio_work_timeout(void *eloop_ctx, void *timeout_ctx)
+{
+       struct wpa_radio_work *work = eloop_ctx;
+       struct wpa_external_work *ework = work->ctx;
+
+       wpa_dbg(work->wpa_s, MSG_DEBUG,
+               "Timing out external radio work %u (%s)",
+               ework->id, work->type);
+       wpa_msg(work->wpa_s, MSG_INFO, EXT_RADIO_WORK_TIMEOUT "%u", ework->id);
+       os_free(ework);
+       radio_work_done(work);
+}
+
+
+static void wpas_ctrl_radio_work_cb(struct wpa_radio_work *work, int deinit)
+{
+       struct wpa_external_work *ework = work->ctx;
+
+       if (deinit) {
+               os_free(ework);
+               return;
+       }
+
+       wpa_dbg(work->wpa_s, MSG_DEBUG, "Starting external radio work %u (%s)",
+               ework->id, ework->type);
+       wpa_msg(work->wpa_s, MSG_INFO, EXT_RADIO_WORK_START "%u", ework->id);
+       if (!ework->timeout)
+               ework->timeout = 10;
+       eloop_register_timeout(ework->timeout, 0, wpas_ctrl_radio_work_timeout,
+                              work, NULL);
+}
+
+
+static int wpas_ctrl_radio_work_add(struct wpa_supplicant *wpa_s, char *cmd,
+                                   char *buf, size_t buflen)
+{
+       struct wpa_external_work *ework;
+       char *pos, *pos2;
+       size_t type_len;
+       int ret;
+       unsigned int freq = 0;
+
+       /* format: <name> [freq=<MHz>] [timeout=<seconds>] */
+
+       ework = os_zalloc(sizeof(*ework));
+       if (ework == NULL)
+               return -1;
+
+       pos = os_strchr(cmd, ' ');
+       if (pos) {
+               type_len = pos - cmd;
+               pos++;
+
+               pos2 = os_strstr(pos, "freq=");
+               if (pos2)
+                       freq = atoi(pos2 + 5);
+
+               pos2 = os_strstr(pos, "timeout=");
+               if (pos2)
+                       ework->timeout = atoi(pos2 + 8);
+       } else {
+               type_len = os_strlen(cmd);
+       }
+       if (4 + type_len >= sizeof(ework->type))
+               type_len = sizeof(ework->type) - 4 - 1;
+       os_strlcpy(ework->type, "ext:", sizeof(ework->type));
+       os_memcpy(ework->type + 4, cmd, type_len);
+       ework->type[4 + type_len] = '\0';
+
+       wpa_s->ext_work_id++;
+       if (wpa_s->ext_work_id == 0)
+               wpa_s->ext_work_id++;
+       ework->id = wpa_s->ext_work_id;
+
+       if (radio_add_work(wpa_s, freq, ework->type, 0, wpas_ctrl_radio_work_cb,
+                          ework) < 0) {
+               os_free(ework);
+               return -1;
+       }
+
+       ret = os_snprintf(buf, buflen, "%u", ework->id);
+       if (ret < 0 || (size_t) ret >= buflen)
+               return -1;
+       return ret;
+}
+
+
+static int wpas_ctrl_radio_work_done(struct wpa_supplicant *wpa_s, char *cmd)
+{
+       struct wpa_radio_work *work;
+       unsigned int id = atoi(cmd);
+
+       dl_list_for_each(work, &wpa_s->radio->work, struct wpa_radio_work, list)
+       {
+               struct wpa_external_work *ework;
+
+               if (os_strncmp(work->type, "ext:", 4) != 0)
+                       continue;
+               ework = work->ctx;
+               if (id && ework->id != id)
+                       continue;
+               wpa_dbg(wpa_s, MSG_DEBUG,
+                       "Completed external radio work %u (%s)",
+                       ework->id, ework->type);
+               eloop_cancel_timeout(wpas_ctrl_radio_work_timeout, work, NULL);
+               os_free(ework);
+               radio_work_done(work);
+               return 3; /* "OK\n" */
+       }
+
+       return -1;
+}
+
+
+static int wpas_ctrl_radio_work(struct wpa_supplicant *wpa_s, char *cmd,
+                               char *buf, size_t buflen)
+{
+       if (os_strcmp(cmd, "show") == 0)
+               return wpas_ctrl_radio_work_show(wpa_s, buf, buflen);
+       if (os_strncmp(cmd, "add ", 4) == 0)
+               return wpas_ctrl_radio_work_add(wpa_s, cmd + 4, buf, buflen);
+       if (os_strncmp(cmd, "done ", 5) == 0)
+               return wpas_ctrl_radio_work_done(wpa_s, cmd + 4);
+       return -1;
+}
+
+
+void wpas_ctrl_radio_work_flush(struct wpa_supplicant *wpa_s)
+{
+       struct wpa_radio_work *work, *tmp;
+
+       dl_list_for_each_safe(work, tmp, &wpa_s->radio->work,
+                             struct wpa_radio_work, list) {
+               struct wpa_external_work *ework;
+
+               if (os_strncmp(work->type, "ext:", 4) != 0)
+                       continue;
+               ework = work->ctx;
+               wpa_dbg(wpa_s, MSG_DEBUG,
+                       "Flushing %sexternal radio work %u (%s)",
+                       work->started ? " started" : "", ework->id,
+                       ework->type);
+               if (work->started)
+                       eloop_cancel_timeout(wpas_ctrl_radio_work_timeout,
+                                            work, NULL);
+               os_free(ework);
+               radio_work_done(work);
+       }
+}
+
+
 static void wpas_ctrl_eapol_response(void *eloop_ctx, void *timeout_ctx)
 {
        struct wpa_supplicant *wpa_s = eloop_ctx;
@@ -5873,6 +6053,9 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,
 #endif /* CONFIG_WNM */
        } else if (os_strcmp(buf, "FLUSH") == 0) {
                wpa_supplicant_ctrl_iface_flush(wpa_s);
+       } else if (os_strncmp(buf, "RADIO_WORK ", 11) == 0) {
+               reply_len = wpas_ctrl_radio_work(wpa_s, buf + 11, reply,
+                                                reply_size);
        } else {
                os_memcpy(reply, "UNKNOWN COMMAND\n", 16);
                reply_len = 16;
index a329ef32a239d10d396aac33bee8399d35d6454f..b0dec53fc45435ddc9869affd034c2e8d19bd804 100644 (file)
@@ -113,6 +113,8 @@ wpa_supplicant_global_ctrl_iface_init(struct wpa_global *global);
 void wpa_supplicant_global_ctrl_iface_deinit(
        struct ctrl_iface_global_priv *priv);
 
+void wpas_ctrl_radio_work_flush(struct wpa_supplicant *wpa_s);
+
 #else /* CONFIG_CTRL_IFACE */
 
 static inline struct ctrl_iface_priv *
@@ -148,6 +150,10 @@ wpa_supplicant_global_ctrl_iface_deinit(struct ctrl_iface_global_priv *priv)
 {
 }
 
+static inline void wpas_ctrl_radio_work_flush(struct wpa_supplicant *wpa_s)
+{
+}
+
 #endif /* CONFIG_CTRL_IFACE */
 
 #endif /* CTRL_IFACE_H */
index aabaa3cccfc8b715d5a327064c855d59a77d0829..6e2a3ef2b2302a47cf08542ce105be125f235881 100644 (file)
@@ -2426,6 +2426,12 @@ static int wpa_cli_cmd_flush(struct wpa_ctrl *ctrl, int argc, char *argv[])
 }
 
 
+static int wpa_cli_cmd_radio_work(struct wpa_ctrl *ctrl, int argc, char *argv[])
+{
+       return wpa_cli_cmd(ctrl, "RADIO_WORK", 1, argc, argv);
+}
+
+
 enum wpa_cli_cmd_flags {
        cli_cmd_flag_none               = 0x00,
        cli_cmd_flag_sensitive          = 0x01
@@ -2893,6 +2899,8 @@ static struct wpa_cli_cmd wpa_cli_commands[] = {
        { "driver", wpa_cli_cmd_driver, NULL, cli_cmd_flag_none,
          "<command> = driver private commands" },
 #endif /* ANDROID */
+       { "radio_work", wpa_cli_cmd_radio_work, NULL, cli_cmd_flag_none,
+         "= radio_work <show/add/done>" },
        { NULL, NULL, NULL, cli_cmd_flag_none, NULL }
 };
 
index 33089b8fae001796156a4ac26de79aa0edbdad08..b722b1319c83b150098f23b6c8d60e1c2fd6197f 100644 (file)
@@ -3185,13 +3185,16 @@ void radio_work_done(struct wpa_radio_work *work)
 {
        struct wpa_supplicant *wpa_s = work->wpa_s;
        struct os_reltime now, diff;
+       unsigned int started = work->started;
 
        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);
+       wpa_dbg(wpa_s, MSG_DEBUG, "Radio work '%s'@%p %s in %ld.%06ld seconds",
+               work->type, work, started ? "done" : "canceled",
+               diff.sec, diff.usec);
        radio_work_free(work);
-       radio_work_check_next(wpa_s);
+       if (started)
+               radio_work_check_next(wpa_s);
 }
 
 
@@ -3514,6 +3517,7 @@ static void wpa_supplicant_deinit_iface(struct wpa_supplicant *wpa_s,
        }
 #endif /* CONFIG_P2P */
 
+       wpas_ctrl_radio_work_flush(wpa_s);
        radio_remove_interface(wpa_s);
 
        if (wpa_s->drv_priv)
index 1c10a036a97dd72cc0f053150f3c004a4156e30a..61d155af1804603c37534565179363118fec74bc 100644 (file)
@@ -317,6 +317,12 @@ int wpas_valid_bss_ssid(struct wpa_supplicant *wpa_s, struct wpa_bss *test_bss,
 void wpas_connect_work_free(struct wpa_connect_work *cwork);
 void wpas_connect_work_done(struct wpa_supplicant *wpa_s);
 
+struct wpa_external_work {
+       unsigned int id;
+       char type[100];
+       unsigned int timeout;
+};
+
 /**
  * offchannel_send_action_result - Result of offchannel send Action frame
  */
@@ -788,6 +794,8 @@ struct wpa_supplicant {
 
        unsigned int num_multichan_concurrent;
        struct wpa_radio_work *connect_work;
+
+       unsigned int ext_work_id;
 };