Message ID | 0b793a3251dbd9cf41066e3e358269fdb1aac3f8.1740499185.git.jerome.forissier@linaro.org |
---|---|
State | New |
Headers | show |
Series | Uthreads | expand |
Hi Jerome, On Tue, 25 Feb 2025 at 18:35, Jerome Forissier <jerome.forissier@linaro.org> wrote: > > Add an new 'a' > 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> > --- > include/uthread.h | 44 ++++++++++++ > lib/Kconfig | 21 ++++++ > lib/Makefile | 2 + > lib/uthread.c | 178 ++++++++++++++++++++++++++++++++++++++++++++++ > 4 files changed, 245 insertions(+) > create mode 100644 include/uthread.h > create mode 100644 lib/uthread.c > > diff --git a/include/uthread.h b/include/uthread.h > new file mode 100644 > index 00000000000..f1f86d210d5 > --- /dev/null > +++ b/include/uthread.h > @@ -0,0 +1,44 @@ > +/* SPDX-License-Identifier: GPL-2.0+ */ > +/* > + * Copyright 2025 Linaro Limited > + */ > + > +#include <linux/types.h> > + > +#ifndef _UTHREAD_H_ > +#define _UTHREAD_H_ > + > +#ifdef CONFIG_UTHREAD > + > +int uthread_create(void (*fn)(void *), void *arg, size_t stack_sz, > + unsigned int grp_id); > +bool uthread_schedule(void); > +unsigned int uthread_grp_new_id(void); > +bool uthread_grp_done(unsigned int grp_id); > + > +#else > + > +static inline int uthread_create(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 1a683dea670..b32740ecbcc 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 stak size for uthreads. Each uthread has its own stack. stack size > + 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 a7bc2f3134a..3610694de7a 100644 > --- a/lib/Makefile > +++ b/lib/Makefile > @@ -164,6 +164,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..430d1c0de32 > --- /dev/null > +++ b/lib/uthread.c > @@ -0,0 +1,178 @@ > +// 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 <asm/setjmp.h> > +#include <linux/kernel.h> > +#include <linux/list.h> > +#include <malloc.h> > +#include <stdint.h> > +#include <uthread.h> > + > +static struct uthread { > + void (*fn)(void *); > + void *arg; > + jmp_buf ctx; > + void *stack; > + bool done; > + unsigned int grp_id; > + struct list_head list; > +} 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 inijmp() initjump > + * 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); > +} > + > +/** > + * uthread_create() - Create a uthread object and make it ready for execution > + * > + * Threads are automatically deleted when then return from their entry point. when they [...] > +/** > + * 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) > +{ > + static unsigned int id = 0; > + > + return ++id; > +} This seems a bit weird. Why do we need this function? > + > +/** > + * uthread_grp_done() - test if all threads in a group are done > + * > + * @grp: 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) > +{ > + 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 > Apart from the minor typos and the function that I can't figure out why we need this look pretty clean Thnaks /Ilias
On 25.02.25 17:34, Jerome Forissier wrote: > Add an new internal API called uthread (Kconfig symbol: UTHREAD) which nits: %s/an/a/ > 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> > --- > include/uthread.h | 44 ++++++++++++ > lib/Kconfig | 21 ++++++ > lib/Makefile | 2 + > lib/uthread.c | 178 ++++++++++++++++++++++++++++++++++++++++++++++ > 4 files changed, 245 insertions(+) > create mode 100644 include/uthread.h > create mode 100644 lib/uthread.c > > diff --git a/include/uthread.h b/include/uthread.h > new file mode 100644 > index 00000000000..f1f86d210d5 > --- /dev/null > +++ b/include/uthread.h > @@ -0,0 +1,44 @@ > +/* SPDX-License-Identifier: GPL-2.0+ */ > +/* > + * Copyright 2025 Linaro Limited > + */ > + > +#include <linux/types.h> > + > +#ifndef _UTHREAD_H_ > +#define _UTHREAD_H_ > + > +#ifdef CONFIG_UTHREAD > + > +int uthread_create(void (*fn)(void *), void *arg, size_t stack_sz, > + unsigned int grp_id); Every function needs a Shinx style documentation. > +bool uthread_schedule(void); > +unsigned int uthread_grp_new_id(void); > +bool uthread_grp_done(unsigned int grp_id); > + > +#else > + > +static inline int uthread_create(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 1a683dea670..b32740ecbcc 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 stak 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 a7bc2f3134a..3610694de7a 100644 > --- a/lib/Makefile > +++ b/lib/Makefile > @@ -164,6 +164,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..430d1c0de32 > --- /dev/null > +++ b/lib/uthread.c > @@ -0,0 +1,178 @@ > +// 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 <asm/setjmp.h> > +#include <linux/kernel.h> > +#include <linux/list.h> > +#include <malloc.h> > +#include <stdint.h> > +#include <uthread.h> > + > +static struct uthread { > + void (*fn)(void *); > + void *arg; > + jmp_buf ctx; > + void *stack; > + bool done; > + unsigned int grp_id; > + struct list_head list; The structure and all its components need to be documented. > +} 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 inijmp() > + * 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); > +} > + > +/** > + * uthread_create() - Create a uthread object and make it ready for execution > + * > + * Threads are automatically deleted when then return from their entry point. > + * > + * @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) > + */ This documentation should be move to the include and the include needs to be added to doc/api/. > +int uthread_create(void (*fn)(void *), void *arg, size_t stack_sz, > + unsigned int grp_id) We should model the functions more like pthreads. Let the caller provide struct uthread(). This will allow us to implement pthread_cancel() and phthread_join(). Best regards Heinrich > +{ > + struct uthread *uthread; > + > + if (!stack_sz) > + stack_sz = CONFIG_UTHREAD_STACK_SIZE; > + > + uthread = calloc(1, sizeof(*uthread)); > + if (!uthread) > + return -1; > + > + uthread->stack = memalign(16, stack_sz); > + if (!uthread->stack) > + goto err; > + > + uthread->fn = fn; > + uthread->arg = arg; > + uthread->grp_id = grp_id; > + > + list_add_tail(&uthread->list, ¤t->list); > + > + initjmp(uthread->ctx, uthread_trampoline, uthread->stack + stack_sz); > + > + return 0; > +err: > + uthread_free(uthread); > + 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); > + } > +} > + > +/** > + * 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) > +{ > + struct uthread *next; > + struct uthread *tmp; > + > + if (list_empty(¤t->list)) > + return false; > + > + 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; > +} > + > +/** > + * 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) > +{ > + static unsigned int id = 0; > + > + return ++id; > +} > + > +/** > + * uthread_grp_done() - test if all threads in a group are done > + * > + * @grp: 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) > +{ > + struct uthread *next; > + > + list_for_each_entry(next, &main_thread.list, list) { > + if (next->grp_id == grp_id && !next->done) > + return false; > + } > + > + return true; > +}
On 2/28/25 14:09, Ilias Apalodimas wrote: > Hi Jerome, > > On Tue, 25 Feb 2025 at 18:35, Jerome Forissier > <jerome.forissier@linaro.org> wrote: >> >> Add an new > > 'a' Fixed in v3 > >> 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> >> --- >> include/uthread.h | 44 ++++++++++++ >> lib/Kconfig | 21 ++++++ >> lib/Makefile | 2 + >> lib/uthread.c | 178 ++++++++++++++++++++++++++++++++++++++++++++++ >> 4 files changed, 245 insertions(+) >> create mode 100644 include/uthread.h >> create mode 100644 lib/uthread.c >> >> diff --git a/include/uthread.h b/include/uthread.h >> new file mode 100644 >> index 00000000000..f1f86d210d5 >> --- /dev/null >> +++ b/include/uthread.h >> @@ -0,0 +1,44 @@ >> +/* SPDX-License-Identifier: GPL-2.0+ */ >> +/* >> + * Copyright 2025 Linaro Limited >> + */ >> + >> +#include <linux/types.h> >> + >> +#ifndef _UTHREAD_H_ >> +#define _UTHREAD_H_ >> + >> +#ifdef CONFIG_UTHREAD >> + >> +int uthread_create(void (*fn)(void *), void *arg, size_t stack_sz, >> + unsigned int grp_id); >> +bool uthread_schedule(void); >> +unsigned int uthread_grp_new_id(void); >> +bool uthread_grp_done(unsigned int grp_id); >> + >> +#else >> + >> +static inline int uthread_create(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 1a683dea670..b32740ecbcc 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 stak size for uthreads. Each uthread has its own stack. > > stack size Fixed in v3 > >> + 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 a7bc2f3134a..3610694de7a 100644 >> --- a/lib/Makefile >> +++ b/lib/Makefile >> @@ -164,6 +164,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..430d1c0de32 >> --- /dev/null >> +++ b/lib/uthread.c >> @@ -0,0 +1,178 @@ >> +// 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 <asm/setjmp.h> >> +#include <linux/kernel.h> >> +#include <linux/list.h> >> +#include <malloc.h> >> +#include <stdint.h> >> +#include <uthread.h> >> + >> +static struct uthread { >> + void (*fn)(void *); >> + void *arg; >> + jmp_buf ctx; >> + void *stack; >> + bool done; >> + unsigned int grp_id; >> + struct list_head list; >> +} 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 inijmp() > > initjump > >> + * 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); >> +} >> + >> +/** >> + * uthread_create() - Create a uthread object and make it ready for execution >> + * >> + * Threads are automatically deleted when then return from their entry point. > > when they Fixed in v3 > > [...] >> +/** >> + * 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) >> +{ >> + static unsigned int id = 0; >> + >> + return ++id; >> +} > > This seems a bit weird. Why do we need this function? As it should appear clearly in the subsequent patches, it is to help with dependencies. Suppose the main thread creates a thread to perform the "usb start" command. At this point we have two threads: let's call them main and usb_start. The latter enumerates the busses and creates one thread per bus to do the actual device scan. Let's assume we have two busses, then usb_start creates scan1 and scan2. The main thread needs to do: while (!done(usb_start)) uthread_schedule(); The usb_start on the other hand is only interested in is own two threads, scan1 and scan2. It has to wait until both are done, and only those two: while (!done(scan1) || !done(scan2)) uthread_schedule(); This can easily be written if threads can be assigned group IDs: /* Main */ usb_start_id = uthread_new_grp_id(); uthread_create(..., usb_start, NULL, usb_start_id); while (!uthread_grp_done(usb_start_id)) uthread_schedule(); /* usb_start() */ scan_id = uthread_new_grp_id(); uthread_create(..., usb_start, bus1, scan_id); uthread_create(..., usb_start, bus2, scan_id); while (!uthread_grp_done(scan_id)) uthread_schedule(); > >> + >> +/** >> + * uthread_grp_done() - test if all threads in a group are done >> + * >> + * @grp: 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) >> +{ >> + 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 >> > > > Apart from the minor typos and the function that I can't figure out > why we need this look pretty clean Thanks! Cheers,
On 2/28/25 14:21, Heinrich Schuchardt wrote: > On 25.02.25 17:34, Jerome Forissier wrote: >> Add an new internal API called uthread (Kconfig symbol: UTHREAD) which > > nits: > > %s/an/a/ Fixed in v3 > >> 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> >> --- >> include/uthread.h | 44 ++++++++++++ >> lib/Kconfig | 21 ++++++ >> lib/Makefile | 2 + >> lib/uthread.c | 178 ++++++++++++++++++++++++++++++++++++++++++++++ >> 4 files changed, 245 insertions(+) >> create mode 100644 include/uthread.h >> create mode 100644 lib/uthread.c >> >> diff --git a/include/uthread.h b/include/uthread.h >> new file mode 100644 >> index 00000000000..f1f86d210d5 >> --- /dev/null >> +++ b/include/uthread.h >> @@ -0,0 +1,44 @@ >> +/* SPDX-License-Identifier: GPL-2.0+ */ >> +/* >> + * Copyright 2025 Linaro Limited >> + */ >> + >> +#include <linux/types.h> >> + >> +#ifndef _UTHREAD_H_ >> +#define _UTHREAD_H_ >> + >> +#ifdef CONFIG_UTHREAD >> + >> +int uthread_create(void (*fn)(void *), void *arg, size_t stack_sz, >> + unsigned int grp_id); > > Every function needs a Shinx style documentation. Ack. > >> +bool uthread_schedule(void); >> +unsigned int uthread_grp_new_id(void); >> +bool uthread_grp_done(unsigned int grp_id); >> + >> +#else >> + >> +static inline int uthread_create(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 1a683dea670..b32740ecbcc 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 stak 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 a7bc2f3134a..3610694de7a 100644 >> --- a/lib/Makefile >> +++ b/lib/Makefile >> @@ -164,6 +164,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..430d1c0de32 >> --- /dev/null >> +++ b/lib/uthread.c >> @@ -0,0 +1,178 @@ >> +// 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 <asm/setjmp.h> >> +#include <linux/kernel.h> >> +#include <linux/list.h> >> +#include <malloc.h> >> +#include <stdint.h> >> +#include <uthread.h> >> + >> +static struct uthread { >> + void (*fn)(void *); >> + void *arg; >> + jmp_buf ctx; >> + void *stack; >> + bool done; >> + unsigned int grp_id; >> + struct list_head list; > > The structure and all its components need to be documented. Ack. > >> +} 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 inijmp() >> + * 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); >> +} >> + >> +/** >> + * uthread_create() - Create a uthread object and make it ready for execution >> + * >> + * Threads are automatically deleted when then return from their entry point. >> + * >> + * @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) >> + */ > > This documentation should be move to the include and the include needs > to be added to doc/api/. OK. > >> +int uthread_create(void (*fn)(void *), void *arg, size_t stack_sz, >> + unsigned int grp_id) > > We should model the functions more like pthreads. > > Let the caller provide struct uthread(). This will allow us to implement > pthread_cancel() and phthread_join(). I'd like to keep the ability to let the framework do as much as possible and the notion of grp_id already allows to wait and could be used to cancel too. That said, the uthread struct would be helpful to get the return status. I can do: int uthread_create(struct uthread *uthread, void (*fn)(void *), void *arg, size_t stack_sz, unsigned int grp_id); ...with uthread being optional (NULL allowed). Would that work for you? Regards,
diff --git a/include/uthread.h b/include/uthread.h new file mode 100644 index 00000000000..f1f86d210d5 --- /dev/null +++ b/include/uthread.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2025 Linaro Limited + */ + +#include <linux/types.h> + +#ifndef _UTHREAD_H_ +#define _UTHREAD_H_ + +#ifdef CONFIG_UTHREAD + +int uthread_create(void (*fn)(void *), void *arg, size_t stack_sz, + unsigned int grp_id); +bool uthread_schedule(void); +unsigned int uthread_grp_new_id(void); +bool uthread_grp_done(unsigned int grp_id); + +#else + +static inline int uthread_create(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 1a683dea670..b32740ecbcc 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 stak 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 a7bc2f3134a..3610694de7a 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -164,6 +164,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..430d1c0de32 --- /dev/null +++ b/lib/uthread.c @@ -0,0 +1,178 @@ +// 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 <asm/setjmp.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <malloc.h> +#include <stdint.h> +#include <uthread.h> + +static struct uthread { + void (*fn)(void *); + void *arg; + jmp_buf ctx; + void *stack; + bool done; + unsigned int grp_id; + struct list_head list; +} 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 inijmp() + * 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); +} + +/** + * uthread_create() - Create a uthread object and make it ready for execution + * + * Threads are automatically deleted when then return from their entry point. + * + * @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(void (*fn)(void *), void *arg, size_t stack_sz, + unsigned int grp_id) +{ + struct uthread *uthread; + + if (!stack_sz) + stack_sz = CONFIG_UTHREAD_STACK_SIZE; + + uthread = calloc(1, sizeof(*uthread)); + if (!uthread) + return -1; + + uthread->stack = memalign(16, stack_sz); + if (!uthread->stack) + goto err; + + uthread->fn = fn; + uthread->arg = arg; + uthread->grp_id = grp_id; + + list_add_tail(&uthread->list, ¤t->list); + + initjmp(uthread->ctx, uthread_trampoline, uthread->stack + stack_sz); + + return 0; +err: + uthread_free(uthread); + 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); + } +} + +/** + * 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) +{ + struct uthread *next; + struct uthread *tmp; + + if (list_empty(¤t->list)) + return false; + + 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; +} + +/** + * 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) +{ + static unsigned int id = 0; + + return ++id; +} + +/** + * uthread_grp_done() - test if all threads in a group are done + * + * @grp: 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) +{ + struct uthread *next; + + list_for_each_entry(next, &main_thread.list, list) { + if (next->grp_id == grp_id && !next->done) + return false; + } + + return true; +}
Add an 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> --- include/uthread.h | 44 ++++++++++++ lib/Kconfig | 21 ++++++ lib/Makefile | 2 + lib/uthread.c | 178 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 245 insertions(+) create mode 100644 include/uthread.h create mode 100644 lib/uthread.c