// 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 #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; } 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; }