Skip to content

Commit 7e71268

Browse files
committed
kvm: nVMX: fix entry with pending interrupt if APICv is enabled
Commit b5861e5 introduced a check on the interrupt-window and NMI-window CPU execution controls in order to inject an external interrupt vmexit before the first guest instruction executes. However, when APIC virtualization is enabled the host does not need a vmexit in order to inject an interrupt at the next interrupt window; instead, it just places the interrupt vector in RVI and the processor will inject it as soon as possible. Therefore, on machines with APICv it is not enough to check the CPU execution controls: the same scenario can also happen if RVI>vPPR. Fixes: b5861e5 Reviewed-by: Nikita Leshchenko <nikita.leshchenko@oracle.com> Cc: Sean Christopherson <sean.j.christopherson@intel.com> Cc: Liran Alon <liran.alon@oracle.com> Cc: Radim Krčmář <rkrcmar@redhat.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
1 parent 2cf7ea9 commit 7e71268

File tree

1 file changed

+26
-12
lines changed

1 file changed

+26
-12
lines changed

arch/x86/kvm/vmx.c

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6162,6 +6162,11 @@ static void vmx_complete_nested_posted_interrupt(struct kvm_vcpu *vcpu)
61626162
nested_mark_vmcs12_pages_dirty(vcpu);
61636163
}
61646164

6165+
static u8 vmx_get_rvi(void)
6166+
{
6167+
return vmcs_read16(GUEST_INTR_STATUS) & 0xff;
6168+
}
6169+
61656170
static bool vmx_guest_apic_has_interrupt(struct kvm_vcpu *vcpu)
61666171
{
61676172
struct vcpu_vmx *vmx = to_vmx(vcpu);
@@ -6174,7 +6179,7 @@ static bool vmx_guest_apic_has_interrupt(struct kvm_vcpu *vcpu)
61746179
WARN_ON_ONCE(!vmx->nested.virtual_apic_page))
61756180
return false;
61766181

6177-
rvi = vmcs_read16(GUEST_INTR_STATUS) & 0xff;
6182+
rvi = vmx_get_rvi();
61786183

61796184
vapic_page = kmap(vmx->nested.virtual_apic_page);
61806185
vppr = *((u32 *)(vapic_page + APIC_PROCPRI));
@@ -10349,6 +10354,14 @@ static int vmx_sync_pir_to_irr(struct kvm_vcpu *vcpu)
1034910354
return max_irr;
1035010355
}
1035110356

10357+
static u8 vmx_has_apicv_interrupt(struct kvm_vcpu *vcpu)
10358+
{
10359+
u8 rvi = vmx_get_rvi();
10360+
u8 vppr = kvm_lapic_get_reg(vcpu->arch.apic, APIC_PROCPRI);
10361+
10362+
return ((rvi & 0xf0) > (vppr & 0xf0));
10363+
}
10364+
1035210365
static void vmx_load_eoi_exitmap(struct kvm_vcpu *vcpu, u64 *eoi_exit_bitmap)
1035310366
{
1035410367
if (!kvm_vcpu_apicv_active(vcpu))
@@ -12593,10 +12606,13 @@ static int enter_vmx_non_root_mode(struct kvm_vcpu *vcpu, u32 *exit_qual)
1259312606
struct vmcs12 *vmcs12 = get_vmcs12(vcpu);
1259412607
bool from_vmentry = !!exit_qual;
1259512608
u32 dummy_exit_qual;
12596-
u32 vmcs01_cpu_exec_ctrl;
12609+
bool evaluate_pending_interrupts;
1259712610
int r = 0;
1259812611

12599-
vmcs01_cpu_exec_ctrl = vmcs_read32(CPU_BASED_VM_EXEC_CONTROL);
12612+
evaluate_pending_interrupts = vmcs_read32(CPU_BASED_VM_EXEC_CONTROL) &
12613+
(CPU_BASED_VIRTUAL_INTR_PENDING | CPU_BASED_VIRTUAL_NMI_PENDING);
12614+
if (likely(!evaluate_pending_interrupts) && kvm_vcpu_apicv_active(vcpu))
12615+
evaluate_pending_interrupts |= vmx_has_apicv_interrupt(vcpu);
1260012616

1260112617
enter_guest_mode(vcpu);
1260212618

@@ -12644,16 +12660,14 @@ static int enter_vmx_non_root_mode(struct kvm_vcpu *vcpu, u32 *exit_qual)
1264412660
* to L1 or delivered directly to L2 (e.g. In case L1 don't
1264512661
* intercept EXTERNAL_INTERRUPT).
1264612662
*
12647-
* Usually this would be handled by L0 requesting a
12648-
* IRQ/NMI window by setting VMCS accordingly. However,
12649-
* this setting was done on VMCS01 and now VMCS02 is active
12650-
* instead. Thus, we force L0 to perform pending event
12651-
* evaluation by requesting a KVM_REQ_EVENT.
12652-
*/
12653-
if (vmcs01_cpu_exec_ctrl &
12654-
(CPU_BASED_VIRTUAL_INTR_PENDING | CPU_BASED_VIRTUAL_NMI_PENDING)) {
12663+
* Usually this would be handled by the processor noticing an
12664+
* IRQ/NMI window request, or checking RVI during evaluation of
12665+
* pending virtual interrupts. However, this setting was done
12666+
* on VMCS01 and now VMCS02 is active instead. Thus, we force L0
12667+
* to perform pending event evaluation by requesting a KVM_REQ_EVENT.
12668+
*/
12669+
if (unlikely(evaluate_pending_interrupts))
1265512670
kvm_make_request(KVM_REQ_EVENT, vcpu);
12656-
}
1265712671

1265812672
/*
1265912673
* Note no nested_vmx_succeed or nested_vmx_fail here. At this point

0 commit comments

Comments
 (0)