mail archive of the barebox mailing list
 help / color / mirror / Atom feed
From: Ahmad Fatoum <a.fatoum@pengutronix.de>
To: barebox@lists.infradead.org
Cc: Ahmad Fatoum <a.fatoum@pengutronix.de>
Subject: [PATCH 01/12] common: add coroutine support
Date: Mon, 15 Feb 2021 11:36:54 +0100	[thread overview]
Message-ID: <20210215103704.32537-2-a.fatoum@pengutronix.de> (raw)
In-Reply-To: <20210215103704.32537-1-a.fatoum@pengutronix.de>

Coroutines generalize subroutines for non-preemptive multitasking,
by allowing execution to be suspended and resumed. We already have very
limited coroutines in the form of pollers. A poller is a function that
is cooperatively scheduled and yields after it has run to completion.
In the next poller_call(), this function is resumed from start.

Proper coroutines allow for the this yielding to happen at any point of
time. The coroutine's state is then saved and execution continues else
where. Later on, execution is resumed by restoring the saved context.

standard C setjmp/longjmp can be used to implement stackless coroutines.
setjmp stores the registers comprising the execution context into a
jmp_buf and longjmp switches to that context and continues execution just
after the setjmp that allocated that jmp_buf.

These coroutines are stackless, because jumping to a setjmp down the
call stack means that the code there will clobber the stack
below it. On resuming the coroutine, it will run with a stack changed
in the interim leading to undefined behavior.

There are ways around that without resorting to Assembly:

  - Allocate a buffer on the scheduler's stack, so coroutine can
    grow into them
     -> Problem: exploits Undefined behavior
  - Yield first time on scheduler stack, then patch jmp_buf to point at
    another stack
     -> Problem: Code switching stacks should not itself use the stack

It thus seems there is no way around adding a new function to initialize
a setjmp with a freshly cloned stack.

This commit adds the C boilerplate. Architectures wishing to use it need
to provide setjmp/longjmp/initjmp and in their arch Kconfig should
select CONFIG_HAS_ARCH_SJLJ. Code wishing to make use of it will need a
depends on CONFIG_HAS_ARCH_SJLJ. For now this will just be the new
poller_yield() facility introduced in a later commit.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 common/Kconfig      |   6 ++
 common/Makefile     |   1 +
 common/coroutine.c  | 178 ++++++++++++++++++++++++++++++++++++++++++++
 include/coroutine.h |  43 +++++++++++
 4 files changed, 228 insertions(+)
 create mode 100644 common/coroutine.c
 create mode 100644 include/coroutine.h

diff --git a/common/Kconfig b/common/Kconfig
index edadcc9f4979..d78aad1deb6b 100644
--- a/common/Kconfig
+++ b/common/Kconfig
@@ -20,6 +20,12 @@ config HAS_CACHE
 	  Drivers that depend on a cache 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 HAS_DMA
 	bool
 	help
diff --git a/common/Makefile b/common/Makefile
index 0e0ba384c9b5..e85a27713177 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_HAS_ARCH_SJLJ)	+= coroutine.o
 obj-$(CONFIG_RESET_SOURCE)	+= reset_source.o
 obj-$(CONFIG_SHELL_HUSH)	+= hush.o
 obj-$(CONFIG_SHELL_SIMPLE)	+= parser.o
diff --git a/common/coroutine.c b/common/coroutine.c
new file mode 100644
index 000000000000..d21cfc45067f
--- /dev/null
+++ b/common/coroutine.c
@@ -0,0 +1,178 @@
+/* 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 <coroutine.h>
+#include <asm/setjmp.h>
+#include <linux/overflow.h>
+
+/** struct coroutine
+ *
+ * @entry	coroutine entry point
+ * @arg		coroutine user-supplied argument
+ * @jmp_buf	coroutine context when yielded
+ * @stack	pointer to stack bottom (for stacks growing down)
+ * @stack_size	size of stack in bytes
+ * @stack_space actual stack for coroutines; empty for main thread
+ */
+struct coroutine {
+	__coroutine (*entry)(void *);
+	void *arg;
+	jmp_buf jmp_buf;
+	void *stack;
+	size_t stack_size;
+	u8 stack_space[] __aligned(16);
+};
+
+/** enum coroutine_action
+ *
+ * @COROUTINE_CHECKPOINT	placeholder, 0 reserved for regular setjmp return
+ * @COROUTINE_BOOTSTRAP		initial yield in trampline
+ * @COROUTINE_ENTER		switching from scheduler to coroutine
+ * @COROUTINE_YIELD		switching from coroutine to scheduler
+ * @COROUTINE_TERMINATE		final yield in trampoline
+ */
+enum coroutine_action {
+	COROUTINE_CHECKPOINT = 0,
+	COROUTINE_BOOTSTRAP,
+	COROUTINE_ENTER,
+	COROUTINE_YIELD,
+	COROUTINE_TERMINATE
+};
+
+/** The main "thread". Execution returns here when a coroutine yields */
+static struct coroutine scheduler;
+/** Argument for trampoline as initjmp doesn't pass an argument */
+static struct coroutine *new_coro;
+
+static enum coroutine_action coroutine_switch(struct coroutine *from,
+					      struct coroutine *to,
+					      enum coroutine_action action);
+
+static void __noreturn coroutine_trampoline(void)
+{
+	struct coroutine *coro = new_coro;
+
+	coroutine_switch(coro, &scheduler, COROUTINE_BOOTSTRAP);
+
+	coro->entry(coro->arg);
+
+	longjmp(scheduler.jmp_buf, COROUTINE_TERMINATE);
+}
+
+struct coroutine *coroutine_alloc(__coroutine (*entry)(void *), void *arg)
+{
+	struct coroutine *coro;
+	int ret;
+
+	coro = malloc(struct_size(coro, stack_space, CONFIG_STACK_SIZE));
+	if (!coro)
+		return NULL;
+
+	coro->stack = coro->stack_space;
+	coro->stack_size = CONFIG_STACK_SIZE;
+	coro->entry = entry;
+	coro->arg = arg;
+
+	/* set up coroutine context with the new stack */
+	ret = initjmp(coro->jmp_buf, coroutine_trampoline,
+		      coro->stack + CONFIG_STACK_SIZE);
+	if (ret) {
+		free(coro);
+		return NULL;
+	}
+
+	/* jump to new context to finish initialization */
+	new_coro = coro;
+	coroutine_schedule(coro);
+	new_coro = NULL;
+
+	return coro;
+}
+
+void coroutine_schedule(struct coroutine *coro)
+{
+	coroutine_switch(&scheduler, coro, COROUTINE_ENTER);
+}
+
+void coroutine_yield(struct coroutine *coro)
+{
+	coroutine_switch(coro, &scheduler, COROUTINE_YIELD);
+}
+
+void coroutine_free(struct coroutine *coro)
+{
+	free(coro);
+}
+
+/*
+ * When using ASAN, it needs to be told when we switch stacks.
+ */
+static void start_switch_fiber(void **fake_stack, struct coroutine *to);
+static void finish_switch_fiber(void *fake_stack_save);
+
+static enum coroutine_action coroutine_switch(struct coroutine *from, struct coroutine *to,
+					      enum coroutine_action action)
+{
+	void *fake_stack_save = NULL;
+	int ret;
+
+	if (action == COROUTINE_BOOTSTRAP)
+		finish_switch_fiber(NULL);
+
+	ret = setjmp(from->jmp_buf);
+	if (ret == 0) {
+		start_switch_fiber(action == COROUTINE_TERMINATE ? NULL : &fake_stack_save, to);
+		longjmp(to->jmp_buf, COROUTINE_YIELD);
+	}
+
+	finish_switch_fiber(fake_stack_save);
+
+	return ret;
+}
+
+#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(void *fake_stack_save)
+{
+    const void *bottom_old;
+    size_t size_old;
+
+    __sanitizer_finish_switch_fiber(fake_stack_save, &bottom_old, &size_old);
+
+    if (!scheduler.stack) {
+        scheduler.stack = (void *)bottom_old;
+        scheduler.stack_size = size_old;
+    }
+}
+
+static void start_switch_fiber(void **fake_stack_save,
+                               struct coroutine *to)
+{
+	__sanitizer_start_switch_fiber(fake_stack_save, to->stack, to->stack_size);
+}
+
+#else
+
+static void finish_switch_fiber(void *fake_stack_save)
+{
+}
+
+static void start_switch_fiber(void **fake_stack_save,
+                               struct coroutine *to)
+{
+}
+
+#endif
diff --git a/include/coroutine.h b/include/coroutine.h
new file mode 100644
index 000000000000..f6484e930166
--- /dev/null
+++ b/include/coroutine.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2021 Ahmad Fatoum, Pengutronix
+ */
+
+#ifndef __COROUTINE_H_
+#define __COROUTINE_H_
+
+#include <linux/stddef.h>
+
+struct coroutine;
+
+#define __coroutine void
+
+#ifdef CONFIG_HAS_ARCH_SJLJ
+
+struct coroutine *coroutine_alloc(__coroutine (*entry)(void *), void *arg);
+void coroutine_schedule(struct coroutine *coro);
+void coroutine_yield(struct coroutine *coro);
+void coroutine_free(struct coroutine *coro);
+
+#else
+
+static inline struct coroutine *coroutine_alloc(__coroutine (*entry)(void *), void *arg)
+{
+	return NULL;
+}
+
+static inline void coroutine_schedule(struct coroutine *coro)
+{
+}
+
+static inline void coroutine_yield(struct coroutine *coro)
+{
+}
+
+static inline void coroutine_free(struct coroutine *coro)
+{
+}
+
+#endif
+
+#endif
-- 
2.29.2


_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

  reply	other threads:[~2021-02-15 10:41 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-02-15 10:36 [PATCH 00/12] poller: run pollers as proper coroutines (green threads) Ahmad Fatoum
2021-02-15 10:36 ` Ahmad Fatoum [this message]
2021-02-15 10:36 ` [PATCH 02/12] poller: run pollers as proper coroutines if architecture supports it Ahmad Fatoum
2021-02-15 10:36 ` [PATCH 03/12] ARM: asm: setjmp: annotate setjmp/longjmp for GCC Ahmad Fatoum
2021-02-15 10:36 ` [PATCH 04/12] ARM: asm: setjmp: implement coroutine dependency initjmp() Ahmad Fatoum
2021-02-15 10:36 ` [PATCH 05/12] sandbox: asm: implement setjmp/longjmp/initjmp Ahmad Fatoum
2021-02-15 10:36 ` [PATCH 06/12] poller: command: add new coroutine check Ahmad Fatoum
2021-02-15 10:37 ` [PATCH 07/12] slice: have assert_command_context() yield until true if possible Ahmad Fatoum
2021-02-15 10:37 ` [PATCH 08/12] poller: implement basic Linux-like completion API Ahmad Fatoum
2021-02-15 10:37 ` [PATCH 09/12] include: add kthread wrappers for pollers Ahmad Fatoum
2021-02-15 12:31   ` Sascha Hauer
2021-02-15 10:37 ` [PATCH 10/12] usbgadget: ums: run gadget loop in a background coroutine if possible Ahmad Fatoum
2021-02-15 10:37 ` [PATCH 11/12] usbgadget: refactor usbgadget_register to accept array Ahmad Fatoum
2021-02-15 10:37 ` [PATCH 12/12] usbgadget: multi: wire mass storage gadget into composite gadget Ahmad Fatoum

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210215103704.32537-2-a.fatoum@pengutronix.de \
    --to=a.fatoum@pengutronix.de \
    --cc=barebox@lists.infradead.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox