SYS_abort_syscall (XCC)
[akaros.git] / user / parlib / vcore.c
index 0440852..0b94ec2 100644 (file)
@@ -19,17 +19,20 @@ static size_t _max_vcores_ever_wanted = 1;
 atomic_t nr_new_vcores_wanted;
 atomic_t vc_req_being_handled;
 
 atomic_t nr_new_vcores_wanted;
 atomic_t vc_req_being_handled;
 
-extern void** vcore_thread_control_blocks;
 bool vc_initialized = FALSE;
 __thread struct syscall __vcore_one_sysc = {.flags = (atomic_t)SC_DONE, 0};
 
 bool vc_initialized = FALSE;
 __thread struct syscall __vcore_one_sysc = {.flags = (atomic_t)SC_DONE, 0};
 
+/* Per vcore entery function used when reentering at the top of a vcore's stack */
+static __thread void (*__vcore_reentry_func)(void) = NULL;
+
 /* TODO: probably don't want to dealloc.  Considering caching */
 static void free_transition_tls(int id)
 {
 /* 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);
        }
 }
 
        }
 }
 
@@ -43,11 +46,11 @@ static int allocate_transition_tls(int id)
        free_transition_tls(id);
 
        void *tcb = allocate_tls();
        free_transition_tls(id);
 
        void *tcb = allocate_tls();
-
-       if ((vcore_thread_control_blocks[id] = tcb) == NULL) {
+       if (!tcb) {
                errno = ENOMEM;
                return -1;
        }
                errno = ENOMEM;
                return -1;
        }
+       set_vcpd_tls_desc(id, tcb);
        return 0;
 }
 
        return 0;
 }
 
@@ -78,18 +81,13 @@ void vcore_init(void)
 {
        uintptr_t mmap_block;
        /* Note this is racy, but okay.  The first time through, we are _S */
 {
        uintptr_t mmap_block;
        /* Note this is racy, but okay.  The first time through, we are _S */
-       init_once(return);
-
-       vcore_thread_control_blocks = (void**)calloc(max_vcores(),sizeof(void*));
-
-       if(!vcore_thread_control_blocks)
-               goto vcore_init_fail;
+       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))
 
        /* 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;
+               goto vcore_init_fail;
 
        /* Initialize our VCPD event queues' ucqs, two pages per ucq, 4 per vcore */
        mmap_block = (uintptr_t)mmap(0, PGSIZE * 4 * max_vcores(),
 
        /* Initialize our VCPD event queues' ucqs, two pages per ucq, 4 per vcore */
        mmap_block = (uintptr_t)mmap(0, PGSIZE * 4 * max_vcores(),
@@ -117,12 +115,30 @@ void vcore_init(void)
         * _M) */
        vc_initialized = TRUE;
        return;
         * _M) */
        vc_initialized = TRUE;
        return;
-vcore_init_tls_fail:
-       free(vcore_thread_control_blocks);
 vcore_init_fail:
        assert(0);
 }
 
 vcore_init_fail:
        assert(0);
 }
 
+/* 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);
+}
+
+void vcore_reenter(void (*entry_func)(void))
+{
+  assert(in_vcore_context());
+  struct preempt_data *vcpd = vcpd_of(vcore_id());
+
+  __vcore_reentry_func = entry_func;
+  set_stack_pointer((void*)vcpd->transition_stack);
+  cmb();
+  __vcore_reenter();
+}
+
 /* This gets called in glibc before calling the programs 'main'.  Need to set
  * ourselves up so that thread0 is a uthread, and then register basic signals to
  * go to vcore 0. */
 /* This gets called in glibc before calling the programs 'main'.  Need to set
  * ourselves up so that thread0 is a uthread, and then register basic signals to
  * go to vcore 0. */
@@ -138,11 +154,13 @@ void vcore_event_init(void)
 /* Helper, picks some sane defaults and changes the process into an MCP */
 void vcore_change_to_m(void)
 {
 /* Helper, picks some sane defaults and changes the process into an MCP */
 void vcore_change_to_m(void)
 {
+       int ret;
        __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());
        __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());
-       assert(!sys_change_to_m());
+       ret = sys_change_to_m();
+       assert(!ret);
        assert(in_multi_mode());
        assert(!in_vcore_context());
 }
        assert(in_multi_mode());
        assert(!in_vcore_context());
 }
@@ -241,7 +259,7 @@ try_handle_it:
         * races with yield, our desires may be old.  Not a big deal; any vcores
         * that pop up will just end up yielding (or get preempt messages.)  */
        if (nr_vcores_wanted > num_vcores())
         * races with yield, our desires may be old.  Not a big deal; any vcores
         * that pop up will just end up yielding (or get preempt messages.)  */
        if (nr_vcores_wanted > num_vcores())
-               sys_poke_ksched(RES_CORES);
+               sys_poke_ksched(0, RES_CORES);  /* 0 -> poke for ourselves */
        /* Unlock, (which lets someone else work), and check to see if more work
         * needs to be done.  If so, we'll make sure it gets handled. */
        atomic_set(&vc_req_being_handled, 0);   /* unlock, to allow others to try */
        /* Unlock, (which lets someone else work), and check to see if more work
         * needs to be done.  If so, we'll make sure it gets handled. */
        atomic_set(&vc_req_being_handled, 0);   /* unlock, to allow others to try */
@@ -258,61 +276,58 @@ try_handle_it:
  * care what other code does - we intend to set those flags no matter what. */
 void vcore_yield(bool preempt_pending)
 {
  * 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);
        __sync_fetch_and_and(&vcpd->flags, ~VC_CAN_RCV_MSG);
        uint32_t vcoreid = vcore_id();
        struct preempt_data *vcpd = vcpd_of(vcoreid);
        __sync_fetch_and_and(&vcpd->flags, ~VC_CAN_RCV_MSG);
-       /* no wrmb() necessary, clear_notif() has an mb() */
-       /* Clears notif pending.  If we had an event outstanding, this will handle
-        * it and return TRUE, at which point 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, not for a FALLBACK.
-        * We need to deal with this notif_pending business regardless of
-        * CAN_RCV_MSG.  We just want to avoid a yield syscall if possible.  It is
-        * important that clear_notif_pending will handle_events().  That is
-        * necessary to do/check after turning off CAN_RCV_MSG. */
-       if (clear_notif_pending(vcoreid)) {
+       /* 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,
+        * not for a FALLBACK.  */
+       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
                __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.  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
+        * 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
         * preempt/timeslicing) - the 2LS will have to notice eventually if it
-        * actually needs more vcores (which it already needs to do).  We need to
-        * atomically decrement, though I don't want the kernel's data type here to
-        * be atomic_t (only userspace cares in this one case). */
-       if (!preempt_pending)
-               __sync_fetch_and_sub(&__procdata.res_req[RES_CORES].amt_wanted, 1);
+        * 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);
 }
 
        /* 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);
 }
 
-/* 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. 
- *
- * Note that this won't catch every race/case of an incoming event.  Future
- * events will get caught in pop_ros_tf() or proc_yield().
- *
- * Also note that this handles events, which may change your current uthread or
- * might not return!  Be careful calling this.  Check run_uthread for an example
- * of how to use this. */
-bool clear_notif_pending(uint32_t vcoreid)
-{
-       bool handled_event = FALSE;
-       do {
-               vcpd_of(vcoreid)->notif_pending = 0;
-               /* need a full mb(), since handle events might be just a read or might
-                * be a write, either way, it needs to happen after notif_pending */
-               mb();
-               handled_event = handle_events(vcoreid);
-       } while (handled_event);
-       return handled_event;
-}
-
 /* 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
 /* 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
@@ -338,11 +353,13 @@ void disable_notifs(uint32_t vcoreid)
 }
 
 /* Like smp_idle(), this will put the core in a state that it can only be woken
 }
 
 /* Like smp_idle(), this will put the core in a state that it can only be woken
- * up by an IPI.  In the future, we may halt or something. */
-void __attribute__((noreturn)) vcore_idle(void)
+ * up by an IPI.  In the future, we may halt or something.  This will return if
+ * an event was pending (could be the one you were waiting for). */
+void vcore_idle(void)
 {
        uint32_t vcoreid = vcore_id();
 {
        uint32_t vcoreid = vcore_id();
-       clear_notif_pending(vcoreid);
+       if (handle_events(vcoreid))
+               return;
        enable_notifs(vcoreid);
        while (1) {
                cpu_relax();
        enable_notifs(vcoreid);
        while (1) {
                cpu_relax();
@@ -398,9 +415,8 @@ void ensure_vcore_runs(uint32_t vcoreid)
  * context.  sys_change_vcore will probably mess you up. */
 void cpu_relax_vc(uint32_t vcoreid)
 {
  * context.  sys_change_vcore will probably mess you up. */
 void cpu_relax_vc(uint32_t vcoreid)
 {
-       static __thread unsigned int spun;              /* vcore TLS */
+       unsigned int spun = 0;
        assert(in_vcore_context());
        assert(in_vcore_context());
-       spun = 0;
        if (spun++ >= NR_RELAX_SPINS) {
                /* if vcoreid == vcore_id(), this might be expensive */
                ensure_vcore_runs(vcoreid);
        if (spun++ >= NR_RELAX_SPINS) {
                /* if vcoreid == vcore_id(), this might be expensive */
                ensure_vcore_runs(vcoreid);