blob: 44093b9d65ea225390356a82ffef5fbec4f3938f [file] [log] [blame] [edit]
#include <ros/arch/membar.h>
#include <arch/atomic.h>
#include <parlib.h>
#include <vcore.h>
#include <uthread.h>
#include <event.h>
#include <stdlib.h>
/* Which operations we'll call for the 2LS. Will change a bit with Lithe. For
* now, there are no defaults. 2LSs can override sched_ops. */
struct schedule_ops default_2ls_ops = {0};
struct schedule_ops *sched_ops __attribute__((weak)) = &default_2ls_ops;
__thread struct uthread *current_uthread = 0;
/* ev_q for all preempt messages (handled here to keep 2LSs from worrying
* extensively about the details. Will call out when necessary. */
struct event_queue *preempt_ev_q;
/* Helpers: */
#define UTH_TLSDESC_NOTLS (void*)(-1)
static inline bool __uthread_has_tls(struct uthread *uthread);
static int __uthread_allocate_tls(struct uthread *uthread);
static int __uthread_reinit_tls(struct uthread *uthread);
static void __uthread_free_tls(struct uthread *uthread);
static void __run_current_uthread_raw(void);
/* Block the calling uthread on sysc until it makes progress or is done */
static void __ros_mcp_syscall_blockon(struct syscall *sysc);
/* Helper, make the uthread code manage thread0. This sets up uthread such
* that the calling code and its TLS are tracked by the uthread struct, and
* vcore0 thinks the uthread is running there. Called only by slim_init (early
* _S code) and lib_init.
*
* Whether or not uthreads have TLS, thread0 has TLS, given to it by glibc.
* This TLS will get set whenever we use thread0, regardless of whether or not
* we use TLS for uthreads in general. glibc cares about this TLS and will use
* it at exit. We can't simply use that TLS for VC0 either, since we don't know
* where thread0 will be running when the program ends. */
static void uthread_manage_thread0(struct uthread *uthread)
{
assert(uthread);
/* Save a pointer to thread0's tls region (the glibc one) into its tcb */
uthread->tls_desc = get_tls_desc(0);
/* Save a pointer to the uthread in its own TLS */
current_uthread = uthread;
/* Thread is currently running (it is 'us') */
uthread->state = UT_RUNNING;
/* utf/as doesn't represent the state of the uthread (we are running) */
uthread->flags &= ~(UTHREAD_SAVED | UTHREAD_FPSAVED);
/* need to track thread0 for TLS deallocation */
uthread->flags |= UTHREAD_IS_THREAD0;
/* Change temporarily to vcore0s tls region so we can save the newly created
* tcb into its current_uthread variable and then restore it. One minor
* issue is that vcore0's transition-TLS isn't TLS_INITed yet. Until it is
* (right before vcore_entry(), don't try and take the address of any of
* its TLS vars. */
set_tls_desc(get_vcpd_tls_desc(0), 0);
/* We might have a basic uthread already installed (from slim_init), so
* free it before installing the new one. */
if (current_uthread)
free(current_uthread);
current_uthread = uthread;
set_tls_desc(uthread->tls_desc, 0);
__vcoreid = 0; /* setting the uthread's TLS var */
assert(!in_vcore_context());
}
/* The real 2LS calls this, passing in a uthread representing thread0. When it
* returns, you're in _M mode, still running thread0, on vcore0 */
void uthread_lib_init(struct uthread *uthread)
{
init_once_racy(return);
vcore_init();
uthread_manage_thread0(uthread);
/* Receive preemption events. Note that this merely tells the kernel how to
* send the messages, and does not necessarily provide storage space for the
* messages. What we're doing is saying that all PREEMPT and CHECK_MSGS
* events should be spammed to vcores that are running, preferring whatever
* the kernel thinks is appropriate. And IPI them.
*
* It is critical that these are either SPAM_PUB or INDIR|FALLBACK, so that
* yielding vcores do not miss the preemption messages. */
ev_handlers[EV_VCORE_PREEMPT] = handle_vc_preempt;
ev_handlers[EV_CHECK_MSGS] = handle_vc_indir;
preempt_ev_q = get_event_q(); /* small ev_q, mostly a vehicle for flags */
preempt_ev_q->ev_flags = EVENT_IPI | EVENT_SPAM_PUBLIC | EVENT_VCORE_APPRO |
EVENT_VCORE_MUST_RUN;
/* Tell the kernel to use the ev_q (it's settings) for the two types. Note
* that we still have two separate handlers. We just want the events
* delivered in the same way. If we ever want to have a big_event_q with
* INDIRs, we could consider using separate ones. */
register_kevent_q(preempt_ev_q, EV_VCORE_PREEMPT);
register_kevent_q(preempt_ev_q, EV_CHECK_MSGS);
printd("[user] registered %08p (flags %08p) for preempt messages\n",
preempt_ev_q, preempt_ev_q->ev_flags);
/* Get ourselves into _M mode. Could consider doing this elsewhere... */
vcore_change_to_m();
}
/* Helper: tells the kernel our SCP is capable of going into vcore context on
* vcore 0. Pairs with k/s/process.c scp_is_vcctx_ready(). */
static void scp_vcctx_ready(void)
{
struct preempt_data *vcpd = vcpd_of(0);
long old_flags;
/* the CAS is a bit overkill; keeping it around in case people use this
* code in other situations. */
do {
old_flags = atomic_read(&vcpd->flags);
/* Spin if the kernel is mucking with the flags */
while (old_flags & VC_K_LOCK)
old_flags = atomic_read(&vcpd->flags);
} while (!atomic_cas(&vcpd->flags, old_flags,
old_flags & ~VC_SCP_NOVCCTX));
}
/* For both of these, VC ctx uses the usual TLS errno/errstr. Uthreads use
* their own storage. Since we're called after manage_thread0, we should always
* have current_uthread if we are not in vc ctx. */
static int *__ros_errno_loc(void)
{
if (in_vcore_context())
return __errno_location_tls();
else
return &current_uthread->err_no;
}
static char *__ros_errstr_loc(void)
{
if (in_vcore_context())
return __errstr_location_tls();
else
return current_uthread->err_str;
}
/* Slim-init - sets up basic uthreading for when we are in _S mode and before
* we set up the 2LS. Some apps may not have a 2LS and thus never do the full
* vcore/2LS/uthread init. */
void uthread_slim_init(void)
{
struct uthread *uthread;
int ret = posix_memalign((void**)&uthread, __alignof__(struct uthread),
sizeof(struct uthread));
assert(!ret);
memset(uthread, 0, sizeof(struct uthread)); /* aggressively 0 for bugs */
/* TODO: consider a vcore_init_vc0 call. */
vcore_init();
uthread_manage_thread0(uthread);
scp_vcctx_ready();
init_posix_signals();
/* change our blockon from glibc's internal one to the mcp one (which can
* handle SCPs too). we must do this before switching to _M, or at least
* before blocking while an _M. it's harmless (and probably saner) to do it
* earlier, so we do it as early as possible. */
ros_syscall_blockon = __ros_mcp_syscall_blockon;
/* Switch our errno/errstr functions to be uthread-aware. See glibc's
* errno.c for more info. */
ros_errno_loc = __ros_errno_loc;
ros_errstr_loc = __ros_errstr_loc;
}
/* 2LSs shouldn't call uthread_vcore_entry directly */
void __attribute__((noreturn)) uthread_vcore_entry(void)
{
uint32_t vcoreid = vcore_id();
struct preempt_data *vcpd = vcpd_of(vcoreid);
/* Should always have notifications disabled when coming in here. */
assert(!notif_is_enabled(vcoreid));
assert(in_vcore_context());
/* If someone is stealing our uthread (from when we were preempted before),
* we can't touch our uthread. But we might be the last vcore around, so
* we'll handle preemption events (spammed to our public mbox).
*
* It's important that we only check/handle one message per loop, otherwise
* we could get stuck in a ping-pong scenario with a recoverer (maybe). */
while (atomic_read(&vcpd->flags) & VC_UTHREAD_STEALING) {
/* Note we're handling INDIRs and other public messages while someone
* is stealing our uthread. Remember that those event handlers cannot
* touch cur_uth, as it is "vcore business". */
handle_one_mbox_msg(&vcpd->ev_mbox_public);
cpu_relax();
}
/* If we have a current uthread that is DONT_MIGRATE, pop it real quick and
* let it disable notifs (like it wants to). Other than dealing with
* preemption events (or other INDIRs), we shouldn't do anything in vc_ctx
* when we have a DONT_MIGRATE uthread. */
if (current_uthread && (current_uthread->flags & UTHREAD_DONT_MIGRATE))
__run_current_uthread_raw();
/* Check and see if we wanted ourselves to handle a remote VCPD mbox. Want
* to do this after we've handled STEALING and DONT_MIGRATE. */
try_handle_remote_mbox();
/* Otherwise, go about our usual vcore business (messages, etc). */
handle_events(vcoreid);
__check_preempt_pending(vcoreid);
assert(in_vcore_context()); /* double check, in case an event changed it */
/* Consider using the default_2ls_op for this, though it's a bit weird. */
if (sched_ops->sched_entry) {
sched_ops->sched_entry();
} else if (current_uthread) {
run_current_uthread();
}
/* 2LS sched_entry should never return */
/* Either the 2LS sched_entry returned, run_cur_uth() returned, or we
* didn't have a current_uthread. If we didn't have a 2LS op, we should be
* in _S mode and always have a current_uthread. */
assert(0);
}
/* Does the uthread initialization of a uthread that the caller created. Call
* this whenever you are "starting over" with a thread. */
void uthread_init(struct uthread *new_thread, struct uth_thread_attr *attr)
{
int ret;
assert(new_thread);
new_thread->state = UT_NOT_RUNNING;
/* They should have zero'd the uthread. Let's check critical things: */
assert(!new_thread->flags && !new_thread->sysc);
/* the utf holds the GP context of the uthread (set by the 2LS earlier).
* There is no FP context to be restored yet. We only save the FPU when we
* were interrupted off a core. */
new_thread->flags |= UTHREAD_SAVED;
if (attr && attr->want_tls) {
/* Get a TLS. If we already have one, reallocate/refresh it */
if (new_thread->tls_desc)
ret = __uthread_reinit_tls(new_thread);
else
ret = __uthread_allocate_tls(new_thread);
assert(!ret);
uthread_set_tls_var(new_thread, current_uthread, new_thread);
} else {
new_thread->tls_desc = UTH_TLSDESC_NOTLS;
}
}
/* This is a wrapper for the sched_ops thread_runnable, for use by functions
* outside the main 2LS. Do not put anything important in this, since the 2LSs
* internally call their sched op. This is to improve batch wakeups (barriers,
* etc) */
void uthread_runnable(struct uthread *uthread)
{
assert(sched_ops->thread_runnable);
sched_ops->thread_runnable(uthread);
}
/* Informs the 2LS that its thread blocked, and it is not under the control of
* the 2LS. This is for informational purposes, and some semantic meaning
* should be passed by flags (from uthread.h's UTH_EXT_BLK_xxx options).
* Eventually, whoever calls this will call uthread_runnable(), giving the
* thread back to the 2LS.
*
* If code outside the 2LS has blocked a thread (via uthread_yield) and ran its
* own callback/yield_func instead of some 2LS code, that callback needs to
* call this.
*
* AKA: obviously_a_uthread_has_blocked_in_lincoln_park() */
void uthread_has_blocked(struct uthread *uthread, int flags)
{
if (sched_ops->thread_has_blocked)
sched_ops->thread_has_blocked(uthread, flags);
}
/* Need to have this as a separate, non-inlined function since we clobber the
* stack pointer before calling it, and don't want the compiler to play games
* with my hart. */
static void __attribute__((noinline, noreturn))
__uthread_yield(void)
{
struct uthread *uthread = current_uthread;
assert(in_vcore_context());
assert(!notif_is_enabled(vcore_id()));
/* Note: we no longer care if the thread is exiting, the 2LS will call
* uthread_destroy() */
uthread->flags &= ~UTHREAD_DONT_MIGRATE;
uthread->state = UT_NOT_RUNNING;
/* Do whatever the yielder wanted us to do */
assert(uthread->yield_func);
uthread->yield_func(uthread, uthread->yield_arg);
/* Make sure you do not touch uthread after that func call */
/* Leave the current vcore completely */
/* TODO: if the yield func can return a failure, we can abort the yield */
current_uthread = NULL;
/* Go back to the entry point, where we can handle notifications or
* reschedule someone. */
uthread_vcore_entry();
}
/* Calling thread yields for some reason. Set 'save_state' if you want to ever
* run the thread again. Once in vcore context in __uthread_yield, yield_func
* will get called with the uthread and yield_arg passed to it. This way, you
* can do whatever you want when you get into vcore context, which can be
* thread_blockon_sysc, unlocking mutexes, joining, whatever.
*
* If you do *not* pass a 2LS sched op or other 2LS function as yield_func,
* then you must also call uthread_has_blocked(flags), which will let the 2LS
* know a thread blocked beyond its control (and why). */
void uthread_yield(bool save_state, void (*yield_func)(struct uthread*, void*),
void *yield_arg)
{
struct uthread *uthread = current_uthread;
volatile bool yielding = TRUE; /* signal to short circuit when restarting */
assert(!in_vcore_context());
assert(uthread->state == UT_RUNNING);
/* Pass info to ourselves across the uth_yield -> __uth_yield transition. */
uthread->yield_func = yield_func;
uthread->yield_arg = yield_arg;
/* Don't migrate this thread to another vcore, since it depends on being on
* the same vcore throughout (once it disables notifs). The race is that we
* read vcoreid, then get interrupted / migrated before disabling notifs. */
uthread->flags |= UTHREAD_DONT_MIGRATE;
cmb(); /* don't let DONT_MIGRATE write pass the vcoreid read */
uint32_t vcoreid = vcore_id();
printd("[U] Uthread %08p is yielding on vcore %d\n", uthread, vcoreid);
struct preempt_data *vcpd = vcpd_of(vcoreid);
/* once we do this, we might miss a notif_pending, so we need to enter vcore
* entry later. Need to disable notifs so we don't get in weird loops with
* save_user_ctx() and pop_user_ctx(). */
disable_notifs(vcoreid);
/* take the current state and save it into t->utf when this pthread
* restarts, it will continue from right after this, see yielding is false,
* and short ciruit the function. Don't do this if we're dying. */
if (save_state) {
/* Need to signal this before we actually save, since save_user_ctx
* returns() twice (once now, once when woken up) */
uthread->flags |= UTHREAD_SAVED;
save_user_ctx(&uthread->u_ctx);
}
cmb(); /* Force reread of yielding. Technically save_user_ctx() suffices*/
/* Restart path doesn't matter if we're dying */
if (!yielding)
goto yield_return_path;
/* From here on down is only executed on the save path (not the wake up) */
yielding = FALSE; /* for when it starts back up */
/* TODO: remove this when all arches support SW contexts */
if (save_state && (uthread->u_ctx.type != ROS_SW_CTX)) {
save_fp_state(&uthread->as);
uthread->flags |= UTHREAD_FPSAVED;
}
/* Change to the transition context (both TLS (if applicable) and stack). */
if (__uthread_has_tls(uthread)) {
set_tls_desc(get_vcpd_tls_desc(vcoreid), vcoreid);
assert(current_uthread == uthread);
assert(in_vcore_context());
} else {
/* Since uthreads and vcores share TLS (it's always the vcore's TLS, the
* uthread one just bootstraps from it), we need to change our state at
* boundaries between the two 'contexts' */
__vcore_context = TRUE;
}
/* After this, make sure you don't use local variables. Also, make sure the
* compiler doesn't use them without telling you (TODO).
*
* In each arch's set_stack_pointer, make sure you subtract off as much room
* as you need to any local vars that might be pushed before calling the
* next function, or for whatever other reason the compiler/hardware might
* walk up the stack a bit when calling a noreturn function. */
set_stack_pointer((void*)vcpd->transition_stack);
/* Finish exiting in another function. */
__uthread_yield();
/* Should never get here */
assert(0);
/* Will jump here when the uthread's trapframe is restarted/popped. */
yield_return_path:
printd("[U] Uthread %08p returning from a yield!\n", uthread);
}
/* We explicitly don't support sleep(), since old callers of it have
* expectations of being woken up by signal handlers. If we need that, we can
* build it in to sleep() later. If you just want to sleep for a while, call
* this helper. */
void uthread_sleep(unsigned int seconds)
{
sys_block(seconds * 1000000); /* usec sleep */
}
/* Cleans up the uthread (the stuff we did in uthread_init()). If you want to
* destroy a currently running uthread, you'll want something like
* pthread_exit(), which yields, and calls this from its sched_ops yield. */
void uthread_cleanup(struct uthread *uthread)
{
printd("[U] thread %08p on vcore %d is DYING!\n", uthread, vcore_id());
/* we alloc and manage the TLS, so lets get rid of it, except for thread0.
* glibc owns it. might need to keep it around for a full exit() */
if (__uthread_has_tls(uthread) && !(uthread->flags & UTHREAD_IS_THREAD0))
__uthread_free_tls(uthread);
}
static void __ros_syscall_spinon(struct syscall *sysc)
{
while (!(atomic_read(&sysc->flags) & (SC_DONE | SC_PROGRESS)))
cpu_relax();
}
/* Attempts to block on sysc, returning when it is done or progress has been
* made. */
void __ros_mcp_syscall_blockon(struct syscall *sysc)
{
/* even if we are in 'vcore context', an _S can block */
if (!in_multi_mode()) {
__ros_scp_syscall_blockon(sysc);
return;
}
/* MCP vcore's don't know what to do yet, so we have to spin */
if (in_vcore_context()) {
__ros_syscall_spinon(sysc);
return;
}
/* At this point, we know we're a uthread in an MCP. If we're a
* DONT_MIGRATE uthread, then it's disabled notifs and is basically in
* vcore context, enough so that it can't call into the 2LS. */
assert(current_uthread);
if (current_uthread->flags & UTHREAD_DONT_MIGRATE) {
assert(!notif_is_enabled(vcore_id())); /* catch bugs */
__ros_syscall_spinon(sysc);
}
/* double check before doing all this crap */
if (atomic_read(&sysc->flags) & (SC_DONE | SC_PROGRESS))
return;
/* for both debugging and syscall cancelling */
current_uthread->sysc = sysc;
/* yield, calling 2ls-blockon(cur_uth, sysc) on the other side */
uthread_yield(TRUE, sched_ops->thread_blockon_sysc, sysc);
}
/* Simply sets current uthread to be whatever the value of uthread is. This
* can be called from outside of sched_entry() to highjack the current context,
* and make sure that the new uthread struct is used to store this context upon
* yielding, etc. USE WITH EXTREME CAUTION! */
void highjack_current_uthread(struct uthread *uthread)
{
uint32_t vcoreid = vcore_id();
assert(uthread != current_uthread);
current_uthread->state = UT_NOT_RUNNING;
uthread->state = UT_RUNNING;
/* Make sure the vcore is tracking the new uthread struct */
if (__uthread_has_tls(current_uthread))
vcore_set_tls_var(current_uthread, uthread);
else
current_uthread = uthread;
/* and make sure we are using the correct TLS for the new uthread */
if (__uthread_has_tls(uthread)) {
assert(uthread->tls_desc);
set_tls_desc(uthread->tls_desc, vcoreid);
__vcoreid = vcoreid; /* setting the uthread's TLS var */
}
}
/* Helper: loads a uthread's TLS on this vcore, if applicable. If our uthreads
* do not have their own TLS, we simply switch the __vc_ctx, signalling that the
* context running here is (soon to be) a uthread. */
static void set_uthread_tls(struct uthread *uthread, uint32_t vcoreid)
{
if (__uthread_has_tls(uthread)) {
set_tls_desc(uthread->tls_desc, vcoreid);
__vcoreid = vcoreid; /* setting the uthread's TLS var */
} else {
__vcore_context = FALSE;
}
}
/* Run the thread that was current_uthread, from a previous run. Should be
* called only when the uthread already was running, and we were interrupted by
* the kernel (event, etc). Do not call this to run a fresh uthread, even if
* you've set it to be current. */
void run_current_uthread(void)
{
uint32_t vcoreid = vcore_id();
struct preempt_data *vcpd = vcpd_of(vcoreid);
assert(current_uthread);
assert(current_uthread->state == UT_RUNNING);
/* Uth was already running, should not have been saved */
assert(!(current_uthread->flags & UTHREAD_SAVED));
assert(!(current_uthread->flags & UTHREAD_FPSAVED));
printd("[U] Vcore %d is restarting uthread %08p\n", vcoreid,
current_uthread);
/* Go ahead and start the uthread */
set_uthread_tls(current_uthread, vcoreid);
/* Run, using the TF in the VCPD. FP state should already be loaded */
pop_user_ctx(&vcpd->uthread_ctx, vcoreid);
assert(0);
}
/* Launches the uthread on the vcore. Don't call this on current_uthread.
*
* In previous versions of this, we used to check for events after setting
* current_uthread. That is super-dangerous. handle_events() doesn't always
* return (which we used to handle), and it may also clear current_uthread. We
* needed to save uthread in current_uthread, in case we didn't return. If we
* didn't return, the vcore started over at vcore_entry, with current set. When
* this happens, we never actually had loaded cur_uth's FP and GP onto the core,
* so cur_uth fails. Check out 4602599be for more info.
*
* Ultimately, handling events again in these 'popping helpers' isn't even
* necessary (we only must do it once for an entire time in VC ctx, and in
* loops), and might have been optimizing a rare event at a cost in both
* instructions and complexity. */
void run_uthread(struct uthread *uthread)
{
uint32_t vcoreid = vcore_id();
struct preempt_data *vcpd = vcpd_of(vcoreid);
assert(!current_uthread);
assert(uthread->state == UT_NOT_RUNNING);
assert(uthread->flags & UTHREAD_SAVED);
/* For HW CTX, FPSAVED must match UTH SAVE (and both be on here). For SW,
* FP should never be saved. */
if (uthread->u_ctx.type == ROS_HW_CTX)
assert(uthread->flags & UTHREAD_FPSAVED);
else
assert(!(uthread->flags & UTHREAD_FPSAVED));
uthread->state = UT_RUNNING;
/* Save a ptr to the uthread we'll run in the transition context's TLS */
current_uthread = uthread;
if (uthread->flags & UTHREAD_FPSAVED) {
uthread->flags &= ~UTHREAD_FPSAVED;
restore_fp_state(&uthread->as);
}
set_uthread_tls(uthread, vcoreid);
/* the uth's context will soon be in the cpu (or VCPD), no longer saved */
uthread->flags &= ~UTHREAD_SAVED;
pop_user_ctx(&uthread->u_ctx, vcoreid);
assert(0);
}
/* Runs the uthread, but doesn't care about notif pending. Only call this when
* there was a DONT_MIGRATE uthread, or a similar situation where the uthread
* will check messages soon (like calling enable_notifs()). */
static void __run_current_uthread_raw(void)
{
uint32_t vcoreid = vcore_id();
struct preempt_data *vcpd = vcpd_of(vcoreid);
/* We need to manually say we have a notif pending, so we eventually return
* to vcore context. (note the kernel turned it off for us) */
vcpd->notif_pending = TRUE;
assert(!(current_uthread->flags & UTHREAD_SAVED));
assert(!(current_uthread->flags & UTHREAD_FPSAVED));
set_uthread_tls(current_uthread, vcoreid);
pop_user_ctx_raw(&vcpd->uthread_ctx, vcoreid);
assert(0);
}
/* Copies the uthread trapframe and silly state from the vcpd to the uthread,
* subject to the uthread's flags and whatnot.
*
* For example: The uthread state might still be in the uthread struct. Imagine
* the 2LS decides to run a new uthread and sets it up as current, but doesn't
* actually run it yet. The 2LS happened to voluntarily give up the VC (due to
* some other event) and then wanted to copy out the thread. This is pretty
* rare - the normal case is when an IRQ of some sort hit the core and the
* kernel copied the running state into VCPD.
*
* The FP state could also be in VCPD (e.g. preemption being handled remotely),
* it could be in the uthread struct (e.g. hasn't started running yet) or even
* in the FPU (e.g. took an IRQ/notif and we're handling the preemption of
* another vcore).
*
* There are some cases where we'll have a uthread SW ctx that needs to be
* copied out: uth syscalls, notif happens, and the core comes back from the
* kernel in VC ctx. VC ctx calls copy_out (response to preempt_pending or done
* while handling a preemption). */
static void copyout_uthread(struct preempt_data *vcpd, struct uthread *uthread,
bool vcore_local)
{
assert(uthread);
if (uthread->flags & UTHREAD_SAVED) {
/* I don't know of scenarios where HW ctxs FP state differs from GP */
if (uthread->u_ctx.type == ROS_HW_CTX)
assert(uthread->flags & UTHREAD_FPSAVED);
assert(vcore_local);
return;
}
/* If we're copying GP state, it must be in VCPD */
uthread->u_ctx = vcpd->uthread_ctx;
uthread->flags |= UTHREAD_SAVED;
printd("VC %d copying out uthread %08p\n", vcore_id(), uthread);
/* Software contexts do not need FP state, nor should we think it has any */
if (uthread->u_ctx.type == ROS_SW_CTX) {
assert(!(uthread->flags & UTHREAD_FPSAVED));
return;
}
/* HW contexts also should not have it saved either. Should be either in
* the VCPD or the FPU. Yes, this is the same assert. */
assert(!(uthread->flags & UTHREAD_FPSAVED));
/* When we're dealing with the uthread running on our own vcore, the FP
* state is in the actual FPU, not VCPD. It might also be in VCPD, but it
* will always be in the FPU (the kernel maintains this for us, in the event
* we were preempted since the uthread was last running). */
if (vcore_local)
save_fp_state(&uthread->as);
else
uthread->as = vcpd->preempt_anc;
uthread->flags |= UTHREAD_FPSAVED;
}
/* Helper, packages up and pauses a uthread that was running on vcoreid. Used
* by preemption handling (and detection) so far. Careful using this, esp if
* it is on another vcore (need to make sure it's not running!). If you are
* using it on the local vcore, set vcore_local = TRUE. */
static void __uthread_pause(struct preempt_data *vcpd, struct uthread *uthread,
bool vcore_local)
{
assert(!(uthread->flags & UTHREAD_DONT_MIGRATE));
copyout_uthread(vcpd, uthread, vcore_local);
uthread->state = UT_NOT_RUNNING;
/* Call out to the 2LS to package up its uthread */
assert(sched_ops->thread_paused);
sched_ops->thread_paused(uthread);
}
/* Deals with a pending preemption (checks, responds). If the 2LS registered a
* function, it will get run. Returns true if you got preempted. Called
* 'check' instead of 'handle', since this isn't an event handler. It's the "Oh
* shit a preempt is on its way ASAP".
*
* Be careful calling this: you might not return, so don't call it if you can't
* handle that. If you are calling this from an event handler, you'll need to
* do things like ev_might_not_return(). If the event can via an INDIR ev_q,
* that ev_q must be a NOTHROTTLE.
*
* Finally, don't call this from a place that might have a DONT_MIGRATE
* cur_uth. This should be safe for most 2LS code. */
bool __check_preempt_pending(uint32_t vcoreid)
{
bool retval = FALSE;
assert(in_vcore_context());
if (__preempt_is_pending(vcoreid)) {
retval = TRUE;
if (sched_ops->preempt_pending)
sched_ops->preempt_pending();
/* If we still have a cur_uth, copy it out and hand it back to the 2LS
* before yielding. */
if (current_uthread) {
__uthread_pause(vcpd_of(vcoreid), current_uthread, TRUE);
current_uthread = 0;
}
/* vcore_yield tries to yield, and will pop back up if this was a spurious
* preempt_pending or if it handled an event. For now, we'll just keep
* trying to yield so long as a preempt is coming in. Eventually, we'll
* handle all of our events and yield, or else the preemption will hit
* and someone will recover us (at which point we'll break out of the
* loop) */
while (__procinfo.vcoremap[vcoreid].preempt_pending) {
vcore_yield(TRUE);
cpu_relax();
}
}
return retval;
}
/* Helper: This is a safe way for code to disable notifs if it *might* be called
* from uthread context (like from a notif_safe lock). Pair this with
* uth_enable_notifs() unless you know what you're doing. */
void uth_disable_notifs(void)
{
if (!in_vcore_context() && in_multi_mode()) {
if (current_uthread)
current_uthread->flags |= UTHREAD_DONT_MIGRATE;
cmb(); /* don't issue the flag write before the vcore_id() read */
disable_notifs(vcore_id());
}
}
/* Helper: Pair this with uth_disable_notifs(). */
void uth_enable_notifs(void)
{
if (!in_vcore_context() && in_multi_mode()) {
if (current_uthread)
current_uthread->flags &= ~UTHREAD_DONT_MIGRATE;
cmb(); /* don't enable before ~DONT_MIGRATE */
enable_notifs(vcore_id());
}
}
/* Helper: returns TRUE if it succeeded in starting the uth stealing process. */
static bool start_uth_stealing(struct preempt_data *vcpd)
{
long old_flags;
do {
old_flags = atomic_read(&vcpd->flags);
/* Spin if the kernel is mucking with the flags */
while (old_flags & VC_K_LOCK)
old_flags = atomic_read(&vcpd->flags);
/* Someone else is stealing, we failed */
if (old_flags & VC_UTHREAD_STEALING)
return FALSE;
} while (!atomic_cas(&vcpd->flags, old_flags,
old_flags | VC_UTHREAD_STEALING));
return TRUE;
}
/* Helper: pairs with stop_uth_stealing */
static void stop_uth_stealing(struct preempt_data *vcpd)
{
long old_flags;
do {
old_flags = atomic_read(&vcpd->flags);
assert(old_flags & VC_UTHREAD_STEALING); /* sanity */
while (old_flags & VC_K_LOCK)
old_flags = atomic_read(&vcpd->flags);
} while (!atomic_cas(&vcpd->flags, old_flags,
old_flags & ~VC_UTHREAD_STEALING));
}
/* Handles INDIRS for another core (the public mbox). We synchronize with the
* kernel (__set_curtf_to_vcoreid). */
static void handle_indirs(uint32_t rem_vcoreid)
{
long old_flags;
struct preempt_data *rem_vcpd = vcpd_of(rem_vcoreid);
/* Turn off their message reception if they are still preempted. If they
* are no longer preempted, we do nothing - they will handle their own
* messages. Turning on CAN_RCV will route this vcore's messages to
* fallback vcores (if applicable). */
do {
old_flags = atomic_read(&rem_vcpd->flags);
while (old_flags & VC_K_LOCK)
old_flags = atomic_read(&rem_vcpd->flags);
if (!(old_flags & VC_PREEMPTED))
return;
} while (!atomic_cas(&rem_vcpd->flags, old_flags,
old_flags & ~VC_CAN_RCV_MSG));
wrmb(); /* don't let the CAN_RCV write pass reads of the mbox status */
/* handle all INDIRs of the remote vcore */
handle_vcpd_mbox(rem_vcoreid);
}
/* Helper. Will ensure a good attempt at changing vcores, meaning we try again
* if we failed for some reason other than the vcore was already running. */
static void __change_vcore(uint32_t rem_vcoreid, bool enable_my_notif)
{
/* okay to do a normal spin/relax here, even though we are in vcore
* context. */
while (-EAGAIN == sys_change_vcore(rem_vcoreid, enable_my_notif))
cpu_relax();
}
/* Helper, used in preemption recovery. When you can freely leave vcore
* context and need to change to another vcore, call this. vcpd is the caller,
* rem_vcoreid is the remote vcore. This will try to package up your uthread.
* It may return, either because the other core already started up (someone else
* got it), or in some very rare cases where we had to stay in our vcore
* context */
static void change_to_vcore(struct preempt_data *vcpd, uint32_t rem_vcoreid)
{
bool were_handling_remotes;
/* Unlikely, but if we have no uthread we can just change. This is the
* check, sync, then really check pattern: we can only really be sure about
* current_uthread after we check STEALING. */
if (!current_uthread) {
/* there might be an issue with doing this while someone is recovering.
* once they 0'd it, we should be good to yield. just a bit dangerous.
* */
were_handling_remotes = ev_might_not_return();
__change_vcore(rem_vcoreid, TRUE); /* noreturn on success */
goto out_we_returned;
}
/* Note that the reason we need to check STEALING is because we can get into
* vcore context and slip past that check in vcore_entry when we are
* handling a preemption message. Anytime preemption recovery cares about
* the calling vcore's cur_uth, it needs to be careful about STEALING. But
* it is safe to do the check up above (if it's 0, it won't concurrently
* become non-zero).
*
* STEALING might be turned on at any time. Whoever turns it on will do
* nothing if we are online or were in vc_ctx. So if it is on, we can't
* touch current_uthread til it is turned off (not sure what state they saw
* us in). We could spin here til they unset STEALING (since they will
* soon), but there is a chance they were preempted, so we need to make
* progress by doing a sys_change_vcore(). */
/* Crap, someone is stealing (unlikely). All we can do is change. */
if (atomic_read(&vcpd->flags) & VC_UTHREAD_STEALING) {
__change_vcore(rem_vcoreid, FALSE); /* returns on success */
return;
}
cmb();
/* Need to recheck, in case someone stole it and finished before we checked
* VC_UTHREAD_STEALING. */
if (!current_uthread) {
were_handling_remotes = ev_might_not_return();
__change_vcore(rem_vcoreid, TRUE); /* noreturn on success */
goto out_we_returned;
}
/* Need to make sure we don't have a DONT_MIGRATE (very rare, someone would
* have to steal from us to get us to handle a preempt message, and then had
* to finish stealing (and fail) fast enough for us to miss the previous
* check). */
if (current_uthread->flags & UTHREAD_DONT_MIGRATE) {
__change_vcore(rem_vcoreid, FALSE); /* returns on success */
return;
}
/* Now save our uthread and restart them */
assert(current_uthread);
__uthread_pause(vcpd, current_uthread, TRUE);
current_uthread = 0;
were_handling_remotes = ev_might_not_return();
__change_vcore(rem_vcoreid, TRUE); /* noreturn on success */
/* Fall-through to out_we_returned */
out_we_returned:
ev_we_returned(were_handling_remotes);
}
/* This handles a preemption message. When this is done, either we recovered,
* or recovery *for our message* isn't needed. */
void handle_vc_preempt(struct event_msg *ev_msg, unsigned int ev_type)
{
uint32_t vcoreid = vcore_id();
struct preempt_data *vcpd = vcpd_of(vcoreid);
uint32_t rem_vcoreid = ev_msg->ev_arg2;
struct preempt_data *rem_vcpd = vcpd_of(rem_vcoreid);
struct uthread *uthread_to_steal = 0;
struct uthread **rem_cur_uth;
bool cant_migrate = FALSE;
assert(in_vcore_context());
/* Just drop messages about ourselves. They are old. If we happen to be
* getting preempted right now, there's another message out there about
* that. */
if (rem_vcoreid == vcoreid)
return;
printd("Vcore %d was preempted (i'm %d), it's flags %08p!\n",
ev_msg->ev_arg2, vcoreid, rem_vcpd->flags);
/* Spin til the kernel is done with flags. This is how we avoid handling
* the preempt message before the preemption. */
while (atomic_read(&rem_vcpd->flags) & VC_K_LOCK)
cpu_relax();
/* If they aren't preempted anymore, just return (optimization). */
if (!(atomic_read(&rem_vcpd->flags) & VC_PREEMPTED))
return;
/* At this point, we need to try to recover */
/* This case handles when the remote core was in vcore context */
if (rem_vcpd->notif_disabled) {
printd("VC %d recovering %d, notifs were disabled\n", vcoreid,
rem_vcoreid);
change_to_vcore(vcpd, rem_vcoreid);
return; /* in case it returns. we've done our job recovering */
}
/* So now it looks like they were not in vcore context. We want to steal
* the uthread. Set stealing, then doublecheck everything. If stealing
* fails, someone else is stealing and we can just leave. That other vcore
* who is stealing will check the VCPD/INDIRs when it is done. */
if (!start_uth_stealing(rem_vcpd))
return;
/* Now we're stealing. Double check everything. A change in preempt status
* or notif_disable status means the vcore has since restarted. The vcore
* may or may not have started after we set STEALING. If it didn't, we'll
* need to bail out (but still check messages, since above we assumed the
* uthread stealer handles the VCPD/INDIRs). Since the vcore is running, we
* don't need to worry about handling the message any further. Future
* preemptions will generate another message, so we can ignore getting the
* uthread or anything like that. */
printd("VC %d recovering %d, trying to steal uthread\n", vcoreid,
rem_vcoreid);
if (!(atomic_read(&rem_vcpd->flags) & VC_PREEMPTED))
goto out_stealing;
/* Might be preempted twice quickly, and the second time had notifs
* disabled.
*
* Also note that the second preemption event had another
* message sent, which either we or someone else will deal with. And also,
* we don't need to worry about how we are stealing still and plan to
* abort. If another vcore handles that second preemption message, either
* the original vcore is in vc ctx or not. If so, we bail out and the
* second preemption handling needs to change_to. If not, we aren't
* bailing out, and we'll handle the preemption as normal, and the second
* handler will bail when it fails to steal. */
if (rem_vcpd->notif_disabled)
goto out_stealing;
/* At this point, we're clear to try and steal the uthread. We used to
* switch to their TLS to steal the uthread, but we can access their
* current_uthread directly. */
rem_cur_uth = get_cur_uth_addr(rem_vcoreid);
uthread_to_steal = *rem_cur_uth;
if (uthread_to_steal) {
/* Extremely rare: they have a uthread, but it can't migrate. So we'll
* need to change to them. */
if (uthread_to_steal->flags & UTHREAD_DONT_MIGRATE) {
printd("VC %d recovering %d, can't migrate uthread!\n", vcoreid,
rem_vcoreid);
stop_uth_stealing(rem_vcpd);
change_to_vcore(vcpd, rem_vcoreid);
return; /* in case it returns. we've done our job recovering */
} else {
*rem_cur_uth = 0;
/* we're clear to steal it */
printd("VC %d recovering %d, uthread %08p stolen\n", vcoreid,
rem_vcoreid, uthread_to_steal);
__uthread_pause(rem_vcpd, uthread_to_steal, FALSE);
/* can't let the cur_uth = 0 write and any writes from __uth_pause()
* to pass stop_uth_stealing. */
wmb();
}
}
/* Fallthrough */
out_stealing:
stop_uth_stealing(rem_vcpd);
handle_indirs(rem_vcoreid);
}
/* This handles a "check indirs" message. When this is done, either we checked
* their indirs, or the vcore restarted enough so that checking them is
* unnecessary. If that happens and they got preempted quickly, then another
* preempt/check_indirs was sent out. */
void handle_vc_indir(struct event_msg *ev_msg, unsigned int ev_type)
{
uint32_t vcoreid = vcore_id();
uint32_t rem_vcoreid = ev_msg->ev_arg2;
if (rem_vcoreid == vcoreid)
return;
handle_indirs(rem_vcoreid);
}
/* Attempts to register ev_q with sysc, so long as sysc is not done/progress.
* Returns true if it succeeded, and false otherwise. False means that the
* syscall is done, and does not need an event set (and should be handled
* accordingly).
*
* A copy of this is in glibc/sysdeps/ros/syscall.c. Keep them in sync. */
bool register_evq(struct syscall *sysc, struct event_queue *ev_q)
{
int old_flags;
sysc->ev_q = ev_q;
wrmb(); /* don't let that write pass any future reads (flags) */
/* Try and set the SC_UEVENT flag (so the kernel knows to look at ev_q) */
do {
/* no cmb() needed, the atomic_read will reread flags */
old_flags = atomic_read(&sysc->flags);
/* Spin if the kernel is mucking with syscall flags */
while (old_flags & SC_K_LOCK)
old_flags = atomic_read(&sysc->flags);
/* If the kernel finishes while we are trying to sign up for an event,
* we need to bail out */
if (old_flags & (SC_DONE | SC_PROGRESS)) {
sysc->ev_q = 0; /* not necessary, but might help with bugs */
return FALSE;
}
} while (!atomic_cas(&sysc->flags, old_flags, old_flags | SC_UEVENT));
return TRUE;
}
/* De-registers a syscall, so that the kernel will not send an event when it is
* done. The call could already be SC_DONE, or could even finish while we try
* to unset SC_UEVENT.
*
* There is a chance the kernel sent an event if you didn't do this in time, but
* once this returns, the kernel won't send a message.
*
* If the kernel is trying to send a message right now, this will spin (on
* SC_K_LOCK). We need to make sure we deregistered, and that if a message
* is coming, that it already was sent (and possibly overflowed), before
* returning. */
void deregister_evq(struct syscall *sysc)
{
int old_flags;
sysc->ev_q = 0;
wrmb(); /* don't let that write pass any future reads (flags) */
/* Try and unset the SC_UEVENT flag */
do {
/* no cmb() needed, the atomic_read will reread flags */
old_flags = atomic_read(&sysc->flags);
/* Spin if the kernel is mucking with syscall flags */
while (old_flags & SC_K_LOCK)
old_flags = atomic_read(&sysc->flags);
/* Note we don't care if the SC_DONE flag is getting set. We just need
* to avoid clobbering flags */
} while (!atomic_cas(&sysc->flags, old_flags, old_flags & ~SC_UEVENT));
}
static inline bool __uthread_has_tls(struct uthread *uthread)
{
return uthread->tls_desc != UTH_TLSDESC_NOTLS;
}
/* TLS helpers */
static int __uthread_allocate_tls(struct uthread *uthread)
{
assert(!uthread->tls_desc);
uthread->tls_desc = allocate_tls();
if (!uthread->tls_desc) {
errno = ENOMEM;
return -1;
}
return 0;
}
static int __uthread_reinit_tls(struct uthread *uthread)
{
uthread->tls_desc = reinit_tls(uthread->tls_desc);
if (!uthread->tls_desc) {
errno = ENOMEM;
return -1;
}
return 0;
}
static void __uthread_free_tls(struct uthread *uthread)
{
free_tls(uthread->tls_desc);
uthread->tls_desc = NULL;
}