summaryrefslogtreecommitdiffstats
path: root/firmware/target/hosted/thread-unix.c
diff options
context:
space:
mode:
authorThomas Martitz <kugel@rockbox.org>2011-02-18 22:46:01 +0000
committerThomas Martitz <kugel@rockbox.org>2011-02-18 22:46:01 +0000
commit6d85de341928aef8178465c60122f3cbe76f5dd6 (patch)
treeff86c384a574ac20d3418c1b904ed4d0de1f6980 /firmware/target/hosted/thread-unix.c
parent3926c30705cc7235122e2f2e35ab506b53238cdf (diff)
downloadrockbox-6d85de341928aef8178465c60122f3cbe76f5dd6.tar.gz
rockbox-6d85de341928aef8178465c60122f3cbe76f5dd6.tar.bz2
rockbox-6d85de341928aef8178465c60122f3cbe76f5dd6.zip
Implement cooperative threads on hosted platforms using C code.
This replaces SDL threads with real cooperative threads, which are less cpu intensive and allow priority scheduling. The backend for context switching is dependant on the host (sigaltstack/longjmp on Unix, Fibers on Windows). configure has options to force or disallow SDL threads. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@29327 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'firmware/target/hosted/thread-unix.c')
-rw-r--r--firmware/target/hosted/thread-unix.c294
1 files changed, 294 insertions, 0 deletions
diff --git a/firmware/target/hosted/thread-unix.c b/firmware/target/hosted/thread-unix.c
new file mode 100644
index 0000000000..a84ac70d56
--- /dev/null
+++ b/firmware/target/hosted/thread-unix.c
@@ -0,0 +1,294 @@
+#include <stdlib.h>
+#include <stdbool.h>
+#include <signal.h>
+#include <stdio.h>
+#include <setjmp.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <errno.h>
+#include "debug.h"
+
+static volatile bool sig_handler_called;
+static volatile jmp_buf tramp_buf;
+static volatile jmp_buf bootstrap_buf;
+static void (*thread_func)(void);
+static const int trampoline_sig = SIGUSR1;
+static pthread_t main_thread;
+
+static struct ctx {
+ jmp_buf thread_buf;
+} thread_bufs[MAXTHREADS];
+static struct ctx* thread_context, *target_context;
+static int curr_uc;
+
+static void trampoline(int sig);
+static void bootstrap_context(void) __attribute__((noinline));
+
+/* The *_context functions are heavily based on Gnu pth
+ * http://www.gnu.org/software/pth/
+ *
+ * adjusted to work in a multi-thread environment to
+ * offer a ucontext-like API
+ */
+
+/*
+ * VARIANT 2: THE SIGNAL STACK TRICK
+ *
+ * This uses sigstack/sigaltstack() and friends and is really the
+ * most tricky part of Pth. When you understand the following
+ * stuff you're a good Unix hacker and then you've already
+ * understood the gory ingredients of Pth. So, either welcome to
+ * the club of hackers, or do yourself a favor and skip this ;)
+ *
+ * The ingenious fact is that this variant runs really on _all_ POSIX
+ * compliant systems without special platform kludges. But be _VERY_
+ * carefully when you change something in the following code. The slightest
+ * change or reordering can lead to horribly broken code. Really every
+ * function call in the following case is intended to be how it is, doubt
+ * me...
+ *
+ * For more details we strongly recommend you to read the companion
+ * paper ``Portable Multithreading -- The Signal Stack Trick for
+ * User-Space Thread Creation'' from Ralf S. Engelschall. A copy of the
+ * draft of this paper you can find in the file rse-pmt.ps inside the
+ * GNU Pth distribution.
+ */
+
+static int make_context(struct ctx *ctx, void (*f)(void), char *sp, size_t stack_size)
+{
+ struct sigaction sa;
+ struct sigaction osa;
+ stack_t ss;
+ stack_t oss;
+ sigset_t osigs;
+ sigset_t sigs;
+
+ disable_irq();
+ /*
+ * Preserve the trampoline_sig signal state, block trampoline_sig,
+ * and establish our signal handler. The signal will
+ * later transfer control onto the signal stack.
+ */
+ sigemptyset(&sigs);
+ sigaddset(&sigs, trampoline_sig);
+ sigprocmask(SIG_BLOCK, &sigs, &osigs);
+ sa.sa_handler = trampoline;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_ONSTACK;
+ if (sigaction(trampoline_sig, &sa, &osa) != 0)
+ {
+ DEBUGF("%s(): %s\n", __func__, strerror(errno));
+ return false;
+ }
+ /*
+ * Set the new stack.
+ *
+ * For sigaltstack we're lucky [from sigaltstack(2) on
+ * FreeBSD 3.1]: ``Signal stacks are automatically adjusted
+ * for the direction of stack growth and alignment
+ * requirements''
+ *
+ * For sigstack we have to decide ourself [from sigstack(2)
+ * on Solaris 2.6]: ``The direction of stack growth is not
+ * indicated in the historical definition of struct sigstack.
+ * The only way to portably establish a stack pointer is for
+ * the application to determine stack growth direction.''
+ */
+ ss.ss_sp = sp;
+ ss.ss_size = stack_size;
+ ss.ss_flags = 0;
+ if (sigaltstack(&ss, &oss) < 0)
+ {
+ DEBUGF("%s(): %s\n", __func__, strerror(errno));
+ return false;
+ }
+
+ /*
+ * Now transfer control onto the signal stack and set it up.
+ * It will return immediately via "return" after the setjmp()
+ * was performed. Be careful here with race conditions. The
+ * signal can be delivered the first time sigsuspend() is
+ * called.
+ */
+ sig_handler_called = false;
+ main_thread = pthread_self();
+ sigfillset(&sigs);
+ sigdelset(&sigs, trampoline_sig);
+ pthread_kill(main_thread, trampoline_sig);
+ while(!sig_handler_called)
+ sigsuspend(&sigs);
+
+ /*
+ * Inform the system that we are back off the signal stack by
+ * removing the alternative signal stack. Be careful here: It
+ * first has to be disabled, before it can be removed.
+ */
+ sigaltstack(NULL, &ss);
+ ss.ss_flags = SS_DISABLE;
+ if (sigaltstack(&ss, NULL) < 0)
+ {
+ DEBUGF("%s(): %s\n", __func__, strerror(errno));
+ return false;
+ }
+ sigaltstack(NULL, &ss);
+ if (!(ss.ss_flags & SS_DISABLE))
+ {
+ DEBUGF("%s(): %s\n", __func__, strerror(errno));
+ return false;
+ }
+ if (!(oss.ss_flags & SS_DISABLE))
+ sigaltstack(&oss, NULL);
+
+ /*
+ * Restore the old trampoline_sig signal handler and mask
+ */
+ sigaction(trampoline_sig, &osa, NULL);
+ sigprocmask(SIG_SETMASK, &osigs, NULL);
+
+ /*
+ * Tell the trampoline and bootstrap function where to dump
+ * the new machine context, and what to do afterwards...
+ */
+ thread_func = f;
+ thread_context = ctx;
+
+ /*
+ * Now enter the trampoline again, but this time not as a signal
+ * handler. Instead we jump into it directly. The functionally
+ * redundant ping-pong pointer arithmentic is neccessary to avoid
+ * type-conversion warnings related to the `volatile' qualifier and
+ * the fact that `jmp_buf' usually is an array type.
+ */
+ if (setjmp(*((jmp_buf *)&bootstrap_buf)) == 0)
+ longjmp(*((jmp_buf *)&tramp_buf), 1);
+
+ /*
+ * Ok, we returned again, so now we're finished
+ */
+ enable_irq();
+ return true;
+}
+
+static void trampoline(int sig)
+{
+ (void)sig;
+ /* sanity check, no other thread should be here */
+ if (pthread_self() != main_thread)
+ return;
+
+ if (setjmp(*((jmp_buf *)&tramp_buf)) == 0)
+ {
+ sig_handler_called = true;
+ return;
+ }
+ /* longjump'd back in */
+ bootstrap_context();
+}
+
+void bootstrap_context(void)
+{
+ /* copy to local storage so we can spawn further threads
+ * in the meantime */
+ void (*thread_entry)(void) = thread_func;
+ struct ctx *t = thread_context;
+
+ /*
+ * Save current machine state (on new stack) and
+ * go back to caller until we're scheduled for real...
+ */
+ if (setjmp(t->thread_buf) == 0)
+ longjmp(*((jmp_buf *)&bootstrap_buf), 1);
+
+ /*
+ * The new thread is now running: GREAT!
+ * Now we just invoke its init function....
+ */
+ thread_entry();
+ DEBUGF("thread left\n");
+ thread_exit();
+}
+
+static inline void set_context(struct ctx *c)
+{
+ longjmp(c->thread_buf, 1);
+}
+
+static inline void swap_context(struct ctx *old, struct ctx *new)
+{
+ if (setjmp(old->thread_buf) == 0)
+ longjmp(new->thread_buf, 1);
+}
+
+static inline void get_context(struct ctx *c)
+{
+ setjmp(c->thread_buf);
+}
+
+
+static void setup_thread(struct regs *context);
+
+#define INIT_MAIN_THREAD
+static void init_main_thread(void *addr)
+{
+ /* get a context for the main thread so that we can jump to it from
+ * other threads */
+ struct regs *context = (struct regs*)addr;
+ context->uc = &thread_bufs[curr_uc++];
+ get_context(context->uc);
+}
+
+#define THREAD_STARTUP_INIT(core, thread, function) \
+ ({ (thread)->context.stack_size = (thread)->stack_size, \
+ (thread)->context.stack = (uintptr_t)(thread)->stack; \
+ (thread)->context.start = function; })
+
+
+
+/*
+ * Prepare context to make the thread runnable by calling swapcontext on it
+ */
+static void setup_thread(struct regs *context)
+{
+ void (*fn)(void) = context->start;
+ context->uc = &thread_bufs[curr_uc++];
+ while (!make_context(context->uc, fn, (char*)context->stack, context->stack_size))
+ DEBUGF("Thread creation failed. Retrying");
+}
+
+
+/*
+ * Save the ucontext_t pointer for later use in swapcontext()
+ *
+ * Cannot do getcontext() here, because jumping back to the context
+ * resumes after the getcontext call (i.e. store_context), but we need
+ * to resume from load_context()
+ */
+static inline void store_context(void* addr)
+{
+ struct regs *r = (struct regs*)addr;
+ target_context = r->uc;
+}
+
+/*
+ * Perform context switch
+ */
+static inline void load_context(const void* addr)
+{
+ struct regs *r = (struct regs*)addr;
+ if (UNLIKELY(r->start))
+ {
+ setup_thread(r);
+ r->start = NULL;
+ }
+ swap_context(target_context, r->uc);
+}
+
+/*
+ * play nice with the host and sleep while waiting for the tick */
+extern void wait_for_interrupt(void);
+static inline void core_sleep(void)
+{
+ enable_irq();
+ wait_for_interrupt();
+}
+