diff mbox series

[v4,12/14] cmd: add spawn and wait commands

Message ID 20250318104659.1114340-13-jerome.forissier@linaro.org
State New
Headers show
Series Uthreads | expand

Commit Message

Jerome Forissier March 18, 2025, 10:46 a.m. UTC
Add a spawn command which runs another command in the background, as
well as a wait command to suspend the shell until one or more background
jobs have completed. The job_id environment variable is set by spawn and
wait accepts optional job ids, so that one can selectively wait on any
job.

Example:

 => date; spawn sleep 5; spawn sleep 3; date; echo "waiting..."; wait; date
 Date: 2025-02-21 (Friday)    Time: 17:04:52
 Date: 2025-02-21 (Friday)    Time: 17:04:52
 waiting...
 Date: 2025-02-21 (Friday)    Time: 17:04:57
 =>

Another example showing how background jobs can make initlizations
faster. The board is i.MX93 EVK, with one spinning HDD connected to
USB1 via a hub, and a network cable plugged into ENET1.

 # From power up / reset
 u-boot=> setenv autoload 0
 u-boot=> setenv ud "usb start; dhcp"
 u-boot=> time run ud
 [...]
 time: 8.058 seconds

 # From power up / reset
 u-boot=> setenv autoload 0
 u-boot=> setenv ud "spawn usb start; spawn dhcp; wait"
 u-boot=> time run ud
 [...]
 time: 4.475 seconds

Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
---
 cmd/Kconfig  |  17 +++++
 cmd/Makefile |   2 +
 cmd/spawn.c  | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 207 insertions(+)
 create mode 100644 cmd/spawn.c

Comments

Ilias Apalodimas March 18, 2025, 11:16 a.m. UTC | #1
On Tue, 18 Mar 2025 at 12:47, Jerome Forissier
<jerome.forissier@linaro.org> wrote:
>
> Add a spawn command which runs another command in the background, as
> well as a wait command to suspend the shell until one or more background
> jobs have completed. The job_id environment variable is set by spawn and
> wait accepts optional job ids, so that one can selectively wait on any
> job.
>
> Example:
>
>  => date; spawn sleep 5; spawn sleep 3; date; echo "waiting..."; wait; date
>  Date: 2025-02-21 (Friday)    Time: 17:04:52
>  Date: 2025-02-21 (Friday)    Time: 17:04:52
>  waiting...
>  Date: 2025-02-21 (Friday)    Time: 17:04:57
>  =>
>
> Another example showing how background jobs can make initlizations
> faster. The board is i.MX93 EVK, with one spinning HDD connected to
> USB1 via a hub, and a network cable plugged into ENET1.
>
>  # From power up / reset
>  u-boot=> setenv autoload 0
>  u-boot=> setenv ud "usb start; dhcp"
>  u-boot=> time run ud
>  [...]
>  time: 8.058 seconds
>
>  # From power up / reset
>  u-boot=> setenv autoload 0
>  u-boot=> setenv ud "spawn usb start; spawn dhcp; wait"
>  u-boot=> time run ud
>  [...]
>  time: 4.475 seconds
>
> Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
> ---
>  cmd/Kconfig  |  17 +++++
>  cmd/Makefile |   2 +
>  cmd/spawn.c  | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 207 insertions(+)
>  create mode 100644 cmd/spawn.c
>
> diff --git a/cmd/Kconfig b/cmd/Kconfig
> index cd391d422ae..0cbb75edfbe 100644
> --- a/cmd/Kconfig
> +++ b/cmd/Kconfig
> @@ -3079,4 +3079,21 @@ config CMD_MESON
>         help
>           Enable useful commands for the Meson Soc family developed by Amlogic Inc.
>
> +config CMD_SPAWN
> +       bool "spawn and wait commands"
> +       depends on UTHREAD
> +       help
> +         spawn runs a command in the background and sets the job_id environment
> +         variable. wait is used to suspend the shell execution until one or more
> +         jobs are complete.
> +
> +config CMD_SPAWN_NUM_JOBS
> +       int "Maximum number of simultaneous jobs for spawn"
> +       default 16
> +       help
> +         Job identifiers are in the range 1..CMD_SPAWN_NUM_JOBS. In other words
> +         there can be no more that CMD_SPAWN_NUM_JOBS running simultaneously.
> +         When a jobs exits, its identifier is available to be re-used by the next
> +         spawn command.
> +
>  endif
> diff --git a/cmd/Makefile b/cmd/Makefile
> index c1275d466c8..b61f6586157 100644
> --- a/cmd/Makefile
> +++ b/cmd/Makefile
> @@ -239,6 +239,8 @@ obj-$(CONFIG_CMD_SCP03) += scp03.o
>
>  obj-$(CONFIG_HUSH_SELECTABLE) += cli.o
>
> +obj-$(CONFIG_CMD_SPAWN) += spawn.o
> +
>  obj-$(CONFIG_ARM) += arm/
>  obj-$(CONFIG_RISCV) += riscv/
>  obj-$(CONFIG_SANDBOX) += sandbox/
> diff --git a/cmd/spawn.c b/cmd/spawn.c
> new file mode 100644
> index 00000000000..f7a9f225f93
> --- /dev/null
> +++ b/cmd/spawn.c
> @@ -0,0 +1,188 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (c) 2011 The Chromium OS Authors.
> + */
> +
> +#include <command.h>
> +#include <console.h>
> +#include <malloc.h>
> +#include <vsprintf.h>
> +#include <uthread.h>
> +
> +/* Spawn arguments and job index  */
> +struct spa {
> +       int argc;
> +       char **argv;
> +       unsigned int job_idx;
> +};
> +
> +/*
> + * uthread group identifiers for each running job
> + * 0: job slot available, != 0: uthread group id
> + * Note that job[0] is job_id 1, job[1] is job_id 2 etc.
> + */
> +static unsigned int job[CONFIG_CMD_SPAWN_NUM_JOBS];
> +/*
> + * Return values of the commands run as jobs */
> +static enum command_ret_t job_ret[CONFIG_CMD_SPAWN_NUM_JOBS];
> +
> +static void spa_free(struct spa *spa)
> +{
> +       int i;
> +
> +       if (!spa)
> +               return;
> +
> +       for (i = 0; i < spa->argc; i++)
> +               free(spa->argv[i]);
> +       free(spa->argv);
> +       free(spa);
> +}
> +
> +static struct spa *spa_create(int argc, char *const argv[])
> +{
> +       struct spa *spa;
> +       int i;
> +
> +       spa = calloc(1, sizeof(*spa));
> +       if (!spa)
> +               return NULL;
> +       spa->argc = argc;
> +       spa->argv = malloc(argc * sizeof(char *));
> +       if (!spa->argv)
> +               goto err;
> +       for (i = 0; i < argc; i++) {
> +               spa->argv[i] = strdup(argv[i]);
> +               if (!spa->argv[i])
> +                       goto err;
> +       }
> +       return spa;
> +err:
> +       spa_free(spa);
> +       return NULL;
> +}
> +
> +static void spawn_thread(void *arg)
> +{
> +       struct spa *spa = (struct spa *)arg;
> +       ulong cycles = 0;
> +       int repeatable = 0;
> +
> +       job_ret[spa->job_idx] = cmd_process(0, spa->argc, spa->argv,
> +                                           &repeatable, &cycles);
> +       spa_free(spa);
> +}
> +
> +static unsigned int next_job_id(void)
> +{
> +       int i;
> +
> +       for (i = 0; i < CONFIG_CMD_SPAWN_NUM_JOBS; i++)
> +               if (!job[i])
> +                       return i + 1;
> +
> +       /* No job available */
> +       return 0;
> +}
> +
> +static void refresh_jobs(void)
> +{
> +       int i;
> +
> +       for (i = 0; i < CONFIG_CMD_SPAWN_NUM_JOBS; i++)
> +               if (job[i] && uthread_grp_done(job[i]))
> +                       job[i] = 0;
> +
> +}
> +
> +static int do_spawn(struct cmd_tbl *cmdtp, int flag, int argc,
> +                   char *const argv[])
> +{
> +       unsigned int id;
> +       unsigned int idx;
> +       struct spa *spa;
> +       int ret;
> +
> +       if (argc == 1)
> +               return CMD_RET_USAGE;
> +
> +       spa = spa_create(argc - 1, argv + 1);
> +       if (!spa)
> +               return CMD_RET_FAILURE;
> +
> +       refresh_jobs();
> +
> +       id = next_job_id();
> +       if (!id)
> +               return CMD_RET_FAILURE;
> +       idx = id - 1;
> +
> +       job[idx] = uthread_grp_new_id();
> +
> +       ret = uthread_create(NULL, spawn_thread, spa, 0, job[idx]);
> +       if (ret) {
> +               job[idx] = 0;
> +               return CMD_RET_FAILURE;
> +       }
> +
> +       ret = env_set_ulong("job_id", id);
> +       if (ret)
> +               return CMD_RET_FAILURE;
> +
> +       return CMD_RET_SUCCESS;
> +}
> +
> +U_BOOT_CMD(spawn, CONFIG_SYS_MAXARGS, 0, do_spawn,
> +          "run commands and summarize execution time",
> +          "command [args...]\n");
> +
> +static enum command_ret_t wait_job(unsigned int idx)
> +{
> +       int prev = disable_ctrlc(false);
> +
> +       while (!uthread_grp_done(job[idx])) {
> +               if (ctrlc()) {
> +                       puts("<INTERRUPT>\n");
> +                       disable_ctrlc(prev);
> +                       return CMD_RET_FAILURE;
> +               }
> +               uthread_schedule();
> +       }
> +
> +       job[idx] = 0;
> +       disable_ctrlc(prev);
> +
> +       return job_ret[idx];
> +}
> +
> +static int do_wait(struct cmd_tbl *cmdtp, int flag, int argc,
> +                  char *const argv[])
> +{
> +       enum command_ret_t ret = CMD_RET_SUCCESS;
> +       unsigned long id;
> +       unsigned int idx;
> +       int i;
> +
> +       if (argc == 1) {
> +               for (i = 0; i < CONFIG_CMD_SPAWN_NUM_JOBS; i++)
> +                       if (job[idx])
> +                               ret = wait_job(i);
> +       } else {
> +               for (i = 1; i < argc; i++) {
> +                       id = dectoul(argv[i], NULL);
> +                       if (id < 0 || id > CONFIG_CMD_SPAWN_NUM_JOBS)
> +                               return CMD_RET_USAGE;
> +                       idx = (int)id - 1;
> +                       ret = wait_job(idx);
> +               }
> +       }
> +
> +       return ret;
> +}
> +
> +U_BOOT_CMD(wait, CONFIG_SYS_MAXARGS, 0, do_wait,
> +          "wait for one or more jobs to complete",
> +          "[job_id ...]\n"
> +          "    - Wait until all specified jobs have exited and return the\n"
> +          "      exit status of the last job waited for. When no job_id is\n"
> +          "      given, wait for all the background jobs.\n");
> --
> 2.43.0
>

Acked-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
diff mbox series

Patch

diff --git a/cmd/Kconfig b/cmd/Kconfig
index cd391d422ae..0cbb75edfbe 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -3079,4 +3079,21 @@  config CMD_MESON
 	help
 	  Enable useful commands for the Meson Soc family developed by Amlogic Inc.
 
+config CMD_SPAWN
+	bool "spawn and wait commands"
+	depends on UTHREAD
+	help
+	  spawn runs a command in the background and sets the job_id environment
+	  variable. wait is used to suspend the shell execution until one or more
+	  jobs are complete.
+
+config CMD_SPAWN_NUM_JOBS
+	int "Maximum number of simultaneous jobs for spawn"
+	default 16
+	help
+	  Job identifiers are in the range 1..CMD_SPAWN_NUM_JOBS. In other words
+	  there can be no more that CMD_SPAWN_NUM_JOBS running simultaneously.
+	  When a jobs exits, its identifier is available to be re-used by the next
+	  spawn command.
+
 endif
diff --git a/cmd/Makefile b/cmd/Makefile
index c1275d466c8..b61f6586157 100644
--- a/cmd/Makefile
+++ b/cmd/Makefile
@@ -239,6 +239,8 @@  obj-$(CONFIG_CMD_SCP03) += scp03.o
 
 obj-$(CONFIG_HUSH_SELECTABLE) += cli.o
 
+obj-$(CONFIG_CMD_SPAWN) += spawn.o
+
 obj-$(CONFIG_ARM) += arm/
 obj-$(CONFIG_RISCV) += riscv/
 obj-$(CONFIG_SANDBOX) += sandbox/
diff --git a/cmd/spawn.c b/cmd/spawn.c
new file mode 100644
index 00000000000..f7a9f225f93
--- /dev/null
+++ b/cmd/spawn.c
@@ -0,0 +1,188 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2011 The Chromium OS Authors.
+ */
+
+#include <command.h>
+#include <console.h>
+#include <malloc.h>
+#include <vsprintf.h>
+#include <uthread.h>
+
+/* Spawn arguments and job index  */
+struct spa {
+	int argc;
+	char **argv;
+	unsigned int job_idx;
+};
+
+/*
+ * uthread group identifiers for each running job
+ * 0: job slot available, != 0: uthread group id
+ * Note that job[0] is job_id 1, job[1] is job_id 2 etc.
+ */
+static unsigned int job[CONFIG_CMD_SPAWN_NUM_JOBS];
+/*
+ * Return values of the commands run as jobs */
+static enum command_ret_t job_ret[CONFIG_CMD_SPAWN_NUM_JOBS];
+
+static void spa_free(struct spa *spa)
+{
+	int i;
+
+	if (!spa)
+		return;
+
+	for (i = 0; i < spa->argc; i++)
+		free(spa->argv[i]);
+	free(spa->argv);
+	free(spa);
+}
+
+static struct spa *spa_create(int argc, char *const argv[])
+{
+	struct spa *spa;
+	int i;
+
+	spa = calloc(1, sizeof(*spa));
+	if (!spa)
+		return NULL;
+	spa->argc = argc;
+	spa->argv = malloc(argc * sizeof(char *));
+	if (!spa->argv)
+		goto err;
+	for (i = 0; i < argc; i++) {
+		spa->argv[i] = strdup(argv[i]);
+		if (!spa->argv[i])
+			goto err;
+	}
+	return spa;
+err:
+	spa_free(spa);
+	return NULL;
+}
+
+static void spawn_thread(void *arg)
+{
+	struct spa *spa = (struct spa *)arg;
+	ulong cycles = 0;
+	int repeatable = 0;
+
+	job_ret[spa->job_idx] = cmd_process(0, spa->argc, spa->argv,
+					    &repeatable, &cycles);
+	spa_free(spa);
+}
+
+static unsigned int next_job_id(void)
+{
+	int i;
+
+	for (i = 0; i < CONFIG_CMD_SPAWN_NUM_JOBS; i++)
+		if (!job[i])
+			return i + 1;
+
+	/* No job available */
+	return 0;
+}
+
+static void refresh_jobs(void)
+{
+	int i;
+
+	for (i = 0; i < CONFIG_CMD_SPAWN_NUM_JOBS; i++)
+		if (job[i] && uthread_grp_done(job[i]))
+			job[i] = 0;
+
+}
+
+static int do_spawn(struct cmd_tbl *cmdtp, int flag, int argc,
+		    char *const argv[])
+{
+	unsigned int id;
+	unsigned int idx;
+	struct spa *spa;
+	int ret;
+
+	if (argc == 1)
+		return CMD_RET_USAGE;
+
+	spa = spa_create(argc - 1, argv + 1);
+	if (!spa)
+		return CMD_RET_FAILURE;
+
+	refresh_jobs();
+
+	id = next_job_id();
+	if (!id)
+		return CMD_RET_FAILURE;
+	idx = id - 1;
+
+	job[idx] = uthread_grp_new_id();
+
+	ret = uthread_create(NULL, spawn_thread, spa, 0, job[idx]);
+	if (ret) {
+		job[idx] = 0;
+		return CMD_RET_FAILURE;
+	}
+
+	ret = env_set_ulong("job_id", id);
+	if (ret)
+		return CMD_RET_FAILURE;
+
+	return CMD_RET_SUCCESS;
+}
+
+U_BOOT_CMD(spawn, CONFIG_SYS_MAXARGS, 0, do_spawn,
+	   "run commands and summarize execution time",
+	   "command [args...]\n");
+
+static enum command_ret_t wait_job(unsigned int idx)
+{
+	int prev = disable_ctrlc(false);
+
+	while (!uthread_grp_done(job[idx])) {
+		if (ctrlc()) {
+			puts("<INTERRUPT>\n");
+			disable_ctrlc(prev);
+			return CMD_RET_FAILURE;
+		}
+		uthread_schedule();
+	}
+
+	job[idx] = 0;
+	disable_ctrlc(prev);
+
+	return job_ret[idx];
+}
+
+static int do_wait(struct cmd_tbl *cmdtp, int flag, int argc,
+		   char *const argv[])
+{
+	enum command_ret_t ret = CMD_RET_SUCCESS;
+	unsigned long id;
+	unsigned int idx;
+	int i;
+
+	if (argc == 1) {
+		for (i = 0; i < CONFIG_CMD_SPAWN_NUM_JOBS; i++)
+			if (job[idx])
+				ret = wait_job(i);
+	} else {
+		for (i = 1; i < argc; i++) {
+			id = dectoul(argv[i], NULL);
+			if (id < 0 || id > CONFIG_CMD_SPAWN_NUM_JOBS)
+				return CMD_RET_USAGE;
+			idx = (int)id - 1;
+			ret = wait_job(idx);
+		}
+	}
+
+	return ret;
+}
+
+U_BOOT_CMD(wait, CONFIG_SYS_MAXARGS, 0, do_wait,
+	   "wait for one or more jobs to complete",
+	   "[job_id ...]\n"
+	   "    - Wait until all specified jobs have exited and return the\n"
+	   "      exit status of the last job waited for. When no job_id is\n"
+	   "      given, wait for all the background jobs.\n");