Re: [PATCH v5 16/36] KVM: arm64: gic-v5: Implement direct injection of PPIs

Sascha Bischoff
2026-03-05T11:23:59Z

On Wed, 2026-03-04 at 09:35 +0000, Marc Zyngier wrote:
> On Thu, 26 Feb 2026 15:59:33 +0000,
> Sascha Bischoff <Sascha.Bischoff@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@arm.com>
> > Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
> > ---
> >  arch/arm64/kvm/vgic/vgic-v5.c | 15 +++++++++++++++
> >  arch/arm64/kvm/vgic/vgic.c    | 10 ++++++++++
> >  arch/arm64/kvm/vgic/vgic.h    |  1 +
> >  include/kvm/arm_vgic.h        |  1 +
> >  4 files changed, 27 insertions(+)
> >
> > diff --git a/arch/arm64/kvm/vgic/vgic-v5.c
> > b/arch/arm64/kvm/vgic/vgic-v5.c
> > index 5b35c756887a9..f5cd9decfc26e 100644
> > --- a/arch/arm64/kvm/vgic/vgic-v5.c
> > +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> > @@ -86,6 +86,21 @@ 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);
> > + unsigned long *p;
> > +
> > + p = (unsigned long *)&cpu_if->vgic_ppi_dvir[ppi / 64];
> > + __assign_bit(ppi % 64, p, dvi);
> > +
> > + 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..62e58fdf611d3 100644
> > --- a/arch/arm64/kvm/vgic/vgic.c
> > +++ b/arch/arm64/kvm/vgic/vgic.c
> > @@ -577,12 +577,22 @@ 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) &&
> > +     __irq_is_ppi(KVM_DEV_TYPE_ARM_VGIC_V5, irq->intid))
> > + irq->directly_injected =
> > !vgic_v5_set_ppi_dvi(vcpu, irq->hwintid,
> > +
> > true);
> > +
>
> Huh. A couple of things here:
>
> - under what conditions would irq->directly_injected not be set to
>   true for a PPI? That can never happen here AFAICT.

If we're mapping a PPI for a GICv5 guest, then we always want to
directly inject it (caveat: this might change a bit when we get to NV,
but for now this holds). Otherwise, we don't want to set up DVI at all
as the PPI is software driven.

The directly_injected flag can be dropped altogther at this point. It
doesn't do anything useful, so I've done that too.

>
> - we have per-IRQ operations, and PPIs do have such ops attached to
>   them. Why can't this be moved to such a callback?

We can, and I've re-worked this change to do that instead.

>
> >   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 81d464d26534f..d7fe867a27b64 100644
> > --- a/arch/arm64/kvm/vgic/vgic.h
> > +++ b/arch/arm64/kvm/vgic/vgic.h
> > @@ -364,6 +364,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);
>
> Doing the above would keep these things private to the vgic-v5
> implementation.

Agreed. Well, mostly.

The arch timer was a bit more awkward as it adds an irq_op itself, so
I've had to add some code there already to make sure that the
irq_queue_unlock doesn't get dropped when the arch timer does that. The
same applies for DVI if doing it with an irq_op.

static struct irq_ops arch_timer_irq_ops_vgic_v5 = {
.get_input_level = kvm_arch_timer_get_input_level,
.queue_irq_unlock = vgic_v5_ppi_queue_irq_unlock,
.set_direct_injection = vgic_v5_set_ppi_dvi,
};

Thanks,
Sascha

>
> Thanks,
>
> M.
>