From: sashiko-bot@kernel.org
To: "Sascha Bischoff" <sascha.bischoff@arm.com>,
kvmarm@lists.linux.dev, kvm@vger.kernel.org
Cc: "Oliver Upton" <oupton@kernel.org>, "Marc Zyngier" <maz@kernel.org>
Subject: Re: [PATCH v2 13/16] KVM: arm64: gic-v5: Implement irq forwarding
Date: Mon, 02 Nov 2026 21:17:09 +0000 [thread overview]
Message-ID: <sashiko-outbox-194187@kernel.org> (raw)
In-Reply-To: <20261102194454.4086911-14-sascha.bischoff@arm.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] KVM: arm64: gic-v5: sleep in atomic context when disabling direct injection
- [High] KVM: arm64: gic-v5: stale host_irq and leaked LPI on unforward failure
- [High] KVM: arm64: gic-v5: unserialized physical ITS MMIO in gicv5_its_itt_cache_inv()
- [Low] KVM: arm64: gic-v5: unused VGIC_V5 check in vgic_supports_direct_msis()
--
commit deaf3d7036edf849d625db99977af89a6c3e3411
Author: Sascha Bischoff <sascha.bischoff@arm.com>
KVM: arm64: gic-v5: Implement irq forwarding
This commit implements direct interrupt injection (IRQ forwarding) for GICv5
via the ITS, placing the GICv4 and GICv5 forwarding hooks behind common
kvm_vgic_set_forwarding() and kvm_vgic_unset_forwarding() shims. It also
tracks the translation cache entry that owns forwarding on each LPI and uses
access_remote_vm() when resolving guest DT/ITT entries from another
process's address space.
> diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c
> index 638ee47f6e27d..3648d3f7c1b29 100644
> --- a/arch/arm64/kvm/vgic/vgic-its-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-its-v5.c
> @@ -38,7 +39,35 @@ static void its_cache_drop_translation(struct kvm *kvm,
> struct its_cache_translation_entry *entry)
> {
> struct vgic_irq *irq = entry->irq;
> + unsigned long flags;
> + bool drop_forwarding_ref = false;
>
> + /*
> + * Multiple translations can share an LPI as we have no way of stopping
> + * the guest from doing so. Only the entry associated with direct injection
> + * may tear down the IRQ's forwarding state, as otherwise we can end up
> + * accidentally tearing something down. Realistically, this only would
> + * result in a performance degradation, but we might as well avoid that
> + * in the first place if we can.
> + *
> + * Keep the lock until the host IRQ is restored, so the producer cannot
> + * free it while we are using its IRQ number.
> + */
> + raw_spin_lock_irqsave(&irq->irq_lock, flags);
> + if (irq->forwarding_owner == entry) {
> + /* The entry is about to be freed, even if unforwarding fails. */
> + irq->forwarding_owner = NULL;
> + if (irq->hw &&
> + !WARN_ON_ONCE(gicv5_its_disable_direct_injection(irq->host_irq))) {
> + irq->hw = false;
> + irq->host_irq = 0;
> + drop_forwarding_ref = true;
> + }
> + }
> + raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
[Severity: High]
Can calling gicv5_its_disable_direct_injection() here and in
kvm_vgic_v5_unset_forwarding() sleep in atomic context?
Both its_cache_drop_translation() and kvm_vgic_v5_unset_forwarding() hold
raw_spin_lock_irqsave(&irq->irq_lock, flags) (and
kvm_arch_update_irqfd_routing() -> kvm_vgic_unset_forwarding() is also
invoked under spin_lock_irq(&kvm->irqfds.lock)). The call chain is:
gicv5_its_disable_direct_injection()
-> irq_set_vcpu_affinity(virq, NULL) [acquires desc->lock raw_spinlock]
-> gicv5_its_set_vcpu_affinity(d, NULL)
-> gicv5_irs_syncr()
-> gicv5_wait_for_op(..., GICV5_IRS_SYNC_STATUSR,
GICV5_IRS_SYNC_STATUSR_IDLE)
In drivers/irqchip/irq-gic-v5-irs.c, gicv5_irs_syncr() uses the sleeping
gicv5_wait_for_op() macro (which expands to readl_poll_timeout() with a 1 us
sleep and calls might_sleep() and usleep_range()) rather than
gicv5_wait_for_op_atomic().
Should gicv5_irs_syncr() use gicv5_wait_for_op_atomic() since it is called
under desc->lock and irq->irq_lock?
[ ... ]
> diff --git a/arch/arm64/kvm/vgic/vgic-mmio.c b/arch/arm64/kvm/vgic/vgic-mmio.c
> index 3c0c858608752..3268e3d3d5170 100644
> --- a/arch/arm64/kvm/vgic/vgic-mmio.c
> +++ b/arch/arm64/kvm/vgic/vgic-mmio.c
> @@ -1144,6 +1144,8 @@ int vgic_register_dist_iodev(struct kvm *kvm, gpa_t dist_base_address,
>
> bool vgic_supports_direct_msis(struct kvm *kvm)
> {
> + const struct vgic_dist *dist = &kvm->arch.vgic;
> +
> /*
> * Deliberately conflate vLPI and vSGI support on GICv4.1 hardware,
> * indirectly allowing userspace to control whether or not vPEs are
> @@ -1152,7 +1154,13 @@ bool vgic_supports_direct_msis(struct kvm *kvm)
> if (system_supports_direct_sgis() && !vgic_supports_direct_sgis(kvm))
> return false;
>
> - return kvm_vgic_global_state.has_gicv4 && vgic_has_its(kvm);
> + if (kvm_vgic_global_state.has_gicv4 && vgic_has_its(kvm))
> + return true;
> + if (kvm_vgic_global_state.type == VGIC_V5 && vgic_has_its(kvm) &&
> + dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
> + return true;
> +
> + return false;
> }
[Severity: Low]
This isn't a bug, but is the new VGIC_V5 check in vgic_supports_direct_msis()
ever reached?
In arch/arm64/kvm/vgic/vgic.h, vgic_supports_direct_irqs() already returns
true early when vgic_is_v5(kvm) is true before calling
vgic_supports_direct_msis(kvm). The only other callers of
vgic_supports_direct_msis() are kvm_vgic_v4_set_forwarding() and
kvm_vgic_v4_unset_forwarding(), whereas kvm_vgic_v5_set_forwarding() and
kvm_vgic_v5_unset_forwarding() check !vgic_has_its(kvm) directly.
Should kvm_vgic_v5_set_forwarding() and kvm_vgic_v5_unset_forwarding() (or
the common kvm_vgic_set_forwarding() and kvm_vgic_unset_forwarding() shims)
call vgic_supports_direct_msis(kvm) instead of vgic_has_its(kvm)?
[ ... ]
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index 18184020e188b..c2ae7b0fb3a7f 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> @@ -1635,3 +1635,151 @@ unsigned long vgic_v5_handle_guest_coresight_idreg_read(u32 offset)
> +int kvm_vgic_v5_set_forwarding(struct kvm *kvm, int virq,
> + struct kvm_kernel_irq_routing_entry *irq_entry)
> +{
[ ... ]
> + ret = gicv5_its_enable_direct_injection(virq, vgic_v5_vm_id(kvm),
> + vgic_v5_get_hwirq_id(irq->intid));
[Severity: High]
What serializes concurrent physical ITS MMIO register accesses in
gicv5_its_itt_cache_inv() when forwarding or unforwarding interrupts?
Enabling and disabling direct injection calls:
gicv5_its_enable_direct_injection() / gicv5_its_disable_direct_injection()
-> irq_set_vcpu_affinity() [locks per-IRQ desc->lock]
-> gicv5_its_set_vcpu_affinity()
-> gicv5_its_itt_cache_inv()
In drivers/irqchip/irq-gic-v5-its.c, gicv5_its_itt_cache_inv() writes the
shared physical ITS registers and waits for completion without a per-ITS lock
in struct gicv5_its_chip_data:
its_writeq_relaxed(its, didr, GICV5_ITS_DIDR);
its_writel_relaxed(its, eidr, GICV5_ITS_EIDR);
its_writel_relaxed(its, eventr, GICV5_ITS_INV_EVENTR);
return gicv5_its_cache_sync(its);
Because irq_set_vcpu_affinity() only holds the per-IRQ desc->lock and
kvm_vgic_v5_unset_forwarding() only holds the per-LPI irq->irq_lock (without
its->its_lock), two CPUs forwarding, unforwarding, or invalidating different
IRQs on the same physical ITS (within the same VM, across VMs, or racing with
host gicv5_its_map_event() / gicv5_its_unmap_event()) can interleave writes
to GICV5_ITS_DIDR and GICV5_ITS_EIDR before GICV5_ITS_INV_EVENTR is written.
Could one CPU overwrite GICV5_ITS_DIDR or GICV5_ITS_EIDR so that the physical
ITS invalidates the wrong DeviceID/EventID entry while gicv5_its_cache_sync()
still sees GICV5_ITS_STATUSR_IDLE and returns success, leaving a stale
virtual or host ITTE cached in the physical ITS?
> + if (ret) {
> + if (ret != -ETIMEDOUT) {
> + vgic_put_irq(kvm, irq);
> + return 0;
> + }
[ ... ]
> +void kvm_vgic_v5_unset_forwarding(struct kvm *kvm, int virq)
> +{
> + struct vgic_irq *irq;
> + bool drop_forwarding_ref = false;
> +
> + if (!vgic_has_its(kvm))
> + return;
> +
> + /*
> + * Note: We don't remove the entry from our translation cache here. It
> + * is up to the guest to invalidate the old translations, and hence the
> + * removal path is tied into ITS_INV_DEVICER and ITS_INV_EVENTR, rather
> + * than being explicitly called here.
> + */
> + irq = vgic_v5_host_irq_get_lpi(kvm, virq);
> + if (!irq)
> + return;
> +
> + scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) {
> + if (irq->hw && irq->host_irq == virq &&
> + !WARN_ON_ONCE(gicv5_its_disable_direct_injection(virq))) {
> + irq->hw = false;
> + irq->host_irq = 0;
> + irq->forwarding_owner = NULL;
> + drop_forwarding_ref = true;
> + }
> + }
> +
> + if (drop_forwarding_ref)
> + vgic_put_irq(kvm, irq);
> +
> + vgic_put_irq(kvm, irq);
> +}
[Severity: High]
If gicv5_its_disable_direct_injection(virq) fails in
kvm_vgic_v5_unset_forwarding(), can a subsequent cache eviction operate on a
freed or reallocated host Linux IRQ number, or leak the LPI and reuse the VM
ID at teardown?
1. kvm_arch_irq_bypass_del_producer() returns void to __disconnect() in
virt/lib/irqbypass.c, so producer unregistration cannot be aborted when
gicv5_its_disable_direct_injection(virq) fails. Once
irq_bypass_unregister_producer() returns, the producer (e.g. VFIO) tears
down and frees virq, which can then be reallocated to another device.
Because irq->forwarding_owner, irq->hw, and irq->host_irq remain set here,
a later guest invalidation or vgic_v5_its_destroy() ->
its_cache_clear_translations() -> its_cache_drop_translation() will call
gicv5_its_disable_direct_injection(irq->host_irq) on that freed or
reallocated virq.
2. Similarly, if kvm_arch_update_irqfd_routing() calls
kvm_vgic_unset_forwarding() when an irqfd routing entry changes from
KVM_IRQ_ROUTING_MSI to a non-MSI type and
gicv5_its_disable_direct_injection() fails, a subsequent
kvm_arch_irq_bypass_del_producer() sees irq_entry->type !=
KVM_IRQ_ROUTING_MSI and returns early without retrying
kvm_vgic_unset_forwarding().
3. If gicv5_its_disable_direct_injection() fails in both
its_cache_drop_translation() and kvm_vgic_v5_unset_forwarding(),
drop_forwarding_ref is never set. When the VM is destroyed,
kvm_vgic_dist_destroy() runs xa_destroy(&dist->lpi_xa) and leaks the
struct vgic_irq, while vgic_v5_teardown() unconditionally calls
vgic_v5_release_vm_id(kvm) even though a physical ITS virtual ITTE may
still reference that VM ID.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102194454.4086911-1-sascha.bischoff@arm.com?part=13
next prev parent reply other threads:[~2026-11-02 21:17 UTC|newest]
Thread overview: 37+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-02 19:44 [PATCH v2 00/16] KVM: arm64: Add GICv5 ITS support Sascha Bischoff
2026-11-02 19:44 ` [PATCH v2 01/16] KVM: arm64: vgic: Split out common LPI allocation Sascha Bischoff
2026-11-03 13:41 ` Fuad Tabba
2026-11-02 19:44 ` [PATCH v2 02/16] KVM: arm64: vgic: Pass opaque device pointer to ITS MMIO callbacks Sascha Bischoff
2026-11-03 13:42 ` Fuad Tabba
2026-11-02 19:44 ` [PATCH v2 03/16] KVM: arm64: gic-v5: Share CoreSight ID register emulation Sascha Bischoff
2026-11-03 13:43 ` Fuad Tabba
2026-11-03 20:37 ` Wei-Lin Chang
2026-11-02 19:44 ` [PATCH v2 04/16] irqchip/gic-v5: Add ITS MMIO register definitions Sascha Bischoff
2026-11-03 13:44 ` Fuad Tabba
2026-11-02 19:44 ` [PATCH v2 05/16] KVM: arm64: gic-v5: Add ITS device skeleton Sascha Bischoff
2026-11-02 20:08 ` sashiko-bot
2026-11-02 19:44 ` [PATCH v2 06/16] KVM: arm64: gic-v5: Add ITS MMIO state emulation Sascha Bischoff
2026-11-02 20:05 ` sashiko-bot
2026-11-03 13:48 ` Fuad Tabba
2026-11-02 19:44 ` [PATCH v2 07/16] KVM: arm64: gic-v5: Add ITS guest table translation emulation Sascha Bischoff
2026-11-02 20:23 ` sashiko-bot
2026-11-02 19:44 ` [PATCH v2 08/16] KVM: arm64: gic-v5: Add ITS translation cache maintenance Sascha Bischoff
2026-11-03 13:54 ` Fuad Tabba
2026-11-02 19:44 ` [PATCH v2 09/16] KVM: arm64: gic-v5: Add ITS MSI injection Sascha Bischoff
2026-11-02 20:38 ` sashiko-bot
2026-11-03 13:56 ` Fuad Tabba
2026-11-02 19:44 ` [PATCH v2 10/16] KVM: arm64: gic-v5: Add ITS userspace register access Sascha Bischoff
2026-11-02 20:52 ` sashiko-bot
2026-11-03 14:22 ` Fuad Tabba
2026-11-02 19:44 ` [PATCH v2 11/16] irqchip/gic-v5: Enable direct injection in ITS Sascha Bischoff
2026-11-02 20:57 ` sashiko-bot
2026-11-03 14:47 ` Fuad Tabba
2026-11-02 19:44 ` [PATCH v2 12/16] KVM: arm64: vgic: Move direct MSI support helper to common MMIO code Sascha Bischoff
2026-11-02 19:44 ` [PATCH v2 13/16] KVM: arm64: gic-v5: Implement irq forwarding Sascha Bischoff
2026-11-02 21:17 ` sashiko-bot [this message]
2026-11-03 15:29 ` Fuad Tabba
2026-11-02 19:44 ` [PATCH v2 14/16] KVM: selftests: Add VGICv5 ITS attribute tests Sascha Bischoff
2026-11-02 19:44 ` [PATCH v2 15/16] KVM: selftests: Add VGICv5 ITS translation cache tests Sascha Bischoff
2026-11-02 21:07 ` sashiko-bot
2026-11-03 16:02 ` Fuad Tabba
2026-11-02 19:44 ` [PATCH v2 16/16] KVM: arm64: gic-v5: Document ITS userspace ABI Sascha Bischoff
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-194187@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=kvm@vger.kernel.org \
--cc=kvmarm@lists.linux.dev \
--cc=maz@kernel.org \
--cc=oupton@kernel.org \
--cc=sascha.bischoff@arm.com \
--cc=sashiko-reviews@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox