Allow external programs to request wpa_radio work items

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>
This commit is contained in:
Jouni Malinen 2014-01-04 13:10:41 +02:00
parent e766f56643
commit 1f965e622a
7 changed files with 283 additions and 3 deletions

View 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 "

View 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

View 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;

View 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 */

View 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 }
};

View 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)

View 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;
};