On Tue, 18 Mar 2025 at 12:47, Jerome Forissier <jerome.foriss...@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.foriss...@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.apalodi...@linaro.org>