Index: sys/amd64/vmm/amd/svm.c =================================================================== --- sys/amd64/vmm/amd/svm.c (revision 271570) +++ sys/amd64/vmm/amd/svm.c (working copy) @@ -948,6 +948,37 @@ } static void +svm_update_virqinfo(struct svm_softc *sc, int vcpu) +{ + struct vm *vm; + struct vlapic *vlapic; + struct vmcb_ctrl *ctrl; + int pending; + + vm = sc->vm; + vlapic = vm_lapic(vm, vcpu); + ctrl = svm_get_vmcb_ctrl(sc, vcpu); + + /* Update %cr8 in the emulated vlapic */ + vlapic_set_cr8(vlapic, ctrl->v_tpr); + + /* + * If V_IRQ indicates that the interrupt injection attempted on then + * last VMRUN was successful then update the vlapic accordingly. + */ + if (ctrl->v_intr_vector != 0) { + pending = ctrl->v_irq; + KASSERT(ctrl->v_intr_vector >= 16, ("%s: invalid " + "v_intr_vector %d", __func__, ctrl->v_intr_vector)); + KASSERT(!ctrl->v_ign_tpr, ("%s: invalid v_ign_tpr", __func__)); + VCPU_CTR2(vm, vcpu, "v_intr_vector %d %s", ctrl->v_intr_vector, + pending ? "pending" : "accepted"); + if (!pending) + vlapic_intr_accepted(vlapic, ctrl->v_intr_vector); + } +} + +static void svm_save_intinfo(struct svm_softc *svm_sc, int vcpu) { struct vmcb_ctrl *ctrl; @@ -970,6 +1001,14 @@ vm_exit_intinfo(svm_sc->vm, vcpu, intinfo); } +static __inline int +vintr_intercept_enabled(struct svm_softc *sc, int vcpu) +{ + + return (svm_get_intercept(sc, vcpu, VMCB_CTRL1_INTCPT, + VMCB_INTCPT_VINTR)); +} + static __inline void enable_intr_window_exiting(struct svm_softc *sc, int vcpu) { @@ -977,14 +1016,19 @@ ctrl = svm_get_vmcb_ctrl(sc, vcpu); - if (ctrl->v_irq == 0) { - VCPU_CTR0(sc->vm, vcpu, "Enable intr window exiting"); - ctrl->v_irq = 1; - ctrl->v_ign_tpr = 1; - vcpu_set_dirty(sc, vcpu, VMCB_CACHE_TPR); - svm_enable_intercept(sc, vcpu, VMCB_CTRL1_INTCPT, - VMCB_INTCPT_VINTR); + if (ctrl->v_irq && ctrl->v_intr_vector == 0) { + KASSERT(ctrl->v_ign_tpr, ("%s: invalid v_ign_tpr", __func__)); + KASSERT(vintr_intercept_enabled(sc, vcpu), + ("%s: vintr intercept should be enabled", __func__)); + return; } + + VCPU_CTR0(sc->vm, vcpu, "Enable intr window exiting"); + ctrl->v_irq = 1; + ctrl->v_ign_tpr = 1; + ctrl->v_intr_vector = 0; + vcpu_set_dirty(sc, vcpu, VMCB_CACHE_TPR); + svm_enable_intercept(sc, vcpu, VMCB_CTRL1_INTCPT, VMCB_INTCPT_VINTR); } static __inline void @@ -994,13 +1038,17 @@ ctrl = svm_get_vmcb_ctrl(sc, vcpu); - if (ctrl->v_irq) { - VCPU_CTR0(sc->vm, vcpu, "Disable intr window exiting"); - ctrl->v_irq = 0; - vcpu_set_dirty(sc, vcpu, VMCB_CACHE_TPR); - svm_disable_intercept(sc, vcpu, VMCB_CTRL1_INTCPT, - VMCB_INTCPT_VINTR); + if (!ctrl->v_irq && ctrl->v_intr_vector == 0) { + KASSERT(!vintr_intercept_enabled(sc, vcpu), + ("%s: vintr intercept should be disabled", __func__)); + return; } + + VCPU_CTR0(sc->vm, vcpu, "Disable intr window exiting"); + ctrl->v_irq = 0; + ctrl->v_intr_vector = 0; + vcpu_set_dirty(sc, vcpu, VMCB_CACHE_TPR); + svm_disable_intercept(sc, vcpu, VMCB_CTRL1_INTCPT, VMCB_INTCPT_VINTR); } static int @@ -1146,6 +1194,7 @@ ("invalid inst_length %d: code (%#lx), info1 (%#lx), info2 (%#lx)", vmexit->inst_length, code, info1, info2)); + svm_update_virqinfo(svm_sc, vcpu); svm_save_intinfo(svm_sc, vcpu); switch (code) { @@ -1317,13 +1366,14 @@ { struct vmcb_ctrl *ctrl; struct vmcb_state *state; - int extint_pending; - int vector, need_intr_window; + uint8_t v_tpr; + int vector, need_intr_window, pending_apic_vector; state = svm_get_vmcb_state(sc, vcpu); ctrl = svm_get_vmcb_ctrl(sc, vcpu); need_intr_window = 0; + pending_apic_vector = 0; /* * Inject pending events or exceptions for this vcpu. @@ -1378,22 +1428,40 @@ } } - extint_pending = vm_extint_pending(sc->vm, vcpu); - - if (!extint_pending) { - /* Ask the local apic for a vector to inject */ - if (!vlapic_pending_intr(vlapic, &vector)) { - goto done; /* nothing to inject */ + if (!vm_extint_pending(sc->vm, vcpu)) { + /* + * APIC interrupts are delivered using the V_IRQ offload. + * + * The primary benefit is that the hypervisor doesn't need to + * deal with the various conditions that inhibit interrupts. + * It also means that TPR changes via CR8 will be handled + * without any hypervisor involvement. + * + * Note that the APIC vector must remain pending in the vIRR + * until it is confirmed that it was delivered to the guest. + * This can be confirmed based on the value of V_IRQ at the + * next #VMEXIT (1 = pending, 0 = delivered). + * + * Also note that it is possible that another higher priority + * vector can become pending before this vector is delivered + * to the guest. This is alright because vcpu_notify_event() + * will send an IPI and force the vcpu to trap back into the + * hypervisor. The higher priority vector will be injected on + * the next VMRUN. + */ + if (vlapic_pending_intr(vlapic, &vector)) { + KASSERT(vector >= 16 && vector <= 255, + ("invalid vector %d from local APIC", vector)); + pending_apic_vector = vector; } - KASSERT(vector >= 16 && vector <= 255, - ("invalid vector %d from local APIC", vector)); - } else { - /* Ask the legacy pic for a vector to inject */ - vatpic_pending_intr(sc->vm, &vector); - KASSERT(vector >= 0 && vector <= 255, - ("invalid vector %d from local APIC", vector)); + goto done; } + /* Ask the legacy pic for a vector to inject */ + vatpic_pending_intr(sc->vm, &vector); + KASSERT(vector >= 0 && vector <= 255, ("invalid vector %d from INTR", + vector)); + /* * If the guest has disabled interrupts or is in an interrupt shadow * then we cannot inject the pending interrupt. @@ -1419,26 +1487,56 @@ goto done; } + /* + * Legacy PIC interrupts are delivered via the event injection + * mechanism. + */ svm_eventinject(sc, vcpu, VMCB_EVENTINJ_TYPE_INTR, vector, 0, false); - if (!extint_pending) { - /* Update the Local APIC ISR */ - vlapic_intr_accepted(vlapic, vector); - } else { - vm_extint_clear(sc->vm, vcpu); - vatpic_intr_accepted(sc->vm, vector); + vm_extint_clear(sc->vm, vcpu); + vatpic_intr_accepted(sc->vm, vector); + + /* + * Force a VM-exit as soon as the vcpu is ready to accept another + * interrupt. This is done because the PIC might have another vector + * that it wants to inject. Also, if the APIC has a pending interrupt + * that was preempted by the ExtInt then it allows us to inject the + * APIC vector as soon as possible. + */ + need_intr_window = 1; +done: + /* + * The guest can modify the TPR by writing to %CR8. In guest mode + * the processor reflects this write to V_TPR without hypervisor + * intervention. + * + * The guest can also modify the TPR by writing to it via the memory + * mapped APIC page. In this case, the write will be emulated by the + * hypervisor. For this reason V_TPR must be updated before every + * VMRUN. + */ + v_tpr = vlapic_get_cr8(vlapic); + KASSERT(v_tpr >= 0 && v_tpr <= 15, ("invalid v_tpr %#x", v_tpr)); + if (ctrl->v_tpr != v_tpr) { + VCPU_CTR2(sc->vm, vcpu, "VMCB V_TPR changed from %#x to %#x", + ctrl->v_tpr, v_tpr); + ctrl->v_tpr = v_tpr; + vcpu_set_dirty(sc, vcpu, VMCB_CACHE_TPR); + } + + if (pending_apic_vector) { /* - * Force a VM-exit as soon as the vcpu is ready to accept - * another interrupt. This is done because the PIC might - * have another vector that it wants to inject. Also, if - * the vlapic has a pending interrupt that was preempted - * by the ExtInt then it allows us to inject the APIC - * vector as soon as possible. + * If an APIC vector is being injected then interrupt window + * exiting is not possible on this VMRUN. */ - need_intr_window = 1; - } -done: - if (need_intr_window) { + KASSERT(!need_intr_window, ("intr_window exiting impossible")); + + ctrl->v_irq = 1; + ctrl->v_ign_tpr = 0; + ctrl->v_intr_vector = pending_apic_vector; + ctrl->v_intr_prio = pending_apic_vector >> 4; + vcpu_set_dirty(sc, vcpu, VMCB_CACHE_TPR); + } else if (need_intr_window) { /* * We use V_IRQ in conjunction with the VINTR intercept to * trap into the hypervisor as soon as a virtual interrupt Index: sys/amd64/vmm/io/vlapic.c =================================================================== --- sys/amd64/vmm/io/vlapic.c (revision 271559) +++ sys/amd64/vmm/io/vlapic.c (working copy) @@ -911,8 +911,12 @@ { struct LAPIC *lapic = vlapic->apic_page; - lapic->tpr = val; - vlapic_update_ppr(vlapic); + if (lapic->tpr != val) { + VCPU_CTR2(vlapic->vm, vlapic->vcpuid, "vlapic TPR changed " + "from %#x to %#x", lapic->tpr, val); + lapic->tpr = val; + vlapic_update_ppr(vlapic); + } } static uint8_t