Skip to content

Commit

Permalink
KVM: arm64: nv: Request vPE doorbell upon nested ERET to L2
Browse files Browse the repository at this point in the history
Running an L2 guest with GICv4 enabled goes absolutely nowhere, and gets
into a vicious cycle of nested ERET followed by nested exception entry
into the L1.

When KVM does a put on a runnable vCPU, it marks the vPE as nonresident
but does not request a doorbell IRQ. Behind the scenes in the ITS
driver's view of the vCPU, its_vpe::pending_last gets set to true to
indicate that context is still runnable.

This comes to a head when doing the nested ERET into L2. The vPE doesn't
get scheduled on the redistributor as it is exclusively part of the L1's
VGIC context. kvm_vgic_vcpu_pending_irq() returns true because the vPE
appears runnable, and KVM does a nested exception entry into the L1
before L2 ever gets off the ground.

This issue can be papered over by requesting a doorbell IRQ when
descheduling a vPE as part of a nested ERET. KVM needs this anyway to
kick the vCPU out of the L2 when an IRQ becomes pending for the L1.

Link: https://lore.kernel.org/r/20240823212703.3576061-4-oliver.upton@linux.dev
Signed-off-by: Marc Zyngier <maz@kernel.org>
Link: https://lore.kernel.org/r/20250225172930.1850838-13-maz@kernel.org
Signed-off-by: Oliver Upton <oliver.upton@linux.dev>
  • Loading branch information
Oliver Upton committed Mar 3, 2025
1 parent 69c9176 commit 93078ae
Show file tree
Hide file tree
Showing 3 changed files with 21 additions and 1 deletion.
2 changes: 2 additions & 0 deletions arch/arm64/include/asm/kvm_host.h
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,8 @@ struct kvm_vcpu_arch {
#define PMUSERENR_ON_CPU __vcpu_single_flag(sflags, BIT(5))
/* WFI instruction trapped */
#define IN_WFI __vcpu_single_flag(sflags, BIT(6))
/* KVM is currently emulating a nested ERET */
#define IN_NESTED_ERET __vcpu_single_flag(sflags, BIT(7))


/* Pointer to the vcpu's SVE FFR for sve_{save,load}_state() */
Expand Down
2 changes: 2 additions & 0 deletions arch/arm64/kvm/emulate-nested.c
Original file line number Diff line number Diff line change
Expand Up @@ -2503,6 +2503,7 @@ void kvm_emulate_nested_eret(struct kvm_vcpu *vcpu)
}

preempt_disable();
vcpu_set_flag(vcpu, IN_NESTED_ERET);
kvm_arch_vcpu_put(vcpu);

if (!esr_iss_is_eretax(esr))
Expand All @@ -2514,6 +2515,7 @@ void kvm_emulate_nested_eret(struct kvm_vcpu *vcpu)
*vcpu_cpsr(vcpu) = spsr;

kvm_arch_vcpu_load(vcpu, smp_processor_id());
vcpu_clear_flag(vcpu, IN_NESTED_ERET);
preempt_enable();

kvm_pmu_nested_transition(vcpu);
Expand Down
18 changes: 17 additions & 1 deletion arch/arm64/kvm/vgic/vgic-v4.c
Original file line number Diff line number Diff line change
Expand Up @@ -336,14 +336,30 @@ void vgic_v4_teardown(struct kvm *kvm)
its_vm->vpes = NULL;
}

static inline bool vgic_v4_want_doorbell(struct kvm_vcpu *vcpu)
{
if (vcpu_get_flag(vcpu, IN_WFI))
return true;

if (likely(!vcpu_has_nv(vcpu)))
return false;

/*
* GICv4 hardware is only ever used for the L1. Mark the vPE (i.e. the
* L1 context) nonresident and request a doorbell to kick us out of the
* L2 when an IRQ becomes pending.
*/
return vcpu_get_flag(vcpu, IN_NESTED_ERET);
}

int vgic_v4_put(struct kvm_vcpu *vcpu)
{
struct its_vpe *vpe = &vcpu->arch.vgic_cpu.vgic_v3.its_vpe;

if (!vgic_supports_direct_msis(vcpu->kvm) || !vpe->resident)
return 0;

return its_make_vpe_non_resident(vpe, !!vcpu_get_flag(vcpu, IN_WFI));
return its_make_vpe_non_resident(vpe, vgic_v4_want_doorbell(vcpu));
}

int vgic_v4_load(struct kvm_vcpu *vcpu)
Expand Down

0 comments on commit 93078ae

Please sign in to comment.