Skip to content

Commit 3d91521

Browse files
committed
KVM: nVMX: Honor event priority when emulating PI delivery during VM-Enter
Move the handling of a nested posted interrupt notification that is unblocked by nested VM-Enter (unblocks L1 IRQs when ack-on-exit is enabled by L1) from VM-Enter emulation to vmx_check_nested_events(). To avoid a pointless forced immediate exit, i.e. to not regress IRQ delivery latency when a nested posted interrupt is pending at VM-Enter, block processing of the notification IRQ if and only if KVM must block _all_ events. Unlike injected events, KVM doesn't need to actually enter L2 before updating the vIRR and vmcs02.GUEST_INTR_STATUS, as the resulting L2 IRQ will be blocked by hardware itself, until VM-Enter to L2 completes. Note, very strictly speaking, moving the IRQ from L2's PIR to IRR before entering L2 is still technically wrong. But, practically speaking, only an L1 hypervisor or an L0 userspace that is deliberately checking event priority against PIR=>IRR processing can even notice; L2 will see architecturally correct behavior, as KVM ensures the VM-Enter is finished before doing anything that would effectively preempt the PIR=>IRR movement. Reported-by: Chao Gao <chao.gao@intel.com> Link: https://lore.kernel.org/r/20241101191447.1807602-6-seanjc@google.com Signed-off-by: Sean Christopherson <seanjc@google.com>
1 parent c829d2c commit 3d91521

File tree

1 file changed

+38
-15
lines changed

1 file changed

+38
-15
lines changed

arch/x86/kvm/vmx/nested.c

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3739,14 +3739,6 @@ static int nested_vmx_run(struct kvm_vcpu *vcpu, bool launch)
37393739
if (unlikely(status != NVMX_VMENTRY_SUCCESS))
37403740
goto vmentry_failed;
37413741

3742-
/* Emulate processing of posted interrupts on VM-Enter. */
3743-
if (nested_cpu_has_posted_intr(vmcs12) &&
3744-
kvm_apic_has_interrupt(vcpu) == vmx->nested.posted_intr_nv) {
3745-
vmx->nested.pi_pending = true;
3746-
kvm_make_request(KVM_REQ_EVENT, vcpu);
3747-
kvm_apic_clear_irr(vcpu, vmx->nested.posted_intr_nv);
3748-
}
3749-
37503742
/* Hide L1D cache contents from the nested guest. */
37513743
vmx->vcpu.arch.l1tf_flush_l1d = true;
37523744

@@ -4208,13 +4200,25 @@ static int vmx_check_nested_events(struct kvm_vcpu *vcpu)
42084200
*/
42094201
bool block_nested_exceptions = vmx->nested.nested_run_pending;
42104202
/*
4211-
* New events (not exceptions) are only recognized at instruction
4203+
* Events that don't require injection, i.e. that are virtualized by
4204+
* hardware, aren't blocked by a pending VM-Enter as KVM doesn't need
4205+
* to regain control in order to deliver the event, and hardware will
4206+
* handle event ordering, e.g. with respect to injected exceptions.
4207+
*
4208+
* But, new events (not exceptions) are only recognized at instruction
42124209
* boundaries. If an event needs reinjection, then KVM is handling a
4213-
* VM-Exit that occurred _during_ instruction execution; new events are
4214-
* blocked until the instruction completes.
4210+
* VM-Exit that occurred _during_ instruction execution; new events,
4211+
* irrespective of whether or not they're injected, are blocked until
4212+
* the instruction completes.
4213+
*/
4214+
bool block_non_injected_events = kvm_event_needs_reinjection(vcpu);
4215+
/*
4216+
* Inject events are blocked by nested VM-Enter, as KVM is responsible
4217+
* for managing priority between concurrent events, i.e. KVM needs to
4218+
* wait until after VM-Enter completes to deliver injected events.
42154219
*/
42164220
bool block_nested_events = block_nested_exceptions ||
4217-
kvm_event_needs_reinjection(vcpu);
4221+
block_non_injected_events;
42184222

42194223
if (lapic_in_kernel(vcpu) &&
42204224
test_bit(KVM_APIC_INIT, &apic->pending_events)) {
@@ -4326,18 +4330,26 @@ static int vmx_check_nested_events(struct kvm_vcpu *vcpu)
43264330
if (kvm_cpu_has_interrupt(vcpu) && !vmx_interrupt_blocked(vcpu)) {
43274331
int irq;
43284332

4329-
if (block_nested_events)
4330-
return -EBUSY;
4331-
if (!nested_exit_on_intr(vcpu))
4333+
if (!nested_exit_on_intr(vcpu)) {
4334+
if (block_nested_events)
4335+
return -EBUSY;
4336+
43324337
goto no_vmexit;
4338+
}
43334339

43344340
if (!nested_exit_intr_ack_set(vcpu)) {
4341+
if (block_nested_events)
4342+
return -EBUSY;
4343+
43354344
nested_vmx_vmexit(vcpu, EXIT_REASON_EXTERNAL_INTERRUPT, 0, 0);
43364345
return 0;
43374346
}
43384347

43394348
irq = kvm_cpu_get_extint(vcpu);
43404349
if (irq != -1) {
4350+
if (block_nested_events)
4351+
return -EBUSY;
4352+
43414353
nested_vmx_vmexit(vcpu, EXIT_REASON_EXTERNAL_INTERRUPT,
43424354
INTR_INFO_VALID_MASK | INTR_TYPE_EXT_INTR | irq, 0);
43434355
return 0;
@@ -4356,11 +4368,22 @@ static int vmx_check_nested_events(struct kvm_vcpu *vcpu)
43564368
* and enabling posted interrupts requires ACK-on-exit.
43574369
*/
43584370
if (irq == vmx->nested.posted_intr_nv) {
4371+
/*
4372+
* Nested posted interrupts are delivered via RVI, i.e.
4373+
* aren't injected by KVM, and so can be queued even if
4374+
* manual event injection is disallowed.
4375+
*/
4376+
if (block_non_injected_events)
4377+
return -EBUSY;
4378+
43594379
vmx->nested.pi_pending = true;
43604380
kvm_apic_clear_irr(vcpu, irq);
43614381
goto no_vmexit;
43624382
}
43634383

4384+
if (block_nested_events)
4385+
return -EBUSY;
4386+
43644387
nested_vmx_vmexit(vcpu, EXIT_REASON_EXTERNAL_INTERRUPT,
43654388
INTR_INFO_VALID_MASK | INTR_TYPE_EXT_INTR | irq, 0);
43664389

0 commit comments

Comments
 (0)