1466 lines
45 KiB
C
1466 lines
45 KiB
C
/**
|
|
* \file
|
|
* \brief Threads implementation.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2007, 2008, 2009, 2010, 2011, 2012, ETH Zurich.
|
|
* All rights reserved.
|
|
*
|
|
* This file is distributed under the terms in the attached LICENSE file.
|
|
* If you do not find this file, copies can be found by writing to:
|
|
* ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <aos/aos.h>
|
|
#include <aos/dispatch.h>
|
|
#include <aos/dispatcher_arch.h>
|
|
#include <aos/debug.h>
|
|
#include <aos/slab.h>
|
|
#include <aos/caddr.h>
|
|
#include <aos/curdispatcher_arch.h>
|
|
#include <aos/paging.h>
|
|
#include <barrelfish_kpi/cpu_arch.h>
|
|
#include <barrelfish_kpi/domain_params.h>
|
|
#include <arch/registers.h>
|
|
|
|
#include "arch/threads.h"
|
|
#include "threads_priv.h"
|
|
#include "init.h"
|
|
|
|
#if defined(__x86_64__)
|
|
# include "arch/ldt.h"
|
|
#endif
|
|
|
|
// (M4): define this once your self-paging implementation works...
|
|
#define SELF_PAGING_WORKS
|
|
|
|
/// Maximum number of threads in a domain, used to size VM region for thread structures
|
|
// there is no point having MAX_THREADS > LDT_NENTRIES on x86 (see ldt.c)
|
|
#define MAX_THREADS 256
|
|
|
|
/// Static stack and storage for a bootstrap/cleanup thread
|
|
// XXX: 16-byte aligned for x86-64
|
|
static uintptr_t staticstack[THREADS_DEFAULT_STACK_BYTES / sizeof(uintptr_t)]
|
|
__attribute__((aligned(STACK_ALIGNMENT)));
|
|
|
|
static struct thread staticthread __attribute__((aligned(THREAD_ALIGNMENT))) = {
|
|
.stack = staticstack,
|
|
.stack_top = (char *)staticstack + sizeof(staticstack)
|
|
};
|
|
static struct thread_mutex staticthread_lock = THREAD_MUTEX_INITIALIZER;
|
|
|
|
/// Storage metadata for thread structures (and TLS data)
|
|
static struct slab_allocator thread_slabs;
|
|
|
|
// XXX: mutex and spinlock protecting thread slabs in spanned domains
|
|
/* This ought to be just a mutex. However, thread_create() is called on the
|
|
* inter-disp message handler thread, and if it blocks in a mutex, there is no
|
|
* way to wake it up and we will deadlock. This is a quick-fix workaround:
|
|
* The spinlock protects the data structure
|
|
* The mutex avoids unneccessary spinning (it is acquired first when safe)
|
|
*/
|
|
static spinlock_t thread_slabs_spinlock;
|
|
static struct thread_mutex thread_slabs_mutex = THREAD_MUTEX_INITIALIZER;
|
|
|
|
/// Base and size of the original ("pristine") thread-local storage init data
|
|
static void *tls_block_init_base;
|
|
static size_t tls_block_init_len;
|
|
static size_t tls_block_total_len;
|
|
|
|
/// Warning already issued about RSP usage. (Prevent repeated warnings
|
|
/// from the same domain -- e.g., when using THC whose stacks appear
|
|
/// invalid here).
|
|
__attribute__((unused)) static bool stack_warned=0;
|
|
|
|
/// Wrapper function for most threads, runs given function then deletes itself
|
|
static void thread_entry(thread_func_t start_func, void *start_data)
|
|
{
|
|
assert((lvaddr_t)start_func >= BASE_PAGE_SIZE);
|
|
int retval = start_func(start_data);
|
|
thread_exit(retval);
|
|
assert(!"thread_exit returned");
|
|
}
|
|
|
|
/// int counter for assigning initial thread ids
|
|
static uintptr_t threadid = 1;
|
|
|
|
#ifndef NDEBUG
|
|
/// Debugging assertions on thread queues
|
|
static void check_queue(struct thread *queue)
|
|
{
|
|
if (queue == NULL) {
|
|
return;
|
|
}
|
|
struct thread *q = queue;
|
|
int i = 0;
|
|
|
|
do {
|
|
assert_disabled(q != NULL);
|
|
|
|
// check for NULL next and prev pointers
|
|
assert_disabled((lvaddr_t)q->next > BASE_PAGE_SIZE);
|
|
assert_disabled((lvaddr_t)q->prev > BASE_PAGE_SIZE);
|
|
|
|
// check that next and prev pointers are sane
|
|
assert_disabled(q->next->prev == q);
|
|
assert_disabled(q->prev->next == q);
|
|
|
|
// advance to next elem
|
|
q = q->next;
|
|
i++;
|
|
assert_disabled(i < MAX_THREADS);
|
|
} while (q != queue);
|
|
}
|
|
#else /* NDEBUG version */
|
|
static inline void check_queue(struct thread *queue) {}
|
|
#endif
|
|
|
|
/**
|
|
* \brief Enqueue a thread in the given queue
|
|
*
|
|
* For safety, should only happen while disabled.
|
|
*/
|
|
void thread_enqueue(struct thread *thread, struct thread **queue)
|
|
{
|
|
assert_disabled(thread != NULL);
|
|
assert_disabled(queue != NULL);
|
|
check_queue(*queue);
|
|
if (*queue == NULL) {
|
|
*queue = thread->prev = thread->next = thread;
|
|
} else {
|
|
assert_disabled((*queue)->prev != NULL);
|
|
thread->prev = (*queue)->prev;
|
|
thread->next = *queue;
|
|
(*queue)->prev = thread;
|
|
assert_disabled(thread->prev != NULL);
|
|
thread->prev->next = thread;
|
|
}
|
|
|
|
check_queue(*queue);
|
|
}
|
|
|
|
/**
|
|
* \brief Dequeue the first thread on the given queue
|
|
*
|
|
* For safety, should only happen while disabled.
|
|
*
|
|
* \returns Pointer to thread that was dequeued
|
|
*/
|
|
struct thread *thread_dequeue(struct thread **queue)
|
|
{
|
|
assert_disabled(queue != NULL);
|
|
struct thread *thread = *queue;
|
|
assert_disabled(thread != NULL);
|
|
check_queue(thread);
|
|
if (thread->prev == thread) {
|
|
assert_disabled(thread->next == thread);
|
|
*queue = NULL;
|
|
} else {
|
|
thread->prev->next = thread->next;
|
|
thread->next->prev = thread->prev;
|
|
*queue = thread->next;
|
|
}
|
|
check_queue(*queue);
|
|
#ifndef NDEBUG
|
|
thread->prev = thread->next = NULL;
|
|
#endif
|
|
return thread;
|
|
}
|
|
|
|
/**
|
|
* \brief Remove a specific thread from a queue
|
|
*
|
|
* Does not check that the thread is in the given queue, which it must be.
|
|
* For safety, should only happen while disabled.
|
|
*/
|
|
void thread_remove_from_queue(struct thread **queue, struct thread *thread)
|
|
{
|
|
assert_disabled(queue != NULL);
|
|
assert_disabled(thread != NULL);
|
|
check_queue(*queue);
|
|
if (thread->prev == thread) {
|
|
assert_disabled(thread->next == thread);
|
|
assert_disabled(*queue == thread);
|
|
*queue = NULL;
|
|
} else {
|
|
thread->prev->next = thread->next;
|
|
thread->next->prev = thread->prev;
|
|
if (*queue == thread) {
|
|
*queue = thread->next;
|
|
}
|
|
}
|
|
check_queue(*queue);
|
|
#ifndef NDEBUG
|
|
thread->prev = thread->next = NULL;
|
|
#endif
|
|
}
|
|
|
|
|
|
/// Refill backing storage for thread region
|
|
#ifdef SELF_PAGING_WORKS
|
|
static errval_t refill_thread_slabs(struct slab_allocator *slabs)
|
|
{
|
|
// (M4):
|
|
// - implement me!
|
|
return slab_default_refill(slabs);
|
|
}
|
|
#endif
|
|
|
|
/// Initialise the state of a new thread structure
|
|
static void thread_init(dispatcher_handle_t disp, struct thread *newthread)
|
|
{
|
|
newthread->self = newthread;
|
|
#ifndef NDEBUG
|
|
newthread->next = newthread->prev = NULL;
|
|
#endif
|
|
newthread->tls_dtv = NULL;
|
|
newthread->disp = disp;
|
|
newthread->coreid = get_dispatcher_generic(disp)->core_id;
|
|
newthread->userptr = NULL;
|
|
memset(newthread->userptrs, 0, sizeof(newthread->userptrs));
|
|
newthread->yield_epoch = 0;
|
|
newthread->wakeup_reason = NULL;
|
|
newthread->return_value = 0;
|
|
thread_cond_init(&newthread->exit_condition);
|
|
thread_mutex_init(&newthread->exit_lock);
|
|
newthread->state = THREAD_STATE_RUNNABLE;
|
|
newthread->detached = false;
|
|
newthread->joining = false;
|
|
newthread->in_exception = false;
|
|
newthread->paused = false;
|
|
newthread->slab = NULL;
|
|
newthread->token = 0;
|
|
newthread->token_number = 1;
|
|
|
|
newthread->rpc_in_progress = false;
|
|
newthread->async_error = SYS_ERR_OK;
|
|
newthread->local_trigger = NULL;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns false if the stack pointer is out of bounds.
|
|
*/
|
|
static bool thread_check_stack_bounds(struct thread *thread,
|
|
arch_registers_state_t *archregs) {
|
|
lvaddr_t sp = (lvaddr_t) registers_get_sp(archregs);
|
|
return sp > (lvaddr_t)thread->stack ||
|
|
sp <= (lvaddr_t)thread->stack_top;
|
|
}
|
|
|
|
/**
|
|
* \brief Schedule and run the next active thread, or yield the dispatcher.
|
|
*
|
|
* This may only be called from the dispatcher (on its stack and while
|
|
* disabled!).
|
|
*
|
|
* \param disp Dispatcher pointer
|
|
*/
|
|
void thread_run_disabled(dispatcher_handle_t handle)
|
|
{
|
|
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
|
|
struct dispatcher_shared_generic *disp =
|
|
get_dispatcher_shared_generic(handle);
|
|
arch_registers_state_t *enabled_area =
|
|
dispatcher_get_enabled_save_area(handle);
|
|
|
|
if (disp_gen->current != NULL) {
|
|
assert_disabled(disp_gen->runq != NULL);
|
|
|
|
// check stack bounds
|
|
warn_disabled(&stack_warned,
|
|
thread_check_stack_bounds(disp_gen->current, enabled_area));
|
|
|
|
struct thread *next = disp_gen->current->next;
|
|
assert_disabled(next != NULL);
|
|
if (next != disp_gen->current) {
|
|
// save previous thread's state
|
|
arch_registers_state_t *cur_regs = &disp_gen->current->regs;
|
|
memcpy(cur_regs, enabled_area, sizeof(arch_registers_state_t));
|
|
disp_gen->current = next;
|
|
disp_resume(handle, &next->regs);
|
|
} else {
|
|
// same thread as before
|
|
disp_resume(handle, enabled_area);
|
|
}
|
|
} else if (disp_gen->runq != NULL) {
|
|
disp_gen->current = disp_gen->runq;
|
|
disp->haswork = true;
|
|
disp_resume(handle, &disp_gen->runq->regs);
|
|
} else {
|
|
// kernel gave us the CPU when we have nothing to do. block!
|
|
disp->haswork = havework_disabled(handle);
|
|
disp_gen->current = NULL;
|
|
disp_yield_disabled(handle);
|
|
}
|
|
}
|
|
|
|
/** Free all heap/slab-allocated state associated with a thread */
|
|
static void free_thread(struct thread *thread)
|
|
{
|
|
#if defined(__x86_64__) // XXX: gungy segment selector stuff
|
|
assert(thread->thread_seg_selector != 0);
|
|
uint16_t fs;
|
|
__asm("mov %%fs, %0" : "=r" (fs));
|
|
if (thread->thread_seg_selector == fs) {
|
|
assert(thread->disp == curdispatcher());
|
|
struct dispatcher_x86_64 *disp_priv = get_dispatcher_x86_64(thread->disp);
|
|
// we're freeing the current thread; make sure we reload a valid segment
|
|
// selector so that curdispatcher() keeps working!
|
|
__asm volatile("mov %%ax, %%fs"
|
|
: /* No outputs */
|
|
: "a" (disp_priv->disp_seg_selector));
|
|
}
|
|
ldt_free_segment(thread->thread_seg_selector);
|
|
#endif
|
|
|
|
//TODO: afeer: properly free stack again
|
|
// free(thread->stack);
|
|
if (thread->tls_dtv != NULL) {
|
|
free(thread->tls_dtv);
|
|
}
|
|
|
|
thread_mutex_lock(&thread_slabs_mutex);
|
|
acquire_spinlock(&thread_slabs_spinlock);
|
|
slab_free(&thread_slabs, thread->slab); // frees thread itself
|
|
release_spinlock(&thread_slabs_spinlock);
|
|
thread_mutex_unlock(&thread_slabs_mutex);
|
|
}
|
|
|
|
#define ALIGN_PTR(ptr, alignment) ((((uintptr_t)(ptr)) + (alignment) - 1) & ~((alignment) - 1))
|
|
|
|
/**
|
|
* \brief Creates a new thread that will not be runnable
|
|
*
|
|
* \param start_func Function to run on the new thread
|
|
* \param arg Argument to pass to function
|
|
* \param stacksize Size of stack, in bytes
|
|
*
|
|
* \returns Thread pointer on success, NULL on failure
|
|
*/
|
|
struct thread *thread_create_unrunnable(thread_func_t start_func, void *arg,
|
|
size_t stacksize)
|
|
{
|
|
// allocate stack
|
|
// debug_printf("[thread_create_unrunnable] stacksize = %x\n", stacksize);
|
|
assert((stacksize % sizeof(uintptr_t)) == 0);
|
|
stacksize += BASE_PAGE_SIZE;
|
|
size_t bytes_allocated;
|
|
void *stack = paging_malloc(stacksize, &bytes_allocated, BASE_PAGE_SIZE);
|
|
if (stack == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
// allocate space for TCB + initial TLS data
|
|
// no mutex as it may deadlock: see comment for thread_slabs_spinlock
|
|
// thread_mutex_lock(&thread_slabs_mutex);
|
|
acquire_spinlock(&thread_slabs_spinlock);
|
|
void *space = slab_alloc(&thread_slabs);
|
|
release_spinlock(&thread_slabs_spinlock);
|
|
// thread_mutex_unlock(&thread_slabs_mutex);
|
|
if (space == NULL) {
|
|
free(stack);
|
|
return NULL;
|
|
}
|
|
|
|
// split space into TLS data followed by TCB
|
|
// XXX: this layout is specific to the x86 ABIs! once other (saner)
|
|
// architectures support TLS, we'll need to break out the logic.
|
|
void *tls_data = space;
|
|
struct thread *newthread = (void *)ALIGN_PTR((uintptr_t)space + tls_block_total_len, THREAD_ALIGNMENT);
|
|
|
|
// init thread
|
|
thread_init(curdispatcher(), newthread);
|
|
newthread->slab = space;
|
|
|
|
if (tls_block_total_len > 0) {
|
|
// populate initial TLS data from pristine copy
|
|
assert(tls_block_init_len <= tls_block_total_len);
|
|
memcpy(tls_data, tls_block_init_base, tls_block_init_len);
|
|
|
|
// zero-fill remainder
|
|
memset((char *)tls_data + tls_block_init_len, 0,
|
|
tls_block_total_len - tls_block_init_len);
|
|
|
|
// create a TLS thread vector
|
|
struct tls_dtv *dtv = malloc(sizeof(struct tls_dtv) + 1 * sizeof(void *));
|
|
assert(dtv != NULL);
|
|
|
|
dtv->gen = 0;
|
|
dtv->dtv[0] = tls_data;
|
|
newthread->tls_dtv = dtv;
|
|
}
|
|
|
|
// FIXME: make arch-specific
|
|
#if defined(__x86_64__) || defined(__k1om__)
|
|
// create segment for TCB
|
|
errval_t err = ldt_alloc_segment(newthread, &newthread->thread_seg_selector);
|
|
if (err_is_fail(err)) {
|
|
DEBUG_ERR(err, "error allocating LDT segment for new thread");
|
|
free_thread(newthread);
|
|
free(stack);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
// init stack
|
|
newthread->stack = stack;
|
|
newthread->stack_top = (char *)stack + stacksize;
|
|
|
|
// waste space for alignment, if malloc gave us an unaligned stack
|
|
newthread->stack_top = (char *)newthread->stack_top
|
|
- (lvaddr_t)newthread->stack_top % STACK_ALIGNMENT;
|
|
|
|
// set thread's ID
|
|
newthread->id = threadid++;
|
|
|
|
paging_init_onthread(newthread);
|
|
|
|
// init registers
|
|
registers_set_initial(&newthread->regs, newthread, (lvaddr_t)thread_entry,
|
|
(lvaddr_t)newthread->stack_top,
|
|
(lvaddr_t)start_func, (lvaddr_t)arg, 0, 0);
|
|
|
|
return newthread;
|
|
}
|
|
|
|
/**
|
|
* \brief Creates a new thread, and makes it runnable
|
|
*
|
|
* \param start_func Function to run on the new thread
|
|
* \param arg Argument to pass to function
|
|
* \param stacksize Size of stack, in bytes
|
|
*
|
|
* \returns Thread pointer on success, NULL on failure
|
|
*/
|
|
struct thread *thread_create_varstack(thread_func_t start_func, void *arg,
|
|
size_t stacksize)
|
|
{
|
|
struct thread *newthread = thread_create_unrunnable(start_func, arg, stacksize);
|
|
if (newthread) {
|
|
// enqueue on runq
|
|
dispatcher_handle_t handle = disp_disable();
|
|
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
|
|
newthread->disp = handle;
|
|
thread_enqueue(newthread, &disp_gen->runq);
|
|
disp_enable(handle);
|
|
}
|
|
return newthread;
|
|
}
|
|
|
|
/**
|
|
* \brief Creates a new thread, and makes it runnable
|
|
*
|
|
* \param start_func Function to run on the new thread
|
|
* \param arg Argument to pass to function
|
|
*
|
|
* \returns Thread pointer on success, NULL on failure
|
|
*/
|
|
struct thread *thread_create(thread_func_t start_func, void *arg)
|
|
{
|
|
return thread_create_varstack(start_func, arg, THREADS_DEFAULT_STACK_BYTES);
|
|
}
|
|
|
|
/**
|
|
* \brief Wait for termination of another thread
|
|
*
|
|
* \param thread Pointer to thread to wait for
|
|
* \param retval Pointer to variable to hold return value of thread, or NULL
|
|
*
|
|
* \returns SYS_ERR_OK on success, error code on error.
|
|
*/
|
|
errval_t thread_join(struct thread *thread, int *retval)
|
|
{
|
|
assert(thread != NULL);
|
|
// this function should only be called for threads on same core
|
|
assert(thread->coreid == disp_get_core_id());
|
|
|
|
thread_mutex_lock(&thread->exit_lock);
|
|
if(thread->detached) {
|
|
// Thread is detached and thus not joinable
|
|
thread_mutex_unlock(&thread->exit_lock);
|
|
return LIB_ERR_THREAD_JOIN_DETACHED;
|
|
}
|
|
|
|
if(thread->joining) {
|
|
// Someone else already joins, that's an error
|
|
thread_mutex_unlock(&thread->exit_lock);
|
|
return LIB_ERR_THREAD_JOIN;
|
|
}
|
|
|
|
thread->joining = true;
|
|
if(thread->state != THREAD_STATE_EXITED) { // Possibly wait for thread exit
|
|
thread_cond_wait(&thread->exit_condition, &thread->exit_lock);
|
|
}
|
|
|
|
if(retval != NULL) {
|
|
*retval = thread->return_value;
|
|
}
|
|
|
|
thread_mutex_unlock(&thread->exit_lock); // Not really needed
|
|
free_thread(thread);
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief Detach a thread. Free its state when it terminates.
|
|
*
|
|
* \param thread Pointer to thread to detach
|
|
*
|
|
* \return SYS_ERR_OK on success.
|
|
*/
|
|
errval_t thread_detach(struct thread *thread)
|
|
{
|
|
assert(thread != NULL);
|
|
thread_mutex_lock(&thread->exit_lock);
|
|
|
|
if(thread->joining) {
|
|
// Someone else already joins, that's an error
|
|
thread_mutex_unlock(&thread->exit_lock);
|
|
return LIB_ERR_THREAD_JOIN;
|
|
}
|
|
|
|
if(!thread->detached) {
|
|
thread->detached = true;
|
|
} else {
|
|
// Detaching more than once is an error
|
|
thread_mutex_unlock(&thread->exit_lock);
|
|
return LIB_ERR_THREAD_DETACHED;
|
|
}
|
|
|
|
if(thread->state == THREAD_STATE_EXITED) {
|
|
// Thread already exited before we detached, clean it up
|
|
free_thread(thread);
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
thread_mutex_unlock(&thread->exit_lock);
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns the thread pointer to the currently-running thread
|
|
*/
|
|
struct thread *thread_self(void)
|
|
{
|
|
struct thread *me;
|
|
#if defined(__x86_64__) // XXX: AB's silly little arch-specific optimisation
|
|
__asm("movq %%fs:0, %0" : "=r" (me));
|
|
#else
|
|
// it's not necessary to disable, but might be once we do migration
|
|
bool was_enabled;
|
|
dispatcher_handle_t handle = disp_try_disable(&was_enabled);
|
|
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
|
|
me = disp_gen->current;
|
|
if (was_enabled)
|
|
disp_enable(handle);
|
|
#endif
|
|
return me;
|
|
}
|
|
|
|
struct thread *thread_self_disabled(void)
|
|
{
|
|
dispatcher_handle_t handle = curdispatcher();
|
|
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
|
|
return disp_gen->current;
|
|
}
|
|
|
|
uintptr_t thread_id(void)
|
|
{
|
|
return thread_self()->id;
|
|
}
|
|
|
|
uintptr_t thread_get_id(struct thread *t)
|
|
{
|
|
return t->id;
|
|
}
|
|
|
|
void thread_set_id(uintptr_t id)
|
|
{
|
|
struct thread *me = thread_self();
|
|
me->id = id;
|
|
}
|
|
|
|
uint32_t thread_set_token(struct waitset_chanstate *channel)
|
|
{
|
|
struct thread *me = thread_self();
|
|
// generate new token
|
|
uint32_t outgoing_token = (uint32_t)((me->id << 16) |
|
|
(me->coreid << 24) | ((me->token_number & 255) << 8)) | 1;
|
|
assert(me->token == 0);
|
|
me->token_number++;
|
|
if (!(me->token_number & 255))
|
|
me->token_number = 1;
|
|
me->token = outgoing_token & ~1; // wait for this token
|
|
me->channel = channel; // on that channel
|
|
return outgoing_token;
|
|
}
|
|
|
|
void thread_clear_token(struct waitset_chanstate *channel)
|
|
{
|
|
struct thread *me = thread_self();
|
|
|
|
me->token = 0; // don't wait anymore
|
|
me->channel = NULL;
|
|
}
|
|
|
|
uint32_t thread_current_token(void)
|
|
{
|
|
return thread_self()->token;
|
|
}
|
|
|
|
void thread_set_outgoing_token(uint32_t token)
|
|
{
|
|
struct thread *me = thread_self();
|
|
|
|
assert(!me->outgoing_token);
|
|
me->outgoing_token = token;
|
|
}
|
|
|
|
void thread_get_outgoing_token(uint32_t *token)
|
|
{
|
|
struct thread *me = thread_self();
|
|
// if thread's outgoing token is set, get it
|
|
if (me->outgoing_token) {
|
|
*token = me->outgoing_token;
|
|
me->outgoing_token = 0;
|
|
}
|
|
}
|
|
|
|
void thread_set_local_trigger(struct waitset_chanstate *trigger)
|
|
{
|
|
struct thread *me = thread_self();
|
|
me->local_trigger = trigger;
|
|
}
|
|
|
|
struct waitset_chanstate * thread_get_local_trigger(void)
|
|
{
|
|
struct thread *me = thread_self();
|
|
return me->local_trigger;
|
|
}
|
|
|
|
void thread_set_rpc_in_progress(bool v)
|
|
{
|
|
thread_self()->rpc_in_progress = v;
|
|
}
|
|
|
|
bool thread_get_rpc_in_progress(void)
|
|
{
|
|
return thread_self()->rpc_in_progress;
|
|
}
|
|
|
|
void thread_set_async_error(errval_t e)
|
|
{
|
|
thread_self()->async_error = e;
|
|
}
|
|
|
|
errval_t thread_get_async_error(void)
|
|
{
|
|
return thread_self()->async_error;
|
|
}
|
|
|
|
/**
|
|
* \brief Store receive slot provided by rpc
|
|
*/
|
|
|
|
void thread_store_recv_slot(struct capref recv_slot)
|
|
{
|
|
dispatcher_handle_t handle = disp_disable();
|
|
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
|
|
|
|
assert(disp_gen->recv_slot_count < MAX_RECV_SLOTS);
|
|
assert(disp_gen->recv_slot_count >= 0);
|
|
disp_gen->recv_slots[disp_gen->recv_slot_count++] = recv_slot;
|
|
|
|
disp_enable(handle);
|
|
}
|
|
|
|
struct capref thread_get_next_recv_slot(void)
|
|
{
|
|
dispatcher_handle_t handle = disp_disable();
|
|
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
|
|
struct capref retcap;
|
|
|
|
// HERE: recv_slot_count is > 0 if we have one+ caps stored
|
|
if (disp_gen->recv_slot_count <= 0) {
|
|
retcap = NULL_CAP;
|
|
} else {
|
|
retcap = disp_gen->recv_slots[--disp_gen->recv_slot_count];
|
|
}
|
|
disp_enable(handle);
|
|
return retcap;
|
|
}
|
|
|
|
void thread_set_status(int status) {
|
|
struct thread *me = thread_self();
|
|
me->return_value = status;
|
|
}
|
|
|
|
/**
|
|
* \brief Yield the calling thread
|
|
*
|
|
* Switches to the next runnable thread in this dispatcher, or if none is
|
|
* available, yields the dispatcher.
|
|
*/
|
|
void thread_yield(void)
|
|
{
|
|
dispatcher_handle_t handle = disp_disable();
|
|
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
|
|
struct dispatcher_shared_generic *disp =
|
|
get_dispatcher_shared_generic(handle);
|
|
arch_registers_state_t *enabled_area =
|
|
dispatcher_get_enabled_save_area(handle);
|
|
|
|
struct thread *me = disp_gen->current;
|
|
struct thread *next = me;
|
|
me->yield_epoch = disp_gen->timeslice;
|
|
|
|
do {
|
|
assert_disabled(next != NULL);
|
|
next = next->next;
|
|
if (next == me) {
|
|
break; // Everybody yielded this timeslice
|
|
}
|
|
} while(next->yield_epoch == disp_gen->timeslice);
|
|
|
|
poll_channels_disabled(handle);
|
|
|
|
if (next != me) {
|
|
disp_gen->current = next;
|
|
disp_switch(handle, &me->regs, &next->regs);
|
|
} else {
|
|
assert_disabled(disp_gen->runq != NULL);
|
|
assert_disabled(disp->haswork);
|
|
disp_save(handle, enabled_area, true, CPTR_NULL);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Yield both the calling thread, and the dispatcher to another domain
|
|
*
|
|
* \param endpoint Endpoint cap to which we wish to yield, or #CAP_NULL
|
|
* for an undirected yield
|
|
*
|
|
* Yields the dispatcher, optionally to another specified dispatcher.
|
|
*/
|
|
void thread_yield_dispatcher(struct capref endpoint)
|
|
{
|
|
dispatcher_handle_t handle = disp_disable();
|
|
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
|
|
struct dispatcher_shared_generic *disp =
|
|
get_dispatcher_shared_generic(handle);
|
|
arch_registers_state_t *enabled_area =
|
|
dispatcher_get_enabled_save_area(handle);
|
|
|
|
assert_disabled(disp_gen->runq != NULL);
|
|
assert_disabled(disp->haswork);
|
|
|
|
disp_save(handle, enabled_area, true, get_cap_addr(endpoint));
|
|
}
|
|
|
|
/// Function that runs on the static thread/stack to clean up a "real" (alloced) thread
|
|
static int cleanup_thread(void *arg)
|
|
{
|
|
struct thread *thread = arg;
|
|
|
|
// free old thread and its stack
|
|
if (thread != NULL) {
|
|
free_thread(thread);
|
|
}
|
|
|
|
// disable and release static thread
|
|
dispatcher_handle_t handle = disp_disable();
|
|
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
|
|
struct dispatcher_shared_generic *disp =
|
|
get_dispatcher_shared_generic(handle);
|
|
struct thread *me = disp_gen->current;
|
|
struct thread *ft =
|
|
thread_mutex_unlock_disabled(handle, &disp_gen->cleanupthread_lock);
|
|
assert(ft == NULL);
|
|
|
|
// run the next thread, if any
|
|
struct thread *next = me->next;
|
|
thread_remove_from_queue(&disp_gen->runq, me);
|
|
if (next != me) {
|
|
disp_gen->current = next;
|
|
disp_resume(handle, &next->regs);
|
|
} else {
|
|
disp_gen->current = NULL;
|
|
disp->haswork = havework_disabled(handle);
|
|
disp_yield_disabled(handle);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Terminate the calling thread
|
|
*/
|
|
void thread_exit(int status)
|
|
{
|
|
struct thread *me = thread_self();
|
|
|
|
thread_mutex_lock(&me->exit_lock);
|
|
|
|
// if this is the static thread, we don't need to do anything but cleanup
|
|
if (me == &staticthread) {
|
|
assert(me->detached);
|
|
// disable and release static thread
|
|
dispatcher_handle_t handle = disp_disable();
|
|
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
|
|
struct dispatcher_shared_generic *disp =
|
|
get_dispatcher_shared_generic(handle);
|
|
assert_disabled(me == &staticthread);
|
|
assert_disabled(me->stack == staticstack);
|
|
struct thread *ft =
|
|
thread_mutex_unlock_disabled(handle, &staticthread_lock);
|
|
assert(ft == NULL);
|
|
|
|
// run the next thread, if any
|
|
struct thread *next = me->next;
|
|
thread_remove_from_queue(&disp_gen->runq, me);
|
|
if (next != me) {
|
|
disp_gen->current = next;
|
|
disp_resume(handle, &next->regs);
|
|
} else {
|
|
disp_gen->current = NULL;
|
|
disp->haswork = havework_disabled(handle);
|
|
disp_yield_disabled(handle);
|
|
}
|
|
}
|
|
|
|
if (me->detached) {
|
|
// otherwise, we use a dispatcher-local thread to perform cleanup
|
|
struct dispatcher_generic *dg = get_dispatcher_generic(curdispatcher());
|
|
thread_mutex_lock(&dg->cleanupthread_lock);
|
|
if(dg->cleanupthread == NULL) {
|
|
dg->cleanupthread =
|
|
thread_create_unrunnable(cleanup_thread, me,
|
|
THREADS_DEFAULT_STACK_BYTES);
|
|
}
|
|
thread_init(curdispatcher(), dg->cleanupthread);
|
|
|
|
registers_set_initial(&dg->cleanupthread->regs, dg->cleanupthread,
|
|
(lvaddr_t)cleanup_thread,
|
|
(lvaddr_t)dg->cleanupthread->stack_top, (lvaddr_t)me,
|
|
0, 0, 0);
|
|
|
|
// Switch to it (on this dispatcher)
|
|
dispatcher_handle_t handle = disp_disable();
|
|
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
|
|
|
|
thread_remove_from_queue(&disp_gen->runq, me);
|
|
thread_enqueue(dg->cleanupthread, &disp_gen->runq);
|
|
disp_gen->cleanupthread->disp = handle;
|
|
disp_gen->current = dg->cleanupthread;
|
|
disp_resume(handle, &dg->cleanupthread->regs);
|
|
} else {
|
|
// We're not detached -- wakeup joiner
|
|
me->return_value = status;
|
|
me->state = THREAD_STATE_EXITED;
|
|
thread_cond_signal(&me->exit_condition);
|
|
|
|
// Disable and unlock exit lock
|
|
dispatcher_handle_t handle = disp_disable();
|
|
struct thread *wakeup =
|
|
thread_mutex_unlock_disabled(handle, &me->exit_lock);
|
|
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
|
|
struct dispatcher_shared_generic *disp =
|
|
get_dispatcher_shared_generic(handle);
|
|
|
|
assert_disabled(wakeup == NULL);
|
|
|
|
// run the next thread, if any
|
|
struct thread *next = me->next;
|
|
thread_remove_from_queue(&disp_gen->runq, me);
|
|
if (next != me) {
|
|
disp_gen->current = next;
|
|
disp_resume(handle, &next->regs);
|
|
} else {
|
|
disp_gen->current = NULL;
|
|
disp->haswork = havework_disabled(handle);
|
|
disp_yield_disabled(handle);
|
|
}
|
|
}
|
|
|
|
USER_PANIC("should never be reached");
|
|
}
|
|
|
|
/**
|
|
* \brief Block the caller, and optionally release a spinlock, while disabled
|
|
*
|
|
* The caller is unconditionally blocked, and placed into the given queue
|
|
* pending a call that will unblock it. After manipulating the queues, and
|
|
* before switching threds, the given spinlock, if specified, is unlocked.
|
|
* This function must only be called while disabled.
|
|
*
|
|
* This function is intended for use by multi-processor thread synchronisation
|
|
* functions.
|
|
*
|
|
* \param disp Dispatcher pointer
|
|
* \param queue (Optional) Queue of threads in which to place caller
|
|
* \param spinlock (Optional) pointer to spinlock
|
|
*
|
|
* \returns Argument passed to thread_unblock, when unblocked
|
|
*/
|
|
void *thread_block_and_release_spinlock_disabled(dispatcher_handle_t handle,
|
|
struct thread **queue,
|
|
spinlock_t *spinlock)
|
|
{
|
|
struct dispatcher_shared_generic *disp =
|
|
get_dispatcher_shared_generic(handle);
|
|
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
|
|
struct thread *me = disp_gen->current;
|
|
struct thread *next = me->next;
|
|
assert_disabled(next != NULL);
|
|
|
|
assert_disabled(me->state == THREAD_STATE_RUNNABLE);
|
|
me->state = THREAD_STATE_BLOCKED;
|
|
|
|
thread_remove_from_queue(&disp_gen->runq, me);
|
|
if (queue != NULL) {
|
|
thread_enqueue(me, queue);
|
|
}
|
|
|
|
if (spinlock != NULL) {
|
|
release_spinlock(spinlock);
|
|
}
|
|
|
|
if (next != me) {
|
|
assert_disabled(disp_gen->runq != NULL);
|
|
disp_gen->current = next;
|
|
disp_switch(handle, &me->regs, &next->regs);
|
|
} else {
|
|
assert_disabled(disp_gen->runq == NULL);
|
|
disp_gen->current = NULL;
|
|
disp->haswork = havework_disabled(handle);
|
|
disp_save(handle, &me->regs, true, CPTR_NULL);
|
|
}
|
|
|
|
assert(me->disp == handle); // didn't migrate while asleep
|
|
return me->wakeup_reason;
|
|
}
|
|
|
|
/**
|
|
* \brief Block the calling thread, while disabled
|
|
*
|
|
* The caller is unconditionally blocked, and placed into the given queue
|
|
* pending a call that will unblock it.
|
|
* This function must only be called while disabled.
|
|
*
|
|
* \param disp Dispatcher pointer
|
|
* \param queue Queue of threads in which to place caller
|
|
*
|
|
* \returns Argument passed to thread_unblock, when unblocked
|
|
*/
|
|
void *thread_block_disabled(dispatcher_handle_t disp, struct thread **queue)
|
|
{
|
|
return thread_block_and_release_spinlock_disabled(disp, queue, NULL);
|
|
}
|
|
|
|
/**
|
|
* \brief Block the calling thread, while enabled
|
|
*
|
|
* The caller is unconditionally blocked, and placed into the given queue
|
|
* pending a call that will unblock it.
|
|
* This function must only be called while enabled.
|
|
*
|
|
* \param queue Queue of threads in which to place caller
|
|
*
|
|
* \returns Argument passed to thread_unblock, when unblocked
|
|
*/
|
|
void *thread_block(struct thread **queue)
|
|
{
|
|
return thread_block_disabled(disp_disable(), queue);
|
|
}
|
|
|
|
/**
|
|
* \brief Unblock a single thread from a given queue, while disabled
|
|
*
|
|
* A single thread is removed from the queue of blocked threads, and awoken.
|
|
* This function must only be called while disabled.
|
|
*
|
|
* \param disp Dispatcher pointer
|
|
* \param queue Queue of threads from which to unblock one
|
|
* \param reason Value to be returned from thread_block()
|
|
*
|
|
* \returns Pointer to thread to be woken on a foreign dispatcher
|
|
*/
|
|
struct thread *thread_unblock_one_disabled(dispatcher_handle_t handle,
|
|
struct thread **queue,
|
|
void *reason)
|
|
{
|
|
assert_disabled(queue != NULL);
|
|
|
|
// Any threads in queue?
|
|
if (*queue == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
// Wakeup one waiting thread
|
|
struct thread *wakeup = thread_dequeue(queue);
|
|
wakeup->wakeup_reason = reason;
|
|
assert_disabled(wakeup->state == THREAD_STATE_BLOCKED);
|
|
wakeup->state = THREAD_STATE_RUNNABLE;
|
|
|
|
/* enqueue on run queue if it's "our" thread, and not paused */
|
|
if (wakeup->disp == handle) {
|
|
if (!wakeup->paused) {
|
|
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
|
|
thread_enqueue(wakeup, &disp_gen->runq);
|
|
}
|
|
return NULL;
|
|
} else {
|
|
return wakeup;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Unblock a single thread from a given queue, while enabled
|
|
*
|
|
* A single thread is removed from the queue of blocked threads, and awoken.
|
|
* This function must only be called while enabled.
|
|
*
|
|
* \param queue Queue of threads from which to unblock one
|
|
* \param reason Value to be returned from thread_block()
|
|
*
|
|
* \returns Pointer to thread to be woken on a foreign dispatcher
|
|
*/
|
|
struct thread *thread_unblock_one(struct thread **queue, void *reason)
|
|
{
|
|
struct thread *thread;
|
|
|
|
dispatcher_handle_t handle = disp_disable();
|
|
thread = thread_unblock_one_disabled(handle, queue, reason);
|
|
disp_enable(handle);
|
|
return thread;
|
|
}
|
|
|
|
/**
|
|
* \brief Unblock all threads on a given queue, while disabled
|
|
*
|
|
* All threads on the queue of blocked threads are awoken.
|
|
* This function must only be called while disabled.
|
|
*
|
|
* \param disp Dispatcher pointer
|
|
* \param queue Queue of threads to unblock
|
|
* \param reason Value to be returned from thread_block()
|
|
*
|
|
* \returns Pointer to list of threads to be woken on a foreign dispatcher
|
|
*/
|
|
struct thread *thread_unblock_all_disabled(dispatcher_handle_t handle,
|
|
struct thread **queue, void *reason)
|
|
{
|
|
assert_disabled(queue != NULL);
|
|
struct thread *wakeupq = NULL;
|
|
|
|
// Wakeup all waiting threads
|
|
while (*queue != NULL) {
|
|
struct thread *wakeup = thread_unblock_one_disabled(handle, queue, reason);
|
|
if (wakeup != NULL) {
|
|
wakeup->next = wakeupq;
|
|
wakeupq = wakeup;
|
|
}
|
|
}
|
|
|
|
return wakeupq;
|
|
}
|
|
|
|
extern int _main(int argc, const char *argv[]);
|
|
|
|
/// Thread created in new domain that runs main()
|
|
static int main_thread(void *params)
|
|
{
|
|
struct spawn_domain_params *p = params;
|
|
exit(_main(p->argc, p->argv));
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
static bool init_domain_global; // XXX
|
|
|
|
/// Thread created on static stack in new domain that runs init code
|
|
static int bootstrap_thread(struct spawn_domain_params *params)
|
|
//int bootstrap_thread(struct spawn_domain_params *params);
|
|
//int bootstrap_thread(struct spawn_domain_params *params)
|
|
{
|
|
errval_t err;
|
|
|
|
// Set libc function pointers
|
|
barrelfish_libc_glue_init();
|
|
|
|
if (params == NULL) {
|
|
printf("%s: error in creating a thread, NULL parameters given\n",
|
|
disp_name());
|
|
}
|
|
assert(params != NULL);
|
|
|
|
// Do we have TLS data?
|
|
tls_block_init_base = params->tls_init_base;
|
|
tls_block_init_len = params->tls_init_len;
|
|
tls_block_total_len = params->tls_total_len;
|
|
|
|
// Initialize subsystems
|
|
err = barrelfish_init_onthread(params);
|
|
if (err_is_fail(err)) {
|
|
DEBUG_ERR(err, "error during libbarrelfish init");
|
|
exit(EXIT_FAILURE);
|
|
assert(!"exit returned!");
|
|
}
|
|
|
|
// Until we have self-paging, we cannot use the paging-region based thread
|
|
// control block slab allocator, so just run main thread directly
|
|
#ifndef SELF_PAGING_WORKS
|
|
// we aren't prepared to run real threads yet
|
|
main_thread(params);
|
|
#else
|
|
// Allocate storage region for real threads
|
|
size_t blocksize = sizeof(struct thread) + tls_block_total_len + THREAD_ALIGNMENT;
|
|
slab_init(&thread_slabs, blocksize, refill_thread_slabs);
|
|
|
|
if (init_domain_global) {
|
|
// run main() on this thread, since we can't allocate
|
|
if (tls_block_total_len > 0) {
|
|
USER_PANIC("unsupported: use of TLS data in bootstrap domain\n");
|
|
}
|
|
DEBUG_PRINTF("running main on staticthread!\n");
|
|
main_thread(params);
|
|
} else {
|
|
// Start real thread to run main()
|
|
struct thread *thread = thread_create(main_thread, params);
|
|
assert(thread != NULL);
|
|
}
|
|
#endif
|
|
|
|
return 0; // ignored
|
|
}
|
|
|
|
/**
|
|
* \brief Initialise thread system while still disabled
|
|
*
|
|
* This function initialises the thread system while the dispatcher is still
|
|
* disabled, before enabling the dispatcher, running the general initialisation
|
|
* code, and calling main().
|
|
*
|
|
* \param disp Dispatcher pointer
|
|
* \param init_domain True if we are a bootstrap domain
|
|
*/
|
|
void thread_init_disabled(dispatcher_handle_t handle, bool init_domain)
|
|
{
|
|
struct dispatcher_shared_generic *disp =
|
|
get_dispatcher_shared_generic(handle);
|
|
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
|
|
arch_registers_state_t *enabled_area =
|
|
dispatcher_get_enabled_save_area(handle);
|
|
|
|
init_domain_global = init_domain;
|
|
|
|
// Create the first thread manually
|
|
struct thread *thread = &staticthread;
|
|
staticthread_lock.locked = true; // XXX: safe while disabled
|
|
|
|
// waste space for alignment, if unaligned
|
|
thread->stack_top = (char *)thread->stack_top
|
|
- (lvaddr_t)thread->stack_top % STACK_ALIGNMENT;
|
|
|
|
// Initialise the first (static) thread
|
|
thread_init(handle, thread);
|
|
thread->detached = true;
|
|
|
|
#if defined(__x86_64__)
|
|
// create segment for TCB
|
|
errval_t err = ldt_alloc_segment_disabled(handle, thread,
|
|
&thread->thread_seg_selector);
|
|
if (err_is_fail(err)) {
|
|
USER_PANIC_ERR(err, "error allocating LDT segment for first thread");
|
|
}
|
|
#endif
|
|
|
|
uintptr_t param;
|
|
registers_get_param(enabled_area, ¶m);
|
|
|
|
registers_set_initial(&thread->regs, thread, (lvaddr_t)thread_entry,
|
|
/* TODO: pass stack base and limit, choose in arch
|
|
* code (possibly setting up other hints on stack) */
|
|
(lvaddr_t)thread->stack_top,
|
|
(lvaddr_t)bootstrap_thread, param, 0, 0);
|
|
|
|
// Switch to it (always on this dispatcher)
|
|
thread->disp = handle;
|
|
thread_enqueue(thread, &disp_gen->runq);
|
|
disp_gen->current = thread;
|
|
disp->haswork = true;
|
|
disp_resume(handle, &thread->regs);
|
|
}
|
|
|
|
/**
|
|
* \brief Called on the remote core when spanning a domain across cores
|
|
*
|
|
* Runs the provided thread after enqueuing it and enabling the dispatcher
|
|
*/
|
|
void thread_init_remote(dispatcher_handle_t handle, struct thread *thread)
|
|
{
|
|
struct dispatcher_shared_generic *disp =
|
|
get_dispatcher_shared_generic(handle);
|
|
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
|
|
thread_enqueue(thread, &disp_gen->runq);
|
|
disp_gen->current = thread;
|
|
disp->haswork = true;
|
|
disp_resume(handle, &thread->regs);
|
|
}
|
|
|
|
|
|
/**
|
|
* \brief Pause (suspend execution of) the given thread, and optionally capture its register state
|
|
*
|
|
* The thread will not be run, until a subsequent call to thread_resume()
|
|
*/
|
|
void thread_pause_and_capture_state(struct thread *thread,
|
|
arch_registers_state_t **ret_regs)
|
|
{
|
|
assert(thread != NULL);
|
|
dispatcher_handle_t dh = disp_disable();
|
|
struct dispatcher_generic *disp = get_dispatcher_generic(dh);
|
|
if (thread->disp == dh) {
|
|
if (!thread->paused) {
|
|
thread->paused = true;
|
|
if (thread == disp->current) { // doesn't make much sense...
|
|
sys_print("Warning: pausing current thread!\n",100);
|
|
assert_disabled(thread->state == THREAD_STATE_RUNNABLE);
|
|
thread_block_disabled(dh, NULL);
|
|
} else if (thread->state == THREAD_STATE_RUNNABLE) {
|
|
thread_remove_from_queue(&disp->runq, thread);
|
|
}
|
|
}
|
|
if (ret_regs != NULL) {
|
|
*ret_regs = &thread->regs;
|
|
}
|
|
} else {
|
|
USER_PANIC("NYI: remote dispatcher thread_pause()");
|
|
}
|
|
disp_enable(dh);
|
|
}
|
|
|
|
/**
|
|
* \brief Pause (suspend execution of) the given thread
|
|
*
|
|
* The thread will not be run, until a subsequent call to thread_resume()
|
|
*/
|
|
void thread_pause(struct thread *thread)
|
|
{
|
|
thread_pause_and_capture_state(thread, NULL);
|
|
}
|
|
|
|
/**
|
|
* \brief Resume execution of a thread previously suspended by thread_pause()
|
|
*/
|
|
void thread_resume(struct thread *thread)
|
|
{
|
|
assert(thread != NULL);
|
|
dispatcher_handle_t dh = disp_disable();
|
|
struct dispatcher_generic *disp = get_dispatcher_generic(dh);
|
|
if (thread->disp == dh) {
|
|
if (thread->paused) {
|
|
thread->paused = false;
|
|
if (thread->state == THREAD_STATE_RUNNABLE) {
|
|
thread_enqueue(thread, &disp->runq);
|
|
}
|
|
}
|
|
} else {
|
|
USER_PANIC("NYI: remote dispatcher thread_resume()");
|
|
}
|
|
disp_enable(dh);
|
|
}
|
|
|
|
/**
|
|
* \brief Set old-style thread-local storage pointer.
|
|
* \param p User's pointer
|
|
*/
|
|
void thread_set_tls(void *p)
|
|
{
|
|
struct thread *me = thread_self();
|
|
me->userptr = p;
|
|
}
|
|
|
|
void thread_set_tls_key(int key, void *p)
|
|
{
|
|
struct thread *me = thread_self();
|
|
me->userptrs[key] = p;
|
|
}
|
|
|
|
/**
|
|
* \brief Return old-style thread-local storage pointer.
|
|
* \return User's pointer, previously passed to thread_set_tls()
|
|
*/
|
|
void *thread_get_tls(void)
|
|
{
|
|
struct thread *me = thread_self();
|
|
return me->userptr;
|
|
}
|
|
|
|
void *thread_get_tls_key(int key)
|
|
{
|
|
struct thread *me = thread_self();
|
|
return me->userptrs[key];
|
|
}
|
|
|
|
/**
|
|
* \brief Set the exception handler function for the current thread.
|
|
* Optionally also change its stack, and return the old values.
|
|
*
|
|
* \param newhandler New exception handler. Pass NULL to disable an existing handler.
|
|
* \param oldhandler If non-NULL, returns previous exception handler
|
|
* \param new_stack_base If non-NULL, sets a new exception handler stack (base)
|
|
* \param new_stack_top If non-NULL, sets a new exception handler stack (top)
|
|
* \param old_stack_base If non-NULL, returns previous stack base
|
|
* \param old_stack_top If non-NULL, returns previous stack top
|
|
*/
|
|
errval_t thread_set_exception_handler(exception_handler_fn newhandler,
|
|
exception_handler_fn *oldhandler,
|
|
void *new_stack_base, void *new_stack_top,
|
|
void **old_stack_base, void **old_stack_top)
|
|
{
|
|
struct thread *me = thread_self();
|
|
|
|
if (oldhandler != NULL) {
|
|
*oldhandler = me->exception_handler;
|
|
}
|
|
|
|
if (old_stack_base != NULL) {
|
|
*old_stack_base = me->exception_stack;
|
|
}
|
|
|
|
if (old_stack_top != NULL) {
|
|
*old_stack_top = me->exception_stack_top;
|
|
}
|
|
|
|
me->exception_handler = newhandler;
|
|
|
|
if (new_stack_base != NULL && new_stack_top != NULL) {
|
|
me->exception_stack = new_stack_base;
|
|
me->exception_stack_top = new_stack_top;
|
|
}
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
static void exception_handler_wrapper(arch_registers_state_t *cpuframe,
|
|
uintptr_t hack_arg, void *addr)
|
|
{
|
|
struct thread *me = thread_self();
|
|
|
|
assert(me->in_exception);
|
|
assert(me->exception_handler != NULL);
|
|
|
|
// XXX: unpack hack arg
|
|
enum exception_type type = hack_arg >> 16;
|
|
int subtype = hack_arg & 0xffff;
|
|
|
|
// run handler
|
|
me->exception_handler(type, subtype, addr, cpuframe);
|
|
|
|
// resume state
|
|
dispatcher_handle_t dh = disp_disable();
|
|
struct dispatcher_generic *disp_gen = get_dispatcher_generic(dh);
|
|
//memcpy(&me->regs, cpuframe, sizeof(arch_registers_state_t));
|
|
|
|
assert_disabled(me->in_exception);
|
|
me->in_exception = false;
|
|
|
|
assert_disabled(disp_gen->current == me);
|
|
disp_resume(dh, cpuframe);
|
|
}
|
|
|
|
#if 0
|
|
void thread_debug_regs(struct thread *t);
|
|
void thread_debug_regs(struct thread *t)
|
|
{
|
|
printf("%d: RIP = %lx, RSP = %lx\n", disp_get_domain_id(),
|
|
t->regs.rip, t->regs.rsp);
|
|
uint64_t *stack = (uint64_t *)t->regs.rsp;
|
|
printf("%d: ", disp_get_domain_id());
|
|
for(int i = 0; i < 30; i++) {
|
|
printf("%lx ", stack[i]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* \brief Deliver an exception to the current thread, and resume.
|
|
*
|
|
* This may only be called from the dispatcher (on its stack and while
|
|
* disabled!).
|
|
*
|
|
* \param handle Dispatcher handle
|
|
* \param type Exception type
|
|
* \param subtype Exception subtype
|
|
* \param addr Exception address
|
|
* \param regs CPU register state at time of exception
|
|
*/
|
|
void thread_deliver_exception_disabled(dispatcher_handle_t handle,
|
|
enum exception_type type, int subtype,
|
|
void *addr, arch_registers_state_t *regs)
|
|
{
|
|
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
|
|
struct thread *thread = disp_gen->current;
|
|
assert_disabled(thread != NULL);
|
|
assert_disabled(disp_gen->runq != NULL);
|
|
|
|
// can we deliver the exception?
|
|
if (thread->exception_handler == NULL || thread->exception_stack_top == NULL
|
|
|| thread->in_exception) {
|
|
if (thread->in_exception) {
|
|
sys_print("Can't deliver exception to thread: already in handler\n",
|
|
100);
|
|
} else {
|
|
sys_print("Can't deliver exception to thread: handler not set\n",
|
|
100);
|
|
}
|
|
|
|
// warn on stack overflow.
|
|
lvaddr_t sp = (lvaddr_t) registers_get_sp(regs);
|
|
if (sp < (lvaddr_t)thread->stack ||
|
|
sp > (lvaddr_t)thread->stack_top) {
|
|
char str[256];
|
|
snprintf(str, sizeof(str), "Error: stack bounds exceeded: sp = 0x%"
|
|
PRIxPTR " but [bottom, top] = [0x%" PRIxPTR ", 0x%"
|
|
PRIxPTR "]\n", (lvaddr_t) sp, (lvaddr_t) thread->stack,
|
|
(lvaddr_t) thread->stack_top);
|
|
sys_print(str, sizeof(str));
|
|
}
|
|
|
|
// TODO: actually delete the thread!
|
|
disp_gen->current = NULL;
|
|
thread_remove_from_queue(&disp_gen->runq, thread);
|
|
return;
|
|
}
|
|
|
|
thread->in_exception = true;
|
|
|
|
lvaddr_t stack_top = (lvaddr_t)thread->exception_stack_top;
|
|
|
|
// save thread's state at time of fault on top of exception stack
|
|
stack_top -= sizeof(arch_registers_state_t);
|
|
// Make sure we store the state at an aligned position
|
|
stack_top -= stack_top % STACK_ALIGNMENT;
|
|
arch_registers_state_t *cpuframe = (void *)stack_top;
|
|
memcpy(cpuframe, regs, sizeof(arch_registers_state_t));
|
|
|
|
// XXX: sanity-check to ensure we have a sensible amount of exception stack left
|
|
assert_disabled(stack_top > (lvaddr_t)thread->exception_stack + 8192);
|
|
|
|
// XXX: pack two small ints together to fit into a single register
|
|
uintptr_t hack_arg = (uintptr_t)type << 16 | (subtype & 0xffff);
|
|
|
|
registers_set_initial(&thread->regs, thread,
|
|
(lvaddr_t)exception_handler_wrapper,
|
|
stack_top, (lvaddr_t)cpuframe, hack_arg,
|
|
(lvaddr_t)addr, 0);
|
|
|
|
disp_resume(handle, &thread->regs);
|
|
}
|