* [PATCH v4 01/14] arch: introduce initjmp() and Kconfig symbol HAVE_INITJMP
2025-03-18 10:46 [PATCH v4 00/14] Uthreads Jerome Forissier
@ 2025-03-18 10:46 ` Jerome Forissier
2025-03-18 10:46 ` [PATCH v4 02/14] arm: add initjmp() Jerome Forissier
` (12 subsequent siblings)
13 siblings, 0 replies; 26+ messages in thread
From: Jerome Forissier @ 2025-03-18 10:46 UTC (permalink / raw)
To: u-boot
Cc: Ilias Apalodimas, Jerome Forissier, Tom Rini, Simon Glass,
Heinrich Schuchardt, Yu-Chien Peter Lin, Leo Yu-Chi Liang,
Jiaxun Yang, Andrew Goodbody, Vasileios Amoiridis,
Christian Marangi
Add the HAVE_INIJMP symbol to be set by architectures that support
initjmp(), a non-standard extension to setjmp()/longjmp() allowing to
initialize a jump buffer with a function pointer and a stack pointer.
This will be useful to later introduce threads. With this new function
it becomes possible to longjmp() to a particular function pointer
(rather than to a point previously reached during program execution as
is the case with setjmp()), and with a custom stack. Both things are
needed to spin off a new thread. Then the usual setjmp()/longjmp() pair
is enough to save and restore a context, i.e., switch thread.
Add the initjmp() prototype to <include/setjmp.h> since it is common to
all architectures.
Add an entry to the API documentation: doc/api/setjmp.rst.
Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
---
arch/Kconfig | 7 +++++++
doc/api/index.rst | 1 +
doc/api/setjmp.rst | 10 ++++++++++
include/setjmp.h | 32 ++++++++++++++++++++++++++++++++
4 files changed, 50 insertions(+)
create mode 100644 doc/api/setjmp.rst
diff --git a/arch/Kconfig b/arch/Kconfig
index 35b19f9bfdc..14111ca14fb 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -13,6 +13,13 @@ config HAVE_SETJMP
help
The architecture supports setjmp() and longjmp().
+config HAVE_INITJMP
+ bool
+ depends on HAVE_SETJMP
+ help
+ The architecture supports initjmp(), a non-standard companion to
+ setjmp() and longjmp().
+
config SUPPORT_BIG_ENDIAN
bool
diff --git a/doc/api/index.rst b/doc/api/index.rst
index a108718ea99..0dc9ad45d41 100644
--- a/doc/api/index.rst
+++ b/doc/api/index.rst
@@ -25,6 +25,7 @@ U-Boot API documentation
rng
sandbox
serial
+ setjmp
sysreset
timer
unicode
diff --git a/doc/api/setjmp.rst b/doc/api/setjmp.rst
new file mode 100644
index 00000000000..7718047085c
--- /dev/null
+++ b/doc/api/setjmp.rst
@@ -0,0 +1,10 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Long jump API
+=============
+
+.. kernel-doc:: include/setjmp.h
+ :doc: Overview
+
+.. kernel-doc:: include/setjmp.h
+ :internal:
diff --git a/include/setjmp.h b/include/setjmp.h
index 37d3a8af85d..32dd48803e9 100644
--- a/include/setjmp.h
+++ b/include/setjmp.h
@@ -3,12 +3,27 @@
#ifndef _SETJMP_H_
#define _SETJMP_H_ 1
+/**
+ * DOC: Overview
+ *
+ * The long jump API allows to perform nonlocal gotos, that is jump from one
+ * function to another typically further down in the stack, while properly
+ * restoring the stack's state (unwinding). The two functions needed to do this
+ * are setjmp() and longjmp().
+ *
+ * In addition to these two standard POSIX.1-2001/C89 functions, a third one is
+ * present in U-Boot: initjmp(). It is an extension which allows to implement
+ * user-mode threads.
+ */
+
#ifdef CONFIG_HAVE_SETJMP
#include <asm/setjmp.h>
#else
struct jmp_buf_data {
};
#endif
+#include <linux/compiler_attributes.h>
+#include <stddef.h>
/**
* typedef jmp_buf - information needed to restore a calling environment
@@ -37,4 +52,21 @@ int setjmp(jmp_buf env);
*/
void longjmp(jmp_buf env, int val);
+/**
+ * initjmp() - prepare for a long jump to a given function with a given stack
+ *
+ * This function sets up a jump buffer for later use with longjmp(). It allows
+ * to branch to a specific function with a specific stack. Please note that
+ * @func MUST NOT return. It shall typically restore the main stack and resume
+ * execution by doing a long jump to a jump buffer initialized by setjmp()
+ * before the long jump. initjmp() allows to implement multithreading.
+ *
+ * @env: jump buffer
+ * @func: function to be called on longjmp(), MUST NOT RETURN
+ * @stack_base: the stack to be used by @func (lower address)
+ * @stack_sz: the stack size in bytes
+ */
+int initjmp(jmp_buf env, void __noreturn (*func)(void), void *stack_base,
+ size_t stack_sz);
+
#endif /* _SETJMP_H_ */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v4 02/14] arm: add initjmp()
2025-03-18 10:46 [PATCH v4 00/14] Uthreads Jerome Forissier
2025-03-18 10:46 ` [PATCH v4 01/14] arch: introduce initjmp() and Kconfig symbol HAVE_INITJMP Jerome Forissier
@ 2025-03-18 10:46 ` Jerome Forissier
2025-03-18 11:15 ` Ilias Apalodimas
2025-03-18 12:28 ` Fabio Estevam
2025-03-18 10:46 ` [PATCH v4 03/14] riscv: " Jerome Forissier
` (11 subsequent siblings)
13 siblings, 2 replies; 26+ messages in thread
From: Jerome Forissier @ 2025-03-18 10:46 UTC (permalink / raw)
To: u-boot
Cc: Ilias Apalodimas, Jerome Forissier, Tom Rini, Simon Glass,
Heinrich Schuchardt, Andrew Goodbody, Yu-Chien Peter Lin,
Jiaxun Yang
Implement initjmp() for Arm.
Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
---
arch/Kconfig | 1 +
arch/arm/lib/setjmp.S | 12 ++++++++++++
arch/arm/lib/setjmp_aarch64.S | 10 ++++++++++
3 files changed, 23 insertions(+)
diff --git a/arch/Kconfig b/arch/Kconfig
index 14111ca14fb..7a3141e92b3 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -95,6 +95,7 @@ config ARC
config ARM
bool "ARM architecture"
select HAVE_SETJMP
+ select HAVE_INITJMP
select ARCH_SUPPORTS_LTO
select CREATE_ARCH_SYMLINK
select HAVE_PRIVATE_LIBGCC if !ARM64
diff --git a/arch/arm/lib/setjmp.S b/arch/arm/lib/setjmp.S
index 2f041aeef01..81bef578719 100644
--- a/arch/arm/lib/setjmp.S
+++ b/arch/arm/lib/setjmp.S
@@ -34,3 +34,15 @@ ENTRY(longjmp)
ret lr
ENDPROC(longjmp)
.popsection
+
+.pushsection .text.initjmp, "ax"
+ENTRY(initjmp)
+ stm a1, {v1-v8}
+ /* a2: entry point address, a3: stack base, a4: stack size */
+ add a3, a3, a4
+ str a3, [a1, #32] /* where setjmp would save sp */
+ str a2, [a1, #36] /* where setjmp would save lr */
+ mov a1, #0
+ ret lr
+ENDPROC(initjmp)
+.popsection
diff --git a/arch/arm/lib/setjmp_aarch64.S b/arch/arm/lib/setjmp_aarch64.S
index 1b8d000eb48..01193ccc426 100644
--- a/arch/arm/lib/setjmp_aarch64.S
+++ b/arch/arm/lib/setjmp_aarch64.S
@@ -39,3 +39,13 @@ ENTRY(longjmp)
ret
ENDPROC(longjmp)
.popsection
+
+.pushsection .text.initjmp, "ax"
+ENTRY(initjmp)
+ /* x1: entry point address, x2: stack base, x3: stack size */
+ add x2, x2, x3
+ stp x1, x2, [x0,#88]
+ mov x0, #0
+ ret
+ENDPROC(initjmp)
+.popsection
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* Re: [PATCH v4 02/14] arm: add initjmp()
2025-03-18 10:46 ` [PATCH v4 02/14] arm: add initjmp() Jerome Forissier
@ 2025-03-18 11:15 ` Ilias Apalodimas
2025-03-18 12:28 ` Fabio Estevam
1 sibling, 0 replies; 26+ messages in thread
From: Ilias Apalodimas @ 2025-03-18 11:15 UTC (permalink / raw)
To: Jerome Forissier
Cc: u-boot, Tom Rini, Simon Glass, Heinrich Schuchardt,
Andrew Goodbody, Yu-Chien Peter Lin, Jiaxun Yang
On Tue, 18 Mar 2025 at 12:47, Jerome Forissier
<jerome.forissier@linaro.org> wrote:
>
> Implement initjmp() for Arm.
>
> Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
> ---
> arch/Kconfig | 1 +
> arch/arm/lib/setjmp.S | 12 ++++++++++++
> arch/arm/lib/setjmp_aarch64.S | 10 ++++++++++
> 3 files changed, 23 insertions(+)
>
> diff --git a/arch/Kconfig b/arch/Kconfig
> index 14111ca14fb..7a3141e92b3 100644
> --- a/arch/Kconfig
> +++ b/arch/Kconfig
> @@ -95,6 +95,7 @@ config ARC
> config ARM
> bool "ARM architecture"
> select HAVE_SETJMP
> + select HAVE_INITJMP
> select ARCH_SUPPORTS_LTO
> select CREATE_ARCH_SYMLINK
> select HAVE_PRIVATE_LIBGCC if !ARM64
> diff --git a/arch/arm/lib/setjmp.S b/arch/arm/lib/setjmp.S
> index 2f041aeef01..81bef578719 100644
> --- a/arch/arm/lib/setjmp.S
> +++ b/arch/arm/lib/setjmp.S
> @@ -34,3 +34,15 @@ ENTRY(longjmp)
> ret lr
> ENDPROC(longjmp)
> .popsection
> +
> +.pushsection .text.initjmp, "ax"
> +ENTRY(initjmp)
> + stm a1, {v1-v8}
> + /* a2: entry point address, a3: stack base, a4: stack size */
> + add a3, a3, a4
> + str a3, [a1, #32] /* where setjmp would save sp */
> + str a2, [a1, #36] /* where setjmp would save lr */
> + mov a1, #0
> + ret lr
> +ENDPROC(initjmp)
> +.popsection
> diff --git a/arch/arm/lib/setjmp_aarch64.S b/arch/arm/lib/setjmp_aarch64.S
> index 1b8d000eb48..01193ccc426 100644
> --- a/arch/arm/lib/setjmp_aarch64.S
> +++ b/arch/arm/lib/setjmp_aarch64.S
> @@ -39,3 +39,13 @@ ENTRY(longjmp)
> ret
> ENDPROC(longjmp)
> .popsection
> +
> +.pushsection .text.initjmp, "ax"
> +ENTRY(initjmp)
> + /* x1: entry point address, x2: stack base, x3: stack size */
> + add x2, x2, x3
> + stp x1, x2, [x0,#88]
> + mov x0, #0
> + ret
> +ENDPROC(initjmp)
> +.popsection
> --
> 2.43.0
>
Acked-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v4 02/14] arm: add initjmp()
2025-03-18 10:46 ` [PATCH v4 02/14] arm: add initjmp() Jerome Forissier
2025-03-18 11:15 ` Ilias Apalodimas
@ 2025-03-18 12:28 ` Fabio Estevam
2025-03-18 12:51 ` Jerome Forissier
1 sibling, 1 reply; 26+ messages in thread
From: Fabio Estevam @ 2025-03-18 12:28 UTC (permalink / raw)
To: Jerome Forissier
Cc: u-boot, Ilias Apalodimas, Tom Rini, Simon Glass,
Heinrich Schuchardt, Andrew Goodbody, Yu-Chien Peter Lin,
Jiaxun Yang
On Tue, Mar 18, 2025 at 7:47 AM Jerome Forissier
<jerome.forissier@linaro.org> wrote:
>
> Implement initjmp() for Arm.
Please improve the commit log by explaining the motivation for adding
initjmp() for Arm.
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v4 02/14] arm: add initjmp()
2025-03-18 12:28 ` Fabio Estevam
@ 2025-03-18 12:51 ` Jerome Forissier
2025-03-18 12:57 ` Fabio Estevam
0 siblings, 1 reply; 26+ messages in thread
From: Jerome Forissier @ 2025-03-18 12:51 UTC (permalink / raw)
To: Fabio Estevam
Cc: u-boot, Ilias Apalodimas, Tom Rini, Simon Glass,
Heinrich Schuchardt, Andrew Goodbody, Yu-Chien Peter Lin,
Jiaxun Yang
Hi Fabio,
On 3/18/25 13:28, Fabio Estevam wrote:
> On Tue, Mar 18, 2025 at 7:47 AM Jerome Forissier
> <jerome.forissier@linaro.org> wrote:
>>
>> Implement initjmp() for Arm.
>
> Please improve the commit log by explaining the motivation for adding
> initjmp() for Arm.
The motivation is described in "[PATCH v4 01/14] arch: introduce initjmp()
and Kconfig symbol HAVE_INITJMP" [1]. Do you think a few words should be
repeated for each and every platform (currently: arm, riscv, sandbox)?
[1] https://lists.denx.de/pipermail/u-boot/2025-March/583830.html
Thanks,
--
Jerome
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v4 02/14] arm: add initjmp()
2025-03-18 12:51 ` Jerome Forissier
@ 2025-03-18 12:57 ` Fabio Estevam
0 siblings, 0 replies; 26+ messages in thread
From: Fabio Estevam @ 2025-03-18 12:57 UTC (permalink / raw)
To: Jerome Forissier
Cc: u-boot, Ilias Apalodimas, Tom Rini, Simon Glass,
Heinrich Schuchardt, Andrew Goodbody, Yu-Chien Peter Lin,
Jiaxun Yang
Hi Jerome,
On Tue, Mar 18, 2025 at 9:51 AM Jerome Forissier
<jerome.forissier@linaro.org> wrote:
> The motivation is described in "[PATCH v4 01/14] arch: introduce initjmp()
> and Kconfig symbol HAVE_INITJMP" [1]. Do you think a few words should be
> repeated for each and every platform (currently: arm, riscv, sandbox)?
Yes, repeating the explanation for every platform would be better.
Thanks
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v4 03/14] riscv: add initjmp()
2025-03-18 10:46 [PATCH v4 00/14] Uthreads Jerome Forissier
2025-03-18 10:46 ` [PATCH v4 01/14] arch: introduce initjmp() and Kconfig symbol HAVE_INITJMP Jerome Forissier
2025-03-18 10:46 ` [PATCH v4 02/14] arm: add initjmp() Jerome Forissier
@ 2025-03-18 10:46 ` Jerome Forissier
2025-03-18 10:46 ` [PATCH v4 04/14] sandbox: " Jerome Forissier
` (10 subsequent siblings)
13 siblings, 0 replies; 26+ messages in thread
From: Jerome Forissier @ 2025-03-18 10:46 UTC (permalink / raw)
To: u-boot
Cc: Ilias Apalodimas, Jerome Forissier, Tom Rini, Rick Chen, Leo,
Simon Glass, Heinrich Schuchardt, Jiaxun Yang, Yu-Chien Peter Lin,
Andrew Goodbody
Implement initjmp() for RISC-V.
Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
---
arch/Kconfig | 1 +
arch/riscv/lib/setjmp.S | 11 +++++++++++
2 files changed, 12 insertions(+)
diff --git a/arch/Kconfig b/arch/Kconfig
index 7a3141e92b3..aa60c5ff03c 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -153,6 +153,7 @@ config RISCV
bool "RISC-V architecture"
select CREATE_ARCH_SYMLINK
select HAVE_SETJMP
+ select HAVE_INITJMP
select SUPPORT_ACPI
select SUPPORT_LITTLE_ENDIAN
select SUPPORT_OF_CONTROL
diff --git a/arch/riscv/lib/setjmp.S b/arch/riscv/lib/setjmp.S
index 99d6195827e..9e1f3d5749b 100644
--- a/arch/riscv/lib/setjmp.S
+++ b/arch/riscv/lib/setjmp.S
@@ -59,3 +59,14 @@ ENTRY(longjmp)
ret
ENDPROC(longjmp)
.popsection
+
+.pushsection .text.initjmp, "ax"
+ENTRY(initjmp)
+ /* a1: entry point address, a2: stack base, a3: stack size */
+ add a2, a2, a3
+ STORE_IDX(a1, 12)
+ STORE_IDX(a2, 13)
+ li a0, 0
+ ret
+ENDPROC(initjmp)
+.popsection
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v4 04/14] sandbox: add initjmp()
2025-03-18 10:46 [PATCH v4 00/14] Uthreads Jerome Forissier
` (2 preceding siblings ...)
2025-03-18 10:46 ` [PATCH v4 03/14] riscv: " Jerome Forissier
@ 2025-03-18 10:46 ` Jerome Forissier
2025-03-18 10:46 ` [PATCH v4 05/14] test: lib: add initjmp() test Jerome Forissier
` (9 subsequent siblings)
13 siblings, 0 replies; 26+ messages in thread
From: Jerome Forissier @ 2025-03-18 10:46 UTC (permalink / raw)
To: u-boot; +Cc: Ilias Apalodimas, Jerome Forissier, Simon Glass, Tom Rini
Add ininijmp() to sandbox. The implementation is taken verbatim from
barebox [1] with the exception of the additional stack_sz argument.
It is quite complex because contrary to U-Boot platform code we don't
know how the system's C library implements the jump buffer, so we can't
just write the function and stack pointers into it.
[1] https://github.com/barebox/barebox/blob/b2a15c383ddc/arch/sandbox/os/setjmp.c
Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
---
arch/sandbox/cpu/Makefile | 11 ++-
arch/sandbox/cpu/initjmp.c | 175 +++++++++++++++++++++++++++++++++++++
2 files changed, 185 insertions(+), 1 deletion(-)
create mode 100644 arch/sandbox/cpu/initjmp.c
diff --git a/arch/sandbox/cpu/Makefile b/arch/sandbox/cpu/Makefile
index bfcdc335d32..038ad78accc 100644
--- a/arch/sandbox/cpu/Makefile
+++ b/arch/sandbox/cpu/Makefile
@@ -5,7 +5,7 @@
# (C) Copyright 2000-2003
# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
-obj-y := cache.o cpu.o state.o
+obj-y := cache.o cpu.o state.o initjmp.o
extra-y := start.o os.o
extra-$(CONFIG_SANDBOX_SDL) += sdl.o
obj-$(CONFIG_XPL_BUILD) += spl.o
@@ -29,6 +29,15 @@ cmd_cc_eth-raw-os.o = $(CC) $(filter-out -nostdinc, \
$(obj)/eth-raw-os.o: $(src)/eth-raw-os.c FORCE
$(call if_changed_dep,cc_eth-raw-os.o)
+# initjmp.c is build in the system environment, so needs standard includes
+# CFLAGS_REMOVE_initjmp.o cannot be used to drop header include path
+quiet_cmd_cc_initjmp.o = CC $(quiet_modtag) $@
+cmd_cc_initjmp.o = $(CC) $(filter-out -nostdinc, \
+ $(patsubst -I%,-idirafter%,$(c_flags))) -c -o $@ $<
+
+$(obj)/initjmp.o: $(src)/initjmp.c FORCE
+ $(call if_changed_dep,cc_initjmp.o)
+
# sdl.c fails to build with -fshort-wchar using musl
cmd_cc_sdl.o = $(CC) $(filter-out -nostdinc -fshort-wchar, \
$(patsubst -I%,-idirafter%,$(c_flags))) -fno-lto -c -o $@ $<
diff --git a/arch/sandbox/cpu/initjmp.c b/arch/sandbox/cpu/initjmp.c
new file mode 100644
index 00000000000..6e72d32cb4b
--- /dev/null
+++ b/arch/sandbox/cpu/initjmp.c
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * An implementation of initjmp() in C, that plays well with the system's
+ * setjmp() and longjmp() functions.
+ * Taken verbatim from arch/sandbox/os/setjmp.c in the barebox project.
+ * Modified so that initjmp() accepts a stack_size argument.
+ *
+ * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
+ * Copyright (C) 2011 Kevin Wolf <kwolf@redhat.com>
+ * Copyright (C) 2012 Alex Barcelo <abarcelo@ac.upc.edu>
+ * Copyright (C) 2021 Ahmad Fatoum, Pengutronix
+ * Copyright (C) 2025 Linaro Ltd.
+ * This file is partly based on pth_mctx.c, from the GNU Portable Threads
+ * Copyright (c) 1999-2006 Ralf S. Engelschall <rse@engelschall.com>
+ */
+
+/* XXX Is there a nicer way to disable glibc's stack check for longjmp? */
+#ifdef _FORTIFY_SOURCE
+#undef _FORTIFY_SOURCE
+#endif
+
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <setjmp.h>
+#include <signal.h>
+
+typedef sigjmp_buf _jmp_buf __attribute__((aligned((16))));
+_Static_assert(sizeof(_jmp_buf) <= 512, "sigjmp_buf size exceeds expectation");
+
+/*
+ * Information for the signal handler (trampoline)
+ */
+static struct {
+ _jmp_buf *reenter;
+ void (*entry)(void);
+ volatile sig_atomic_t called;
+} tr_state;
+
+/*
+ * "boot" function
+ * This is what starts the coroutine, is called from the trampoline
+ * (from the signal handler when it is not signal handling, read ahead
+ * for more information).
+ */
+static void __attribute__((noinline, noreturn))
+coroutine_bootstrap(void (*entry)(void))
+{
+ for (;;)
+ entry();
+}
+
+/*
+ * This is used as the signal handler. This is called with the brand new stack
+ * (thanks to sigaltstack). We have to return, given that this is a signal
+ * handler and the sigmask and some other things are changed.
+ */
+static void coroutine_trampoline(int signal)
+{
+ /* Get the thread specific information */
+ tr_state.called = 1;
+
+ /*
+ * Here we have to do a bit of a ping pong between the caller, given that
+ * this is a signal handler and we have to do a return "soon". Then the
+ * caller can reestablish everything and do a siglongjmp here again.
+ */
+ if (!sigsetjmp(*tr_state.reenter, 0)) {
+ return;
+ }
+
+ /*
+ * Ok, the caller has siglongjmp'ed back to us, so now prepare
+ * us for the real machine state switching. We have to jump
+ * into another function here to get a new stack context for
+ * the auto variables (which have to be auto-variables
+ * because the start of the thread happens later). Else with
+ * PIC (i.e. Position Independent Code which is used when PTH
+ * is built as a shared library) most platforms would
+ * horrible core dump as experience showed.
+ */
+ coroutine_bootstrap(tr_state.entry);
+}
+
+int __attribute__((weak)) initjmp(_jmp_buf jmp, void (*func)(void),
+ void *stack_base, size_t stack_size)
+{
+ struct sigaction sa;
+ struct sigaction osa;
+ stack_t ss;
+ stack_t oss;
+ sigset_t sigs;
+ sigset_t osigs;
+
+ /* The way to manipulate stack is with the sigaltstack function. We
+ * prepare a stack, with it delivering a signal to ourselves and then
+ * put sigsetjmp/siglongjmp where needed.
+ * This has been done keeping coroutine-ucontext (from the QEMU project)
+ * as a model and with the pth ideas (GNU Portable Threads).
+ * See coroutine-ucontext for the basics of the coroutines and see
+ * pth_mctx.c (from the pth project) for the
+ * sigaltstack way of manipulating stacks.
+ */
+
+ tr_state.entry = func;
+ tr_state.reenter = (void *)jmp;
+
+ /*
+ * Preserve the SIGUSR2 signal state, block SIGUSR2,
+ * and establish our signal handler. The signal will
+ * later transfer control onto the signal stack.
+ */
+ sigemptyset(&sigs);
+ sigaddset(&sigs, SIGUSR2);
+ pthread_sigmask(SIG_BLOCK, &sigs, &osigs);
+ sa.sa_handler = coroutine_trampoline;
+ sigfillset(&sa.sa_mask);
+ sa.sa_flags = SA_ONSTACK;
+ if (sigaction(SIGUSR2, &sa, &osa) != 0) {
+ return -1;
+ }
+
+ /*
+ * Set the new stack.
+ */
+ ss.ss_sp = stack_base;
+ ss.ss_size = stack_size;
+ ss.ss_flags = 0;
+ if (sigaltstack(&ss, &oss) < 0) {
+ return -1;
+ }
+
+ /*
+ * Now transfer control onto the signal stack and set it up.
+ * It will return immediately via "return" after the sigsetjmp()
+ * was performed. Be careful here with race conditions. The
+ * signal can be delivered the first time sigsuspend() is
+ * called.
+ */
+ tr_state.called = 0;
+ pthread_kill(pthread_self(), SIGUSR2);
+ sigfillset(&sigs);
+ sigdelset(&sigs, SIGUSR2);
+ while (!tr_state.called) {
+ sigsuspend(&sigs);
+ }
+
+ /*
+ * Inform the system that we are back off the signal stack by
+ * removing the alternative signal stack. Be careful here: It
+ * first has to be disabled, before it can be removed.
+ */
+ sigaltstack(NULL, &ss);
+ ss.ss_flags = SS_DISABLE;
+ if (sigaltstack(&ss, NULL) < 0) {
+ return -1;
+ }
+ sigaltstack(NULL, &ss);
+ if (!(oss.ss_flags & SS_DISABLE)) {
+ sigaltstack(&oss, NULL);
+ }
+
+ /*
+ * Restore the old SIGUSR2 signal handler and mask
+ */
+ sigaction(SIGUSR2, &osa, NULL);
+ pthread_sigmask(SIG_SETMASK, &osigs, NULL);
+
+ /*
+ * jmp can now be used to enter the trampoline again, but not as a
+ * signal handler. Instead it's longjmp'd to directly.
+ */
+ return 0;
+}
+
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v4 05/14] test: lib: add initjmp() test
2025-03-18 10:46 [PATCH v4 00/14] Uthreads Jerome Forissier
` (3 preceding siblings ...)
2025-03-18 10:46 ` [PATCH v4 04/14] sandbox: " Jerome Forissier
@ 2025-03-18 10:46 ` Jerome Forissier
2025-03-18 10:46 ` [PATCH v4 06/14] uthread: add cooperative multi-tasking interface Jerome Forissier
` (8 subsequent siblings)
13 siblings, 0 replies; 26+ messages in thread
From: Jerome Forissier @ 2025-03-18 10:46 UTC (permalink / raw)
To: u-boot
Cc: Ilias Apalodimas, Jerome Forissier, Tom Rini, Simon Glass,
Heinrich Schuchardt, Raymond Mao, Philippe Reynes
Test the initjmp() function when HAVE_INITJMP is set. Use the test as an
example in the API documentation.
Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
---
doc/api/setjmp.rst | 10 +++++++
test/lib/Makefile | 1 +
test/lib/initjmp.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 84 insertions(+)
create mode 100644 test/lib/initjmp.c
diff --git a/doc/api/setjmp.rst b/doc/api/setjmp.rst
index 7718047085c..c48b2e3e096 100644
--- a/doc/api/setjmp.rst
+++ b/doc/api/setjmp.rst
@@ -8,3 +8,13 @@ Long jump API
.. kernel-doc:: include/setjmp.h
:internal:
+
+Example
+-------
+
+Here is an example showing how to use the a long jump functions and
+initjmp() in particular:
+
+.. literalinclude:: ../../test/lib/initjmp.c
+ :language: c
+ :linenos:
diff --git a/test/lib/Makefile b/test/lib/Makefile
index 0e4cb8e3dfd..bf04685dae1 100644
--- a/test/lib/Makefile
+++ b/test/lib/Makefile
@@ -14,6 +14,7 @@ obj-y += hexdump.o
obj-$(CONFIG_SANDBOX) += kconfig.o
obj-y += lmb.o
obj-$(CONFIG_HAVE_SETJMP) += longjmp.o
+obj-$(CONFIG_HAVE_INITJMP) += initjmp.o
obj-$(CONFIG_CONSOLE_RECORD) += test_print.o
obj-$(CONFIG_SSCANF) += sscanf.o
obj-$(CONFIG_$(PHASE_)STRTO) += str.o
diff --git a/test/lib/initjmp.c b/test/lib/initjmp.c
new file mode 100644
index 00000000000..5b4b50b3f0f
--- /dev/null
+++ b/test/lib/initjmp.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2025 Linaro Limited
+ *
+ * Unit test for initjmp()
+ */
+
+#include <compiler.h>
+#include <setjmp.h>
+#include <stdbool.h>
+#include <test/lib.h>
+#include <test/ut.h>
+#include <vsprintf.h>
+
+static bool ep_entered;
+static jmp_buf return_buf;
+
+static void __noreturn entrypoint(void)
+{
+ ep_entered = true;
+
+ /* Jump back to the main routine */
+ longjmp(return_buf, 1);
+
+ /* Not reached */
+ panic("longjmp failed\n");
+}
+
+static int lib_initjmp(struct unit_test_state *uts)
+{
+ int ret;
+ void *stack;
+ jmp_buf buf;
+ /* Arbitrary but smaller values (< page size?) fail on SANDBOX */
+ size_t stack_sz = 8192;
+
+ (void)entrypoint;
+
+ ep_entered = false;
+
+ stack = malloc(stack_sz);
+ ut_assertnonnull(stack);
+
+ /*
+ * Prepare return_buf so that entrypoint may jump back just after the
+ * if()
+ */
+ if (!setjmp(return_buf)) {
+ /* return_buf initialized, entrypoint not yet called */
+
+ /*
+ * Prepare another jump buffer to jump into entrypoint with the
+ * given stack
+ */
+ ret = initjmp(buf, entrypoint, stack, stack_sz);
+ ut_assertok(ret);
+
+ /* Jump into entrypoint */
+ longjmp(buf, 1);
+ /*
+ * Not reached since entrypoint is expected to branch after
+ * the if()
+ */
+ ut_assert(false);
+ }
+
+ ut_assert(ep_entered);
+
+ free(stack);
+
+ return 0;
+}
+LIB_TEST(lib_initjmp, 0);
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v4 06/14] uthread: add cooperative multi-tasking interface
2025-03-18 10:46 [PATCH v4 00/14] Uthreads Jerome Forissier
` (4 preceding siblings ...)
2025-03-18 10:46 ` [PATCH v4 05/14] test: lib: add initjmp() test Jerome Forissier
@ 2025-03-18 10:46 ` Jerome Forissier
2025-03-18 10:46 ` [PATCH v4 07/14] cyclic: invoke uthread_schedule() from schedule() Jerome Forissier
` (7 subsequent siblings)
13 siblings, 0 replies; 26+ messages in thread
From: Jerome Forissier @ 2025-03-18 10:46 UTC (permalink / raw)
To: u-boot
Cc: Ilias Apalodimas, Jerome Forissier, Tom Rini, Christian Marangi,
Heinrich Schuchardt, Vasileios Amoiridis, Simon Glass,
Sughosh Ganu, Raymond Mao, Patrick Rudolph, Michal Simek,
Mattijs Korpershoek
Add a new internal API called uthread (Kconfig symbol: UTHREAD) which
provides cooperative multi-tasking. The goal is to be able to improve
the performance of some parts of U-Boot by overlapping lengthy
operations, and also implement background jobs in the U-Boot shell.
Each uthread has its own stack allocated on the heap. The default stack
size is defined by the UTHREAD_STACK_SIZE symbol and is used when
uthread_create() receives zero for the stack_sz argument.
The implementation is based on context-switching via initjmp()/setjmp()/
longjmp() and is inspired from barebox threads [1]. A notion of thread
group helps with dependencies, such as when a thread needs to block
until a number of other threads have returned.
The name "uthread" comes from "user-space threads" because the
scheduling happens with no help from a higher privileged mode, contrary
to more complex models where kernel threads are defined. But the 'u'
may as well stand for 'U-Boot' since the bootloader may actually be
running at any privilege level and the notion of user vs. kernel may
not make much sense in this context.
[1] https://github.com/barebox/barebox/blob/master/common/bthread.c
Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
---
doc/api/index.rst | 1 +
doc/api/uthread.rst | 7 +++
include/uthread.h | 123 +++++++++++++++++++++++++++++++++++++++
lib/Kconfig | 21 +++++++
lib/Makefile | 2 +
lib/uthread.c | 139 ++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 293 insertions(+)
create mode 100644 doc/api/uthread.rst
create mode 100644 include/uthread.h
create mode 100644 lib/uthread.c
diff --git a/doc/api/index.rst b/doc/api/index.rst
index 0dc9ad45d41..506843ed74a 100644
--- a/doc/api/index.rst
+++ b/doc/api/index.rst
@@ -29,3 +29,4 @@ U-Boot API documentation
sysreset
timer
unicode
+ uthread
diff --git a/doc/api/uthread.rst b/doc/api/uthread.rst
new file mode 100644
index 00000000000..21233ff6b22
--- /dev/null
+++ b/doc/api/uthread.rst
@@ -0,0 +1,7 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Uthread API
+===========
+
+.. kernel-doc:: include/uthread.h
+ :internal:
diff --git a/include/uthread.h b/include/uthread.h
new file mode 100644
index 00000000000..ec3034c097a
--- /dev/null
+++ b/include/uthread.h
@@ -0,0 +1,123 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2025 Linaro Limited
+ */
+
+#include <linux/list.h>
+#include <linux/types.h>
+#include <setjmp.h>
+
+#ifndef _UTHREAD_H_
+#define _UTHREAD_H_
+
+/**
+ * DOC: Overview
+ *
+ * The uthread framework is a basic task scheduler that allows to run functions
+ * "in parallel" on a single CPU core. The scheduling is cooperative, not
+ * preemptive -- meaning that context switches from one task to another task is
+ * voluntary, via a call to uthread_schedule(). This characteristic makes thread
+ * synchronization much easier, because a thread cannot be interrupted in the
+ * middle of a critical section (reading from or writing to shared state, for
+ * instance).
+ *
+ * CONFIG_UTHREAD in lib/Kconfig enables the uthread framework. When disabled,
+ * the uthread_create() and uthread_schedule() functions may still be used so
+ * that code differences between uthreads enabled and disabled can be reduced to
+ * a minimum.
+ */
+
+/**
+ * struct uthread - a thread object
+ *
+ * @fn: thread entry point
+ * @arg: argument passed to the entry point when the thread is started
+ * @ctx: context to resume execution of this thread (via longjmp())
+ * @stack: initial stack pointer for the thread
+ * @done: true once @fn has returned, false otherwise
+ * @grp_id: user-supplied identifier for this thread and possibly others. A
+ * thread can belong to zero or one group (not more), and a group may contain
+ * any number of threads.
+ * @list: link in the global scheduler list
+ */
+struct uthread {
+ void (*fn)(void *);
+ void *arg;
+ jmp_buf ctx;
+ void *stack;
+ bool done;
+ unsigned int grp_id;
+ struct list_head list;
+};
+
+#ifdef CONFIG_UTHREAD
+
+/**
+ * uthread_create() - Create a uthread object and make it ready for execution
+ *
+ * Threads are automatically deleted when they return from their entry point.
+ *
+ * @uthr: a pointer to a user-allocated uthread structure to store information
+ * about the new thread, or NULL to let the framework allocate and manage its
+ * own structure.
+ * @fn: the thread's entry point
+ * @arg: argument passed to the thread's entry point
+ * @stack_sz: stack size for the new thread (in bytes). The stack is allocated
+ * on the heap.
+ * @grp_id: an optional thread group ID that the new thread should belong to
+ * (zero for no group)
+ */
+int uthread_create(struct uthread *uthr, void (*fn)(void *), void *arg,
+ size_t stack_sz, unsigned int grp_id);
+/**
+ * uthread_schedule() - yield the CPU to the next runnable thread
+ *
+ * This function is called either by the main thread or any secondary thread
+ * (that is, any thread created via uthread_create()) to switch execution to
+ * the next runnable thread.
+ *
+ * Return: true if a thread was scheduled, false if no runnable thread was found
+ */
+bool uthread_schedule(void);
+/**
+ * uthread_grp_new_id() - return a new ID for a thread group
+ *
+ * Return: the new thread group ID
+ */
+unsigned int uthread_grp_new_id(void);
+/**
+ * uthread_grp_done() - test if all threads in a group are done
+ *
+ * @grp_id: the ID of the thread group that should be considered
+ * Return: false if the group contains at least one runnable thread (i.e., one
+ * thread which entry point has not returned yet), true otherwise
+ */
+bool uthread_grp_done(unsigned int grp_id);
+
+#else
+
+static inline int uthread_create(struct uthread *uthr, void (*fn)(void *),
+ void *arg, size_t stack_sz,
+ unsigned int grp_id)
+{
+ fn(arg);
+ return 0;
+}
+
+static inline bool uthread_schedule(void)
+{
+ return false;
+}
+
+static inline unsigned int uthread_grp_new_id(void)
+{
+ return 0;
+}
+
+static inline bool uthread_grp_done(unsigned int grp_id)
+{
+ return true;
+}
+
+#endif /* CONFIG_UTHREAD */
+#endif /* _UTHREAD_H_ */
diff --git a/lib/Kconfig b/lib/Kconfig
index a21b3378fa7..db9debaaeeb 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -1255,6 +1255,27 @@ config PHANDLE_CHECK_SEQ
enable this config option to distinguish them using
phandles in fdtdec_get_alias_seq() function.
+config UTHREAD
+ bool "Enable thread support"
+ depends on HAVE_INITJMP
+ help
+ Implement a simple form of cooperative multi-tasking based on
+ context-switching via initjmp(), setjmp() and longjmp(). The
+ uthread_ interface enables the main thread of execution to create
+ one or more secondary threads and schedule them until they all have
+ returned. At any point a thread may suspend its execution and
+ schedule another thread, which allows for the efficient multiplexing
+ of leghthy operations.
+
+config UTHREAD_STACK_SIZE
+ int "Default uthread stack size"
+ depends on UTHREAD
+ default 32768
+ help
+ The default stack size for uthreads. Each uthread has its own stack.
+ When the stack_sz argument to uthread_create() is zero then this
+ value is used.
+
endmenu
source "lib/fwu_updates/Kconfig"
diff --git a/lib/Makefile b/lib/Makefile
index a30ce1595d5..cd7d04528c6 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -160,6 +160,8 @@ obj-$(CONFIG_LIB_ELF) += elf.o
obj-$(CONFIG_$(PHASE_)SEMIHOSTING) += semihosting.o
+obj-$(CONFIG_UTHREAD) += uthread.o
+
#
# Build a fast OID lookup registry from include/linux/oid_registry.h
#
diff --git a/lib/uthread.c b/lib/uthread.c
new file mode 100644
index 00000000000..c3f6c84c234
--- /dev/null
+++ b/lib/uthread.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2021 Ahmad Fatoum, Pengutronix
+ * Copyright (C) 2025 Linaro Limited
+ *
+ * An implementation of cooperative multi-tasking inspired from barebox threads
+ * https://github.com/barebox/barebox/blob/master/common/bthread.c
+ */
+
+#include <compiler.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <malloc.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include <uthread.h>
+
+static struct uthread main_thread = {
+ .list = LIST_HEAD_INIT(main_thread.list),
+};
+
+static struct uthread *current = &main_thread;
+
+/**
+ * uthread_trampoline() - Call the current thread's entry point then resume the
+ * main thread.
+ *
+ * This is a helper function which is used as the @func argument to the
+ * initjmp() function, and ultimately invoked via setjmp(). It does not return
+ * but instead longjmp()'s back to the main thread.
+ */
+static void __noreturn uthread_trampoline(void)
+{
+ struct uthread *curr = current;
+
+ curr->fn(curr->arg);
+ curr->done = true;
+ current = &main_thread;
+ longjmp(current->ctx, 1);
+ /* Not reached */
+ while (true)
+ ;
+}
+
+/**
+ * uthread_free() - Free memory used by a uthread object.
+ */
+static void uthread_free(struct uthread *uthread)
+{
+ if (!uthread)
+ return;
+ free(uthread->stack);
+ free(uthread);
+}
+
+int uthread_create(struct uthread *uthr, void (*fn)(void *), void *arg,
+ size_t stack_sz, unsigned int grp_id)
+{
+ bool user_allocated = false;
+
+ if (!stack_sz)
+ stack_sz = CONFIG_UTHREAD_STACK_SIZE;
+
+ if (uthr) {
+ user_allocated = true;
+ } else {
+ uthr = calloc(1, sizeof(*uthr));
+ if (!uthr)
+ return -1;
+ }
+
+ uthr->stack = memalign(16, stack_sz);
+ if (!uthr->stack)
+ goto err;
+
+ uthr->fn = fn;
+ uthr->arg = arg;
+ uthr->grp_id = grp_id;
+
+ list_add_tail(&uthr->list, ¤t->list);
+
+ initjmp(uthr->ctx, uthread_trampoline, uthr->stack, stack_sz);
+
+ return 0;
+err:
+ if (!user_allocated)
+ free(uthr);
+ return -1;
+}
+
+/**
+ * uthread_resume() - switch execution to a given thread
+ *
+ * @uthread: the thread object that should be resumed
+ */
+static void uthread_resume(struct uthread *uthread)
+{
+ if (!setjmp(current->ctx)) {
+ current = uthread;
+ longjmp(uthread->ctx, 1);
+ }
+}
+
+bool uthread_schedule(void)
+{
+ struct uthread *next;
+ struct uthread *tmp;
+
+ list_for_each_entry_safe(next, tmp, ¤t->list, list) {
+ if (!next->done) {
+ uthread_resume(next);
+ return true;
+ } else {
+ /* Found a 'done' thread, free its resources */
+ list_del(&next->list);
+ uthread_free(next);
+ }
+ }
+ return false;
+}
+
+unsigned int uthread_grp_new_id(void)
+{
+ static unsigned int id = 0;
+
+ return ++id;
+}
+
+bool uthread_grp_done(unsigned int grp_id)
+{
+ struct uthread *next;
+
+ list_for_each_entry(next, &main_thread.list, list) {
+ if (next->grp_id == grp_id && !next->done)
+ return false;
+ }
+
+ return true;
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v4 07/14] cyclic: invoke uthread_schedule() from schedule()
2025-03-18 10:46 [PATCH v4 00/14] Uthreads Jerome Forissier
` (5 preceding siblings ...)
2025-03-18 10:46 ` [PATCH v4 06/14] uthread: add cooperative multi-tasking interface Jerome Forissier
@ 2025-03-18 10:46 ` Jerome Forissier
2025-03-18 10:46 ` [PATCH v4 08/14] lib: time: hook uthread_schedule() into udelay() Jerome Forissier
` (6 subsequent siblings)
13 siblings, 0 replies; 26+ messages in thread
From: Jerome Forissier @ 2025-03-18 10:46 UTC (permalink / raw)
To: u-boot
Cc: Ilias Apalodimas, Jerome Forissier, Stefan Roese, Tom Rini,
Rasmus Villemoes, Simon Glass, Patrice Chotard
Make the schedule() call from the CYCLIC framework a uthread scheduling
point too. This makes sense since schedule() is called from a lot of
places where uthread_schedule() needs to be called.
Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
Reviewed-by: Stefan Roese <sr@denx.de>
---
common/cyclic.c | 3 +++
include/u-boot/schedule.h | 3 +++
2 files changed, 6 insertions(+)
diff --git a/common/cyclic.c b/common/cyclic.c
index fad071a39c6..b695f092f52 100644
--- a/common/cyclic.c
+++ b/common/cyclic.c
@@ -16,6 +16,7 @@
#include <linux/list.h>
#include <asm/global_data.h>
#include <u-boot/schedule.h>
+#include <uthread.h>
DECLARE_GLOBAL_DATA_PTR;
@@ -100,6 +101,8 @@ void schedule(void)
*/
if (gd)
cyclic_run();
+
+ uthread_schedule();
}
int cyclic_unregister_all(void)
diff --git a/include/u-boot/schedule.h b/include/u-boot/schedule.h
index 4fd34c41229..4605971fdcb 100644
--- a/include/u-boot/schedule.h
+++ b/include/u-boot/schedule.h
@@ -3,6 +3,8 @@
#ifndef _U_BOOT_SCHEDULE_H
#define _U_BOOT_SCHEDULE_H
+#include <uthread.h>
+
#if CONFIG_IS_ENABLED(CYCLIC)
/**
* schedule() - Schedule all potentially waiting tasks
@@ -17,6 +19,7 @@ void schedule(void);
static inline void schedule(void)
{
+ uthread_schedule();
}
#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v4 08/14] lib: time: hook uthread_schedule() into udelay()
2025-03-18 10:46 [PATCH v4 00/14] Uthreads Jerome Forissier
` (6 preceding siblings ...)
2025-03-18 10:46 ` [PATCH v4 07/14] cyclic: invoke uthread_schedule() from schedule() Jerome Forissier
@ 2025-03-18 10:46 ` Jerome Forissier
2025-03-18 11:16 ` Ilias Apalodimas
2025-03-18 10:46 ` [PATCH v4 09/14] test: lib: add uthread test Jerome Forissier
` (5 subsequent siblings)
13 siblings, 1 reply; 26+ messages in thread
From: Jerome Forissier @ 2025-03-18 10:46 UTC (permalink / raw)
To: u-boot; +Cc: Ilias Apalodimas, Jerome Forissier, Tom Rini, Simon Glass
Introduce a uthread scheduling loop into udelay() when CONFIG_UTHREAD
is enabled. This means that any uthread calling into udelay() may yield
to uthread and be scheduled again later. There is no delay in the
scheduling loop because tests have shown that such a delay can have a
detrimental effect on the console (input drops characters).
Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
---
lib/time.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/lib/time.c b/lib/time.c
index d88edafb196..0e9b079f9cf 100644
--- a/lib/time.c
+++ b/lib/time.c
@@ -17,6 +17,7 @@
#include <asm/global_data.h>
#include <asm/io.h>
#include <linux/delay.h>
+#include <uthread.h>
#ifndef CFG_WD_PERIOD
# define CFG_WD_PERIOD (10 * 1000 * 1000) /* 10 seconds default */
@@ -197,7 +198,13 @@ void udelay(unsigned long usec)
do {
schedule();
kv = usec > CFG_WD_PERIOD ? CFG_WD_PERIOD : usec;
- __udelay(kv);
+ if (CONFIG_IS_ENABLED(UTHREAD)) {
+ ulong t0 = timer_get_us();
+ while (timer_get_us() - t0 < kv)
+ uthread_schedule();
+ } else {
+ __udelay(kv);
+ }
usec -= kv;
} while(usec);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* Re: [PATCH v4 08/14] lib: time: hook uthread_schedule() into udelay()
2025-03-18 10:46 ` [PATCH v4 08/14] lib: time: hook uthread_schedule() into udelay() Jerome Forissier
@ 2025-03-18 11:16 ` Ilias Apalodimas
0 siblings, 0 replies; 26+ messages in thread
From: Ilias Apalodimas @ 2025-03-18 11:16 UTC (permalink / raw)
To: Jerome Forissier; +Cc: u-boot, Tom Rini, Simon Glass
On Tue, 18 Mar 2025 at 12:47, Jerome Forissier
<jerome.forissier@linaro.org> wrote:
>
> Introduce a uthread scheduling loop into udelay() when CONFIG_UTHREAD
> is enabled. This means that any uthread calling into udelay() may yield
> to uthread and be scheduled again later. There is no delay in the
> scheduling loop because tests have shown that such a delay can have a
> detrimental effect on the console (input drops characters).
>
> Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
> ---
> lib/time.c | 9 ++++++++-
> 1 file changed, 8 insertions(+), 1 deletion(-)
>
> diff --git a/lib/time.c b/lib/time.c
> index d88edafb196..0e9b079f9cf 100644
> --- a/lib/time.c
> +++ b/lib/time.c
> @@ -17,6 +17,7 @@
> #include <asm/global_data.h>
> #include <asm/io.h>
> #include <linux/delay.h>
> +#include <uthread.h>
>
> #ifndef CFG_WD_PERIOD
> # define CFG_WD_PERIOD (10 * 1000 * 1000) /* 10 seconds default */
> @@ -197,7 +198,13 @@ void udelay(unsigned long usec)
> do {
> schedule();
> kv = usec > CFG_WD_PERIOD ? CFG_WD_PERIOD : usec;
> - __udelay(kv);
> + if (CONFIG_IS_ENABLED(UTHREAD)) {
> + ulong t0 = timer_get_us();
> + while (timer_get_us() - t0 < kv)
> + uthread_schedule();
> + } else {
> + __udelay(kv);
> + }
> usec -= kv;
> } while(usec);
> }
> --
> 2.43.0
>
Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v4 09/14] test: lib: add uthread test
2025-03-18 10:46 [PATCH v4 00/14] Uthreads Jerome Forissier
` (7 preceding siblings ...)
2025-03-18 10:46 ` [PATCH v4 08/14] lib: time: hook uthread_schedule() into udelay() Jerome Forissier
@ 2025-03-18 10:46 ` Jerome Forissier
2025-03-18 10:46 ` [PATCH v4 10/14] dm: usb: move bus initialization into new static function usb_init_bus() Jerome Forissier
` (4 subsequent siblings)
13 siblings, 0 replies; 26+ messages in thread
From: Jerome Forissier @ 2025-03-18 10:46 UTC (permalink / raw)
To: u-boot
Cc: Ilias Apalodimas, Jerome Forissier, Tom Rini, Heinrich Schuchardt,
Simon Glass, Philippe Reynes
Add a thread framework test to the lib tests. Update the API
documentation to use the test as an example.
Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
Acked-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
---
doc/api/uthread.rst | 12 +++++++
test/lib/Makefile | 1 +
test/lib/uthread.c | 80 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 93 insertions(+)
create mode 100644 test/lib/uthread.c
diff --git a/doc/api/uthread.rst b/doc/api/uthread.rst
index 21233ff6b22..8b25cc1ff80 100644
--- a/doc/api/uthread.rst
+++ b/doc/api/uthread.rst
@@ -3,5 +3,17 @@
Uthread API
===========
+.. kernel-doc:: include/uthread.h
+ :doc: Overview
+
.. kernel-doc:: include/uthread.h
:internal:
+
+Example
+-------
+
+Here is an example of how to use this API:
+
+.. literalinclude:: ../../test/lib/uthread.c
+ :language: c
+ :linenos:
diff --git a/test/lib/Makefile b/test/lib/Makefile
index bf04685dae1..c991dff1c63 100644
--- a/test/lib/Makefile
+++ b/test/lib/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_CRC8) += test_crc8.o
obj-$(CONFIG_UT_LIB_CRYPT) += test_crypt.o
obj-$(CONFIG_UT_TIME) += time.o
obj-$(CONFIG_$(XPL_)UT_UNICODE) += unicode.o
+obj-$(CONFIG_UTHREAD) += uthread.o
obj-$(CONFIG_LIB_UUID) += uuid.o
else
obj-$(CONFIG_SANDBOX) += kconfig_spl.o
diff --git a/test/lib/uthread.c b/test/lib/uthread.c
new file mode 100644
index 00000000000..0c0540585e7
--- /dev/null
+++ b/test/lib/uthread.c
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2025 Linaro Limited
+ *
+ * Unit test for uthread
+ */
+
+#include <stdbool.h>
+#include <test/lib.h>
+#include <test/ut.h>
+#include <uthread.h>
+
+static int count;
+
+/* A thread entry point */
+static void worker(void *arg)
+{
+ int loops = (int)(unsigned long)arg;
+ int i;
+
+ for (i = 0; i < loops; i++) {
+ count++;
+ uthread_schedule();
+ }
+}
+
+/*
+ * lib_uthread() - testing the uthread API
+ *
+ * This function creates two threads with the same entry point. The first one
+ * receives 5 as an argument, the second one receives 10. The number indicates
+ * the number of time the worker thread should loop on uthread_schedule()
+ * before returning. The workers increment a global counter each time they loop.
+ * As a result the main thread knows how many times it should call
+ * uthread_schedule() to let the two threads proceed, and it also knows which
+ * value the counter should have at any moment.
+ */
+static int lib_uthread(struct unit_test_state *uts)
+{
+ int i;
+ int id1, id2;
+
+ count = 0;
+ id1 = uthread_grp_new_id();
+ ut_assert(id1 != 0);
+ id2 = uthread_grp_new_id();
+ ut_assert(id2 != 0);
+ ut_assert(id1 != id2);
+ ut_assertok(uthread_create(NULL, worker, (void *)5, 0, id1));
+ ut_assertok(uthread_create(NULL ,worker, (void *)10, 0, 0));
+ /*
+ * The first call is expected to schedule the first worker, which will
+ * schedule the second one, which will schedule back to the main thread
+ * (here). Therefore count should be 2.
+ */
+ ut_assert(uthread_schedule());
+ ut_asserteq(2, count);
+ ut_assert(!uthread_grp_done(id1));
+ /* Four more calls should bring the count to 10 */
+ for (i = 0; i < 4; i++) {
+ ut_assert(!uthread_grp_done(id1));
+ ut_assert(uthread_schedule());
+ }
+ ut_asserteq(10, count);
+ /* This one allows the first worker to exit */
+ ut_assert(uthread_schedule());
+ /* At this point there should be no runnable thread in group 'id1' */
+ ut_assert(uthread_grp_done(id1));
+ /* Five more calls for the second worker to finish incrementing */
+ for (i = 0; i < 5; i++)
+ ut_assert(uthread_schedule());
+ ut_asserteq(15, count);
+ /* Plus one call to let the second worker return from its entry point */
+ ut_assert(uthread_schedule());
+ /* Now both tasks should be done, schedule should return false */
+ ut_assert(!uthread_schedule());
+
+ return 0;
+}
+LIB_TEST(lib_uthread, 0);
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v4 10/14] dm: usb: move bus initialization into new static function usb_init_bus()
2025-03-18 10:46 [PATCH v4 00/14] Uthreads Jerome Forissier
` (8 preceding siblings ...)
2025-03-18 10:46 ` [PATCH v4 09/14] test: lib: add uthread test Jerome Forissier
@ 2025-03-18 10:46 ` Jerome Forissier
2025-03-18 12:20 ` Marek Vasut
2025-03-18 10:46 ` [PATCH v4 11/14] dm: usb: initialize and scan multiple buses simultaneously with uthread Jerome Forissier
` (3 subsequent siblings)
13 siblings, 1 reply; 26+ messages in thread
From: Jerome Forissier @ 2025-03-18 10:46 UTC (permalink / raw)
To: u-boot
Cc: Ilias Apalodimas, Jerome Forissier, Marek Vasut, Tom Rini,
Mattijs Korpershoek, Caleb Connolly, Dragan Simic,
Heinrich Schuchardt
To prepare for the introduction of threads in the USB initialization
sequence, move code out of usb_init() into a new helper function:
usb_init_bus(). No functional change.
Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
---
drivers/usb/host/usb-uclass.c | 88 +++++++++++++++++++----------------
1 file changed, 48 insertions(+), 40 deletions(-)
diff --git a/drivers/usb/host/usb-uclass.c b/drivers/usb/host/usb-uclass.c
index bfec303e7af..cc803241461 100644
--- a/drivers/usb/host/usb-uclass.c
+++ b/drivers/usb/host/usb-uclass.c
@@ -287,9 +287,55 @@ static int usb_probe_companion(struct udevice *bus)
return 0;
}
+static int controllers_initialized;
+
+static void usb_init_bus(struct udevice *bus)
+{
+ int ret;
+
+ /* init low_level USB */
+ printf("Bus %s: ", bus->name);
+
+ /*
+ * For Sandbox, we need scan the device tree each time when we
+ * start the USB stack, in order to re-create the emulated USB
+ * devices and bind drivers for them before we actually do the
+ * driver probe.
+ *
+ * For USB onboard HUB, we need to do some non-trivial init
+ * like enabling a power regulator, before enumeration.
+ */
+ if (IS_ENABLED(CONFIG_SANDBOX) ||
+ IS_ENABLED(CONFIG_USB_ONBOARD_HUB)) {
+ ret = dm_scan_fdt_dev(bus);
+ if (ret) {
+ printf("USB device scan from fdt failed (%d)", ret);
+ return;
+ }
+ }
+
+ ret = device_probe(bus);
+ if (ret == -ENODEV) { /* No such device. */
+ puts("Port not available.\n");
+ controllers_initialized++;
+ return;
+ }
+
+ if (ret) { /* Other error. */
+ printf("probe failed, error %d\n", ret);
+ return;
+ }
+
+ ret = usb_probe_companion(bus);
+ if (ret)
+ return;
+
+ controllers_initialized++;
+ usb_started = true;
+}
+
int usb_init(void)
{
- int controllers_initialized = 0;
struct usb_uclass_priv *uc_priv;
struct usb_bus_priv *priv;
struct udevice *bus;
@@ -305,45 +351,7 @@ int usb_init(void)
uc_priv = uclass_get_priv(uc);
uclass_foreach_dev(bus, uc) {
- /* init low_level USB */
- printf("Bus %s: ", bus->name);
-
- /*
- * For Sandbox, we need scan the device tree each time when we
- * start the USB stack, in order to re-create the emulated USB
- * devices and bind drivers for them before we actually do the
- * driver probe.
- *
- * For USB onboard HUB, we need to do some non-trivial init
- * like enabling a power regulator, before enumeration.
- */
- if (IS_ENABLED(CONFIG_SANDBOX) ||
- IS_ENABLED(CONFIG_USB_ONBOARD_HUB)) {
- ret = dm_scan_fdt_dev(bus);
- if (ret) {
- printf("USB device scan from fdt failed (%d)", ret);
- continue;
- }
- }
-
- ret = device_probe(bus);
- if (ret == -ENODEV) { /* No such device. */
- puts("Port not available.\n");
- controllers_initialized++;
- continue;
- }
-
- if (ret) { /* Other error. */
- printf("probe failed, error %d\n", ret);
- continue;
- }
-
- ret = usb_probe_companion(bus);
- if (ret)
- continue;
-
- controllers_initialized++;
- usb_started = true;
+ usb_init_bus(bus);
}
/*
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* Re: [PATCH v4 10/14] dm: usb: move bus initialization into new static function usb_init_bus()
2025-03-18 10:46 ` [PATCH v4 10/14] dm: usb: move bus initialization into new static function usb_init_bus() Jerome Forissier
@ 2025-03-18 12:20 ` Marek Vasut
2025-03-19 10:43 ` Jerome Forissier
0 siblings, 1 reply; 26+ messages in thread
From: Marek Vasut @ 2025-03-18 12:20 UTC (permalink / raw)
To: Jerome Forissier, u-boot
Cc: Ilias Apalodimas, Tom Rini, Mattijs Korpershoek, Caleb Connolly,
Dragan Simic, Heinrich Schuchardt
On 3/18/25 11:46 AM, Jerome Forissier wrote:
> To prepare for the introduction of threads in the USB initialization
> sequence, move code out of usb_init() into a new helper function:
> usb_init_bus(). No functional change.
>
> Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
> Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
> ---
> drivers/usb/host/usb-uclass.c | 88 +++++++++++++++++++----------------
> 1 file changed, 48 insertions(+), 40 deletions(-)
>
> diff --git a/drivers/usb/host/usb-uclass.c b/drivers/usb/host/usb-uclass.c
> index bfec303e7af..cc803241461 100644
> --- a/drivers/usb/host/usb-uclass.c
> +++ b/drivers/usb/host/usb-uclass.c
> @@ -287,9 +287,55 @@ static int usb_probe_companion(struct udevice *bus)
> return 0;
> }
>
> +static int controllers_initialized;
I'm afraid this won't work, you will have to track the controllers in a
list somehow, because it is possible to unbind DM devices even using the
'unbind' command from U-Boot command line.
Also, why not simply track the controller state using DM in the first place?
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v4 10/14] dm: usb: move bus initialization into new static function usb_init_bus()
2025-03-18 12:20 ` Marek Vasut
@ 2025-03-19 10:43 ` Jerome Forissier
2025-03-19 15:03 ` Simon Glass
0 siblings, 1 reply; 26+ messages in thread
From: Jerome Forissier @ 2025-03-19 10:43 UTC (permalink / raw)
To: Marek Vasut, u-boot
Cc: Ilias Apalodimas, Tom Rini, Mattijs Korpershoek, Caleb Connolly,
Dragan Simic, Heinrich Schuchardt
Hello Marek,
On 3/18/25 13:20, Marek Vasut wrote:
> On 3/18/25 11:46 AM, Jerome Forissier wrote:
>> To prepare for the introduction of threads in the USB initialization
>> sequence, move code out of usb_init() into a new helper function:
>> usb_init_bus(). No functional change.
>>
>> Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
>> Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
>> ---
>> drivers/usb/host/usb-uclass.c | 88 +++++++++++++++++++----------------
>> 1 file changed, 48 insertions(+), 40 deletions(-)
>>
>> diff --git a/drivers/usb/host/usb-uclass.c b/drivers/usb/host/usb-uclass.c
>> index bfec303e7af..cc803241461 100644
>> --- a/drivers/usb/host/usb-uclass.c
>> +++ b/drivers/usb/host/usb-uclass.c
>> @@ -287,9 +287,55 @@ static int usb_probe_companion(struct udevice *bus)
>> return 0;
>> }
>> +static int controllers_initialized;
> I'm afraid this won't work, you will have to track the controllers in a list somehow, because it is possible to unbind DM devices even using the 'unbind' command from U-Boot command line.
>
> Also, why not simply track the controller state using DM in the first place?
I'll keep controllers_initialized local to usb_init() and use device_active()
in the second uclass_foreach_dev() loop.
Thanks,
--
Jerome
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v4 10/14] dm: usb: move bus initialization into new static function usb_init_bus()
2025-03-19 10:43 ` Jerome Forissier
@ 2025-03-19 15:03 ` Simon Glass
0 siblings, 0 replies; 26+ messages in thread
From: Simon Glass @ 2025-03-19 15:03 UTC (permalink / raw)
To: Jerome Forissier
Cc: Marek Vasut, u-boot, Ilias Apalodimas, Tom Rini,
Mattijs Korpershoek, Caleb Connolly, Dragan Simic,
Heinrich Schuchardt
Hi Jerome,
On Wed, 19 Mar 2025 at 11:44, Jerome Forissier
<jerome.forissier@linaro.org> wrote:
>
> Hello Marek,
>
> On 3/18/25 13:20, Marek Vasut wrote:
> > On 3/18/25 11:46 AM, Jerome Forissier wrote:
> >> To prepare for the introduction of threads in the USB initialization
> >> sequence, move code out of usb_init() into a new helper function:
> >> usb_init_bus(). No functional change.
> >>
> >> Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
> >> Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
> >> ---
> >> drivers/usb/host/usb-uclass.c | 88 +++++++++++++++++++----------------
> >> 1 file changed, 48 insertions(+), 40 deletions(-)
> >>
> >> diff --git a/drivers/usb/host/usb-uclass.c b/drivers/usb/host/usb-uclass.c
> >> index bfec303e7af..cc803241461 100644
> >> --- a/drivers/usb/host/usb-uclass.c
> >> +++ b/drivers/usb/host/usb-uclass.c
> >> @@ -287,9 +287,55 @@ static int usb_probe_companion(struct udevice *bus)
> >> return 0;
> >> }
> >> +static int controllers_initialized;
> > I'm afraid this won't work, you will have to track the controllers in a list somehow, because it is possible to unbind DM devices even using the 'unbind' command from U-Boot command line.
> >
> > Also, why not simply track the controller state using DM in the first place?
>
> I'll keep controllers_initialized local to usb_init() and use device_active()
> in the second uclass_foreach_dev() loop.
Could you please take a bit of a look at collecting the USB-scanning
state into a struct?
We should be able to call it using iteration:
struct usb_iter iter;
usb_iter_init(&iter);
usb_iter_process()
It could collect all the devices it finds into an alist and then the
caller can take a look.
The reason I am keen on this is that it will potentially allow removal
of the time delays in the USB stack, so we can deal with sticks which
take longer to respond.
I may have this wrong, but it seems that the primary delay is in
usb_urb_submit(). So what we really need is an usb_urb_async() which
enqueues the urb and then another method to poll it. That would
perhaps allow much faster USB since all devices would be scanning in
parallel, including individual devices on the same hub.
Turning the USB stack 'inside out' is how I've made bootstd work. I
means that devices which are found can be reported as they are found
and we can potentially wait a long time for the long-tail devices,
while still returning immediately to the cmdline so the user can
continue.
Regards,
Simon
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v4 11/14] dm: usb: initialize and scan multiple buses simultaneously with uthread
2025-03-18 10:46 [PATCH v4 00/14] Uthreads Jerome Forissier
` (9 preceding siblings ...)
2025-03-18 10:46 ` [PATCH v4 10/14] dm: usb: move bus initialization into new static function usb_init_bus() Jerome Forissier
@ 2025-03-18 10:46 ` Jerome Forissier
2025-03-18 10:46 ` [PATCH v4 12/14] cmd: add spawn and wait commands Jerome Forissier
` (2 subsequent siblings)
13 siblings, 0 replies; 26+ messages in thread
From: Jerome Forissier @ 2025-03-18 10:46 UTC (permalink / raw)
To: u-boot
Cc: Ilias Apalodimas, Jerome Forissier, Marek Vasut, Tom Rini,
Simon Glass, Mattijs Korpershoek, Heinrich Schuchardt,
Caleb Connolly, Julius Lehmann, Guillaume La Roque
Use the uthread framework to initialize and scan USB buses in parallel
for better performance. The console output is slightly modified with a
final per-bus report of the number of devices found, common to UTHREAD
and !UTHREAD. The USB tests are updated accordingly.
Tested on two platforms:
1. arm64 QEMU on a somewhat contrived example (4 USB buses, each with
one audio device, one keyboard, one mouse and one tablet)
$ make qemu_arm64_defconfig
$ make -j$(nproc) CROSS_COMPILE="ccache aarch64-linux-gnu-"
$ qemu-system-aarch64 -M virt -nographic -cpu max -bios u-boot.bin \
$(for i in {1..4}; do echo -device qemu-xhci,id=xhci$i \
-device\ usb-{audio,kbd,mouse,tablet},bus=xhci$i.0; \
done)
2. i.MX93 EVK (imx93_11x11_evk_defconfig) with two USB hubs, each with
one webcam and one ethernet adapter, resulting in the following device
tree:
USB device tree:
1 Hub (480 Mb/s, 0mA)
| u-boot EHCI Host Controller
|
+-2 Hub (480 Mb/s, 100mA)
| GenesysLogic USB2.1 Hub
|
+-3 Vendor specific (480 Mb/s, 350mA)
| Realtek USB 10/100/1000 LAN 001000001
|
+-4 (480 Mb/s, 500mA)
HD Pro Webcam C920 8F7CD51F
1 Hub (480 Mb/s, 0mA)
| u-boot EHCI Host Controller
|
+-2 Hub (480 Mb/s, 100mA)
| USB 2.0 Hub
|
+-3 Vendor specific (480 Mb/s, 200mA)
| Realtek USB 10/100/1000 LAN 000001
|
+-4 (480 Mb/s, 500mA)
Generic OnLan-CS30 201801010008
Note that i.MX was tested on top of the downstream repository [1] since
USB doesn't work in the upstream master branch.
[1] https://github.com/nxp-imx/uboot-imx/tree/lf-6.6.52-2.2.0
commit 6c4545203d12 ("LF-13928 update key for capsule")
The time spent in usb_init() ("usb start" command) is reported on
the console. Here are the results:
| CONFIG_UTHREAD=n | CONFIG_UTHREAD=y
--------+------------------+-----------------
QEMU | 5628 ms | 2212 ms
i.MX93 | 4591 ms | 2441 ms
Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
---
drivers/usb/host/usb-uclass.c | 92 ++++++++++++++++++++++++++++-------
test/boot/bootdev.c | 14 +++---
test/boot/bootflow.c | 2 +-
3 files changed, 83 insertions(+), 25 deletions(-)
diff --git a/drivers/usb/host/usb-uclass.c b/drivers/usb/host/usb-uclass.c
index cc803241461..7443bcc0e2c 100644
--- a/drivers/usb/host/usb-uclass.c
+++ b/drivers/usb/host/usb-uclass.c
@@ -9,6 +9,7 @@
#define LOG_CATEGORY UCLASS_USB
#include <bootdev.h>
+#include <uthread.h>
#include <dm.h>
#include <errno.h>
#include <log.h>
@@ -17,6 +18,7 @@
#include <dm/device-internal.h>
#include <dm/lists.h>
#include <dm/uclass-internal.h>
+#include <time.h>
static bool asynch_allowed;
@@ -221,25 +223,18 @@ int usb_stop(void)
return err;
}
-static void usb_scan_bus(struct udevice *bus, bool recurse)
+static void _usb_scan_bus(void *arg)
{
+ struct udevice *bus = (struct udevice *)arg;
struct usb_bus_priv *priv;
struct udevice *dev;
int ret;
priv = dev_get_uclass_priv(bus);
- assert(recurse); /* TODO: Support non-recusive */
-
- printf("scanning bus %s for devices... ", bus->name);
- debug("\n");
ret = usb_scan_device(bus, 0, USB_SPEED_FULL, &dev);
if (ret)
- printf("failed, error %d\n", ret);
- else if (priv->next_addr == 0)
- printf("No USB Device found\n");
- else
- printf("%d USB Device(s) found\n", priv->next_addr);
+ printf("Scanning bus %s failed, error %d\n", bus->name, ret);
}
static void remove_inactive_children(struct uclass *uc, struct udevice *bus)
@@ -289,12 +284,12 @@ static int usb_probe_companion(struct udevice *bus)
static int controllers_initialized;
-static void usb_init_bus(struct udevice *bus)
+static void _usb_init_bus(void *arg)
{
+ struct udevice *bus = (struct udevice *)arg;
int ret;
/* init low_level USB */
- printf("Bus %s: ", bus->name);
/*
* For Sandbox, we need scan the device tree each time when we
@@ -309,33 +304,84 @@ static void usb_init_bus(struct udevice *bus)
IS_ENABLED(CONFIG_USB_ONBOARD_HUB)) {
ret = dm_scan_fdt_dev(bus);
if (ret) {
- printf("USB device scan from fdt failed (%d)", ret);
+ printf("Bus %s: USB device scan from fdt failed (%d)\n",
+ bus->name, ret);
return;
}
}
ret = device_probe(bus);
if (ret == -ENODEV) { /* No such device. */
- puts("Port not available.\n");
+ printf("Bus %s: Port not available.\n", bus->name);
controllers_initialized++;
return;
}
if (ret) { /* Other error. */
- printf("probe failed, error %d\n", ret);
+ printf("Bus %s: probe failed, error %d\n", bus->name, ret);
return;
}
ret = usb_probe_companion(bus);
- if (ret)
+ if (ret) {
return;
+ }
controllers_initialized++;
usb_started = true;
}
+static int nthr;
+static int grp_id;
+
+static void usb_init_bus(struct udevice *bus)
+{
+ if (!grp_id)
+ grp_id = uthread_grp_new_id();
+ if (!uthread_create(NULL, _usb_init_bus, (void *)bus, 0, grp_id))
+ nthr++;
+}
+
+static void usb_scan_bus(struct udevice *bus, bool recurse)
+{
+ if (!grp_id)
+ grp_id = uthread_grp_new_id();
+ if (!uthread_create(NULL, _usb_scan_bus, (void *)bus, 0, grp_id))
+ nthr++;
+}
+
+static void usb_report_devices(struct uclass *uc)
+{
+ struct usb_bus_priv *priv;
+ struct udevice *bus;
+
+ uclass_foreach_dev(bus, uc) {
+ if (!device_active(bus))
+ continue;
+ priv = dev_get_uclass_priv(bus);
+ printf("Bus %s: ", bus->name);
+ if (priv->next_addr == 0)
+ printf("No USB Device found\n");
+ else
+ printf("%d USB Device(s) found\n", priv->next_addr);
+ }
+}
+
+static void run_threads(void)
+{
+#if CONFIG_IS_ENABLED(UTHREAD)
+ if (!nthr)
+ return;
+ while (!uthread_grp_done(grp_id))
+ uthread_schedule();
+ nthr = 0;
+ grp_id = 0;
+#endif
+}
+
int usb_init(void)
{
+ unsigned long t0 = timer_get_us();
struct usb_uclass_priv *uc_priv;
struct usb_bus_priv *priv;
struct udevice *bus;
@@ -354,6 +400,9 @@ int usb_init(void)
usb_init_bus(bus);
}
+ if (CONFIG_IS_ENABLED(UTHREAD))
+ run_threads();
+
/*
* lowlevel init done, now scan the bus for devices i.e. search HUBs
* and configure them, first scan primary controllers.
@@ -367,6 +416,9 @@ int usb_init(void)
usb_scan_bus(bus, true);
}
+ if (CONFIG_IS_ENABLED(UTHREAD))
+ run_threads();
+
/*
* Now that the primary controllers have been scanned and have handed
* over any devices they do not understand to their companions, scan
@@ -383,7 +435,10 @@ int usb_init(void)
}
}
- debug("scan end\n");
+ if (CONFIG_IS_ENABLED(UTHREAD))
+ run_threads();
+
+ usb_report_devices(uc);
/* Remove any devices that were not found on this scan */
remove_inactive_children(uc, bus);
@@ -397,6 +452,9 @@ int usb_init(void)
if (controllers_initialized == 0)
printf("No USB controllers found\n");
+ debug("USB initialized in %ld ms\n",
+ (timer_get_us() - t0) / 1000);
+
return usb_started ? 0 : -ENOENT;
}
diff --git a/test/boot/bootdev.c b/test/boot/bootdev.c
index 5f07430714e..70a1d868de8 100644
--- a/test/boot/bootdev.c
+++ b/test/boot/bootdev.c
@@ -392,8 +392,8 @@ static int bootdev_test_hunter(struct unit_test_state *uts)
ut_assert_console_end();
ut_assertok(bootdev_hunt("usb1", false));
- ut_assert_nextline(
- "Bus usb@1: scanning bus usb@1 for devices... 5 USB Device(s) found");
+ ut_assert_skip_to_line(
+ "Bus usb@1: 5 USB Device(s) found");
ut_assert_console_end();
/* USB is 7th in the list, so bit 8 */
@@ -448,8 +448,8 @@ static int bootdev_test_cmd_hunt(struct unit_test_state *uts)
ut_assert_nextline("scanning bus for devices...");
ut_assert_skip_to_line("Hunting with: spi_flash");
ut_assert_nextline("Hunting with: usb");
- ut_assert_nextline(
- "Bus usb@1: scanning bus usb@1 for devices... 5 USB Device(s) found");
+ ut_assert_skip_to_line(
+ "Bus usb@1: 5 USB Device(s) found");
ut_assert_nextline("Hunting with: virtio");
ut_assert_console_end();
@@ -550,8 +550,8 @@ static int bootdev_test_hunt_prio(struct unit_test_state *uts)
ut_assertok(bootdev_hunt_prio(BOOTDEVP_5_SCAN_SLOW, true));
ut_assert_nextline("Hunting with: ide");
ut_assert_nextline("Hunting with: usb");
- ut_assert_nextline(
- "Bus usb@1: scanning bus usb@1 for devices... 5 USB Device(s) found");
+ ut_assert_skip_to_line(
+ "Bus usb@1: 5 USB Device(s) found");
ut_assert_console_end();
return 0;
@@ -603,7 +603,7 @@ static int bootdev_test_hunt_label(struct unit_test_state *uts)
ut_assertnonnull(dev);
ut_asserteq_str("usb_mass_storage.lun0.bootdev", dev->name);
ut_asserteq(BOOTFLOW_METHF_SINGLE_UCLASS, mflags);
- ut_assert_nextlinen("Bus usb@1: scanning bus usb@1");
+ ut_assert_nextline("Bus usb@1: 5 USB Device(s) found");
ut_assert_console_end();
return 0;
diff --git a/test/boot/bootflow.c b/test/boot/bootflow.c
index eb7f00af39a..699ba0edaaf 100644
--- a/test/boot/bootflow.c
+++ b/test/boot/bootflow.c
@@ -1289,7 +1289,7 @@ static int bootflow_efi(struct unit_test_state *uts)
ut_assertok(run_command("bootflow scan", 0));
ut_assert_skip_to_line(
- "Bus usb@1: scanning bus usb@1 for devices... 5 USB Device(s) found");
+ "Bus usb@1: 5 USB Device(s) found");
ut_assertok(run_command("bootflow list", 0));
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v4 12/14] cmd: add spawn and wait commands
2025-03-18 10:46 [PATCH v4 00/14] Uthreads Jerome Forissier
` (10 preceding siblings ...)
2025-03-18 10:46 ` [PATCH v4 11/14] dm: usb: initialize and scan multiple buses simultaneously with uthread Jerome Forissier
@ 2025-03-18 10:46 ` Jerome Forissier
2025-03-18 11:16 ` Ilias Apalodimas
2025-03-31 7:54 ` Mattijs Korpershoek
2025-03-18 10:46 ` [PATCH v4 13/14] test: cmd: add test for " Jerome Forissier
2025-03-18 10:46 ` [PATCH v4 14/14] MAINTAINERS: add UTHREAD Jerome Forissier
13 siblings, 2 replies; 26+ messages in thread
From: Jerome Forissier @ 2025-03-18 10:46 UTC (permalink / raw)
To: u-boot
Cc: Ilias Apalodimas, Jerome Forissier, Tom Rini, Simon Glass,
Heinrich Schuchardt, Mattijs Korpershoek, Ibai Erkiaga,
Michal Simek, Caleb Connolly, Sebastian Reichel, Dmitry Rokosov
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
^ permalink raw reply related [flat|nested] 26+ messages in thread* Re: [PATCH v4 12/14] cmd: add spawn and wait commands
2025-03-18 10:46 ` [PATCH v4 12/14] cmd: add spawn and wait commands Jerome Forissier
@ 2025-03-18 11:16 ` Ilias Apalodimas
2025-03-31 7:54 ` Mattijs Korpershoek
1 sibling, 0 replies; 26+ messages in thread
From: Ilias Apalodimas @ 2025-03-18 11:16 UTC (permalink / raw)
To: Jerome Forissier
Cc: u-boot, Tom Rini, Simon Glass, Heinrich Schuchardt,
Mattijs Korpershoek, Ibai Erkiaga, Michal Simek, Caleb Connolly,
Sebastian Reichel, Dmitry Rokosov
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>
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v4 12/14] cmd: add spawn and wait commands
2025-03-18 10:46 ` [PATCH v4 12/14] cmd: add spawn and wait commands Jerome Forissier
2025-03-18 11:16 ` Ilias Apalodimas
@ 2025-03-31 7:54 ` Mattijs Korpershoek
2025-03-31 9:21 ` Jerome Forissier
1 sibling, 1 reply; 26+ messages in thread
From: Mattijs Korpershoek @ 2025-03-31 7:54 UTC (permalink / raw)
To: Jerome Forissier, u-boot
Cc: Ilias Apalodimas, Jerome Forissier, Tom Rini, Simon Glass,
Heinrich Schuchardt, Ibai Erkiaga, Michal Simek, Caleb Connolly,
Sebastian Reichel, Dmitry Rokosov
Hi Jerome,
Thank you for the patch.
On mar., mars 18, 2025 at 11:46, Jerome Forissier <jerome.forissier@linaro.org> wrote:
[...]
>
> 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.
> + */
Nitpick: Is this the correct copyright? For the test code in the next
patch you used
* Copyright 2025, Linaro Ltd.
Besides that:
Reviewed-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
[...]
> +
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v4 12/14] cmd: add spawn and wait commands
2025-03-31 7:54 ` Mattijs Korpershoek
@ 2025-03-31 9:21 ` Jerome Forissier
0 siblings, 0 replies; 26+ messages in thread
From: Jerome Forissier @ 2025-03-31 9:21 UTC (permalink / raw)
To: Mattijs Korpershoek, u-boot
Cc: Ilias Apalodimas, Tom Rini, Simon Glass, Heinrich Schuchardt,
Ibai Erkiaga, Michal Simek, Caleb Connolly, Sebastian Reichel,
Dmitry Rokosov
Hi Mattijs,
On 3/31/25 09:54, Mattijs Korpershoek wrote:
> Hi Jerome,
>
> Thank you for the patch.
>
> On mar., mars 18, 2025 at 11:46, Jerome Forissier <jerome.forissier@linaro.org> wrote:
>
> [...]
>
>>
>> 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.
>> + */
>
> Nitpick: Is this the correct copyright? For the test code in the next
> patch you used
>
> * Copyright 2025, Linaro Ltd.
Good catch, I'll fix it.
> Besides that:
>
> Reviewed-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
>
> [...]
>
>> +
Thanks!
--
Jerome
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v4 13/14] test: cmd: add test for spawn and wait commands
2025-03-18 10:46 [PATCH v4 00/14] Uthreads Jerome Forissier
` (11 preceding siblings ...)
2025-03-18 10:46 ` [PATCH v4 12/14] cmd: add spawn and wait commands Jerome Forissier
@ 2025-03-18 10:46 ` Jerome Forissier
2025-03-18 10:46 ` [PATCH v4 14/14] MAINTAINERS: add UTHREAD Jerome Forissier
13 siblings, 0 replies; 26+ messages in thread
From: Jerome Forissier @ 2025-03-18 10:46 UTC (permalink / raw)
To: u-boot
Cc: Ilias Apalodimas, Jerome Forissier, Tom Rini, Simon Glass,
Heinrich Schuchardt
Test the spawn and wait commands.
Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
Acked-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
---
test/cmd/Makefile | 1 +
test/cmd/spawn.c | 32 ++++++++++++++++++++++++++++++++
2 files changed, 33 insertions(+)
create mode 100644 test/cmd/spawn.c
diff --git a/test/cmd/Makefile b/test/cmd/Makefile
index d8a5e77402d..cf47f04851c 100644
--- a/test/cmd/Makefile
+++ b/test/cmd/Makefile
@@ -39,3 +39,4 @@ obj-$(CONFIG_CMD_WGET) += wget.o
endif
obj-$(CONFIG_ARM_FFA_TRANSPORT) += armffa.o
endif
+obj-$(CONFIG_CMD_SPAWN) += spawn.o
diff --git a/test/cmd/spawn.c b/test/cmd/spawn.c
new file mode 100644
index 00000000000..8f48f5ee25c
--- /dev/null
+++ b/test/cmd/spawn.c
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Tests for spawn and wait commands
+ *
+ * Copyright 2025, Linaro Ltd.
+ */
+
+#include <command.h>
+#include <test/cmd.h>
+#include <test/test.h>
+#include <test/ut.h>
+
+static int test_cmd_spawn(struct unit_test_state *uts)
+{
+ ut_assertok(run_command("wait; spawn sleep 2; setenv j ${job_id}; "
+ "spawn setenv spawned true; "
+ "setenv jj ${job_id}; wait; "
+ "echo ${j} ${jj} ${spawned}", 0));
+ console_record_readline(uts->actual_str, sizeof(uts->actual_str));
+ ut_asserteq_ptr(uts->actual_str,
+ strstr(uts->actual_str, "1 2 true"));
+
+ ut_assertok(run_command("spawn true; wait; setenv t $?; spawn false; "
+ "wait; setenv f $?; wait; echo $t $f $?", 0));
+ console_record_readline(uts->actual_str, sizeof(uts->actual_str));
+ ut_asserteq_ptr(uts->actual_str,
+ strstr(uts->actual_str, "0 1 0"));
+ ut_assert_console_end();
+
+ return 0;
+}
+CMD_TEST(test_cmd_spawn, UTF_CONSOLE);
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH v4 14/14] MAINTAINERS: add UTHREAD
2025-03-18 10:46 [PATCH v4 00/14] Uthreads Jerome Forissier
` (12 preceding siblings ...)
2025-03-18 10:46 ` [PATCH v4 13/14] test: cmd: add test for " Jerome Forissier
@ 2025-03-18 10:46 ` Jerome Forissier
13 siblings, 0 replies; 26+ messages in thread
From: Jerome Forissier @ 2025-03-18 10:46 UTC (permalink / raw)
To: u-boot
Cc: Ilias Apalodimas, Jerome Forissier, Tom Rini, Greg Malysa,
Nathan Barrett-Morrison, Simon Glass, Oliver Gaskell,
Mattijs Korpershoek, Caleb Connolly, Sam Protsenko, Robert Marko,
Sumit Garg
Add myself as the maintainer for the UTHREAD framework, the spawn/wait
commands and the associated tests.
Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
Acked-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
---
MAINTAINERS | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 92d4a158fd0..4b6bb7dfcd7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1817,6 +1817,15 @@ T: git https://source.denx.de/u-boot/custodians/u-boot-usb.git topic-xhci
F: drivers/usb/host/xhci*
F: include/usb/xhci.h
+UTHREAD
+M: Jerome Forissier <jerome.forissier@linaro.org>
+S: Maintained
+F: lib/uthread.c
+F: include/uthread.h
+F: test/lib/uthread.c
+F: cmd/spawn.c
+F: test/cmd/spawn.c
+
UUID testing
M: Abdellatif El Khlifi <abdellatif.elkhlifi@arm.com>
S: Maintained
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread