VMM: Sync halting GPCs and interrupt injection
authorBarret Rhoden <brho@cs.berkeley.edu>
Mon, 20 Jun 2016 20:48:57 +0000 (16:48 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Fri, 24 Jun 2016 18:24:06 +0000 (14:24 -0400)
Previously, halts and mwaits were just immediately returning.  This led to
the guest spinning while waiting on console I/O.  You could see this if you
ran 'ps' while the VM was running.

Now when we send interrupts, we synchronize with the halting guest thread,
such that the guest can halt until it receives an IRQ.  Everyone who wants
to send an IRQ (vector!) to a guest pcore must use vmm_interrupt_guest().

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
tests/vmm/vmrunkernel.c
user/vmm/include/vmm/sched.h
user/vmm/include/vmm/vmm.h
user/vmm/sched.c
user/vmm/vmexit.c
user/vmm/vmm.c [new file with mode: 0644]

index 0a928b8..d00c441 100644 (file)
@@ -154,7 +154,6 @@ int resumeprompt = 0;
 //             vring_new_virtqueue(0, 512, 8192, 0, inpages, NULL, NULL, "test");
 
 void vapic_status_dump(FILE *f, void *vapic);
-static void set_posted_interrupt(int vector);
 
 #if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 1)
 #error "Get a gcc newer than 4.4.0"
@@ -175,10 +174,8 @@ void timer_thread(void *arg)
        while (1) {
                vector = ((uint32_t *)gpci.vapic_addr)[0x32] & 0xff;
                initial_count = ((uint32_t *)gpci.vapic_addr)[0x38];
-               if (vector && initial_count) {
-                       set_posted_interrupt(vector);
-                       ros_syscall(SYS_vmm_poke_guest, 0, 0, 0, 0, 0, 0);
-               }
+               if (vector && initial_count)
+                       vmm_interrupt_guest(vm, 0, vector);
                uthread_usleep(100000);
        }
        fprintf(stderr, "SENDING TIMER\n");
@@ -188,10 +185,10 @@ void timer_thread(void *arg)
 // FIXME.
 volatile int consdata = 0;
 
+/* TODO: pass a core id to poke_guest */
 static void virtio_poke_guest(uint8_t vec)
 {
-       set_posted_interrupt(vec);
-       ros_syscall(SYS_vmm_poke_guest, 0, 0, 0, 0, 0, 0);
+       vmm_interrupt_guest(vm, 0, vec);
 }
 
 static struct virtio_mmio_dev cons_mmio_dev = {
@@ -282,13 +279,6 @@ static void pir_dump()
        fprintf(stderr, "-------End PIR dump-------\n");
 }
 
-static void set_posted_interrupt(int vector)
-{
-       test_and_set_bit(vector, gpci.posted_irq_desc);
-       /* LOCKed instruction provides the mb() */
-       test_and_set_bit(VMX_POSTED_OUTSTANDING_NOTIF, gpci.posted_irq_desc);
-}
-
 int main(int argc, char **argv)
 {
        struct boot_params *bp;
index 483146f..c9a7136 100644 (file)
@@ -30,6 +30,8 @@ struct guest_thread {
        struct uthread                          uthread;
        struct ctlr_thread                      *buddy;
        unsigned int                            gpc_id;
+       uth_mutex_t                                     halt_mtx;
+       uth_cond_var_t                          halt_cv;
 };
 
 struct ctlr_thread {
index fbc12b7..a61d9ad 100644 (file)
@@ -45,6 +45,8 @@ int do_ioapic(struct guest_thread *vm_thread, uint64_t gpa,
 bool handle_vmexit(struct guest_thread *gth);
 int __apic_access(struct guest_thread *vm_thread, uint64_t gpa, int destreg,
                   uint64_t *regp, int store);
+int vmm_interrupt_guest(struct virtual_machine *vm, unsigned int gpcoreid,
+                        unsigned int vector);
 
 /* Lookup helpers */
 
index d8973b8..5ecdf6d 100644 (file)
@@ -419,6 +419,8 @@ static struct guest_thread *create_guest_thread(struct virtual_machine *vm,
        /* TODO: give it a correct FP state.  Our current one is probably fine */
        restore_fp_state(&gth->uthread.as);
        gth->uthread.flags |= UTHREAD_FPSAVED;
+       gth->halt_mtx = uth_mutex_alloc();
+       gth->halt_cv = uth_cond_var_alloc();
        return gth;
 }
 
index cefecdd..173afa6 100644 (file)
@@ -8,10 +8,61 @@
 #include <vmm/virtio_config.h>
 #include <vmm/vmm.h>
 #include <parlib/arch/trap.h>
+#include <parlib/bitmask.h>
 #include <stdio.h>
 
-/* TODO: need infrastructure to handle GPC wakeup properly */
-static bool consdata = FALSE;
+static bool pir_notif_is_set(struct vmm_gpcore_init *gpci)
+{
+       return GET_BITMASK_BIT(gpci->posted_irq_desc, VMX_POSTED_OUTSTANDING_NOTIF);
+}
+
+static bool rvi_is_set(struct guest_thread *gth)
+{
+       uint8_t rvi = gth_to_vmtf(gth)->tf_guest_intr_status & 0xff;
+
+       return rvi != 0;
+}
+
+/* Blocks a guest pcore / thread until it has an IRQ pending.  Syncs with
+ * vmm_interrupt_guest(). */
+static void sleep_til_irq(struct guest_thread *gth)
+{
+       struct vmm_gpcore_init *gpci = gth_to_gpci(gth);
+
+       /* The invariant is that if an IRQ is posted, but not delivered, we will not
+        * sleep.  Anyone who posts an IRQ must signal after setting it.
+        * vmm_interrupt_guest() does this.  If we use alternate sources of IRQ
+        * posting, we'll need to revist this.
+        *
+        * Although vmm_interrupt_guest() only writes OUTSTANDING_NOTIF, it's
+        * possible that the hardware attempted to post the interrupt.  In SDM
+        * parlance, the processor could have "recognized" the virtual IRQ, but not
+        * delivered it yet.  This could happen if the guest had executed "sti", but
+        * not "hlt" yet.  The IRQ was posted and recognized, but not delivered
+        * ("sti blocking").  Then the guest executes "hlt", and vmexits.
+        * OUTSTANDING_NOTIF will be clear in this case.  RVI should be set - at
+        * least to the vector we just sent, but possibly to a greater vector if
+        * multiple were sent.  RVI should only be cleared after virtual IRQs were
+        * actually delivered.  So checking OUTSTANDING_NOTIF and RVI should
+        * suffice.
+        *
+        * Generally, we should also check GUEST_INTERRUPTIBILITY_INFO to see if
+        * there's some reason to not deliver the interrupt and check things like
+        * the VPPR (priority register).  But since we're emulating a halt, mwait,
+        * or something else that needs to be woken by an IRQ, we can ignore that
+        * and just wake them up.  Note that we won't actually deliver the IRQ,
+        * we'll just restart the guest and the hardware will deliver the virtual
+        * IRQ at the appropriate time.  So in the event that something weird
+        * happens, the halt/mwait just returns spuriously.
+        *
+        * The more traditional race here is if the halt starts concurrently with
+        * the post; that's why we sync with the mutex to make sure there is an
+        * ordering between the actual halt (this function) and the posting. */
+       uth_mutex_lock(gth->halt_mtx);
+       while (!(pir_notif_is_set(gpci) || rvi_is_set(gth)))
+               uth_cond_var_wait(gth->halt_cv, gth->halt_mtx);
+       uth_mutex_unlock(gth->halt_mtx);
+}
 
 static bool handle_ept_fault(struct guest_thread *gth)
 {
@@ -116,8 +167,9 @@ static bool handle_halt(struct guest_thread *gth)
 {
        struct vm_trapframe *vm_tf = gth_to_vmtf(gth);
 
-       while (!consdata)
-               ;
+       /* It's possible the guest disabled IRQs and halted, perhaps waiting on an
+        * NMI or something.  If we need to support that, we can change this.  */
+       sleep_til_irq(gth);
        vm_tf->tf_rip += 1;
        return TRUE;
 }
@@ -126,8 +178,10 @@ static bool handle_mwait(struct guest_thread *gth)
 {
        struct vm_trapframe *vm_tf = gth_to_vmtf(gth);
 
-       while (!consdata)
-               ;
+       /* TODO: we need to handle the actual monitor part of mwait.  This just
+        * implements the power management / halting.  Likewise, it's possible IRQs
+        * are disabled (as with halt). */
+       sleep_til_irq(gth);
        vm_tf->tf_rip += 3;
        return TRUE;
 }
diff --git a/user/vmm/vmm.c b/user/vmm/vmm.c
new file mode 100644 (file)
index 0000000..ac83066
--- /dev/null
@@ -0,0 +1,50 @@
+/* Copyright (c) 2016 Google Inc.
+ * Barret Rhoden <brho@cs.berkeley.edu>
+ * See LICENSE for details.
+ *
+ * Helper functions for virtual machines */
+
+#include <vmm/vmm.h>
+#include <errno.h>
+#include <parlib/bitmask.h>
+#include <parlib/uthread.h>
+#include <sys/syscall.h>
+
+/* Sends an interrupt with @vector to guest pcore @gpcoreid.  Returns 0 on
+ * success, -1 with errstr set o/w. */
+int vmm_interrupt_guest(struct virtual_machine *vm, unsigned int gpcoreid,
+                        unsigned int vector)
+{
+       struct guest_thread *gth;
+       struct vmm_gpcore_init *gpci;
+
+       if (gpcoreid >= vm->nr_gpcs) {
+               werrstr("Guest pcoreid %d out of range (%d gpcs)", gpcoreid,
+                       vm->nr_gpcs);
+               return -1;
+       }
+       gth = vm->gths[gpcoreid];
+       gpci = &vm->gpcis[gpcoreid];
+       /* The OUTSTANDING_NOTIF bit (256) is one greater than the last valid
+        * descriptor */
+       if (vector >= VMX_POSTED_OUTSTANDING_NOTIF) {
+               werrstr("Interrupt vector %d too high (max %d)", vector,
+                       VMX_POSTED_OUTSTANDING_NOTIF - 1);
+               return -1;
+       }
+       /* Syncing with halting guest threads.  The Mutex protects changes to the
+        * posted irq descriptor. */
+       uth_mutex_lock(gth->halt_mtx);
+       SET_BITMASK_BIT_ATOMIC(gpci->posted_irq_desc, vector);
+       /* Atomic op provides the mb() btw writing the vector and mucking with
+        * OUTSTANDING_NOTIF.  If the notif was already set, then a previous thread
+        * poked the guest and signaled the CV. */
+       if (!GET_BITMASK_BIT(gpci->posted_irq_desc, VMX_POSTED_OUTSTANDING_NOTIF)) {
+               SET_BITMASK_BIT_ATOMIC(gpci->posted_irq_desc,
+                                      VMX_POSTED_OUTSTANDING_NOTIF);
+               ros_syscall(SYS_vmm_poke_guest, gpcoreid, 0, 0, 0, 0, 0);
+               uth_cond_var_signal(gth->halt_cv);
+       }
+       uth_mutex_unlock(gth->halt_mtx);
+       return 0;
+}