diff options
Diffstat (limited to 'common')
-rw-r--r-- | common/Kconfig | 13 | ||||
-rw-r--r-- | common/Makefile | 1 | ||||
-rw-r--r-- | common/bthread.c | 219 | ||||
-rw-r--r-- | common/clock.c | 4 | ||||
-rw-r--r-- | common/console.c | 6 |
5 files changed, 238 insertions, 5 deletions
diff --git a/common/Kconfig b/common/Kconfig index 2170f985be..56064d12c5 100644 --- a/common/Kconfig +++ b/common/Kconfig @@ -28,6 +28,11 @@ config HAS_DMA Drivers that depend on a DMA implementation can depend on this config, so that you don't get a compilation error. +config HAS_ARCH_SJLJ + bool + help + Architecture has support implemented for setjmp()/longjmp()/initjmp() + config GENERIC_GPIO bool @@ -958,6 +963,14 @@ config BAREBOXCRC32_TARGET config POLLER bool "generic polling infrastructure" +config BTHREAD + bool "barebox co-operative (green) thread infrastructure" + depends on HAS_ARCH_SJLJ + help + barebox threads are lightweight cooperative (green) threads that are + scheduled within delay loops and the console idle to asynchronously + execute actions, like checking for link up or feeding a watchdog. + config STATE bool "generic state infrastructure" select CRC32 diff --git a/common/Makefile b/common/Makefile index 0e0ba384c9..c0b45d263e 100644 --- a/common/Makefile +++ b/common/Makefile @@ -50,6 +50,7 @@ obj-$(CONFIG_OFTREE) += oftree.o obj-$(CONFIG_PARTITION_DISK) += partitions.o partitions/ obj-$(CONFIG_PASSWORD) += password.o obj-$(CONFIG_POLLER) += poller.o +obj-$(CONFIG_BTHREAD) += bthread.o obj-$(CONFIG_RESET_SOURCE) += reset_source.o obj-$(CONFIG_SHELL_HUSH) += hush.o obj-$(CONFIG_SHELL_SIMPLE) += parser.o diff --git a/common/bthread.c b/common/bthread.c new file mode 100644 index 0000000000..c811797130 --- /dev/null +++ b/common/bthread.c @@ -0,0 +1,219 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021 Ahmad Fatoum, Pengutronix + * + * ASAN bookkeeping based on Qemu coroutine-ucontext.c + */ + +/* To avoid future issues; fortify doesn't like longjmp up the call stack */ +#ifndef __NO_FORTIFY +#define __NO_FORTIFY +#endif + +#include <common.h> +#include <bthread.h> +#include <asm/setjmp.h> +#include <linux/overflow.h> + +#if defined CONFIG_ASAN && !defined CONFIG_32BIT +#define HAVE_FIBER_SANITIZER +#endif + +static struct bthread { + int (*threadfn)(void *); + union { + void *data; + int ret; + }; + char *name; + jmp_buf jmp_buf; + void *stack; + u32 stack_size; + struct list_head list; +#ifdef HAVE_FIBER_SANITIZER + void *fake_stack_save; +#endif + u8 awake :1; + u8 should_stop :1; + u8 has_stopped :1; +} main_thread = { + .list = LIST_HEAD_INIT(main_thread.list), + .name = "main", +}; + +struct bthread *current = &main_thread; + +/* + * When using ASAN, it needs to be told when we switch stacks. + */ +static void start_switch_fiber(struct bthread *, bool terminate_old); +static void finish_switch_fiber(struct bthread *); + +static void __noreturn bthread_trampoline(void) +{ + finish_switch_fiber(current); + bthread_reschedule(); + + current->ret = current->threadfn(current->data); + + bthread_suspend(current); + current->has_stopped = true; + + current = &main_thread; + start_switch_fiber(current, true); + longjmp(current->jmp_buf, 1); +} + +bool bthread_is_main(struct bthread *bthread) +{ + return bthread == &main_thread; +} + +void bthread_free(struct bthread *bthread) +{ + free(bthread->stack); + free(bthread->name); + free(bthread); +} + +const char *bthread_name(struct bthread *bthread) +{ + return bthread->name; +} + +struct bthread *bthread_create(int (*threadfn)(void *), void *data, + const char *namefmt, ...) +{ + struct bthread *bthread; + va_list ap; + int len; + + bthread = calloc(1, sizeof(*bthread)); + if (!bthread) + goto err; + + bthread->stack = memalign(16, CONFIG_STACK_SIZE); + if (!bthread->stack) + goto err; + + bthread->stack_size = CONFIG_STACK_SIZE; + bthread->threadfn = threadfn; + bthread->data = data; + + va_start(ap, namefmt); + len = vasprintf(&bthread->name, namefmt, ap); + va_end(ap); + + if (len < 0) + goto err; + + /* set up bthread context with the new stack */ + initjmp(bthread->jmp_buf, bthread_trampoline, + bthread->stack + CONFIG_STACK_SIZE); + + return bthread; +err: + bthread_free(bthread); + return NULL; +} + +void bthread_wake(struct bthread *bthread) +{ + if (bthread->awake) + return; + list_add_tail(&bthread->list, ¤t->list); + bthread->awake = true; +} + +void bthread_suspend(struct bthread *bthread) +{ + if (!bthread->awake) + return; + bthread->awake = false; + list_del(&bthread->list); +} + +int bthread_stop(struct bthread *bthread) +{ + bthread->should_stop = true; + + while (!bthread->has_stopped) + bthread_reschedule(); + + return bthread->ret; +} + +int bthread_should_stop(void) +{ + if (bthread_is_main(current)) + return -EINTR; + bthread_reschedule(); + return current->should_stop; +} + +void bthread_info(void) +{ + struct bthread *bthread; + + printf("Registered barebox threads:\n%s\n", current->name); + + list_for_each_entry(bthread, ¤t->list, list) + printf("%s\n", bthread->name); +} + +void bthread_reschedule(void) +{ + bthread_schedule(list_next_entry(current, list)); +} + +void bthread_schedule(struct bthread *to) +{ + struct bthread *from = current; + int ret; + + start_switch_fiber(to, false); + + ret = setjmp(from->jmp_buf); + if (ret == 0) { + current = to; + longjmp(to->jmp_buf, 1); + } + + finish_switch_fiber(from); +} + +#ifdef HAVE_FIBER_SANITIZER + +void __sanitizer_start_switch_fiber(void **fake_stack_save, const void *bottom, size_t size); +void __sanitizer_finish_switch_fiber(void *fake_stack_save, const void **bottom_old, size_t *size_old); + +static void finish_switch_fiber(struct bthread *bthread) +{ + const void *bottom_old; + size_t size_old; + + __sanitizer_finish_switch_fiber(bthread->fake_stack_save, &bottom_old, &size_old); + + if (!main_thread.stack) { + main_thread.stack = (void *)bottom_old; + main_thread.stack_size = size_old; + } +} + +static void start_switch_fiber(struct bthread *to, bool terminate_old) +{ + __sanitizer_start_switch_fiber(terminate_old ? &to->fake_stack_save : NULL, + to->stack, to->stack_size); +} + +#else + +static void finish_switch_fiber(struct bthread *bthread) +{ +} + +static void start_switch_fiber(struct bthread *to, bool terminate_old) +{ +} + +#endif diff --git a/common/clock.c b/common/clock.c index 7eeba88317..fa90d1a457 100644 --- a/common/clock.c +++ b/common/clock.c @@ -13,7 +13,7 @@ #include <init.h> #include <linux/math64.h> #include <clock.h> -#include <poller.h> +#include <sched.h> static uint64_t time_ns; @@ -172,7 +172,7 @@ int is_timeout(uint64_t start_ns, uint64_t time_offset_ns) int ret = is_timeout_non_interruptible(start_ns, time_offset_ns); if (time_offset_ns >= 100 * USECOND) - poller_call(); + resched(); return ret; } diff --git a/common/console.c b/common/console.c index 306149c99e..8a0af75a1f 100644 --- a/common/console.c +++ b/common/console.c @@ -17,7 +17,7 @@ #include <clock.h> #include <kfifo.h> #include <module.h> -#include <poller.h> +#include <sched.h> #include <ratp_bb.h> #include <magicvar.h> #include <globalvar.h> @@ -579,14 +579,14 @@ int ctrlc(void) { int ret = 0; + resched(); + if (!ctrlc_allowed) return 0; if (ctrlc_abort) return 1; - poller_call(); - #ifdef ARCH_HAS_CTRLC ret = arch_ctrlc(); #else |