WIP-pop-3000
[akaros.git] / user / parlib / vcore.c
index 547643c..3898aed 100644 (file)
@@ -1,69 +1,58 @@
-#include <arch/arch.h>
+#include <parlib/arch/arch.h>
 #include <stdbool.h>
 #include <errno.h>
-#include <vcore.h>
-#include <mcs.h>
+#include <parlib/vcore.h>
+#include <parlib/mcs.h>
 #include <sys/param.h>
-#include <parlib.h>
+#include <parlib/parlib.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <sys/mman.h>
-#include <rstdio.h>
-#include <glibc-tls.h>
-#include <event.h>
+#include <stdio.h>
+#include <parlib/event.h>
+#include <parlib/uthread.h>
+#include <parlib/ucq.h>
 #include <ros/arch/membar.h>
+#include <parlib/printf-ext.h>
+#include <parlib/poke.h>
+#include <parlib/assert.h>
+#include <parlib/stdio.h>
 
-/* starting with 1 since we alloc vcore0's stacks and TLS in vcore_init(). */
-static size_t _max_vcores_ever_wanted = 1;
-static mcs_lock_t _vcore_lock = MCS_LOCK_INIT;
+__thread int __vcoreid = 0;
+__thread bool __vcore_context = FALSE;
 
-/* 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 syscall __vcore_one_sysc = {.flags = (atomic_t)SC_DONE, 0};
 
-extern void** vcore_thread_control_blocks;
+/* Per vcore entery function used when reentering at the top of a vcore's stack */
+static __thread void (*__vcore_reentry_func)(void) = NULL;
 
-__thread struct uthread *current_uthread = 0;
-
-/* Get a TLS, returns 0 on failure.  Vcores have their own TLS, and any thread
- * created by a user-level scheduler needs to create a TLS as well. */
-void *allocate_tls(void)
-{
-       extern void *_dl_allocate_tls(void *mem) internal_function;
-       void *tcb = _dl_allocate_tls(NULL);
-       if (!tcb)
-               return 0;
-       /* Make sure the TLS is set up properly - its tcb pointer points to itself.
-        * Keep this in sync with sysdeps/ros/XXX/tls.h.  For whatever reason,
-        * dynamically linked programs do not need this to be redone, but statics
-        * do. */
-       tcbhead_t *head = (tcbhead_t*)tcb;
-       head->tcb = tcb;
-       head->self = tcb;
-       return tcb;
-}
-
-/* Free a previously allocated TLS region */
-void free_tls(void *tcb)
+/* The default user vcore_entry function. */
+void __attribute__((noreturn)) __vcore_entry(void)
 {
-       extern void _dl_deallocate_tls (void *tcb, bool dealloc_tcb) internal_function;
-       assert(tcb);
-       _dl_deallocate_tls(tcb, TRUE);
+       extern void uthread_vcore_entry(void);
+       uthread_vcore_entry();
+       fprintf(stderr, "vcore_entry() should never return!\n");
+       abort();
+       __builtin_unreachable();
 }
+void vcore_entry(void) __attribute__((weak, alias ("__vcore_entry")));
 
 /* TODO: probably don't want to dealloc.  Considering caching */
 static void free_transition_tls(int id)
 {
-       if(vcore_thread_control_blocks[id])
-       {
-               free_tls(vcore_thread_control_blocks[id]);
-               vcore_thread_control_blocks[id] = NULL;
+       if (get_vcpd_tls_desc(id)) {
+               /* Note we briefly have no TLS desc in VCPD.  This is fine so long as
+                * that vcore doesn't get started fresh before we put in a new desc */
+               free_tls(get_vcpd_tls_desc(id));
+               set_vcpd_tls_desc(id, NULL);
        }
 }
 
 static int allocate_transition_tls(int id)
 {
+       /* Libc function to initialize TLS-based locale info for ctype functions. */
+       extern void __ctype_init(void);
+
        /* We want to free and then reallocate the tls rather than simply 
         * reinitializing it because its size may have changed.  TODO: not sure if
         * this is right.  0-ing is one thing, but freeing and reallocating can be
@@ -72,439 +61,480 @@ static int allocate_transition_tls(int id)
        free_transition_tls(id);
 
        void *tcb = allocate_tls();
-
-       if ((vcore_thread_control_blocks[id] = tcb) == NULL) {
+       if (!tcb) {
                errno = ENOMEM;
                return -1;
        }
+
+       /* Setup some intitial TLS data for the newly allocated transition tls. */
+       void *temp_tcb = get_tls_desc();
+       set_tls_desc(tcb);
+       begin_safe_access_tls_vars();
+       __vcoreid = id;
+       __vcore_context = TRUE;
+       __ctype_init();
+       end_safe_access_tls_vars();
+       set_tls_desc(temp_tcb);
+
+       /* Install the new tls into the vcpd. */
+       set_vcpd_tls_desc(id, tcb);
        return 0;
 }
 
-static void free_transition_stack(int id)
+static void free_vcore_stack(int id)
 {
        // don't actually free stacks
 }
 
-static int allocate_transition_stack(int id)
+static int allocate_vcore_stack(int id)
 {
-       struct preempt_data *vcpd = &__procdata.vcore_preempt_data[id];
-       if (vcpd->transition_stack)
+       struct preempt_data *vcpd = vcpd_of(id);
+       if (vcpd->vcore_stack)
                return 0; // reuse old stack
 
+// XXX consider adding a guard page.  yeah, it fucks with the VMR map
+//             at least for debugging
+//     force_a_page_fault = ACCESS_ONCE(*(int*)(pt->stacktop - sizeof(int)));
+//                     also, can change this in pth code to use the syscall_async (faster)
+//     syscall_async(&uthread->local_sysc, SYS_populate_va, aux, 1);
+
        void* stackbot = mmap(0, TRANSITION_STACK_SIZE,
-                             PROT_READ|PROT_WRITE|PROT_EXEC,
-                             MAP_POPULATE|MAP_ANONYMOUS, -1, 0);
+                             PROT_READ | PROT_WRITE | PROT_EXEC,
+                             MAP_POPULATE | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
 
        if(stackbot == MAP_FAILED)
                return -1; // errno set by mmap
 
-       vcpd->transition_stack = (uintptr_t)stackbot + TRANSITION_STACK_SIZE;
+       vcpd->vcore_stack = (uintptr_t)stackbot + TRANSITION_STACK_SIZE;
 
        return 0;
 }
 
-int vcore_init()
+/* Helper: prepares a vcore for use.  Takes a block of pages for the UCQs.
+ *
+ * Vcores need certain things, such as a stack and TLS.  These are determined by
+ * userspace.  Every vcore needs these set up before we drop into vcore context
+ * on that vcore.  This means we need to prep before asking the kernel for those
+ * vcores.
+ *
+ * We could have this function do its own mmap, at the expense of O(n) syscalls
+ * when we prepare the extra vcores. */
+static void __prep_vcore(int vcoreid, uintptr_t mmap_block)
 {
-       static int initialized = 0;
-       if(initialized)
-               return 0;
+       struct preempt_data *vcpd = vcpd_of(vcoreid);
+       int ret;
+
+       ret = allocate_vcore_stack(vcoreid);
+               assert(!ret);
+       ret = allocate_transition_tls(vcoreid);
+               assert(!ret);
+
+       vcpd->ev_mbox_public.type = EV_MBOX_UCQ;
+       ucq_init_raw(&vcpd->ev_mbox_public.ucq,
+                    mmap_block + 0 * PGSIZE,
+                    mmap_block + 1 * PGSIZE);
+       vcpd->ev_mbox_private.type = EV_MBOX_UCQ;
+       ucq_init_raw(&vcpd->ev_mbox_private.ucq,
+                    mmap_block + 2 * PGSIZE,
+                    mmap_block + 3 * PGSIZE);
+
+       /* Set the lowest level entry point for each vcore. */
+       vcpd->vcore_entry = (uintptr_t)__kernel_vcore_entry;
+}
 
-       vcore_thread_control_blocks = (void**)calloc(max_vcores(),sizeof(void*));
+static void prep_vcore_0(void)
+{
+       uintptr_t mmap_block;
+
+       mmap_block = (uintptr_t)mmap(0, PGSIZE * 4,
+                                    PROT_WRITE | PROT_READ,
+                                    MAP_POPULATE | MAP_ANONYMOUS | MAP_PRIVATE,
+                                    -1, 0);
+       assert((void*)mmap_block != MAP_FAILED);
+       __prep_vcore(0, mmap_block);
+}
+
+static void prep_remaining_vcores(void)
+{
+       uintptr_t mmap_block;
+
+       mmap_block = (uintptr_t)mmap(0, PGSIZE * 4 * (max_vcores() - 1),
+                                    PROT_WRITE | PROT_READ,
+                                    MAP_POPULATE | MAP_ANONYMOUS | MAP_PRIVATE,
+                                    -1, 0);
+       assert((void*)mmap_block != MAP_FAILED);
+       for (int i = 1; i < max_vcores(); i++)
+               __prep_vcore(i, mmap_block + 4 * (i - 1) * PGSIZE);
+}
 
-       if(!vcore_thread_control_blocks)
-               goto vcore_init_fail;
+/* Run libc specific early setup code. */
+static void vcore_libc_init(void)
+{
+       register_printf_specifier('r', printf_errstr, printf_errstr_info);
+       /* TODO: register for other kevents/signals and whatnot (can probably reuse
+        * the simple ev_q).  Could also do this via explicit functions from the
+        * program. */
+}
 
+/* We need to separate the guts of vcore_lib_ctor() into a separate function,
+ * since the uthread ctor depends on this ctor running first.
+ *
+ * Also note that if you make a global ctor (not static, like this used to be),
+ * any shared objects that you load when the binary is built with -rdynamic will
+ * run the global ctor from the binary, not the one from the .so. */
+void vcore_lib_init(void)
+{
+       /* Note this is racy, but okay.  The first time through, we are _S.
+        * Also, this is the "lowest" level constructor for now, so we don't need
+        * to call any other init functions after our run_once() call. This may
+        * change in the future. */
+       parlib_init_once_racy(return);
        /* Need to alloc vcore0's transition stuff here (technically, just the TLS)
         * so that schedulers can use vcore0's transition TLS before it comes up in
         * vcore_entry() */
-       if(allocate_transition_stack(0) || allocate_transition_tls(0))
-               goto vcore_init_tls_fail;
-
-       assert(!in_vcore_context());
-
-       /* Bug if vcore init was called with no 2LS */
-       assert(sched_ops->sched_init);
-       /* Get thread 0's thread struct (2LS allocs it) */
-       struct uthread *uthread = sched_ops->sched_init();
-       
-       /* 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;
-
-       /* 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. */
-       extern void** vcore_thread_control_blocks;
-       set_tls_desc(vcore_thread_control_blocks[0], 0);
-       current_uthread = uthread;
-       set_tls_desc(uthread->tls_desc, 0);
+       prep_vcore_0();
        assert(!in_vcore_context());
-
-       /* don't forget to enable notifs on vcore0.  if you don't, the kernel will
-        * restart your _S with notifs disabled, which is a path to confusion. */
-       enable_notifs(0);
-
-       initialized = 1;
-       return 0;
-
-vcore_init_tls_fail:
-       free(vcore_thread_control_blocks);
-vcore_init_fail:
-       errno = ENOMEM;
-       return -1;
+       vcore_libc_init();
 }
 
-/* Returns -1 with errno set on error, or 0 on success.  This does not return
- * the number of cores actually granted (though some parts of the kernel do
- * internally).
- *
- * Note the doesn't block or anything (despite the min number requested is
- * 1), since the kernel won't block the call. */
-int vcore_request(size_t k)
+static void __attribute__((constructor)) vcore_lib_ctor(void)
 {
-       int ret = -1;
-       size_t i,j;
+       if (__in_fake_parlib())
+               return;
+       vcore_lib_init();
+}
 
-       if(vcore_init() < 0)
-               return -1;
+/* Helper functions used to reenter at the top of a vcore's stack for an
+ * arbitrary function */
+static void __attribute__((noinline, noreturn)) 
+__vcore_reenter()
+{
+  __vcore_reentry_func();
+  assert(0);
+}
 
-       // TODO: could do this function without a lock once we 
-       // have atomic fetch and add in user space
-       mcs_lock_lock(&_vcore_lock);
+void vcore_reenter(void (*entry_func)(void))
+{
+  assert(in_vcore_context());
+  struct preempt_data *vcpd = vcpd_of(vcore_id());
 
-       size_t vcores_wanted = num_vcores() + k;
-       if(k < 0 || vcores_wanted > max_vcores())
-       {
-               errno = EAGAIN;
-               goto fail;
-       }
+  __vcore_reentry_func = entry_func;
+  set_stack_pointer((void*)vcpd->vcore_stack);
+  cmb();
+  __vcore_reenter();
+}
 
-       for(i = _max_vcores_ever_wanted; i < vcores_wanted; i++)
-       {
-               if(allocate_transition_stack(i) || allocate_transition_tls(i))
-                       goto fail; // errno set by the call that failed
-               _max_vcores_ever_wanted++;
-       }
-       ret = sys_resource_req(RES_CORES, vcores_wanted, 1, 0);
+/* Helper, picks some sane defaults and changes the process into an MCP */
+void vcore_change_to_m(void)
+{
+       int ret;
 
-fail:
-       mcs_lock_unlock(&_vcore_lock);
-       return ret;
+       prep_remaining_vcores();
+       __procdata.res_req[RES_CORES].amt_wanted = 1;
+       __procdata.res_req[RES_CORES].amt_wanted_min = 1;       /* whatever */
+       assert(!in_multi_mode());
+       assert(!in_vcore_context());
+       ret = sys_change_to_m();
+       assert(!ret);
+       assert(in_multi_mode());
+       assert(!in_vcore_context());
 }
 
-void vcore_yield()
+static void __vc_req_poke(void *nr_vc_wanted)
 {
-       sys_yield(0);
+       long nr_vcores_wanted = *(long*)nr_vc_wanted;
+
+       /* We init'd up to max_vcores() VCs during init.  This assumes the kernel
+        * doesn't magically change that value (which it should not do). */
+       nr_vcores_wanted = MIN(nr_vcores_wanted, max_vcores());
+       if (nr_vcores_wanted > __procdata.res_req[RES_CORES].amt_wanted)
+               __procdata.res_req[RES_CORES].amt_wanted = nr_vcores_wanted;
+       if (nr_vcores_wanted > num_vcores())
+               sys_poke_ksched(0, RES_CORES);  /* 0 -> poke for ourselves */
 }
+static struct poke_tracker vc_req_poke = POKE_INITIALIZER(__vc_req_poke);
 
-/* 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". */
-bool check_preempt_pending(uint32_t vcoreid)
+/* Requests the kernel that we have a total of nr_vcores_wanted.
+ *
+ * This is callable by multiple threads/vcores concurrently.  Exactly one of
+ * them will actually run __vc_req_poke.  The others will just return.
+ *
+ * This means that two threads could ask for differing amounts, and only one of
+ * them will succeed.  This is no different than a racy write to a shared
+ * variable.  The poke provides a single-threaded environment, so that we don't
+ * worry about racing on VCPDs or hitting the kernel with excessive SYS_pokes.
+ *
+ * Since we're using the post-and-poke style, we can do a 'last write wins'
+ * policy for the value used in the poke (and subsequent pokes). */
+void vcore_request_total(long nr_vcores_wanted)
 {
-       bool retval = FALSE;
-       if (__procinfo.vcoremap[vcoreid].preempt_pending) {
-               retval = TRUE;
-               if (sched_ops->preempt_pending)
-                       sched_ops->preempt_pending();
-               /* this tries to yield, but will pop back up if this was a spurious
-                * preempt_pending. */
-               sys_yield(TRUE);
-       }
-       return retval;
+       static long nr_vc_wanted;
+
+       if (parlib_never_vc_request || !parlib_wants_to_be_mcp)
+               return;
+       if (nr_vcores_wanted == __procdata.res_req[RES_CORES].amt_wanted)
+               return;
+
+       /* We race to "post our work" here.  Whoever handles the poke will get the
+        * latest value written here. */
+       nr_vc_wanted = nr_vcores_wanted;
+       poke(&vc_req_poke, &nr_vc_wanted);
 }
 
-/* Clear pending, and try to handle events that came in between a previous call
- * to handle_events() and the clearing of pending.  While it's not a big deal,
- * we'll loop in case we catch any.  Will break out of this once there are no
- * events, and we will have send pending to 0. 
+/* This tries to get "more vcores", based on the number we currently have.
  *
- * Note that this won't catch every race/case of an incoming event.  Future
- * events will get caught in pop_ros_tf() */
-void clear_notif_pending(uint32_t vcoreid)
+ * What happens is we can have a bunch of threads trying to get "another vcore",
+ * which currently means more than num_vcores().  If you have someone ask for
+ * two more, and then someone else ask for one more, how many you ultimately ask
+ * for depends on if the kernel heard you and adjusted num_vcores in between the
+ * two calls.  Or maybe your amt_wanted already was num_vcores + 5, so neither
+ * call is telling the kernel anything new.  It comes down to "one more than I
+ * have" vs "one more than I've already asked for".
+ *
+ * So for now, this will keep the older behavior (one more than I have).  This
+ * is all quite racy, so we can just guess and request a total number of vcores.
+ */
+void vcore_request_more(long nr_new_vcores)
 {
-       do {
-               cmb();
-               __procdata.vcore_preempt_data[vcoreid].notif_pending = 0;
-       } while (handle_events(vcoreid));
+       vcore_request_total(nr_new_vcores + num_vcores());
 }
 
-/****************** uthread *******************/
-/* static helpers: */
-static int __uthread_allocate_tls(struct uthread *uthread);
-static void __uthread_free_tls(struct uthread *uthread);
-
-/* 2LSs shouldn't call vcore_entry() directly */
-// XXX this is going to break testing apps like mhello and syscall
-void __attribute__((noreturn)) vcore_entry()
+/* This can return, if you failed to yield due to a concurrent event.  Note
+ * we're atomicly setting the CAN_RCV flag, and aren't bothering with CASing
+ * (either with the kernel or uthread's handle_indirs()).  We don't particularly
+ * care what other code does - we intend to set those flags no matter what. */
+void vcore_yield(bool preempt_pending)
 {
+       unsigned long old_nr;
        uint32_t vcoreid = vcore_id();
+       struct preempt_data *vcpd = vcpd_of(vcoreid);
+
+       if (!preempt_pending && parlib_never_yield)
+               return;
+       __sync_fetch_and_and(&vcpd->flags, ~VC_CAN_RCV_MSG);
+       /* no wrmb() necessary, handle_events() has an mb() if it is checking */
+       /* Clears notif pending and tries to handle events.  This is an optimization
+        * to avoid the yield syscall if we have an event pending.  If there is one,
+        * we want to unwind and return to the 2LS loop, where we may not want to
+        * yield anymore.
+        * Note that the kernel only cares about CAN_RCV_MSG for the desired vcore;
+        * when spamming, it relies on membership of lists within the kernel.  Look
+        * at spam_list_member() for more info (k/s/event.c). */
+       if (handle_events(vcoreid)) {
+               __sync_fetch_and_or(&vcpd->flags, VC_CAN_RCV_MSG);
+               return;
+       }
+       /* If we are yielding since we don't want the core, tell the kernel we want
+        * one less vcore (vc_yield assumes a dumb 2LS).
+        *
+        * If yield fails (slight race), we may end up having more vcores than
+        * amt_wanted for a while, and might lose one later on (after a
+        * preempt/timeslicing) - the 2LS will have to notice eventually if it
+        * actually needs more vcores (which it already needs to do).  amt_wanted
+        * could even be 0.
+        *
+        * In general, any time userspace decrements or sets to 0, it could get
+        * preempted, so the kernel will still give us at least one, until the last
+        * vcore properly yields without missing a message (and becomes a WAITING
+        * proc, which the ksched will not give cores to).
+        *
+        * I think it's possible for userspace to do this (lock, read amt_wanted,
+        * check all message queues for all vcores, subtract amt_wanted (not set to
+        * 0), unlock) so long as every event handler +1s the amt wanted, but that's
+        * a huge pain, and we already have event handling code making sure a
+        * process can't sleep (transition to WAITING) if a message arrives (can't
+        * yield if notif_pending, can't go WAITING without yielding, and the event
+        * posting the notif_pending will find the online VC or be delayed by
+        * spinlock til the proc is WAITING). */
+       if (!preempt_pending) {
+               do {
+                       old_nr = __procdata.res_req[RES_CORES].amt_wanted;
+                       if (old_nr == 0)
+                               break;
+               } while (!__sync_bool_compare_and_swap(
+                            &__procdata.res_req[RES_CORES].amt_wanted,
+                            old_nr, old_nr - 1));
+       }
+       /* We can probably yield.  This may pop back up if notif_pending became set
+        * by the kernel after we cleared it and we lost the race. */
+       sys_yield(preempt_pending);
+       __sync_fetch_and_or(&vcpd->flags, VC_CAN_RCV_MSG);
+}
 
-       struct preempt_data *vcpd = &__procdata.vcore_preempt_data[vcoreid];
-
-       /* Should always have notifications disabled when coming in here. */
-       assert(vcpd->notif_enabled == FALSE);
-       assert(in_vcore_context());
-
-       check_preempt_pending(vcoreid);
-       handle_events(vcoreid);
-       assert(in_vcore_context());     /* double check, in case and event changed it */
-       assert(sched_ops->sched_entry);
-       sched_ops->sched_entry();
-       /* If we get here, the 2LS entry returned.  We can call out to the 2LS for
-        * guidance about whether or not to yield, etc.  Or the 2LS can do it and
-        * just not return.  Whatever we do, it ought to parallel what we do for
-        * requesting more cores in uthread_create(). */
-       printd("Vcore %d is yielding\n", vcoreid);
-       sys_yield(0);
-       assert(0);
+/* Enables notifs, and deals with missed notifs by self notifying.  This should
+ * be rare, so the syscall overhead isn't a big deal.  The other alternative
+ * would be to uthread_yield(), which would require us to revert some uthread
+ * interface changes. */
+void enable_notifs(uint32_t vcoreid)
+{
+       __enable_notifs(vcoreid);
+       wrmb(); /* need to read after the write that enabled notifs */
+       /* Note we could get migrated before executing this.  If that happens, our
+        * vcore had gone into vcore context (which is what we wanted), and this
+        * self_notify to our old vcore is spurious and harmless. */
+       if (vcpd_of(vcoreid)->notif_pending)
+               sys_self_notify(vcoreid, EV_NONE, 0, TRUE);
 }
 
-/* Creates a uthread.  Will pass udata to sched_ops's thread_create.  For now,
- * the vcore/default 2ls code handles start routines and args.  Mostly because
- * this is used when initing a utf, which is vcore specific for now. */
-struct uthread *uthread_create(void (*func)(void), void *udata)
+/* Helper to disable notifs.  It simply checks to make sure we disabled uthread
+ * migration, which is a common mistake. */
+void disable_notifs(uint32_t vcoreid)
 {
-       /* First time through, init the vcore code (which makes a uthread out of
-        * thread0 / the current code.  Could move this to a ctor. */
-       static bool first = TRUE;
-       if (first) {
-               if (vcore_init())               /* could make this uthread_init */
-                       printf("Vcore init failed!\n");
-               first = FALSE;
-       }
-       assert(!in_vcore_context());
-       assert(sched_ops->thread_create);
-       struct uthread *new_thread = sched_ops->thread_create(func, udata);
-       /* Get a TLS */
-       assert(!__uthread_allocate_tls(new_thread));
-       /* Switch into the new guys TLS and let it know who it is */
-       struct uthread *caller = current_uthread;
-       assert(caller);
-       /* Don't migrate this thread to another vcore, since it depends on being on
-        * the same vcore throughout. */
-       caller->dont_migrate = TRUE;
-       wmb();
-       /* Note the first time we call this, we technically aren't on a vcore */
-       uint32_t vcoreid = vcore_id();
-       /* Save the new_thread to the new uthread in that uthread's TLS */
-       set_tls_desc(new_thread->tls_desc, vcoreid);
-       current_uthread = new_thread;
-       /* Switch back to the caller */
-       set_tls_desc(caller->tls_desc, vcoreid);
-       /* Okay to migrate now. */
-       wmb();
-       caller->dont_migrate = FALSE;
-       return new_thread;
+       if (!in_vcore_context() && current_uthread)
+               assert(current_uthread->flags & UTHREAD_DONT_MIGRATE);
+       __disable_notifs(vcoreid);
 }
 
-void uthread_runnable(struct uthread *uthread)
+/* Like smp_idle(), this will put the core in a state that it can only be woken
+ * up by an IPI.  For now, this is a halt.  Maybe an mwait in the future.
+ *
+ * This will return if an event was pending (could be the one you were waiting
+ * for) or if the halt failed for some reason, such as a concurrent RKM.  If
+ * successful, this will not return at all, and the vcore will restart from the
+ * top next time it wakes.  Any sort of IRQ will wake the core.
+ *
+ * Alternatively, I might make this so it never returns, if that's easier to
+ * work with (similar issues with yield). */
+void vcore_idle(void)
 {
-       /* Allow the 2LS to make the thread runnable, and do whatever. */
-       assert(sched_ops->thread_runnable);
-       sched_ops->thread_runnable(uthread);
-       /* This is where we'll call out to a smarter 2LS function to see if we want
-        * to get more cores (and how many). */
-       /* Need to get some vcores.  If this is the first time, we'd like to get
-        * two: one for the main thread (aka thread0), and another for the pthread
-        * we are creating.  Can rework this if we get another vcore interface that
-        * deals with absolute core counts.
+       uint32_t vcoreid = vcore_id();
+       /* Once we enable notifs, the calling context will be treated like a uthread
+        * (saved into the uth slot).  We don't want to ever run it again, so we
+        * need to make sure there's no cur_uth. */
+       assert(!current_uthread);
+       /* This clears notif_pending (check, signal, check again pattern). */
+       if (handle_events(vcoreid))
+               return;
+       /* This enables notifs, but also checks notif pending.  At this point, any
+        * new notifs will restart the vcore from the top. */
+       enable_notifs(vcoreid);
+       /* From now, til we get into the kernel, any notifs will permanently destroy
+        * this context and start the VC from the top.
         *
-        * Need to get at least one core to put us in _M mode so we can run the 2LS,
-        * etc, so for now we'll just spin until we get at least one (might be none
-        * available).
+        * Once we're in the kernel, any messages (__notify, __preempt), will be
+        * RKMs.  halt will need to check for those atomically.  Checking for
+        * notif_pending in the kernel (sleep only if not set) is not enough, since
+        * not all reasons for the kernel to stay awak set notif_pending (e.g.,
+        * __preempts and __death).
         *
-        * TODO: do something smarter regarding asking for cores (paired with
-        * yielding), and block or something until one core is available (will need
-        * kernel support). */
-       static bool first_time = TRUE;
-       if (first_time) {
-               first_time = FALSE;
-               /* Try for two, don't settle for less than 1 */
-               while (num_vcores() < 1) {
-                       vcore_request(2);
-                       cpu_relax();
-               }
-       } else {        /* common case */
-               /* Try to get another for the new thread, but doesn't matter if we get
-                * one or not, so long as we still have at least 1. */
-               vcore_request(1);
-       }
+        * At this point, we're out of VC ctx, so anyone who sets notif_pending
+        * should also send an IPI / __notify */
+       sys_halt_core(0);
+       /* in case halt returns without actually restarting the VC ctx. */
+       disable_notifs(vcoreid);
 }
 
-/* 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.
- *
- * TODO: combine this 2-step logic with uthread_exit() */
-static void __attribute__((noinline, noreturn)) 
-__uthread_yield(struct uthread *uthread)
+/* Helper, that actually makes sure a vcore is running.  Call this is you really
+ * want vcoreid.  More often, you'll want to call the regular version. */
+static void __ensure_vcore_runs(uint32_t vcoreid)
 {
-       assert(in_vcore_context());
-       /* TODO: want to set this to FALSE once we no longer depend on being on this
-        * vcore.  Though if we are using TLS, we are depending on the vcore.  Since
-        * notifs are disabled and we are in a transition context, we probably
-        * shouldn't be moved anyway.  It does mean that a pthread could get jammed.
-        * If we do this after putting it on the active list, we'll have a race on
-        * dont_migrate. */
-       uthread->dont_migrate = FALSE;
-       assert(sched_ops->thread_yield);
-       /* 2LS will save the thread somewhere for restarting.  Later on, we'll
-        * probably have a generic function for all sorts of waiting. */
-       sched_ops->thread_yield(uthread);
-       /* Leave the current vcore completely */
-       current_uthread = NULL; // this might be okay, even with a migration
-       /* Go back to the entry point, where we can handle notifications or
-        * reschedule someone. */
-       vcore_entry();
+       if (vcore_is_preempted(vcoreid)) {
+               printd("[vcore]: VC %d changing to VC %d\n", vcore_id(), vcoreid);
+               /* Note that at this moment, the vcore could still be mapped (we're
+                * racing with __preempt.  If that happens, we'll just fail the
+                * sys_change_vcore(), and next time __ensure runs we'll get it. */
+               /* We want to recover them from preemption.  Since we know they have
+                * notifs disabled, they will need to be directly restarted, so we can
+                * skip the other logic and cut straight to the sys_change_vcore() */
+               sys_change_vcore(vcoreid, FALSE);
+       }
 }
 
-/* Calling thread yields.  TODO: combine similar code with uthread_exit() (done
- * like this to ease the transition to the 2LS-ops */
-void uthread_yield(void)
+/* Helper, looks for any preempted vcores, making sure each of them runs at some
+ * point.  This is pretty heavy-weight, and should be used to help get out of
+ * weird deadlocks (spinning in vcore context, waiting on another vcore).  If
+ * you might know which vcore you are waiting on, use ensure_vc_runs. */
+static void __ensure_all_run(void)
 {
-       struct uthread *uthread = current_uthread;
-       volatile bool yielding = TRUE; /* signal to short circuit when restarting */
-       /* TODO: (HSS) Save silly state */
-       // save_fp_state(&t->as);
-       assert(!in_vcore_context());
-       /* Don't migrate this thread to another vcore, since it depends on being on
-        * the same vcore throughout (once it disables notifs). */
-       uthread->dont_migrate = TRUE;
-       wmb();
-       uint32_t vcoreid = vcore_id();
-       printd("[U] Uthread %08p is yielding on vcore %d\n", uthread, vcoreid);
-       struct preempt_data *vcpd = &__procdata.vcore_preempt_data[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_ros_tf() and pop_ros_tf(). */
-       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. */
-       save_ros_tf(&uthread->utf);
-       if (!yielding)
-               goto yield_return_path;
-       yielding = FALSE; /* for when it starts back up */
-       /* Change to the transition context (both TLS and stack). */
-       extern void** vcore_thread_control_blocks;
-       set_tls_desc(vcore_thread_control_blocks[vcoreid], vcoreid);
-       assert(current_uthread == uthread);     
-       assert(in_vcore_context());     /* technically, we aren't fully in vcore context */
-       /* After this, make sure you don't use local variables.  Note the warning in
-        * pthread_exit() */
-       set_stack_pointer((void*)vcpd->transition_stack);
-       /* Finish exiting in another function. */
-       __uthread_yield(current_uthread);
-       /* Should never get here */
-       assert(0);
-       /* Will jump here when the pthread's trapframe is restarted/popped. */
-yield_return_path:
-       printd("[U] Uthread %08p returning from a yield!\n", uthread);
+       for (int i = 0; i < max_vcores(); i++)
+               __ensure_vcore_runs(i);
 }
 
-/* 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_exit(struct uthread *uthread)
+/* Makes sure a vcore is running.  If it is preempted, we'll switch to
+ * it.  This will return, either immediately if the vcore is running, or later
+ * when someone preempt-recovers us.
+ *
+ * If you pass in your own vcoreid, this will make sure all other preempted
+ * vcores run. */
+void ensure_vcore_runs(uint32_t vcoreid)
 {
-       assert(in_vcore_context());
-       /* we alloc and manage the TLS, so lets get rid of it */
-       __uthread_free_tls(uthread);
-       /* 2LS specific cleanup */
-       assert(sched_ops->thread_exit);
-       sched_ops->thread_exit(uthread);
-       current_uthread = NULL;
-       /* Go back to the entry point, where we can handle notifications or
-        * reschedule someone. */
-       vcore_entry();
+       /* if the vcoreid is ourselves, make sure everyone else is running */
+       if (vcoreid == vcore_id()) {
+               __ensure_all_run();
+               return;
+       }
+       __ensure_vcore_runs(vcoreid);
 }
 
-/* Exits from the uthread */
-void uthread_exit(void)
+#define NR_RELAX_SPINS 1000
+/* If you are spinning and waiting on another vcore, call this.  Pass in the
+ * vcoreid of the core you are waiting on, or your own vcoreid if you don't
+ * know.  It will spin for a bit before firing up the potentially expensive
+ * __ensure_all_run(). */
+void cpu_relax_vc(uint32_t other_vcoreid)
 {
-       assert(!in_vcore_context());
-       struct uthread *uthread = current_uthread;
-       /* Don't migrate this thread to anothe vcore, since it depends on being on
-        * the same vcore throughout. */
-       uthread->dont_migrate = TRUE; // won't set to false later, since he is dying
-       wmb();
-       uint32_t vcoreid = vcore_id();
-       struct preempt_data *vcpd = &__procdata.vcore_preempt_data[vcoreid];
-       printd("[U] Uthread %08p is exiting on vcore %d\n", uthread, vcoreid);
-       /* once we do this, we might miss a notif_pending, so we need to enter vcore
-        * entry later. */
-       disable_notifs(vcoreid);
-       /* Change to the transition context (both TLS and stack). */
-       extern void** vcore_thread_control_blocks;
-       set_tls_desc(vcore_thread_control_blocks[vcoreid], vcoreid);
-       assert(current_uthread == uthread);     
-       /* 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.  Ugh. */
-       __uthread_exit(current_uthread);
+       static __thread unsigned int __vc_relax_spun = 0;
+
+       /* Uthreads with notifs enabled can just spin normally.  This actually
+        * depends on the 2LS preemption policy.  Currently, we receive notifs
+        * whenever another core is preempted, so we don't need to poll. */
+       if (notif_is_enabled(vcore_id())) {
+               cpu_relax();
+               return;
+       }
+       if (__vc_relax_spun++ >= NR_RELAX_SPINS) {
+               /* if other_vcoreid == vcore_id(), this might be expensive */
+               ensure_vcore_runs(other_vcoreid);
+               __vc_relax_spun = 0;
+       }
+       cpu_relax();
 }
 
-/* Runs whatever thread is vcore's current_uthread */
-void run_current_uthread(void)
+/* Check with the kernel to determine what vcore we are.  Normally, you should
+ * never call this, since your vcoreid is stored in your TLS.  Also, if you call
+ * it from a uthread, you could get migrated, so you should drop into some form
+ * of vcore context (DONT_MIGRATE on) */
+uint32_t get_vcoreid(void)
 {
-       uint32_t vcoreid = vcore_id();
-       struct preempt_data *vcpd = &__procdata.vcore_preempt_data[vcoreid];
-       assert(current_uthread);
-       printd("[U] Vcore %d is restarting uthread %d\n", vcoreid, uthread->id);
-       clear_notif_pending(vcoreid);
-       set_tls_desc(current_uthread->tls_desc, vcoreid);
-       /* Pop the user trap frame */
-       pop_ros_tf(&vcpd->notif_tf, vcoreid);
-       assert(0);
+       if (!in_vcore_context()) {
+               assert(current_uthread);
+               assert(current_uthread->flags & UTHREAD_DONT_MIGRATE);
+       }
+       return __get_vcoreid();
 }
 
-/* Launches the uthread on the vcore */
-void run_uthread(struct uthread *uthread)
+/* Debugging helper.  Pass in the string you want printed if your vcoreid is
+ * wrong, and pass in what vcoreid you think you are.  Don't call from uthread
+ * context unless migrations are disabled.  Will print some stuff and return
+ * FALSE if you were wrong. */
+bool check_vcoreid(const char *str, uint32_t vcoreid)
 {
-       /* Save a ptr to the pthread running in the transition context's TLS */
-       uint32_t vcoreid = vcore_id();
-       struct preempt_data *vcpd = &__procdata.vcore_preempt_data[vcoreid];
-       printd("[U] Vcore %d is starting uthread %d\n", vcoreid, uthread->id);
-       current_uthread = uthread;
-       clear_notif_pending(vcoreid);
-       set_tls_desc(uthread->tls_desc, vcoreid);
-       /* Load silly state (Floating point) too.  For real */
-       /* TODO: (HSS) */
-       /* Pop the user trap frame */
-       pop_ros_tf(&uthread->utf, vcoreid);
-       assert(0);
+       uint32_t kvcoreid = get_vcoreid();
+       if (vcoreid != kvcoreid) {
+               printf("%s: VC %d thought it was VC %d\n", str, kvcoreid, vcoreid);
+               return FALSE;
+       }
+       return TRUE;
 }
 
-/* TLS helpers */
-static int __uthread_allocate_tls(struct uthread *uthread)
+/* Helper.  Yields the vcore, or restarts it from scratch. */
+void __attribute__((noreturn)) vcore_yield_or_restart(void)
 {
-       assert(!uthread->tls_desc);
-       uthread->tls_desc = allocate_tls();
-       if (!uthread->tls_desc) {
-               errno = ENOMEM;
-               return -1;
-       }
-       return 0;
+       struct preempt_data *vcpd = vcpd_of(vcore_id());
+
+       vcore_yield(FALSE);
+       /* If vcore_yield returns, we have an event.  Just restart vcore context. */
+       set_stack_pointer((void*)vcpd->vcore_stack);
+       vcore_entry();
 }
 
-/* TODO: probably don't want to dealloc.  Considering caching */
-static void __uthread_free_tls(struct uthread *uthread)
+void vcore_wake(uint32_t vcoreid, bool force_ipi)
 {
-       extern void _dl_deallocate_tls (void *tcb, bool dealloc_tcb) internal_function;
+       struct preempt_data *vcpd = vcpd_of(vcoreid);
 
-       assert(uthread->tls_desc);
-       _dl_deallocate_tls(uthread->tls_desc, TRUE);
-       uthread->tls_desc = NULL;
+       vcpd->notif_pending = true;
+       if (vcoreid == vcore_id())
+               return;
+       if (force_ipi || !arch_has_mwait())
+               sys_self_notify(vcoreid, EV_NONE, 0, true);
 }