svn commit: r262144 - head/sys/amd64/vmm/intel

John Baldwin jhb at FreeBSD.org
Tue Feb 18 03:07:37 UTC 2014


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 {


More information about the svn-src-head mailing list