[PATCH 32/43] KVM: arm64: gic-v5: Support SPI injection

Sascha Bischoff
2026-04-27T16:18:04Z

GICv5 SPI lifecycle is handled by the GICv5 hardware once the pending
state has been injected.

This change adds support for injecting and managing SPIs to the core
VGIC code and GICv5 code. First of all, allow GICv5 SPIs to be looked
up by ID via vgic_get_irq(). Previously, only PPIs were supported.

Two irq_ops are used to inject the SPI pending state into the
hardware, and to append the SPI to the VM's global SPI AP list. The
set_pending_state() irq_op is used to inject the SPI's pending state
into the guest. The queue_irq_unlock irq_op is used to append the SPI
to the SPI AP list - they are not added to a per-VCPU AP list as they
are global to the VM. Also, this would require KVM to track the
affinity of individual interrupts, which would negate much of the
benefit of their lifecycle's being hardware managed.

While the SPIs are on the global AP list, their state is checked on
every vcpu exit, and once they've been consumed they are removed from
the AP list again.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-irs-v5.c | 1 +
arch/arm64/kvm/vgic/vgic-v5.c | 91 +++++++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic.c | 14 +++--
arch/arm64/kvm/vgic/vgic.h | 2 +
4 files changed, 103 insertions(+), 5 deletions(-)

diff --git a/arch/arm64/kvm/vgic/vgic-irs-v5.c b/arch/arm64/kvm/vgic/vgic-irs-v5.c
index 8e69b624194d5..3a2539bf99d0d 100644
--- a/arch/arm64/kvm/vgic/vgic-irs-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-irs-v5.c
@@ -724,6 +724,7 @@ int kvm_vgic_v5_irs_init(struct kvm *kvm, unsigned int nr_spis)
* view it is always enabled.
*/
irq->enabled = 1;
+ vgic_v5_set_spi_ops(irq);
}

nr_spi_bits = fls(roundup_pow_of_two(nr_spis)) - 1;
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index f36d37c694d71..38e37a03ac951 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -1274,6 +1274,97 @@ void vgic_v5_fold_irq_state(struct kvm_vcpu *vcpu)
}
}

+static bool vgic_v5_set_spi_pending_state(struct kvm_vcpu *vcpu,
+ struct vgic_irq *irq)
+{
+ vgic_v5_set_irq_pend(irq->target_vcpu, irq);
+ return true;
+}
+
+/*
+ * Put the SPI on the SPI AP list. No need to kick the VCPU. If it is running,
+ * the interrupt will signal at some point, and if not, then a VPE doorbell will
+ * fire (based on the IAFFID the guest has configured).
+ */
+static bool vgic_v5_spi_queue_irq_unlock(struct kvm *kvm,
+ struct vgic_irq *irq,
+ unsigned long flags)
+ __releases(&irq->irq_lock)
+{
+ struct vgic_dist *vgic_dist = &kvm->arch.vgic;
+
+ lockdep_assert_held(&irq->irq_lock);
+
+ if (WARN_ON(!__irq_is_spi(KVM_DEV_TYPE_ARM_VGIC_V5, irq->intid)))
+ return false;
+
+retry:
+ /*
+ * We're already on the AP list or don't need to be on
+ * one; nothing more to do.
+ */
+ if (irq->vcpu) {
+ raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
+ return true;
+ }
+
+ raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
+
+ /* someone can do stuff here, which we re-check below */
+ raw_spin_lock_irqsave(&vgic_dist->vgic_v5_spi_ap_list_lock, flags);
+ raw_spin_lock(&irq->irq_lock);
+
+ /*
+ * We've lost the race; and have already been queued. Unlock
+ * global AP list, relock IRQ, and retry.
+ */
+ if (unlikely(irq->vcpu)) {
+ raw_spin_unlock(&irq->irq_lock);
+ raw_spin_unlock_irqrestore(&vgic_dist->vgic_v5_spi_ap_list_lock, flags);
+
+ raw_spin_lock_irqsave(&irq->irq_lock, flags);
+
+ goto retry;
+ }
+
+ list_add_tail(&irq->ap_list, &vgic_dist->vgic_v5_spi_ap_list_head);
+
+ /*
+ * Use the VCPU we've been given as the target VCPU to track
+ * that we're on an AP list. We're not queued on that VCPU's AP
+ * list, but in lieu of an AP flag, this will do.
+ */
+ irq->vcpu = irq->target_vcpu;
+
+ raw_spin_unlock(&irq->irq_lock);
+ raw_spin_unlock_irqrestore(&vgic_dist->vgic_v5_spi_ap_list_lock, flags);
+
+ return true;
+}
+
+static struct irq_ops vgic_v5_spi_irq_ops = {
+ .set_pending_state = vgic_v5_set_spi_pending_state,
+ .queue_irq_unlock = vgic_v5_spi_queue_irq_unlock,
+};
+
+void vgic_v5_set_spi_ops(struct vgic_irq *irq)
+{
+ if (WARN_ON(!irq) || WARN_ON(irq->ops))
+ return;
+
+ irq->ops = &vgic_v5_spi_irq_ops;
+}
+
+/* Set the pending state for GICv5 SPIs and LPIs */
+void vgic_v5_set_irq_pend(struct kvm_vcpu *vcpu, struct vgic_irq *irq)
+{
+ if (WARN_ON(__irq_is_ppi(KVM_DEV_TYPE_ARM_VGIC_V5, irq->intid)))
+ return;
+
+ kvm_call_hyp(__vgic_v5_vdpend, irq->intid, irq_is_pending(irq),
+ vcpu->kvm->arch.vgic.gicv5_vm.vm_id);
+}
+
void vgic_v5_load(struct kvm_vcpu *vcpu)
{
bool irichppidis = !vcpu->kvm->arch.vgic.vgic_v5_irs_data->enabled;
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index b35833a4e2bf9..3c8655aee8bbf 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -86,12 +86,16 @@ static struct vgic_irq *vgic_get_lpi(struct kvm *kvm, u32 intid)
*/
struct vgic_irq *vgic_get_irq(struct kvm *kvm, u32 intid)
{
- /* Non-private IRQs are not yet implemented for GICv5 */
- if (vgic_is_v5(kvm))
- return NULL;
-
/* SPIs */
- if (intid >= VGIC_NR_PRIVATE_IRQS &&
+ if (vgic_is_v5(kvm) && __irq_is_spi(KVM_DEV_TYPE_ARM_VGIC_V5, intid)) {
+ u32 int_num = FIELD_GET(GICV5_HWIRQ_ID, intid);
+
+ if (int_num >= kvm->arch.vgic.nr_spis)
+ return NULL;
+
+ int_num = array_index_nospec(int_num, kvm->arch.vgic.nr_spis);
+ return &kvm->arch.vgic.spis[int_num];
+ } else if (intid >= VGIC_NR_PRIVATE_IRQS &&
intid < (kvm->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS)) {
intid = array_index_nospec(intid, kvm->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS);
return &kvm->arch.vgic.spis[intid - VGIC_NR_PRIVATE_IRQS];
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 7eef8ece52dde..b5036170430dd 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -370,6 +370,8 @@ int kvm_vgic_v5_irs_init(struct kvm *kvm, unsigned int nr_spis);
void vgic_v5_teardown(struct kvm *kvm);
int vgic_v5_map_resources(struct kvm *kvm);
void vgic_v5_set_ppi_ops(struct kvm_vcpu *vcpu, u32 vintid);
+void vgic_v5_set_spi_ops(struct vgic_irq *irq);
+void vgic_v5_set_irq_pend(struct kvm_vcpu *vcpu, struct vgic_irq *irq);
bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu);
void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu);
void vgic_v5_fold_irq_state(struct kvm_vcpu *vcpu);
--
2.34.1