diff options
-rw-r--r-- | Documentation/devel/background-execution.rst | 37 | ||||
-rw-r--r-- | common/Kconfig | 8 | ||||
-rw-r--r-- | common/Makefile | 1 | ||||
-rw-r--r-- | common/bthread.c | 215 | ||||
-rw-r--r-- | include/bthread.h | 53 | ||||
-rw-r--r-- | include/poller.h | 2 | ||||
-rw-r--r-- | include/sched.h | 3 | ||||
-rw-r--r-- | include/slice.h | 17 |
8 files changed, 324 insertions, 12 deletions
diff --git a/Documentation/devel/background-execution.rst b/Documentation/devel/background-execution.rst index eadad9d898..fa4d23e6d2 100644 --- a/Documentation/devel/background-execution.rst +++ b/Documentation/devel/background-execution.rst @@ -1,10 +1,10 @@ Background execution in barebox =============================== -barebox is single-threaded and doesn't support interrupts. Nevertheless it is -sometimes desired to execute code "in the background", like for example polling -for completion of transfers or to regularly blink a heartbeat LED. For these -scenarios barebox offers the techniques described below. +barebox does not use interrupts to avoid the associated increase in complexity. +Nevertheless it is sometimes desired to execute code "in the background", +like for example polling for completion of transfers or to regularly blink a +heartbeat LED. For these scenarios barebox offers the techniques described below. Pollers ------- @@ -71,6 +71,35 @@ actually queueing a work item on a work queue. This can be called from poller code. Usually a work item is allocated by the poller and then freed either in ``work_queue.fn()`` or in ``work_queue.cancel()``. +bthreads +-------- + +barebox threads are co-operative green threads, which are scheduled whenever +``is_timeout()`` is called. This has a few implications. First of all, +bthreads are not scheduled when ``is_timeout()`` is not called. +For this and other reasons, loops polling for hardware events should always +use a timeout, which is best implemented with ``is_timeout()``. +Another thing to remember is that bthreads can be scheduled anywhere +in the middle of other device accesses whenever ``is_timeout()`` is +called. Care must be taken that a green thread doesn't access the very same device +again itself. See "slices" below on how devices can safely be accessed from +bthreads. + +The bthread interface is declared in ``include/bthread.h``. +``bthread_create()`` is used to allocate a bthread control block along with +its stack. ``bthread_wake()`` can be used to add it into the run queue. +From this moment on and until the thread terminates, the thread will be +switched to regularly as long as someone calls ``is_timeout()``. +bthreads are allowed to call ``is_timeout()``, which will arrange for +other threads to execute. + +barebox threads are planned to replace previous infrastructure, pollers +and workqueues. Poller like behavior can be easily achieved by looping +and yielding on every iteration. There's ``bthread_should_stop()``, which +can be used as condition for continuing the loop. Workqueues could be +replaced along the same line, but with mutexes protecting underlying device +access. + Slices ------ diff --git a/common/Kconfig b/common/Kconfig index c0ff57bcdb..b1f4543e03 100644 --- a/common/Kconfig +++ b/common/Kconfig @@ -960,6 +960,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..5563559cf6 --- /dev/null +++ b/common/bthread.c @@ -0,0 +1,215 @@ +/* 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> + +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 CONFIG_ASAN + 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 CONFIG_ASAN + +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/include/bthread.h b/include/bthread.h new file mode 100644 index 0000000000..e3871fb115 --- /dev/null +++ b/include/bthread.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021 Ahmad Fatoum, Pengutronix + */ + +#ifndef __BTHREAD_H_ +#define __BTHREAD_H_ + +#include <linux/stddef.h> + +struct bthread; + +extern struct bthread *current; + +struct bthread *bthread_create(int (*threadfn)(void *), void *data, const char *namefmt, ...); +void bthread_free(struct bthread *bthread); + +void bthread_schedule(struct bthread *); +void bthread_wake(struct bthread *bthread); +void bthread_suspend(struct bthread *bthread); +int bthread_should_stop(void); +int bthread_stop(struct bthread *bthread); +void bthread_info(void); +const char *bthread_name(struct bthread *bthread); +bool bthread_is_main(struct bthread *bthread); + +/** + * bthread_run - create and wake a thread. + * @threadfn: the function to run for coming reschedule cycles + * @data: data ptr for @threadfn. + * @namefmt: printf-style name for the thread. + * + * Description: Convenient wrapper for bthread_create() followed by + * bthread_wakeup(). Returns the bthread or NULL + */ +#define bthread_run(threadfn, data, namefmt, ...) \ +({ \ + struct bthread *__b \ + = bthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \ + if (__b) \ + bthread_wake(__b); \ + __b; \ +}) + +#ifdef CONFIG_BTHREAD +void bthread_reschedule(void); +#else +static inline void bthread_reschedule(void) +{ +} +#endif + +#endif diff --git a/include/poller.h b/include/poller.h index db773265b2..371dafc6f8 100644 --- a/include/poller.h +++ b/include/poller.h @@ -39,6 +39,8 @@ static inline bool poller_async_active(struct poller_async *pa) return pa->active; } +extern int poller_active; + #ifdef CONFIG_POLLER void poller_call(void); #else diff --git a/include/sched.h b/include/sched.h index 43d239c3ef..57be1678fd 100644 --- a/include/sched.h +++ b/include/sched.h @@ -2,11 +2,14 @@ #ifndef __BAREBOX_SCHED_H_ #define __BAREBOX_SCHED_H_ +#include <bthread.h> #include <poller.h> static inline void resched(void) { poller_call(); + if (!IS_ENABLED(CONFIG_POLLER) || !poller_active) + bthread_reschedule(); } #endif diff --git a/include/slice.h b/include/slice.h index b2d65b80cd..cf684300a8 100644 --- a/include/slice.h +++ b/include/slice.h @@ -1,6 +1,8 @@ #ifndef __SLICE_H #define __SLICE_H +#include <bthread.h> + enum slice_action { SLICE_ACQUIRE = 1, SLICE_RELEASE = -1, @@ -35,12 +37,11 @@ void command_slice_release(void); extern int poller_active; -#ifdef CONFIG_POLLER -#define assert_command_context() ({ \ - WARN_ONCE(poller_active, "%s called in poller\n", __func__); \ -}) -#else -#define assert_command_context() do { } while (0) -#endif +#define assert_command_context() do { \ + WARN_ONCE(IS_ENABLED(CONFIG_POLLER) && poller_active, \ + "%s called in poller\n", __func__); \ + WARN_ONCE(IS_ENABLED(CONFIG_BTHREAD) && !bthread_is_main(current), \ + "%s called in secondary bthread\n", __func__); \ +} while (0) -#endif /* __SLICE_H */ +#endif |