x86: vmm: Track state for handling vmexits as KERNEL
[akaros.git] / kern / arch / x86 / trap.c
index 6bc1079..9f168fa 100644 (file)
@@ -1,13 +1,9 @@
-#ifdef __SHARC__
-#pragma nosharc
-#define SINIT(x) x
-#endif
-
 #include <arch/mmu.h>
 #include <arch/x86.h>
 #include <arch/arch.h>
 #include <arch/console.h>
 #include <arch/apic.h>
+#include <arch/perfmon.h>
 #include <ros/common.h>
 #include <smp.h>
 #include <assert.h>
 #include <syscall.h>
 #include <kdebug.h>
 #include <kmalloc.h>
+#include <ex_table.h>
+#include <arch/mptables.h>
+#include <ros/procinfo.h>
+
+enum {
+       NMI_NORMAL_OPN = 0,
+       NMI_IN_PROGRESS,
+       NMI_HANDLE_ANOTHER,
+};
 
-taskstate_t RO ts;
+taskstate_t ts;
 
 /* Interrupt descriptor table.  64 bit needs 16 byte alignment (i think). */
 gatedesc_t __attribute__((aligned (16))) idt[256] = { { 0 } };
@@ -33,14 +38,23 @@ pseudodesc_t idt_pd;
 struct irq_handler *irq_handlers[NUM_IRQS];
 spinlock_t irq_handler_wlock = SPINLOCK_INITIALIZER_IRQSAVE;
 
-/* Which pci devices hang off of which irqs */
-/* TODO: make this an array of SLISTs (pain from ioapic.c, etc...) */
-struct pci_device *irq_pci_map[NUM_IRQS] = {0};
+static bool try_handle_exception_fixup(struct hw_trapframe *hw_tf)
+{
+       if (in_kernel(hw_tf)) {
+               uintptr_t fixup_ip = get_fixup_ip(hw_tf->tf_rip);
+
+               if (fixup_ip != 0) {
+                       hw_tf->tf_rip = fixup_ip;
+                       return true;
+               }
+       }
+
+       return false;
+}
 
 const char *x86_trapname(int trapno)
 {
-    // zra: excnames is SREADONLY because Ivy doesn't trust const
-       static const char *NT const (RO excnames)[] = {
+       static const char *const excnames[] = {
                "Divide error",
                "Debug",
                "Non-Maskable Interrupt",
@@ -71,9 +85,7 @@ const char *x86_trapname(int trapno)
 }
 
 /* Set stacktop for the current core to be the stack the kernel will start on
- * when trapping/interrupting from userspace.  Don't use this til after
- * smp_percpu_init().  We can probably get the TSS by reading the task register
- * and then the GDT.  Still, it's a pain. */
+ * when trapping/interrupting from userspace. */
 void set_stack_top(uintptr_t stacktop)
 {
        struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
@@ -88,11 +100,7 @@ uintptr_t get_stack_top(void)
 {
        struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
        uintptr_t stacktop;
-       /* so we can check this in interrupt handlers (before smp_boot()) */
-       /* TODO: These are dangerous - it assumes we're on a one-page stack.  If we
-        * change it to KSTKSIZE, then we assume stacks are KSTKSIZE-aligned */
-       if (!pcpui->tss)
-               return ROUNDUP(read_sp(), PGSIZE);
+
        stacktop = x86_get_stacktop_tss(pcpui->tss);
        if (stacktop != ROUNDUP(read_sp(), PGSIZE))
                panic("Bad stacktop: %p esp one is %p\n", stacktop,
@@ -138,17 +146,21 @@ void idt_init(void)
                (uintptr_t)idt[T_SYSCALL].gd_off_15_0));
        /* turn on trap-based syscall handling and other user-accessible ints
         * DPL 3 means this can be triggered by the int instruction */
-       idt[T_SYSCALL].gd_dpl = SINIT(3);
-       idt[T_BRKPT].gd_dpl = SINIT(3);
+       idt[T_SYSCALL].gd_dpl = 3;
+       idt[T_BRKPT].gd_dpl = 3;
+       /* Send NMIs to their own stack (IST1 in every core's TSS) */
+       idt[T_NMI].gd_ist = 1;
+       /* Send double faults to their own stack (IST2 in every core's TSS) */
+       idt[T_DBLFLT].gd_ist = 2;
+
+       /* The sooner we set this, the sooner we can use set/get_stack_top. */
+       per_cpu_info[0].tss = &ts;
+       per_cpu_info[0].gdt = gdt;
 
        /* Set up our kernel stack when changing rings */
        /* Note: we want 16 byte aligned kernel stack frames (AMD 2:8.9.3) */
-       x86_set_stacktop_tss(&ts, (uintptr_t)bootstacktop);
-       x86_sysenter_init((uintptr_t)bootstacktop);
-
-#ifdef CONFIG_KTHREAD_POISON
-       *kstack_bottom_addr((uintptr_t)bootstacktop) = 0xdeadbeef;
-#endif /* CONFIG_KTHREAD_POISON */
+       x86_sysenter_init();
+       set_stack_top((uintptr_t)bootstacktop);
 
        /* Initialize the TSS field of the gdt.  The size of the TSS desc differs
         * between 64 and 32 bit, hence the pointer acrobatics */
@@ -168,32 +180,31 @@ void idt_init(void)
        pic_remap();
        pic_mask_all();
 
-#ifdef CONFIG_ENABLE_MPTABLES
-       int ncleft;
-       int mpsinit(int maxcores);
+       int ncleft = MAX_NUM_CORES;
+       int num_cores_mpacpi;
 
-       ncleft = mpsinit(MAX_NUM_CPUS);
-       /* NEVER printd here ... */
-       printk("mpacpi is %d\n", mpacpi(ncleft));
+       ncleft = mpsinit(ncleft);
+       ncleft = mpacpi(ncleft);
+       num_cores_mpacpi = MAX_NUM_CORES - ncleft;
+       printk("MP and ACPI found %d cores\n", num_cores_mpacpi);
+       if (num_cores != num_cores_mpacpi)
+               warn("Topology (%d) and MP/ACPI (%d) differ on num_cores!", num_cores,
+                    num_cores_mpacpi);
 
-       void ioapiconline(void);
-       void apiconline(void);
-       apiconline(); /* TODO: do this this for all cores*/
+       apiconline();
        ioapiconline();
-#else
-       // set LINT0 to receive ExtINTs (KVM's default).  At reset they are 0x1000.
-       write_mmreg32(LAPIC_LVT_LINT0, 0x700);
-       lapic_enable();
-       unmask_lapic_lvt(LAPIC_LVT_LINT0);
-#endif
 
-       /* register the generic timer_interrupt() handler for the per-core timers */
-       register_raw_irq(IdtLAPIC_TIMER, timer_interrupt, NULL);
-       /* register the kernel message handler */
-       register_raw_irq(I_KERNEL_MSG, handle_kmsg_ipi, NULL);
+       /* the lapic IRQs need to be unmasked on a per-core basis */
+       register_irq(IdtLAPIC_TIMER, timer_interrupt, NULL,
+                    MKBUS(BusLAPIC, 0, 0, 0));
+       register_irq(IdtLAPIC_ERROR, handle_lapic_error, NULL,
+                    MKBUS(BusLAPIC, 0, 0, 0));
+       register_irq(IdtLAPIC_PCINT, perfmon_interrupt, NULL,
+                    MKBUS(BusLAPIC, 0, 0, 0));
+       register_irq(I_KERNEL_MSG, handle_kmsg_ipi, NULL, MKBUS(BusIPI, 0, 0, 0));
 }
 
-static void handle_fperr(struct hw_trapframe *hw_tf)
+static void print_fperr(struct hw_trapframe *hw_tf)
 {
        uint16_t fpcw, fpsw;
        uint32_t mxcsr;
@@ -225,102 +236,333 @@ static void handle_fperr(struct hw_trapframe *hw_tf)
                printk("\tNumeric Underflow\n");
        if (fpsw & ~fpcw & FP_EXCP_PE)
                printk("\tInexact result (precision)\n");
-       printk("Killing the process.\n");
-       enable_irq();
-       proc_destroy(current);
 }
 
-void backtrace_kframe(struct hw_trapframe *hw_tf)
+static bool __handler_user_page_fault(struct hw_trapframe *hw_tf,
+                                      uintptr_t fault_va, int prot)
 {
        struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
-       pcpui->__lock_checking_enabled--;
-       printk("\nBacktrace of faulting kernel context on Core %d:\n", core_id());
-       backtrace_frame(x86_get_hwtf_pc(hw_tf), x86_get_hwtf_fp(hw_tf));
-       pcpui->__lock_checking_enabled++;
+       int err;
+
+       assert(pcpui->owning_proc == pcpui->cur_proc);
+       enable_irq();
+       err = handle_page_fault(pcpui->owning_proc, fault_va, prot);
+       disable_irq();
+       if (err) {
+               if (err == -EAGAIN)
+                       hw_tf->tf_err |= PF_VMR_BACKED;
+               return FALSE;
+       }
+       return TRUE;
 }
 
-static bool __handle_page_fault(struct hw_trapframe *hw_tf, unsigned long *aux)
+static bool __handler_kernel_page_fault(struct hw_trapframe *hw_tf,
+                                        uintptr_t fault_va, int prot)
 {
-       uintptr_t fault_va = rcr2();
-       int prot = hw_tf->tf_err & PF_ERROR_WRITE ? PROT_WRITE : PROT_READ;
+       struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
        int err;
 
-       /* TODO - handle kernel page faults */
-       if ((hw_tf->tf_cs & 3) == 0) {
+       /* The only thing an NMI handler that faults can do is a fixup */
+       if (pcpui->nmi_status != NMI_NORMAL_OPN) {
+               assert(in_kernel(hw_tf));
+               return try_handle_exception_fixup(hw_tf);
+       }
+       /* In general, if there's no cur_proc, a KPF is a bug. */
+       if (!pcpui->cur_proc) {
+               /* This only runs from test_uaccess(), where it is expected to fail. */
+               if (try_handle_exception_fixup(hw_tf))
+                       return TRUE;
                print_trapframe(hw_tf);
-               backtrace_kframe(hw_tf);
-               panic("Page Fault in the Kernel at %p!", fault_va);
-               /* if we want to do something like kill a process or other code, be
-                * aware we are in a sort of irq-like context, meaning the main kernel
-                * code we 'interrupted' could be holding locks - even irqsave locks. */
+               backtrace_hwtf(hw_tf);
+               panic("Proc-less Page Fault in the Kernel at %p!", fault_va);
        }
-       /* safe to reenable after rcr2 */
-       enable_irq();
-       if ((err = handle_page_fault(current, fault_va, prot))) {
-               if (err == -EAGAIN)
-                       hw_tf->tf_err |= PF_VMR_BACKED;
-               *aux = fault_va;
-               return FALSE;
-               /* useful debugging */
-               printk("[%08x] user %s fault va %p ip %p on core %d with err %d\n",
-                      current->pid, prot & PROT_READ ? "READ" : "WRITE", fault_va,
-                      hw_tf->tf_rip, core_id(), err);
+       /* TODO - handle kernel page faults.  This is dangerous, since we might be
+        * holding locks in the kernel and could deadlock when we HPF.  For now, I'm
+        * just disabling the lock checker, since it'll flip out when it sees there
+        * is a kernel trap.  Will need to think about this a bit, esp when we
+        * properly handle bad addrs and whatnot. */
+       pcpui->__lock_checking_enabled--;
+       /* It is a bug for the kernel to access user memory while holding locks that
+        * are used by handle_page_fault.  At a minimum, this includes p->vmr_lock
+        * and memory allocation locks.
+        *
+        * In an effort to reduce the number of locks (both now and in the future),
+        * the kernel will not attempt to handle faults on file-back VMRs.  We
+        * probably can turn that on in the future, but I'd rather keep things safe
+        * for now.  (We'll probably need to change this when we stop
+        * MAP_POPULATE | MAP_LOCKED entire binaries).
+        *
+        * Note that we do not enable IRQs here, unlike in the user case.  Again,
+        * this is to limit the locks we could be grabbing. */
+       err = handle_page_fault_nofile(pcpui->cur_proc, fault_va, prot);
+       pcpui->__lock_checking_enabled++;
+       if (err) {
+               if (try_handle_exception_fixup(hw_tf))
+                       return TRUE;
                print_trapframe(hw_tf);
+               backtrace_hwtf(hw_tf);
                /* Turn this on to help debug bad function pointers */
-#ifdef CONFIG_X86_64
                printd("rsp %p\n\t 0(rsp): %p\n\t 8(rsp): %p\n\t 16(rsp): %p\n"
                       "\t24(rsp): %p\n", hw_tf->tf_rsp,
                       *(uintptr_t*)(hw_tf->tf_rsp +  0),
                       *(uintptr_t*)(hw_tf->tf_rsp +  8),
                       *(uintptr_t*)(hw_tf->tf_rsp + 16),
                       *(uintptr_t*)(hw_tf->tf_rsp + 24));
-#else
-               printd("esp %p\n\t 0(esp): %p\n\t 4(esp): %p\n\t 8(esp): %p\n"
-                      "\t12(esp): %p\n", hw_tf->tf_esp,
-                      *(uintptr_t*)(hw_tf->tf_esp +  0),
-                      *(uintptr_t*)(hw_tf->tf_esp +  4),
-                      *(uintptr_t*)(hw_tf->tf_esp +  8),
-                      *(uintptr_t*)(hw_tf->tf_esp + 12));
-#endif
+               panic("Proc-ful Page Fault in the Kernel at %p!", fault_va);
+               /* if we want to do something like kill a process or other code, be
+                * aware we are in a sort of irq-like context, meaning the main
+                * kernel code we 'interrupted' could be holding locks - even
+                * irqsave locks. */
        }
        return TRUE;
 }
 
+static bool __handle_page_fault(struct hw_trapframe *hw_tf, unsigned long *aux)
+{
+       uintptr_t fault_va = rcr2();
+       int prot = hw_tf->tf_err & PF_ERROR_WRITE ? PROT_WRITE : PROT_READ;
+
+       *aux = fault_va;
+       if (in_kernel(hw_tf))
+               return __handler_kernel_page_fault(hw_tf, fault_va, prot);
+       else
+               return __handler_user_page_fault(hw_tf, fault_va, prot);
+}
+
+/* Actual body of work done when an NMI arrives */
+static void do_nmi_work(struct hw_trapframe *hw_tf)
+{
+       assert(!irq_is_enabled());
+       /* It's mostly harmless to snapshot the TF, and we can send a spurious PCINT
+        * interrupt.  perfmon.c just uses the interrupt to tell it to check its
+        * counters for overflow.  Note that the PCINT interrupt is just a regular
+        * IRQ.  The backtrace was recorded during the NMI and emitted during IRQ.
+        *
+        * That being said, it's OK if the monitor triggers debugging NMIs while
+        * perf is running.  If perf triggers an NMI when the monitor wants to
+        * print, the monitor will debug *that* NMI, and not the one that gets sent
+        * moments later.  That's fine. */
+       emit_monitor_backtrace(ROS_HW_CTX, hw_tf);
+       perfmon_snapshot_hwtf(hw_tf);
+       send_self_ipi(IdtLAPIC_PCINT);
+}
+
+/* NMI HW_TF hacking involves four symbols:
+ *
+ * [__nmi_pop_ok_start, __nmi_pop_ok_end) mark the beginning and end of the
+ * code for an nmi popping routine that will actually pop at the end.
+ *
+ * [__nmi_pop_fail_start, __nmi_pop_fail_end) mark the beginning and end of the
+ * shadow code for an nmi popping routine that will fail at the end.
+ *
+ * If we see a TF in the OK section, we'll move it to the FAIL section.  If it's
+ * already in the FAIL section, we'll report that as a success. */
+extern char __nmi_pop_ok_start[], __nmi_pop_ok_end[];
+extern char __nmi_pop_fail_start[], __nmi_pop_fail_end[];
+
+static bool nmi_hw_tf_needs_hacked(struct hw_trapframe *hw_tf)
+{
+       return ((uintptr_t)__nmi_pop_ok_start <= hw_tf->tf_rip) &&
+              (hw_tf->tf_rip < (uintptr_t)__nmi_pop_ok_end);
+}
+
+static bool nmi_hw_tf_was_hacked(struct hw_trapframe *hw_tf)
+{
+       return ((uintptr_t)__nmi_pop_fail_start <= hw_tf->tf_rip) &&
+              (hw_tf->tf_rip < (uintptr_t)__nmi_pop_fail_end);
+}
+
+/* Helper.  Hacks the TF if it was in the OK section so that it is at the same
+ * spot in the FAIL section.  Returns TRUE if the TF is hacked, meaning the NMI
+ * handler can just return. */
+static bool nmi_check_and_hack_tf(struct hw_trapframe *hw_tf)
+{
+       uintptr_t offset;
+
+       if (!nmi_hw_tf_needs_hacked(hw_tf))
+               return FALSE;
+       if (nmi_hw_tf_was_hacked(hw_tf))
+               return TRUE;
+       offset = hw_tf->tf_rip - (uintptr_t)__nmi_pop_ok_start;
+       hw_tf->tf_rip = (uintptr_t)__nmi_pop_fail_start + offset;
+       return TRUE;
+}
+
+/* Bottom half of the NMI handler.  This can be interrupted under some
+ * circumstances by NMIs.  It exits by popping the hw_tf in assembly. */
+void __attribute__((noinline, noreturn))
+__nmi_bottom_half(struct hw_trapframe *hw_tf)
+{
+       struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
+
+       while (1) {
+               /* Signal that we're doing work.  A concurrent NMI will set this to
+                * NMI_HANDLE_ANOTHER if we should continue, which we'll catch later. */
+               pcpui->nmi_status = NMI_IN_PROGRESS;
+               do_nmi_work(hw_tf);
+               /* We need to check nmi_status to see if it is NMI_HANDLE_ANOTHER (if
+                * so, run again), write NMI_NORMAL_OPN, leave this stack, and return to
+                * the original context.  We need to do that in such a manner that an
+                * NMI can come in at any time.  There are two concerns.
+                *
+                * First, we need to not "miss the signal" telling us to re-run the NMI
+                * handler.  To do that, we'll do the actual checking in asm.  Being in
+                * the asm code block is a signal to the real NMI handler that we need
+                * to abort and do_nmi_work() again.
+                *
+                * Second, we need to atomically leave the stack and return.  By being
+                * in asm, the NMI handler knows to just hack our PC to make us return,
+                * instead of starting up a fresh __nmi_bottom_half().
+                *
+                * The NMI handler works together with the following function such that
+                * if that race occurs while we're in the function, it'll fail and
+                * return.  Then we'll just do_nmi_work() and try again. */
+               extern void nmi_try_to_pop(struct hw_trapframe *tf, int *status,
+                                          int old_val, int new_val);
+
+               nmi_try_to_pop(hw_tf, &pcpui->nmi_status, NMI_IN_PROGRESS,
+                              NMI_NORMAL_OPN);
+               /* Either we returned on our own, since we lost a race with nmi_status
+                * and didn't write (status = ANOTHER), or we won the race, but an NMI
+                * handler set the status to ANOTHER and restarted us. */
+               assert(pcpui->nmi_status != NMI_NORMAL_OPN);
+       }
+}
+
+/* Separate handler from traps, since there's too many rules for NMI ctx.
+ *
+ * The general rule is that any writes from NMI context must be very careful.
+ * When talking about reads and writes to per-core data:
+ * - If NMIs write things written by normal kernel contexts, including IRQs and
+ *   traps with IRQs disabled, then you must use atomics on both sides.
+ * - If NMIs write things read by normal contexts, then readers must be careful,
+ *   since the data can change at will.
+ * - If NMIs read things written by normal contexts, don't worry: you're running
+ *   uninterrupted (given x86 NMI caveats).
+ * - We cannot block.  The current kthread thinks its stacktop is different than
+ *   the one we're on.  Just get in and get out.
+ * - If we interrupted a user TF, then we don't need to worry any more than for
+ *   normal traps/IRQs.
+ * - However, we cannot call proc_restartcore.  That could trigger all sorts of
+ *   things, like kthreads blocking.
+ * - Parallel accesses (from other cores) are the same as always.  You just
+ *   can't lock easily.
+ *
+ * Normally, once you're in NMI, other NMIs are blocked until we return.
+ * However, if our NMI handler faults (PF, GPF, breakpoint) due to something
+ * like tracing, the iret from that fault will cancel our NMI protections.  Thus
+ * we need another layer of code to make sure we don't run the NMI handler
+ * concurrently on the same core.  See https://lwn.net/Articles/484932/ for more
+ * info.
+ *
+ * We'll get around the problem by running on yet another NMI stack.  All NMIs
+ * come in on the nmi entry stack (tss->ist1).  While we're on that stack, we
+ * will not be interrupted.  We jump to another stack to do_nmi_work.  That code
+ * can be interrupted, but we are careful to only have one 'thread' running on
+ * that stack at a time.  We do this by carefully hopping off the stack in
+ * assembly, similar to popping user TFs. */
+void handle_nmi(struct hw_trapframe *hw_tf)
+{
+       struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
+       struct hw_trapframe *hw_tf_copy;
+       uintptr_t worker_stacktop;
+
+       /* At this point, we're an NMI and other NMIs are blocked.  Only once we
+        * hop to the bottom half could that be no longer true.  NMI with NMIs fully
+        * blocked will run without interruption.  For that reason, we don't have to
+        * be careful about any memory accesses or compiler tricks. */
+       if (pcpui->nmi_status == NMI_HANDLE_ANOTHER)
+               return;
+       if (pcpui->nmi_status == NMI_IN_PROGRESS) {
+               /* Force the handler to run again.  We don't need to worry about
+                * concurrent access here.  We're running, they are not.  We cannot
+                * 'PAUSE' since NMIs are fully blocked.
+                *
+                * The asm routine, for its part, does a compare-and-swap, so if we
+                * happened to interrupt it before it wrote NMI_NORMAL_OPN, it'll
+                * notice, abort, and not write the status. */
+               pcpui->nmi_status = NMI_HANDLE_ANOTHER;
+               return;
+       }
+       assert(pcpui->nmi_status == NMI_NORMAL_OPN);
+       pcpui->nmi_status = NMI_HANDLE_ANOTHER;
+       /* We could be interrupting an NMI that is trying to pop back to a normal
+        * context.  We can tell by looking at its PC.  If it is within the popping
+        * routine, then we interrupted it at this bad time.  We'll hack the TF such
+        * that it will return instead of succeeding. */
+       if (nmi_check_and_hack_tf(hw_tf))
+               return;
+       /* OK, so we didn't interrupt an NMI that was trying to return.  So we need
+        * to run the bottom half.  We're going to jump stacks, but we also need to
+        * copy the hw_tf.  The existing one will be clobbered by any interrupting
+        * NMIs.
+        *
+        * We also need to save some space on the top of that stack for a pointer to
+        * pcpui and a scratch register, which nmi_try_to_pop() will use.  The
+        * target stack will look like this:
+        *
+        *               +--------------------------+ Page boundary (e.g. 0x6000)
+        *               |   scratch space (rsp)    |
+        *               |       pcpui pointer      |
+        *               |      tf_ss + padding     | HW_TF end
+        *               |          tf_rsp          |
+        *               |            .             |
+        *               |            .             |
+        * RSP ->        |         tf_gsbase        | HW_TF start, hw_tf_copy
+        *               +--------------------------+
+        *               |            .             |
+        *               |            .             |
+        *               |            .             |
+        *               +--------------------------+ Page boundary (e.g. 0x5000)
+        *
+        * __nmi_bottom_half() just picks up using the stack below tf_gsbase.  It'll
+        * push as needed, growing down.  Basically we're just using the space
+        * 'above' the stack as storage. */
+       worker_stacktop = pcpui->nmi_worker_stacktop - 2 * sizeof(uintptr_t);
+       *(uintptr_t*)worker_stacktop = (uintptr_t)pcpui;
+       worker_stacktop = worker_stacktop - sizeof(struct hw_trapframe);
+       hw_tf_copy = (struct hw_trapframe*)worker_stacktop;
+       *hw_tf_copy = *hw_tf;
+       /* Once we head to the bottom half, consider ourselves interruptible (though
+        * it's not until the first time we do_nmi_work()).  We'll never come back
+        * to this stack.  Doing this in asm so we can easily pass an argument.  We
+        * don't need to call (vs jmp), but it helps keep the stack aligned. */
+       asm volatile("mov $0x0, %%rbp;"
+                    "mov %0, %%rsp;"
+                    "call __nmi_bottom_half;"
+                    : : "r"(worker_stacktop), "D"(hw_tf_copy));
+       assert(0);
+}
+
+void handle_double_fault(struct hw_trapframe *hw_tf)
+{
+       print_trapframe(hw_tf);
+       backtrace_hwtf(hw_tf);
+       panic("Double fault!  Check the kernel stack pointer; you likely ran off the end of the stack.");
+}
+
 /* Certain traps want IRQs enabled, such as the syscall.  Others can't handle
  * it, like the page fault handler.  Turn them on on a case-by-case basis. */
 static void trap_dispatch(struct hw_trapframe *hw_tf)
 {
        struct per_cpu_info *pcpui;
-       bool handled = TRUE;
+       bool handled = FALSE;
        unsigned long aux = 0;
+       uintptr_t fixup_ip;
+
        // Handle processor exceptions.
        switch(hw_tf->tf_trapno) {
-               case T_NMI:
-                       /* Temporarily disable deadlock detection when we print.  We could
-                        * deadlock if we were printing when we NMIed. */
-                       pcpui = &per_cpu_info[core_id()];
-                       pcpui->__lock_checking_enabled--;
-                       /* This is a bit hacky, but we don't have a decent API yet */
-                       extern bool mon_verbose_trace;
-                       if (mon_verbose_trace) {
-                               print_trapframe(hw_tf);
-                               backtrace_kframe(hw_tf);
-                       }
-                       char *fn_name = get_fn_name(x86_get_ip_hw(hw_tf));
-                       printk("Core %d is at %p (%s)\n", core_id(), x86_get_ip_hw(hw_tf),
-                              fn_name);
-                       kfree(fn_name);
-                       print_kmsgs(core_id());
-                       pcpui->__lock_checking_enabled++;
-                       break;
                case T_BRKPT:
-                       enable_irq();
-                       monitor(hw_tf);
+                       if (!in_kernel(hw_tf))
+                               backtrace_user_ctx(current, current_ctx);
+                       else
+                               monitor(hw_tf);
+                       handled = TRUE;
                        break;
                case T_ILLOP:
                {
                        /* TODO: this can PF if there is a concurrent unmap/PM removal. */
-                       uintptr_t ip = x86_get_ip_hw(hw_tf);
+                       uintptr_t ip = get_hwtf_pc(hw_tf);
                        pcpui = &per_cpu_info[core_id()];
                        pcpui->__lock_checking_enabled--;               /* for print debugging */
                        /* We will muck with the actual TF.  If we're dealing with
@@ -329,28 +571,26 @@ static void trap_dispatch(struct hw_trapframe *hw_tf)
                         * the same).  See set_current_ctx() for more info. */
                        if (!in_kernel(hw_tf))
                                hw_tf = &pcpui->cur_ctx->tf.hw_tf;
-                       printd("bad opcode, eip: %p, next 3 bytes: %x %x %x\n", ip, 
-                              *(uint8_t*)(ip + 0), 
-                              *(uint8_t*)(ip + 1), 
-                              *(uint8_t*)(ip + 2)); 
+                       printd("bad opcode, eip: %p, next 3 bytes: %x %x %x\n", ip,
+                              *(uint8_t*)(ip + 0),
+                              *(uint8_t*)(ip + 1),
+                              *(uint8_t*)(ip + 2));
                        /* rdtscp: 0f 01 f9 */
-                       if (*(uint8_t*)(ip + 0) == 0x0f, 
-                           *(uint8_t*)(ip + 1) == 0x01, 
+                       if (*(uint8_t*)(ip + 0) == 0x0f,
+                           *(uint8_t*)(ip + 1) == 0x01,
                            *(uint8_t*)(ip + 2) == 0xf9) {
                                x86_fake_rdtscp(hw_tf);
-                               pcpui->__lock_checking_enabled++;       /* for print debugging */
-                               return;
+                               handled = TRUE;
                        }
-                       enable_irq();
-                       monitor(hw_tf);
                        pcpui->__lock_checking_enabled++;               /* for print debugging */
                        break;
                }
                case T_PGFLT:
                        handled = __handle_page_fault(hw_tf, &aux);
                        break;
+               case T_GPFLT:
                case T_FPERR:
-                       handle_fperr(hw_tf);
+                       handled = try_handle_exception_fixup(hw_tf);
                        break;
                case T_SYSCALL:
                        enable_irq();
@@ -361,17 +601,18 @@ static void trap_dispatch(struct hw_trapframe *hw_tf)
                        prep_syscalls(current,
                                      (struct syscall*)x86_get_systrap_arg0(hw_tf),
                                                  (unsigned int)x86_get_systrap_arg1(hw_tf));
+                       disable_irq();
+                       handled = TRUE;
                        break;
-               default:
-                       if (hw_tf->tf_cs == GD_KT) {
-                               print_trapframe(hw_tf);
-                               panic("Damn Damn!  Unhandled trap in the kernel!");
-                       } else {
-                               handled = FALSE;
-                       }
        }
-       if (!handled)
+
+       if (!handled) {
+               if (in_kernel(hw_tf)) {
+                       print_trapframe(hw_tf);
+                       panic("Damn Damn!  Unhandled trap in the kernel!");
+               }
                reflect_unhandled_trap(hw_tf->tf_trapno, hw_tf->tf_err, aux);
+       }
 }
 
 /* Helper.  For now, this copies out the TF to pcpui.  Eventually, we should
@@ -390,7 +631,6 @@ static void set_current_ctx_hw(struct per_cpu_info *pcpui,
                                struct hw_trapframe *hw_tf)
 {
        assert(!irq_is_enabled());
-       assert(!pcpui->cur_ctx);
        pcpui->actual_ctx.type = ROS_HW_CTX;
        pcpui->actual_ctx.tf.hw_tf = *hw_tf;
        pcpui->cur_ctx = &pcpui->actual_ctx;
@@ -400,39 +640,31 @@ static void set_current_ctx_sw(struct per_cpu_info *pcpui,
                                struct sw_trapframe *sw_tf)
 {
        assert(!irq_is_enabled());
-       assert(!pcpui->cur_ctx);
        pcpui->actual_ctx.type = ROS_SW_CTX;
        pcpui->actual_ctx.tf.sw_tf = *sw_tf;
        pcpui->cur_ctx = &pcpui->actual_ctx;
 }
 
-/* If the interrupt interrupted a halt, we advance past it.  Made to work with
- * x86's custom cpu_halt() in arch/arch.h.  Note this nearly never gets called.
- * I needed to insert exactly one 'nop' in cpu_halt() (that isn't there now) to
- * get the interrupt to trip on the hlt, o/w the hlt will execute before the
- * interrupt arrives (even with a pending interrupt that should hit right after
- * an interrupt_enable (sti)).  This was on the i7. */
-static void abort_halt(struct hw_trapframe *hw_tf)
+static void set_current_ctx_vm(struct per_cpu_info *pcpui,
+                               struct vm_trapframe *vm_tf)
 {
-       /* Don't care about user TFs.  Incidentally, dereferencing user EIPs is
-        * reading userspace memory, which can be dangerous.  It can page fault,
-        * like immediately after a fork (which doesn't populate the pages). */
-       if (!in_kernel(hw_tf))
-               return;
-       /* the halt instruction in is 0xf4, and it's size is 1 byte */
-       if (*(uint8_t*)x86_get_ip_hw(hw_tf) == 0xf4)
-               x86_advance_ip(hw_tf, 1);
+       assert(!irq_is_enabled());
+       pcpui->actual_ctx.type = ROS_VM_CTX;
+       pcpui->actual_ctx.tf.vm_tf = *vm_tf;
+       pcpui->cur_ctx = &pcpui->actual_ctx;
 }
 
 void trap(struct hw_trapframe *hw_tf)
 {
        struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
        /* Copy out the TF for now */
-       if (!in_kernel(hw_tf))
+       if (!in_kernel(hw_tf)) {
                set_current_ctx_hw(pcpui, hw_tf);
-       else
+               /* ignoring state for nested kernel traps.  should be rare. */
+               __set_cpu_state(pcpui, CPU_STATE_KERNEL);
+       } else {
                inc_ktrap_depth(pcpui);
-
+       }
        printd("Incoming TRAP %d on core %d, TF at %p\n", hw_tf->tf_trapno,
               core_id(), hw_tf);
        if ((hw_tf->tf_cs & ~3) != GD_UT && (hw_tf->tf_cs & ~3) != GD_KT) {
@@ -451,35 +683,42 @@ void trap(struct hw_trapframe *hw_tf)
        assert(0);
 }
 
-/* Note IRQs are disabled unless explicitly turned on.
- *
- * In general, we should only get trapno's >= PIC1_OFFSET (32).  Anything else
- * should be a trap.  Even if we don't use the PIC, that should be the standard.
- * It is possible to get a spurious LAPIC IRQ with vector 15 (or similar), but
- * the spurious check should catch that.
- *
- * Note that from hardware's perspective (PIC, etc), IRQs start from 0, but they
- * are all mapped up at PIC1_OFFSET for the cpu / irq_handler. */
-void handle_irq(struct hw_trapframe *hw_tf)
+static bool vector_is_irq(int apic_vec)
+{
+       /* arguably, we could limit them to MaxIdtIOAPIC */
+       return (IdtPIC <= apic_vec) && (apic_vec <= IdtMAX);
+}
+
+static void irq_dispatch(struct hw_trapframe *hw_tf)
 {
        struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
        struct irq_handler *irq_h;
-       /* Copy out the TF for now */
-       if (!in_kernel(hw_tf))
-               set_current_ctx_hw(pcpui, hw_tf);
+
+       if (!in_irq_ctx(pcpui))
+               __set_cpu_state(pcpui, CPU_STATE_IRQ);
        inc_irq_depth(pcpui);
-       /* Coupled with cpu_halt() and smp_idle() */
-       abort_halt(hw_tf);
        //if (core_id())
        if (hw_tf->tf_trapno != IdtLAPIC_TIMER) /* timer irq */
-       if (hw_tf->tf_trapno != 255) /* kmsg */
-       if (hw_tf->tf_trapno != 36)     /* serial */
+       if (hw_tf->tf_trapno != I_KERNEL_MSG)
+       if (hw_tf->tf_trapno != 65)     /* qemu serial tends to get this one */
                printd("Incoming IRQ, ISR: %d on core %d\n", hw_tf->tf_trapno,
                       core_id());
        /* TODO: RCU read lock */
        irq_h = irq_handlers[hw_tf->tf_trapno];
-       if (!irq_h || irq_h->check_spurious(hw_tf->tf_trapno))
+       if (!irq_h) {
+               warn_once("Received IRQ %d, had no handler registered!",
+                         hw_tf->tf_trapno);
+               /* If we don't have an IRQ handler, we don't know how to EOI.  Odds are,
+                * it's a LAPIC IRQ, such as I_TESTING */
+               if (!lapic_check_spurious(hw_tf->tf_trapno))
+                       lapic_send_eoi(hw_tf->tf_trapno);
+               goto out_no_eoi;
+       }
+       if (irq_h->check_spurious(hw_tf->tf_trapno))
                goto out_no_eoi;
+       /* Can now be interrupted/nested by higher priority IRQs, but not by our
+        * current IRQ vector, til we EOI. */
+       enable_irq();
        while (irq_h) {
                irq_h->isr(hw_tf, irq_h->data);
                irq_h = irq_h->next;
@@ -489,11 +728,33 @@ void handle_irq(struct hw_trapframe *hw_tf)
        if ((I_SMP_CALL0 <= hw_tf->tf_trapno) &&
            (hw_tf->tf_trapno <= I_SMP_CALL_LAST))
                down_checklist(handler_wrappers[hw_tf->tf_trapno & 0x0f].cpu_list);
+       disable_irq();
        /* Keep in sync with ipi_is_pending */
        irq_handlers[hw_tf->tf_trapno]->eoi(hw_tf->tf_trapno);
        /* Fall-through */
 out_no_eoi:
        dec_irq_depth(pcpui);
+       if (!in_irq_ctx(pcpui))
+               __set_cpu_state(pcpui, CPU_STATE_KERNEL);
+}
+
+/* Note IRQs are disabled unless explicitly turned on.
+ *
+ * In general, we should only get trapno's >= PIC1_OFFSET (32).  Anything else
+ * should be a trap.  Even if we don't use the PIC, that should be the standard.
+ * It is possible to get a spurious LAPIC IRQ with vector 15 (or similar), but
+ * the spurious check should catch that.
+ *
+ * Note that from hardware's perspective (PIC, etc), IRQs start from 0, but they
+ * are all mapped up at PIC1_OFFSET for the cpu / irq_handler. */
+void handle_irq(struct hw_trapframe *hw_tf)
+{
+       struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
+
+       /* Copy out the TF for now */
+       if (!in_kernel(hw_tf))
+               set_current_ctx_hw(pcpui, hw_tf);
+       irq_dispatch(hw_tf);
        /* Return to the current process, which should be runnable.  If we're the
         * kernel, we should just return naturally.  Note that current and tf need
         * to still be okay (might not be after blocking) */
@@ -503,123 +764,434 @@ out_no_eoi:
        assert(0);
 }
 
-/* Tells us if an interrupt (trap_nr) came from the PIC or not */
-static bool irq_from_pic(uint32_t trap_nr)
-{
-       /* The 16 IRQs within the range [PIC1_OFFSET, PIC1_OFFSET + 15] came from
-        * the PIC.  [32-47] */
-       if (trap_nr < PIC1_OFFSET)
-               return FALSE;
-       if (trap_nr > PIC1_OFFSET + 15)
-               return FALSE;
-       return TRUE;
-}
-
-/* TODO: remove the distinction btw raw and device IRQs */
-void register_raw_irq(unsigned int vector, isr_t handler, void *data)
+/* The irq field may be ignored based on the type of Bus. */
+int register_irq(int irq, isr_t handler, void *irq_arg, uint32_t tbdf)
 {
        struct irq_handler *irq_h;
-       irq_h = kmalloc(sizeof(struct irq_handler), 0);
+       int vector;
+       irq_h = kzmalloc(sizeof(struct irq_handler), 0);
        assert(irq_h);
-       spin_lock_irqsave(&irq_handler_wlock);
-       irq_h->isr = handler;
-       irq_h->data = data;
-       /* TODO: better way to sort out LAPIC vs PIC */
-       if (irq_from_pic(vector)) {
-               irq_h->check_spurious = pic_check_spurious;
-               irq_h->eoi = pic_send_eoi;
-               irq_h->mask = pic_mask_irq;
-               irq_h->unmask = pic_unmask_irq;
-               irq_h->type = "pic";
-       } else {
-               irq_h->check_spurious = lapic_check_spurious;
-               irq_h->eoi = lapic_send_eoi;
-               /* TODO: which mask we pick also depends on source: IOAPIC, LINT, etc */
-               irq_h->mask = 0;
-               irq_h->unmask = 0;
-               irq_h->type = "lapic";
+       irq_h->dev_irq = irq;
+       irq_h->tbdf = tbdf;
+       vector = bus_irq_setup(irq_h);
+       if (vector == -1) {
+               kfree(irq_h);
+               return -1;
        }
+       printk("IRQ %d, vector %d (0x%x), type %s\n", irq, vector, vector,
+              irq_h->type);
+       assert(irq_h->check_spurious && irq_h->eoi);
+       irq_h->isr = handler;
+       irq_h->data = irq_arg;
        irq_h->apic_vector = vector;
+       /* RCU write lock */
+       spin_lock_irqsave(&irq_handler_wlock);
        irq_h->next = irq_handlers[vector];
        wmb();  /* make sure irq_h is done before publishing to readers */
        irq_handlers[vector] = irq_h;
        spin_unlock_irqsave(&irq_handler_wlock);
+       /* Most IRQs other than the BusIPI should need their irq unmasked.
+        * Might need to pass the irq_h, in case unmask needs more info.
+        * The lapic IRQs need to be unmasked on a per-core basis */
+       if (irq_h->unmask && strcmp(irq_h->type, "lapic"))
+               irq_h->unmask(irq_h, vector);
+       return 0;
 }
 
-void unregister_raw_irq(unsigned int vector, isr_t handler, void *data)
+/* These routing functions only allow the routing of an irq to a single core.
+ * If we want to route to multiple cores, we'll probably need to set up logical
+ * groups or something and take some additional parameters. */
+static int route_irq_h(struct irq_handler *irq_h, int os_coreid)
 {
-       /* TODO: RCU */
-       printk("Unregistering not supported\n");
+       int hw_coreid;
+       if (!irq_h->route_irq) {
+               printk("[kernel] apic_vec %d, type %s cannot be routed\n",
+                      irq_h->apic_vector, irq_h->type);
+               return -1;
+       }
+       if (os_coreid >= MAX_NUM_CORES) {
+               printk("[kernel] os_coreid %d out of range!\n", os_coreid);
+               return -1;
+       }
+       hw_coreid = get_hw_coreid(os_coreid);
+       if (hw_coreid == -1) {
+               printk("[kernel] os_coreid %d not a valid hw core!\n", os_coreid);
+               return -1;
+       }
+       irq_h->route_irq(irq_h, irq_h->apic_vector, hw_coreid);
+       return 0;
 }
 
-/* The devno is arbitrary data. Normally, however, it will be a
- * PCI type-bus-dev.func. It is required for ioapics.
- */
-int register_dev_irq(int irq, isr_t handler, void *irq_arg, uint32_t tbdf)
+/* Routes all irqs for a given apic_vector to os_coreid.  Returns 0 if all of
+ * them succeeded.  -1 if there were none or if any of them failed.  We don't
+ * share IRQs often (if ever anymore), so this shouldn't be an issue. */
+int route_irqs(int apic_vec, int os_coreid)
 {
-       /* TODO: whenever we sort out the ACPI/IOAPIC business, we'll probably want
-        * a helper to reroute an irq? */
-#ifdef CONFIG_ENABLE_MPTABLES
-       /* TODO: dirty hack to get the IOAPIC vector */
-extern int intrenable(int irq, void (*f) (void *, void *), void *a, int tbdf);
-int x =        intrenable(irq, handler, irq_arg, tbdf);
-       if (x > 0)
-               register_raw_irq(x, handler, irq_arg);
-#else
-       register_raw_irq(irq + IdtPIC, handler, irq_arg);
-       pic_unmask_irq(irq + IdtPIC);
-#endif
-       return 0;
+       struct irq_handler *irq_h;
+       int ret = -1;
+       if (!vector_is_irq(apic_vec)) {
+               printk("[kernel] vector %d is not an IRQ vector!\n", apic_vec);
+               return -1;
+       }
+       irq_h = irq_handlers[apic_vec];
+       while (irq_h) {
+               assert(irq_h->apic_vector == apic_vec);
+               ret = route_irq_h(irq_h, os_coreid);
+               irq_h = irq_h->next;
+       }
+       return ret;
 }
 
 /* It's a moderate pain in the ass to put these in bit-specific files (header
  * hell with the set_current_ helpers) */
-#ifdef CONFIG_X86_64
 void sysenter_callwrapper(struct syscall *sysc, unsigned long count,
                           struct sw_trapframe *sw_tf)
 {
        struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
        set_current_ctx_sw(pcpui, sw_tf);
+       __set_cpu_state(pcpui, CPU_STATE_KERNEL);
        /* Once we've set_current_ctx, we can enable interrupts.  This used to be
         * mandatory (we had immediate KMSGs that would muck with cur_ctx).  Now it
         * should only help for sanity/debugging. */
        enable_irq();
-       /* Set up and run the async calls */
+       /* Set up and run the async calls.  This may block, and we could migrate to
+        * another core.  If you use pcpui again, you need to reread it. */
        prep_syscalls(current, sysc, count);
-       /* If you use pcpui again, reread it, since you might have migrated */
+       disable_irq();
        proc_restartcore();
 }
 
-#else
+/* Declared in x86/arch.h */
+void send_ipi(uint32_t os_coreid, uint8_t vector)
+{
+       int hw_coreid = get_hw_coreid(os_coreid);
+       if (hw_coreid == -1) {
+               panic("Unmapped OS coreid (OS %d)!\n", os_coreid);
+               return;
+       }
+       assert(vector != T_NMI);
+       __send_ipi(hw_coreid, vector);
+}
+
+/****************** VM exit handling ******************/
+
+static bool handle_vmexit_cpuid(struct vm_trapframe *tf)
+{
+       uint32_t eax, ebx, ecx, edx;
+
+       if (tf->tf_rax == 0x0B)
+               return FALSE;   // Handle in userspace.
+
+       cpuid(tf->tf_rax, tf->tf_rcx, &eax, &ebx, &ecx, &edx);
+       switch (tf->tf_rax) {
+               case 0x01:
+                       /* Set the hypervisor bit to let the guest know it is virtualized */
+                       ecx |= 1 << 31;
+                       /* Unset the monitor capability bit so that the guest does not try
+                        * to use monitor/mwait. */
+                       ecx &= ~(1 << 3);
+                       /* Unset the vmx capability bit so that the guest does not try
+                        * to turn it on. */
+                       ecx &= ~(1 << 5);
+                       /* Unset the perf capability bit so that the guest does not try
+                        * to turn it on. */
+                       ecx &= ~(1 << 15);
+
+                       /* Set the guest pcore id into the apic ID field in CPUID. */
+                       ebx &= 0x0000ffff;
+                       ebx |= (current->vmm.nr_guest_pcores & 0xff) << 16;
+                       ebx |= (tf->tf_guest_pcoreid & 0xff) << 24;
+                       break;
+               case 0x0A:
+                       eax = 0;
+                       ebx = 0;
+                       ecx = 0;
+                       edx = 0;
+                       break;
+               /* Signal the use of KVM. */
+               case 0x40000000:
+                       eax = 0;
+                       ebx = 0x4b4d564b;
+                       ecx = 0x564b4d56;
+                       edx = 0x0000004d;
+                       break;
+               /* Hypervisor Features. */
+               case 0x40000003:
+                       /* Unset the monitor capability bit so that the guest does not try
+                        * to use monitor/mwait. */
+                       edx &= ~(1 << 0);
+                       break;
+               default:
+                       break;
+       }
+       tf->tf_rax = eax;
+       tf->tf_rbx = ebx;
+       tf->tf_rcx = ecx;
+       tf->tf_rdx = edx;
+       tf->tf_rip += 2;
+       return TRUE;
+}
+
+static bool handle_vmexit_ept_fault(struct vm_trapframe *tf)
+{
+       int prot = 0;
+       int ret;
+
+       prot |= tf->tf_exit_qual & VMX_EPT_FAULT_READ ? PROT_READ : 0;
+       prot |= tf->tf_exit_qual & VMX_EPT_FAULT_WRITE ? PROT_WRITE : 0;
+       prot |= tf->tf_exit_qual & VMX_EPT_FAULT_INS ? PROT_EXEC : 0;
+       ret = handle_page_fault(current, tf->tf_guest_pa, prot);
+       if (ret == 0)
+               return TRUE;
+
+       //Mirror behavior in uthreads, tell userspace to try again.
+       if (ret == -EAGAIN)
+               tf->tf_flags |= VMCTX_FL_EPT_VMR_BACKED;
+
+       return FALSE;
+}
+
+/* Regarding NMI blocking,
+ *             "An NMI causes subsequent NMIs to be blocked, but only after the VM exit
+ *             completes." (SDM)
+ *
+ * Like handle_nmi(), this function and anything it calls directly cannot fault,
+ * or else we lose our NMI protections. */
+static bool handle_vmexit_nmi(struct vm_trapframe *tf)
+{
+       /* Sanity checks, make sure we really got an NMI.  Feel free to remove. */
+       assert((tf->tf_intrinfo2 & INTR_INFO_INTR_TYPE_MASK) == INTR_TYPE_NMI_INTR);
+       assert((tf->tf_intrinfo2 & INTR_INFO_VECTOR_MASK) == T_NMI);
+       assert(!irq_is_enabled());
+
+       emit_monitor_backtrace(ROS_VM_CTX, tf);
+       perfmon_snapshot_vmtf(tf);
+       send_self_ipi(IdtLAPIC_PCINT);
+       return TRUE;
+}
+
+bool handle_vmexit_msr(struct vm_trapframe *tf)
+{
+       bool ret;
+
+       ret = vmm_emulate_msr(tf,
+                             (tf->tf_exit_reason == EXIT_REASON_MSR_READ
+                                                  ? VMM_MSR_EMU_READ : VMM_MSR_EMU_WRITE));
+       if (ret)
+               tf->tf_rip += 2;
+       return ret;
+}
+
+bool handle_vmexit_extirq(struct vm_trapframe *tf)
+{
+       struct hw_trapframe hw_tf;
+
+       /* For now, we just handle external IRQs.  I think guest traps should go to
+        * the guest, based on our vmctls */
+       assert((tf->tf_intrinfo2 & INTR_INFO_INTR_TYPE_MASK) == INTR_TYPE_EXT_INTR);
+       /* TODO: Our IRQ handlers all expect TFs.  Let's fake one.  A bunch of
+        * handlers (e.g. backtrace/perf) will probably be unhappy about a user TF
+        * that is really a VM, so this all needs work. */
+       hw_tf.tf_gsbase = 0;
+       hw_tf.tf_fsbase = 0;
+       hw_tf.tf_rax = tf->tf_rax;
+       hw_tf.tf_rbx = tf->tf_rbx;
+       hw_tf.tf_rcx = tf->tf_rcx;
+       hw_tf.tf_rdx = tf->tf_rdx;
+       hw_tf.tf_rbp = tf->tf_rbp;
+       hw_tf.tf_rsi = tf->tf_rsi;
+       hw_tf.tf_rdi = tf->tf_rdi;
+       hw_tf.tf_r8 = tf->tf_r8;
+       hw_tf.tf_r9 = tf->tf_r9;
+       hw_tf.tf_r10 = tf->tf_r10;
+       hw_tf.tf_r11 = tf->tf_r11;
+       hw_tf.tf_r12 = tf->tf_r12;
+       hw_tf.tf_r13 = tf->tf_r13;
+       hw_tf.tf_r14 = tf->tf_r14;
+       hw_tf.tf_r15 = tf->tf_r15;
+       hw_tf.tf_trapno = tf->tf_intrinfo2 & INTR_INFO_VECTOR_MASK;
+       hw_tf.tf_err = 0;
+       hw_tf.tf_rip = tf->tf_rip;
+       hw_tf.tf_cs = GD_UT;    /* faking a user TF, even though it's a VM */
+       hw_tf.tf_rflags = tf->tf_rflags;
+       hw_tf.tf_rsp = tf->tf_rsp;
+       hw_tf.tf_ss = GD_UD;
+
+       irq_dispatch(&hw_tf);
+       /* Consider returning whether or not there was a handler registered */
+       return TRUE;
+}
+
+static bool handle_vmexit_xsetbv(struct vm_trapframe *tf)
+{
+       // The VM's requested-feature bitmap is represented by edx:eax
+       uint64_t vm_rfbm = (tf->tf_rdx << 32) | tf->tf_rax;
+
+       // If the VM tries to set xcr0 to a superset
+       // of Akaros's default value, kill the VM.
+
+       // Bit in vm_rfbm and x86_default_xcr0:        Ok. Requested and allowed.
+       // Bit in vm_rfbm but not x86_default_xcr0:    Bad! Requested, not allowed.
+       // Bit not in vm_rfbm but in x86_default_xcr0: Ok. Not requested.
+
+       // vm_rfbm & (~x86_default_xcr0) is nonzero if any bits
+       // are set in vm_rfbm but not x86_default_xcr0
+
+       if (vm_rfbm & (~__proc_global_info.x86_default_xcr0))
+               return FALSE;
+
+
+       // If attempting to use vm_rfbm for xsetbv
+       // causes a fault, we reflect to the VMM.
+       if (safe_lxcr0(vm_rfbm))
+               return FALSE;
+
+
+       // If no fault, advance the instruction pointer
+       // and return TRUE to make the VM resume.
+       tf->tf_rip += 3; // XSETBV is a 3-byte instruction
+       return TRUE;
+}
+
+static void vmexit_dispatch(struct vm_trapframe *tf)
+{
+       bool handled = FALSE;
 
-/* This is called from sysenter's asm, with the tf on the kernel stack. */
-/* TODO: use a sw_tf for sysenter */
-void sysenter_callwrapper(struct hw_trapframe *hw_tf)
+       /* Do not block in any of these functions.
+        *
+        * If we block, we'll probably need to finalize the context.  If we do, then
+        * there's a chance the guest pcore can start somewhere else, and then we
+        * can't get the GPC loaded again.  Plus, they could be running a GPC with
+        * an unresolved vmexit.  It's just mess.
+        *
+        * If we want to enable IRQs, we can do so on a case-by-case basis.  Don't
+        * do it for external IRQs - the irq_dispatch code will handle it. */
+       switch (tf->tf_exit_reason) {
+       case EXIT_REASON_VMCALL:
+               if (current->vmm.flags & VMM_VMCALL_PRINTF &&
+                   tf->tf_rax == VMCALL_PRINTC) {
+                       printk("%c", tf->tf_rdi);
+                       tf->tf_rip += 3;
+                       handled = TRUE;
+               }
+               break;
+       case EXIT_REASON_CPUID:
+               handled = handle_vmexit_cpuid(tf);
+               break;
+       case EXIT_REASON_EPT_VIOLATION:
+               handled = handle_vmexit_ept_fault(tf);
+               break;
+       case EXIT_REASON_EXCEPTION_NMI:
+               handled = handle_vmexit_nmi(tf);
+               break;
+       case EXIT_REASON_MSR_READ:
+       case EXIT_REASON_MSR_WRITE:
+               handled = handle_vmexit_msr(tf);
+               break;
+       case EXIT_REASON_EXTERNAL_INTERRUPT:
+               handled = handle_vmexit_extirq(tf);
+               break;
+       case EXIT_REASON_XSETBV:
+               handled = handle_vmexit_xsetbv(tf);
+               break;
+       default:
+               printd("Unhandled vmexit: reason 0x%x, exit qualification 0x%x\n",
+                      tf->tf_exit_reason, tf->tf_exit_qual);
+       }
+       if (!handled) {
+               tf->tf_flags |= VMCTX_FL_HAS_FAULT;
+               if (reflect_current_context()) {
+                       /* VM contexts shouldn't be in vcore context, so this should be
+                        * pretty rare (unlike SCPs or VC ctx page faults). */
+                       printk("[kernel] Unable to reflect VM Exit\n");
+                       print_vmtrapframe(tf);
+                       proc_destroy(current);
+               }
+       }
+}
+
+void handle_vmexit(struct vm_trapframe *tf)
 {
        struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
-       assert(!in_kernel(hw_tf));
-       set_current_ctx_hw(pcpui, hw_tf);
-       /* Once we've set_current_ctx, we can enable interrupts.  This used to be
-        * mandatory (we had immediate KMSGs that would muck with cur_ctx).  Now it
-        * should only help for sanity/debugging. */
-       enable_irq();
 
-       /* Set up and run the async calls */
-       prep_syscalls(current,
-                                 (struct syscall*)x86_get_sysenter_arg0(hw_tf),
-                                 (unsigned int)x86_get_sysenter_arg1(hw_tf));
-       /* If you use pcpui again, reread it, since you might have migrated */
+       tf->tf_rip = vmcs_read(GUEST_RIP);
+       tf->tf_rflags = vmcs_read(GUEST_RFLAGS);
+       tf->tf_rsp = vmcs_read(GUEST_RSP);
+       tf->tf_cr2 = rcr2();
+       tf->tf_cr3 = vmcs_read(GUEST_CR3);
+       tf->tf_guest_pcoreid = pcpui->guest_pcoreid;
+       tf->tf_flags |= VMCTX_FL_PARTIAL;
+       tf->tf_guest_intr_status = vmcs_read(GUEST_INTR_STATUS);
+       tf->tf_exit_reason = vmcs_read(VM_EXIT_REASON);
+       tf->tf_exit_qual = vmcs_read(EXIT_QUALIFICATION);
+       tf->tf_intrinfo1 = vmcs_read(GUEST_INTERRUPTIBILITY_INFO);
+       tf->tf_intrinfo2 = vmcs_read(VM_EXIT_INTR_INFO);
+       tf->tf_guest_va = vmcs_read(GUEST_LINEAR_ADDRESS);
+       tf->tf_guest_pa = vmcs_read(GUEST_PHYSICAL_ADDRESS);
+
+       set_current_ctx_vm(pcpui, tf);
+       __set_cpu_state(pcpui, CPU_STATE_KERNEL);
+       tf = &pcpui->cur_ctx->tf.vm_tf;
+       vmexit_dispatch(tf);
+       /* We're either restarting a partial VM ctx (vmcs was launched, loaded on
+        * the core, etc) or a SW vc ctx for the reflected trap.  Or the proc is
+        * dying and we'll handle a __death KMSG shortly. */
        proc_restartcore();
 }
-#endif
 
-/* Declared in x86/arch.h */
-void send_ipi(uint32_t os_coreid, uint8_t vector)
+/* Partial contexts for HW and SW TFs have the user's gs in MSR_KERNEL_GS_BASE.
+ * The kernel's gs is loaded into gs.  We need to put the kernel's gs into
+ * KERNEL_GS_BASE so the core is ready to run another full context, save the
+ * user's {GS,FS}_BASE into their TF so it can run on another core, and keep GS
+ * loaded with the current GS (the kernel's). */
+static void x86_finalize_hwtf(struct hw_trapframe *tf)
 {
-       int hw_coreid = get_hw_coreid(os_coreid);
-       if (hw_coreid == -1) {
-               panic("Unmapped OS coreid (OS %d)!\n", os_coreid);
+       tf->tf_gsbase = read_kern_gsbase();
+       write_kern_gsbase(read_gsbase());
+       tf->tf_fsbase = read_fsbase();
+       x86_hwtf_clear_partial(tf);
+}
+
+static void x86_finalize_swtf(struct sw_trapframe *tf)
+{
+       tf->tf_gsbase = read_kern_gsbase();
+       write_kern_gsbase(read_gsbase());
+       tf->tf_fsbase = read_fsbase();
+       x86_swtf_clear_partial(tf);
+}
+
+static void x86_finalize_vmtf(struct vm_trapframe *tf)
+{
+       struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
+
+       x86_vmtf_clear_partial(tf);
+       unload_guest_pcore(pcpui->owning_proc, pcpui->guest_pcoreid);
+}
+
+/* Makes sure that the user context is fully saved into ctx and not split across
+ * the struct and HW, meaning it is not a "partial context".
+ *
+ * Be careful to zero out any part of the ctx struct not in use, to avoid
+ * leaking information from other processes. */
+void arch_finalize_ctx(struct user_context *ctx)
+{
+       if (!arch_ctx_is_partial(ctx))
                return;
+       switch (ctx->type) {
+       case ROS_HW_CTX:
+               x86_finalize_hwtf(&ctx->tf.hw_tf);
+               memset((uint8_t*)&ctx->tf + sizeof(struct hw_trapframe), 0,
+                          sizeof(ctx->tf) - sizeof(struct hw_trapframe));
+               break;
+       case ROS_SW_CTX:
+               x86_finalize_swtf(&ctx->tf.sw_tf);
+               memset((uint8_t*)&ctx->tf + sizeof(struct sw_trapframe), 0,
+                          sizeof(ctx->tf) - sizeof(struct sw_trapframe));
+               break;
+       case ROS_VM_CTX:
+               x86_finalize_vmtf(&ctx->tf.vm_tf);
+               memset((uint8_t*)&ctx->tf + sizeof(struct vm_trapframe), 0,
+                          sizeof(ctx->tf) - sizeof(struct vm_trapframe));
+               break;
        }
-       __send_ipi(hw_coreid, vector);
 }