KVM: arm64: Resolve vLPI by host IRQ in vgic_v4_unset_forwarding()
authorOliver Upton <oliver.upton@linux.dev>
Fri, 23 May 2025 19:47:20 +0000 (12:47 -0700)
committerMarc Zyngier <maz@kernel.org>
Fri, 30 May 2025 08:11:29 +0000 (09:11 +0100)
The virtual mapping and "GSI" routing of a particular vLPI is subject to
change in response to the guest / userspace. This can be pretty annoying
to deal with when KVM needs to track the physical state that's managed
for vLPI direct injection.

Make vgic_v4_unset_forwarding() resilient by using the host IRQ to
resolve the vgic IRQ. Since this uses the LPI xarray directly, finding
the ITS by doorbell address + grabbing it's its_lock is no longer
necessary. Note that matching the right ITS / ITE is already handled in
vgic_v4_set_forwarding(), and unless there's a bug in KVM's VGIC ITS
emulation the virtual mapping that should remain stable for the lifetime
of the vLPI mapping.

Tested-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Oliver Upton <oliver.upton@linux.dev>
Link: https://lore.kernel.org/r/20250523194722.4066715-4-oliver.upton@linux.dev
Signed-off-by: Marc Zyngier <maz@kernel.org>
arch/arm64/kvm/arm.c
arch/arm64/kvm/vgic/vgic-v4.c
include/kvm/arm_vgic.h

index 36cfcffb40d89c8a66f9c4cb10211cf66ebbdad8..1de49b48e35eeef6075459db50efb15ada68d064 100644 (file)
@@ -2800,8 +2800,7 @@ void kvm_arch_irq_bypass_del_producer(struct irq_bypass_consumer *cons,
        if (irq_entry->type != KVM_IRQ_ROUTING_MSI)
                return;
 
-       kvm_vgic_v4_unset_forwarding(irqfd->kvm, prod->irq,
-                                    &irqfd->irq_entry);
+       kvm_vgic_v4_unset_forwarding(irqfd->kvm, prod->irq);
 }
 
 void kvm_arch_irq_bypass_stop(struct irq_bypass_consumer *cons)
index 01a5de8e9e94e8b904ad9d7f010623bbbcd3a160..1939461081923f3e7d9735e5f043a50c24d967e0 100644 (file)
@@ -508,10 +508,27 @@ out_unlock_irq:
        return ret;
 }
 
-int kvm_vgic_v4_unset_forwarding(struct kvm *kvm, int virq,
-                                struct kvm_kernel_irq_routing_entry *irq_entry)
+static struct vgic_irq *__vgic_host_irq_get_vlpi(struct kvm *kvm, int host_irq)
+{
+       struct vgic_irq *irq;
+       unsigned long idx;
+
+       guard(rcu)();
+       xa_for_each(&kvm->arch.vgic.lpi_xa, idx, irq) {
+               if (!irq->hw || irq->host_irq != host_irq)
+                       continue;
+
+               if (!vgic_try_get_irq_kref(irq))
+                       return NULL;
+
+               return irq;
+       }
+
+       return NULL;
+}
+
+int kvm_vgic_v4_unset_forwarding(struct kvm *kvm, int host_irq)
 {
-       struct vgic_its *its;
        struct vgic_irq *irq;
        unsigned long flags;
        int ret = 0;
@@ -519,31 +536,19 @@ int kvm_vgic_v4_unset_forwarding(struct kvm *kvm, int virq,
        if (!vgic_supports_direct_msis(kvm))
                return 0;
 
-       /*
-        * Get the ITS, and escape early on error (not a valid
-        * doorbell for any of our vITSs).
-        */
-       its = vgic_get_its(kvm, irq_entry);
-       if (IS_ERR(its))
+       irq = __vgic_host_irq_get_vlpi(kvm, host_irq);
+       if (!irq)
                return 0;
 
-       mutex_lock(&its->its_lock);
-
-       ret = vgic_its_resolve_lpi(kvm, its, irq_entry->msi.devid,
-                                  irq_entry->msi.data, &irq);
-       if (ret)
-               goto out;
-
        raw_spin_lock_irqsave(&irq->irq_lock, flags);
-       WARN_ON(irq->hw && irq->host_irq != virq);
+       WARN_ON(irq->hw && irq->host_irq != host_irq);
        if (irq->hw) {
                atomic_dec(&irq->target_vcpu->arch.vgic_cpu.vgic_v3.its_vpe.vlpi_count);
                irq->hw = false;
-               ret = its_unmap_vlpi(virq);
+               ret = its_unmap_vlpi(host_irq);
        }
 
        raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
-out:
-       mutex_unlock(&its->its_lock);
+       vgic_put_irq(kvm, irq);
        return ret;
 }
index 714cef854c1c32d78b0e134422d0f62e93b59912..4a34f7f0a86488a0bda7cc95916e1a4e47b69a07 100644 (file)
@@ -434,8 +434,7 @@ struct kvm_kernel_irq_routing_entry;
 int kvm_vgic_v4_set_forwarding(struct kvm *kvm, int irq,
                               struct kvm_kernel_irq_routing_entry *irq_entry);
 
-int kvm_vgic_v4_unset_forwarding(struct kvm *kvm, int irq,
-                                struct kvm_kernel_irq_routing_entry *irq_entry);
+int kvm_vgic_v4_unset_forwarding(struct kvm *kvm, int host_irq);
 
 int vgic_v4_load(struct kvm_vcpu *vcpu);
 void vgic_v4_commit(struct kvm_vcpu *vcpu);