| // 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/errno.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; |
| } |
| /* 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; |
| } |
| |
| 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; |
| } |