From d181963296b8810e066ab27f2df2b029515b0315 Mon Sep 17 00:00:00 2001 From: Neel Natu Date: Fri, 12 Sep 2014 06:15:20 +0000 Subject: [PATCH] Optimize the common case of injecting an interrupt into a vcpu after a HLT by explicitly moving it out of the interrupt shadow. The hypervisor is done "executing" the HLT and by definition this moves the vcpu out of the 1-instruction interrupt shadow. Prior to this change the interrupt would be held pending because the VMCS guest-interruptibility-state would indicate that "blocking by STI" was in effect. This resulted in an unnecessary round trip into the guest before the pending interrupt could be injected. Reviewed by: grehan --- sys/amd64/include/vmm.h | 1 + sys/amd64/vmm/intel/vmx.c | 46 +++++++++++++++++++++++++++++++++++++++ sys/amd64/vmm/vmm.c | 18 ++++++++++++++- 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/sys/amd64/include/vmm.h b/sys/amd64/include/vmm.h index 58af2a5abfe3..2529d343b195 100644 --- a/sys/amd64/include/vmm.h +++ b/sys/amd64/include/vmm.h @@ -82,6 +82,7 @@ enum vm_reg_name { VM_REG_GUEST_PDPTE1, VM_REG_GUEST_PDPTE2, VM_REG_GUEST_PDPTE3, + VM_REG_GUEST_INTR_SHADOW, VM_REG_LAST }; diff --git a/sys/amd64/vmm/intel/vmx.c b/sys/amd64/vmm/intel/vmx.c index b2c570216a78..b7ecf2b5382a 100644 --- a/sys/amd64/vmm/intel/vmx.c +++ b/sys/amd64/vmm/intel/vmx.c @@ -2711,6 +2711,46 @@ vmxctx_setreg(struct vmxctx *vmxctx, int reg, uint64_t val) return (EINVAL); } +static int +vmx_get_intr_shadow(struct vmx *vmx, int vcpu, int running, uint64_t *retval) +{ + uint64_t gi; + int error; + + error = vmcs_getreg(&vmx->vmcs[vcpu], running, + VMCS_IDENT(VMCS_GUEST_INTERRUPTIBILITY), &gi); + *retval = (gi & HWINTR_BLOCKING) ? 1 : 0; + return (error); +} + +static int +vmx_modify_intr_shadow(struct vmx *vmx, int vcpu, int running, uint64_t val) +{ + struct vmcs *vmcs; + uint64_t gi; + int error, ident; + + /* + * Forcing the vcpu into an interrupt shadow is not supported. + */ + if (val) { + error = EINVAL; + goto done; + } + + vmcs = &vmx->vmcs[vcpu]; + ident = VMCS_IDENT(VMCS_GUEST_INTERRUPTIBILITY); + error = vmcs_getreg(vmcs, running, ident, &gi); + if (error == 0) { + gi &= ~HWINTR_BLOCKING; + error = vmcs_setreg(vmcs, running, ident, gi); + } +done: + VCPU_CTR2(vmx->vm, vcpu, "Setting intr_shadow to %#lx %s", val, + error ? "failed" : "succeeded"); + return (error); +} + static int vmx_shadow_reg(int reg) { @@ -2742,6 +2782,9 @@ vmx_getreg(void *arg, int vcpu, int reg, uint64_t *retval) if (running && hostcpu != curcpu) panic("vmx_getreg: %s%d is running", vm_name(vmx->vm), vcpu); + if (reg == VM_REG_GUEST_INTR_SHADOW) + return (vmx_get_intr_shadow(vmx, vcpu, running, retval)); + if (vmxctx_getreg(&vmx->ctx[vcpu], reg, retval) == 0) return (0); @@ -2760,6 +2803,9 @@ vmx_setreg(void *arg, int vcpu, int reg, uint64_t val) if (running && hostcpu != curcpu) panic("vmx_setreg: %s%d is running", vm_name(vmx->vm), vcpu); + if (reg == VM_REG_GUEST_INTR_SHADOW) + return (vmx_modify_intr_shadow(vmx, vcpu, running, val)); + if (vmxctx_setreg(&vmx->ctx[vcpu], reg, val) == 0) return (0); diff --git a/sys/amd64/vmm/vmm.c b/sys/amd64/vmm/vmm.c index ae67c58962eb..f7c9ce438494 100644 --- a/sys/amd64/vmm/vmm.c +++ b/sys/amd64/vmm/vmm.c @@ -1090,7 +1090,7 @@ vm_handle_hlt(struct vm *vm, int vcpuid, bool intr_disabled, bool *retu) { struct vcpu *vcpu; const char *wmesg; - int t, vcpu_halted, vm_halted; + int error, t, vcpu_halted, vm_halted; KASSERT(!CPU_ISSET(vcpuid, &vm->halted_cpus), ("vcpu already halted")); @@ -1098,6 +1098,22 @@ vm_handle_hlt(struct vm *vm, int vcpuid, bool intr_disabled, bool *retu) vcpu_halted = 0; vm_halted = 0; + /* + * The typical way to halt a cpu is to execute: "sti; hlt" + * + * STI sets RFLAGS.IF to enable interrupts. However, the processor + * remains in an "interrupt shadow" for an additional instruction + * following the STI. This guarantees that "sti; hlt" sequence is + * atomic and a pending interrupt will be recognized after the HLT. + * + * After the HLT emulation is done the vcpu is no longer in an + * interrupt shadow and a pending interrupt can be injected on + * the next entry into the guest. + */ + error = vm_set_register(vm, vcpuid, VM_REG_GUEST_INTR_SHADOW, 0); + KASSERT(error == 0, ("%s: error %d clearing interrupt shadow", + __func__, error)); + vcpu_lock(vcpu); while (1) { /*