From f9384796179abcc7e5796815c79b2137f5f83b12 Mon Sep 17 00:00:00 2001 From: Jerome Forissier Date: Fri, 18 Apr 2025 16:09:34 +0200 Subject: uthread: add cooperative multi-tasking interface 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 Reviewed-by: Ilias Apalodimas --- lib/Kconfig | 21 +++++++++ lib/Makefile | 2 + lib/uthread.c | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 lib/uthread.c (limited to 'lib') diff --git a/lib/Kconfig b/lib/Kconfig index ac34ec45bb1..b2aecd8a49e 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -1258,6 +1258,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 7178c5a3e3c..18ae0cd87bf 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -159,6 +159,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..aa264b1d95f --- /dev/null +++ b/lib/uthread.c @@ -0,0 +1,138 @@ +// 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 +#include +#include +#include +#include +#include +#include + +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; + } + /* 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; + + 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; +} -- cgit v1.3.1 From b01735b448bfdd39bb0b0f8f28db149d8a088e55 Mon Sep 17 00:00:00 2001 From: Jerome Forissier Date: Fri, 18 Apr 2025 16:09:35 +0200 Subject: uthread: add uthread_mutex Add struct uthread_mutex and uthread_mutex_lock(), uthread_mutex_trylock(), uthread_mutex_unlock() to protect shared data structures from concurrent modifications. Signed-off-by: Jerome Forissier --- include/uthread.h | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/uthread.c | 27 +++++++++++++++++++++++++ 2 files changed, 87 insertions(+) (limited to 'lib') diff --git a/include/uthread.h b/include/uthread.h index f796a16f25f..89fa552a6f6 100644 --- a/include/uthread.h +++ b/include/uthread.h @@ -50,6 +50,23 @@ struct uthread { struct list_head list; }; +/** + * Internal state of a struct uthread_mutex + */ +enum uthread_mutex_state { + UTHREAD_MUTEX_UNLOCKED = 0, + UTHREAD_MUTEX_LOCKED = 1 +}; + +/** + * Uthread mutex + */ +struct uthread_mutex { + enum uthread_mutex_state state; +}; + +#define UTHREAD_MUTEX_INITIALIZER { .state = UTHREAD_MUTEX_UNLOCKED } + #ifdef CONFIG_UTHREAD /** @@ -94,6 +111,44 @@ unsigned int uthread_grp_new_id(void); */ bool uthread_grp_done(unsigned int grp_id); +/** + * uthread_mutex_lock() - lock a mutex + * + * If the cwmutexlock is available (i.e., not owned by any other thread), then + * it is locked for use by the current thread. Otherwise the current thread + * blocks: it enters a wait loop by scheduling other threads until the mutex + * becomes unlocked. + * + * @mutex: pointer to the mutex to lock + * Return: 0 on success, in which case the lock is owned by the calling thread. + * != 0 otherwise (the lock is not owned by the calling thread). + */ +int uthread_mutex_lock(struct uthread_mutex *mutex); + +/** + * uthread_mutex_trylock() - lock a mutex if not currently locked + * + * Similar to uthread_mutex_lock() except return immediately if the mutex is + * locked already. + * + * @mutex: pointer to the mutex to lock + * Return: 0 on success, in which case the lock is owned by the calling thread. + * EBUSY if the mutex is already locked by another thread. Any other non-zero + * value on error. + */ +int uthread_mutex_trylock(struct uthread_mutex *mutex); + +/** + * uthread_mutex_unlock() - unlock a mutex + * + * The mutex is assumed to be owned by the calling thread on entry. On exit, it + * is unlocked. + * + * @mutex: pointer to the mutex to unlock + * Return: 0 on success, != 0 on error + */ +int uthread_mutex_unlock(struct uthread_mutex *mutex); + #else static inline int uthread_create(struct uthread *uthr, void (*fn)(void *), @@ -119,5 +174,10 @@ static inline bool uthread_grp_done(unsigned int grp_id) return true; } +/* These are macros for convenience on the caller side */ +#define uthread_mutex_lock(_mutex) ({ 0; }) +#define uthread_mutex_trylock(_mutex) ({ 0 }) +#define uthread_mutex_unlock(_mutex) ({ 0; }) + #endif /* CONFIG_UTHREAD */ #endif /* _UTHREAD_H_ */ diff --git a/lib/uthread.c b/lib/uthread.c index aa264b1d95f..062fca7d209 100644 --- a/lib/uthread.c +++ b/lib/uthread.c @@ -8,6 +8,7 @@ */ #include +#include #include #include #include @@ -136,3 +137,29 @@ bool uthread_grp_done(unsigned int grp_id) return true; } + +int uthread_mutex_lock(struct uthread_mutex *mutex) +{ + while (mutex->state == UTHREAD_MUTEX_LOCKED) + uthread_schedule(); + + mutex->state = UTHREAD_MUTEX_LOCKED; + return 0; +} + +int uthread_mutex_trylock(struct uthread_mutex *mutex) +{ + if (mutex->state == UTHREAD_MUTEX_UNLOCKED) { + mutex->state = UTHREAD_MUTEX_LOCKED; + return 0; + } + + return -EBUSY; +} + +int uthread_mutex_unlock(struct uthread_mutex *mutex) +{ + mutex->state = UTHREAD_MUTEX_UNLOCKED; + + return 0; +} -- cgit v1.3.1 From 2325621fff219ee3b80641f714545f8b33013c0c Mon Sep 17 00:00:00 2001 From: Jerome Forissier Date: Fri, 18 Apr 2025 16:09:37 +0200 Subject: lib: time: hook uthread_schedule() into udelay() 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 Reviewed-by: Ilias Apalodimas --- lib/time.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'lib') 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 #include #include +#include #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); } -- cgit v1.3.1