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