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.foriss...@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; +}