vcore_idle(): halts a core
authorBarret Rhoden <brho@cs.berkeley.edu>
Tue, 2 Dec 2014 22:49:21 +0000 (14:49 -0800)
committerBarret Rhoden <brho@cs.berkeley.edu>
Tue, 2 Dec 2014 22:57:07 +0000 (14:57 -0800)
MCPs can halt their cores, waiting on an interrupt.  An event sent to an
event queue with EVENT_IPI is sufficient to wake a vcore.  Other IRQs or
races can result in wakeups, either from a fresh vcore context or by
having vcore_idle() return.

kern/src/syscall.c
tests/mcp_halt.c [new file with mode: 0644]
user/parlib/vcore.c

index 25f0a0d..7eaf509 100644 (file)
@@ -35,6 +35,7 @@
 #include <arsc_server.h>
 #include <event.h>
 #include <termios.h>
 #include <arsc_server.h>
 #include <event.h>
 #include <termios.h>
+#include <manager.h>
 
 /* Tracing Globals */
 int systrace_flags = 0;
 
 /* Tracing Globals */
 int systrace_flags = 0;
@@ -1091,31 +1092,52 @@ static int sys_vc_entry(struct proc *p)
        return 0;
 }
 
        return 0;
 }
 
-/* This will set a local timer for usec, then shut down the core.  There's a
- * slight race between spinner and halt.  For now, the core will wake up for
- * other interrupts and service them, but will not process routine messages or
- * do anything other than halt until the alarm goes off.  We could just unset
- * the alarm and return early.  On hardware, there are a lot of interrupts that
- * come in.  If we ever use this, we can take a closer look.  */
+/* This will halt the core, waking on an IRQ.  These could be kernel IRQs for
+ * things like timers or devices, or they could be IPIs for RKMs (__notify for
+ * an evq with IPIs for a syscall completion, etc).
+ *
+ * We don't need to finish the syscall early (worried about the syscall struct,
+ * on the vcore's stack).  The syscall will finish before any __preempt RKM
+ * executes, so the vcore will not restart somewhere else before the syscall
+ * completes (unlike with yield, where the syscall itself adjusts the vcore
+ * structures).
+ *
+ * In the future, RKM code might avoid sending IPIs if the core is already in
+ * the kernel.  That code will need to check the CPU's state in some manner, and
+ * send if the core is halted/idle.
+ *
+ * The core must wake up for RKMs, including RKMs that arrive while the kernel
+ * is trying to halt.  The core need not abort the halt for notif_pending for
+ * the vcore, only for a __notify or other RKM.  Anyone setting notif_pending
+ * should then attempt to __notify (o/w it's probably a bug). */
 static int sys_halt_core(struct proc *p, unsigned int usec)
 {
 static int sys_halt_core(struct proc *p, unsigned int usec)
 {
-       struct timer_chain *tchain = &per_cpu_info[core_id()].tchain;
-       struct alarm_waiter a_waiter;
-       bool spinner = TRUE;
-       void unblock(struct alarm_waiter *waiter)
-       {
-               spinner = FALSE;
+       struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
+       struct preempt_data *vcpd;
+       /* The user can only halt CG cores!  (ones it owns) */
+       if (management_core())
+               return -1;
+       disable_irq();
+       /* both for accounting and possible RKM optimizations */
+       __set_cpu_state(pcpui, CPU_STATE_IDLE);
+       wrmb();
+       if (has_routine_kmsg()) {
+               __set_cpu_state(pcpui, CPU_STATE_KERNEL);
+               enable_irq();
+               return 0;
        }
        }
-       init_awaiter(&a_waiter, unblock);
-       set_awaiter_rel(&a_waiter, MAX(usec, 100));
-       set_alarm(tchain, &a_waiter);
-       enable_irq();
-       /* Could wake up due to another interrupt, but we want to sleep still. */
-       while (spinner) {
-               cpu_halt();     /* slight race between spinner and halt */
-               cpu_relax();
+       /* This situation possible, though the check is not necessary.  We can't
+        * assert notif_pending isn't set, since another core may be in the
+        * proc_notify.  Thus we can't tell if this check here caught a bug, or just
+        * aborted early. */
+       vcpd = &p->procdata->vcore_preempt_data[pcpui->owning_vcoreid];
+       if (vcpd->notif_pending) {
+               __set_cpu_state(pcpui, CPU_STATE_KERNEL);
+               enable_irq();
+               return 0;
        }
        }
-       printd("Returning from halting\n");
+       /* CPU_STATE is reset to KERNEL by the IRQ handler that wakes us */
+       cpu_halt();
        return 0;
 }
 
        return 0;
 }
 
diff --git a/tests/mcp_halt.c b/tests/mcp_halt.c
new file mode 100644 (file)
index 0000000..ccbc22d
--- /dev/null
@@ -0,0 +1,65 @@
+#include <parlib.h>
+#include <ros/mman.h>
+#include <ros/resource.h>
+#include <ros/procdata.h>
+#include <ros/event.h>
+#include <ros/bcq.h>
+#include <arch/arch.h>
+#include <stdio.h>
+#include <vcore.h>
+#include <mcs.h>
+#include <timing.h>
+#include <rassert.h>
+#include <event.h>
+#include <uthread.h>
+
+void ghetto_vcore_entry(void);
+
+struct schedule_ops ghetto_sched_ops = {
+       .sched_entry = ghetto_vcore_entry,
+};
+struct schedule_ops *sched_ops = &ghetto_sched_ops;
+
+/* All MCP syscalls will spin instead of blocking */
+static void __ros_syscall_spinon(struct syscall *sysc)
+{
+       while (!(atomic_read(&sysc->flags) & (SC_DONE | SC_PROGRESS)))
+               cpu_relax();
+}
+
+int main(int argc, char** argv)
+{
+       uint32_t vcoreid;
+       int nr_vcores;
+
+       if (argc < 2)
+               nr_vcores = max_vcores();
+       else
+               nr_vcores = atoi(argv[1]);
+
+       /* Inits a thread for us, though we won't use it.  Just a hack to get into
+        * _M mode.  Note this requests one vcore for us */
+       struct uthread dummy = {0};
+       uthread_lib_init(&dummy);
+
+       /* Reset the blockon to be the spinner...  This is really shitty.  Any
+        * blocking calls after we become an MCP and before this will fail.  This is
+        * just mhello showing its warts due to trying to work outside uthread.c */
+       ros_syscall_blockon = __ros_syscall_spinon;
+
+       vcore_request(nr_vcores - 1); /* since we already have 1 */
+
+       while (1)
+               sys_halt_core(0);
+
+       return 0;
+}
+
+void ghetto_vcore_entry(void)
+{
+       if (vcore_id() == 0)
+               run_current_uthread();
+
+       while (1)
+               sys_halt_core(0);
+}
index 8932ac7..7c982cc 100644 (file)
@@ -355,17 +355,42 @@ 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.  This will return if
- * an event was pending (could be the one you were waiting for). */
+ * 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)
 {
        uint32_t vcoreid = vcore_id();
 void vcore_idle(void)
 {
        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;
        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);
        enable_notifs(vcoreid);
-       while (1) {
-               cpu_relax();
-       }
+       /* From now, til we get into the kernel, any notifs will permanently destroy
+        * this context and start the VC from the top.
+        *
+        * 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).
+        *
+        * 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);
 }
 
 /* Helper, that actually makes sure a vcore is running.  Call this is you really
 }
 
 /* Helper, that actually makes sure a vcore is running.  Call this is you really