diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h index 030df130d..9bd59a9c9 100644 --- a/src/common/wpa_ctrl.h +++ b/src/common/wpa_ctrl.h @@ -162,6 +162,9 @@ extern "C" { /* parameters: */ #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 " diff --git a/wpa_supplicant/README b/wpa_supplicant/README index 8e9cc45f3..3e566b773 100644 --- a/wpa_supplicant/README +++ b/wpa_supplicant/README @@ -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 [freq=] [timeout=]" 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 " +event message is indicated that the external processing can start. Once +the operation has been completed, "RADIO_WORK done " 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 " event ot indicate that this has +happened. "RADIO_WORK done " 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 diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index 9e553f202..8e3931ffe 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -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: [freq=] [timeout=] */ + + 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; diff --git a/wpa_supplicant/ctrl_iface.h b/wpa_supplicant/ctrl_iface.h index a329ef32a..b0dec53fc 100644 --- a/wpa_supplicant/ctrl_iface.h +++ b/wpa_supplicant/ctrl_iface.h @@ -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 */ diff --git a/wpa_supplicant/wpa_cli.c b/wpa_supplicant/wpa_cli.c index aabaa3ccc..6e2a3ef2b 100644 --- a/wpa_supplicant/wpa_cli.c +++ b/wpa_supplicant/wpa_cli.c @@ -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, " = driver private commands" }, #endif /* ANDROID */ + { "radio_work", wpa_cli_cmd_radio_work, NULL, cli_cmd_flag_none, + "= radio_work " }, { NULL, NULL, NULL, cli_cmd_flag_none, NULL } }; diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index 33089b8fa..b722b1319 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -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) diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index 1c10a036a..61d155af1 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -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; };