Tracks proc's vcoreids in pcpu info
[akaros.git] / kern / src / process.c
index c64fce9..4a4366a 100644 (file)
@@ -38,6 +38,7 @@ static uint32_t get_vcoreid(struct proc *p, uint32_t pcoreid);
 static uint32_t try_get_pcoreid(struct proc *p, uint32_t vcoreid);
 static uint32_t get_pcoreid(struct proc *p, uint32_t vcoreid);
 static void __proc_free(struct kref *kref);
+static bool scp_is_vcctx_ready(struct preempt_data *vcpd);
 
 /* PID management. */
 #define PID_MAX 32767 // goes from 0 to 32767, with 0 reserved
@@ -209,6 +210,9 @@ static void proc_init_procinfo(struct proc* p)
 static void proc_init_procdata(struct proc *p)
 {
        memset(p->procdata, 0, sizeof(struct procdata));
+       /* processes can't go into vc context on vc 0 til they unset this.  This is
+        * for processes that block before initing uthread code (like rtld). */
+       atomic_set(&p->procdata->vcore_preempt_data[0].flags, VC_SCP_NOVCCTX);
 }
 
 /* Allocates and initializes a process, with the given parent.  Currently
@@ -226,8 +230,9 @@ error_t proc_alloc(struct proc **pp, struct proc *parent)
 
        { INITSTRUCT(*p)
 
-       /* one reference for the proc existing, and one for the ref we pass back. */
-       kref_init(&p->p_kref, __proc_free, 2);
+       /* only one ref, which we pass back.  the old 'existence' ref is managed by
+        * the ksched */
+       kref_init(&p->p_kref, __proc_free, 1);
        // Setup the default map of where to get cache colors from
        p->cache_colors_map = global_cache_colors_map;
        p->next_cache_color = 0;
@@ -243,6 +248,7 @@ error_t proc_alloc(struct proc **pp, struct proc *parent)
        /* Set the basic status variables. */
        spinlock_init(&p->proc_lock);
        p->exitcode = 1337;     /* so we can see processes killed by the kernel */
+       init_sem(&p->state_change, 0);
        p->ppid = parent ? parent->pid : 0;
        p->state = PROC_CREATED; /* shouldn't go through state machine for init */
        p->env_flags = 0;
@@ -301,6 +307,8 @@ error_t proc_alloc(struct proc **pp, struct proc *parent)
  * push setting the state to CREATED into here. */
 void __proc_ready(struct proc *p)
 {
+       /* Tell the ksched about us */
+       register_proc(p);
        spin_lock(&pid_hash_lock);
        hashtable_insert(pid_hash, (void*)(long)p->pid, p);
        spin_unlock(&pid_hash_lock);
@@ -410,6 +418,14 @@ static void __set_proc_current(struct proc *p)
        }
 }
 
+/* Flag says if vcore context is not ready, which is set in init_procdata.  The
+ * process must turn off this flag on vcore0 at some point.  It's off by default
+ * on all other vcores. */
+static bool scp_is_vcctx_ready(struct preempt_data *vcpd)
+{
+       return !(atomic_read(&vcpd->flags) & VC_SCP_NOVCCTX);
+}
+
 /* Dispatches a _S process to run on the current core.  This should never be
  * called to "restart" a core.   
  *
@@ -421,6 +437,10 @@ static void __set_proc_current(struct proc *p)
  * documentation talks about this a bit). */
 void proc_run_s(struct proc *p)
 {
+       int8_t state = 0;
+       uint32_t coreid = core_id();
+       struct per_cpu_info *pcpui = &per_cpu_info[coreid];
+       struct preempt_data *vcpd = &p->procdata->vcore_preempt_data[0];
        spin_lock(&p->proc_lock);
        switch (p->state) {
                case (PROC_DYING):
@@ -428,7 +448,6 @@ void proc_run_s(struct proc *p)
                        printk("[kernel] _S %d not starting due to async death\n", p->pid);
                        return;
                case (PROC_RUNNABLE_S):
-                       assert(current != p);
                        __proc_set_state(p, PROC_RUNNING_S);
                        /* We will want to know where this process is running, even if it is
                         * only in RUNNING_S.  can use the vcoremap, which makes death easy.
@@ -439,21 +458,47 @@ void proc_run_s(struct proc *p)
                        /* TODO: For now, we won't count this as an active vcore (on the
                         * lists).  This gets unmapped in resource.c and yield_s, and needs
                         * work. */
-                       __map_vcore(p, 0, core_id()); // sort of.  this needs work.
+                       __map_vcore(p, 0, coreid); /* not treated like a true vcore */
                        __seq_end_write(&p->procinfo->coremap_seqctr);
-                       /* incref, since we're saving a reference in owning proc */
+                       /* incref, since we're saving a reference in owning proc later */
                        proc_incref(p, 1);
+                       /* disable interrupts to protect cur_tf, owning_proc, and current */
+                       disable_irqsave(&state);
+                       /* wait til ints are disabled before unlocking, in case someone else
+                        * grabs the lock and IPIs us before we get set up in cur_tf */
+                       spin_unlock(&p->proc_lock);
                        /* redundant with proc_startcore, might be able to remove that one*/
                        __set_proc_current(p);
-                       /* We restartcore, instead of startcore, since startcore is a bit
-                        * lower level and we want a chance to process kmsgs before starting
-                        * the process. */
-                       spin_unlock(&p->proc_lock);
-                       disable_irq();          /* before mucking with cur_tf / owning_proc */
-                       /* this is one of the few times cur_tf != &actual_tf */
-                       current_tf = &p->env_tf;        /* no need for irq disable yet */
-                       /* storing the passed in ref of p in owning_proc */
-                       per_cpu_info[core_id()].owning_proc = p;
+                       /* set us up as owning_proc.  ksched bug if there is already one,
+                        * for now.  can simply clear_owning if we want to. */
+                       assert(!pcpui->owning_proc);
+                       pcpui->owning_proc = p;
+                       pcpui->owning_vcoreid = 0; /* TODO (VC#) */
+                       /* TODO: (HSS) set silly state here (__startcore does it instantly) */
+                       /* similar to the old __startcore, start them in vcore context if
+                        * they have notifs and aren't already in vcore context.  o/w, start
+                        * them wherever they were before (could be either vc ctx or not) */
+                       if (!vcpd->notif_disabled && vcpd->notif_pending
+                                                 && scp_is_vcctx_ready(vcpd)) {
+                               vcpd->notif_disabled = TRUE;
+                               /* save the _S's tf in the notify slot, build and pop a new one
+                                * in actual/cur_tf. */
+                               vcpd->notif_tf = p->env_tf;
+                               pcpui->cur_tf = &pcpui->actual_tf;
+                               memset(pcpui->cur_tf, 0, sizeof(struct trapframe));
+                               proc_init_trapframe(pcpui->cur_tf, 0, p->env_entry,
+                                                   vcpd->transition_stack);
+                       } else {
+                               /* If they have no transition stack, then they can't receive
+                                * events.  The most they are getting is a wakeup from the
+                                * kernel.  They won't even turn off notif_pending, so we'll do
+                                * that for them. */
+                               if (!scp_is_vcctx_ready(vcpd))
+                                       vcpd->notif_pending = FALSE;
+                               /* this is one of the few times cur_tf != &actual_tf */
+                               pcpui->cur_tf = &p->env_tf;
+                       }
+                       enable_irqsave(&state);
                        /* When the calling core idles, it'll call restartcore and run the
                         * _S process's context. */
                        return;
@@ -522,7 +567,8 @@ void __proc_run_m(struct proc *p)
                                 * turn online */
                                TAILQ_FOREACH(vc_i, &p->online_vcs, list) {
                                        send_kernel_message(vc_i->pcoreid, __startcore, (long)p,
-                                                           0, 0, KMSG_IMMEDIATE);
+                                                           (long)vcore2vcoreid(p, vc_i), 0,
+                                                           KMSG_IMMEDIATE);
                                }
                        } else {
                                warn("Tried to proc_run() an _M with no vcores!");
@@ -593,6 +639,8 @@ void proc_restartcore(void)
 {
        struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
        assert(!pcpui->cur_sysc);
+       /* TODO: can probably remove this enable_irq.  it was an optimization for
+        * RKMs */
        /* Try and get any interrupts before we pop back to userspace.  If we didn't
         * do this, we'd just get them in userspace, but this might save us some
         * effort/overhead. */
@@ -612,10 +660,12 @@ void proc_restartcore(void)
        __proc_startcore(pcpui->owning_proc, pcpui->cur_tf);
 }
 
-/*
- * Destroys the given process.  This may be called from another process, a light
- * kernel thread (no real process context), asynchronously/cross-core, or from
- * the process on its own core.
+/* Destroys the process.  This should be called by the ksched, which needs to
+ * hold the lock.  It will destroy the process and return any cores allocated to
+ * the proc via pc_arr and nr_revoked.  It's up to the caller to have enough
+ * space for pc_arr.  This will return TRUE if we successfully killed it, FALSE
+ * otherwise.  Failure isn't a big deal either - it can happen due to concurrent
+ * calls to proc_destroy. 
  *
  * Here's the way process death works:
  * 0. grab the lock (protects state transition and core map)
@@ -635,20 +685,16 @@ void proc_restartcore(void)
  * come in, making you abandon_core, as if you weren't running.  It may be that
  * the only reference to p is the one you passed in, and when you decref, it'll
  * get __proc_free()d. */
-void proc_destroy(struct proc *p)
+bool __proc_destroy(struct proc *p, uint32_t *pc_arr, uint32_t *nr_revoked)
 {
-       uint32_t num_revoked = 0;
-       spin_lock(&p->proc_lock);
-       /* storage for pc_arr is alloced at decl, which is after grabbing the lock*/
-       uint32_t pc_arr[p->procinfo->num_vcores];
+       struct kthread *sleeper;
        switch (p->state) {
                case PROC_DYING: // someone else killed this already.
-                       spin_unlock(&p->proc_lock);
-                       return;
+                       return FALSE;
                case PROC_RUNNABLE_M:
                        /* Need to reclaim any cores this proc might have, even though it's
                         * not running yet. */
-                       num_revoked = __proc_take_allcores(p, pc_arr, FALSE);
+                       *nr_revoked = __proc_take_allcores(p, pc_arr, FALSE);
                        // fallthrough
                case PROC_RUNNABLE_S:
                        /* might need to pull from lists, though i'm currently a fan of the
@@ -680,38 +726,40 @@ void proc_destroy(struct proc *p)
                         * deallocate the cores.
                         * The rule is that the vcoremap is set before proc_run, and reset
                         * within proc_destroy */
-                       num_revoked = __proc_take_allcores(p, pc_arr, FALSE);
+                       *nr_revoked = __proc_take_allcores(p, pc_arr, FALSE);
                        break;
                case PROC_CREATED:
                        break;
                default:
-                       panic("Weird state(%s) in %s()", procstate2str(p->state),
-                             __FUNCTION__);
+                       warn("Weird state(%s) in %s()", procstate2str(p->state),
+                            __FUNCTION__);
+                       return FALSE;
        }
+       /* At this point, a death IPI should be on its way, either from the
+        * RUNNING_S one, or from proc_take_cores with a __death.  in general,
+        * interrupts should be on when you call proc_destroy locally, but currently
+        * aren't for all things (like traphandlers). */
        __proc_set_state(p, PROC_DYING);
        /* This prevents processes from accessing their old files while dying, and
         * will help if these files (or similar objects in the future) hold
         * references to p (preventing a __proc_free()). */
        close_all_files(&p->open_files, FALSE);
-       /* This decref is for the process's existence. */
-       proc_decref(p);
-       /* Unlock.  A death IPI should be on its way, either from the RUNNING_S one,
-        * or from proc_take_cores with a __death.  in general, interrupts should be
-        * on when you call proc_destroy locally, but currently aren't for all
-        * things (like traphandlers). */
-       spin_unlock(&p->proc_lock);
-       /* Return the cores to the ksched */
-       if (num_revoked)
-               put_idle_cores(pc_arr, num_revoked);
-       return;
+       /* Signal our state change.  Assuming we only have one waiter right now. */
+       sleeper = __up_sem(&p->state_change, TRUE);
+       if (sleeper)
+               kthread_runnable(sleeper);
+       return TRUE;
 }
 
 /* Turns *p into an MCP.  Needs to be called from a local syscall of a RUNNING_S
- * process.  Currently, this ignores whether or not you are an _M already.  You
- * should hold the lock before calling. */
-void __proc_change_to_m(struct proc *p)
+ * process.  Returns 0 if it succeeded, an error code otherwise.  You should
+ * hold the lock before calling. */
+int __proc_change_to_m(struct proc *p)
 {
        int8_t state = 0;
+       /* in case userspace erroneously tries to change more than once */
+       if (__proc_is_mcp(p))
+               return -EINVAL;
        switch (p->state) {
                case (PROC_RUNNING_S):
                        /* issue with if we're async or not (need to preempt it)
@@ -758,13 +806,15 @@ void __proc_change_to_m(struct proc *p)
                         * it, and not clearly thinking through how this would happen.
                         * Perhaps an async call that gets serviced after you're
                         * descheduled? */
-                       panic("Not supporting RUNNABLE_S -> RUNNABLE_M yet.\n");
-                       break;
+                       warn("Not supporting RUNNABLE_S -> RUNNABLE_M yet.\n");
+                       return -EINVAL;
                case (PROC_DYING):
                        warn("Dying, core request coming from %d\n", core_id());
+                       return -EINVAL;
                default:
-                       break;
+                       return -EINVAL;
        }
+       return 0;
 }
 
 /* Old code to turn a RUNNING_M to a RUNNING_S, with the calling context
@@ -822,27 +872,30 @@ static uint32_t get_pcoreid(struct proc *p, uint32_t vcoreid)
        return try_get_pcoreid(p, vcoreid);
 }
 
-/* Helper function: yields / wraps up current_tf and schedules the _S */
-void __proc_yield_s(struct proc *p, struct trapframe *tf)
+/* Helper: saves the SCP's tf state and unmaps vcore 0.  In the future, we'll
+ * probably use vc0's space for env_tf and the silly state. */
+void __proc_save_context_s(struct proc *p, struct trapframe *tf)
 {
-       assert(p->state == PROC_RUNNING_S);
        p->env_tf= *tf;
        env_push_ancillary_state(p);                    /* TODO: (HSS) */
        __unmap_vcore(p, 0);    /* VC# keep in sync with proc_run_s */
-       __proc_set_state(p, PROC_RUNNABLE_S);
-       schedule_scp(p);
 }
 
 /* Yields the calling core.  Must be called locally (not async) for now.
- * - If RUNNING_S, you just give up your time slice and will eventually return.
+ * - If RUNNING_S, you just give up your time slice and will eventually return,
+ *   possibly after WAITING on an event.
  * - If RUNNING_M, you give up the current vcore (which never returns), and
  *   adjust the amount of cores wanted/granted.
- * - If you have only one vcore, you switch to RUNNABLE_M.  When you run again,
- *   you'll have one guaranteed core, starting from the entry point.
+ * - If you have only one vcore, you switch to WAITING.  There's no 'classic
+ *   yield' for MCPs (at least not now).  When you run again, you'll have one
+ *   guaranteed core, starting from the entry point.
  *
- * If the call is being nice, it means that it is in response to a preemption
- * (which needs to be checked).  If there is no preemption pending, just return.
- * No matter what, don't adjust the number of cores wanted.
+ * If the call is being nice, it means different things for SCPs and MCPs.  For
+ * MCPs, it means that it is in response to a preemption (which needs to be
+ * checked).  If there is no preemption pending, just return.  For SCPs, it
+ * means the proc wants to give up the core, but still has work to do.  If not,
+ * the proc is trying to wait on an event.  It's not being nice to others, it
+ * just has no work to do.
  *
  * This usually does not return (smp_idle()), so it will eat your reference.
  * Also note that it needs a non-current/edible reference, since it will abandon
@@ -871,8 +924,46 @@ void proc_yield(struct proc *SAFE p, bool being_nice)
        spin_lock(&p->proc_lock); /* horrible scalability.  =( */
        switch (p->state) {
                case (PROC_RUNNING_S):
-                       __proc_yield_s(p, current_tf);  /* current_tf 0'd in abandon core */
-                       spin_unlock(&p->proc_lock);
+                       if (!being_nice) {
+                               /* waiting for an event to unblock us */
+                               vcpd = &p->procdata->vcore_preempt_data[0];
+                               /* this check is an early optimization (check, signal, check
+                                * again pattern).  We could also lock before spamming the
+                                * vcore in event.c */
+                               if (vcpd->notif_pending) {
+                                       /* they can't handle events, just need to prevent a yield.
+                                        * (note the notif_pendings are collapsed). */
+                                       if (!scp_is_vcctx_ready(vcpd))
+                                               vcpd->notif_pending = FALSE;
+                                       goto out_failed;
+                               }
+                               /* syncing with event's SCP code.  we set waiting, then check
+                                * pending.  they set pending, then check waiting.  it's not
+                                * possible for us to miss the notif *and* for them to miss
+                                * WAITING.  one (or both) of us will see and make sure the proc
+                                * wakes up.  */
+                               __proc_set_state(p, PROC_WAITING);
+                               wrmb(); /* don't let the state write pass the notif read */ 
+                               if (vcpd->notif_pending) {
+                                       __proc_set_state(p, PROC_RUNNING_S);
+                                       if (!scp_is_vcctx_ready(vcpd))
+                                               vcpd->notif_pending = FALSE;
+                                       goto out_failed;
+                               }
+                               /* if we're here, we want to sleep.  a concurrent event that
+                                * hasn't already written notif_pending will have seen WAITING,
+                                * and will be spinning while we do this. */
+                               __proc_save_context_s(p, current_tf);
+                               spin_unlock(&p->proc_lock);     /* note irqs are not enabled yet */
+                       } else {
+                               /* yielding to allow other processes to run.  we're briefly
+                                * WAITING, til we are woken up */
+                               __proc_set_state(p, PROC_WAITING);
+                               __proc_save_context_s(p, current_tf);
+                               spin_unlock(&p->proc_lock);     /* note irqs are not enabled yet */
+                               /* immediately wake up the proc (makes it runnable) */
+                               proc_wakeup(p);
+                       }
                        goto out_yield_core;
                case (PROC_RUNNING_M):
                        break;                          /* will handle this stuff below */
@@ -903,9 +994,17 @@ void proc_yield(struct proc *SAFE p, bool being_nice)
        /* At this point, AFAIK there should be no preempt/death messages on the
         * way, and we're on the online list.  So we'll go ahead and do the yielding
         * business. */
-       /* no need to preempt later, since we are yielding (nice or otherwise) */
-       if (vc->preempt_pending)
+       /* If there's a preempt pending, we don't need to preempt later since we are
+        * yielding (nice or otherwise).  If not, this is just a regular yield. */
+       if (vc->preempt_pending) {
                vc->preempt_pending = 0;
+       } else {
+               /* Optional: on a normal yield, check to see if we are putting them
+                * below amt_wanted (help with user races) and bail. */
+               if (p->procdata->res_req[RES_CORES].amt_wanted >=
+                                      p->procinfo->num_vcores)
+                       goto out_failed;
+       }
        /* Don't let them yield if they are missing a notification.  Userspace must
         * not leave vcore context without dealing with notif_pending.  pop_ros_tf()
         * handles leaving via uthread context.  This handles leaving via a yield.
@@ -915,10 +1014,6 @@ void proc_yield(struct proc *SAFE p, bool being_nice)
         * posting). */
        if (vcpd->notif_pending)
                goto out_failed;
-       /* Optional: check to see if we are putting them below amt_wanted (help with
-        * correctness-benign user races) and bail */
-       if (p->procdata->res_req[RES_CORES].amt_wanted >= p->procinfo->num_vcores)
-               goto out_failed;
        /* Now we'll actually try to yield */
        printd("[K] Process %d (%p) is yielding on vcore %d\n", p->pid, p,
               get_vcoreid(p, coreid));
@@ -953,7 +1048,7 @@ void proc_yield(struct proc *SAFE p, bool being_nice)
        }
        spin_unlock(&p->proc_lock);
        /* Hand the now-idle core to the ksched */
-       put_idle_core(pcoreid);
+       put_idle_core(p, pcoreid);
        goto out_yield_core;
 out_failed:
        /* for some reason we just want to return, either to take a KMSG that cleans
@@ -976,7 +1071,8 @@ out_yield_core:                            /* successfully yielded the core */
  * kernel - check the documentation.  Note that pending is more about messages.
  * The process needs to be in vcore_context, and the reason is usually a
  * message.  We set pending here in case we were called to prod them into vcore
- * context (like via a sys_self_notify. */
+ * context (like via a sys_self_notify).  Also note that this works for _S
+ * procs, if you send to vcore 0 (and the proc is running). */
 void proc_notify(struct proc *p, uint32_t vcoreid)
 {
        struct preempt_data *vcpd = &p->procdata->vcore_preempt_data[vcoreid];
@@ -988,8 +1084,7 @@ void proc_notify(struct proc *p, uint32_t vcoreid)
                 * and don't want the proc_lock to be an irqsave.  Spurious
                 * __notify() kmsgs are okay (it checks to see if the right receiver
                 * is current). */
-               if ((p->state & PROC_RUNNING_M) && // TODO: (VC#) (_S state)
-                             vcore_is_mapped(p, vcoreid)) {
+               if (vcore_is_mapped(p, vcoreid)) {
                        printd("[kernel] sending notif to vcore %d\n", vcoreid);
                        /* This use of try_get_pcoreid is racy, might be unmapped */
                        send_kernel_message(try_get_pcoreid(p, vcoreid), __notify, (long)p,
@@ -998,23 +1093,53 @@ void proc_notify(struct proc *p, uint32_t vcoreid)
        }
 }
 
-/* Hold the lock before calling this.  If the process is WAITING, it will wake
- * it up and schedule it. */
+/* Makes sure p is runnable.  May be spammed, via the ksched.  Called only by
+ * the ksched when it holds the ksched lock (or whatever).  We need to lock both
+ * the ksched and the proc at some point, so we need to start this call in the
+ * ksched (lock ordering).
+ *
+ * Will call back to the ksched via one of the __sched_.cp_wakeup() calls. */
 void __proc_wakeup(struct proc *p)
 {
-       if (p->state != PROC_WAITING)
-               return;
+       spin_lock(&p->proc_lock);
        if (__proc_is_mcp(p)) {
-               /* Need to make sure they want at least 1 vcore, so the ksched gives
-                * them something.  Might do this via short handler later. */
+               /* we only wake up WAITING mcps */
+               if (p->state != PROC_WAITING)
+                       goto out_unlock;
                if (!p->procdata->res_req[RES_CORES].amt_wanted)
                        p->procdata->res_req[RES_CORES].amt_wanted = 1;
                __proc_set_state(p, PROC_RUNNABLE_M);
+               spin_unlock(&p->proc_lock);
+               __sched_mcp_wakeup(p);
+               goto out;
        } else {
-               printk("[kernel] FYI, waking up an _S proc\n");
-               __proc_set_state(p, PROC_RUNNABLE_S);
-               schedule_scp(p);
+               /* SCPs can wake up for a variety of reasons.  the only times we need
+                * to do something is if it was waiting or just created.  other cases
+                * are either benign (just go out), or potential bugs (_Ms) */
+               switch (p->state) {
+                       case (PROC_CREATED):
+                       case (PROC_WAITING):
+                               __proc_set_state(p, PROC_RUNNABLE_S);
+                               break;
+                       case (PROC_RUNNABLE_S):
+                       case (PROC_RUNNING_S):
+                       case (PROC_DYING):
+                               goto out_unlock;
+                       case (PROC_RUNNABLE_M):
+                       case (PROC_RUNNING_M):
+                               warn("Weird state(%s) in %s()", procstate2str(p->state),
+                                    __FUNCTION__);
+                               goto out_unlock;
+               }
+               printd("[kernel] FYI, waking up an _S proc\n"); /* thanks, past brho! */
+               spin_unlock(&p->proc_lock);
+               __sched_scp_wakeup(p);
+               goto out;
        }
+out_unlock:
+       spin_unlock(&p->proc_lock);
+out:
+       return;
 }
 
 /* Is the process in multi_mode / is an MCP or not?  */
@@ -1124,7 +1249,7 @@ void proc_preempt_core(struct proc *p, uint32_t pcoreid, uint64_t usec)
        }
        spin_unlock(&p->proc_lock);
        if (preempted)
-               put_idle_core(pcoreid);
+               put_idle_core(p, pcoreid);
 }
 
 /* Warns and preempts all from p.  No delaying / alarming, or anything.  The
@@ -1149,7 +1274,7 @@ void proc_preempt_all(struct proc *p, uint64_t usec)
        spin_unlock(&p->proc_lock);
        /* Return the cores to the ksched */
        if (num_revoked)
-               put_idle_cores(pc_arr, num_revoked);
+               put_idle_cores(p, pc_arr, num_revoked);
 }
 
 /* Give the specific pcore to proc p.  Lots of assumptions, so don't really use
@@ -1168,25 +1293,8 @@ void proc_give(struct proc *p, uint32_t pcoreid)
  * out). */
 uint32_t proc_get_vcoreid(struct proc *SAFE p, uint32_t pcoreid)
 {
-       uint32_t vcoreid;
-       // TODO: the code currently doesn't track the vcoreid properly for _S (VC#)
-       spin_lock(&p->proc_lock);
-       switch (p->state) {
-               case PROC_RUNNING_S:
-                       spin_unlock(&p->proc_lock);
-                       return 0; // TODO: here's the ugly part
-               case PROC_RUNNING_M:
-                       vcoreid = get_vcoreid(p, pcoreid);
-                       spin_unlock(&p->proc_lock);
-                       return vcoreid;
-               case PROC_DYING: // death message is on the way
-                       spin_unlock(&p->proc_lock);
-                       return 0;
-               default:
-                       spin_unlock(&p->proc_lock);
-                       panic("Weird state(%s) in %s()", procstate2str(p->state),
-                             __FUNCTION__);
-       }
+       struct per_cpu_info *pcpui = &per_cpu_info[pcoreid];
+       return pcpui->owning_vcoreid;
 }
 
 /* TODO: make all of these static inlines when we gut the env crap */
@@ -1209,9 +1317,10 @@ struct vcore *vcoreid2vcore(struct proc *p, uint32_t vcoreid)
 /********** Core granting (bulk and single) ***********/
 
 /* Helper: gives pcore to the process, mapping it to the next available vcore
- * from list vc_list.  Returns TRUE if we succeeded (non-empty). */
+ * from list vc_list.  Returns TRUE if we succeeded (non-empty).  If you pass in
+ * **vc, we'll tell you which vcore it was. */
 static bool __proc_give_a_pcore(struct proc *p, uint32_t pcore,
-                                struct vcore_tailq *vc_list)
+                                struct vcore_tailq *vc_list, struct vcore **vc)
 {
        struct vcore *new_vc;
        new_vc = TAILQ_FIRST(vc_list);
@@ -1222,6 +1331,8 @@ static bool __proc_give_a_pcore(struct proc *p, uint32_t pcore,
        TAILQ_REMOVE(vc_list, new_vc, list);
        TAILQ_INSERT_TAIL(&p->online_vcs, new_vc, list);
        __map_vcore(p, vcore2vcoreid(p, new_vc), pcore);
+       if (vc)
+               *vc = new_vc;
        return TRUE;
 }
 
@@ -1235,12 +1346,12 @@ static void __proc_give_cores_runnable(struct proc *p, uint32_t *pc_arr,
        p->procinfo->num_vcores += num;
        for (int i = 0; i < num; i++) {
                /* Try from the bulk list first */
-               if (__proc_give_a_pcore(p, pc_arr[i], &p->bulk_preempted_vcs))
+               if (__proc_give_a_pcore(p, pc_arr[i], &p->bulk_preempted_vcs, 0))
                        continue;
                /* o/w, try from the inactive list.  at one point, i thought there might
                 * be a legit way in which the inactive list could be empty, but that i
                 * wanted to catch it via an assert. */
-               assert(__proc_give_a_pcore(p, pc_arr[i], &p->inactive_vcs));
+               assert(__proc_give_a_pcore(p, pc_arr[i], &p->inactive_vcs, 0));
        }
        __seq_end_write(&p->procinfo->coremap_seqctr);
 }
@@ -1248,6 +1359,7 @@ static void __proc_give_cores_runnable(struct proc *p, uint32_t *pc_arr,
 static void __proc_give_cores_running(struct proc *p, uint32_t *pc_arr,
                                       uint32_t num)
 {
+       struct vcore *vc_i;
        /* Up the refcnt, since num cores are going to start using this
         * process and have it loaded in their owning_proc and 'current'. */
        proc_incref(p, num * 2);        /* keep in sync with __startcore */
@@ -1255,9 +1367,9 @@ static void __proc_give_cores_running(struct proc *p, uint32_t *pc_arr,
        p->procinfo->num_vcores += num;
        assert(TAILQ_EMPTY(&p->bulk_preempted_vcs));
        for (int i = 0; i < num; i++) {
-               assert(__proc_give_a_pcore(p, pc_arr[i], &p->inactive_vcs));
-               send_kernel_message(pc_arr[i], __startcore, (long)p, 0, 0,
-                                   KMSG_IMMEDIATE);
+               assert(__proc_give_a_pcore(p, pc_arr[i], &p->inactive_vcs, &vc_i));
+               send_kernel_message(pc_arr[i], __startcore, (long)p,
+                                   (long)vcore2vcoreid(p, vc_i), 0, KMSG_IMMEDIATE);
        }
        __seq_end_write(&p->procinfo->coremap_seqctr);
 }
@@ -1467,6 +1579,7 @@ void clear_owning_proc(uint32_t coreid)
        struct proc *p = pcpui->owning_proc;
        assert(!irq_is_enabled());
        pcpui->owning_proc = 0;
+       pcpui->owning_vcoreid = 0xdeadbeef;
        pcpui->cur_tf = 0;                      /* catch bugs for now (will go away soon) */
        if (p);
                proc_decref(p);
@@ -1602,6 +1715,7 @@ void proc_change_to_vcore(struct proc *p, uint32_t new_vcoreid,
                           bool enable_my_notif)
 {
        uint32_t caller_vcoreid, pcoreid = core_id();
+       struct per_cpu_info *pcpui = &per_cpu_info[pcoreid];
        struct preempt_data *caller_vcpd;
        struct vcore *caller_vc, *new_vc;
        struct event_msg preempt_msg = {0};
@@ -1631,7 +1745,8 @@ void proc_change_to_vcore(struct proc *p, uint32_t new_vcoreid,
        if (!is_mapped_vcore(p, pcoreid))
                goto out_failed;
        /* Get all our info */
-       caller_vcoreid = get_vcoreid(p, pcoreid);
+       caller_vcoreid = get_vcoreid(p, pcoreid);       /* holding lock, we can check */
+       assert(caller_vcoreid == pcpui->owning_vcoreid);
        caller_vcpd = &p->procdata->vcore_preempt_data[caller_vcoreid];
        caller_vc = vcoreid2vcore(p, caller_vcoreid);
        /* Should only call from vcore context */
@@ -1674,6 +1789,8 @@ void proc_change_to_vcore(struct proc *p, uint32_t new_vcoreid,
        __unmap_vcore(p, caller_vcoreid);
        __map_vcore(p, new_vcoreid, pcoreid);
        __seq_end_write(&p->procinfo->coremap_seqctr);
+       /* So this core knows which vcore is here: */
+       pcpui->owning_vcoreid = new_vcoreid;
        /* Send either a PREEMPT msg or a CHECK_MSGS msg.  If they said to
         * enable_my_notif, then all userspace needs is to check messages, not a
         * full preemption recovery. */
@@ -1693,7 +1810,8 @@ out_failed:
  * Interrupts are disabled. */
 void __startcore(struct trapframe *tf, uint32_t srcid, long a0, long a1, long a2)
 {
-       uint32_t vcoreid, coreid = core_id();
+       uint32_t vcoreid = (uint32_t)a1;
+       uint32_t coreid = core_id();
        struct per_cpu_info *pcpui = &per_cpu_info[coreid];
        struct proc *p_to_run = (struct proc *CT(1))a0;
 
@@ -1702,6 +1820,7 @@ void __startcore(struct trapframe *tf, uint32_t srcid, long a0, long a1, long a2
        assert(!pcpui->owning_proc);
        /* the sender of the amsg increfed already for this saved ref to p_to_run */
        pcpui->owning_proc = p_to_run;
+       pcpui->owning_vcoreid = vcoreid;
        /* sender increfed again, assuming we'd install to cur_proc.  only do this
         * if no one else is there.  this is an optimization, since we expect to
         * send these __startcores to idles cores, and this saves a scramble to
@@ -1714,7 +1833,6 @@ void __startcore(struct trapframe *tf, uint32_t srcid, long a0, long a1, long a2
                proc_decref(p_to_run);          /* can't install, decref the extra one */
        }
        /* Note we are not necessarily in the cr3 of p_to_run */
-       vcoreid = get_vcoreid(p_to_run, coreid);
        /* Now that we sorted refcnts and know p / which vcore it should be, set up
         * pcpui->cur_tf so that it will run that particular vcore */
        __set_curtf_to_vcoreid(p_to_run, vcoreid);
@@ -1733,23 +1851,22 @@ void __notify(struct trapframe *tf, uint32_t srcid, long a0, long a1, long a2)
        /* Not the right proc */
        if (p != pcpui->owning_proc)
                return;
-       /* Common cur_tf sanity checks */
+       /* Common cur_tf sanity checks.  Note cur_tf could be an _S's env_tf */
        assert(pcpui->cur_tf);
-       assert(pcpui->cur_tf == &pcpui->actual_tf);
        assert(!in_kernel(pcpui->cur_tf));
-       /* We shouldn't need to lock here, since unmapping happens on the pcore and
-        * mapping would only happen if the vcore was free, which it isn't until
-        * after we unmap. */
-       vcoreid = get_vcoreid(p, coreid);
+       vcoreid = pcpui->owning_vcoreid;
+       assert(vcoreid == get_vcoreid(p, coreid));
        vcpd = &p->procdata->vcore_preempt_data[vcoreid];
+       /* for SCPs that haven't (and might never) call vc_event_init, like rtld.
+        * this is harmless for MCPS to check this */
+       if (!scp_is_vcctx_ready(vcpd))
+               return;
        printd("received active notification for proc %d's vcore %d on pcore %d\n",
               p->procinfo->pid, vcoreid, coreid);
        /* sort signals.  notifs are now masked, like an interrupt gate */
        if (vcpd->notif_disabled)
                return;
        vcpd->notif_disabled = TRUE;
-       /* This bit shouldn't be important anymore */
-       vcpd->notif_pending = FALSE; // no longer pending - it made it here
        /* save the old tf in the notify slot, build and pop a new one.  Note that
         * silly state isn't our business for a notification. */
        vcpd->notif_tf = *pcpui->cur_tf;
@@ -1775,10 +1892,8 @@ void __preempt(struct trapframe *tf, uint32_t srcid, long a0, long a1, long a2)
        assert(pcpui->cur_tf);
        assert(pcpui->cur_tf == &pcpui->actual_tf);
        assert(!in_kernel(pcpui->cur_tf));
-       /* We shouldn't need to lock here, since unmapping happens on the pcore and
-        * mapping would only happen if the vcore was free, which it isn't until
-        * after we unmap. */
-       vcoreid = get_vcoreid(p, coreid);
+       vcoreid = pcpui->owning_vcoreid;
+       assert(vcoreid == get_vcoreid(p, coreid));
        p->procinfo->vcoremap[vcoreid].preempt_served = FALSE;
        /* either __preempt or proc_yield() ends the preempt phase. */
        p->procinfo->vcoremap[vcoreid].preempt_pending = 0;
@@ -1816,7 +1931,8 @@ void __death(struct trapframe *tf, uint32_t srcid, long a0, long a1, long a2)
        struct per_cpu_info *pcpui = &per_cpu_info[coreid];
        struct proc *p = pcpui->owning_proc;
        if (p) {
-               vcoreid = get_vcoreid(p, coreid);
+               vcoreid = pcpui->owning_vcoreid;
+               assert(vcoreid == get_vcoreid(p, coreid));
                printd("[kernel] death on physical core %d for process %d's vcore %d\n",
                       coreid, p->pid, vcoreid);
                __unmap_vcore(p, vcoreid);