[PATCH v5 21/36] KVM: arm64: gic-v5: Check for pending PPIs

Sascha Bischoff
2026-02-26T16:02:01Z

This change allows KVM to check for pending PPI interrupts. This has
two main components:

First of all, the effective priority mask is calculated. This is a
combination of the priority mask in the VPEs ICC_PCR_EL1.PRIORITY and
the currently running priority as determined from the VPE's
ICH_APR_EL1. If an interrupt's priority is greater than or equal to
the effective priority mask, it can be signalled. Otherwise, it
cannot.

Secondly, any Enabled and Pending PPIs must be checked against this
compound priority mask. The reqires the PPI priorities to by synced
back to the KVM shadow state on WFI entry - this is skipped in general
operation as it isn't required and is rather expensive. If any Enabled
and Pending PPIs are of sufficient priority to be signalled, then
there are pending PPIs. Else, there are not. This ensures that a VPE
is not woken when it cannot actually process the pending interrupts.

As the PPI priorities are not synced back to the KVM shadow state on
every guest exit, they must by synced prior to checking if there are
pending interrupts for the guest. The sync itself happens in
vgic_v5_put() if, and only if, the vcpu is entering WFI as this is the
only case where it is not planned to run the vcpu thread again. If the
vcpu enters WFI, the vcpu thread will be descheduled and won't be
rescheduled again until it has a pending interrupt, which is checked
from kvm_arch_vcpu_runnable().

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Reviewed-by: Joey Gouly <joey.gouly@arm.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
---
arch/arm64/kvm/vgic/vgic-v5.c | 118 ++++++++++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic.c | 3 +
arch/arm64/kvm/vgic/vgic.h | 1 +
3 files changed, 122 insertions(+)

diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index a230c45db46ee..adf8548a5264c 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -132,6 +132,29 @@ int vgic_v5_finalize_ppi_state(struct kvm *kvm)
return 0;
}

+static u32 vgic_v5_get_effective_priority_mask(struct kvm_vcpu *vcpu)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+ u32 highest_ap, priority_mask;
+
+ /*
+ * Counting the number of trailing zeros gives the current active
+ * priority. Explicitly use the 32-bit version here as we have 32
+ * priorities. 32 then means that there are no active priorities.
+ */
+ highest_ap = cpu_if->vgic_apr ? __builtin_ctz(cpu_if->vgic_apr) : 32;
+
+ /*
+ * An interrupt is of sufficient priority if it is equal to or
+ * greater than the priority mask. Add 1 to the priority mask
+ * (i.e., lower priority) to match the APR logic before taking
+ * the min. This gives us the lowest priority that is masked.
+ */
+ priority_mask = FIELD_GET(FEAT_GCIE_ICH_VMCR_EL2_VPMR, cpu_if->vgic_vmcr);
+
+ return min(highest_ap, priority_mask + 1);
+}
+
/*
* For GICv5, the PPIs are mostly directly managed by the hardware. We (the
* hypervisor) handle the pending, active, enable state save/restore, but don't
@@ -182,6 +205,97 @@ void vgic_v5_set_ppi_ops(struct vgic_irq *irq)
irq->ops = &vgic_v5_ppi_irq_ops;
}

+/*
+ * Sync back the PPI priorities to the vgic_irq shadow state for any interrupts
+ * exposed to the guest (skipping all others).
+ */
+static void vgic_v5_sync_ppi_priorities(struct kvm_vcpu *vcpu)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+ u64 priorityr;
+
+ /*
+ * We have 16 PPI Priority regs, but only have a few interrupts that the
+ * guest is allowed to use. Limit our sync of PPI priorities to those
+ * actually exposed to the guest by first iterating over the mask of
+ * exposed PPIs.
+ */
+ for (int mask_reg = 0; mask_reg < 2; mask_reg++) {
+ u64 mask = vcpu->kvm->arch.vgic.gicv5_vm.vgic_ppi_mask[mask_reg];
+ unsigned long bm_p = 0;
+ int i;
+
+ bitmap_from_arr64(&bm_p, &mask, 64);
+
+ for_each_set_bit(i, &bm_p, 64) {
+ struct vgic_irq *irq;
+ int pri_idx, pri_reg;
+ u32 intid;
+ u8 priority;
+
+ pri_reg = (mask_reg * 64 + i) / 8;
+ pri_idx = (mask_reg * 64 + i) % 8;
+
+ priorityr = cpu_if->vgic_ppi_priorityr[pri_reg];
+ priority = (priorityr >> (pri_idx * 8)) & GENMASK(4, 0);
+
+ intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
+ intid |= FIELD_PREP(GICV5_HWIRQ_ID, mask_reg * 64 + i);
+
+ irq = vgic_get_vcpu_irq(vcpu, intid);
+
+ scoped_guard(raw_spinlock_irqsave, &irq->irq_lock)
+ irq->priority = priority;
+
+ vgic_put_irq(vcpu->kvm, irq);
+ }
+ }
+}
+
+bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu)
+{
+ unsigned int priority_mask;
+
+ priority_mask = vgic_v5_get_effective_priority_mask(vcpu);
+
+ /* If the combined priority mask is 0, nothing can be signalled! */
+ if (!priority_mask)
+ return false;
+
+ for (int reg = 0; reg < 2; reg++) {
+ u64 mask = vcpu->kvm->arch.vgic.gicv5_vm.vgic_ppi_mask[reg];
+ unsigned long bm_p = 0;
+ int i;
+
+ /* Only iterate over the PPIs exposed to the guest */
+ bitmap_from_arr64(&bm_p, &mask, 64);
+
+ for_each_set_bit(i, &bm_p, 64) {
+ bool has_pending = false;
+ struct vgic_irq *irq;
+ u32 intid;
+
+ intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
+ intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg * 64 + i);
+
+ irq = vgic_get_vcpu_irq(vcpu, intid);
+
+ scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) {
+ if (irq->enabled && irq_is_pending(irq) &&
+ irq->priority <= priority_mask)
+ has_pending = true;
+ }
+
+ vgic_put_irq(vcpu->kvm, irq);
+
+ if (has_pending)
+ return true;
+ }
+ }
+
+ return false;
+}
+
/*
* Detect any PPIs state changes, and propagate the state with KVM's
* shadow structures.
@@ -341,6 +455,10 @@ void vgic_v5_put(struct kvm_vcpu *vcpu)
kvm_call_hyp(__vgic_v5_save_apr, cpu_if);

WRITE_ONCE(cpu_if->gicv5_vpe.resident, false);
+
+ /* The shadow priority is only updated on entering WFI */
+ if (vcpu_get_flag(vcpu, IN_WFI))
+ vgic_v5_sync_ppi_priorities(vcpu);
}

void vgic_v5_get_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcrp)
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index 69bfa0f81624c..cd45e5db03d4b 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -1171,6 +1171,9 @@ int kvm_vgic_vcpu_pending_irq(struct kvm_vcpu *vcpu)
unsigned long flags;
struct vgic_vmcr vmcr;

+ if (vgic_is_v5(vcpu->kvm))
+ return vgic_v5_has_pending_ppi(vcpu);
+
if (!vcpu->kvm->arch.vgic.enabled)
return false;

diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 47b9eac06e97a..55c5f4722a0a1 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -366,6 +366,7 @@ void vgic_debug_destroy(struct kvm *kvm);
int vgic_v5_probe(const struct gic_kvm_info *info);
void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
+bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu);
void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu);
void vgic_v5_fold_ppi_state(struct kvm_vcpu *vcpu);
void vgic_v5_load(struct kvm_vcpu *vcpu);
--
2.34.1