[PATCH 15/32] KVM: arm64: gic-v5: Implement direct injection of PPIs
Marc Zyngier
maz at kernel.org
Wed Dec 17 03:40:31 PST 2025
On Fri, 12 Dec 2025 15:22:40 +0000,
Sascha Bischoff <Sascha.Bischoff at arm.com> wrote:
>
> GICv5 is able to directly inject PPI pending state into a guest using
> a mechanism called DVI whereby the pending bit for a paticular PPI is
> driven directly by the physically-connected hardware. This mechanism
> itself doesn't allow for any ID translation, so the host interrupt is
> directly mapped into a guest with the same interrupt ID.
>
> When mapping a virtual interrupt to a physical interrupt via
> kvm_vgic_map_irq for a GICv5 guest, check if the interrupt itself is a
> PPI or not. If it is, and the host's interrupt ID matches that used
> for the guest DVI is enabled, and the interrupt itself is marked as
> directly_injected.
>
> When the interrupt is unmapped again, this process is reversed, and
> DVI is disabled for the interrupt again.
>
> Note: the expectation is that a directly injected PPI is disabled on
> the host while the guest state is loaded. The reason is that although
> DVI is enabled to drive the guest's pending state directly, the host
> pending state also remains driven. In order to avoid the same PPI
> firing on both the host and the guest, the host's interrupt must be
> disabled (masked). This is left up to the code that owns the device
> generating the PPI as this needs to be handled on a per-VM basis. One
> VM might use DVI, while another might not, in which case the physical
> PPI should be enabled for the latter.
>
> Co-authored-by: Timothy Hayes <timothy.hayes at arm.com>
> Signed-off-by: Timothy Hayes <timothy.hayes at arm.com>
> Signed-off-by: Sascha Bischoff <sascha.bischoff at arm.com>
> ---
> arch/arm64/kvm/vgic/vgic-v5.c | 22 ++++++++++++++++++++++
> arch/arm64/kvm/vgic/vgic.c | 16 ++++++++++++++++
> arch/arm64/kvm/vgic/vgic.h | 1 +
> include/kvm/arm_vgic.h | 1 +
> 4 files changed, 40 insertions(+)
>
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index 2fb2db23ed39a..22558080711eb 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> @@ -54,6 +54,28 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
> return 0;
> }
>
> +/*
> + * Sets/clears the corresponding bit in the ICH_PPI_DVIR register.
> + */
> +int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi)
> +{
> + struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> + u32 ppi = FIELD_GET(GICV5_HWIRQ_ID, irq);
> +
> + if (ppi >= 128)
> + return -EINVAL;
Surely this is bad. *very* bad. How can we get here the first place?
> +
> + if (dvi) {
> + /* Set the bit */
> + cpu_if->vgic_ppi_dvir[ppi / 64] |= 1UL << (ppi % 64);
> + } else {
> + /* Clear the bit */
> + cpu_if->vgic_ppi_dvir[ppi / 64] &= ~(1UL << (ppi % 64));
> + }
This should be simplified:
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index d74cc3543b9a4..f434ee85f7e1a 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -191,8 +191,8 @@ bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
struct vgic_irq *irq)
{
struct vgic_v5_cpu_if *cpu_if;
- const u32 id_bit = BIT_ULL(irq->intid % 64);
const u32 reg = FIELD_GET(GICV5_HWIRQ_ID, irq->intid) / 64;
+ unsigned long *p;
if (!vcpu || !irq)
return false;
@@ -203,10 +203,8 @@ bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
- if (irq_is_pending(irq))
- cpu_if->vgic_ppi_pendr[reg] |= id_bit;
- else
- cpu_if->vgic_ppi_pendr[reg] &= ~id_bit;
+ p = (unsigned long *)&cpu_if->vgic_ppi_pendr[reg];
+ __assign_bit(irq->intid % 64, p, irq_is_pending(irq));
return true;
}
@@ -449,17 +447,13 @@ int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi)
{
struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
u32 ppi = FIELD_GET(GICV5_HWIRQ_ID, irq);
+ unsigned long *p;
if (ppi >= 128)
return -EINVAL;
- if (dvi) {
- /* Set the bit */
- cpu_if->vgic_ppi_dvir[ppi / 64] |= 1UL << (ppi % 64);
- } else {
- /* Clear the bit */
- cpu_if->vgic_ppi_dvir[ppi / 64] &= ~(1UL << (ppi % 64));
- }
+ p = (unsigned long *)&cpu_if->vgic_ppi_dvir[ppi / 64];
+ __assign_bit(ppi % 64, p, dvi);
return 0;
}
(yes, unsigned long and u64 are the same thing on any sane
architecture).
> +
> + return 0;
> +}
> +
> void vgic_v5_load(struct kvm_vcpu *vcpu)
> {
> struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
> index 1005ff5f36235..1fe3dcc997860 100644
> --- a/arch/arm64/kvm/vgic/vgic.c
> +++ b/arch/arm64/kvm/vgic/vgic.c
> @@ -577,12 +577,28 @@ static int kvm_vgic_map_irq(struct kvm_vcpu *vcpu, struct vgic_irq *irq,
> irq->host_irq = host_irq;
> irq->hwintid = data->hwirq;
> irq->ops = ops;
> +
> + if (vgic_is_v5(vcpu->kvm)) {
> + /* Nothing for us to do */
> + if (!irq_is_ppi_v5(irq->intid))
> + return 0;
> +
> + if (FIELD_GET(GICV5_HWIRQ_ID, irq->intid) == irq->hwintid) {
> + if (!vgic_v5_set_ppi_dvi(vcpu, irq->hwintid, true))
> + irq->directly_injected = true;
The error handling gives me the creeps. If we can end-up at this stage
with the wrong INTID, we're screwed.
> + }
> + }
> +
> return 0;
> }
>
> /* @irq->irq_lock must be held */
> static inline void kvm_vgic_unmap_irq(struct vgic_irq *irq)
> {
> + if (irq->directly_injected && vgic_is_v5(irq->target_vcpu->kvm))
> + WARN_ON(vgic_v5_set_ppi_dvi(irq->target_vcpu, irq->hwintid, false));
> +
> + irq->directly_injected = false;
> irq->hw = false;
> irq->hwintid = 0;
> irq->ops = NULL;
> diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
> index 6e1f386dffade..b6e3f5e3aba18 100644
> --- a/arch/arm64/kvm/vgic/vgic.h
> +++ b/arch/arm64/kvm/vgic/vgic.h
> @@ -363,6 +363,7 @@ void vgic_debug_init(struct kvm *kvm);
> void vgic_debug_destroy(struct kvm *kvm);
>
> int vgic_v5_probe(const struct gic_kvm_info *info);
> +int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
> void vgic_v5_load(struct kvm_vcpu *vcpu);
> void vgic_v5_put(struct kvm_vcpu *vcpu);
> void vgic_v5_set_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcr);
> diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
> index 45d83f45b065d..ce9e149b85a58 100644
> --- a/include/kvm/arm_vgic.h
> +++ b/include/kvm/arm_vgic.h
> @@ -163,6 +163,7 @@ struct vgic_irq {
> bool enabled:1;
> bool active:1;
> bool hw:1; /* Tied to HW IRQ */
> + bool directly_injected:1; /* A directly injected HW IRQ */
> bool on_lr:1; /* Present in a CPU LR */
> refcount_t refcount; /* Used for LPIs */
> u32 hwintid; /* HW INTID number */
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
More information about the linux-arm-kernel
mailing list