Allows uthread_init() to be called repeatedly
[akaros.git] / user / parlib / vcore.c
index c2cfcef..b521fdf 100644 (file)
@@ -8,20 +8,17 @@
 #include <unistd.h>
 #include <stdlib.h>
 #include <sys/mman.h>
-#include <rstdio.h>
+#include <stdio.h>
 #include <glibc-tls.h>
 #include <event.h>
+#include <uthread.h>
+#include <ucq.h>
 #include <ros/arch/membar.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;
 
-/* Which operations we'll call for the 2LS.  Will change a bit with Lithe.  For
- * now, there are no defaults. */
-struct schedule_ops default_2ls_ops = {0};
-struct schedule_ops *sched_ops = &default_2ls_ops;
-
 extern void** vcore_thread_control_blocks;
 
 /* Get a TLS, returns 0 on failure.  Vcores have their own TLS, and any thread
@@ -42,13 +39,31 @@ void *allocate_tls(void)
        return tcb;
 }
 
+/* Free a previously allocated TLS region */
+void free_tls(void *tcb)
+{
+       extern void _dl_deallocate_tls (void *tcb, bool dealloc_tcb) internal_function;
+       assert(tcb);
+       _dl_deallocate_tls(tcb, TRUE);
+}
+
+/* Reinitialize / reset / refresh a TLS to its initial values.  This doesn't do
+ * it properly yet, it merely frees and re-allocates the TLS, which is why we're
+ * slightly ghetto and return the pointer you should use for the TCB. */
+void *reinit_tls(void *tcb)
+{
+       /* TODO: keep this in sync with the methods used in
+        * allocate_transition_tls() */
+       free_tls(tcb);
+       return allocate_tls();
+}
+
 /* TODO: probably don't want to dealloc.  Considering caching */
 static void free_transition_tls(int id)
 {
-       extern void _dl_deallocate_tls (void *tcb, bool dealloc_tcb) internal_function;
        if(vcore_thread_control_blocks[id])
        {
-               _dl_deallocate_tls(vcore_thread_control_blocks[id],true);
+               free_tls(vcore_thread_control_blocks[id]);
                vcore_thread_control_blocks[id] = NULL;
        }
 }
@@ -97,6 +112,7 @@ static int allocate_transition_stack(int id)
 int vcore_init()
 {
        static int initialized = 0;
+       uintptr_t mmap_block;
        if(initialized)
                return 0;
 
@@ -111,9 +127,25 @@ int vcore_init()
        if(allocate_transition_stack(0) || allocate_transition_tls(0))
                goto vcore_init_tls_fail;
 
+       /* Initialize our VCPD event queues' ucqs, two pages per vcore */
+       mmap_block = (uintptr_t)mmap(0, PGSIZE * 2 * max_vcores(),
+                                    PROT_WRITE | PROT_READ,
+                                    MAP_POPULATE | MAP_ANONYMOUS, -1, 0);
+       /* Yeah, this doesn't fit in the error-handling scheme, but this whole
+        * system doesn't really handle failure, and needs a rewrite involving less
+        * mmaps/munmaps. */
+       assert(mmap_block);
+       /* Note we may end up doing vcore 0's elsewhere, for _Ss, or else have a
+        * separate ev_q for that. */
+       for (int i = 0; i < max_vcores(); i++) {
+               /* two pages each from the big block */
+               ucq_init_raw(&__procdata.vcore_preempt_data[i].ev_mbox.ev_msgs,
+                            mmap_block + (2 * i    ) * PGSIZE, 
+                            mmap_block + (2 * i + 1) * PGSIZE); 
+       }
+       assert(!in_vcore_context());
        initialized = 1;
        return 0;
-
 vcore_init_tls_fail:
        free(vcore_thread_control_blocks);
 vcore_init_fail:
@@ -121,8 +153,15 @@ vcore_init_fail:
        return -1;
 }
 
+/* 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)
 {
+       struct mcs_lock_qnode local_qn = {0};
        int ret = -1;
        size_t i,j;
 
@@ -131,7 +170,7 @@ int vcore_request(size_t k)
 
        // TODO: could do this function without a lock once we 
        // have atomic fetch and add in user space
-       mcs_lock_lock(&_vcore_lock);
+       mcs_lock_notifsafe(&_vcore_lock, &local_qn);
 
        size_t vcores_wanted = num_vcores() + k;
        if(k < 0 || vcores_wanted > max_vcores())
@@ -146,49 +185,30 @@ int vcore_request(size_t k)
                        goto fail; // errno set by the call that failed
                _max_vcores_ever_wanted++;
        }
+       /* Ugly hack, but we need to be able to transition to _M mode */
+       if (num_vcores() == 0)
+               __enable_notifs(vcore_id());
        ret = sys_resource_req(RES_CORES, vcores_wanted, 1, 0);
 
 fail:
-       mcs_lock_unlock(&_vcore_lock);
+       mcs_unlock_notifsafe(&_vcore_lock, &local_qn);
        return ret;
 }
 
+/* This can return, if you failed to yield due to a concurrent event. */
 void vcore_yield()
 {
-       sys_yield(0);
-}
-
-size_t max_vcores()
-{
-       return MIN(__procinfo.max_vcores, MAX_VCORES);
-}
-
-size_t num_vcores()
-{
-       return __procinfo.num_vcores;
-}
-
-int vcore_id()
-{
-       return __vcoreid;
-}
-
-/* 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)
-{
-       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);
+       uint32_t vcoreid = vcore_id();
+       struct preempt_data *vcpd = &__procdata.vcore_preempt_data[vcoreid];
+       vcpd->can_rcv_msg = FALSE;
+       wmb();
+       if (handle_events(vcoreid)) {
+               /* we handled outstanding events, turn the flag back on and return */
+               vcpd->can_rcv_msg = TRUE;
+               return;
        }
-       return retval;
+       /* o/w, we can safely yield */
+       sys_yield(0);
 }
 
 /* Clear pending, and try to handle events that came in between a previous call
@@ -205,3 +225,24 @@ void clear_notif_pending(uint32_t vcoreid)
                __procdata.vcore_preempt_data[vcoreid].notif_pending = 0;
        } while (handle_events(vcoreid));
 }
+
+/* Enables notifs, and deals with missed notifs by self notifying.  This should
+ * be rare, so the syscall overhead isn't a big deal. */
+void enable_notifs(uint32_t vcoreid)
+{
+       __enable_notifs(vcoreid);
+       if (__procdata.vcore_preempt_data[vcoreid].notif_pending)
+               sys_self_notify(vcoreid, EV_NONE, 0);
+}
+
+/* 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)
+{
+       uint32_t vcoreid = vcore_id();
+       clear_notif_pending(vcoreid);
+       enable_notifs(vcoreid);
+       while (1) {
+               cpu_relax();
+       }
+}