summaryrefslogtreecommitdiff
path: root/cmd/spawn.c
diff options
context:
space:
mode:
authorTom Rini <trini@konsulko.com>2025-04-23 13:21:39 -0600
committerTom Rini <trini@konsulko.com>2025-04-23 13:21:39 -0600
commit233fda6af674736dbc6ff37a9ef003b9fa4b8074 (patch)
tree2765c8660d4134c3b7fceff325410b4846f6761a /cmd/spawn.c
parent6e325df4891cb9be954f1e62f16cd3096b267bdb (diff)
parent1717f46a1da7f15e749405da2178944c3a76fac0 (diff)
Merge patch series "Uthreads"
Jerome Forissier <jerome.forissier@linaro.org> says: This series introduces threads and uses them to improve the performance of the USB bus scanning code and to implement background jobs in the shell via two new commands: 'spawn' and 'wait'. The threading framework is called 'uthread' and is inspired from the barebox threads [2]. setjmp() and longjmp() are used to save and restore contexts, as well as a non-standard extension called initjmp(). This new function is added in several patches, one for each architecture that supports HAVE_SETJMP. A new symbol is defined: HAVE_INITJMP. Two tests, one for initjmp() and one for the uthread scheduling, are added to the lib suite. After introducing threads and making schedule() and udelay() a thread re-scheduling point, the USB stack initialization is modified to benefit from concurrency when UTHREAD is enabled, where uthreads are used in usb_init() to initialize and scan multiple busses at the same time. The code was tested on arm64 and arm QEMU with 4 simulated XHCI buses and some devices. On this platform the USB scan takes 2.2 s instead of 5.6 s. Tested on i.MX93 EVK with two USB hubs, one ethernet adapter and one webcam on each, "usb start" takes 2.4 s instead of 4.6 s. Finally, the spawn and wait commands are introduced, allowing the use of threads from the shell. Tested on the i.MX93 EVK with a spinning HDD connected to USB1 and the network connected to ENET1. The USB plus DHCP init sequence "spawn usb start; spawn dhcp; wait" takes 4.5 seconds instead of 8 seconds for "usb start; dhcp". [1] https://patchwork.ozlabs.org/project/uboot/list/?series=446674 [2] https://github.com/barebox/barebox/blob/master/common/bthread.c Link: https://lore.kernel.org/r/20250418141114.2056981-1-jerome.forissier@linaro.org
Diffstat (limited to 'cmd/spawn.c')
-rw-r--r--cmd/spawn.c187
1 files changed, 187 insertions, 0 deletions
diff --git a/cmd/spawn.c b/cmd/spawn.c
new file mode 100644
index 00000000000..eddbcb792b3
--- /dev/null
+++ b/cmd/spawn.c
@@ -0,0 +1,187 @@
+// 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[i])
+ 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");