Author: jhb
Date: Tue Feb 18 03:07:36 2014
New Revision: 262144
URL: http://svnweb.freebsd.org/changeset/base/262144

Log:
  A first pass at adding support for injecting hardware exceptions for
  emulated instructions.
  - Add helper routines to inject interrupt information for a hardware
    exception from the VM exit callback routines.
  - Use the new routines to inject GP and UD exceptions for invalid
    operations when emulating the xsetbv instruction.
  - Don't directly manipulate the entry interrupt info when a user event
    is injected.  Instead, store the event info in the vmx state and
    only apply it during a VM entry if a hardware exception or NMI is
    not already pending.
  - While here, use HANDLED/UNHANDLED instead of 1/0 in a couple of
    routines.
  
  Reviewed by:  neel

Modified:
  head/sys/amd64/vmm/intel/vmcs.h
  head/sys/amd64/vmm/intel/vmx.c
  head/sys/amd64/vmm/intel/vmx.h

Modified: head/sys/amd64/vmm/intel/vmcs.h
==============================================================================
--- head/sys/amd64/vmm/intel/vmcs.h     Tue Feb 18 03:00:20 2014        
(r262143)
+++ head/sys/amd64/vmm/intel/vmcs.h     Tue Feb 18 03:07:36 2014        
(r262144)
@@ -345,6 +345,8 @@ vmcs_write(uint32_t encoding, uint64_t v
 #define        VMCS_INTR_T_MASK        0x700           /* Interruption-info 
type */
 #define        VMCS_INTR_T_HWINTR      (0 << 8)
 #define        VMCS_INTR_T_NMI         (2 << 8)
+#define        VMCS_INTR_T_HWEXCEPTION (3 << 8)
+#define        VMCS_INTR_DEL_ERRCODE   (1 << 11)
 
 /*
  * VMCS IDT-Vectoring information fields

Modified: head/sys/amd64/vmm/intel/vmx.c
==============================================================================
--- head/sys/amd64/vmm/intel/vmx.c      Tue Feb 18 03:00:20 2014        
(r262143)
+++ head/sys/amd64/vmm/intel/vmx.c      Tue Feb 18 03:07:36 2014        
(r262144)
@@ -884,6 +884,7 @@ vmx_vminit(struct vm *vm, pmap_t pmap)
 
                vmx->state[i].lastcpu = -1;
                vmx->state[i].vpid = vpid[i];
+               vmx->state[i].user_event.intr_info = 0;
 
                msr_save_area_init(vmx->guest_msrs[i], &guest_msr_count);
 
@@ -1062,6 +1063,66 @@ vmx_clear_nmi_window_exiting(struct vmx 
                         VMCS_INTERRUPTIBILITY_MOVSS_BLOCKING)
 
 static void
+vmx_inject_user_event(struct vmx *vmx, int vcpu)
+{
+       struct vmxevent *user_event;
+       uint32_t info;
+
+       user_event = &vmx->state[vcpu].user_event;
+       
+       info = vmcs_read(VMCS_ENTRY_INTR_INFO);
+       KASSERT((info & VMCS_INTR_VALID) == 0, ("vmx_inject_user_event: invalid 
"
+           "VM-entry interruption information %#x", info));
+
+       vmcs_write(VMCS_ENTRY_INTR_INFO, user_event->intr_info);
+       if (user_event->intr_info & VMCS_INTR_DEL_ERRCODE)
+               vmcs_write(VMCS_ENTRY_EXCEPTION_ERROR, user_event->error_code);
+       user_event->intr_info = 0;
+}
+
+static void
+vmx_inject_exception(struct vmx *vmx, int vcpu, struct vm_exit *vmexit,
+    int fault, int errvalid, int errcode)
+{
+       uint32_t info;
+
+       info = vmcs_read(VMCS_ENTRY_INTR_INFO);
+       KASSERT((info & VMCS_INTR_VALID) == 0, ("vmx_inject_exception: invalid "
+           "VM-entry interruption information %#x", info));
+
+       /*
+        * Although INTR_T_HWEXCEPTION does not advance %rip, vmx_run()
+        * always advances it, so we clear the instruction length to zero
+        * explicitly.
+        */
+       vmexit->inst_length = 0;
+       info = fault | VMCS_INTR_T_HWEXCEPTION | VMCS_INTR_VALID;
+       if (errvalid) {
+               info |= VMCS_INTR_DEL_ERRCODE;
+               vmcs_write(VMCS_ENTRY_EXCEPTION_ERROR, errcode);
+       }
+       vmcs_write(VMCS_ENTRY_INTR_INFO, info);
+
+       VCPU_CTR2(vmx->vm, vcpu, "Injecting fault %d (errcode %d)", fault,
+           errcode);
+}
+
+/* All GP# faults VMM injects use an error code of 0. */
+static void
+vmx_inject_gp(struct vmx *vmx, int vcpu, struct vm_exit *vmexit)
+{
+
+       vmx_inject_exception(vmx, vcpu, vmexit, IDT_GP, 1, 0);
+}
+
+static void
+vmx_inject_ud(struct vmx *vmx, int vcpu, struct vm_exit *vmexit)
+{
+
+       vmx_inject_exception(vmx, vcpu, vmexit, IDT_UD, 0, 0);
+}
+
+static void
 vmx_inject_nmi(struct vmx *vmx, int vcpu)
 {
        uint32_t gi, info;
@@ -1126,6 +1187,24 @@ vmx_inject_interrupts(struct vmx *vmx, i
                        vmx_set_nmi_window_exiting(vmx, vcpu);
        }
 
+       /*
+        * If there is a user injection event pending and there isn't
+        * an interrupt queued already, inject the user event.
+        */
+       if (vmx->state[vcpu].user_event.intr_info & VMCS_INTR_VALID) {
+               info = vmcs_read(VMCS_ENTRY_INTR_INFO);
+               if ((info & VMCS_INTR_VALID) == 0) {
+                       vmx_inject_user_event(vmx, vcpu);
+               } else {
+                       /*
+                        * XXX: Do we need to force an exit so this can
+                        * be injected?
+                        */
+                       VCPU_CTR1(vmx->vm, vcpu, "Cannot inject user event "
+                           "due to VM-entry intr info %#x", info);
+               }
+       }
+        
        if (virtual_interrupt_delivery) {
                vmx_inject_pir(vlapic);
                return;
@@ -1228,7 +1307,7 @@ vmx_clear_nmi_blocking(struct vmx *vmx, 
 }
 
 static int
-vmx_emulate_xsetbv(struct vmx *vmx, int vcpu)
+vmx_emulate_xsetbv(struct vmx *vmx, int vcpu, struct vm_exit *vmexit)
 {
        struct vmxctx *vmxctx;
        uint64_t xcrval;
@@ -1237,20 +1316,40 @@ vmx_emulate_xsetbv(struct vmx *vmx, int 
        vmxctx = &vmx->ctx[vcpu];
        limits = vmm_get_xsave_limits();
 
-       /* We only handle xcr0 if the host has XSAVE enabled. */
-       if (vmxctx->guest_rcx != 0 || !limits->xsave_enabled)
-               return (UNHANDLED);
+       /*
+        * Note that the processor raises a GP# fault on its own if
+        * xsetbv is executed for CPL != 0, so we do not have to
+        * emulate that fault here.
+        */
+
+       /* Only xcr0 is supported. */
+       if (vmxctx->guest_rcx != 0) {
+               vmx_inject_gp(vmx, vcpu, vmexit);
+               return (HANDLED);
+       }
+
+       /* We only handle xcr0 if both the host and guest have XSAVE enabled. */
+       if (!limits->xsave_enabled || !(vmcs_read(VMCS_GUEST_CR4) & CR4_XSAVE)) 
{
+               vmx_inject_ud(vmx, vcpu, vmexit);
+               return (HANDLED);
+       }
 
        xcrval = vmxctx->guest_rdx << 32 | (vmxctx->guest_rax & 0xffffffff);
-       if ((xcrval & ~limits->xcr0_allowed) != 0)
-               return (UNHANDLED);
+       if ((xcrval & ~limits->xcr0_allowed) != 0) {
+               vmx_inject_gp(vmx, vcpu, vmexit);
+               return (HANDLED);
+       }
 
-       if (!(xcrval & XFEATURE_ENABLED_X87))
-               return (UNHANDLED);
+       if (!(xcrval & XFEATURE_ENABLED_X87)) {
+               vmx_inject_gp(vmx, vcpu, vmexit);
+               return (HANDLED);
+       }
 
        if ((xcrval & (XFEATURE_ENABLED_AVX | XFEATURE_ENABLED_SSE)) ==
-           XFEATURE_ENABLED_AVX)
-               return (UNHANDLED);
+           XFEATURE_ENABLED_AVX) {
+               vmx_inject_gp(vmx, vcpu, vmexit);
+               return (HANDLED);
+       }
 
        /*
         * This runs "inside" vmrun() with the guest's FPU state, so
@@ -1448,7 +1547,7 @@ vmx_handle_apic_write(struct vlapic *vla
        if (!virtual_interrupt_delivery)
                return (UNHANDLED);
 
-       handled = 1;
+       handled = HANDLED;
        offset = APIC_WRITE_OFFSET(qual);
        switch (offset) {
        case APIC_OFFSET_ID:
@@ -1470,7 +1569,7 @@ vmx_handle_apic_write(struct vlapic *vla
                retu = false;
                error = vlapic_icrlo_write_handler(vlapic, &retu);
                if (error != 0 || retu)
-                       handled = 0;
+                       handled = UNHANDLED;
                break;
        case APIC_OFFSET_CMCI_LVT:
        case APIC_OFFSET_TIMER_LVT ... APIC_OFFSET_ERROR_LVT:
@@ -1483,7 +1582,7 @@ vmx_handle_apic_write(struct vlapic *vla
                vlapic_dcr_write_handler(vlapic);
                break;
        default:
-               handled = 0;
+               handled = UNHANDLED;
                break;
        }
        return (handled);
@@ -1583,7 +1682,7 @@ vmx_exit_process(struct vmx *vmx, int vc
        CTASSERT((PINBASED_CTLS_ONE_SETTING & PINBASED_VIRTUAL_NMI) != 0);
        CTASSERT((PINBASED_CTLS_ONE_SETTING & PINBASED_NMI_EXITING) != 0);
 
-       handled = 0;
+       handled = UNHANDLED;
        vmxctx = &vmx->ctx[vcpu];
 
        qual = vmexit->u.vmx.exit_qualification;
@@ -1646,7 +1745,7 @@ vmx_exit_process(struct vmx *vmx, int vc
                        vmexit->exitcode = VM_EXITCODE_RDMSR;
                        vmexit->u.msr.code = ecx;
                } else if (!retu) {
-                       handled = 1;
+                       handled = HANDLED;
                } else {
                        /* Return to userspace with a valid exitcode */
                        KASSERT(vmexit->exitcode != VM_EXITCODE_BOGUS,
@@ -1666,7 +1765,7 @@ vmx_exit_process(struct vmx *vmx, int vc
                        vmexit->u.msr.code = ecx;
                        vmexit->u.msr.wval = (uint64_t)edx << 32 | eax;
                } else if (!retu) {
-                       handled = 1;
+                       handled = HANDLED;
                } else {
                        /* Return to userspace with a valid exitcode */
                        KASSERT(vmexit->exitcode != VM_EXITCODE_BOGUS,
@@ -1809,7 +1908,7 @@ vmx_exit_process(struct vmx *vmx, int vc
                handled = vmx_handle_apic_write(vlapic, qual);
                break;
        case EXIT_REASON_XSETBV:
-               handled = vmx_emulate_xsetbv(vmx, vcpu);
+               handled = vmx_emulate_xsetbv(vmx, vcpu, vmexit);
                break;
        default:
                vmm_stat_incr(vmx->vm, vcpu, VMEXIT_UNKNOWN, 1);
@@ -2239,10 +2338,8 @@ static int
 vmx_inject(void *arg, int vcpu, int type, int vector, uint32_t code,
           int code_valid)
 {
-       int error;
-       uint64_t info;
        struct vmx *vmx = arg;
-       struct vmcs *vmcs = &vmx->vmcs[vcpu];
+       struct vmxevent *user_event = &vmx->state[vcpu].user_event;
 
        static uint32_t type_map[VM_EVENT_MAX] = {
                0x1,            /* VM_EVENT_NONE */
@@ -2258,25 +2355,15 @@ vmx_inject(void *arg, int vcpu, int type
         * If there is already an exception pending to be delivered to the
         * vcpu then just return.
         */
-       error = vmcs_getreg(vmcs, 0, VMCS_IDENT(VMCS_ENTRY_INTR_INFO), &info);
-       if (error)
-               return (error);
-
-       if (info & VMCS_INTR_VALID)
+       if (user_event->intr_info & VMCS_INTR_VALID)
                return (EAGAIN);
 
-       info = vector | (type_map[type] << 8) | (code_valid ? 1 << 11 : 0);
-       info |= VMCS_INTR_VALID;
-       error = vmcs_setreg(vmcs, 0, VMCS_IDENT(VMCS_ENTRY_INTR_INFO), info);
-       if (error != 0)
-               return (error);
-
+       user_event->intr_info = vector | (type_map[type] << 8) | 
VMCS_INTR_VALID;
        if (code_valid) {
-               error = vmcs_setreg(vmcs, 0,
-                                   VMCS_IDENT(VMCS_ENTRY_EXCEPTION_ERROR),
-                                   code);
+               user_event->intr_info |= VMCS_INTR_DEL_ERRCODE;
+               user_event->error_code = code;
        }
-       return (error);
+       return (0);
 }
 
 static int

Modified: head/sys/amd64/vmm/intel/vmx.h
==============================================================================
--- head/sys/amd64/vmm/intel/vmx.h      Tue Feb 18 03:00:20 2014        
(r262143)
+++ head/sys/amd64/vmm/intel/vmx.h      Tue Feb 18 03:07:36 2014        
(r262144)
@@ -80,9 +80,15 @@ struct vmxcap {
        uint32_t proc_ctls2;
 };
 
+struct vmxevent {
+       uint32_t intr_info;
+       uint32_t error_code;
+};
+
 struct vmxstate {
        int     lastcpu;        /* host cpu that this 'vcpu' last ran on */
        uint16_t vpid;
+       struct vmxevent user_event;
 };
 
 struct apic_page {
_______________________________________________
svn-src-all@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "svn-src-all-unsubscr...@freebsd.org"

Reply via email to