From e99ff76f4a02be8542e2c3ecc8843d7339a0200d Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 13 Oct 2026 09:16:46 +0100 Subject: [PATCH] KVM: arm64: vgic-its: Remove the pending state of a discarded LPI DISCARD removes the mapping of an event and instructs the Redistributor to remove the pending state of its LPI (IHI 0069H.b, 5.3.4). KVM only frees the ITE, so an LPI that is pending when the guest discards it stays queued on its target vCPU, and the guest still takes it. Clear the pending state before freeing the ITE. A queued LPI can be pending in a running vCPU's list register, so halt the guest first when the LPI is queued, to fold that state back. A forwarded LPI already loses its pending state in its_free_ite(), whose unmap from the vPE sends a DISCARD to the host ITS. [maz: folded-in rework to only pause the involved vcpus] Fixes: df9f58fbea9bc ("KVM: arm64: vgic-its: Implement ITS command queue command handlers") Suggested-by: Marc Zyngier Signed-off-by: Fuad Tabba Reviewed-by: Oliver Upton Link: https://patch.msgid.link/20261013081646.933312-3-fuad.tabba@linux.dev Signed-off-by: Marc Zyngier --- arch/arm64/kvm/vgic/vgic-its.c | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c index 73d7aa700687..37059d133840 100644 --- a/arch/arm64/kvm/vgic/vgic-its.c +++ b/arch/arm64/kvm/vgic/vgic-its.c @@ -833,13 +833,36 @@ static int vgic_its_cmd_handle_discard(struct kvm *kvm, struct vgic_its *its, struct its_device *device = find_its_device(its, device_id); int ite_esz = vgic_its_get_abi(its)->ite_esz; gpa_t gpa = device->itt_addr + ite->event_id * ite_esz; - /* - * Though the spec talks about removing the pending state, we - * don't bother here since we clear the ITTE anyway and the - * pending state is a property of the ITTE struct. - */ + struct vgic_irq *irq = ite->irq; + struct kvm_vcpu *vcpu, *target; + vgic_its_invalidate_cache(its); + /* + * Only an LPI on a vCPU's AP list can be in its list registers, + * which the exit path folds back, and that exit moves the LPI + * to its target vCPU if the target has changed: pause both. + */ + scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) { + vcpu = irq->vcpu; + target = irq->target_vcpu; + } + + if (vcpu) { + if (target != vcpu) + kvm_pause_vcpu(target); + kvm_pause_vcpu(vcpu); + } + + scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) + irq->pending_latch = false; + + if (vcpu) { + kvm_resume_vcpu(vcpu); + if (target != vcpu) + kvm_resume_vcpu(target); + } + its_free_ite(kvm, ite); return vgic_its_write_entry_lock(its, gpa, 0ULL, ite);