Glibc syscalls now block properly (XCC)
[akaros.git] / user / parlib / uthread.c
index c5817bd..9287b92 100644 (file)
@@ -22,17 +22,15 @@ static void __uthread_free_tls(struct uthread *uthread);
 static void __run_current_uthread_raw(void);
 static void handle_vc_preempt(struct event_msg *ev_msg, unsigned int ev_type);
 
-/* The real 2LS calls this, passing in a uthread representing thread0.  When it
- * returns, you're in _M mode, still running thread0, on vcore0 */
-int uthread_lib_init(struct uthread *uthread)
+/* Block the calling uthread on sysc until it makes progress or is done */
+static void __ros_mcp_syscall_blockon(struct syscall *sysc);
+
+/* Helper, make the uthread code manage thread0.  This sets up uthread such
+ * that the calling code and its TLS are tracked by the uthread struct, and
+ * vcore0 thinks the uthread is running there.  Called only by slim_init (early
+ * _S code) and lib_init. */
+static void uthread_manage_thread0(struct uthread *uthread)
 {
-       /* Make sure this only runs once */
-       static bool initialized = FALSE;
-       if (initialized)
-               return -1;
-       initialized = TRUE;
-       /* Init the vcore system */
-       assert(!vcore_init());
        assert(uthread);
        /* Save a pointer to thread0's tls region (the glibc one) into its tcb */
        uthread->tls_desc = get_tls_desc(0);
@@ -49,9 +47,27 @@ int uthread_lib_init(struct uthread *uthread)
         * its TLS vars. */
        extern void** vcore_thread_control_blocks;
        set_tls_desc(vcore_thread_control_blocks[0], 0);
+       /* We might have a basic uthread already installed (from slim_init), so
+        * free it before installing the new one. */
+       if (current_uthread)
+               free(current_uthread);
        current_uthread = uthread;
        set_tls_desc(uthread->tls_desc, 0);
        assert(!in_vcore_context());
+}
+
+/* The real 2LS calls this, passing in a uthread representing thread0.  When it
+ * returns, you're in _M mode, still running thread0, on vcore0 */
+int uthread_lib_init(struct uthread *uthread)
+{
+       /* Make sure this only runs once */
+       static bool initialized = FALSE;
+       if (initialized)
+               return -1;
+       initialized = TRUE;
+       /* Init the vcore system */
+       assert(!vcore_init());
+       uthread_manage_thread0(uthread);
        /* Receive preemption events.  Note that this merely tells the kernel how to
         * send the messages, and does not necessarily provide storage space for the
         * messages.  What we're doing is saying that all PREEMPT and CHECK_MSGS
@@ -67,14 +83,44 @@ int uthread_lib_init(struct uthread *uthread)
        printd("[user] registered %08p (flags %08p) for preempt messages\n",
               preempt_ev_q, preempt_ev_q->ev_flags);
        /* Get ourselves into _M mode.  Could consider doing this elsewhere... */
-       while (!in_multi_mode()) {
-               vcore_request(1);
-               /* TODO: consider blocking */
-               cpu_relax();
-       }
+       vcore_change_to_m();
        return 0;
 }
 
+/* Helper: tells the kernel our SCP is capable of going into vcore context on
+ * vcore 0.  Pairs with k/s/process.c scp_is_vcctx_ready(). */
+static void scp_vcctx_ready(void)
+{
+       struct preempt_data *vcpd = vcpd_of(0);
+       long old_flags;
+       /* the CAS is a bit overkill; keeping it around in case people use this
+        * code in other situations. */
+       do {
+               old_flags = atomic_read(&vcpd->flags);
+               /* Spin if the kernel is mucking with the flags */
+               while (old_flags & VC_K_LOCK)
+                       old_flags = atomic_read(&vcpd->flags);
+       } while (!atomic_cas(&vcpd->flags, old_flags,
+                            old_flags & ~VC_SCP_NOVCCTX));
+}
+
+/* Slim-init - sets up basic uthreading for when we are in _S mode and before
+ * we set up the 2LS.  Some apps may not have a 2LS and thus never do the full
+ * vcore/2LS/uthread init. */
+void uthread_slim_init(void)
+{
+       struct uthread *uthread = malloc(sizeof(*uthread));
+       /* TODO: consider a vcore_init_vc0 call.  Init the vcore system */
+       assert(!vcore_init());
+       uthread_manage_thread0(uthread);
+       scp_vcctx_ready();
+       /* change our blockon from glibc's internal one to the mcp one (which can
+        * handle SCPs too).  we must do this before switching to _M, or at least
+        * before blocking while an _M.  it's harmless (and probably saner) to do it
+        * earlier, so we do it as early as possible. */
+       ros_syscall_blockon = __ros_mcp_syscall_blockon;
+}
+
 /* 2LSs shouldn't call uthread_vcore_entry directly */
 void __attribute__((noreturn)) uthread_vcore_entry(void)
 {
@@ -103,9 +149,16 @@ void __attribute__((noreturn)) uthread_vcore_entry(void)
        handle_events(vcoreid);
        __check_preempt_pending(vcoreid);
        assert(in_vcore_context());     /* double check, in case an event changed it */
-       assert(sched_ops->sched_entry);
-       sched_ops->sched_entry();
+       /* Consider using the default_2ls_op for this, though it's a bit weird. */
+       if (sched_ops->sched_entry) {
+               sched_ops->sched_entry();
+       } else if (current_uthread) {
+               run_current_uthread();
+       }
        /* 2LS sched_entry should never return */
+       /* Either the 2LS sched_entry returned, run_cur_uth() returned, or we
+        * didn't have a current_uthread.  If we didn't have a 2LS op, we should be
+        * in _S mode and always have a current_uthread. */
        assert(0);
 }
 
@@ -182,6 +235,7 @@ __uthread_yield(void)
                uthread->state = UT_BLOCKED;
                assert(sched_ops->thread_blockon_sysc);
                sched_ops->thread_blockon_sysc(uthread->sysc);
+               /* make sure you don't touch uthread after that sched ops call */
        } else { /* generic yield */
                uthread->state = UT_RUNNABLE;
                assert(sched_ops->thread_yield);
@@ -267,27 +321,33 @@ void uthread_cleanup(struct uthread *uthread)
        __uthread_free_tls(uthread);
 }
 
+static void __ros_syscall_spinon(struct syscall *sysc)
+{
+       while (!(atomic_read(&sysc->flags) & (SC_DONE | SC_PROGRESS)))
+               cpu_relax();
+}
+
 /* Attempts to block on sysc, returning when it is done or progress has been
  * made. */
-void ros_syscall_blockon(struct syscall *sysc)
+void __ros_mcp_syscall_blockon(struct syscall *sysc)
 {
-       if (in_vcore_context()) {
-               /* vcore's don't know what to do yet, so do the default (spin) */
-               __ros_syscall_blockon(sysc);
+       /* even if we are in 'vcore context', an _S can block */
+       if (!in_multi_mode()) {
+               __ros_scp_syscall_blockon(sysc);
                return;
        }
-       if (!sched_ops->thread_blockon_sysc || !in_multi_mode()) {
-               /* There isn't a 2LS op for blocking, or we're _S.  Spin for now. */
-               __ros_syscall_blockon(sysc);
+       /* MCP vcore's don't know what to do yet, so we have to spin */
+       if (in_vcore_context()) {
+               __ros_syscall_spinon(sysc);
                return;
        }
-       /* At this point, we know we're a uthread.  If we're a DONT_MIGRATE uthread,
-        * then it's disabled notifs and is basically in vcore context, enough so
-        * that it can't call into the 2LS. */
+       /* At this point, we know we're a uthread in an MCP.  If we're a
+        * DONT_MIGRATE uthread, then it's disabled notifs and is basically in
+        * vcore context, enough so that it can't call into the 2LS. */
        assert(current_uthread);
        if (current_uthread->flags & UTHREAD_DONT_MIGRATE) {
                assert(!notif_is_enabled(vcore_id()));  /* catch bugs */
-               __ros_syscall_blockon(sysc);
+               __ros_syscall_spinon(sysc);
        }
        /* double check before doing all this crap */
        if (atomic_read(&sysc->flags) & (SC_DONE | SC_PROGRESS))
@@ -298,16 +358,19 @@ void ros_syscall_blockon(struct syscall *sysc)
 }
 
 /* Helper for run_current and run_uthread.  Make sure the uthread you want to
- * run is the current_uthread before calling this.  It will pop the TF of
- * whatever you send in (run_cur and run_uth use different TFs).
+ * run is the current_uthread before calling this.  Both of those are just
+ * wrappers for this, and they manage current_uthread and its states.   This
+ * manages the TF, FP state, and related flags.
  *
  * This will adjust the thread's state, do one last check on notif_pending, and
  * pop the tf.  Note that the notif check is an optimization.  pop_ros_tf() will
  * definitely handle it, but it will take a syscall to do so later. */
-static void __run_cur_uthread(struct user_trapframe *utf)
+static void __run_cur_uthread(void)
 {
        uint32_t vcoreid = vcore_id();
        struct preempt_data *vcpd = vcpd_of(vcoreid);
+       struct uthread *uthread;
+       /* Last check for messages.  Might not return, or cur_uth might be unset. */
        clear_notif_pending(vcoreid);
        /* clear_notif might have handled a preemption event, and we might not have
         * a current_uthread anymore.  Need to recheck */
@@ -320,15 +383,28 @@ static void __run_cur_uthread(struct user_trapframe *utf)
                uthread_vcore_entry();
                assert(0);
        }
+       uthread = current_uthread;      /* for TLS sanity */
+       /* Load silly state (Floating point) too.  For real */
+       if (uthread->flags & UTHREAD_FPSAVED) {
+               uthread->flags &= ~UTHREAD_FPSAVED;
+               /* TODO: (HSS) actually load it */
+       }
        /* Go ahead and start the uthread */
-       /* utf no longer represents the current state of the uthread */
-       current_uthread->flags &= ~UTHREAD_SAVED;
-       set_tls_desc(current_uthread->tls_desc, vcoreid);
-       /* Pop the user trap frame */
-       pop_ros_tf(utf, vcoreid);
+       set_tls_desc(uthread->tls_desc, vcoreid);
+       /* Depending on where it was saved, we pop differently.  This assumes that
+        * if a uthread was not saved, that it was running in the vcpd notif tf.
+        * There should never be a time that the TF is unsaved and not in the notif
+        * TF (or about to be in that TF). */
+       if (uthread->flags & UTHREAD_SAVED) {
+               uthread->flags &= ~UTHREAD_SAVED;
+               pop_ros_tf(&uthread->utf, vcoreid);
+       } else  {
+               pop_ros_tf(&vcpd->notif_tf, vcoreid);
+       }
 }
 
-/* Runs whatever thread is vcore's current_uthread */
+/* Runs whatever thread is vcore's current_uthread.  This is nothing but a
+ * couple checks, then the real run_cur_uth. */
 void run_current_uthread(void)
 {
        uint32_t vcoreid = vcore_id();
@@ -338,11 +414,13 @@ void run_current_uthread(void)
        printd("[U] Vcore %d is restarting uthread %08p\n", vcoreid,
               current_uthread);
        /* Run, using the TF in the VCPD.  FP state should already be loaded */
-       __run_cur_uthread(&vcpd->notif_tf);
+       __run_cur_uthread();
        assert(0);
 }
 
-/* Launches the uthread on the vcore.  Don't call this on current_uthread. */
+/* Launches the uthread on the vcore.  Don't call this on current_uthread.  All
+ * this does is set up uthread as cur_uth, check for bugs, and then runs the
+ * real run_cur_uth. */
 void run_uthread(struct uthread *uthread)
 {
        uint32_t vcoreid = vcore_id();
@@ -357,10 +435,7 @@ void run_uthread(struct uthread *uthread)
        uthread->state = UT_RUNNING;
        /* Save a ptr to the uthread we'll run in the transition context's TLS */
        current_uthread = uthread;
-       /* Load silly state (Floating point) too.  For real */
-       /* TODO: (HSS) */
-       uthread->flags &= ~UTHREAD_FPSAVED;     /* uth->as no longer has the latest FP*/
-       __run_cur_uthread(&uthread->utf);
+       __run_cur_uthread();
        assert(0);
 }
 
@@ -671,7 +746,9 @@ out_indirs:
 /* Attempts to register ev_q with sysc, so long as sysc is not done/progress.
  * Returns true if it succeeded, and false otherwise.  False means that the
  * syscall is done, and does not need an event set (and should be handled
- * accordingly)*/
+ * accordingly).
+ * 
+ * A copy of this is in glibc/sysdeps/ros/syscall.c.  Keep them in sync. */
 bool register_evq(struct syscall *sysc, struct event_queue *ev_q)
 {
        int old_flags;