* [PATCH 00/16] KVM: arm64: Add GICv5 ITS support
@ 2026-10-16 15:57 Sascha Bischoff
2026-10-16 15:57 ` [PATCH 01/16] KVM: arm64: vgic: Split out common LPI allocation Sascha Bischoff
` (15 more replies)
0 siblings, 16 replies; 28+ messages in thread
From: Sascha Bischoff @ 2026-10-16 15:57 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, kvm
Cc: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
peter.maydell, lpieralisi, timothy.hayes, fuad.tabba
Hi all,
This series builds on the GICv5 IRS support now in kvmarm/next (and
soon to be in 7.4, hopefully) and adds support for the GICv5 ITS, as
described by the GICv5 specification [1]. It allows a GICv5 guest to
receive LPIs generated by MSIs, including those from physical PCI
devices mapped into the guest via VFIO.
The ITS is exposed as a separate KVM device, alongside the VGICv5
device. The guest owns the device table and interrupt translation
tables. KVM walks these tables to resolve a DeviceID and EventID to an
LPI, and caches the result until the guest invalidates the relevant
translation or changes the table configuration.
The main pieces are:
* An emulated GICv5 ITS CONFIG_FRAME, including table configuration,
event generation, and invalidation registers.
* Guest table walking for linear and two-level device and interrupt
translation tables, together with a disposable translation cache.
* MSI injection through KVM_SIGNAL_MSI and irqfd. GICv5 requires a
DeviceID for each MSI, and the virtual ITS supports 16-bit EventIDs.
* Direct injection through the host ITS when an interrupt is forwarded
to a vCPU. Forwarding teardown restores the host translation.
* Userspace attributes to configure the ITS address, access its
registers, and reset the emulated state, together with ABI
documentation.
* Selftests for the ITS attributes, guest table translation, MSI
delivery, and guest-driven cache invalidation.
The guest tables remain the authoritative translation state. KVM's
cache can be discarded and rebuilt from guest memory; there is no
separate ITS table data for the VMM to save/restore.
Lorenzo's GICv5 Linux software enablement guide [2] describes how to
set up the GICv5 software stack. These changes have been tested on an
Arm FVP 11.32 release [3].
These changes are based on kvmarm/next-7.4+fixes-7.3-2. The
corresponding kvmtool patches have been posted separately [4].
These changes are also available as a branch at [5].
Thanks for taking the time to look at these changes!
Thanks,
Sascha
[1] https://support.arm.com/documentation/111701/latest
[2] https://linaro.atlassian.net/wiki/x/CQAF-wY
[3] https://support.arm.com/tools-and-software/fixed-virtual-platforms/arm-architecture-fvps
[4] https://lore.kernel.org/all/20261016153939.913764-1-sascha.bischoff@arm.com
[5] https://gitlab.arm.com/linux-arm/linux-sb/-/tree/gicv5_kvm_its_support_v1
Sascha Bischoff (16):
KVM: arm64: vgic: Split out common LPI allocation
KVM: arm64: vgic: Pass opaque device pointer to ITS MMIO callbacks
KVM: arm64: gic-v5: Share CoreSight ID register emulation
irqchip/gic-v5: Add ITS MMIO register definitions
KVM: arm64: gic-v5: Add ITS device skeleton
KVM: arm64: gic-v5: Add ITS MMIO state emulation
KVM: arm64: gic-v5: Add ITS guest table translation emulation
KVM: arm64: gic-v5: Add ITS translation cache maintenance
KVM: arm64: gic-v5: Add ITS MSI injection
KVM: arm64: gic-v5: Add ITS userspace register access
irqchip/gic-v5: Enable direct injection in ITS
KVM: arm64: vgic: Move direct MSI support helper to common MMIO code
KVM: arm64: gic-v5: Implement irq forwarding
KVM: selftests: Add VGICv5 ITS attribute tests
KVM: selftests: Add VGICv5 ITS translation cache tests
KVM: arm64: gic-v5: Document ITS userspace ABI
.../virt/kvm/devices/arm-vgic-v5-its.rst | 102 ++
Documentation/virt/kvm/devices/index.rst | 1 +
arch/arm64/include/uapi/asm/kvm.h | 2 +
arch/arm64/kvm/Makefile | 1 +
arch/arm64/kvm/arm.c | 6 +-
arch/arm64/kvm/vgic/vgic-init.c | 5 +
arch/arm64/kvm/vgic/vgic-irqfd.c | 8 +
arch/arm64/kvm/vgic/vgic-irs-v5.c | 72 +-
arch/arm64/kvm/vgic/vgic-its-v5.c | 1585 +++++++++++++++++
arch/arm64/kvm/vgic/vgic-its.c | 137 +-
arch/arm64/kvm/vgic/vgic-kvm-device.c | 4 +
arch/arm64/kvm/vgic/vgic-mmio-v3.c | 23 -
arch/arm64/kvm/vgic/vgic-mmio.c | 39 +
arch/arm64/kvm/vgic/vgic-mmio.h | 7 +-
arch/arm64/kvm/vgic/vgic-v5.c | 207 +++
arch/arm64/kvm/vgic/vgic.c | 89 +
arch/arm64/kvm/vgic/vgic.h | 19 +
drivers/irqchip/irq-gic-v5-its.c | 134 ++
include/kvm/arm_vgic.h | 120 +-
include/linux/irqchip/arm-gic-v5.h | 136 +-
include/uapi/linux/kvm.h | 2 +
tools/include/uapi/linux/kvm.h | 2 +
tools/testing/selftests/kvm/arm64/vgic_v5.c | 670 ++++++-
.../selftests/kvm/include/arm64/gic_v5.h | 81 +
24 files changed, 3216 insertions(+), 236 deletions(-)
create mode 100644 Documentation/virt/kvm/devices/arm-vgic-v5-its.rst
create mode 100644 arch/arm64/kvm/vgic/vgic-its-v5.c
base-commit: 678f16b6e4767096aa3e3e6eef60e981b3f61e12
--
2.34.1
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH 01/16] KVM: arm64: vgic: Split out common LPI allocation
2026-10-16 15:57 [PATCH 00/16] KVM: arm64: Add GICv5 ITS support Sascha Bischoff
@ 2026-10-16 15:57 ` Sascha Bischoff
2026-10-16 16:14 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 02/16] KVM: arm64: vgic: Pass opaque device pointer to ITS MMIO callbacks Sascha Bischoff
` (14 subsequent siblings)
15 siblings, 1 reply; 28+ messages in thread
From: Sascha Bischoff @ 2026-10-16 15:57 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, kvm
Cc: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
peter.maydell, lpieralisi, timothy.hayes, fuad.tabba
The GICv3 ITS code currently owns vgic_add_lpi(), even though the
first half of the helper is not ITS specific. It allocates or finds
the VM-wide vgic_irq object in lpi_xa and initializes the common LPI
state before the GICv3 ITS code reads the property table and pending
state.
GICv5 needs to reuse the same LPI object lifetime and refcounting, but
without inheriting GICv3 ITS policy such as forcing LPIs into Group 1
or reading the GICv3 property table. Move the common allocation and
xarray insertion logic into vgic.c, expose it through vgic.h, and keep
a GICv3 ITS wrapper for the property-table and pending-state
synchronization.
This keeps the GICv3 behaviour unchanged while making the common LPI
storage usable by other VGIC users.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-its.c | 73 +++++-----------------------------
arch/arm64/kvm/vgic/vgic.c | 72 +++++++++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic.h | 2 +
3 files changed, 83 insertions(+), 64 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c
index e5657cb603567..8e7bee1bae95d 100644
--- a/arch/arm64/kvm/vgic/vgic-its.c
+++ b/arch/arm64/kvm/vgic/vgic-its.c
@@ -66,77 +66,22 @@ static int update_lpi_config(struct kvm *kvm, struct vgic_irq *irq,
__ret; \
})
-/*
- * Creates a new (reference to a) struct vgic_irq for a given LPI.
- * If this LPI is already mapped on another ITS, we increase its refcount
- * and return a pointer to the existing structure.
- * If this is a "new" LPI, we allocate and initialize a new struct vgic_irq.
- * This function returns a pointer to the _unlocked_ structure.
- */
-static struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid,
- struct kvm_vcpu *vcpu)
+static struct vgic_irq *vgic_its_add_lpi(struct kvm *kvm, u32 intid,
+ struct kvm_vcpu *vcpu)
{
- struct vgic_dist *dist = &kvm->arch.vgic;
- struct vgic_irq *irq = vgic_get_irq(kvm, intid), *oldirq;
- unsigned long flags;
+ struct vgic_irq *irq;
int ret;
- /* In this case there is no put, since we keep the reference. */
+ irq = vgic_get_irq(kvm, intid);
if (irq)
return irq;
- irq = kzalloc_obj(struct vgic_irq, GFP_KERNEL_ACCOUNT);
- if (!irq)
- return ERR_PTR(-ENOMEM);
-
- ret = xa_reserve_irq(&dist->lpi_xa, intid, GFP_KERNEL_ACCOUNT);
- if (ret) {
- kfree(irq);
- return ERR_PTR(ret);
- }
-
- INIT_LIST_HEAD(&irq->ap_list);
- raw_spin_lock_init(&irq->irq_lock);
+ irq = vgic_add_lpi(kvm, intid, vcpu);
+ if (IS_ERR(irq))
+ return irq;
- irq->config = VGIC_CONFIG_EDGE;
- refcount_set(&irq->refcount, 1);
- irq->intid = intid;
- irq->target_vcpu = vcpu;
irq->group = 1;
- xa_lock_irqsave(&dist->lpi_xa, flags);
-
- /*
- * There could be a race with another vgic_add_lpi(), so we need to
- * check that we don't add a second list entry with the same LPI.
- */
- oldirq = xa_load(&dist->lpi_xa, intid);
- if (vgic_try_get_irq_ref(oldirq)) {
- /* Someone was faster with adding this LPI, lets use that. */
- kfree(irq);
- irq = oldirq;
- } else {
- /*
- * The entry is either empty or contains a dead LPI (refcount=0)
- * from the deferred release path, pending cleanup by
- * vgic_release_deleted_lpis(). Evict and free it if present.
- */
- oldirq = __xa_store(&dist->lpi_xa, intid, irq,
- GFP_NOWAIT | __GFP_ACCOUNT);
- ret = xa_err(oldirq);
- if (ret) {
- xa_unlock_irqrestore(&dist->lpi_xa, flags);
- kfree(irq);
-
- return ERR_PTR(ret);
- }
-
- if (oldirq && !WARN_ON_ONCE(refcount_read(&oldirq->refcount)))
- kfree_rcu(oldirq, rcu);
- }
-
- xa_unlock_irqrestore(&dist->lpi_xa, flags);
-
/*
* We "cache" the configuration table entries in our struct vgic_irq's.
* However we only have those structs for mapped IRQs, so we read in
@@ -1092,7 +1037,7 @@ static int vgic_its_cmd_handle_mapi(struct kvm *kvm, struct vgic_its *its,
if (its_is_collection_mapped(collection))
vcpu = collection_to_vcpu(kvm, collection);
- irq = vgic_add_lpi(kvm, lpi_nr, vcpu);
+ irq = vgic_its_add_lpi(kvm, lpi_nr, vcpu);
if (IS_ERR(irq)) {
if (new_coll)
vgic_its_free_collection(its, coll_id);
@@ -2203,7 +2148,7 @@ static int vgic_its_restore_ite(struct vgic_its *its, u32 event_id,
if (its_is_collection_mapped(collection))
vcpu = kvm_get_vcpu_by_id(kvm, collection->target_addr);
- irq = vgic_add_lpi(kvm, lpi_id, vcpu);
+ irq = vgic_its_add_lpi(kvm, lpi_id, vcpu);
if (IS_ERR(irq)) {
its_free_ite(kvm, ite);
return PTR_ERR(irq);
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index c2726d4c06e6e..bc0171c479a95 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -79,6 +79,78 @@ static struct vgic_irq *vgic_get_lpi(struct kvm *kvm, u32 intid)
return irq;
}
+/*
+ * Creates a new (reference to a) struct vgic_irq for a given LPI.
+ * If this LPI already exists, we increase its refcount and return it.
+ * If this is a "new" LPI, we allocate and initialize a new struct vgic_irq.
+ * This function returns a pointer to the _unlocked_ structure.
+ */
+struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid,
+ struct kvm_vcpu *vcpu)
+{
+ struct vgic_dist *dist = &kvm->arch.vgic;
+ struct vgic_irq *irq = vgic_get_irq(kvm, intid), *oldirq;
+ unsigned long flags;
+ int ret;
+
+ /* In this case there is no put, since we keep the reference. */
+ if (irq)
+ return irq;
+
+ irq = kzalloc_obj(struct vgic_irq, GFP_KERNEL_ACCOUNT);
+ if (!irq)
+ return ERR_PTR(-ENOMEM);
+
+ ret = xa_reserve_irq(&dist->lpi_xa, intid, GFP_KERNEL_ACCOUNT);
+ if (ret) {
+ kfree(irq);
+ return ERR_PTR(ret);
+ }
+
+ INIT_LIST_HEAD(&irq->ap_list);
+ raw_spin_lock_init(&irq->irq_lock);
+
+ irq->config = VGIC_CONFIG_EDGE;
+ refcount_set(&irq->refcount, 1);
+ irq->intid = intid;
+ irq->target_vcpu = vcpu;
+
+ xa_lock_irqsave(&dist->lpi_xa, flags);
+
+ /*
+ * There could be a race with another vgic_add_lpi(), so we need to
+ * check that we don't add a second list entry with the same LPI.
+ */
+ oldirq = xa_load(&dist->lpi_xa, intid);
+ if (vgic_try_get_irq_ref(oldirq)) {
+ /* Someone was faster with adding this LPI, lets use that. */
+ kfree(irq);
+ irq = oldirq;
+ } else {
+ /*
+ * The entry is either empty or contains a dead LPI (refcount=0)
+ * from the deferred release path, pending cleanup by
+ * vgic_release_deleted_lpis(). Evict and free it if present.
+ */
+ oldirq = __xa_store(&dist->lpi_xa, intid, irq,
+ GFP_NOWAIT | __GFP_ACCOUNT);
+ ret = xa_err(oldirq);
+ if (ret) {
+ xa_unlock_irqrestore(&dist->lpi_xa, flags);
+ kfree(irq);
+
+ return ERR_PTR(ret);
+ }
+
+ if (oldirq && !WARN_ON_ONCE(refcount_read(&oldirq->refcount)))
+ kfree_rcu(oldirq, rcu);
+ }
+
+ xa_unlock_irqrestore(&dist->lpi_xa, flags);
+
+ return irq;
+}
+
/*
* This looks up the virtual interrupt ID to get the corresponding
* struct vgic_irq. It also increases the refcount, so any caller is expected
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 97fc3bdf520e5..c33cdd7098df5 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -280,6 +280,8 @@ vgic_get_mmio_region(struct kvm_vcpu *vcpu, struct vgic_io_device *iodev,
gpa_t addr, int len);
struct vgic_irq *vgic_get_irq(struct kvm *kvm, u32 intid);
struct vgic_irq *vgic_get_vcpu_irq(struct kvm_vcpu *vcpu, u32 intid);
+struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid,
+ struct kvm_vcpu *vcpu);
void vgic_put_irq(struct kvm *kvm, struct vgic_irq *irq);
struct kvm_vcpu *vgic_target_oracle(struct vgic_irq *irq);
bool vgic_get_phys_line_level(struct vgic_irq *irq);
--
2.34.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH 02/16] KVM: arm64: vgic: Pass opaque device pointer to ITS MMIO callbacks
2026-10-16 15:57 [PATCH 00/16] KVM: arm64: Add GICv5 ITS support Sascha Bischoff
2026-10-16 15:57 ` [PATCH 01/16] KVM: arm64: vgic: Split out common LPI allocation Sascha Bischoff
@ 2026-10-16 15:57 ` Sascha Bischoff
2026-10-16 15:57 ` [PATCH 03/16] KVM: arm64: gic-v5: Share CoreSight ID register emulation Sascha Bischoff
` (13 subsequent siblings)
15 siblings, 0 replies; 28+ messages in thread
From: Sascha Bischoff @ 2026-10-16 15:57 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, kvm
Cc: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
peter.maydell, lpieralisi, timothy.hayes, fuad.tabba
The ITS MMIO callbacks currently take a struct vgic_its pointer,
which ties them to the GICv3 ITS. GICv5 needs to use the same MMIO
dispatch path with a different device structure.
Pass an opaque device pointer to the callbacks and cast it in the
GICv3 handlers. This does not change GICv3 behaviour.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-its.c | 55 +++++++++++++++++----------------
arch/arm64/kvm/vgic/vgic-mmio.h | 6 ++--
2 files changed, 32 insertions(+), 29 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c
index 8e7bee1bae95d..dff942b4f4233 100644
--- a/arch/arm64/kvm/vgic/vgic-its.c
+++ b/arch/arm64/kvm/vgic/vgic-its.c
@@ -388,10 +388,10 @@ static int its_sync_lpi_pending_table(struct kvm_vcpu *vcpu)
return ret;
}
-static unsigned long vgic_mmio_read_its_typer(struct kvm *kvm,
- struct vgic_its *its,
+static unsigned long vgic_mmio_read_its_typer(struct kvm *kvm, void *dev,
gpa_t addr, unsigned int len)
{
+ struct vgic_its *its = dev;
const struct vgic_its_abi *abi = vgic_its_get_abi(its);
u64 reg = GITS_TYPER_PLPIS;
@@ -410,10 +410,10 @@ static unsigned long vgic_mmio_read_its_typer(struct kvm *kvm,
return extract_bytes(reg, addr & 7, len);
}
-static unsigned long vgic_mmio_read_its_iidr(struct kvm *kvm,
- struct vgic_its *its,
+static unsigned long vgic_mmio_read_its_iidr(struct kvm *kvm, void *dev,
gpa_t addr, unsigned int len)
{
+ struct vgic_its *its = dev;
u32 val;
val = (its->abi_rev << GITS_IIDR_REV_SHIFT) & GITS_IIDR_REV_MASK;
@@ -421,11 +421,11 @@ static unsigned long vgic_mmio_read_its_iidr(struct kvm *kvm,
return val;
}
-static int vgic_mmio_uaccess_write_its_iidr(struct kvm *kvm,
- struct vgic_its *its,
+static int vgic_mmio_uaccess_write_its_iidr(struct kvm *kvm, void *dev,
gpa_t addr, unsigned int len,
unsigned long val)
{
+ struct vgic_its *its = dev;
u32 rev = GITS_IIDR_REV(val);
if (rev >= NR_ITS_ABIS)
@@ -435,7 +435,7 @@ static int vgic_mmio_uaccess_write_its_iidr(struct kvm *kvm,
}
static unsigned long vgic_mmio_read_its_idregs(struct kvm *kvm,
- struct vgic_its *its,
+ void *dev,
gpa_t addr, unsigned int len)
{
switch (addr & 0xffff) {
@@ -1445,17 +1445,18 @@ static u64 vgic_sanitise_its_cbaser(u64 reg)
return reg;
}
-static unsigned long vgic_mmio_read_its_cbaser(struct kvm *kvm,
- struct vgic_its *its,
+static unsigned long vgic_mmio_read_its_cbaser(struct kvm *kvm, void *dev,
gpa_t addr, unsigned int len)
{
+ struct vgic_its *its = dev;
return extract_bytes(its->cbaser, addr & 7, len);
}
-static void vgic_mmio_write_its_cbaser(struct kvm *kvm, struct vgic_its *its,
+static void vgic_mmio_write_its_cbaser(struct kvm *kvm, void *dev,
gpa_t addr, unsigned int len,
unsigned long val)
{
+ struct vgic_its *its = dev;
/* When GITS_CTLR.Enable is 1, this register is RO. */
if (its->enabled)
return;
@@ -1513,10 +1514,11 @@ static void vgic_its_process_commands(struct kvm *kvm, struct vgic_its *its)
* protects our ring buffer variables, so that there is only one user
* per ITS handling commands at a given time.
*/
-static void vgic_mmio_write_its_cwriter(struct kvm *kvm, struct vgic_its *its,
+static void vgic_mmio_write_its_cwriter(struct kvm *kvm, void *dev,
gpa_t addr, unsigned int len,
unsigned long val)
{
+ struct vgic_its *its = dev;
u64 reg;
if (!its)
@@ -1537,25 +1539,25 @@ static void vgic_mmio_write_its_cwriter(struct kvm *kvm, struct vgic_its *its,
mutex_unlock(&its->cmd_lock);
}
-static unsigned long vgic_mmio_read_its_cwriter(struct kvm *kvm,
- struct vgic_its *its,
+static unsigned long vgic_mmio_read_its_cwriter(struct kvm *kvm, void *dev,
gpa_t addr, unsigned int len)
{
+ struct vgic_its *its = dev;
return extract_bytes(its->cwriter, addr & 0x7, len);
}
-static unsigned long vgic_mmio_read_its_creadr(struct kvm *kvm,
- struct vgic_its *its,
+static unsigned long vgic_mmio_read_its_creadr(struct kvm *kvm, void *dev,
gpa_t addr, unsigned int len)
{
+ struct vgic_its *its = dev;
return extract_bytes(its->creadr, addr & 0x7, len);
}
-static int vgic_mmio_uaccess_write_its_creadr(struct kvm *kvm,
- struct vgic_its *its,
+static int vgic_mmio_uaccess_write_its_creadr(struct kvm *kvm, void *dev,
gpa_t addr, unsigned int len,
unsigned long val)
{
+ struct vgic_its *its = dev;
u32 cmd_offset;
int ret = 0;
@@ -1579,10 +1581,10 @@ static int vgic_mmio_uaccess_write_its_creadr(struct kvm *kvm,
}
#define BASER_INDEX(addr) (((addr) / sizeof(u64)) & 0x7)
-static unsigned long vgic_mmio_read_its_baser(struct kvm *kvm,
- struct vgic_its *its,
+static unsigned long vgic_mmio_read_its_baser(struct kvm *kvm, void *dev,
gpa_t addr, unsigned int len)
{
+ struct vgic_its *its = dev;
u64 reg;
switch (BASER_INDEX(addr)) {
@@ -1601,11 +1603,11 @@ static unsigned long vgic_mmio_read_its_baser(struct kvm *kvm,
}
#define GITS_BASER_RO_MASK (GENMASK_ULL(52, 48) | GENMASK_ULL(58, 56))
-static void vgic_mmio_write_its_baser(struct kvm *kvm,
- struct vgic_its *its,
+static void vgic_mmio_write_its_baser(struct kvm *kvm, void *dev,
gpa_t addr, unsigned int len,
unsigned long val)
{
+ struct vgic_its *its = dev;
const struct vgic_its_abi *abi = vgic_its_get_abi(its);
u64 old, entry_size, table_type;
u64 reg, *regptr, clearbits = 0;
@@ -1660,10 +1662,10 @@ static void vgic_mmio_write_its_baser(struct kvm *kvm,
}
}
-static unsigned long vgic_mmio_read_its_ctlr(struct kvm *vcpu,
- struct vgic_its *its,
+static unsigned long vgic_mmio_read_its_ctlr(struct kvm *vcpu, void *dev,
gpa_t addr, unsigned int len)
{
+ struct vgic_its *its = dev;
u32 reg = 0;
mutex_lock(&its->cmd_lock);
@@ -1676,10 +1678,11 @@ static unsigned long vgic_mmio_read_its_ctlr(struct kvm *vcpu,
return reg;
}
-static void vgic_mmio_write_its_ctlr(struct kvm *kvm, struct vgic_its *its,
+static void vgic_mmio_write_its_ctlr(struct kvm *kvm, void *dev,
gpa_t addr, unsigned int len,
unsigned long val)
{
+ struct vgic_its *its = dev;
mutex_lock(&its->cmd_lock);
/*
@@ -1725,8 +1728,8 @@ static void vgic_mmio_write_its_ctlr(struct kvm *kvm, struct vgic_its *its,
.uaccess_its_write = uwr, \
}
-static void its_mmio_write_wi(struct kvm *kvm, struct vgic_its *its,
- gpa_t addr, unsigned int len, unsigned long val)
+static void its_mmio_write_wi(struct kvm *kvm, void *dev, gpa_t addr,
+ unsigned int len, unsigned long val)
{
/* Ignore */
}
diff --git a/arch/arm64/kvm/vgic/vgic-mmio.h b/arch/arm64/kvm/vgic/vgic-mmio.h
index 38ed730d68ac3..c3b6d3e752d8d 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio.h
+++ b/arch/arm64/kvm/vgic/vgic-mmio.h
@@ -13,13 +13,13 @@ struct vgic_register_region {
union {
unsigned long (*read)(struct kvm_vcpu *vcpu, gpa_t addr,
unsigned int len);
- unsigned long (*its_read)(struct kvm *kvm, struct vgic_its *its,
+ unsigned long (*its_read)(struct kvm *kvm, void *dev,
gpa_t addr, unsigned int len);
};
union {
void (*write)(struct kvm_vcpu *vcpu, gpa_t addr,
unsigned int len, unsigned long val);
- void (*its_write)(struct kvm *kvm, struct vgic_its *its,
+ void (*its_write)(struct kvm *kvm, void *dev,
gpa_t addr, unsigned int len,
unsigned long val);
};
@@ -28,7 +28,7 @@ struct vgic_register_region {
union {
int (*uaccess_write)(struct kvm_vcpu *vcpu, gpa_t addr,
unsigned int len, unsigned long val);
- int (*uaccess_its_write)(struct kvm *kvm, struct vgic_its *its,
+ int (*uaccess_its_write)(struct kvm *kvm, void *dev,
gpa_t addr, unsigned int len,
unsigned long val);
};
--
2.34.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH 03/16] KVM: arm64: gic-v5: Share CoreSight ID register emulation
2026-10-16 15:57 [PATCH 00/16] KVM: arm64: Add GICv5 ITS support Sascha Bischoff
2026-10-16 15:57 ` [PATCH 01/16] KVM: arm64: vgic: Split out common LPI allocation Sascha Bischoff
2026-10-16 15:57 ` [PATCH 02/16] KVM: arm64: vgic: Pass opaque device pointer to ITS MMIO callbacks Sascha Bischoff
@ 2026-10-16 15:57 ` Sascha Bischoff
2026-10-16 15:57 ` [PATCH 04/16] irqchip/gic-v5: Add ITS MMIO register definitions Sascha Bischoff
` (12 subsequent siblings)
15 siblings, 0 replies; 28+ messages in thread
From: Sascha Bischoff @ 2026-10-16 15:57 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, kvm
Cc: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
peter.maydell, lpieralisi, timothy.hayes, fuad.tabba
The GICv5 IRS and ITS expose the same CoreSight identification register
block. Move the register-value switch into a shared helper and describe
the contiguous ID register range with one register-region descriptor.
This keeps the IRS behaviour unchanged while giving the ITS MMIO
emulation a helper it can reuse when it is added in the following commit.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-irs-v5.c | 72 ++----------------------------
arch/arm64/kvm/vgic/vgic-v5.c | 34 ++++++++++++++
arch/arm64/kvm/vgic/vgic.h | 1 +
include/linux/irqchip/arm-gic-v5.h | 2 +
4 files changed, 41 insertions(+), 68 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-irs-v5.c b/arch/arm64/kvm/vgic/vgic-irs-v5.c
index 1874c3c289a6e..63571dc58546c 100644
--- a/arch/arm64/kvm/vgic/vgic-irs-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-irs-v5.c
@@ -520,38 +520,9 @@ static void vgic_v5_mmio_write_irs_ist(struct kvm_vcpu *vcpu, gpa_t addr,
static unsigned long vgic_v5_coresight_read(struct kvm_vcpu *vcpu,
gpa_t addr, unsigned int len)
{
- const size_t offset = addr & (SZ_64K - 1);
+ u32 offset = addr & (SZ_64K - 1);
- switch (offset) {
- case GICV5_CORESIGHT_DEVARCH:
- return GICV5_CORESIGHT_DEVARCH_VAL;
- case GICV5_CORESIGHT_PIDR4:
- return GICV5_CORESIGHT_PIDR4_JEP106_CONT;
- case GICV5_CORESIGHT_PIDR5:
- return GICV5_CORESIGHT_PIDR5_RES0;
- case GICV5_CORESIGHT_PIDR6:
- return GICV5_CORESIGHT_PIDR6_RES0;
- case GICV5_CORESIGHT_PIDR7:
- return GICV5_CORESIGHT_PIDR7_RES0;
- case GICV5_CORESIGHT_PIDR0:
- return GICV5_CORESIGHT_PIDR0_PART_0;
- case GICV5_CORESIGHT_PIDR1:
- return GICV5_CORESIGHT_PIDR1_DES_0_PART_1;
- case GICV5_CORESIGHT_PIDR2:
- return GICV5_CORESIGHT_PIDR2_DES_1;
- case GICV5_CORESIGHT_PIDR3:
- return GICV5_CORESIGHT_PIDR3_REVAND_CMOD;
- case GICV5_CORESIGHT_CIDR0:
- return GICV5_CORESIGHT_CIDR0_VAL;
- case GICV5_CORESIGHT_CIDR1:
- return GICV5_CORESIGHT_CIDR1_VAL;
- case GICV5_CORESIGHT_CIDR2:
- return GICV5_CORESIGHT_CIDR2_VAL;
- case GICV5_CORESIGHT_CIDR3:
- return GICV5_CORESIGHT_CIDR3_VAL;
- default:
- return 0;
- }
+ return vgic_v5_handle_guest_coresight_idreg_read(offset);
}
static unsigned long vgic_v5_mmio_uaccess_read_irs_status(struct kvm_vcpu *vcpu,
@@ -905,43 +876,8 @@ static const struct vgic_register_region vgic_v5_irs_registers[] = {
/* CoreSight identification registers */
REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_DEVARCH,
- vgic_v5_coresight_read, vgic_mmio_write_wi, 4,
- VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_PIDR4,
- vgic_v5_coresight_read, vgic_mmio_write_wi, 4,
- VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_PIDR5,
- vgic_v5_coresight_read, vgic_mmio_write_wi, 4,
- VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_PIDR6,
- vgic_v5_coresight_read, vgic_mmio_write_wi, 4,
- VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_PIDR7,
- vgic_v5_coresight_read, vgic_mmio_write_wi, 4,
- VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_PIDR0,
- vgic_v5_coresight_read, vgic_mmio_write_wi, 4,
- VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_PIDR1,
- vgic_v5_coresight_read, vgic_mmio_write_wi, 4,
- VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_PIDR2,
- vgic_v5_coresight_read, vgic_mmio_write_wi, 4,
- VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_PIDR3,
- vgic_v5_coresight_read, vgic_mmio_write_wi, 4,
- VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_CIDR0,
- vgic_v5_coresight_read, vgic_mmio_write_wi, 4,
- VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_CIDR1,
- vgic_v5_coresight_read, vgic_mmio_write_wi, 4,
- VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_CIDR2,
- vgic_v5_coresight_read, vgic_mmio_write_wi, 4,
- VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_CORESIGHT_CIDR3,
- vgic_v5_coresight_read, vgic_mmio_write_wi, 4,
+ vgic_v5_coresight_read, vgic_mmio_write_wi,
+ GICV5_CORESIGHT_IDREGS_SIZE,
VGIC_ACCESS_32bit),
};
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 91810fbd99afa..18184020e188b 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -1601,3 +1601,37 @@ int vgic_v5_irs_restore_ists(struct kvm *kvm, struct kvm_device_attr *attr)
return ret;
}
+
+unsigned long vgic_v5_handle_guest_coresight_idreg_read(u32 offset)
+{
+ switch (offset) {
+ case GICV5_CORESIGHT_DEVARCH:
+ return GICV5_CORESIGHT_DEVARCH_VAL;
+ case GICV5_CORESIGHT_PIDR4:
+ return GICV5_CORESIGHT_PIDR4_JEP106_CONT;
+ case GICV5_CORESIGHT_PIDR5:
+ return GICV5_CORESIGHT_PIDR5_RES0;
+ case GICV5_CORESIGHT_PIDR6:
+ return GICV5_CORESIGHT_PIDR6_RES0;
+ case GICV5_CORESIGHT_PIDR7:
+ return GICV5_CORESIGHT_PIDR7_RES0;
+ case GICV5_CORESIGHT_PIDR0:
+ return GICV5_CORESIGHT_PIDR0_PART_0;
+ case GICV5_CORESIGHT_PIDR1:
+ return GICV5_CORESIGHT_PIDR1_DES_0_PART_1;
+ case GICV5_CORESIGHT_PIDR2:
+ return GICV5_CORESIGHT_PIDR2_DES_1;
+ case GICV5_CORESIGHT_PIDR3:
+ return GICV5_CORESIGHT_PIDR3_REVAND_CMOD;
+ case GICV5_CORESIGHT_CIDR0:
+ return GICV5_CORESIGHT_CIDR0_VAL;
+ case GICV5_CORESIGHT_CIDR1:
+ return GICV5_CORESIGHT_CIDR1_VAL;
+ case GICV5_CORESIGHT_CIDR2:
+ return GICV5_CORESIGHT_CIDR2_VAL;
+ case GICV5_CORESIGHT_CIDR3:
+ return GICV5_CORESIGHT_CIDR3_VAL;
+ default:
+ return 0;
+ }
+}
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index c33cdd7098df5..66044232ae025 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -388,6 +388,7 @@ int vgic_v5_probe(const struct gic_kvm_info *info);
void vgic_v5_reset(struct kvm_vcpu *vcpu);
int vgic_v5_init(struct kvm *kvm);
int kvm_vgic_v5_irs_init(struct kvm *kvm, unsigned int nr_spis);
+unsigned long vgic_v5_handle_guest_coresight_idreg_read(u32 offset);
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);
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index 299e75bc95255..a4879774124f8 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -498,6 +498,8 @@
#define GICV5_CORESIGHT_CIDR1 0xfff4
#define GICV5_CORESIGHT_CIDR2 0xfff8
#define GICV5_CORESIGHT_CIDR3 0xfffc
+#define GICV5_CORESIGHT_IDREGS_SIZE \
+ (GICV5_CORESIGHT_CIDR3 + 4 - GICV5_CORESIGHT_DEVARCH)
#define GICV5_CORESIGHT_DEVARCH_VAL \
(FIELD_PREP(GENMASK(31, 21), 0x23b) | \
--
2.34.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH 04/16] irqchip/gic-v5: Add ITS MMIO register definitions
2026-10-16 15:57 [PATCH 00/16] KVM: arm64: Add GICv5 ITS support Sascha Bischoff
` (2 preceding siblings ...)
2026-10-16 15:57 ` [PATCH 03/16] KVM: arm64: gic-v5: Share CoreSight ID register emulation Sascha Bischoff
@ 2026-10-16 15:57 ` Sascha Bischoff
2026-10-16 15:57 ` [PATCH 05/16] KVM: arm64: gic-v5: Add ITS device skeleton Sascha Bischoff
` (11 subsequent siblings)
15 siblings, 0 replies; 28+ messages in thread
From: Sascha Bischoff @ 2026-10-16 15:57 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, kvm
Cc: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
peter.maydell, lpieralisi, timothy.hayes, fuad.tabba
Introduce the GICv5 ITS MMIO register offsets and bit definitions in
one place before the virtual ITS starts consuming them.
Keeping the ITS register frame definitions together avoids spreading
register-definition churn across the subsequent KVM ITS emulation
patches.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
include/linux/irqchip/arm-gic-v5.h | 126 +++++++++++++++++++++--------
1 file changed, 92 insertions(+), 34 deletions(-)
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index a4879774124f8..7e64eb0436394 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -364,50 +364,88 @@
/*
* ITS registers and tables structures
*/
-#define GICV5_ITS_IDR1 0x0004
-#define GICV5_ITS_IDR2 0x0008
-#define GICV5_ITS_CR0 0x0080
-#define GICV5_ITS_CR1 0x0084
-#define GICV5_ITS_DT_BASER 0x00c0
-#define GICV5_ITS_DT_CFGR 0x00d0
-#define GICV5_ITS_DIDR 0x0100
-#define GICV5_ITS_EIDR 0x0108
-#define GICV5_ITS_INV_EVENTR 0x010c
-#define GICV5_ITS_INV_DEVICER 0x0110
-#define GICV5_ITS_STATUSR 0x0120
-#define GICV5_ITS_SYNCR 0x0140
-#define GICV5_ITS_SYNC_STATUSR 0x0148
-
-#define GICV5_ITS_IDR1_L2SZ GENMASK(10, 8)
-#define GICV5_ITS_IDR1_ITT_LEVELS BIT(7)
-#define GICV5_ITS_IDR1_DT_LEVELS BIT(6)
-#define GICV5_ITS_IDR1_DEVICEID_BITS GENMASK(5, 0)
+#define GICV5_ITS_IDR0 0x0000
+#define GICV5_ITS_IDR1 0x0004
+#define GICV5_ITS_IDR2 0x0008
+#define GICV5_ITS_IIDR 0x0040
+#define GICV5_ITS_AIDR 0x0044
+#define GICV5_ITS_CR0 0x0080
+#define GICV5_ITS_CR1 0x0084
+#define GICV5_ITS_DT_BASER 0x00c0
+#define GICV5_ITS_DT_CFGR 0x00d0
+#define GICV5_ITS_DIDR 0x0100
+#define GICV5_ITS_EIDR 0x0108
+#define GICV5_ITS_INV_EVENTR 0x010c
+#define GICV5_ITS_INV_DEVICER 0x0110
+#define GICV5_ITS_READ_EVENTR 0x0114
+#define GICV5_ITS_READ_EVENT_DATAR 0x0118
+#define GICV5_ITS_STATUSR 0x0120
+#define GICV5_ITS_SYNCR 0x0140
+#define GICV5_ITS_SYNC_STATUSR 0x0148
+#define GICV5_ITS_GEN_EVENT_DIDR 0x0180
+#define GICV5_ITS_GEN_EVENT_EIDR 0x0188
+#define GICV5_ITS_GEN_EVENTR 0x018c
+#define GICV5_ITS_GEN_EVENT_STATUSR 0x0190
+#define GICV5_ITS_MEC_IDR 0x01c0
+#define GICV5_ITS_MEC_MECID_R 0x01c4
+#define GICV5_ITS_MPAM_IDR 0x0200
+#define GICV5_ITS_MPAM_PARTID_R 0x0204
+#define GICV5_ITS_SWERR_STATUSR 0x0240
+#define GICV5_ITS_SWERR_SYNDROMER0 0x0248
+#define GICV5_ITS_SWERR_SYNDROMER1 0x0250
+#define GICV5_ITS_MPAM_PMGR 0x0258
+
+#define GICV5_ITS_TRANSLATER 0x0000
+#define GICV5_ITS_RL_TRANSLATER 0x0008
+
+#define GICV5_ITS_IDR0_ITSID GENMASK(31, 16)
+#define GICV5_ITS_IDR0_SWE BIT(8)
+#define GICV5_ITS_IDR0_MPAM BIT(7)
+#define GICV5_ITS_IDR0_MEC BIT(6)
+#define GICV5_ITS_IDR0_PA_RANGE GENMASK(5, 2)
+#define GICV5_ITS_IDR0_DOM GENMASK(1, 0)
+
+#define GICV5_ITS_IDR0_DOMAIN_SECURE 0b00
+#define GICV5_ITS_IDR0_DOMAIN_NON_SECURE 0b01
+#define GICV5_ITS_IDR0_DOMAIN_EL3 0b10
+#define GICV5_ITS_IDR0_DOMAIN_REALM 0b11
+
+#define GICV5_ITS_IDR1_L2SZ GENMASK(10, 8)
+#define GICV5_ITS_IDR1_ITT_LEVELS BIT(7)
+#define GICV5_ITS_IDR1_DT_LEVELS BIT(6)
+#define GICV5_ITS_IDR1_DEVICEID_BITS GENMASK(5, 0)
#define GICV5_ITS_IDR1_L2SZ_SUPPORT_4KB(r) FIELD_GET(BIT(8), (r))
#define GICV5_ITS_IDR1_L2SZ_SUPPORT_16KB(r) FIELD_GET(BIT(9), (r))
#define GICV5_ITS_IDR1_L2SZ_SUPPORT_64KB(r) FIELD_GET(BIT(10), (r))
-#define GICV5_ITS_IDR2_XDMN_EVENTs GENMASK(6, 5)
-#define GICV5_ITS_IDR2_EVENTID_BITS GENMASK(4, 0)
+#define GICV5_ITS_IDR2_XDMN_EVENTS GENMASK(6, 5)
+#define GICV5_ITS_IDR2_EVENTID_BITS GENMASK(4, 0)
-#define GICV5_ITS_CR0_IDLE BIT(1)
-#define GICV5_ITS_CR0_ITSEN BIT(0)
+#define GICV5_ITS_IIDR_PRODUCT_ID GENMASK(31, 20)
+#define GICV5_ITS_IIDR_VARIANT GENMASK(19, 16)
+#define GICV5_ITS_IIDR_REVISION GENMASK(15, 12)
+#define GICV5_ITS_IIDR_IMPLEMENTER GENMASK(11, 0)
-#define GICV5_ITS_CR1_ITT_RA BIT(7)
-#define GICV5_ITS_CR1_DT_RA BIT(6)
-#define GICV5_ITS_CR1_IC GENMASK(5, 4)
-#define GICV5_ITS_CR1_OC GENMASK(3, 2)
-#define GICV5_ITS_CR1_SH GENMASK(1, 0)
+#define GICV5_ITS_AIDR_COMPONENT GENMASK(11, 8)
+#define GICV5_ITS_AIDR_ARCHMAJORREV GENMASK(7, 4)
+#define GICV5_ITS_AIDR_ARCHMINORREV GENMASK(3, 0)
-#define GICV5_ITS_DT_CFGR_STRUCTURE BIT(16)
-#define GICV5_ITS_DT_CFGR_L2SZ GENMASK(7, 6)
-#define GICV5_ITS_DT_CFGR_DEVICEID_BITS GENMASK(5, 0)
+#define GICV5_ITS_CR0_IDLE BIT(1)
+#define GICV5_ITS_CR0_ITSEN BIT(0)
-#define GICV5_ITS_DT_BASER_ADDR_MASK GENMASK_ULL(55, 3)
+#define GICV5_ITS_CR1_ITT_RA BIT(7)
+#define GICV5_ITS_CR1_DT_RA BIT(6)
+#define GICV5_ITS_CR1_IC GENMASK(5, 4)
+#define GICV5_ITS_CR1_OC GENMASK(3, 2)
+#define GICV5_ITS_CR1_SH GENMASK(1, 0)
-#define GICV5_ITS_INV_DEVICER_I BIT(31)
-#define GICV5_ITS_INV_DEVICER_EVENTID_BITS GENMASK(5, 1)
-#define GICV5_ITS_INV_DEVICER_L1 BIT(0)
+#define GICV5_ITS_DT_BASER_ADDR_MASK GENMASK_ULL(55, 3)
+#define GICV5_ITS_DT_BASER_ADDR_SHIFT 3ULL
+
+#define GICV5_ITS_DT_CFGR_STRUCTURE BIT(16)
+#define GICV5_ITS_DT_CFGR_L2SZ GENMASK(7, 6)
+#define GICV5_ITS_DT_CFGR_DEVICEID_BITS GENMASK(5, 0)
#define GICV5_ITS_DIDR_DEVICEID GENMASK_ULL(31, 0)
@@ -417,6 +455,17 @@
#define GICV5_ITS_INV_EVENTR_ITT_L2SZ GENMASK(2, 1)
#define GICV5_ITS_INV_EVENTR_L1 BIT(0)
+#define GICV5_ITS_INV_DEVICER_I BIT(31)
+#define GICV5_ITS_INV_DEVICER_EVENTID_BITS GENMASK(5, 1)
+#define GICV5_ITS_INV_DEVICER_L1 BIT(0)
+
+#define GICV5_ITS_READ_EVENTR_R BIT(31)
+
+#define GICV5_ITS_READ_EVENT_DATAR_VIRT BIT_ULL(63)
+#define GICV5_ITS_READ_EVENT_DATAR_VM_ID GENMASK_ULL(47, 32)
+#define GICV5_ITS_READ_EVENT_DATAR_VALID BIT_ULL(31)
+#define GICV5_ITS_READ_EVENT_DATAR_LPI_ID GENMASK_ULL(23, 0)
+
#define GICV5_ITS_STATUSR_IDLE BIT(0)
#define GICV5_ITS_SYNCR_SYNC BIT_ULL(63)
@@ -425,6 +474,15 @@
#define GICV5_ITS_SYNC_STATUSR_IDLE BIT(0)
+#define GICV5_ITS_GEN_EVENT_DIDR_DEVICEID GENMASK_ULL(31, 0)
+
+#define GICV5_ITS_GEN_EVENT_EIDR_EVENTID GENMASK(15, 0)
+
+#define GICV5_ITS_GEN_EVENTR_R BIT(31)
+#define GICV5_ITS_GEN_EVENTR_TARGET_DOMAIN GENMASK(1, 0)
+
+#define GICV5_ITS_GEN_EVENT_STATUSR_IDLE BIT(0)
+
#define GICV5_DTL1E_VALID BIT_ULL(0)
/* Note that there is no shift for the address by design */
#define GICV5_DTL1E_L2_ADDR_MASK GENMASK_ULL(55, 3)
--
2.34.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH 05/16] KVM: arm64: gic-v5: Add ITS device skeleton
2026-10-16 15:57 [PATCH 00/16] KVM: arm64: Add GICv5 ITS support Sascha Bischoff
` (3 preceding siblings ...)
2026-10-16 15:57 ` [PATCH 04/16] irqchip/gic-v5: Add ITS MMIO register definitions Sascha Bischoff
@ 2026-10-16 15:57 ` Sascha Bischoff
2026-10-16 15:57 ` [PATCH 06/16] KVM: arm64: gic-v5: Add ITS MMIO state emulation Sascha Bischoff
` (10 subsequent siblings)
15 siblings, 0 replies; 28+ messages in thread
From: Sascha Bischoff @ 2026-10-16 15:57 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, kvm
Cc: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
peter.maydell, lpieralisi, timothy.hayes, fuad.tabba
Introduce the basic KVM device plumbing for a GICv5 ITS. This adds the
GICv5 ITS device type and address attribute, registers the device ops,
and wires a GICv5 ITS IODEV into the generic VGIC MMIO dispatch path.
Add struct vgic_v5_its to hold the ITS base address, the IODEV plumbing,
the backing KVM device pointer, and the general ITS mutex. Allocate this
state from the KVM device create path, initialise the mutex and base
address, and mark the VM as having an ITS. GICv5 MSIs require a DeviceID,
so set msis_require_devid when the ITS is created.
Record the VGIC model required by an ITS and reject mismatched device
creation in either order. Check GICv3 ITS creation as well, so the two
ITS models cannot be combined in one VM.
The MMIO region is registered once userspace provides the ITS base
address through KVM_VGIC_V5_ADDR_TYPE_ITS. For now the IODEV only
provides a RAZ/WI placeholder region; the actual ITS register emulation
is added separately.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/include/uapi/asm/kvm.h | 2 +
arch/arm64/kvm/Makefile | 1 +
arch/arm64/kvm/vgic/vgic-init.c | 5 +
arch/arm64/kvm/vgic/vgic-its-v5.c | 187 ++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic-its.c | 9 ++
arch/arm64/kvm/vgic/vgic-kvm-device.c | 4 +
arch/arm64/kvm/vgic/vgic-mmio-v3.c | 10 --
arch/arm64/kvm/vgic/vgic-mmio.c | 18 +++
arch/arm64/kvm/vgic/vgic-mmio.h | 1 +
arch/arm64/kvm/vgic/vgic.h | 1 +
include/kvm/arm_vgic.h | 18 ++-
include/uapi/linux/kvm.h | 2 +
tools/include/uapi/linux/kvm.h | 2 +
13 files changed, 249 insertions(+), 11 deletions(-)
create mode 100644 arch/arm64/kvm/vgic/vgic-its-v5.c
diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
index 65ee09bd14636..8c701173a2d88 100644
--- a/arch/arm64/include/uapi/asm/kvm.h
+++ b/arch/arm64/include/uapi/asm/kvm.h
@@ -99,8 +99,10 @@ struct kvm_regs {
/* Supported VGICv5 address types */
#define KVM_VGIC_V5_ADDR_TYPE_IRS 6
+#define KVM_VGIC_V5_ADDR_TYPE_ITS 7
#define KVM_VGIC_V5_IRS_SIZE (2 * SZ_64K)
+#define KVM_VGIC_V5_ITS_SIZE (2 * SZ_64K)
#define KVM_ARM_VCPU_POWER_OFF 0 /* CPU is started in OFF state */
#define KVM_ARM_VCPU_EL1_32BIT 1 /* CPU running a 32bit VM */
diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile
index 7aaeeb84e788e..47923e1047f16 100644
--- a/arch/arm64/kvm/Makefile
+++ b/arch/arm64/kvm/Makefile
@@ -25,6 +25,7 @@ kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \
vgic/vgic-mmio-v3.o vgic/vgic-kvm-device.o \
vgic/vgic-its.o vgic/vgic-debug.o vgic/vgic-v3-nested.o \
vgic/vgic-v5.o vgic/vgic-v5-tables.o vgic/vgic-irs-v5.o \
+ vgic/vgic-its-v5.o \
vgic-sys-reg-v5.o
kvm-$(CONFIG_HW_PERF_EVENTS) += pmu-emul.o pmu.o
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index b5ea1e39c6862..48a92555fa6ab 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -125,6 +125,11 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
goto out_unlock;
}
+ if (kvm->arch.vgic.its_model && kvm->arch.vgic.its_model != type) {
+ ret = -ENODEV;
+ goto out_unlock;
+ }
+
if (kvm_id_regs_final(kvm))
goto out_unlock;
ret = 0;
diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c
new file mode 100644
index 0000000000000..f8184b4ae0ccd
--- /dev/null
+++ b/arch/arm64/kvm/vgic/vgic-its-v5.c
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 ARM Limited, All Rights Reserved.
+ */
+
+#include <linux/kvm_host.h>
+#include <linux/slab.h>
+
+#include <asm/kvm_mmu.h>
+
+#include <kvm/arm_vgic.h>
+
+#include "vgic.h"
+#include "vgic-mmio.h"
+
+static unsigned long vgic_v5_mmio_read_its_raz(struct kvm *kvm, void *dev,
+ gpa_t addr, unsigned int len)
+{
+ return 0;
+}
+
+static void vgic_v5_mmio_write_its_wi(struct kvm *kvm, void *dev, gpa_t addr,
+ unsigned int len, unsigned long val)
+{
+}
+
+#define REGISTER_ITS_V5_DESC(off, rd, wr, length, acc) \
+ { \
+ .reg_offset = off, .len = length, .access_flags = acc, \
+ .its_read = rd, .its_write = wr, \
+ }
+
+static const struct vgic_register_region vgic_v5_its_registers[] = {
+ REGISTER_ITS_V5_DESC(0, vgic_v5_mmio_read_its_raz,
+ vgic_v5_mmio_write_its_wi, KVM_VGIC_V5_ITS_SIZE,
+ VGIC_ACCESS_32bit),
+};
+
+static int vgic_v5_its_create(struct kvm_device *dev, u32 type)
+{
+ struct vgic_v5_its *its;
+
+ if (type != KVM_DEV_TYPE_ARM_VGIC_V5_ITS)
+ return -ENODEV;
+
+ its = kzalloc_obj(*its, GFP_KERNEL_ACCOUNT);
+ if (!its)
+ return -ENOMEM;
+
+ mutex_init(&its->its_lock);
+
+ guard(mutex)(&dev->kvm->arch.config_lock);
+ if ((irqchip_in_kernel(dev->kvm) && !vgic_is_v5(dev->kvm)) ||
+ (dev->kvm->arch.vgic.its_model &&
+ dev->kvm->arch.vgic.its_model != KVM_DEV_TYPE_ARM_VGIC_V5)) {
+ kfree(its);
+ return -ENODEV;
+ }
+
+ its->vgic_v5_its_base = VGIC_ADDR_UNDEF;
+ its->dev = dev;
+
+ dev->kvm->arch.vgic.msis_require_devid = true;
+ dev->kvm->arch.vgic.has_its = true;
+ dev->kvm->arch.vgic.its_model = KVM_DEV_TYPE_ARM_VGIC_V5;
+ dev->private = its;
+
+ return 0;
+}
+
+static void vgic_v5_its_destroy(struct kvm_device *kvm_dev)
+{
+ struct vgic_v5_its *its = kvm_dev->private;
+
+ kfree(its);
+ kfree(kvm_dev);
+}
+
+static int vgic_v5_register_its_iodev(struct kvm *kvm, struct vgic_v5_its *its,
+ u64 addr)
+{
+ struct vgic_io_device *iodev = &its->iodev;
+ int ret;
+
+ mutex_lock(&kvm->slots_lock);
+ if (!IS_VGIC_ADDR_UNDEF(its->vgic_v5_its_base)) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ if (!IS_ALIGNED(addr, SZ_64K)) {
+ kvm_err("ITS Base address is not aligned to 64k\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ its->vgic_v5_its_base = addr;
+ iodev->base_addr = addr;
+ iodev->regions = vgic_v5_its_registers;
+ iodev->nr_regions = ARRAY_SIZE(vgic_v5_its_registers);
+ iodev->iodev_type = IODEV_GICV5_ITS;
+ iodev->redist_vcpu = NULL;
+ iodev->v5its = its;
+ kvm_iodevice_init(&iodev->dev, &kvm_io_gic_ops);
+
+ ret = kvm_io_bus_register_dev(kvm, KVM_MMIO_BUS, iodev->base_addr,
+ KVM_VGIC_V5_ITS_SIZE, &iodev->dev);
+out:
+ mutex_unlock(&kvm->slots_lock);
+
+ return ret;
+}
+
+static int vgic_v5_its_set_attr(struct kvm_device *dev,
+ struct kvm_device_attr *attr)
+{
+ struct vgic_v5_its *its = dev->private;
+ u64 __user *uaddr = (u64 __user *)(long)attr->addr;
+ unsigned long type = (unsigned long)attr->attr;
+ u64 addr;
+ int ret;
+
+ if (attr->group != KVM_DEV_ARM_VGIC_GRP_ADDR)
+ return -ENXIO;
+
+ if (type != KVM_VGIC_V5_ADDR_TYPE_ITS)
+ return -ENODEV;
+
+ if (copy_from_user(&addr, uaddr, sizeof(addr)))
+ return -EFAULT;
+
+ ret = vgic_check_iorange(dev->kvm, its->vgic_v5_its_base, addr,
+ SZ_64K, KVM_VGIC_V5_ITS_SIZE);
+ if (ret)
+ return ret;
+
+ return vgic_v5_register_its_iodev(dev->kvm, its, addr);
+}
+
+static int vgic_v5_its_get_attr(struct kvm_device *dev,
+ struct kvm_device_attr *attr)
+{
+ struct vgic_v5_its *its = dev->private;
+ u64 __user *uaddr = (u64 __user *)(long)attr->addr;
+ unsigned long type = (unsigned long)attr->attr;
+ u64 addr = its->vgic_v5_its_base;
+
+ if (attr->group != KVM_DEV_ARM_VGIC_GRP_ADDR)
+ return -ENXIO;
+
+ if (type != KVM_VGIC_V5_ADDR_TYPE_ITS)
+ return -ENODEV;
+
+ if (copy_to_user(uaddr, &addr, sizeof(addr)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int vgic_v5_its_has_attr(struct kvm_device *dev,
+ struct kvm_device_attr *attr)
+{
+ if (attr->group != KVM_DEV_ARM_VGIC_GRP_ADDR)
+ return -ENXIO;
+
+ switch (attr->attr) {
+ case KVM_VGIC_V5_ADDR_TYPE_ITS:
+ return 0;
+ default:
+ return -ENXIO;
+ }
+}
+
+static struct kvm_device_ops kvm_arm_vgic_v5_its_ops = {
+ .name = "kvm-arm-vgic-v5-its",
+ .create = vgic_v5_its_create,
+ .destroy = vgic_v5_its_destroy,
+ .set_attr = vgic_v5_its_set_attr,
+ .get_attr = vgic_v5_its_get_attr,
+ .has_attr = vgic_v5_its_has_attr,
+};
+
+int kvm_vgic_v5_register_its_device(void)
+{
+ return kvm_register_device_ops(&kvm_arm_vgic_v5_its_ops,
+ KVM_DEV_TYPE_ARM_VGIC_V5_ITS);
+}
diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c
index dff942b4f4233..6dfc2bab3ba60 100644
--- a/arch/arm64/kvm/vgic/vgic-its.c
+++ b/arch/arm64/kvm/vgic/vgic-its.c
@@ -1823,6 +1823,14 @@ static int vgic_its_create(struct kvm_device *dev, u32 type)
mutex_lock(&dev->kvm->arch.config_lock);
+ if ((irqchip_in_kernel(dev->kvm) && !vgic_is_v3(dev->kvm)) ||
+ (dev->kvm->arch.vgic.its_model &&
+ dev->kvm->arch.vgic.its_model != KVM_DEV_TYPE_ARM_VGIC_V3)) {
+ mutex_unlock(&dev->kvm->arch.config_lock);
+ kfree(its);
+ return -ENODEV;
+ }
+
if (vgic_initialized(dev->kvm)) {
ret = vgic_v4_init(dev->kvm);
if (ret < 0) {
@@ -1851,6 +1859,7 @@ static int vgic_its_create(struct kvm_device *dev, u32 type)
dev->kvm->arch.vgic.msis_require_devid = true;
dev->kvm->arch.vgic.has_its = true;
+ dev->kvm->arch.vgic.its_model = KVM_DEV_TYPE_ARM_VGIC_V3;
its->enabled = false;
its->dev = dev;
diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c
index 1c205cb1361fd..e7630f68e2a74 100644
--- a/arch/arm64/kvm/vgic/vgic-kvm-device.c
+++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c
@@ -369,6 +369,10 @@ int kvm_register_vgic_device(unsigned long type)
case KVM_DEV_TYPE_ARM_VGIC_V5:
ret = kvm_register_device_ops(&kvm_arm_vgic_v5_ops,
KVM_DEV_TYPE_ARM_VGIC_V5);
+ if (ret)
+ break;
+
+ ret = kvm_vgic_v5_register_its_device();
break;
}
diff --git a/arch/arm64/kvm/vgic/vgic-mmio-v3.c b/arch/arm64/kvm/vgic/vgic-mmio-v3.c
index 5ab789accc9d4..f5e6c1fe52431 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio-v3.c
+++ b/arch/arm64/kvm/vgic/vgic-mmio-v3.c
@@ -38,16 +38,6 @@ u64 update_64bit_reg(u64 reg, unsigned int offset, unsigned int len,
return reg | ((u64)val << lower);
}
-bool vgic_has_its(struct kvm *kvm)
-{
- struct vgic_dist *dist = &kvm->arch.vgic;
-
- if (dist->vgic_model != KVM_DEV_TYPE_ARM_VGIC_V3)
- return false;
-
- return dist->has_its;
-}
-
bool vgic_supports_direct_msis(struct kvm *kvm)
{
/*
diff --git a/arch/arm64/kvm/vgic/vgic-mmio.c b/arch/arm64/kvm/vgic/vgic-mmio.c
index fddb9da0403d5..755bc2d74c352 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio.c
+++ b/arch/arm64/kvm/vgic/vgic-mmio.c
@@ -1068,6 +1068,9 @@ static int dispatch_mmio_read(struct kvm_vcpu *vcpu, struct kvm_io_device *dev,
case IODEV_GICV5_IRS:
data = region->read(vcpu, addr, len);
break;
+ case IODEV_GICV5_ITS:
+ data = region->its_read(vcpu->kvm, iodev->v5its, addr, len);
+ break;
}
vgic_data_host_to_mmio_bus(val, len, data);
@@ -1101,6 +1104,9 @@ static int dispatch_mmio_write(struct kvm_vcpu *vcpu, struct kvm_io_device *dev,
case IODEV_GICV5_IRS:
region->write(vcpu, addr, len, data);
break;
+ case IODEV_GICV5_ITS:
+ region->its_write(vcpu->kvm, iodev->v5its, addr, len, data);
+ break;
}
return 0;
@@ -1135,3 +1141,15 @@ int vgic_register_dist_iodev(struct kvm *kvm, gpa_t dist_base_address,
return kvm_io_bus_register_dev(kvm, KVM_MMIO_BUS, dist_base_address,
len, &io_device->dev);
}
+
+bool vgic_has_its(struct kvm *kvm)
+{
+ struct vgic_dist *dist = &kvm->arch.vgic;
+
+ if (vgic_is_v3(kvm) && dist->has_its)
+ return true;
+ if (vgic_is_v5(kvm) && dist->has_its)
+ return true;
+
+ return false;
+}
diff --git a/arch/arm64/kvm/vgic/vgic-mmio.h b/arch/arm64/kvm/vgic/vgic-mmio.h
index c3b6d3e752d8d..37fee94ac2a0a 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio.h
+++ b/arch/arm64/kvm/vgic/vgic-mmio.h
@@ -218,6 +218,7 @@ unsigned int vgic_v2_init_cpuif_iodev(struct vgic_io_device *dev);
unsigned int vgic_v3_init_dist_iodev(struct vgic_io_device *dev);
unsigned int vgic_v5_init_irs_iodev(struct vgic_io_device *dev);
+unsigned int vgic_v5_init_its_iodev(struct vgic_io_device *dev);
u64 vgic_sanitise_outer_cacheability(u64 reg);
u64 vgic_sanitise_inner_cacheability(u64 reg);
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 66044232ae025..5a9aa9bd11dd7 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -361,6 +361,7 @@ void vgic_v3_put(struct kvm_vcpu *vcpu);
bool vgic_has_its(struct kvm *kvm);
int kvm_vgic_register_its_device(void);
+int kvm_vgic_v5_register_its_device(void);
void vgic_enable_lpis(struct kvm_vcpu *vcpu);
void vgic_flush_pending_lpis(struct kvm_vcpu *vcpu);
int vgic_its_inject_msi(struct kvm *kvm, struct kvm_msi *msi);
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 7be81b936bf10..f3bfeee6104c2 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -324,7 +324,8 @@ enum iodev_type {
IODEV_DIST,
IODEV_REDIST,
IODEV_ITS,
- IODEV_GICV5_IRS
+ IODEV_GICV5_IRS,
+ IODEV_GICV5_ITS
};
struct vgic_io_device {
@@ -332,6 +333,7 @@ struct vgic_io_device {
union {
struct kvm_vcpu *redist_vcpu;
struct vgic_its *its;
+ struct vgic_v5_its *v5its;
};
const struct vgic_register_region *regions;
enum iodev_type iodev_type;
@@ -496,6 +498,18 @@ struct vgic_v5_irs {
bool lpi_ist_restore_pending;
};
+/*** GICv5 ***/
+struct vgic_v5_its {
+ /* base addresses in guest physical address space: */
+ gpa_t vgic_v5_its_base;
+
+ struct vgic_io_device iodev;
+ struct kvm_device *dev;
+
+ /* Protects the device in general */
+ struct mutex its_lock;
+};
+
struct vgic_dist {
bool in_kernel;
bool ready;
@@ -548,6 +562,8 @@ struct vgic_dist {
struct vgic_io_device cpuif_iodev;
bool has_its;
+ /* VGIC model required by the VM's ITS devices. */
+ u32 its_model;
bool table_write_in_progress;
/*
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index ac2d77d149635..4e16c55902f5e 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1238,6 +1238,8 @@ enum kvm_device_type {
#define KVM_DEV_TYPE_LOONGARCH_DMSINTC KVM_DEV_TYPE_LOONGARCH_DMSINTC
KVM_DEV_TYPE_ARM_VGIC_V5,
#define KVM_DEV_TYPE_ARM_VGIC_V5 KVM_DEV_TYPE_ARM_VGIC_V5
+ KVM_DEV_TYPE_ARM_VGIC_V5_ITS,
+#define KVM_DEV_TYPE_ARM_VGIC_V5_ITS KVM_DEV_TYPE_ARM_VGIC_V5_ITS
KVM_DEV_TYPE_MAX,
diff --git a/tools/include/uapi/linux/kvm.h b/tools/include/uapi/linux/kvm.h
index 419011097fa8e..99cf4775ed083 100644
--- a/tools/include/uapi/linux/kvm.h
+++ b/tools/include/uapi/linux/kvm.h
@@ -1236,6 +1236,8 @@ enum kvm_device_type {
#define KVM_DEV_TYPE_LOONGARCH_DMSINTC KVM_DEV_TYPE_LOONGARCH_DMSINTC
KVM_DEV_TYPE_ARM_VGIC_V5,
#define KVM_DEV_TYPE_ARM_VGIC_V5 KVM_DEV_TYPE_ARM_VGIC_V5
+ KVM_DEV_TYPE_ARM_VGIC_V5_ITS,
+#define KVM_DEV_TYPE_ARM_VGIC_V5_ITS KVM_DEV_TYPE_ARM_VGIC_V5_ITS
KVM_DEV_TYPE_MAX,
--
2.34.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH 06/16] KVM: arm64: gic-v5: Add ITS MMIO state emulation
2026-10-16 15:57 [PATCH 00/16] KVM: arm64: Add GICv5 ITS support Sascha Bischoff
` (4 preceding siblings ...)
2026-10-16 15:57 ` [PATCH 05/16] KVM: arm64: gic-v5: Add ITS device skeleton Sascha Bischoff
@ 2026-10-16 15:57 ` Sascha Bischoff
2026-10-16 16:11 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 07/16] KVM: arm64: gic-v5: Add ITS guest table translation emulation Sascha Bischoff
` (9 subsequent siblings)
15 siblings, 1 reply; 28+ messages in thread
From: Sascha Bischoff @ 2026-10-16 15:57 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, kvm
Cc: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
peter.maydell, lpieralisi, timothy.hayes, fuad.tabba
In order to expose a virtual GICv5 ITS to the guest, KVM needs to
emulate the ITS CONFIG_FRAME and its translation-control registers.
Replace the placeholder ITS IODEV region with the GICv5 ITS register
layout and handlers for the architected MMIO state.
Provide read/write emulation for the ITS ID registers, IIDR, AIDR,
CR0, CR1, DT_BASER, DT_CFGR, DIDR, EIDR, invalidation registers,
READ_EVENT, GEN_EVENT, status registers, and the RAZ/WI MMIO
registers that KVM does not implement. Reuse the shared CoreSight
identification-register helper so the ITS reports the same component
identification values as the IRS.
At this stage the translation-triggering registers only record state.
Walking the guest's DT/ITT, generating READ_EVENT results, and
injecting GEN_EVENT LPIs are added by later commits.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-its-v5.c | 404 +++++++++++++++++++++++++++++-
include/kvm/arm_vgic.h | 84 +++++++
2 files changed, 486 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c
index f8184b4ae0ccd..fda75314229a5 100644
--- a/arch/arm64/kvm/vgic/vgic-its-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-its-v5.c
@@ -6,19 +6,280 @@
#include <linux/kvm_host.h>
#include <linux/slab.h>
+#include <asm/kvm_arm.h>
#include <asm/kvm_mmu.h>
#include <kvm/arm_vgic.h>
+#include <linux/irqchip/arm-gic-v5.h>
+
#include "vgic.h"
#include "vgic-mmio.h"
+#define VITS_V5_DEVICE_ID_BITS 16
+#define VITS_V5_EVENT_ID_BITS 16
+
+static unsigned long vgic_v5_mmio_read_its_misc(struct kvm *kvm, void *dev,
+ gpa_t addr, unsigned int len)
+{
+ struct vgic_v5_its *its = dev;
+ u64 value = 0;
+ size_t offset = addr & (SZ_64K - 1);
+
+ switch (offset) {
+ case GICV5_ITS_IDR0:
+ value = FIELD_PREP(GICV5_ITS_IDR0_ITSID, its->idr0.its_id);
+ value |= FIELD_PREP(GICV5_ITS_IDR0_SWE, its->idr0.swe);
+ value |= FIELD_PREP(GICV5_ITS_IDR0_MPAM, its->idr0.mpam);
+ value |= FIELD_PREP(GICV5_ITS_IDR0_MEC, its->idr0.mec);
+ value |= FIELD_PREP(GICV5_ITS_IDR0_PA_RANGE,
+ its->idr0.pa_range);
+ value |= FIELD_PREP(GICV5_ITS_IDR0_DOM, its->idr0.domain);
+ break;
+ case GICV5_ITS_IDR1:
+ value = FIELD_PREP(GICV5_ITS_IDR1_L2SZ, its->idr1.l2sz);
+ value |= FIELD_PREP(GICV5_ITS_IDR1_ITT_LEVELS,
+ its->idr1.itt_levels);
+ value |= FIELD_PREP(GICV5_ITS_IDR1_DT_LEVELS,
+ its->idr1.dt_levels);
+ value |= FIELD_PREP(GICV5_ITS_IDR1_DEVICEID_BITS,
+ its->idr1.device_id_bits);
+ break;
+ case GICV5_ITS_IDR2:
+ value = FIELD_PREP(GICV5_ITS_IDR2_XDMN_EVENTS,
+ its->idr2.xdmn_events);
+ value |= FIELD_PREP(GICV5_ITS_IDR2_EVENTID_BITS,
+ its->idr2.event_id_bits);
+ break;
+ case GICV5_ITS_IIDR:
+ value = FIELD_PREP(GICV5_ITS_IIDR_PRODUCT_ID, PRODUCT_ID_KVM);
+ value |= FIELD_PREP(GICV5_ITS_IIDR_VARIANT, 0);
+ value |= FIELD_PREP(GICV5_ITS_IIDR_REVISION, 0);
+ value |= FIELD_PREP(GICV5_ITS_IIDR_IMPLEMENTER,
+ IMPLEMENTER_ARM);
+ break;
+ case GICV5_ITS_AIDR:
+ value = FIELD_PREP(GICV5_ITS_AIDR_COMPONENT,
+ GICV5_AIDR_COMPONENT_ITS);
+ value |= FIELD_PREP(GICV5_ITS_AIDR_ARCHMAJORREV,
+ GICV5_AIDR_ARCH_MAJ_REV_V5);
+ value |= FIELD_PREP(GICV5_ITS_AIDR_ARCHMINORREV,
+ GICV5_AIDR_ARCH_MIN_REV_V0);
+ break;
+ case GICV5_ITS_CR0:
+ value = GICV5_ITS_CR0_IDLE;
+ value |= FIELD_PREP(GICV5_ITS_CR0_ITSEN, its->enabled);
+ break;
+ case GICV5_ITS_CR1:
+ value = FIELD_PREP(GICV5_ITS_CR1_ITT_RA, its->cr1.itt_ra);
+ value |= FIELD_PREP(GICV5_ITS_CR1_DT_RA, its->cr1.dt_ra);
+ value |= FIELD_PREP(GICV5_ITS_CR1_IC, its->cr1.ic);
+ value |= FIELD_PREP(GICV5_ITS_CR1_OC, its->cr1.oc);
+ value |= FIELD_PREP(GICV5_ITS_CR1_SH, its->cr1.sh);
+ break;
+ default:
+ return 0;
+ }
+
+ return value;
+}
+
+static void vgic_v5_mmio_write_its_misc(struct kvm *kvm, void *dev, gpa_t addr,
+ unsigned int len, unsigned long val)
+{
+ struct vgic_v5_its *its = dev;
+ size_t offset = addr & (SZ_64K - 1);
+
+ switch (offset) {
+ case GICV5_ITS_CR0:
+ its->enabled = FIELD_GET(GICV5_ITS_CR0_ITSEN, val);
+ return;
+ case GICV5_ITS_CR1:
+ its->cr1.sh = FIELD_GET(GICV5_ITS_CR1_SH, val);
+ its->cr1.oc = FIELD_GET(GICV5_ITS_CR1_OC, val);
+ its->cr1.ic = FIELD_GET(GICV5_ITS_CR1_IC, val);
+ its->cr1.dt_ra = FIELD_GET(GICV5_ITS_CR1_DT_RA, val);
+ its->cr1.itt_ra = FIELD_GET(GICV5_ITS_CR1_ITT_RA, val);
+ return;
+ default:
+ return;
+ }
+}
+
+static void vgic_v5_write_its_dt_reg(struct vgic_v5_its *its, size_t offset,
+ unsigned long val)
+{
+ switch (offset) {
+ case GICV5_ITS_DT_CFGR:
+ its->dt_cfgr.device_id_bits =
+ FIELD_GET(GICV5_ITS_DT_CFGR_DEVICEID_BITS, val);
+ its->dt_cfgr.l2sz =
+ FIELD_GET(GICV5_ITS_DT_CFGR_L2SZ, val);
+ its->dt_cfgr.structure =
+ FIELD_GET(GICV5_ITS_DT_CFGR_STRUCTURE, val);
+ return;
+ case GICV5_ITS_DT_BASER:
+ its->dt_baser.addr =
+ FIELD_GET(GICV5_ITS_DT_BASER_ADDR_MASK, val)
+ << GICV5_ITS_DT_BASER_ADDR_SHIFT;
+ return;
+ }
+}
+
+static u64 vgic_v5_read_its_dt_baser(struct vgic_v5_its *its)
+{
+ return FIELD_PREP(GICV5_ITS_DT_BASER_ADDR_MASK,
+ its->dt_baser.addr >> GICV5_ITS_DT_BASER_ADDR_SHIFT);
+}
+
+static unsigned long vgic_v5_mmio_read_its_dt_itt(struct kvm *kvm, void *dev,
+ gpa_t addr, unsigned int len)
+{
+ struct vgic_v5_its *its = dev;
+ u64 value = 0;
+ size_t offset = addr & (SZ_64K - 1);
+
+ switch (offset) {
+ case GICV5_ITS_DT_CFGR:
+ value = FIELD_PREP(GICV5_ITS_DT_CFGR_STRUCTURE,
+ its->dt_cfgr.structure);
+ value |= FIELD_PREP(GICV5_ITS_DT_CFGR_L2SZ,
+ its->dt_cfgr.l2sz);
+ value |= FIELD_PREP(GICV5_ITS_DT_CFGR_DEVICEID_BITS,
+ its->dt_cfgr.device_id_bits);
+ break;
+ case GICV5_ITS_DT_BASER:
+ case GICV5_ITS_DT_BASER + 4:
+ value = vgic_v5_read_its_dt_baser(its);
+ return extract_bytes(value, offset & 4, len);
+ case GICV5_ITS_DIDR:
+ case GICV5_ITS_DIDR + 4:
+ value = FIELD_PREP(GICV5_ITS_DIDR_DEVICEID,
+ its->didr.device_id);
+ return extract_bytes(value, offset & 4, len);
+ case GICV5_ITS_EIDR:
+ value = FIELD_PREP(GICV5_ITS_EIDR_EVENTID,
+ its->eidr.event_id);
+ break;
+ case GICV5_ITS_STATUSR:
+ value = GICV5_ITS_STATUSR_IDLE;
+ break;
+ case GICV5_ITS_SYNC_STATUSR:
+ value = GICV5_ITS_SYNC_STATUSR_IDLE;
+ break;
+ case GICV5_ITS_READ_EVENT_DATAR:
+ case GICV5_ITS_READ_EVENT_DATAR + 4:
+ value = FIELD_PREP(GICV5_ITS_READ_EVENT_DATAR_VIRT,
+ its->read_event_datar.virt);
+ value |= FIELD_PREP(GICV5_ITS_READ_EVENT_DATAR_VM_ID,
+ its->read_event_datar.vm_id);
+ value |= FIELD_PREP(GICV5_ITS_READ_EVENT_DATAR_VALID,
+ its->read_event_datar.valid);
+ value |= FIELD_PREP(GICV5_ITS_READ_EVENT_DATAR_LPI_ID,
+ its->read_event_datar.lpi_id);
+ return extract_bytes(value, offset & 4, len);
+ case GICV5_ITS_GEN_EVENT_EIDR:
+ value = FIELD_PREP(GICV5_ITS_GEN_EVENT_EIDR_EVENTID,
+ its->gen_event_eidr.event_id);
+ break;
+ case GICV5_ITS_GEN_EVENT_DIDR:
+ case GICV5_ITS_GEN_EVENT_DIDR + 4:
+ value = FIELD_PREP(GICV5_ITS_GEN_EVENT_DIDR_DEVICEID,
+ its->gen_event_didr.device_id);
+ return extract_bytes(value, offset & 4, len);
+ case GICV5_ITS_GEN_EVENT_STATUSR:
+ value = GICV5_ITS_GEN_EVENT_STATUSR_IDLE;
+ break;
+ default:
+ return 0;
+ }
+
+ return value;
+}
+
+static void vgic_v5_mmio_write_its_dt_itt(struct kvm *kvm, void *dev,
+ gpa_t addr, unsigned int len,
+ unsigned long val)
+{
+ struct vgic_v5_its *its = dev;
+ size_t offset = addr & (SZ_64K - 1);
+
+ guard(mutex)(&its->its_lock);
+
+ switch (offset) {
+ case GICV5_ITS_DT_CFGR:
+ if (!its->enabled)
+ vgic_v5_write_its_dt_reg(its, offset, val);
+ return;
+ case GICV5_ITS_DT_BASER:
+ case GICV5_ITS_DT_BASER + 4:
+ if (!its->enabled) {
+ u64 baser = vgic_v5_read_its_dt_baser(its);
+
+ baser = update_64bit_reg(baser, offset & 4, len, val);
+ vgic_v5_write_its_dt_reg(its, GICV5_ITS_DT_BASER,
+ baser);
+ }
+ return;
+ case GICV5_ITS_INV_DEVICER:
+ its->inv_devicer.l1 =
+ FIELD_GET(GICV5_ITS_INV_DEVICER_L1, val);
+ its->inv_devicer.event_id_bits =
+ FIELD_GET(GICV5_ITS_INV_DEVICER_EVENTID_BITS, val);
+ its->inv_devicer.i =
+ FIELD_GET(GICV5_ITS_INV_DEVICER_I, val);
+ return;
+ case GICV5_ITS_DIDR:
+ case GICV5_ITS_DIDR + 4:
+ if (offset & 4)
+ return;
+ its->didr.device_id = FIELD_GET(GICV5_ITS_DIDR_DEVICEID, val);
+ return;
+ case GICV5_ITS_EIDR:
+ its->eidr.event_id = FIELD_GET(GICV5_ITS_EIDR_EVENTID, val);
+ return;
+ case GICV5_ITS_INV_EVENTR:
+ its->inv_eventr.l1 =
+ FIELD_GET(GICV5_ITS_INV_EVENTR_L1, val);
+ its->inv_eventr.itt_l2sz =
+ FIELD_GET(GICV5_ITS_INV_EVENTR_ITT_L2SZ, val);
+ its->inv_eventr.i = FIELD_GET(GICV5_ITS_INV_EVENTR_I, val);
+ return;
+ case GICV5_ITS_SYNCR:
+ case GICV5_ITS_READ_EVENTR:
+ return;
+ case GICV5_ITS_GEN_EVENT_EIDR:
+ its->gen_event_eidr.event_id =
+ FIELD_GET(GICV5_ITS_GEN_EVENT_EIDR_EVENTID, val);
+ return;
+ case GICV5_ITS_GEN_EVENT_DIDR:
+ case GICV5_ITS_GEN_EVENT_DIDR + 4:
+ if (offset & 4)
+ return;
+ its->gen_event_didr.device_id =
+ FIELD_GET(GICV5_ITS_GEN_EVENT_DIDR_DEVICEID, val);
+ return;
+ case GICV5_ITS_GEN_EVENTR:
+ return;
+ default:
+ return;
+ }
+}
+
static unsigned long vgic_v5_mmio_read_its_raz(struct kvm *kvm, void *dev,
gpa_t addr, unsigned int len)
{
return 0;
}
+static unsigned long vgic_v5_its_coresight_read(struct kvm *kvm, void *dev,
+ gpa_t addr, unsigned int len)
+{
+ u32 offset = addr & (SZ_64K - 1);
+
+ return vgic_v5_handle_guest_coresight_idreg_read(offset);
+}
+
static void vgic_v5_mmio_write_its_wi(struct kvm *kvm, void *dev, gpa_t addr,
unsigned int len, unsigned long val)
{
@@ -31,11 +292,148 @@ static void vgic_v5_mmio_write_its_wi(struct kvm *kvm, void *dev, gpa_t addr,
}
static const struct vgic_register_region vgic_v5_its_registers[] = {
- REGISTER_ITS_V5_DESC(0, vgic_v5_mmio_read_its_raz,
- vgic_v5_mmio_write_its_wi, KVM_VGIC_V5_ITS_SIZE,
+ REGISTER_ITS_V5_DESC(GICV5_ITS_IDR0, vgic_v5_mmio_read_its_misc,
+ vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_IDR1, vgic_v5_mmio_read_its_misc,
+ vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_IDR2, vgic_v5_mmio_read_its_misc,
+ vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_IIDR, vgic_v5_mmio_read_its_misc,
+ vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_AIDR, vgic_v5_mmio_read_its_misc,
+ vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_CR0, vgic_v5_mmio_read_its_misc,
+ vgic_v5_mmio_write_its_misc, 4, VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_CR1, vgic_v5_mmio_read_its_misc,
+ vgic_v5_mmio_write_its_misc, 4, VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_DT_BASER,
+ vgic_v5_mmio_read_its_dt_itt,
+ vgic_v5_mmio_write_its_dt_itt, 8,
+ VGIC_ACCESS_64bit | VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_DT_CFGR,
+ vgic_v5_mmio_read_its_dt_itt,
+ vgic_v5_mmio_write_its_dt_itt, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_DIDR, vgic_v5_mmio_read_its_dt_itt,
+ vgic_v5_mmio_write_its_dt_itt, 8,
+ VGIC_ACCESS_64bit | VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_EIDR, vgic_v5_mmio_read_its_dt_itt,
+ vgic_v5_mmio_write_its_dt_itt, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_INV_EVENTR, vgic_v5_mmio_read_its_raz,
+ vgic_v5_mmio_write_its_dt_itt, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_INV_DEVICER, vgic_v5_mmio_read_its_raz,
+ vgic_v5_mmio_write_its_dt_itt, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_READ_EVENTR, vgic_v5_mmio_read_its_raz,
+ vgic_v5_mmio_write_its_dt_itt, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_READ_EVENT_DATAR,
+ vgic_v5_mmio_read_its_dt_itt,
+ vgic_v5_mmio_write_its_wi, 8,
+ VGIC_ACCESS_64bit | VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_STATUSR, vgic_v5_mmio_read_its_dt_itt,
+ vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_SYNCR, vgic_v5_mmio_read_its_raz,
+ vgic_v5_mmio_write_its_dt_itt, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_SYNC_STATUSR,
+ vgic_v5_mmio_read_its_dt_itt,
+ vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_GEN_EVENT_DIDR,
+ vgic_v5_mmio_read_its_dt_itt,
+ vgic_v5_mmio_write_its_dt_itt, 8,
+ VGIC_ACCESS_64bit | VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_GEN_EVENT_EIDR,
+ vgic_v5_mmio_read_its_dt_itt,
+ vgic_v5_mmio_write_its_dt_itt, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_GEN_EVENTR, vgic_v5_mmio_read_its_raz,
+ vgic_v5_mmio_write_its_dt_itt, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_GEN_EVENT_STATUSR,
+ vgic_v5_mmio_read_its_dt_itt,
+ vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_MEC_IDR, vgic_v5_mmio_read_its_raz,
+ vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_MEC_MECID_R, vgic_v5_mmio_read_its_raz,
+ vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_MPAM_IDR, vgic_v5_mmio_read_its_raz,
+ vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_MPAM_PARTID_R,
+ vgic_v5_mmio_read_its_raz,
+ vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_SWERR_STATUSR, vgic_v5_mmio_read_its_raz,
+ vgic_v5_mmio_write_its_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_SWERR_SYNDROMER0,
+ vgic_v5_mmio_read_its_raz,
+ vgic_v5_mmio_write_its_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_SWERR_SYNDROMER1,
+ vgic_v5_mmio_read_its_raz,
+ vgic_v5_mmio_write_its_wi, 8,
+ VGIC_ACCESS_64bit),
+ REGISTER_ITS_V5_DESC(GICV5_ITS_MPAM_PMGR, vgic_v5_mmio_read_its_raz,
+ vgic_v5_mmio_write_its_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_ITS_V5_DESC(GICV5_CORESIGHT_DEVARCH,
+ vgic_v5_its_coresight_read,
+ vgic_v5_mmio_write_its_wi,
+ GICV5_CORESIGHT_IDREGS_SIZE,
VGIC_ACCESS_32bit),
};
+static void vgic_v5_its_reset(struct vgic_v5_its *its)
+{
+ u64 mmfr0;
+
+ its->enabled = false;
+
+ mmfr0 = read_sanitised_ftr_reg(SYS_ID_AA64MMFR0_EL1);
+ its->idr0.pa_range = cpuid_feature_extract_unsigned_field(
+ mmfr0, ID_AA64MMFR0_EL1_PARANGE_SHIFT);
+ its->idr0.domain = GICV5_ITS_IDR0_DOMAIN_NON_SECURE;
+ its->idr0.mec = 0;
+ its->idr0.mpam = 0;
+ its->idr0.swe = 0;
+ its->idr0.its_id = 0;
+
+ its->idr1.dt_levels = 1;
+ its->idr1.itt_levels = 1;
+ its->idr1.l2sz = 0;
+ its->idr2.xdmn_events = 0;
+
+ its->cr1.sh = 0;
+ its->cr1.oc = 0;
+ its->cr1.ic = 0;
+ its->cr1.dt_ra = false;
+ its->cr1.itt_ra = false;
+
+ its->idr1.device_id_bits = VITS_V5_DEVICE_ID_BITS;
+ its->idr2.event_id_bits = VITS_V5_EVENT_ID_BITS;
+
+ its->dt_cfgr.device_id_bits = 0;
+ its->dt_cfgr.l2sz = 0;
+ its->dt_cfgr.structure = false;
+ its->dt_baser.addr = 0;
+
+ its->inv_devicer.l1 = false;
+ its->inv_devicer.event_id_bits = 0;
+ its->inv_devicer.i = false;
+ its->didr.device_id = 0;
+ its->eidr.event_id = 0;
+ its->inv_eventr.l1 = false;
+ its->inv_eventr.itt_l2sz = 0;
+ its->inv_eventr.i = false;
+
+ its->read_event_datar.lpi_id = 0;
+ its->read_event_datar.valid = false;
+ its->read_event_datar.vm_id = 0;
+ its->read_event_datar.virt = false;
+ its->gen_event_eidr.event_id = 0;
+ its->gen_event_didr.device_id = 0;
+
+}
+
static int vgic_v5_its_create(struct kvm_device *dev, u32 type)
{
struct vgic_v5_its *its;
@@ -60,6 +458,8 @@ static int vgic_v5_its_create(struct kvm_device *dev, u32 type)
its->vgic_v5_its_base = VGIC_ADDR_UNDEF;
its->dev = dev;
+ vgic_v5_its_reset(its);
+
dev->kvm->arch.vgic.msis_require_devid = true;
dev->kvm->arch.vgic.has_its = true;
dev->kvm->arch.vgic.its_model = KVM_DEV_TYPE_ARM_VGIC_V5;
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index f3bfeee6104c2..c9eaa7897b57e 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -506,6 +506,90 @@ struct vgic_v5_its {
struct vgic_io_device iodev;
struct kvm_device *dev;
+ bool enabled;
+
+ /* ITS state - used for registers etc */
+ struct its_idr0 {
+ u8 domain;
+ u8 pa_range;
+ bool mec;
+ bool mpam;
+ bool swe;
+ u16 its_id;
+ } idr0;
+
+ struct its_idr1 {
+ u8 device_id_bits;
+ u8 dt_levels;
+ u8 itt_levels;
+ u8 l2sz;
+ } idr1;
+
+ struct its_idr2 {
+ u8 event_id_bits;
+ u8 xdmn_events;
+ } idr2;
+
+ struct its_iidr {
+ u16 implementer;
+ u8 revision;
+ u8 variant;
+ u16 product_id;
+ } iidr;
+
+ struct its_cr1 {
+ u8 sh;
+ u8 oc;
+ u8 ic;
+ bool dt_ra;
+ bool itt_ra;
+ } cr1;
+
+ struct its_dt_cfgr {
+ u8 device_id_bits;
+ u8 l2sz;
+ bool structure;
+ } dt_cfgr;
+
+ struct its_dt_baser {
+ u64 addr;
+ } dt_baser;
+
+ struct its_inv_devicer {
+ bool l1;
+ u8 event_id_bits;
+ bool i;
+ } inv_devicer;
+
+ struct its_didr {
+ u32 device_id;
+ } didr;
+
+ struct its_eidr {
+ u16 event_id;
+ } eidr;
+
+ struct its_inv_eventr {
+ bool l1;
+ u8 itt_l2sz;
+ bool i;
+ } inv_eventr;
+
+ struct its_read_event_datar {
+ u32 lpi_id;
+ bool valid;
+ u16 vm_id;
+ bool virt;
+ } read_event_datar;
+
+ struct its_gen_event_eidr {
+ u16 event_id;
+ } gen_event_eidr;
+
+ struct its_gen_event_didr {
+ u32 device_id;
+ } gen_event_didr;
+
/* Protects the device in general */
struct mutex its_lock;
};
--
2.34.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH 07/16] KVM: arm64: gic-v5: Add ITS guest table translation emulation
2026-10-16 15:57 [PATCH 00/16] KVM: arm64: Add GICv5 ITS support Sascha Bischoff
` (5 preceding siblings ...)
2026-10-16 15:57 ` [PATCH 06/16] KVM: arm64: gic-v5: Add ITS MMIO state emulation Sascha Bischoff
@ 2026-10-16 15:57 ` Sascha Bischoff
2026-10-16 16:17 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 08/16] KVM: arm64: gic-v5: Add ITS translation cache maintenance Sascha Bischoff
` (8 subsequent siblings)
15 siblings, 1 reply; 28+ messages in thread
From: Sascha Bischoff @ 2026-10-16 15:57 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, kvm
Cc: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
peter.maydell, lpieralisi, timothy.hayes, fuad.tabba
The GICv5 ITS consumes guest-owned translation tables. Unlike the
GICv3 ITS, KVM does not maintain a separate software device/table
model for GICv5 translations; the guest's DT and ITTs are the
authoritative source of DeviceID/EventID to LPI mappings.
Add helpers to walk both linear and two-level guest Device Tables and
Interrupt Translation Tables. The walker reads the guest table entries
under the ITS lock, validates the relevant valid bits and range fields,
and resolves a DeviceID/EventID pair to the LPI encoded in the guest's
ITTE.
Use the table walker to implement the translation side effects of the
MMIO interface. READ_EVENTR now resolves DIDR/EIDR and reports the LPI
through READ_EVENT_DATAR, while GEN_EVENTR resolves GEN_EVENT_DIDR and
GEN_EVENT_EIDR and injects the resulting LPI into the VM.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-its-v5.c | 298 ++++++++++++++++++++++++++++++
1 file changed, 298 insertions(+)
diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c
index fda75314229a5..f6d2acca4ae4c 100644
--- a/arch/arm64/kvm/vgic/vgic-its-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-its-v5.c
@@ -19,6 +19,300 @@
#define VITS_V5_DEVICE_ID_BITS 16
#define VITS_V5_EVENT_ID_BITS 16
+static int parse_linear_dt(struct kvm *kvm, gpa_t dt_base, u32 device_id,
+ gpa_t *itt_base, bool *two_level_itt,
+ unsigned int *event_id_bits, unsigned int *itt_l2sz)
+{
+ __le64 dt_l2_entry;
+ gpa_t entry_addr;
+ u64 val;
+ int ret;
+
+ entry_addr = dt_base + device_id * sizeof(__le64);
+ ret = kvm_read_guest_lock(kvm, entry_addr, &dt_l2_entry,
+ sizeof(dt_l2_entry));
+ if (ret) {
+ kvm_debug("Failed to read guest memory for L2 DT\n");
+ return ret;
+ }
+
+ val = le64_to_cpu(dt_l2_entry);
+ if (!FIELD_GET(GICV5_DTL2E_VALID, val)) {
+ kvm_debug("Guest DT entry is invalid\n");
+ return -EINVAL;
+ }
+
+ *itt_base = FIELD_GET(GICV5_DTL2E_ITT_ADDR_MASK, val) << 3;
+ *two_level_itt = FIELD_GET(GICV5_DTL2E_ITT_STRUCTURE, val);
+ *event_id_bits = FIELD_GET(GICV5_DTL2E_EVENT_ID_BITS, val);
+ *itt_l2sz = FIELD_GET(GICV5_DTL2E_ITT_L2SZ, val);
+
+ return 0;
+}
+
+static int parse_two_level_dt(struct kvm *kvm, gpa_t dt_base,
+ unsigned int dt_l2sz, u32 device_id,
+ gpa_t *itt_base, bool *two_level_itt,
+ unsigned int *event_id_bits,
+ unsigned int *itt_l2sz)
+{
+ unsigned int device_id_l1_shift, l2_device_id;
+ __le64 dt_l1_entry;
+ gpa_t dt_l2_base;
+ gpa_t entry_addr;
+ u64 val;
+ int ret;
+
+ switch (dt_l2sz) {
+ case 0:
+ device_id_l1_shift = 9;
+ break;
+ case 1:
+ device_id_l1_shift = 11;
+ break;
+ case 2:
+ device_id_l1_shift = 13;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ entry_addr = dt_base +
+ (device_id >> device_id_l1_shift) * sizeof(__le64);
+
+ ret = kvm_read_guest_lock(kvm, entry_addr, &dt_l1_entry,
+ sizeof(dt_l1_entry));
+ if (ret) {
+ kvm_debug("Failed to read guest memory for L1 DT\n");
+ return ret;
+ }
+
+ val = le64_to_cpu(dt_l1_entry);
+ if (!FIELD_GET(GICV5_DTL1E_VALID, val)) {
+ kvm_debug("Guest DT L1 entry is invalid\n");
+ return -EINVAL;
+ }
+
+ dt_l2_base = FIELD_GET(GICV5_DTL1E_L2_ADDR_MASK, val) << 3;
+ l2_device_id = device_id % (1UL << device_id_l1_shift);
+ if (l2_device_id >= (1UL << FIELD_GET(GICV5_DTL1E_SPAN, val))) {
+ kvm_debug("DeviceID 0x%x out of range of guest DT\n",
+ device_id);
+ return -ENXIO;
+ }
+
+ return parse_linear_dt(kvm, dt_l2_base, l2_device_id, itt_base,
+ two_level_itt, event_id_bits, itt_l2sz);
+}
+
+static int get_itt(struct kvm *kvm, struct vgic_v5_its *its, u32 device_id,
+ gpa_t *itt_base, bool *two_level_itt,
+ unsigned int *event_id_bits, unsigned int *itt_l2sz)
+{
+ unsigned int dt_l2sz, device_id_bits;
+ bool two_level_dt;
+ gpa_t dt_base;
+
+ dt_base = its->dt_baser.addr;
+ two_level_dt = its->dt_cfgr.structure;
+ dt_l2sz = its->dt_cfgr.l2sz;
+ device_id_bits = its->dt_cfgr.device_id_bits;
+
+ if (device_id >= 1UL << device_id_bits) {
+ kvm_debug("Device ID out of range\n");
+ return -E2BIG;
+ }
+
+ if (!two_level_dt)
+ return parse_linear_dt(kvm, dt_base, device_id, itt_base,
+ two_level_itt, event_id_bits, itt_l2sz);
+
+ return parse_two_level_dt(kvm, dt_base, dt_l2sz, device_id, itt_base,
+ two_level_itt, event_id_bits, itt_l2sz);
+}
+
+static int parse_linear_itt(struct kvm *kvm, u32 event_id, gpa_t itt_base,
+ __le64 *itt_l2_entry)
+{
+ gpa_t entry_addr = itt_base + event_id * sizeof(*itt_l2_entry);
+ int ret;
+
+ ret = kvm_read_guest_lock(kvm, entry_addr, itt_l2_entry,
+ sizeof(*itt_l2_entry));
+ if (ret)
+ kvm_debug("Failed to read guest memory for L2 ITT\n");
+
+ return ret;
+}
+
+static int parse_two_level_itt(struct kvm *kvm, u32 event_id, gpa_t itt_base,
+ unsigned int itt_l2sz, __le64 *itt_l2_entry)
+{
+ unsigned int itt_split_bits, l2_event_id_bits;
+ gpa_t itt_l2_base, entry_addr;
+ u32 l1_event_id, l2_event_id;
+ __le64 itt_l1_entry;
+ u64 val;
+ int ret;
+
+ switch (itt_l2sz) {
+ case 0:
+ itt_split_bits = 9;
+ break;
+ case 1:
+ itt_split_bits = 11;
+ break;
+ case 2:
+ itt_split_bits = 13;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ l1_event_id = event_id >> itt_split_bits;
+ entry_addr = itt_base + l1_event_id * sizeof(itt_l1_entry);
+ ret = kvm_read_guest_lock(kvm, entry_addr, &itt_l1_entry,
+ sizeof(itt_l1_entry));
+ if (ret) {
+ kvm_debug("Failed to read guest memory for L1 ITT\n");
+ return ret;
+ }
+
+ val = le64_to_cpu(itt_l1_entry);
+ if (!FIELD_GET(GICV5_ITTL1E_VALID, val)) {
+ kvm_debug("Guest ITT L1 entry is invalid\n");
+ return -EINVAL;
+ }
+
+ itt_l2_base = FIELD_GET(GICV5_ITTL1E_L2_ADDR_MASK, val) << 3;
+ l2_event_id_bits =
+ min(FIELD_GET(GICV5_ITTL1E_SPAN, val), itt_split_bits);
+ l2_event_id = event_id % (1UL << l2_event_id_bits);
+
+ if ((itt_split_bits != l2_event_id_bits) &&
+ (event_id & ((1UL << itt_split_bits) - 1)) != l2_event_id) {
+ kvm_debug("EventID is out of range of L2 ITT\n");
+ return -ENXIO;
+ }
+
+ return parse_linear_itt(kvm, l2_event_id, itt_l2_base, itt_l2_entry);
+}
+
+static int get_itte(struct kvm *kvm, u32 event_id, gpa_t itt_base,
+ bool two_level_itt, unsigned int itt_l2sz,
+ __le64 *itt_l2_entry)
+{
+ if (!two_level_itt)
+ return parse_linear_itt(kvm, event_id, itt_base, itt_l2_entry);
+
+ return parse_two_level_itt(kvm, event_id, itt_base, itt_l2sz,
+ itt_l2_entry);
+}
+
+static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its,
+ u32 device_id, u16 event_id,
+ struct vgic_irq **irq)
+{
+ unsigned int itt_l2sz, event_id_bits;
+ __le64 itt_l2_entry;
+ bool two_level_itt;
+ gpa_t itt_base;
+ u32 lpi;
+ u64 val;
+ int ret;
+
+ lockdep_assert_held(&its->its_lock);
+
+ if (!its->enabled) {
+ kvm_debug("Cannot look up LPI; vITS is disabled\n");
+ return -ENXIO;
+ }
+
+ if (WARN_ON_ONCE(kvm->mm != get_current()->mm))
+ return -EWOULDBLOCK;
+
+ ret = get_itt(kvm, its, device_id, &itt_base, &two_level_itt,
+ &event_id_bits, &itt_l2sz);
+ if (ret) {
+ kvm_debug("Error parsing guest's DT\n");
+ return ret;
+ }
+
+ if (event_id >= 1UL << event_id_bits) {
+ kvm_debug("Event ID out of range\n");
+ return -E2BIG;
+ }
+
+ ret = get_itte(kvm, event_id, itt_base, two_level_itt, itt_l2sz,
+ &itt_l2_entry);
+ if (ret)
+ return ret;
+
+ val = le64_to_cpu(itt_l2_entry);
+ if (!FIELD_GET(GICV5_ITTL2E_VALID, val)) {
+ kvm_debug("ITTE is not valid\n");
+ return -EINVAL;
+ }
+
+ lpi = FIELD_GET(GICV5_ITTL2E_LPI_ID, val);
+
+ *irq = vgic_add_lpi(kvm, vgic_v5_make_lpi(lpi), NULL);
+ if (IS_ERR(*irq))
+ return PTR_ERR(*irq);
+
+ return 0;
+}
+
+static void vgic_v5_its_inject_lpi(struct kvm *kvm, struct vgic_irq *irq)
+{
+ kvm_call_hyp(__vgic_v5_vdpend, irq->intid, true,
+ kvm->arch.vgic.gicv5_vm.vm_id);
+}
+
+static void vgic_v5_its_handle_read_event(struct kvm *kvm,
+ struct vgic_v5_its *its)
+{
+ struct vgic_irq *irq;
+ int ret;
+
+ its->read_event_datar.valid = false;
+ its->read_event_datar.lpi_id = 0;
+ its->read_event_datar.vm_id = 0;
+ its->read_event_datar.virt = false;
+
+ ret = __vgic_v5_its_resolve_lpi(kvm, its, its->didr.device_id,
+ its->eidr.event_id, &irq);
+ if (ret)
+ return;
+
+ its->read_event_datar.lpi_id = vgic_v5_get_hwirq_id(irq->intid);
+ its->read_event_datar.valid = true;
+ vgic_put_irq(kvm, irq);
+}
+
+static void vgic_v5_its_handle_gen_event(struct kvm *kvm,
+ struct vgic_v5_its *its,
+ unsigned long val)
+{
+ struct vgic_irq *irq;
+ int ret;
+
+ if (!FIELD_GET(GICV5_ITS_GEN_EVENTR_R, val))
+ return;
+
+ if (FIELD_GET(GICV5_ITS_GEN_EVENTR_TARGET_DOMAIN, val))
+ return;
+
+ ret = __vgic_v5_its_resolve_lpi(kvm, its,
+ its->gen_event_didr.device_id,
+ its->gen_event_eidr.event_id, &irq);
+ if (ret)
+ return;
+
+ vgic_v5_its_inject_lpi(kvm, irq);
+ vgic_put_irq(kvm, irq);
+}
+
static unsigned long vgic_v5_mmio_read_its_misc(struct kvm *kvm, void *dev,
gpa_t addr, unsigned int len)
{
@@ -246,7 +540,10 @@ static void vgic_v5_mmio_write_its_dt_itt(struct kvm *kvm, void *dev,
its->inv_eventr.i = FIELD_GET(GICV5_ITS_INV_EVENTR_I, val);
return;
case GICV5_ITS_SYNCR:
+ return;
case GICV5_ITS_READ_EVENTR:
+ if (FIELD_GET(GICV5_ITS_READ_EVENTR_R, val))
+ vgic_v5_its_handle_read_event(kvm, its);
return;
case GICV5_ITS_GEN_EVENT_EIDR:
its->gen_event_eidr.event_id =
@@ -260,6 +557,7 @@ static void vgic_v5_mmio_write_its_dt_itt(struct kvm *kvm, void *dev,
FIELD_GET(GICV5_ITS_GEN_EVENT_DIDR_DEVICEID, val);
return;
case GICV5_ITS_GEN_EVENTR:
+ vgic_v5_its_handle_gen_event(kvm, its, val);
return;
default:
return;
--
2.34.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH 08/16] KVM: arm64: gic-v5: Add ITS translation cache maintenance
2026-10-16 15:57 [PATCH 00/16] KVM: arm64: Add GICv5 ITS support Sascha Bischoff
` (6 preceding siblings ...)
2026-10-16 15:57 ` [PATCH 07/16] KVM: arm64: gic-v5: Add ITS guest table translation emulation Sascha Bischoff
@ 2026-10-16 15:57 ` Sascha Bischoff
2026-10-16 16:10 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 09/16] KVM: arm64: gic-v5: Add ITS MSI injection Sascha Bischoff
` (7 subsequent siblings)
15 siblings, 1 reply; 28+ messages in thread
From: Sascha Bischoff @ 2026-10-16 15:57 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, kvm
Cc: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
peter.maydell, lpieralisi, timothy.hayes, fuad.tabba
Walking the guest DT and ITTs on every MSI or ITS event is unnecessary
once a DeviceID/EventID pair has been resolved. Add a small hash table
to cache resolved translations as a disposable acceleration structure
which avoids iterating over guest memory whenever possible.
Cache entries hold references to the resolved struct vgic_irq objects
stored in the VM-wide LPI xarray. Lookups take an IRQ reference before
returning an entry to the caller, and cache eviction drops the
reference held by the cache. This keeps the cache tied to the normal
LPI object lifetime instead of storing raw LPI numbers.
Invalidate cached translations when the table configuration or guest
invalidation commands make them stale. Changes to DT_BASER or DT_CFGR
drop the entire cache, while writes of unchanged values leave it intact.
INV_EVENTR removes either one event or the events covered by an L1 ITT
entry, and INV_DEVICER removes the translations covered by either an L2
DT entry or an L1 DT entry.
The cache is not part of any userspace ABI and is never authoritative:
entries can always be rebuilt from guest memory on the next lookup.
The vgic_irq objects for LPIs track valid translations rather than
mirror interrupt state. Mirroring that state would require querying
the GIC on every guest exit. Once direct injection is added, the
objects also record whether an LPI is mapped into the host ITT.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-its-v5.c | 343 ++++++++++++++++++++++++++++--
include/kvm/arm_vgic.h | 7 +
2 files changed, 335 insertions(+), 15 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c
index f6d2acca4ae4c..e4c0415137605 100644
--- a/arch/arm64/kvm/vgic/vgic-its-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-its-v5.c
@@ -19,6 +19,272 @@
#define VITS_V5_DEVICE_ID_BITS 16
#define VITS_V5_EVENT_ID_BITS 16
+struct its_cache_translation_entry {
+ struct hlist_node node;
+ u64 key;
+ struct vgic_irq *irq;
+};
+
+static void its_cache_drop_translation(struct kvm *kvm,
+ struct its_cache_translation_entry *entry)
+{
+ struct vgic_irq *irq = entry->irq;
+
+ vgic_put_irq(kvm, irq);
+ kfree(entry);
+}
+
+static void its_cache_drop_removed_translations(struct kvm *kvm,
+ struct hlist_head *removed)
+{
+ struct its_cache_translation_entry *entry;
+ struct hlist_node *tmp;
+
+ hlist_for_each_entry_safe(entry, tmp, removed, node) {
+ hlist_del(&entry->node);
+ its_cache_drop_translation(kvm, entry);
+ }
+}
+
+static int its_cache_add_translation(struct kvm *kvm, struct vgic_v5_its *its,
+ u32 device_id, u16 event_id,
+ struct vgic_irq *irq)
+{
+ u64 key = ((u64)device_id << 16) | (u64)event_id;
+ struct its_cache_translation_entry *entry;
+ struct its_cache_translation_entry *old;
+ struct hlist_node *tmp;
+ HLIST_HEAD(removed);
+
+ entry = kzalloc_obj(*entry, GFP_KERNEL);
+ if (!entry)
+ return -ENOMEM;
+
+ entry->key = key;
+ entry->irq = irq;
+
+ scoped_guard(raw_spinlock, &its->translation_cache_lock) {
+ hash_for_each_possible_safe(its->translation_cache, old, tmp,
+ node, key) {
+ if (old->key == key) {
+ hash_del(&old->node);
+ hlist_add_head(&old->node, &removed);
+ }
+ }
+
+ hash_add(its->translation_cache, &entry->node, entry->key);
+ }
+
+ its_cache_drop_removed_translations(kvm, &removed);
+
+ return 0;
+}
+
+static int its_cache_remove_translation(struct vgic_v5_its *its, u32 device_id,
+ u16 event_id)
+{
+ u64 key = ((u64)device_id << 16) | (u64)event_id;
+ struct its_cache_translation_entry *entry;
+ struct hlist_node *tmp;
+ HLIST_HEAD(removed);
+ bool found = false;
+
+ scoped_guard(raw_spinlock, &its->translation_cache_lock) {
+ hash_for_each_possible_safe(its->translation_cache, entry, tmp,
+ node, key) {
+ if (entry->key == key) {
+ hash_del(&entry->node);
+ hlist_add_head(&entry->node, &removed);
+ found = true;
+ }
+ }
+ }
+
+ its_cache_drop_removed_translations(its->dev->kvm, &removed);
+
+ return found ? 0 : -EINVAL;
+}
+
+static void its_cache_clear_translations(struct vgic_v5_its *its)
+{
+ struct its_cache_translation_entry *entry;
+ struct hlist_node *tmp;
+ HLIST_HEAD(removed);
+ int bkt;
+
+ scoped_guard(raw_spinlock, &its->translation_cache_lock) {
+ hash_for_each_safe(its->translation_cache, bkt, tmp, entry,
+ node) {
+ hash_del(&entry->node);
+ hlist_add_head(&entry->node, &removed);
+ }
+
+ WARN_ON(!hash_empty(its->translation_cache));
+ }
+
+ its_cache_drop_removed_translations(its->dev->kvm, &removed);
+}
+
+static int its_cache_look_up_translation(struct vgic_v5_its *its,
+ u32 device_id, u16 event_id,
+ struct vgic_irq **irq)
+{
+ u64 key = ((u64)device_id << 16) | (u64)event_id;
+ struct its_cache_translation_entry *entry;
+
+ guard(raw_spinlock)(&its->translation_cache_lock);
+
+ hash_for_each_possible(its->translation_cache, entry, node, key) {
+ if (entry->key == key) {
+ if (!vgic_try_get_irq_ref(entry->irq))
+ return -ENXIO;
+
+ *irq = entry->irq;
+ return 0;
+ }
+ }
+
+ return -ENXIO;
+}
+
+static void its_cache_handle_inv_devicer_l2(struct vgic_v5_its *its,
+ u32 device_id, u8 event_id_bits)
+{
+ struct its_cache_translation_entry *entry;
+ struct hlist_node *tmp;
+ HLIST_HEAD(removed);
+ u64 event_id_limit;
+ int bkt;
+
+ if (event_id_bits > 16)
+ event_id_bits = 16;
+ event_id_limit = 1ULL << event_id_bits;
+
+ scoped_guard(raw_spinlock, &its->translation_cache_lock) {
+ hash_for_each_safe(its->translation_cache, bkt, tmp, entry,
+ node) {
+ if ((entry->key >> 16 == device_id) &&
+ ((entry->key & 0xffffULL) < event_id_limit)) {
+ hash_del(&entry->node);
+ hlist_add_head(&entry->node, &removed);
+ }
+ }
+ }
+
+ its_cache_drop_removed_translations(its->dev->kvm, &removed);
+}
+
+static void its_cache_handle_inv_devicer_l1(struct vgic_v5_its *its,
+ u32 device_id, u8 l2sz)
+{
+ struct its_cache_translation_entry *entry;
+ struct hlist_node *tmp;
+ HLIST_HEAD(removed);
+ u8 device_id_split_bits;
+ u32 device_id_mask;
+ int bkt;
+
+ switch (l2sz) {
+ case 0:
+ device_id_split_bits = 9;
+ break;
+ case 1:
+ device_id_split_bits = 11;
+ break;
+ case 2:
+ device_id_split_bits = 13;
+ break;
+ default:
+ return;
+ }
+
+ device_id_mask = ~((1U << device_id_split_bits) - 1);
+ device_id &= device_id_mask;
+
+ scoped_guard(raw_spinlock, &its->translation_cache_lock) {
+ hash_for_each_safe(its->translation_cache, bkt, tmp, entry,
+ node) {
+ if (((entry->key >> 16) & device_id_mask) ==
+ device_id) {
+ hash_del(&entry->node);
+ hlist_add_head(&entry->node, &removed);
+ }
+ }
+ }
+
+ its_cache_drop_removed_translations(its->dev->kvm, &removed);
+}
+
+static void its_cache_handle_inv_devicer(struct vgic_v5_its *its)
+{
+ const u8 event_id_bits = its->inv_devicer.event_id_bits;
+ const bool structure = its->dt_cfgr.structure;
+ const bool l1 = its->inv_devicer.l1 && structure;
+ const u32 device_id = its->didr.device_id;
+ const u8 l2sz = its->dt_cfgr.l2sz;
+
+ if (!l1)
+ its_cache_handle_inv_devicer_l2(its, device_id, event_id_bits);
+ else
+ its_cache_handle_inv_devicer_l1(its, device_id, l2sz);
+}
+
+static void its_cache_handle_inv_eventr_l1(struct vgic_v5_its *its,
+ u32 device_id, u16 event_id,
+ u8 itt_l2sz)
+{
+ struct its_cache_translation_entry *entry;
+ struct hlist_node *tmp;
+ HLIST_HEAD(removed);
+ u8 lower_event_id_bit;
+ u16 event_id_mask;
+ int bkt;
+
+ switch (itt_l2sz) {
+ case 0:
+ lower_event_id_bit = 9;
+ break;
+ case 1:
+ lower_event_id_bit = 11;
+ break;
+ case 2:
+ lower_event_id_bit = 13;
+ break;
+ default:
+ return;
+ }
+
+ event_id_mask = (u16)((0xffffU << lower_event_id_bit) & 0xffffU);
+ event_id &= event_id_mask;
+
+ scoped_guard(raw_spinlock, &its->translation_cache_lock) {
+ hash_for_each_safe(its->translation_cache, bkt, tmp, entry,
+ node) {
+ if ((entry->key >> 16 == device_id) &&
+ ((entry->key & event_id_mask) == event_id)) {
+ hash_del(&entry->node);
+ hlist_add_head(&entry->node, &removed);
+ }
+ }
+ }
+
+ its_cache_drop_removed_translations(its->dev->kvm, &removed);
+}
+
+static void its_cache_handle_inv_eventr(struct vgic_v5_its *its)
+{
+ const bool l1 = its->inv_eventr.l1;
+ const u8 itt_l2sz = its->inv_eventr.itt_l2sz;
+ const u16 event_id = its->eidr.event_id;
+ const u32 device_id = its->didr.device_id;
+
+ if (!l1)
+ its_cache_remove_translation(its, device_id, event_id);
+ else
+ its_cache_handle_inv_eventr_l1(its, device_id, event_id,
+ itt_l2sz);
+}
+
static int parse_linear_dt(struct kvm *kvm, gpa_t dt_base, u32 device_id,
gpa_t *itt_base, bool *two_level_itt,
unsigned int *event_id_bits, unsigned int *itt_l2sz)
@@ -228,6 +494,10 @@ static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its,
return -ENXIO;
}
+ ret = its_cache_look_up_translation(its, device_id, event_id, irq);
+ if (!ret)
+ return 0;
+
if (WARN_ON_ONCE(kvm->mm != get_current()->mm))
return -EWOULDBLOCK;
@@ -260,7 +530,15 @@ static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its,
if (IS_ERR(*irq))
return PTR_ERR(*irq);
- return 0;
+ vgic_get_irq_ref(*irq);
+ ret = its_cache_add_translation(kvm, its, device_id, event_id, *irq);
+ if (ret) {
+ vgic_put_irq(kvm, *irq);
+ vgic_put_irq(kvm, *irq);
+ *irq = NULL;
+ }
+
+ return ret;
}
static void vgic_v5_its_inject_lpi(struct kvm *kvm, struct vgic_irq *irq)
@@ -385,9 +663,17 @@ static void vgic_v5_mmio_write_its_misc(struct kvm *kvm, void *dev, gpa_t addr,
size_t offset = addr & (SZ_64K - 1);
switch (offset) {
- case GICV5_ITS_CR0:
- its->enabled = FIELD_GET(GICV5_ITS_CR0_ITSEN, val);
+ case GICV5_ITS_CR0: {
+ guard(mutex)(&its->its_lock);
+
+ if (FIELD_GET(GICV5_ITS_CR0_ITSEN, val)) {
+ its->enabled = true;
+ } else {
+ its->enabled = false;
+ its_cache_clear_translations(its);
+ }
return;
+ }
case GICV5_ITS_CR1:
its->cr1.sh = FIELD_GET(GICV5_ITS_CR1_SH, val);
its->cr1.oc = FIELD_GET(GICV5_ITS_CR1_OC, val);
@@ -403,21 +689,39 @@ static void vgic_v5_mmio_write_its_misc(struct kvm *kvm, void *dev, gpa_t addr,
static void vgic_v5_write_its_dt_reg(struct vgic_v5_its *its, size_t offset,
unsigned long val)
{
+ lockdep_assert_held(&its->its_lock);
+
switch (offset) {
- case GICV5_ITS_DT_CFGR:
- its->dt_cfgr.device_id_bits =
- FIELD_GET(GICV5_ITS_DT_CFGR_DEVICEID_BITS, val);
- its->dt_cfgr.l2sz =
- FIELD_GET(GICV5_ITS_DT_CFGR_L2SZ, val);
- its->dt_cfgr.structure =
- FIELD_GET(GICV5_ITS_DT_CFGR_STRUCTURE, val);
- return;
- case GICV5_ITS_DT_BASER:
- its->dt_baser.addr =
- FIELD_GET(GICV5_ITS_DT_BASER_ADDR_MASK, val)
- << GICV5_ITS_DT_BASER_ADDR_SHIFT;
+ case GICV5_ITS_DT_CFGR: {
+ u8 device_id_bits = FIELD_GET(GICV5_ITS_DT_CFGR_DEVICEID_BITS, val);
+ u8 l2sz = FIELD_GET(GICV5_ITS_DT_CFGR_L2SZ, val);
+ bool structure = FIELD_GET(GICV5_ITS_DT_CFGR_STRUCTURE, val);
+
+ if (its->dt_cfgr.device_id_bits == device_id_bits &&
+ its->dt_cfgr.l2sz == l2sz &&
+ its->dt_cfgr.structure == structure)
+ return;
+
+ its->dt_cfgr.device_id_bits = device_id_bits;
+ its->dt_cfgr.l2sz = l2sz;
+ its->dt_cfgr.structure = structure;
+ break;
+ }
+ case GICV5_ITS_DT_BASER: {
+ u64 base = FIELD_GET(GICV5_ITS_DT_BASER_ADDR_MASK, val)
+ << GICV5_ITS_DT_BASER_ADDR_SHIFT;
+
+ if (its->dt_baser.addr == base)
+ return;
+
+ its->dt_baser.addr = base;
+ break;
+ }
+ default:
return;
}
+
+ its_cache_clear_translations(its);
}
static u64 vgic_v5_read_its_dt_baser(struct vgic_v5_its *its)
@@ -522,6 +826,8 @@ static void vgic_v5_mmio_write_its_dt_itt(struct kvm *kvm, void *dev,
FIELD_GET(GICV5_ITS_INV_DEVICER_EVENTID_BITS, val);
its->inv_devicer.i =
FIELD_GET(GICV5_ITS_INV_DEVICER_I, val);
+ if (its->inv_devicer.i)
+ its_cache_handle_inv_devicer(its);
return;
case GICV5_ITS_DIDR:
case GICV5_ITS_DIDR + 4:
@@ -538,6 +844,8 @@ static void vgic_v5_mmio_write_its_dt_itt(struct kvm *kvm, void *dev,
its->inv_eventr.itt_l2sz =
FIELD_GET(GICV5_ITS_INV_EVENTR_ITT_L2SZ, val);
its->inv_eventr.i = FIELD_GET(GICV5_ITS_INV_EVENTR_I, val);
+ if (its->inv_eventr.i)
+ its_cache_handle_inv_eventr(its);
return;
case GICV5_ITS_SYNCR:
return;
@@ -730,6 +1038,7 @@ static void vgic_v5_its_reset(struct vgic_v5_its *its)
its->gen_event_eidr.event_id = 0;
its->gen_event_didr.device_id = 0;
+ its_cache_clear_translations(its);
}
static int vgic_v5_its_create(struct kvm_device *dev, u32 type)
@@ -753,6 +1062,9 @@ static int vgic_v5_its_create(struct kvm_device *dev, u32 type)
return -ENODEV;
}
+ hash_init(its->translation_cache);
+ raw_spin_lock_init(&its->translation_cache_lock);
+
its->vgic_v5_its_base = VGIC_ADDR_UNDEF;
its->dev = dev;
@@ -770,6 +1082,7 @@ static void vgic_v5_its_destroy(struct kvm_device *kvm_dev)
{
struct vgic_v5_its *its = kvm_dev->private;
+ its_cache_clear_translations(its);
kfree(its);
kfree(kvm_dev);
}
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index c9eaa7897b57e..bf970275868c9 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -17,6 +17,7 @@
#include <kvm/iodev.h>
#include <linux/list.h>
#include <linux/jump_label.h>
+#include <linux/hashtable.h>
#include <linux/irqchip/arm-gic-v4.h>
#include <linux/irqchip/arm-gic-v5.h>
@@ -590,6 +591,12 @@ struct vgic_v5_its {
u32 device_id;
} gen_event_didr;
+ /* A hash table with 2^3 buckets */
+ DECLARE_HASHTABLE(translation_cache, 3);
+
+ /* Protects the translation_cache */
+ raw_spinlock_t translation_cache_lock;
+
/* Protects the device in general */
struct mutex its_lock;
};
--
2.34.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH 09/16] KVM: arm64: gic-v5: Add ITS MSI injection
2026-10-16 15:57 [PATCH 00/16] KVM: arm64: Add GICv5 ITS support Sascha Bischoff
` (7 preceding siblings ...)
2026-10-16 15:57 ` [PATCH 08/16] KVM: arm64: gic-v5: Add ITS translation cache maintenance Sascha Bischoff
@ 2026-10-16 15:57 ` Sascha Bischoff
2026-10-16 16:23 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 10/16] KVM: arm64: gic-v5: Add ITS userspace register access Sascha Bischoff
` (6 subsequent siblings)
15 siblings, 1 reply; 28+ messages in thread
From: Sascha Bischoff @ 2026-10-16 15:57 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, kvm
Cc: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
peter.maydell, lpieralisi, timothy.hayes, fuad.tabba
Now that KVM can resolve GICv5 ITS translations from guest memory,
allow userspace and irqfd MSI injection to target a virtual GICv5 ITS.
Add helpers to validate that an MSI address resolves to a GICv5 ITS
IODEV, find the ITS for an MSI, resolve the DeviceID/EventID pair to a
vgic_irq, and inject the resulting LPI with the GICv5 VDPEND path.
GICv5 MSIs require KVM_MSI_VALID_DEVID, as the DeviceID is part of the
ITS translation key.
Although MSI data is 32 bits, GICv5 supports only 16-bit EventIDs.
Reject a nonzero upper half before narrowing the EventID for cache
lookups, so malformed MSI data cannot alias a valid translation.
Provide both the slow path, which resolves the translation from guest
memory and populates the ITS cache, and the inatomic cached path used
by irqfd. If the cached path misses, irqfd retries in a work item. The
worker borrows the VM's mm while walking the guest tables, then drops
it before injecting the LPI.
Wire kvm_set_msi() and kvm_arch_set_irq_inatomic() to use the GICv5
ITS injection helpers for GICv5 VMs, while keeping the existing GICv3
ITS path unchanged for GICv3 VMs.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-irqfd.c | 8 ++
arch/arm64/kvm/vgic/vgic-its-v5.c | 127 +++++++++++++++++++++++++++++-
arch/arm64/kvm/vgic/vgic-v5.c | 25 ++++++
arch/arm64/kvm/vgic/vgic.h | 7 ++
4 files changed, 164 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-irqfd.c b/arch/arm64/kvm/vgic/vgic-irqfd.c
index ee1a1b214f03c..c5f5ce8b80c1d 100644
--- a/arch/arm64/kvm/vgic/vgic-irqfd.c
+++ b/arch/arm64/kvm/vgic/vgic-irqfd.c
@@ -114,6 +114,10 @@ int kvm_set_msi(struct kvm_kernel_irq_routing_entry *e,
return -1;
kvm_populate_msi(e, &msi);
+
+ if (vgic_is_v5(kvm))
+ return vgic_v5_its_inject_msi(kvm, &msi);
+
return vgic_its_inject_msi(kvm, &msi);
}
@@ -135,6 +139,10 @@ int kvm_arch_set_irq_inatomic(struct kvm_kernel_irq_routing_entry *e,
break;
kvm_populate_msi(e, &msi);
+
+ if (vgic_is_v5(kvm))
+ return vgic_v5_its_inject_cached_translation(kvm, &msi);
+
return vgic_its_inject_cached_translation(kvm, &msi);
}
diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c
index e4c0415137605..5920fa1bcb2cf 100644
--- a/arch/arm64/kvm/vgic/vgic-its-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-its-v5.c
@@ -3,7 +3,9 @@
* Copyright (C) 2025 ARM Limited, All Rights Reserved.
*/
+#include <linux/kthread.h>
#include <linux/kvm_host.h>
+#include <linux/sched/mm.h>
#include <linux/slab.h>
#include <asm/kvm_arm.h>
@@ -475,8 +477,45 @@ static int get_itte(struct kvm *kvm, u32 event_id, gpa_t itt_base,
itt_l2_entry);
}
+struct vgic_v5_its *vgic_v5_msi_to_its(struct kvm *kvm, struct kvm_msi *msi)
+{
+ struct kvm_io_device *kvm_io_dev;
+ struct vgic_io_device *iodev;
+ u64 address;
+
+ if (!vgic_has_its(kvm))
+ return ERR_PTR(-ENODEV);
+
+ if (!(msi->flags & KVM_MSI_VALID_DEVID))
+ return ERR_PTR(-EINVAL);
+
+ /*
+ * MSI data is 32 bits, but GICv5 limits EventIDs to 16 bits.
+ * Match that limit in the vITS.
+ */
+ if (msi->data >> VITS_V5_EVENT_ID_BITS)
+ return ERR_PTR(-EINVAL);
+
+ address = (u64)msi->address_hi << 32 | msi->address_lo;
+
+ guard(srcu)(&kvm->srcu);
+
+ kvm_io_dev = kvm_io_bus_get_dev(kvm, KVM_MMIO_BUS, address);
+ if (!kvm_io_dev)
+ return ERR_PTR(-EINVAL);
+
+ if (kvm_io_dev->ops != &kvm_io_gic_ops)
+ return ERR_PTR(-EINVAL);
+
+ iodev = container_of(kvm_io_dev, struct vgic_io_device, dev);
+ if (iodev->iodev_type != IODEV_GICV5_ITS)
+ return ERR_PTR(-EINVAL);
+
+ return iodev->v5its;
+}
+
static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its,
- u32 device_id, u16 event_id,
+ u32 device_id, u32 event_id,
struct vgic_irq **irq)
{
unsigned int itt_l2sz, event_id_bits;
@@ -488,13 +527,15 @@ static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its,
int ret;
lockdep_assert_held(&its->its_lock);
+ if (event_id >> VITS_V5_EVENT_ID_BITS)
+ return -EINVAL;
if (!its->enabled) {
kvm_debug("Cannot look up LPI; vITS is disabled\n");
return -ENXIO;
}
- ret = its_cache_look_up_translation(its, device_id, event_id, irq);
+ ret = its_cache_look_up_translation(its, device_id, (u16)event_id, irq);
if (!ret)
return 0;
@@ -531,7 +572,8 @@ static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its,
return PTR_ERR(*irq);
vgic_get_irq_ref(*irq);
- ret = its_cache_add_translation(kvm, its, device_id, event_id, *irq);
+ ret = its_cache_add_translation(kvm, its, device_id, (u16)event_id,
+ *irq);
if (ret) {
vgic_put_irq(kvm, *irq);
vgic_put_irq(kvm, *irq);
@@ -541,12 +583,91 @@ static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its,
return ret;
}
+int vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its,
+ u32 device_id, u32 event_id,
+ struct vgic_irq **irq)
+{
+ guard(mutex)(&its->its_lock);
+
+ return __vgic_v5_its_resolve_lpi(kvm, its, device_id, event_id, irq);
+}
+
static void vgic_v5_its_inject_lpi(struct kvm *kvm, struct vgic_irq *irq)
{
kvm_call_hyp(__vgic_v5_vdpend, irq->intid, true,
kvm->arch.vgic.gicv5_vm.vm_id);
}
+int vgic_v5_its_inject_msi(struct kvm *kvm, struct kvm_msi *msi)
+{
+ struct vgic_v5_its *its;
+ struct vgic_irq *irq;
+ bool borrowed_mm = false;
+ int ret;
+
+ ret = vgic_v5_check_msi(kvm, msi);
+ if (ret)
+ return ret;
+
+ its = vgic_v5_msi_to_its(kvm, msi);
+ if (IS_ERR(its))
+ return PTR_ERR(its);
+
+ /* An irqfd cache miss is resolved by a worker outside the VM's mm. */
+ if (current->mm != kvm->mm) {
+ if (WARN_ON_ONCE(current->mm || !(current->flags & PF_KTHREAD)))
+ return -EWOULDBLOCK;
+
+ if (!mmget_not_zero(kvm->mm))
+ return -EFAULT;
+
+ kthread_use_mm(kvm->mm);
+ borrowed_mm = true;
+ }
+
+ ret = vgic_v5_its_resolve_lpi(kvm, its, msi->devid, msi->data, &irq);
+ if (borrowed_mm) {
+ kthread_unuse_mm(kvm->mm);
+ mmput(kvm->mm);
+ }
+
+ if (ret)
+ return ret;
+
+ vgic_v5_its_inject_lpi(kvm, irq);
+ vgic_put_irq(kvm, irq);
+
+ return 1;
+}
+
+int vgic_v5_its_inject_cached_translation(struct kvm *kvm, struct kvm_msi *msi)
+{
+ struct vgic_v5_its *its;
+ struct vgic_irq *irq;
+ int ret;
+
+ ret = vgic_v5_check_msi(kvm, msi);
+ if (ret)
+ return ret;
+
+ its = vgic_v5_msi_to_its(kvm, msi);
+ if (IS_ERR(its))
+ return PTR_ERR(its);
+
+ if (!READ_ONCE(its->enabled))
+ return -EWOULDBLOCK;
+
+ ret = its_cache_look_up_translation(its, msi->devid, (u16)msi->data,
+ &irq);
+ if (ret)
+ return -EWOULDBLOCK;
+
+ vgic_v5_its_inject_lpi(kvm, irq);
+ vgic_put_irq(kvm, irq);
+
+ return 0;
+}
+
static void vgic_v5_its_handle_read_event(struct kvm *kvm,
struct vgic_v5_its *its)
{
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 18184020e188b..c90e00f56b298 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -11,6 +11,7 @@
#include <linux/kvm_host.h>
#include <linux/uaccess.h>
+#include "vgic-mmio.h"
#include "vgic-v5-tables.h"
#include "vgic.h"
@@ -1635,3 +1636,27 @@ unsigned long vgic_v5_handle_guest_coresight_idreg_read(u32 offset)
return 0;
}
}
+
+int vgic_v5_check_msi(struct kvm *kvm, struct kvm_msi *msi)
+{
+ struct kvm_io_device *kvm_io_dev;
+ struct vgic_io_device *iodev;
+ u64 address;
+
+ address = (u64)msi->address_hi << 32 | msi->address_lo;
+
+ guard(srcu)(&kvm->srcu);
+
+ kvm_io_dev = kvm_io_bus_get_dev(kvm, KVM_MMIO_BUS, address);
+ if (!kvm_io_dev)
+ return -EINVAL;
+
+ if (kvm_io_dev->ops != &kvm_io_gic_ops)
+ return -EINVAL;
+
+ iodev = container_of(kvm_io_dev, struct vgic_io_device, dev);
+ if (iodev->iodev_type != IODEV_GICV5_ITS)
+ return -EINVAL;
+
+ return 0;
+}
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 5a9aa9bd11dd7..3d06786142c2d 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -362,6 +362,13 @@ void vgic_v3_put(struct kvm_vcpu *vcpu);
bool vgic_has_its(struct kvm *kvm);
int kvm_vgic_register_its_device(void);
int kvm_vgic_v5_register_its_device(void);
+int vgic_v5_check_msi(struct kvm *kvm, struct kvm_msi *msi);
+struct vgic_v5_its *vgic_v5_msi_to_its(struct kvm *kvm, struct kvm_msi *msi);
+int vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its,
+ u32 device_id, u32 event_id,
+ struct vgic_irq **irq);
+int vgic_v5_its_inject_msi(struct kvm *kvm, struct kvm_msi *msi);
+int vgic_v5_its_inject_cached_translation(struct kvm *kvm, struct kvm_msi *msi);
void vgic_enable_lpis(struct kvm_vcpu *vcpu);
void vgic_flush_pending_lpis(struct kvm_vcpu *vcpu);
int vgic_its_inject_msi(struct kvm *kvm, struct kvm_msi *msi);
--
2.34.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH 10/16] KVM: arm64: gic-v5: Add ITS userspace register access
2026-10-16 15:57 [PATCH 00/16] KVM: arm64: Add GICv5 ITS support Sascha Bischoff
` (8 preceding siblings ...)
2026-10-16 15:57 ` [PATCH 09/16] KVM: arm64: gic-v5: Add ITS MSI injection Sascha Bischoff
@ 2026-10-16 15:57 ` Sascha Bischoff
2026-10-16 16:19 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 11/16] irqchip/gic-v5: Enable direct injection in ITS Sascha Bischoff
` (5 subsequent siblings)
15 siblings, 1 reply; 28+ messages in thread
From: Sascha Bischoff @ 2026-10-16 15:57 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, kvm
Cc: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
peter.maydell, lpieralisi, timothy.hayes, fuad.tabba
Userspace needs access to the emulated GICv5 ITS MMIO state in order
to inspect and restore the device state around VM reset and migration.
Expose the GICv5 ITS register frame through KVM_DEV_ARM_VGIC_GRP_ITS_REGS.
Reuse the same MMIO register descriptors used by the guest-facing
IODEV, but apply the usual userspace access constraints: the ITS base
address must have been configured, the register offset must map to an
emulated ITS register, and userspace accesses must use the natural
32-bit or 64-bit register alignment.
Add control handling for KVM_DEV_ARM_ITS_CTRL_RESET. The reset is
performed while all vCPUs are stopped and while holding the ITS lock,
so it cannot race with guest MMIO updates or translation lookups. The
reset clears guest-programmed state and cached translations while
preserving the advertised ITS capability defaults.
Do not expose GICv5 ITS SAVE_TABLES or RESTORE_TABLES controls. For
GICv5, the guest-owned DT and ITTs are the authoritative translation
state, and the KVM translation cache is disposable state that can be
rebuilt from guest memory.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-its-v5.c | 270 +++++++++++++++++++++++++-----
1 file changed, 229 insertions(+), 41 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c
index 5920fa1bcb2cf..62c1ca8004007 100644
--- a/arch/arm64/kvm/vgic/vgic-its-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-its-v5.c
@@ -851,6 +851,19 @@ static u64 vgic_v5_read_its_dt_baser(struct vgic_v5_its *its)
its->dt_baser.addr >> GICV5_ITS_DT_BASER_ADDR_SHIFT);
}
+static int vgic_v5_uaccess_write_its_dt_itt(struct kvm *kvm, void *dev,
+ gpa_t addr, unsigned int len,
+ unsigned long val)
+{
+ struct vgic_v5_its *its = dev;
+ size_t offset = addr & (SZ_64K - 1);
+
+ guard(mutex)(&its->its_lock);
+
+ vgic_v5_write_its_dt_reg(its, offset, val);
+ return 0;
+}
+
static unsigned long vgic_v5_mmio_read_its_dt_itt(struct kvm *kvm, void *dev,
gpa_t addr, unsigned int len)
{
@@ -1018,6 +1031,13 @@ static void vgic_v5_mmio_write_its_wi(struct kvm *kvm, void *dev, gpa_t addr,
.its_read = rd, .its_write = wr, \
}
+#define REGISTER_ITS_V5_DESC_UACCESS(off, rd, wr, uwr, length, acc) \
+ { \
+ .reg_offset = off, .len = length, .access_flags = acc, \
+ .its_read = rd, .its_write = wr, \
+ .uaccess_its_write = uwr, \
+ }
+
static const struct vgic_register_region vgic_v5_its_registers[] = {
REGISTER_ITS_V5_DESC(GICV5_ITS_IDR0, vgic_v5_mmio_read_its_misc,
vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit),
@@ -1033,14 +1053,16 @@ static const struct vgic_register_region vgic_v5_its_registers[] = {
vgic_v5_mmio_write_its_misc, 4, VGIC_ACCESS_32bit),
REGISTER_ITS_V5_DESC(GICV5_ITS_CR1, vgic_v5_mmio_read_its_misc,
vgic_v5_mmio_write_its_misc, 4, VGIC_ACCESS_32bit),
- REGISTER_ITS_V5_DESC(GICV5_ITS_DT_BASER,
- vgic_v5_mmio_read_its_dt_itt,
- vgic_v5_mmio_write_its_dt_itt, 8,
- VGIC_ACCESS_64bit | VGIC_ACCESS_32bit),
- REGISTER_ITS_V5_DESC(GICV5_ITS_DT_CFGR,
- vgic_v5_mmio_read_its_dt_itt,
- vgic_v5_mmio_write_its_dt_itt, 4,
- VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC_UACCESS(GICV5_ITS_DT_BASER,
+ vgic_v5_mmio_read_its_dt_itt,
+ vgic_v5_mmio_write_its_dt_itt,
+ vgic_v5_uaccess_write_its_dt_itt, 8,
+ VGIC_ACCESS_64bit | VGIC_ACCESS_32bit),
+ REGISTER_ITS_V5_DESC_UACCESS(GICV5_ITS_DT_CFGR,
+ vgic_v5_mmio_read_its_dt_itt,
+ vgic_v5_mmio_write_its_dt_itt,
+ vgic_v5_uaccess_write_its_dt_itt, 4,
+ VGIC_ACCESS_32bit),
REGISTER_ITS_V5_DESC(GICV5_ITS_DIDR, vgic_v5_mmio_read_its_dt_itt,
vgic_v5_mmio_write_its_dt_itt, 8,
VGIC_ACCESS_64bit | VGIC_ACCESS_32bit),
@@ -1243,64 +1265,230 @@ static int vgic_v5_register_its_iodev(struct kvm *kvm, struct vgic_v5_its *its,
return ret;
}
+static int vgic_v5_its_has_attr_regs(struct kvm_device *dev,
+ struct kvm_device_attr *attr)
+{
+ struct vgic_v5_its *its = dev->private;
+ const struct vgic_register_region *region;
+ gpa_t offset = attr->attr;
+ int align;
+
+ if (IS_VGIC_ADDR_UNDEF(its->vgic_v5_its_base))
+ return -ENXIO;
+
+ region = vgic_find_mmio_region(vgic_v5_its_registers,
+ ARRAY_SIZE(vgic_v5_its_registers),
+ offset);
+ if (!region)
+ return -ENXIO;
+
+ align = region->access_flags & VGIC_ACCESS_64bit ? 0x7 : 0x3;
+ if (offset & align)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int vgic_v5_its_attr_regs_access(struct kvm_device *dev,
+ struct kvm_device_attr *attr,
+ u64 *reg, bool is_write)
+{
+ const struct vgic_register_region *region;
+ struct vgic_v5_its *its = dev->private;
+ gpa_t offset = attr->attr;
+ unsigned int len;
+ gpa_t addr;
+ int align;
+ int ret = 0;
+
+ mutex_lock(&dev->kvm->lock);
+
+ if (kvm_trylock_all_vcpus(dev->kvm)) {
+ mutex_unlock(&dev->kvm->lock);
+ return -EBUSY;
+ }
+
+ mutex_lock(&dev->kvm->arch.config_lock);
+
+ if (IS_VGIC_ADDR_UNDEF(its->vgic_v5_its_base)) {
+ ret = -ENXIO;
+ goto out;
+ }
+
+ region = vgic_find_mmio_region(vgic_v5_its_registers,
+ ARRAY_SIZE(vgic_v5_its_registers),
+ offset);
+ if (!region) {
+ ret = -ENXIO;
+ goto out;
+ }
+
+ /*
+ * Although the spec supports upper/lower 32-bit accesses to
+ * 64-bit ITS registers, the userspace ABI requires 64-bit
+ * accesses to all 64-bit wide registers.
+ */
+ align = region->access_flags & VGIC_ACCESS_64bit ? 0x7 : 0x3;
+ len = region->access_flags & VGIC_ACCESS_64bit ? 8 : 4;
+
+ if (offset & align) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ addr = its->vgic_v5_its_base + offset;
+
+ if (is_write) {
+ if (region->uaccess_its_write)
+ ret = region->uaccess_its_write(dev->kvm, its, addr,
+ len, *reg);
+ else
+ region->its_write(dev->kvm, its, addr, len, *reg);
+ } else {
+ *reg = region->its_read(dev->kvm, its, addr, len);
+ }
+out:
+ mutex_unlock(&dev->kvm->arch.config_lock);
+ kvm_unlock_all_vcpus(dev->kvm);
+ mutex_unlock(&dev->kvm->lock);
+
+ return ret;
+}
+
+static int vgic_v5_its_ctrl(struct kvm *kvm, struct vgic_v5_its *its, u64 attr)
+{
+ int ret = 0;
+
+ if (attr == KVM_DEV_ARM_VGIC_CTRL_INIT)
+ return 0;
+
+ mutex_lock(&kvm->lock);
+
+ if (kvm_trylock_all_vcpus(kvm)) {
+ mutex_unlock(&kvm->lock);
+ return -EBUSY;
+ }
+
+ mutex_lock(&kvm->arch.config_lock);
+ mutex_lock(&its->its_lock);
+
+ switch (attr) {
+ case KVM_DEV_ARM_ITS_CTRL_RESET:
+ vgic_v5_its_reset(its);
+ break;
+ default:
+ ret = -ENXIO;
+ break;
+ }
+
+ mutex_unlock(&its->its_lock);
+ mutex_unlock(&kvm->arch.config_lock);
+ kvm_unlock_all_vcpus(kvm);
+ mutex_unlock(&kvm->lock);
+
+ return ret;
+}
+
static int vgic_v5_its_set_attr(struct kvm_device *dev,
struct kvm_device_attr *attr)
{
struct vgic_v5_its *its = dev->private;
- u64 __user *uaddr = (u64 __user *)(long)attr->addr;
- unsigned long type = (unsigned long)attr->attr;
- u64 addr;
int ret;
- if (attr->group != KVM_DEV_ARM_VGIC_GRP_ADDR)
- return -ENXIO;
+ switch (attr->group) {
+ case KVM_DEV_ARM_VGIC_GRP_ADDR: {
+ u64 __user *uaddr = (u64 __user *)(long)attr->addr;
+ unsigned long type = (unsigned long)attr->attr;
+ u64 addr;
- if (type != KVM_VGIC_V5_ADDR_TYPE_ITS)
- return -ENODEV;
+ if (type != KVM_VGIC_V5_ADDR_TYPE_ITS)
+ return -ENODEV;
- if (copy_from_user(&addr, uaddr, sizeof(addr)))
- return -EFAULT;
+ if (copy_from_user(&addr, uaddr, sizeof(addr)))
+ return -EFAULT;
- ret = vgic_check_iorange(dev->kvm, its->vgic_v5_its_base, addr,
- SZ_64K, KVM_VGIC_V5_ITS_SIZE);
- if (ret)
- return ret;
+ ret = vgic_check_iorange(dev->kvm, its->vgic_v5_its_base,
+ addr, SZ_64K, KVM_VGIC_V5_ITS_SIZE);
+ if (ret)
+ return ret;
+
+ return vgic_v5_register_its_iodev(dev->kvm, its, addr);
+ }
+ case KVM_DEV_ARM_VGIC_GRP_CTRL:
+ return vgic_v5_its_ctrl(dev->kvm, its, attr->attr);
+ case KVM_DEV_ARM_VGIC_GRP_ITS_REGS: {
+ u64 __user *uaddr = (u64 __user *)(long)attr->addr;
+ u64 reg;
+
+ if (get_user(reg, uaddr))
+ return -EFAULT;
+
+ return vgic_v5_its_attr_regs_access(dev, attr, ®, true);
+ }
+ }
- return vgic_v5_register_its_iodev(dev->kvm, its, addr);
+ return -ENXIO;
}
static int vgic_v5_its_get_attr(struct kvm_device *dev,
struct kvm_device_attr *attr)
{
- struct vgic_v5_its *its = dev->private;
- u64 __user *uaddr = (u64 __user *)(long)attr->addr;
- unsigned long type = (unsigned long)attr->attr;
- u64 addr = its->vgic_v5_its_base;
+ switch (attr->group) {
+ case KVM_DEV_ARM_VGIC_GRP_ADDR: {
+ struct vgic_v5_its *its = dev->private;
+ u64 __user *uaddr = (u64 __user *)(long)attr->addr;
+ unsigned long type = (unsigned long)attr->attr;
+ u64 addr = its->vgic_v5_its_base;
- if (attr->group != KVM_DEV_ARM_VGIC_GRP_ADDR)
- return -ENXIO;
+ if (type != KVM_VGIC_V5_ADDR_TYPE_ITS)
+ return -ENODEV;
- if (type != KVM_VGIC_V5_ADDR_TYPE_ITS)
- return -ENODEV;
+ if (copy_to_user(uaddr, &addr, sizeof(addr)))
+ return -EFAULT;
- if (copy_to_user(uaddr, &addr, sizeof(addr)))
- return -EFAULT;
+ return 0;
+ }
+ case KVM_DEV_ARM_VGIC_GRP_ITS_REGS: {
+ u64 __user *uaddr = (u64 __user *)(long)attr->addr;
+ u64 reg;
+ int ret;
- return 0;
+ ret = vgic_v5_its_attr_regs_access(dev, attr, ®, false);
+ if (ret)
+ return ret;
+
+ if (put_user(reg, uaddr))
+ return -EFAULT;
+
+ return 0;
+ }
+ }
+
+ return -ENXIO;
}
static int vgic_v5_its_has_attr(struct kvm_device *dev,
struct kvm_device_attr *attr)
{
- if (attr->group != KVM_DEV_ARM_VGIC_GRP_ADDR)
- return -ENXIO;
-
- switch (attr->attr) {
- case KVM_VGIC_V5_ADDR_TYPE_ITS:
- return 0;
- default:
- return -ENXIO;
+ switch (attr->group) {
+ case KVM_DEV_ARM_VGIC_GRP_ADDR:
+ switch (attr->attr) {
+ case KVM_VGIC_V5_ADDR_TYPE_ITS:
+ return 0;
+ }
+ break;
+ case KVM_DEV_ARM_VGIC_GRP_CTRL:
+ switch (attr->attr) {
+ case KVM_DEV_ARM_VGIC_CTRL_INIT:
+ case KVM_DEV_ARM_ITS_CTRL_RESET:
+ return 0;
+ }
+ break;
+ case KVM_DEV_ARM_VGIC_GRP_ITS_REGS:
+ return vgic_v5_its_has_attr_regs(dev, attr);
}
+
+ return -ENXIO;
}
static struct kvm_device_ops kvm_arm_vgic_v5_its_ops = {
--
2.34.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH 11/16] irqchip/gic-v5: Enable direct injection in ITS
2026-10-16 15:57 [PATCH 00/16] KVM: arm64: Add GICv5 ITS support Sascha Bischoff
` (9 preceding siblings ...)
2026-10-16 15:57 ` [PATCH 10/16] KVM: arm64: gic-v5: Add ITS userspace register access Sascha Bischoff
@ 2026-10-16 15:57 ` Sascha Bischoff
2026-10-16 16:22 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 12/16] KVM: arm64: vgic: Move direct MSI support helper to common MMIO code Sascha Bischoff
` (4 subsequent siblings)
15 siblings, 1 reply; 28+ messages in thread
From: Sascha Bischoff @ 2026-10-16 15:57 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, kvm
Cc: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
peter.maydell, lpieralisi, timothy.hayes, fuad.tabba
Allow KVM to replace a host ITTE with a virtual translation that
directly injects an interrupt into a guest. Add an irq_chip callback to
install and remove the virtual translation, and disable lazy masking
while the interrupt is forwarded. The KVM caller is added later.
The ITS writes the ITTE before invalidating its cache. If invalidation
times out, hardware may still use the virtual translation even though
setup failed. Allow disable to restore the host ITTE when the forwarded
flag is clear, and retain retryable state if restoration also times
out. Keep lazy masking disabled until the host mapping is restored.
Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
drivers/irqchip/irq-gic-v5-its.c | 134 +++++++++++++++++++++++++++++
include/linux/irqchip/arm-gic-v5.h | 8 ++
2 files changed, 142 insertions(+)
diff --git a/drivers/irqchip/irq-gic-v5-its.c b/drivers/irqchip/irq-gic-v5-its.c
index 28e39b065de0e..7de40a7cff403 100644
--- a/drivers/irqchip/irq-gic-v5-its.c
+++ b/drivers/irqchip/irq-gic-v5-its.c
@@ -61,6 +61,48 @@ static void its_writeq_relaxed(struct gicv5_its_chip_data *its_node, const u64 v
writeq_relaxed(val, its_node->its_base + reg_offset);
}
+#ifdef CONFIG_KVM
+int gicv5_its_enable_direct_injection(int virq, u16 vm_id, u32 guest_lpi)
+{
+ struct virt_intr_data virt_intr = {
+ .vm_id = vm_id,
+ .guest_lpi = guest_lpi,
+ };
+ int ret;
+
+ /*
+ * The host will never see that interrupt firing again, so it
+ * is vital that we don't do any lazy masking.
+ */
+ irq_set_status_flags(virq, IRQ_DISABLE_UNLAZY);
+
+ ret = irq_set_vcpu_affinity(virq, &virt_intr);
+ if (ret && ret != -ETIMEDOUT && ret != -EBUSY)
+ irq_clear_status_flags(virq, IRQ_DISABLE_UNLAZY);
+
+ return ret;
+}
+
+int gicv5_its_disable_direct_injection(int virq)
+{
+ int ret;
+
+ ret = irq_set_vcpu_affinity(virq, NULL);
+ if (ret) {
+ pr_err("Failed to restore host interrupt\n");
+ return ret;
+ }
+
+ /*
+ * In theory the host could see the interrupt again now. Undo this
+ * again.
+ */
+ irq_clear_status_flags(virq, IRQ_DISABLE_UNLAZY);
+
+ return 0;
+}
+#endif /* CONFIG_KVM */
+
static void gicv5_its_dcache_clean(struct gicv5_its_chip_data *its, void *start,
size_t sz)
{
@@ -723,6 +765,95 @@ static void gicv5_its_compose_msi_msg(struct irq_data *d, struct msi_msg *msg)
msi_msg_set_addr(irq_data_get_msi_desc(d), msg, addr);
}
+#ifdef CONFIG_KVM
+static int gicv5_its_set_vcpu_affinity(struct irq_data *d, void *int_data)
+{
+ struct gicv5_its_dev *its_dev = irq_data_get_irq_chip_data(d);
+ u16 event_id = FIELD_GET(GICV5_ITS_HWIRQ_EVENT_ID, d->hwirq);
+ u32 dev_id = FIELD_GET(GICV5_ITS_HWIRQ_DEVICE_ID, d->hwirq);
+ struct virt_intr_data *data = int_data;
+ struct gicv5_its_chip_data *its;
+ __le64 *itte;
+ u64 itte_val, tmp;
+ int ret = 0;
+
+ if (IS_ERR(its_dev)) {
+ pr_err("Failed to find ITS entry for DeviceID 0x%x", dev_id);
+ return PTR_ERR(its_dev);
+ }
+
+ its = its_dev->its_node;
+
+ itte = gicv5_its_device_get_itte_ref(its_dev, event_id);
+ itte_val = le64_to_cpu(*itte);
+
+ /*
+ * The host entry should be valid, else something strange is going on!
+ */
+ if (!(itte_val & GICV5_ITTL2E_VALID)) {
+ if (data)
+ return -EINVAL;
+ if (!irqd_is_forwarded_to_vcpu(d))
+ return 0;
+ goto invalidate;
+ }
+
+ if (data) {
+ if (irqd_is_forwarded_to_vcpu(d)) {
+ if ((itte_val & GICV5_ITTL2E_VIRTUAL) &&
+ FIELD_GET(GICV5_ITTL2E_LPI_ID, itte_val) == data->guest_lpi &&
+ FIELD_GET(GICV5_ITTL2E_VM_ID, itte_val) == data->vm_id)
+ return 0;
+ return -EBUSY;
+ }
+
+ /* Stale virtual translation without forwarded state. */
+ if (itte_val & GICV5_ITTL2E_VIRTUAL)
+ return -EBUSY;
+
+ tmp = FIELD_PREP(GICV5_ITTL2E_LPI_ID, data->guest_lpi);
+ tmp |= FIELD_PREP(GICV5_ITTL2E_VM_ID, data->vm_id);
+ tmp |= GICV5_ITTL2E_VIRTUAL;
+ tmp |= GICV5_ITTL2E_VALID;
+
+ its_write_table_entry(its, itte, tmp);
+ } else {
+ /*
+ * A failed sync still needs retrying, even if the ITTE is
+ * host-owned.
+ */
+ if (!(itte_val & GICV5_ITTL2E_VIRTUAL) &&
+ !irqd_is_forwarded_to_vcpu(d))
+ return 0;
+
+ if (itte_val & GICV5_ITTL2E_VIRTUAL) {
+ tmp = FIELD_PREP(GICV5_ITTL2E_LPI_ID,
+ d->parent_data->hwirq);
+ tmp |= GICV5_ITTL2E_VALID;
+ its_write_table_entry(its, itte, tmp);
+ }
+ }
+
+invalidate:
+ /* Make the change ITS-visible */
+ dsb(st);
+ ret = gicv5_its_itt_cache_inv(its, dev_id, event_id);
+ if (ret) {
+ /* Keep a failed restoration distinguishable from a clean host ITTE. */
+ if (!data)
+ irqd_set_forwarded_to_vcpu(d);
+ return ret;
+ }
+
+ if (data)
+ irqd_set_forwarded_to_vcpu(d);
+ else
+ irqd_clr_forwarded_to_vcpu(d);
+
+ return 0;
+}
+#endif /* CONFIG_KVM */
+
static const struct irq_chip gicv5_its_irq_chip = {
.name = "GICv5-ITS-MSI",
.irq_mask = irq_chip_mask_parent,
@@ -732,6 +863,9 @@ static const struct irq_chip gicv5_its_irq_chip = {
.irq_get_irqchip_state = irq_chip_get_parent_state,
.irq_set_irqchip_state = irq_chip_set_parent_state,
.irq_compose_msi_msg = gicv5_its_compose_msi_msg,
+#ifdef CONFIG_KVM
+ .irq_set_vcpu_affinity = gicv5_its_set_vcpu_affinity,
+#endif /* CONFIG_KVM */
};
static struct gicv5_its_dev *gicv5_its_find_device(struct gicv5_its_chip_data *its,
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index 7e64eb0436394..791627bdf5436 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -727,4 +727,12 @@ enum gicv5_vcpu_cmd {
LPI_VIST_MAKE_INVALID, /* Triggered by a guest */
};
+struct virt_intr_data {
+ u16 vm_id;
+ u32 guest_lpi;
+};
+
+int gicv5_its_enable_direct_injection(int virq, u16 vm_id, u32 guest_lpi);
+int gicv5_its_disable_direct_injection(int virq);
+
#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH 12/16] KVM: arm64: vgic: Move direct MSI support helper to common MMIO code
2026-10-16 15:57 [PATCH 00/16] KVM: arm64: Add GICv5 ITS support Sascha Bischoff
` (10 preceding siblings ...)
2026-10-16 15:57 ` [PATCH 11/16] irqchip/gic-v5: Enable direct injection in ITS Sascha Bischoff
@ 2026-10-16 15:57 ` Sascha Bischoff
2026-10-16 15:57 ` [PATCH 13/16] KVM: arm64: gic-v5: Implement irq forwarding Sascha Bischoff
` (3 subsequent siblings)
15 siblings, 0 replies; 28+ messages in thread
From: Sascha Bischoff @ 2026-10-16 15:57 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, kvm
Cc: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
peter.maydell, lpieralisi, timothy.hayes, fuad.tabba
Move vgic_supports_direct_msis() from the GICv3 MMIO code to the
common VGIC MMIO file without changing its behavior. GICv5 will extend
this helper when irq forwarding support is added.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-mmio-v3.c | 13 -------------
arch/arm64/kvm/vgic/vgic-mmio.c | 13 +++++++++++++
2 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-mmio-v3.c b/arch/arm64/kvm/vgic/vgic-mmio-v3.c
index f5e6c1fe52431..9f16e19c25a23 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio-v3.c
+++ b/arch/arm64/kvm/vgic/vgic-mmio-v3.c
@@ -38,19 +38,6 @@ u64 update_64bit_reg(u64 reg, unsigned int offset, unsigned int len,
return reg | ((u64)val << lower);
}
-bool vgic_supports_direct_msis(struct kvm *kvm)
-{
- /*
- * Deliberately conflate vLPI and vSGI support on GICv4.1 hardware,
- * indirectly allowing userspace to control whether or not vPEs are
- * allocated for the VM.
- */
- if (system_supports_direct_sgis() && !vgic_supports_direct_sgis(kvm))
- return false;
-
- return kvm_vgic_global_state.has_gicv4 && vgic_has_its(kvm);
-}
-
bool system_supports_direct_sgis(void)
{
return kvm_vgic_global_state.has_gicv4_1 && gic_cpuif_has_vsgi();
diff --git a/arch/arm64/kvm/vgic/vgic-mmio.c b/arch/arm64/kvm/vgic/vgic-mmio.c
index 755bc2d74c352..44f4a7f33fa3e 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio.c
+++ b/arch/arm64/kvm/vgic/vgic-mmio.c
@@ -1142,6 +1142,19 @@ int vgic_register_dist_iodev(struct kvm *kvm, gpa_t dist_base_address,
len, &io_device->dev);
}
+bool vgic_supports_direct_msis(struct kvm *kvm)
+{
+ /*
+ * Deliberately conflate vLPI and vSGI support on GICv4.1 hardware,
+ * indirectly allowing userspace to control whether or not vPEs are
+ * allocated for the VM.
+ */
+ if (system_supports_direct_sgis() && !vgic_supports_direct_sgis(kvm))
+ return false;
+
+ return kvm_vgic_global_state.has_gicv4 && vgic_has_its(kvm);
+}
+
bool vgic_has_its(struct kvm *kvm)
{
struct vgic_dist *dist = &kvm->arch.vgic;
--
2.34.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH 13/16] KVM: arm64: gic-v5: Implement irq forwarding
2026-10-16 15:57 [PATCH 00/16] KVM: arm64: Add GICv5 ITS support Sascha Bischoff
` (11 preceding siblings ...)
2026-10-16 15:57 ` [PATCH 12/16] KVM: arm64: vgic: Move direct MSI support helper to common MMIO code Sascha Bischoff
@ 2026-10-16 15:57 ` Sascha Bischoff
2026-10-16 16:25 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 14/16] KVM: selftests: Add VGICv5 ITS attribute tests Sascha Bischoff
` (2 subsequent siblings)
15 siblings, 1 reply; 28+ messages in thread
From: Sascha Bischoff @ 2026-10-16 15:57 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, kvm
Cc: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
peter.maydell, lpieralisi, timothy.hayes, fuad.tabba
GICv5, like GICv4, supports direct injection of interrupts via the
ITS. Move the existing GICv4 interface behind a common shim that selects
the appropriate implementation. For GICv5 forwarding, the ITS driver
replaces a host ITTE with one targeting a guest. The host no longer
receives that interrupt until forwarding is removed and the host ITTE is
restored.
A guest can make multiple DeviceID/EventID pairs resolve to the same
LPI, even though it should not. Track the cache entry responsible for
direct injection, and let only that entry tear it down during guest
invalidation. Explicitly unforwarding an IRQ clears the owner without
removing the cached translation.
The ITS driver writes the virtual ITTE before invalidating its cache.
If invalidation times out, attempt to restore the host ITTE before
falling back to software injection. If the restoration also times out,
keep the host IRQ association and a separate LPI reference so
unforwarding can be retried even after the guest invalidates the
translation. Clear KVM's forwarding state only after the host ITTE has
been restored.
Forwarding can be set up from another process's address space through
IRQ bypass. A cache miss in this path must still walk the guest's DT and
ITT. Read the entries from the VM's mm using access_remote_vm() when
the caller does not have that mm. Hold an mm reference and protect the
memslot lookup with SRCU, and fail the lookup on a short read.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/arm.c | 6 +-
arch/arm64/kvm/vgic/vgic-its-v5.c | 122 +++++++++++++++++++-----
arch/arm64/kvm/vgic/vgic-mmio.c | 10 +-
arch/arm64/kvm/vgic/vgic-v5.c | 148 ++++++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic.c | 17 ++++
arch/arm64/kvm/vgic/vgic.h | 8 ++
include/kvm/arm_vgic.h | 11 +++
7 files changed, 296 insertions(+), 26 deletions(-)
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index d593ba9280933..2d9b2bc8f0c45 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -3171,7 +3171,7 @@ int kvm_arch_irq_bypass_add_producer(struct irq_bypass_consumer *cons,
if (irq_entry->type != KVM_IRQ_ROUTING_MSI)
return 0;
- return kvm_vgic_v4_set_forwarding(irqfd->kvm, prod->irq,
+ return kvm_vgic_set_forwarding(irqfd->kvm, prod->irq,
&irqfd->irq_entry);
}
@@ -3185,7 +3185,7 @@ void kvm_arch_irq_bypass_del_producer(struct irq_bypass_consumer *cons,
if (irq_entry->type != KVM_IRQ_ROUTING_MSI)
return;
- kvm_vgic_v4_unset_forwarding(irqfd->kvm, prod->irq);
+ kvm_vgic_unset_forwarding(irqfd->kvm, prod->irq);
}
void kvm_arch_update_irqfd_routing(struct kvm_kernel_irqfd *irqfd,
@@ -3204,7 +3204,7 @@ void kvm_arch_update_irqfd_routing(struct kvm_kernel_irqfd *irqfd,
*
* Unmap the vLPI and fall back to software LPI injection.
*/
- return kvm_vgic_v4_unset_forwarding(irqfd->kvm, irqfd->producer->irq);
+ kvm_vgic_unset_forwarding(irqfd->kvm, irqfd->producer->irq);
}
void kvm_arch_irq_bypass_stop(struct irq_bypass_consumer *cons)
diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c
index 62c1ca8004007..e71cd67652bae 100644
--- a/arch/arm64/kvm/vgic/vgic-its-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-its-v5.c
@@ -5,6 +5,7 @@
#include <linux/kthread.h>
#include <linux/kvm_host.h>
+#include <linux/mm.h>
#include <linux/sched/mm.h>
#include <linux/slab.h>
@@ -31,7 +32,41 @@ static void its_cache_drop_translation(struct kvm *kvm,
struct its_cache_translation_entry *entry)
{
struct vgic_irq *irq = entry->irq;
+ unsigned long flags;
+ int virq = 0;
+ bool hw = false;
+ 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.
+ */
+ 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;
+ virq = irq->host_irq;
+ hw = irq->hw;
+ }
+ raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
+
+ if (hw && !WARN_ON_ONCE(gicv5_its_disable_direct_injection(virq))) {
+ raw_spin_lock_irqsave(&irq->irq_lock, flags);
+ if (irq->hw && irq->host_irq == virq &&
+ !irq->forwarding_owner) {
+ irq->hw = false;
+ irq->host_irq = 0;
+ drop_forwarding_ref = true;
+ }
+ raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
+ }
+
+ if (drop_forwarding_ref)
+ vgic_put_irq(kvm, irq);
vgic_put_irq(kvm, irq);
kfree(entry);
}
@@ -50,7 +85,8 @@ static void its_cache_drop_removed_translations(struct kvm *kvm,
static int its_cache_add_translation(struct kvm *kvm, struct vgic_v5_its *its,
u32 device_id, u16 event_id,
- struct vgic_irq *irq)
+ struct vgic_irq *irq,
+ void **owner)
{
u64 key = ((u64)device_id << 16) | (u64)event_id;
struct its_cache_translation_entry *entry;
@@ -78,6 +114,8 @@ static int its_cache_add_translation(struct kvm *kvm, struct vgic_v5_its *its,
}
its_cache_drop_removed_translations(kvm, &removed);
+ if (owner)
+ *owner = entry;
return 0;
}
@@ -129,7 +167,8 @@ static void its_cache_clear_translations(struct vgic_v5_its *its)
static int its_cache_look_up_translation(struct vgic_v5_its *its,
u32 device_id, u16 event_id,
- struct vgic_irq **irq)
+ struct vgic_irq **irq,
+ void **owner)
{
u64 key = ((u64)device_id << 16) | (u64)event_id;
struct its_cache_translation_entry *entry;
@@ -142,6 +181,8 @@ static int its_cache_look_up_translation(struct vgic_v5_its *its,
return -ENXIO;
*irq = entry->irq;
+ if (owner)
+ *owner = entry;
return 0;
}
}
@@ -287,6 +328,37 @@ static void its_cache_handle_inv_eventr(struct vgic_v5_its *its)
itt_l2sz);
}
+static int vgic_v5_its_read_entry(struct kvm *kvm, gpa_t gpa, __le64 *entry)
+{
+ struct mm_struct *mm = kvm->mm;
+ unsigned long hva;
+ int srcu_idx, ret;
+
+ if (current->mm == mm)
+ return kvm_read_guest_lock(kvm, gpa, entry, sizeof(*entry));
+
+ /* Forwarding setup can run in the IRQ producer's address space. */
+ if (!mmget_not_zero(mm))
+ return -EFAULT;
+
+ srcu_idx = srcu_read_lock(&kvm->srcu);
+ hva = gfn_to_hva_prot(kvm, gpa >> PAGE_SHIFT, NULL);
+ if (kvm_is_error_hva(hva)) {
+ ret = -EFAULT;
+ goto out;
+ }
+
+ /* Table entries are 8-byte aligned, so each read fits in one page. */
+ ret = access_remote_vm(mm, hva + offset_in_page(gpa), entry,
+ sizeof(*entry), 0);
+
+out:
+ srcu_read_unlock(&kvm->srcu, srcu_idx);
+ mmput(mm);
+
+ return ret == sizeof(*entry) ? 0 : -EFAULT;
+}
+
static int parse_linear_dt(struct kvm *kvm, gpa_t dt_base, u32 device_id,
gpa_t *itt_base, bool *two_level_itt,
unsigned int *event_id_bits, unsigned int *itt_l2sz)
@@ -297,8 +369,7 @@ static int parse_linear_dt(struct kvm *kvm, gpa_t dt_base, u32 device_id,
int ret;
entry_addr = dt_base + device_id * sizeof(__le64);
- ret = kvm_read_guest_lock(kvm, entry_addr, &dt_l2_entry,
- sizeof(dt_l2_entry));
+ ret = vgic_v5_its_read_entry(kvm, entry_addr, &dt_l2_entry);
if (ret) {
kvm_debug("Failed to read guest memory for L2 DT\n");
return ret;
@@ -348,8 +419,7 @@ static int parse_two_level_dt(struct kvm *kvm, gpa_t dt_base,
entry_addr = dt_base +
(device_id >> device_id_l1_shift) * sizeof(__le64);
- ret = kvm_read_guest_lock(kvm, entry_addr, &dt_l1_entry,
- sizeof(dt_l1_entry));
+ ret = vgic_v5_its_read_entry(kvm, entry_addr, &dt_l1_entry);
if (ret) {
kvm_debug("Failed to read guest memory for L1 DT\n");
return ret;
@@ -405,8 +475,7 @@ static int parse_linear_itt(struct kvm *kvm, u32 event_id, gpa_t itt_base,
gpa_t entry_addr = itt_base + event_id * sizeof(*itt_l2_entry);
int ret;
- ret = kvm_read_guest_lock(kvm, entry_addr, itt_l2_entry,
- sizeof(*itt_l2_entry));
+ ret = vgic_v5_its_read_entry(kvm, entry_addr, itt_l2_entry);
if (ret)
kvm_debug("Failed to read guest memory for L2 ITT\n");
@@ -439,8 +508,7 @@ static int parse_two_level_itt(struct kvm *kvm, u32 event_id, gpa_t itt_base,
l1_event_id = event_id >> itt_split_bits;
entry_addr = itt_base + l1_event_id * sizeof(itt_l1_entry);
- ret = kvm_read_guest_lock(kvm, entry_addr, &itt_l1_entry,
- sizeof(itt_l1_entry));
+ ret = vgic_v5_its_read_entry(kvm, entry_addr, &itt_l1_entry);
if (ret) {
kvm_debug("Failed to read guest memory for L1 ITT\n");
return ret;
@@ -516,7 +584,8 @@ struct vgic_v5_its *vgic_v5_msi_to_its(struct kvm *kvm, struct kvm_msi *msi)
static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its,
u32 device_id, u32 event_id,
- struct vgic_irq **irq)
+ struct vgic_irq **irq,
+ void **owner)
{
unsigned int itt_l2sz, event_id_bits;
__le64 itt_l2_entry;
@@ -535,13 +604,11 @@ static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its,
return -ENXIO;
}
- ret = its_cache_look_up_translation(its, device_id, (u16)event_id, irq);
+ ret = its_cache_look_up_translation(its, device_id, (u16)event_id, irq,
+ owner);
if (!ret)
return 0;
- if (WARN_ON_ONCE(kvm->mm != get_current()->mm))
- return -EWOULDBLOCK;
-
ret = get_itt(kvm, its, device_id, &itt_base, &two_level_itt,
&event_id_bits, &itt_l2sz);
if (ret) {
@@ -572,8 +639,8 @@ static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its,
return PTR_ERR(*irq);
vgic_get_irq_ref(*irq);
- ret = its_cache_add_translation(kvm, its, device_id, (u16)event_id,
- *irq);
+ ret = its_cache_add_translation(kvm, its, device_id, (u16)event_id, *irq,
+ owner);
if (ret) {
vgic_put_irq(kvm, *irq);
vgic_put_irq(kvm, *irq);
@@ -583,13 +650,24 @@ static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its,
return ret;
}
+int vgic_v5_its_resolve_lpi_locked(struct kvm *kvm,
+ struct vgic_v5_its *its,
+ u32 device_id, u32 event_id,
+ struct vgic_irq **irq,
+ void **owner)
+{
+ return __vgic_v5_its_resolve_lpi(kvm, its, device_id, event_id, irq,
+ owner);
+}
+
int vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its,
u32 device_id, u32 event_id,
struct vgic_irq **irq)
{
guard(mutex)(&its->its_lock);
- return __vgic_v5_its_resolve_lpi(kvm, its, device_id, event_id, irq);
+ return __vgic_v5_its_resolve_lpi(kvm, its, device_id, event_id, irq,
+ NULL);
}
static void vgic_v5_its_inject_lpi(struct kvm *kvm, struct vgic_irq *irq)
@@ -657,8 +735,8 @@ int vgic_v5_its_inject_cached_translation(struct kvm *kvm, struct kvm_msi *msi)
if (!READ_ONCE(its->enabled))
return -EWOULDBLOCK;
- ret = its_cache_look_up_translation(its, msi->devid, (u16)msi->data,
- &irq);
+ ret = its_cache_look_up_translation(its, msi->devid, (u16)msi->data, &irq,
+ NULL);
if (ret)
return -EWOULDBLOCK;
@@ -680,7 +758,7 @@ static void vgic_v5_its_handle_read_event(struct kvm *kvm,
its->read_event_datar.virt = false;
ret = __vgic_v5_its_resolve_lpi(kvm, its, its->didr.device_id,
- its->eidr.event_id, &irq);
+ its->eidr.event_id, &irq, NULL);
if (ret)
return;
@@ -704,7 +782,7 @@ static void vgic_v5_its_handle_gen_event(struct kvm *kvm,
ret = __vgic_v5_its_resolve_lpi(kvm, its,
its->gen_event_didr.device_id,
- its->gen_event_eidr.event_id, &irq);
+ its->gen_event_eidr.event_id, &irq, NULL);
if (ret)
return;
diff --git a/arch/arm64/kvm/vgic/vgic-mmio.c b/arch/arm64/kvm/vgic/vgic-mmio.c
index 44f4a7f33fa3e..ca57275f87f67 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;
}
bool vgic_has_its(struct kvm *kvm)
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index c90e00f56b298..970df3f41cf24 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -1660,3 +1660,151 @@ int vgic_v5_check_msi(struct kvm *kvm, struct kvm_msi *msi)
return 0;
}
+
+static struct vgic_v5_its *
+vgic_v5_get_its(struct kvm *kvm, struct kvm_kernel_irq_routing_entry *irq_entry)
+{
+ struct kvm_msi msi = (struct kvm_msi){
+ .address_lo = irq_entry->msi.address_lo,
+ .address_hi = irq_entry->msi.address_hi,
+ .data = irq_entry->msi.data,
+ .flags = irq_entry->msi.flags,
+ .devid = irq_entry->msi.devid,
+ };
+
+ return vgic_v5_msi_to_its(kvm, &msi);
+}
+
+int kvm_vgic_v5_set_forwarding(struct kvm *kvm, int virq,
+ struct kvm_kernel_irq_routing_entry *irq_entry)
+{
+ struct vgic_v5_its *its_data;
+ struct vgic_irq *irq;
+ void *owner;
+ bool hw;
+ int ret;
+
+ if (!vgic_has_its(kvm))
+ return 0;
+
+ /* Look up the ITS; bail out if we can't look it up */
+ its_data = vgic_v5_get_its(kvm, irq_entry);
+ if (IS_ERR(its_data))
+ return 0;
+
+ /*
+ * Look up and cache the translation. Bail out if we failed to look up
+ * the translation at this stage. If the translation becomes valid at a
+ * later stage, we should fall back to software-based injection.
+ */
+ guard(mutex)(&its_data->its_lock);
+
+ ret = vgic_v5_its_resolve_lpi_locked(kvm, its_data,
+ irq_entry->msi.devid,
+ irq_entry->msi.data, &irq, &owner);
+ if (ret)
+ return 0;
+
+ scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) {
+ hw = irq->hw;
+ }
+
+ if (hw)
+ goto out_put_irq;
+
+ ret = gicv5_its_enable_direct_injection(virq, vgic_v5_vm_id(kvm),
+ vgic_v5_get_hwirq_id(irq->intid));
+ if (ret) {
+ if (ret != -ETIMEDOUT)
+ goto out_put_irq;
+
+ /* The ITTE was written; confirm restoration before falling back. */
+ if (!gicv5_its_disable_direct_injection(virq))
+ goto out_put_irq;
+
+ /* Keep the host IRQ association so a later unforward can retry. */
+ ret = 0;
+ }
+
+ scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) {
+ if (!irq->hw) {
+ /* Keep the LPI available if invalidation must retry unforwarding. */
+ vgic_get_irq_ref(irq);
+ irq->hw = true;
+ irq->host_irq = virq;
+ /*
+ * Record the entry responsible for this host IRQ, even if
+ * cleanup after a failed sync is still pending. Only this
+ * entry may unforward on invalidation; another translation
+ * could map to the same LPI.
+ */
+ irq->forwarding_owner = owner;
+ } else {
+ ret = -EBUSY;
+ }
+ }
+
+ if (ret)
+ WARN_ON_ONCE(gicv5_its_disable_direct_injection(virq));
+
+out_put_irq:
+ vgic_put_irq(kvm, irq);
+
+ return 0;
+}
+
+static struct vgic_irq *vgic_v5_host_irq_get_lpi(struct kvm *kvm, int host_irq)
+{
+ struct vgic_irq *irq;
+ unsigned long idx;
+
+ guard(rcu)();
+ xa_for_each(&kvm->arch.vgic.lpi_xa, idx, irq) {
+ if (!irq->hw || irq->host_irq != host_irq)
+ continue;
+
+ if (!vgic_try_get_irq_ref(irq))
+ return NULL;
+
+ return irq;
+ }
+
+ return NULL;
+}
+
+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;
+
+ if (WARN_ON_ONCE(gicv5_its_disable_direct_injection(virq)))
+ goto out_put_irq;
+
+ scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) {
+ if (irq->hw && irq->host_irq == 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);
+
+out_put_irq:
+ vgic_put_irq(kvm, irq);
+}
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index bc0171c479a95..ea5ed49080cd8 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -1439,3 +1439,20 @@ void vgic_irq_handle_resampling(struct vgic_irq *irq,
vgic_irq_set_phys_active(irq, false);
}
}
+
+int kvm_vgic_set_forwarding(struct kvm *kvm, int irq,
+ struct kvm_kernel_irq_routing_entry *irq_entry)
+{
+ if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
+ return kvm_vgic_v5_set_forwarding(kvm, irq, irq_entry);
+ else
+ return kvm_vgic_v4_set_forwarding(kvm, irq, irq_entry);
+}
+
+void kvm_vgic_unset_forwarding(struct kvm *kvm, int irq)
+{
+ if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
+ kvm_vgic_v5_unset_forwarding(kvm, irq);
+ else
+ kvm_vgic_v4_unset_forwarding(kvm, irq);
+}
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 3d06786142c2d..70ad0641d4f45 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -367,8 +367,16 @@ struct vgic_v5_its *vgic_v5_msi_to_its(struct kvm *kvm, struct kvm_msi *msi);
int vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its,
u32 device_id, u32 event_id,
struct vgic_irq **irq);
+int vgic_v5_its_resolve_lpi_locked(struct kvm *kvm,
+ struct vgic_v5_its *its,
+ u32 device_id, u32 event_id,
+ struct vgic_irq **irq,
+ void **owner);
int vgic_v5_its_inject_msi(struct kvm *kvm, struct kvm_msi *msi);
int vgic_v5_its_inject_cached_translation(struct kvm *kvm, struct kvm_msi *msi);
+int kvm_vgic_v5_set_forwarding(struct kvm *kvm, int irq,
+ struct kvm_kernel_irq_routing_entry *irq_entry);
+void kvm_vgic_v5_unset_forwarding(struct kvm *kvm, int irq);
void vgic_enable_lpis(struct kvm_vcpu *vcpu);
void vgic_flush_pending_lpis(struct kvm_vcpu *vcpu);
int vgic_its_inject_msi(struct kvm *kvm, struct kvm_msi *msi);
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index bf970275868c9..e3271f9301675 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -296,6 +296,13 @@ struct vgic_irq {
refcount_t refcount; /* Used for LPIs */
u32 hwintid; /* HW INTID number */
unsigned int host_irq; /* linux irq corresponding to hwintid */
+ /*
+ * Opaque identity of the mapping responsible for direct injection. On
+ * invalidation, only the matching mapping may disable forwarding.
+ * Protected by irq_lock. It can be NULL while an unforward retry is
+ * pending after the mapping has been invalidated.
+ */
+ void *forwarding_owner;
union {
u8 targets; /* GICv2 target VCPUs mask */
u32 mpidr; /* GICv3 target VCPU */
@@ -938,6 +945,10 @@ bool vgic_v5_ppi_queue_irq_unlock(struct kvm *kvm, struct vgic_irq *irq,
unsigned long flags);
void vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, struct vgic_irq *irq, bool dvi);
+int kvm_vgic_set_forwarding(struct kvm *kvm, int irq,
+ struct kvm_kernel_irq_routing_entry *irq_entry);
+void kvm_vgic_unset_forwarding(struct kvm *kvm, int irq);
+
bool vgic_state_is_nested(struct kvm_vcpu *vcpu);
/* CPU HP callbacks */
--
2.34.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH 14/16] KVM: selftests: Add VGICv5 ITS attribute tests
2026-10-16 15:57 [PATCH 00/16] KVM: arm64: Add GICv5 ITS support Sascha Bischoff
` (12 preceding siblings ...)
2026-10-16 15:57 ` [PATCH 13/16] KVM: arm64: gic-v5: Implement irq forwarding Sascha Bischoff
@ 2026-10-16 15:57 ` Sascha Bischoff
2026-10-16 16:18 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 15/16] KVM: selftests: Add VGICv5 ITS translation cache tests Sascha Bischoff
2026-10-16 15:57 ` [PATCH 16/16] KVM: arm64: gic-v5: Document ITS userspace ABI Sascha Bischoff
15 siblings, 1 reply; 28+ messages in thread
From: Sascha Bischoff @ 2026-10-16 15:57 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, kvm
Cc: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
peter.maydell, lpieralisi, timothy.hayes, fuad.tabba
Add coverage for the VGICv5 ITS device address and ITS_REGS attribute
groups. Userspace needs to be able to discover and save the virtual
ITS register state, while KVM must reject invalid address placement
and malformed register accesses.
Verify that the ITS device exposes only the ITS address type, reports
VGIC_ADDR_UNDEF before configuration, rejects misaligned and
out-of-IPA bases, accepts a valid 64K-aligned base, reports it back,
and rejects a second base assignment.
Exercise ITS_REGS after the ITS base is configured by checking all
exposed offsets, read/write-back access, bad offsets, bad alignment,
bad userspace pointers and idle status register values. Also
initialise the shared VM wrapper so non-ITS tests safely ignore the
optional ITS fd during cleanup.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
tools/testing/selftests/kvm/arm64/vgic_v5.c | 251 +++++++++++++++++-
.../selftests/kvm/include/arm64/gic_v5.h | 60 +++++
2 files changed, 300 insertions(+), 11 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
index d5df742b23001..b7fb002a64f6c 100644
--- a/tools/testing/selftests/kvm/arm64/vgic_v5.c
+++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
@@ -43,6 +43,7 @@ static u64 max_phys_size;
struct vm_gic {
struct kvm_vm *vm;
int gic_fd;
+ int its_fd;
u32 gic_dev_type;
};
@@ -581,9 +582,20 @@ static int run_vcpu(struct kvm_vcpu *vcpu)
return __vcpu_run(vcpu) ? -errno : 0;
}
+static void vm_gic_init(struct vm_gic *v)
+{
+ v->vm = NULL;
+ v->gic_fd = -1;
+ v->its_fd = -1;
+ v->gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+}
+
static void vm_gic_destroy(struct vm_gic *v)
{
- close(v->gic_fd);
+ if (v->its_fd >= 0)
+ close(v->its_fd);
+ if (v->gic_fd >= 0)
+ close(v->gic_fd);
kvm_vm_free(v->vm);
}
@@ -623,7 +635,7 @@ static void vgic_v5_spi_line_vm_create(struct vm_gic *v,
{
u64 attr;
- v->gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ vm_gic_init(v);
v->vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
v->gic_fd = kvm_create_device(v->vm, v->gic_dev_type);
*vcpu = vm_vcpu_add(v->vm, 0, guest_spi_line_code);
@@ -654,7 +666,7 @@ static void vgic_v5_lpi_ist_vm_create(struct vm_gic *v,
u64 attr;
int i;
- v->gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ vm_gic_init(v);
v->vm = __vm_create(VM_SHAPE_DEFAULT, VGIC_V5_LPI_NR_VCPUS, 0);
v->gic_fd = kvm_create_device(v->vm, v->gic_dev_type);
@@ -779,6 +791,12 @@ static const struct vgic_region_attr gic_v5_irs_region = {
.alignment = GICV5_IRS_ALIGN,
};
+static const struct vgic_region_attr gic_v5_its_region = {
+ .attr = KVM_VGIC_V5_ADDR_TYPE_ITS,
+ .size = GICV5_ITS_SIZE,
+ .alignment = GICV5_ITS_ALIGN,
+};
+
struct vgic_sysreg_attr {
const char *name;
u32 encoding;
@@ -863,6 +881,39 @@ static const struct vgic_irs_reg_attr gic_v5_irs_regs[] = {
IRS_REG(GICV5_IRS_SWERR_SYNDROMER1),
};
+static const struct vgic_irs_reg_attr gic_v5_its_regs[] = {
+ IRS_REG(GICV5_ITS_IDR0),
+ IRS_REG(GICV5_ITS_IDR1),
+ IRS_REG(GICV5_ITS_IDR2),
+ IRS_REG(GICV5_ITS_IIDR),
+ IRS_REG(GICV5_ITS_AIDR),
+ IRS_REG(GICV5_ITS_CR0),
+ IRS_REG(GICV5_ITS_CR1),
+ IRS_REG(GICV5_ITS_DT_BASER),
+ IRS_REG(GICV5_ITS_DT_CFGR),
+ IRS_REG(GICV5_ITS_DIDR),
+ IRS_REG(GICV5_ITS_EIDR),
+ IRS_REG(GICV5_ITS_INV_EVENTR),
+ IRS_REG(GICV5_ITS_INV_DEVICER),
+ IRS_REG(GICV5_ITS_READ_EVENTR),
+ IRS_REG(GICV5_ITS_READ_EVENT_DATAR),
+ IRS_REG(GICV5_ITS_STATUSR),
+ IRS_REG(GICV5_ITS_SYNCR),
+ IRS_REG(GICV5_ITS_SYNC_STATUSR),
+ IRS_REG(GICV5_ITS_GEN_EVENT_DIDR),
+ IRS_REG(GICV5_ITS_GEN_EVENT_EIDR),
+ IRS_REG(GICV5_ITS_GEN_EVENTR),
+ IRS_REG(GICV5_ITS_GEN_EVENT_STATUSR),
+ IRS_REG(GICV5_ITS_MEC_IDR),
+ IRS_REG(GICV5_ITS_MEC_MECID_R),
+ IRS_REG(GICV5_ITS_MPAM_IDR),
+ IRS_REG(GICV5_ITS_MPAM_PARTID_R),
+ IRS_REG(GICV5_ITS_SWERR_STATUSR),
+ IRS_REG(GICV5_ITS_SWERR_SYNDROMER0),
+ IRS_REG(GICV5_ITS_SWERR_SYNDROMER1),
+ IRS_REG(GICV5_ITS_MPAM_PMGR),
+};
+
static const struct vgic_sysreg_attr gic_v5_cpu_sysregs[] = {
SR(SYS_ICC_ICSR_EL1),
SR(SYS_ICC_PPI_ENABLER0_EL1),
@@ -945,7 +996,7 @@ static void test_vgic_v5_addr_attrs(void)
u64 addr;
int ret;
- v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ vm_gic_init(&v);
v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
@@ -1001,6 +1052,7 @@ static void test_vgic_v5_addr_attrs(void)
vm_gic_destroy(&v);
/* Try running a VM without ever setting the IRS base addr */
+ vm_gic_init(&v);
v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
vcpu = vm_vcpu_add(v.vm, 0, NULL);
@@ -1019,7 +1071,7 @@ static void test_vgic_v5_nr_irqs_attrs(void)
u32 nr_irqs;
int ret;
- v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ vm_gic_init(&v);
v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
@@ -1077,6 +1129,7 @@ static void test_vgic_v5_nr_irqs_attrs(void)
vm_gic_destroy(&v);
/* If userspace does not set NR_IRQS, init selects the default. */
+ vm_gic_init(&v);
v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
vcpu = vm_vcpu_add(v.vm, 0, NULL);
@@ -1096,7 +1149,6 @@ static void test_vgic_v5_nr_irqs_attrs(void)
TEST_ASSERT(ret && errno == EBUSY, "GICv5 NR_IRQS set after init");
vm_gic_destroy(&v);
-
}
static void test_vgic_v5_irs_regs_attrs(void)
@@ -1106,7 +1158,7 @@ static void test_vgic_v5_irs_regs_attrs(void)
u64 attr, val;
int ret, i;
- v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ vm_gic_init(&v);
v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
vcpu = vm_vcpu_add(v.vm, 0, NULL);
@@ -1287,6 +1339,177 @@ static void test_vgic_v5_irs_regs_attrs(void)
vm_gic_destroy(&v);
}
+static void test_vgic_v5_its_attrs(void)
+{
+ struct vm_gic v;
+ u64 attr, val;
+ int ret, i;
+
+ vm_gic_init(&v);
+ v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
+ v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
+ v.its_fd = kvm_create_device(v.vm, KVM_DEV_TYPE_ARM_VGIC_V5_ITS);
+
+ kvm_has_device_attr(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ gic_v5_its_region.attr);
+
+ /* We should have the ITS Address attr */
+ ret = __kvm_has_device_attr(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_ITS);
+ TEST_ASSERT(!ret, "GICv5 ITS address attr missing");
+
+ /* But not the IRS one */
+ ret = __kvm_has_device_attr(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS);
+ TEST_ASSERT(ret && errno == ENXIO, "GICv5 ITS accepted IRS address attr");
+
+ /* Default address is undefined */
+ ret = __kvm_device_attr_get(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_ITS, &attr);
+ TEST_ASSERT(!ret && attr == (-1ULL),
+ "GICv5 ITS returns VGIC_ADDR_UNDEF");
+
+ /* Misaligned address */
+ attr = gic_v5_its_region.alignment / 0x10;
+ ret = __kvm_device_attr_set(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_ITS, &attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 ITS base not aligned");
+
+ /* Address out of PA range */
+ attr = max_phys_size;
+ ret = __kvm_device_attr_set(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_ITS, &attr);
+ TEST_ASSERT(ret && errno == E2BIG, "GICv5 ITS address beyond IPA limit");
+
+ /* Address + size too big to fit in PA range */
+ attr = max_phys_size - gic_v5_its_region.alignment;
+ ret = __kvm_device_attr_set(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_ITS, &attr);
+ TEST_ASSERT(ret && errno == E2BIG,
+ "half of the ITS is beyond IPA limit");
+
+ /* Set a valid address */
+ attr = GICV5_ITS_BASE_GPA;
+ ret = __kvm_device_attr_set(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_ITS, &attr);
+ TEST_ASSERT(!ret, "GICv5 ITS base correctly set");
+
+ /* Read back the set address */
+ attr = 0xbad;
+ ret = __kvm_device_attr_get(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_ITS, &attr);
+ TEST_ASSERT(!ret && attr == GICV5_ITS_BASE_GPA,
+ "GICv5 ITS base readback mismatch");
+
+ /* Attempt to set the address again */
+ attr = GICV5_ITS_BASE_GPA + gic_v5_its_region.alignment;
+ ret = __kvm_device_attr_set(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_ITS, &attr);
+ TEST_ASSERT(ret && errno == EEXIST, "GICv5 ITS base set again");
+
+ /* Check existing group/attribute */
+ kvm_has_device_attr(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
+ GICV5_ITS_IDR0);
+
+ /* Check MMIO by checking if present, reading, then writing back */
+ for (i = 0; i < ARRAY_SIZE(gic_v5_its_regs); i++) {
+ attr = gic_v5_its_regs[i].attr;
+ ret = __kvm_has_device_attr(v.its_fd,
+ KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
+ attr);
+ TEST_ASSERT(!ret, "GICv5 ITS_REGS missing %s",
+ gic_v5_its_regs[i].name);
+
+ val = 0xbad;
+ ret = __kvm_device_attr_get(v.its_fd,
+ KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
+ attr, &val);
+ TEST_ASSERT(!ret, "GICv5 ITS_REGS get failed for %s",
+ gic_v5_its_regs[i].name);
+
+ ret = __kvm_device_attr_set(v.its_fd,
+ KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
+ attr, &val);
+ TEST_ASSERT(!ret, "GICv5 ITS_REGS set failed for %s",
+ gic_v5_its_regs[i].name);
+ }
+
+ /* Check setting and getting an out-of-range offset */
+ attr = GICV5_ITS_SIZE;
+ ret = __kvm_has_device_attr(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
+ attr);
+ TEST_ASSERT(ret && errno == ENXIO, "GICv5 ITS_REGS accepted bad offset");
+
+ ret = __kvm_device_attr_get(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
+ attr, &val);
+ TEST_ASSERT(ret && errno == ENXIO, "GICv5 ITS_REGS get bad offset");
+
+ /* Check (mis)alignment */
+ attr = GICV5_ITS_IDR0 + 2;
+ ret = __kvm_has_device_attr(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
+ attr);
+ TEST_ASSERT(ret && errno == EINVAL,
+ "GICv5 ITS_REGS accepted unaligned 32-bit attr");
+
+ attr = GICV5_ITS_DT_BASER + 4;
+ ret = __kvm_has_device_attr(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
+ attr);
+ TEST_ASSERT(ret && errno == EINVAL,
+ "GICv5 ITS_REGS accepted unaligned 64-bit attr");
+
+ /* Fail to provide attr pointer */
+ ret = __kvm_device_attr_get(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
+ GICV5_ITS_IDR0, NULL);
+ TEST_ASSERT(ret && errno == EFAULT, "GICv5 ITS_REGS get with bad pointer");
+
+ ret = __kvm_device_attr_set(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
+ GICV5_ITS_IDR0, NULL);
+ TEST_ASSERT(ret && errno == EFAULT, "GICv5 ITS_REGS set with bad pointer");
+
+ /* Make sure we can restore the vITS's enable state */
+ val = GICV5_ITS_CR0_ITSEN;
+ ret = __kvm_device_attr_set(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
+ GICV5_ITS_CR0, &val);
+ TEST_ASSERT(!ret, "GICv5 ITS_REGS failed to enable ITS");
+
+ /* And DT CFGR/BASER */
+ val = 0;
+ ret = __kvm_device_attr_set(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
+ GICV5_ITS_DT_CFGR, &val);
+ TEST_ASSERT(!ret, "GICv5 ITS_REGS rejected enabled DT_CFGR update");
+ ret = __kvm_device_attr_get(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
+ GICV5_ITS_DT_CFGR, &val);
+ TEST_ASSERT(!ret && !val, "GICv5 ITS_REGS DT_CFGR restore mismatch");
+
+ ret = __kvm_device_attr_set(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
+ GICV5_ITS_DT_BASER, &val);
+ TEST_ASSERT(!ret, "GICv5 ITS_REGS rejected enabled DT_BASER update");
+ ret = __kvm_device_attr_get(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
+ GICV5_ITS_DT_BASER, &val);
+ TEST_ASSERT(!ret && !val, "GICv5 ITS_REGS DT_BASER restore mismatch");
+
+ vm_gic_destroy(&v);
+
+ vm_gic_init(&v);
+ v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
+ v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
+ v.its_fd = kvm_create_device(v.vm, KVM_DEV_TYPE_ARM_VGIC_V5_ITS);
+
+ /* Try to access MMIO before setting ITS address */
+ ret = __kvm_has_device_attr(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
+ GICV5_ITS_IDR0);
+ TEST_ASSERT(ret && errno == ENXIO,
+ "GICv5 ITS_REGS accepted without ITS base");
+
+ val = 0;
+ ret = __kvm_device_attr_get(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
+ GICV5_ITS_IDR0, &val);
+ TEST_ASSERT(ret && errno == ENXIO,
+ "GICv5 ITS_REGS get accepted without ITS base");
+
+ vm_gic_destroy(&v);
+}
+
static void test_vgic_v5_ist_attrs(void)
{
struct kvm_vgic_v5_ist ist_attr;
@@ -1296,7 +1519,7 @@ static void test_vgic_v5_ist_attrs(void)
u64 attr;
int ret;
- v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ vm_gic_init(&v);
v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
vcpu = vm_vcpu_add(v.vm, 0, NULL);
@@ -1383,6 +1606,7 @@ static void test_vgic_v5_ist_attrs(void)
vm_gic_destroy(&v);
/* IST restore is rejected after the VM has run. */
+ vm_gic_init(&v);
v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
vcpu = vm_vcpu_add(v.vm, 0, guest_code);
@@ -1411,7 +1635,7 @@ static void test_vgic_v5_userspace_ppis_attrs(void)
u64 user_ppis[2];
int ret;
- v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ vm_gic_init(&v);
v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
vcpu = vm_vcpu_add(v.vm, 0, NULL);
@@ -1452,7 +1676,7 @@ static void test_vgic_v5_cpu_sysreg_attrs(void)
u64 attr, val;
int ret, i;
- v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ vm_gic_init(&v);
v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
vcpu = vm_vcpu_add(v.vm, 0, NULL);
@@ -1544,7 +1768,7 @@ static void test_vgic_v5_sparse_vcpu_ids(void)
struct vm_gic v;
u64 attr;
- v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ vm_gic_init(&v);
v.vm = __vm_create(VM_SHAPE_DEFAULT, VGIC_V5_SPARSE_NR_VCPUS, 0);
v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
@@ -1588,6 +1812,7 @@ static void test_vgic_v5_ppis(u32 gic_dev_type)
uint64_t attr;
int ret, i;
+ vm_gic_init(&v);
v.gic_dev_type = gic_dev_type;
v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
@@ -1983,6 +2208,7 @@ int test_kvm_device(u32 gic_dev_type)
struct vm_gic v;
int ret;
+ vm_gic_init(&v);
v.vm = vm_create_with_vcpus(NR_VCPUS, guest_code, vcpus);
/* try to create a non existing KVM device */
@@ -2014,6 +2240,9 @@ void run_tests(u32 gic_dev_type)
pr_info("Test VGICv5 IRS_REGS attrs\n");
test_vgic_v5_irs_regs_attrs();
+ pr_info("Test VGICv5 ITS attrs\n");
+ test_vgic_v5_its_attrs();
+
pr_info("Test VGICv5 IST attrs\n");
test_vgic_v5_ist_attrs();
diff --git a/tools/testing/selftests/kvm/include/arm64/gic_v5.h b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
index 2d455515563e0..22ab78626af5a 100644
--- a/tools/testing/selftests/kvm/include/arm64/gic_v5.h
+++ b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
@@ -13,6 +13,8 @@
/* GIC component base address is guest PA space */
#define GICV5_IRS_CONFIG_BASE_GPA 0x8000000ULL
#define GICV5_IRS_CONFIG_BASE_GVA ((volatile void *)GICV5_IRS_CONFIG_BASE_GPA)
+#define GICV5_ITS_BASE_GPA 0x8030000ULL
+#define GICV5_ITS_BASE_GVA ((volatile void *)GICV5_ITS_BASE_GPA)
#define GICV5_IRS_SIZE 0x20000
#define GICV5_IRS_ALIGN 0x10000
@@ -74,6 +76,64 @@
#define GICV5_IRS_SWERR_SYNDROMER0 0x03c8
#define GICV5_IRS_SWERR_SYNDROMER1 0x03d0
+/*
+ * ITS register offsets and fields used by the GICv5 ITS selftests.
+ */
+#define GICV5_ITS_IDR0 0x0000
+#define GICV5_ITS_IDR1 0x0004
+#define GICV5_ITS_IDR2 0x0008
+#define GICV5_ITS_IIDR 0x0040
+#define GICV5_ITS_AIDR 0x0044
+#define GICV5_ITS_CR0 0x0080
+#define GICV5_ITS_CR1 0x0084
+#define GICV5_ITS_DT_BASER 0x00c0
+#define GICV5_ITS_DT_CFGR 0x00d0
+#define GICV5_ITS_DIDR 0x0100
+#define GICV5_ITS_EIDR 0x0108
+#define GICV5_ITS_INV_EVENTR 0x010c
+#define GICV5_ITS_INV_DEVICER 0x0110
+#define GICV5_ITS_READ_EVENTR 0x0114
+#define GICV5_ITS_READ_EVENT_DATAR 0x0118
+#define GICV5_ITS_STATUSR 0x0120
+#define GICV5_ITS_SYNCR 0x0140
+#define GICV5_ITS_SYNC_STATUSR 0x0148
+#define GICV5_ITS_GEN_EVENT_DIDR 0x0180
+#define GICV5_ITS_GEN_EVENT_EIDR 0x0188
+#define GICV5_ITS_GEN_EVENTR 0x018c
+#define GICV5_ITS_GEN_EVENT_STATUSR 0x0190
+#define GICV5_ITS_MEC_IDR 0x01c0
+#define GICV5_ITS_MEC_MECID_R 0x01c4
+#define GICV5_ITS_MPAM_IDR 0x0200
+#define GICV5_ITS_MPAM_PARTID_R 0x0204
+#define GICV5_ITS_SWERR_STATUSR 0x0240
+#define GICV5_ITS_SWERR_SYNDROMER0 0x0248
+#define GICV5_ITS_SWERR_SYNDROMER1 0x0250
+#define GICV5_ITS_MPAM_PMGR 0x0258
+#define GICV5_ITS_TRANSLATER 0x10000
+#define GICV5_ITS_RL_TRANSLATER 0x10008
+
+#define GICV5_ITS_SIZE 0x20000
+#define GICV5_ITS_ALIGN 0x10000
+
+#define GICV5_ITS_CR0_IDLE BIT(1)
+#define GICV5_ITS_CR0_ITSEN BIT(0)
+#define GICV5_ITS_DT_BASER_ADDR_MASK GENMASK_ULL(55, 3)
+#define GICV5_ITS_DT_BASER_ADDR_SHIFT 3ULL
+#define GICV5_ITS_DT_CFGR_STRUCTURE BIT(16)
+#define GICV5_ITS_DT_CFGR_L2SZ GENMASK(7, 6)
+#define GICV5_ITS_DT_CFGR_DEVICEID_BITS GENMASK(5, 0)
+#define GICV5_ITS_DIDR_DEVICEID GENMASK_ULL(31, 0)
+#define GICV5_ITS_EIDR_EVENTID GENMASK(15, 0)
+#define GICV5_ITS_INV_EVENTR_I BIT(31)
+#define GICV5_ITS_INV_EVENTR_ITT_L2SZ GENMASK(2, 1)
+#define GICV5_ITS_INV_EVENTR_L1 BIT(0)
+#define GICV5_ITS_INV_DEVICER_I BIT(31)
+#define GICV5_ITS_INV_DEVICER_EVENTID_BITS GENMASK(5, 1)
+#define GICV5_ITS_INV_DEVICER_L1 BIT(0)
+#define GICV5_ITS_STATUSR_IDLE BIT(0)
+#define GICV5_ITS_SYNC_STATUSR_IDLE BIT(0)
+#define GICV5_ITS_GEN_EVENT_STATUSR_IDLE BIT(0)
+
#define GICV5_IRS_IDR0_SETLPI BIT(9)
#define GICV5_IRS_IDR0_INT_DOM GENMASK(1, 0)
#define GICV5_IRS_IDR0_INT_DOM_SECURE 0b00
--
2.34.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH 15/16] KVM: selftests: Add VGICv5 ITS translation cache tests
2026-10-16 15:57 [PATCH 00/16] KVM: arm64: Add GICv5 ITS support Sascha Bischoff
` (13 preceding siblings ...)
2026-10-16 15:57 ` [PATCH 14/16] KVM: selftests: Add VGICv5 ITS attribute tests Sascha Bischoff
@ 2026-10-16 15:57 ` Sascha Bischoff
2026-10-16 16:22 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 16/16] KVM: arm64: gic-v5: Document ITS userspace ABI Sascha Bischoff
15 siblings, 1 reply; 28+ messages in thread
From: Sascha Bischoff @ 2026-10-16 15:57 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, kvm
Cc: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
peter.maydell, lpieralisi, timothy.hayes, fuad.tabba
Add end-to-end coverage for VGICv5 ITS translation and cache
invalidation. The ITS attribute test only checks the userspace API
surface; this test verifies that KVM can parse guest-provided linear
DT and ITT state and use it to deliver LPIs through guest GEN_EVENTR
and KVM_SIGNAL_MSI.
Build a two-VPE VM with IRS, ITS and an LPI IST. VPE0 programs a
linear device table and interrupt translation table, enables the
virtual ITS, enables two LPIs targeting VPE1, and sends GEN_EVENTR to
populate the translation cache with EventID to LPI A. VPE1 reports the
LPI identity it consumed.
VPE0 then rewrites the ITT to point at LPI B without invalidation, and
a host KVM_SIGNAL_MSI injection verifies that the stale cached LPI A
translation is reused. VPE0 invalidates the event with ITS_INV_EVENTR
and the host verifies that LPI B is delivered.
VPE0 then rewrites the ITT back to LPI A, invalidates the device with
ITS_INV_DEVICER, and the host verifies that LPI A is delivered again.
This exercises the guest-side event and device invalidation paths
rather than invalidating translations directly from the test host.
Also cover negative MSI paths by checking that KVM_SIGNAL_MSI rejects a
message without KVM_MSI_VALID_DEVID and rejects a message whose address
does not target the GICv5 ITS. Finally, inject once before guest-side
ITS disable to prove delivery still works, then have VPE0 disable the
ITS and verify that a cached KVM_SIGNAL_MSI delivery is rejected with
ENXIO.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
tools/testing/selftests/kvm/arm64/vgic_v5.c | 419 +++++++++++++++++-
.../selftests/kvm/include/arm64/gic_v5.h | 21 +
2 files changed, 437 insertions(+), 3 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
index b7fb002a64f6c..6ea6aef4f0f46 100644
--- a/tools/testing/selftests/kvm/arm64/vgic_v5.c
+++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
@@ -18,6 +18,7 @@
#define VGIC_V5_SPARSE_VCPU0_ID 17
#define VGIC_V5_SPARSE_VCPU1_ID 3
#define VGIC_V5_LPI_MEMSLOT 1
+#define VGIC_V5_ITS_TABLE_MEMSLOT 2
#define VGIC_V5_NR_PRIVATE_IRQS 64
#define VGIC_V5_DEFAULT_NR_SPIS 32
#define VGIC_V5_MAX_NR_SPIS BIT(10)
@@ -25,8 +26,17 @@
#define VGIC_V5_IRS_WAIT_RETRIES 1000000
#define VGIC_V5_LPI_IST_BASE_GPA (GICV5_IRS_CONFIG_BASE_GPA + VGIC_V5_IRS_SIZE)
#define VGIC_V5_LPI_IST_SIZE 0x10000
+#define VGIC_V5_ITS_DT_BASE_GPA (GICV5_ITS_BASE_GPA + GICV5_ITS_SIZE)
+#define VGIC_V5_ITS_ITT_BASE_GPA (VGIC_V5_ITS_DT_BASE_GPA + 0x10000)
+#define VGIC_V5_ITS_TABLE_SIZE 0x20000
#define LPI_TEST_TO_VPE1 0
#define LPI_TEST_TO_VPE0 1
+#define ITS_TEST_DEVICE_ID 5
+#define ITS_TEST_EVENT_ID 3
+#define ITS_TEST_LPI_A 2
+#define ITS_TEST_LPI_B 3
+#define ITS_TEST_DEVICE_ID_BITS 6
+#define ITS_TEST_EVENT_ID_BITS 4
#define RESTORE_TEST_LPI_PENDING 0
#define RESTORE_TEST_LPI_ENABLED 1
#define RESTORE_TEST_SPI_PRIO_HIGH 0
@@ -56,6 +66,13 @@ struct vm_gic {
#define GUEST_CMD_IST_SOURCE_READY 16
#define GUEST_CMD_IST_SPI_INJECT_READY 17
#define GUEST_CMD_IST_LPI_INJECT_READY 18
+#define GUEST_CMD_ITS_READY 19
+#define GUEST_CMD_ITS_LPI_DELIVERED 20
+#define GUEST_CMD_ITS_GEN_EVENT_SENT 21
+#define GUEST_CMD_ITS_ITT_REWRITTEN 22
+#define GUEST_CMD_ITS_EVENT_INVALIDATED 23
+#define GUEST_CMD_ITS_DEVICE_INVALIDATED 24
+#define GUEST_CMD_ITS_DISABLED 25
static struct kvm_vgic_v5_ist vgic_v5_ist_attr(void *spi_ist, size_t spi_size,
void *lpi_ist, size_t lpi_size)
@@ -71,6 +88,7 @@ static struct kvm_vgic_v5_ist vgic_v5_ist_attr(void *spi_ist, size_t spi_size,
static u32 spi_line_expected;
static bool spi_line_level_sensitive;
static bool lpi_ist_ready;
+static u32 its_expected_lpi;
static u32 ist_restore_irq_count[VGIC_V5_LPI_NR_VCPUS];
static u64 gicv5_hwirq(u32 type, u32 intid)
@@ -222,6 +240,140 @@ static void gicv5_configure_lpi_ist(void)
GICV5_IRS_IST_BASER), val);
}
+/* We only use a single DeviceID and hence only have a single ITT */
+static void gicv5_write_its_itte(u32 event_id, u32 lpi)
+{
+ u64 val;
+
+ val = FIELD_PREP(GICV5_ITTL2E_LPI_ID, lpi) | GICV5_ITTL2E_VALID;
+ WRITE_ONCE(*(u64 *)(VGIC_V5_ITS_ITT_BASE_GPA +
+ event_id * sizeof(u64)), val);
+}
+
+/*
+ * Set up the ITS "tables". Point the DTE to the ITT and make valid. Then set up
+ * the translation in the ITT. We are cheating a bit here, and have the most
+ * simplistic table setup possible. A Linear DT with one DeviceID in use which
+ * points to a linear ITT.
+ */
+static void gicv5_configure_its_tables(u32 lpi)
+{
+ u64 val;
+
+ val = FIELD_PREP(GICV5_DTL2E_ITT_ADDR_MASK,
+ VGIC_V5_ITS_ITT_BASE_GPA >> GICV5_ITS_DT_BASER_ADDR_SHIFT) |
+ FIELD_PREP(GICV5_DTL2E_EVENT_ID_BITS, ITS_TEST_EVENT_ID_BITS) |
+ GICV5_DTL2E_VALID;
+ WRITE_ONCE(*(u64 *)(VGIC_V5_ITS_DT_BASE_GPA +
+ ITS_TEST_DEVICE_ID * sizeof(u64)), val);
+
+ gicv5_write_its_itte(ITS_TEST_EVENT_ID, lpi);
+}
+
+/* Set ITS_DT_CFGR, ITS_DT_BASER, and enable the ITS */
+static void gicv5_configure_its(void)
+{
+ u64 val;
+
+ val = FIELD_PREP(GICV5_ITS_DT_BASER_ADDR_MASK,
+ VGIC_V5_ITS_DT_BASE_GPA >> GICV5_ITS_DT_BASER_ADDR_SHIFT);
+ writeq(val, GICV5_ITS_BASE_GVA + GICV5_ITS_DT_BASER);
+
+ val = FIELD_PREP(GICV5_ITS_DT_CFGR_DEVICEID_BITS,
+ ITS_TEST_DEVICE_ID_BITS);
+ writel(val, GICV5_ITS_BASE_GVA + GICV5_ITS_DT_CFGR);
+
+ writel(GICV5_ITS_CR0_ITSEN, GICV5_ITS_BASE_GVA + GICV5_ITS_CR0);
+ GUEST_ASSERT(readl(GICV5_ITS_BASE_GVA + GICV5_ITS_CR0) & GICV5_ITS_CR0_ITSEN);
+}
+
+/*
+ * The DT registers are read-only to the guest while the ITS is enabled. Write
+ * modified versions of the current values and check that the writes are
+ * ignored. Avoid literal zero here: the arm64 selftest MMIO helpers allow a
+ * zero-immediate operand, but a store instruction needs a register operand.
+ */
+static void gicv5_check_enabled_its_dt_regs_ro(void)
+{
+ const u64 dt_baser = readq(GICV5_ITS_BASE_GVA + GICV5_ITS_DT_BASER);
+ const u32 dt_cfgr = readl(GICV5_ITS_BASE_GVA + GICV5_ITS_DT_CFGR);
+ const u64 new_dt_baser = dt_baser ^ BIT_ULL(16);
+ const u32 new_dt_cfgr = dt_cfgr ^ BIT(0);
+
+ writeq(new_dt_baser, GICV5_ITS_BASE_GVA + GICV5_ITS_DT_BASER);
+ writel(new_dt_cfgr, GICV5_ITS_BASE_GVA + GICV5_ITS_DT_CFGR);
+
+ GUEST_ASSERT_EQ(readq(GICV5_ITS_BASE_GVA + GICV5_ITS_DT_BASER),
+ dt_baser);
+ GUEST_ASSERT_EQ(readl(GICV5_ITS_BASE_GVA + GICV5_ITS_DT_CFGR),
+ dt_cfgr);
+}
+
+/*
+ * Read out the Event Data from ITS_READ_EVENT_DATAR and check it to ensure the
+ * expected translation is returned.
+ */
+static void gicv5_check_its_read_event(u32 device_id, u32 event_id, u32 lpi)
+{
+ u64 val;
+
+ val = FIELD_PREP(GICV5_ITS_DIDR_DEVICEID, device_id);
+ writeq(val, GICV5_ITS_BASE_GVA + GICV5_ITS_DIDR);
+
+ val = FIELD_PREP(GICV5_ITS_EIDR_EVENTID, event_id);
+ writel(val, GICV5_ITS_BASE_GVA + GICV5_ITS_EIDR);
+
+ writel(GICV5_ITS_READ_EVENTR_R,
+ GICV5_ITS_BASE_GVA + GICV5_ITS_READ_EVENTR);
+
+ val = readq(GICV5_ITS_BASE_GVA + GICV5_ITS_READ_EVENT_DATAR);
+ GUEST_ASSERT(val & GICV5_ITS_READ_EVENT_DATAR_VALID);
+ GUEST_ASSERT_EQ(FIELD_GET(GICV5_ITS_READ_EVENT_DATAR_LPI_ID, val),
+ lpi);
+}
+
+/* Inject a manually triggered translation via the ITS_GEN_EVENTR */
+static void gicv5_send_its_gen_event(u32 device_id, u32 event_id)
+{
+ u64 val;
+
+ val = FIELD_PREP(GICV5_ITS_GEN_EVENT_DIDR_DEVICEID, device_id);
+ writeq(val, GICV5_ITS_BASE_GVA + GICV5_ITS_GEN_EVENT_DIDR);
+
+ val = FIELD_PREP(GICV5_ITS_GEN_EVENT_EIDR_EVENTID, event_id);
+ writel(val, GICV5_ITS_BASE_GVA + GICV5_ITS_GEN_EVENT_EIDR);
+
+ writel(GICV5_ITS_GEN_EVENTR_R |
+ FIELD_PREP(GICV5_ITS_GEN_EVENTR_TARGET_DOMAIN, 0),
+ GICV5_ITS_BASE_GVA + GICV5_ITS_GEN_EVENTR);
+}
+
+static void gicv5_invalidate_its_event(u32 device_id, u32 event_id)
+{
+ u64 val;
+
+ val = FIELD_PREP(GICV5_ITS_DIDR_DEVICEID, device_id);
+ writeq(val, GICV5_ITS_BASE_GVA + GICV5_ITS_DIDR);
+
+ val = FIELD_PREP(GICV5_ITS_EIDR_EVENTID, event_id);
+ writel(val, GICV5_ITS_BASE_GVA + GICV5_ITS_EIDR);
+
+ writel(GICV5_ITS_INV_EVENTR_I,
+ GICV5_ITS_BASE_GVA + GICV5_ITS_INV_EVENTR);
+}
+
+static void gicv5_invalidate_its_device(u32 device_id, u32 event_id_bits)
+{
+ u64 val;
+
+ val = FIELD_PREP(GICV5_ITS_DIDR_DEVICEID, device_id);
+ writeq(val, GICV5_ITS_BASE_GVA + GICV5_ITS_DIDR);
+
+ val = GICV5_ITS_INV_DEVICER_I |
+ FIELD_PREP(GICV5_ITS_INV_DEVICER_EVENTID_BITS, event_id_bits);
+ writel(val, GICV5_ITS_BASE_GVA + GICV5_ITS_INV_DEVICER);
+}
+
static void gicv5_enable_irs(void)
{
writel(GICV5_IRS_CR0_IRSEN,
@@ -417,6 +569,80 @@ static void guest_lpi_code(void)
wfi();
}
+static void guest_its_irq_handler(struct ex_regs *regs)
+{
+ u64 expected_hwirq = gicv5_lpi_hwirq(READ_ONCE(its_expected_lpi));
+ u32 hwirq;
+ u64 ia;
+
+ ia = gicr_insn(CDIA);
+ if (!GICV5_GICR_CDIA_VALID(ia))
+ return;
+
+ gsb_ack();
+ isb();
+
+ hwirq = FIELD_GET(GICV5_GICR_CDIA_INTID, ia);
+ GUEST_ASSERT_EQ(hwirq, expected_hwirq);
+
+ gic_insn(hwirq, CDDI);
+ gic_insn(0, CDEOI);
+
+ GUEST_SYNC(GUEST_CMD_ITS_LPI_DELIVERED);
+}
+
+static void guest_its_code(void)
+{
+ local_irq_disable();
+
+ if (!guest_get_vcpuid()) { /* VCPU0 */
+ gicv5_enable_irs();
+ gicv5_configure_lpi_ist();
+ gicv5_enable_lpi(ITS_TEST_LPI_A, 1);
+ gicv5_enable_lpi(ITS_TEST_LPI_B, 1);
+ gicv5_configure_its_tables(ITS_TEST_LPI_A);
+ gicv5_configure_its();
+ gicv5_check_enabled_its_dt_regs_ro();
+ gicv5_check_its_read_event(ITS_TEST_DEVICE_ID,
+ ITS_TEST_EVENT_ID,
+ ITS_TEST_LPI_A);
+ WRITE_ONCE(lpi_ist_ready, true);
+
+ GUEST_SYNC(GUEST_CMD_ITS_READY);
+
+ gicv5_send_its_gen_event(ITS_TEST_DEVICE_ID,
+ ITS_TEST_EVENT_ID);
+
+ GUEST_SYNC(GUEST_CMD_ITS_GEN_EVENT_SENT);
+
+ gicv5_write_its_itte(ITS_TEST_EVENT_ID, ITS_TEST_LPI_B);
+ GUEST_SYNC(GUEST_CMD_ITS_ITT_REWRITTEN);
+
+ gicv5_invalidate_its_event(ITS_TEST_DEVICE_ID,
+ ITS_TEST_EVENT_ID);
+ GUEST_SYNC(GUEST_CMD_ITS_EVENT_INVALIDATED);
+
+ gicv5_write_its_itte(ITS_TEST_EVENT_ID, ITS_TEST_LPI_A);
+ gicv5_invalidate_its_device(ITS_TEST_DEVICE_ID,
+ ITS_TEST_EVENT_ID_BITS);
+ GUEST_SYNC(GUEST_CMD_ITS_DEVICE_INVALIDATED);
+
+ writel(0, GICV5_ITS_BASE_GVA + GICV5_ITS_CR0);
+ GUEST_ASSERT(!(readl(GICV5_ITS_BASE_GVA + GICV5_ITS_CR0) &
+ GICV5_ITS_CR0_ITSEN));
+ GUEST_SYNC(GUEST_CMD_ITS_DISABLED);
+ } else { /* VCPU1 */
+ GUEST_ASSERT(READ_ONCE(lpi_ist_ready));
+ gicv5_cpu_enable_interrupts();
+ local_irq_enable();
+
+ GUEST_SYNC(GUEST_CMD_ITS_READY);
+ }
+
+ while (1)
+ wfi();
+}
+
static void restore_test_setup_hwirq(u64 hwirq, u32 target_vpe,
u32 priority, bool enable)
{
@@ -610,6 +836,29 @@ static void vgic_v5_map_irs(struct kvm_vm *vm)
GICV5_IRS_CONFIG_BASE_GPA, nr_irs_pages);
}
+static void vgic_v5_map_its(struct kvm_vm *vm)
+{
+ unsigned int nr_its_pages;
+
+ nr_its_pages = vm_calc_num_guest_pages(vm->mode, GICV5_ITS_SIZE);
+
+ /* Map the ITS at VA == IPA so guest MMIO writes hit the ITS IODEV. */
+ virt_map(vm, GICV5_ITS_BASE_GPA, GICV5_ITS_BASE_GPA, nr_its_pages);
+}
+
+static void vgic_v5_add_its_table_mem(struct kvm_vm *vm)
+{
+ unsigned int nr_pages;
+
+ nr_pages = vm_calc_num_guest_pages(vm->mode, VGIC_V5_ITS_TABLE_SIZE);
+ vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
+ VGIC_V5_ITS_DT_BASE_GPA,
+ VGIC_V5_ITS_TABLE_MEMSLOT,
+ nr_pages, 0);
+ virt_map(vm, VGIC_V5_ITS_DT_BASE_GPA, VGIC_V5_ITS_DT_BASE_GPA,
+ nr_pages);
+}
+
static u32 vgic_v5_irq_line_payload(u32 type, u32 num)
{
return (type << KVM_ARM_IRQ_TYPE_SHIFT) |
@@ -660,7 +909,8 @@ static void vgic_v5_spi_line_vm_create(struct vm_gic *v,
static void vgic_v5_lpi_ist_vm_create(struct vm_gic *v,
struct kvm_vcpu *vcpus[VGIC_V5_LPI_NR_VCPUS],
void (*guest_code)(void),
- void (*irq_handler)(struct ex_regs *))
+ void (*irq_handler)(struct ex_regs *),
+ bool with_its)
{
unsigned int nr_lpi_ist_pages;
u64 attr;
@@ -669,6 +919,9 @@ static void vgic_v5_lpi_ist_vm_create(struct vm_gic *v,
vm_gic_init(v);
v->vm = __vm_create(VM_SHAPE_DEFAULT, VGIC_V5_LPI_NR_VCPUS, 0);
v->gic_fd = kvm_create_device(v->vm, v->gic_dev_type);
+ if (with_its)
+ v->its_fd = kvm_create_device(v->vm,
+ KVM_DEV_TYPE_ARM_VGIC_V5_ITS);
for (i = 0; i < VGIC_V5_LPI_NR_VCPUS; i++) {
vcpus[i] = vm_vcpu_add(v->vm, i, guest_code);
@@ -687,6 +940,16 @@ static void vgic_v5_lpi_ist_vm_create(struct vm_gic *v,
KVM_VGIC_V5_ADDR_TYPE_IRS, &attr);
vgic_v5_map_irs(v->vm);
+ if (with_its) {
+ attr = GICV5_ITS_BASE_GPA;
+ kvm_device_attr_set(v->its_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_ITS, &attr);
+ kvm_device_attr_set(v->its_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+ vgic_v5_map_its(v->vm);
+ vgic_v5_add_its_table_mem(v->vm);
+ }
+
nr_lpi_ist_pages = vm_calc_num_guest_pages(v->vm->mode,
VGIC_V5_LPI_IST_SIZE);
vm_userspace_mem_region_add(v->vm, VM_MEM_SRC_ANONYMOUS,
@@ -708,7 +971,14 @@ static void vgic_v5_lpi_vm_create(struct vm_gic *v,
struct kvm_vcpu *vcpus[VGIC_V5_LPI_NR_VCPUS])
{
vgic_v5_lpi_ist_vm_create(v, vcpus, guest_lpi_code,
- guest_lpi_irq_handler);
+ guest_lpi_irq_handler, false);
+}
+
+static void vgic_v5_its_vm_create(struct vm_gic *v,
+ struct kvm_vcpu *vcpus[VGIC_V5_LPI_NR_VCPUS])
+{
+ vgic_v5_lpi_ist_vm_create(v, vcpus, guest_its_code,
+ guest_its_irq_handler, true);
}
static void vgic_v5_ist_restore_vm_create(struct vm_gic *v,
@@ -716,7 +986,7 @@ static void vgic_v5_ist_restore_vm_create(struct vm_gic *v,
void (*guest_code)(void),
void (*irq_handler)(struct ex_regs *))
{
- vgic_v5_lpi_ist_vm_create(v, vcpus, guest_code, irq_handler);
+ vgic_v5_lpi_ist_vm_create(v, vcpus, guest_code, irq_handler, false);
}
static void vgic_v5_run_spi_line_test(u32 nr_spis, u32 expected_spi,
@@ -2199,6 +2469,146 @@ static void test_vgic_v5_ist_save_restore(void)
vm_gic_destroy(&dst);
}
+static int __vgic_v5_signal_msi(struct kvm_vm *vm, u64 address,
+ u32 device_id, u32 event_id, u32 flags)
+{
+ struct kvm_msi msi = {
+ .address_lo = address,
+ .address_hi = address >> 32,
+ .data = event_id,
+ .devid = device_id,
+ .flags = flags,
+ };
+
+ return __vm_ioctl(vm, KVM_SIGNAL_MSI, &msi);
+}
+
+static void vgic_v5_signal_msi(struct kvm_vm *vm, u32 device_id,
+ u32 event_id)
+{
+ u64 addr = GICV5_ITS_BASE_GPA + GICV5_ITS_TRANSLATER;
+ int ret;
+
+ ret = __vgic_v5_signal_msi(vm, addr, device_id, event_id,
+ KVM_MSI_VALID_DEVID);
+ TEST_ASSERT(ret == 1, "KVM_SIGNAL_MSI failed for GICv5 ITS");
+}
+
+static void vgic_v5_expect_its_lpi(struct vm_gic *v, struct kvm_vcpu *vcpu,
+ u32 lpi)
+{
+ int ret;
+
+ its_expected_lpi = lpi;
+ sync_global_to_guest(v->vm, its_expected_lpi);
+
+ vgic_v5_signal_msi(v->vm, ITS_TEST_DEVICE_ID, ITS_TEST_EVENT_ID);
+
+ ret = run_vcpu(vcpu);
+ TEST_ASSERT(!ret, "Failed to run GICv5 ITS receiver");
+ vgic_v5_expect_sync(vcpu, GUEST_CMD_ITS_LPI_DELIVERED,
+ "GICv5 ITS guest did not consume expected LPI");
+}
+
+static void test_vgic_v5_its_translation_cache(void)
+{
+ struct kvm_vcpu *vcpus[VGIC_V5_LPI_NR_VCPUS];
+ struct vm_gic v;
+ u64 wrong_addr;
+ int ret;
+
+ lpi_ist_ready = false;
+ its_expected_lpi = ITS_TEST_LPI_A;
+ vgic_v5_its_vm_create(&v, vcpus);
+ sync_global_to_guest(v.vm, lpi_ist_ready);
+ sync_global_to_guest(v.vm, its_expected_lpi);
+
+ ret = run_vcpu(vcpus[0]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 ITS setup vCPU");
+ vgic_v5_expect_sync(vcpus[0], GUEST_CMD_ITS_READY,
+ "GICv5 ITS setup guest did not become ready");
+
+ ret = run_vcpu(vcpus[1]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 ITS receiver vCPU");
+ vgic_v5_expect_sync(vcpus[1], GUEST_CMD_ITS_READY,
+ "GICv5 ITS receiver guest did not become ready");
+
+ ret = run_vcpu(vcpus[0]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 ITS GEN_EVENTR sender");
+ vgic_v5_expect_sync(vcpus[0], GUEST_CMD_ITS_GEN_EVENT_SENT,
+ "GICv5 ITS setup guest did not send GEN_EVENTR");
+
+ ret = run_vcpu(vcpus[1]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 ITS GEN_EVENTR receiver");
+ vgic_v5_expect_sync(vcpus[1], GUEST_CMD_ITS_LPI_DELIVERED,
+ "GICv5 ITS GEN_EVENTR did not deliver expected LPI");
+
+ ret = __vgic_v5_signal_msi(v.vm, GICV5_ITS_BASE_GPA +
+ GICV5_ITS_TRANSLATER,
+ ITS_TEST_DEVICE_ID, ITS_TEST_EVENT_ID, 0);
+ TEST_ASSERT(ret < 0 && errno == EINVAL,
+ "GICv5 ITS accepted MSI without device ID flag");
+
+ wrong_addr = GICV5_ITS_BASE_GPA + GICV5_ITS_SIZE;
+ ret = __vgic_v5_signal_msi(v.vm, wrong_addr, ITS_TEST_DEVICE_ID,
+ ITS_TEST_EVENT_ID, KVM_MSI_VALID_DEVID);
+ TEST_ASSERT(ret < 0 && errno == EINVAL,
+ "GICv5 ITS accepted MSI for wrong address");
+
+ /*
+ * The earlier GEN_EVENTR has cached EventID -> LPI_A. At this stage,
+ * VCPU0 rewrites the event, but doesn't invalidate it. The result is
+ * that the MSI injection reuses the (stale) cache translation. This is
+ * broken guest behaviour, as it should always issue an invalidation
+ * when a translation changes.
+ */
+ ret = run_vcpu(vcpus[0]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 ITS ITT rewrite");
+ vgic_v5_expect_sync(vcpus[0], GUEST_CMD_ITS_ITT_REWRITTEN,
+ "GICv5 ITS guest did not rewrite ITT");
+ vgic_v5_expect_its_lpi(&v, vcpus[1], ITS_TEST_LPI_A);
+
+ /*
+ * Event invalidation must drop the stale EventID -> LPI_A entry. We now
+ * expect LPI B.
+ */
+ ret = run_vcpu(vcpus[0]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 ITS event invalidation");
+ vgic_v5_expect_sync(vcpus[0], GUEST_CMD_ITS_EVENT_INVALIDATED,
+ "GICv5 ITS guest did not invalidate event");
+ vgic_v5_expect_its_lpi(&v, vcpus[1], ITS_TEST_LPI_B);
+
+ /*
+ * Device invalidation must cover all cached EventIDs for the
+ * DeviceID. The guest has again re-written the event to translate to
+ * LPI A.
+ */
+ ret = run_vcpu(vcpus[0]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 ITS device invalidation");
+ vgic_v5_expect_sync(vcpus[0], GUEST_CMD_ITS_DEVICE_INVALIDATED,
+ "GICv5 ITS guest did not invalidate device");
+ vgic_v5_expect_its_lpi(&v, vcpus[1], ITS_TEST_LPI_A);
+
+ /* Disabling the ITS must also prevent delivery from cached entries. */
+ ret = run_vcpu(vcpus[0]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 ITS disable");
+ vgic_v5_expect_sync(vcpus[0], GUEST_CMD_ITS_DISABLED,
+ "GICv5 ITS guest did not disable ITS");
+
+ /*
+ * We should fail to inject this MSI. Use the raw call to ensure we
+ * catch the return value.
+ */
+ ret = __vgic_v5_signal_msi(v.vm, GICV5_ITS_BASE_GPA +
+ GICV5_ITS_TRANSLATER,
+ ITS_TEST_DEVICE_ID, ITS_TEST_EVENT_ID,
+ KVM_MSI_VALID_DEVID);
+ TEST_ASSERT(ret < 0 && errno == ENXIO,
+ "GICv5 ITS accepted cached MSI while disabled");
+
+ vm_gic_destroy(&v);
+}
+
/*
* Returns 0 if it's possible to create GIC device of a given type (V5).
*/
@@ -2266,6 +2676,9 @@ void run_tests(u32 gic_dev_type)
pr_info("Test VGICv5 IST save/restore\n");
test_vgic_v5_ist_save_restore();
+
+ pr_info("Test VGICv5 ITS translation cache\n");
+ test_vgic_v5_its_translation_cache();
}
int main(int ac, char **av)
diff --git a/tools/testing/selftests/kvm/include/arm64/gic_v5.h b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
index 22ab78626af5a..cd3eda8cc77e2 100644
--- a/tools/testing/selftests/kvm/include/arm64/gic_v5.h
+++ b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
@@ -130,10 +130,31 @@
#define GICV5_ITS_INV_DEVICER_I BIT(31)
#define GICV5_ITS_INV_DEVICER_EVENTID_BITS GENMASK(5, 1)
#define GICV5_ITS_INV_DEVICER_L1 BIT(0)
+#define GICV5_ITS_READ_EVENTR_R BIT(31)
+#define GICV5_ITS_READ_EVENT_DATAR_VIRT BIT_ULL(63)
+#define GICV5_ITS_READ_EVENT_DATAR_VM_ID GENMASK_ULL(47, 32)
+#define GICV5_ITS_READ_EVENT_DATAR_VALID BIT_ULL(31)
+#define GICV5_ITS_READ_EVENT_DATAR_LPI_ID GENMASK_ULL(23, 0)
+#define GICV5_ITS_GEN_EVENT_DIDR_DEVICEID GENMASK_ULL(31, 0)
+#define GICV5_ITS_GEN_EVENT_EIDR_EVENTID GENMASK(15, 0)
+#define GICV5_ITS_GEN_EVENTR_R BIT(31)
+#define GICV5_ITS_GEN_EVENTR_TARGET_DOMAIN GENMASK(1, 0)
#define GICV5_ITS_STATUSR_IDLE BIT(0)
#define GICV5_ITS_SYNC_STATUSR_IDLE BIT(0)
#define GICV5_ITS_GEN_EVENT_STATUSR_IDLE BIT(0)
+#define GICV5_DTL2E_VALID BIT_ULL(0)
+#define GICV5_DTL2E_ITT_L2SZ GENMASK_ULL(2, 1)
+#define GICV5_DTL2E_ITT_ADDR_MASK GENMASK_ULL(55, 3)
+#define GICV5_DTL2E_ITT_DSWE BIT_ULL(57)
+#define GICV5_DTL2E_ITT_STRUCTURE BIT_ULL(58)
+#define GICV5_DTL2E_EVENT_ID_BITS GENMASK_ULL(63, 59)
+
+#define GICV5_ITTL2E_LPI_ID GENMASK_ULL(23, 0)
+#define GICV5_ITTL2E_DAC GENMASK_ULL(29, 28)
+#define GICV5_ITTL2E_VIRTUAL BIT_ULL(30)
+#define GICV5_ITTL2E_VALID BIT_ULL(31)
+
#define GICV5_IRS_IDR0_SETLPI BIT(9)
#define GICV5_IRS_IDR0_INT_DOM GENMASK(1, 0)
#define GICV5_IRS_IDR0_INT_DOM_SECURE 0b00
--
2.34.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH 16/16] KVM: arm64: gic-v5: Document ITS userspace ABI
2026-10-16 15:57 [PATCH 00/16] KVM: arm64: Add GICv5 ITS support Sascha Bischoff
` (14 preceding siblings ...)
2026-10-16 15:57 ` [PATCH 15/16] KVM: selftests: Add VGICv5 ITS translation cache tests Sascha Bischoff
@ 2026-10-16 15:57 ` Sascha Bischoff
2026-10-16 16:27 ` sashiko-bot
15 siblings, 1 reply; 28+ messages in thread
From: Sascha Bischoff @ 2026-10-16 15:57 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, kvm
Cc: maz, oliver.upton, joey.gouly, suzuki.poulose, yuzenghui,
peter.maydell, lpieralisi, timothy.hayes, fuad.tabba
Document the GICv5 ITS device and its userspace ABI, including the
MMIO address, reset control, register attributes, and MSI requirements.
State that software should target ITS_TRANSLATER, while KVM uses the
MSI address to identify the virtual ITS.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
.../virt/kvm/devices/arm-vgic-v5-its.rst | 102 ++++++++++++++++++
Documentation/virt/kvm/devices/index.rst | 1 +
2 files changed, 103 insertions(+)
create mode 100644 Documentation/virt/kvm/devices/arm-vgic-v5-its.rst
diff --git a/Documentation/virt/kvm/devices/arm-vgic-v5-its.rst b/Documentation/virt/kvm/devices/arm-vgic-v5-its.rst
new file mode 100644
index 0000000000000..250057e4445d7
--- /dev/null
+++ b/Documentation/virt/kvm/devices/arm-vgic-v5-its.rst
@@ -0,0 +1,102 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+============================================================
+ARM Virtual Generic Interrupt Controller v5 ITS (VGICv5 ITS)
+============================================================
+
+Device types supported:
+ KVM_DEV_TYPE_ARM_VGIC_V5_ITS ARM GICv5 Interrupt Translation Service
+
+The GICv5 ITS translates MSI(-X) interrupts into LPIs. It is optional and
+requires a GICv5 host and a KVM_DEV_TYPE_ARM_VGIC_V5 device for the VM. The
+ITS and VGIC devices may be created in either order, but their models must
+match. Userspace should assign each ITS a separate, non-overlapping MMIO
+region.
+
+MSIs injected through KVM_SIGNAL_MSI must provide a DeviceID using
+KVM_MSI_VALID_DEVID. The MSI address should target ITS_TRANSLATER in the
+ITS_TRANSLATE_FRAME. KVM uses the address to identify the virtual ITS, and
+the MSI data supplies the EventID. The virtual ITS supports 16-bit EventIDs;
+the upper 16 bits of the MSI data must be zero.
+
+
+Groups
+======
+
+KVM_DEV_ARM_VGIC_GRP_ADDR
+-------------------------
+
+ Attributes:
+ KVM_VGIC_V5_ADDR_TYPE_ITS (rw, 64-bit)
+ Base address of the ITS in the guest physical address space. The address
+ must be 64K aligned. The region covers 128K: a 64K control frame and a
+ 64K translation frame. The address must be set before accessing ITS
+ registers through KVM_DEV_ARM_VGIC_GRP_ITS_REGS. It can only be set once.
+
+ Errors:
+
+ ======= =========================================================
+ -E2BIG Address outside the guest's addressable IPA range
+ -EINVAL Address is not 64K aligned, or the 128K range wraps
+ -EEXIST Address has already been configured
+ -EFAULT Invalid user pointer for kvm_device_attr.addr
+ -ENODEV Unsupported address attribute for GET or SET
+ -ENXIO Unsupported address attribute for HAS
+ -EBUSY Concurrent address configuration has already set the base
+ -ENOMEM Unable to allocate space for the MMIO bus entry
+ -ENOSPC MMIO bus device limit reached
+ ======= =========================================================
+
+
+KVM_DEV_ARM_VGIC_GRP_CTRL
+-------------------------
+
+ Attributes:
+ KVM_DEV_ARM_VGIC_CTRL_INIT
+ Accepted for consistency with other VGIC devices; no additional ITS
+ initialization is required.
+
+ KVM_DEV_ARM_ITS_CTRL_RESET
+ Reset the emulated ITS registers and discard cached translations. The
+ configured MMIO base address is retained. Guest memory containing the
+ device and interrupt translation tables is not modified.
+
+ Errors:
+
+ ======= =========================================================
+ -EBUSY A vCPU could not be locked for reset
+ -ENXIO Unsupported control attribute for SET, or any GET
+ ======= =========================================================
+
+
+KVM_DEV_ARM_VGIC_GRP_ITS_REGS
+-----------------------------
+
+ Attributes:
+ The attr field of kvm_device_attr contains the ITS register offset from
+ the control frame base address. kvm_device_attr.addr points to a __u64,
+ regardless of whether the register is 32 or 64 bits wide. A 64-bit
+ register must be addressed at its base offset; userspace cannot access
+ its upper or lower half separately. Guest MMIO may use aligned 32-bit
+ accesses to DT_BASER, DIDR, READ_EVENT_DATAR, and GEN_EVENT_DIDR.
+
+ The ITS base address must be configured before this group can be used.
+ Writes to read-only registers are ignored. Writes to DT_CFGR and DT_BASER
+ through this interface update their values even when the ITS is enabled;
+ guest MMIO writes to those registers are ignored while it is enabled.
+
+ The registers with readable, writable state are CR0, CR1, DT_CFGR,
+ DT_BASER, DIDR, EIDR, GEN_EVENT_DIDR, and GEN_EVENT_EIDR.
+
+ Some registers initiate guest-visible operations when written, including
+ INV_EVENTR, INV_DEVICER, READ_EVENTR, and GEN_EVENTR. READ_EVENT_DATAR is
+ a read-only result register.
+
+ Errors:
+
+ ======= =========================================================
+ -ENXIO ITS base address is unset, or offset is unsupported
+ -EINVAL Offset is not aligned to the register width
+ -EFAULT Invalid user pointer for kvm_device_attr.addr
+ -EBUSY A vCPU could not be locked
+ ======= =========================================================
diff --git a/Documentation/virt/kvm/devices/index.rst b/Documentation/virt/kvm/devices/index.rst
index 70845aba38f45..d6d64b7266e83 100644
--- a/Documentation/virt/kvm/devices/index.rst
+++ b/Documentation/virt/kvm/devices/index.rst
@@ -11,6 +11,7 @@ Devices
arm-vgic
arm-vgic-v3
arm-vgic-v5
+ arm-vgic-v5-its
mpic
s390_flic
vcpu
--
2.34.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* Re: [PATCH 08/16] KVM: arm64: gic-v5: Add ITS translation cache maintenance
2026-10-16 15:57 ` [PATCH 08/16] KVM: arm64: gic-v5: Add ITS translation cache maintenance Sascha Bischoff
@ 2026-10-16 16:10 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-10-16 16:10 UTC (permalink / raw)
To: Sascha Bischoff, kvmarm, kvm; +Cc: Oliver Upton, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] kvm: missing memcg accounting for ITS translation cache allocations
- [High] kvm: undersized ITS translation hash table causes raw spinlock DoS
- [Medium] kvm: arm64: gic-v5: cache insertion failure drops valid LPI
--
commit b6b2c7ac3d0d6dc377bf1fd1bb7de1527b73194f
Author: Sascha Bischoff <sascha.bischoff@arm.com>
KVM: arm64: gic-v5: Add ITS translation cache maintenance
This commit introduces a disposable acceleration structure to cache
resolved ITS translations, avoiding iterating over guest memory on every
MSI or ITS event.
> diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c
> index f6d2acca4ae4c..e4c0415137605 100644
> --- a/arch/arm64/kvm/vgic/vgic-its-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-its-v5.c
[ ... ]
> @@ -19,6 +19,272 @@
> #define VITS_V5_DEVICE_ID_BITS 16
> #define VITS_V5_EVENT_ID_BITS 16
[ ... ]
> +static int its_cache_add_translation(struct kvm *kvm, struct vgic_v5_its *its,
> + u32 device_id, u16 event_id,
> + struct vgic_irq *irq)
> +{
> + u64 key = ((u64)device_id << 16) | (u64)event_id;
> + struct its_cache_translation_entry *entry;
> + struct its_cache_translation_entry *old;
> + struct hlist_node *tmp;
> + HLIST_HEAD(removed);
> +
> + entry = kzalloc_obj(*entry, GFP_KERNEL);
> + if (!entry)
> + return -ENOMEM;
[Severity: High]
Does this allocation need memcg accounting?
The GFP_KERNEL flag is missing __GFP_ACCOUNT, which means these guest-driven
allocations are not charged to the VM memory cgroup. Since the cache has no
upper size bound, could a guest inject arbitrary device_id and event_id
combinations via GICV5_ITS_GEN_EVENTR to exhaust host kernel memory and
cause an out-of-memory denial of service?
[ ... ]
> +static int its_cache_look_up_translation(struct vgic_v5_its *its,
> + u32 device_id, u16 event_id,
> + struct vgic_irq **irq)
> +{
> + u64 key = ((u64)device_id << 16) | (u64)event_id;
> + struct its_cache_translation_entry *entry;
> +
> + guard(raw_spinlock)(&its->translation_cache_lock);
> +
> + hash_for_each_possible(its->translation_cache, entry, node, key) {
[Severity: High]
Can this traversal cause a host lockup under a raw spinlock?
Since the translation cache is unbounded, a guest could trigger the insertion
of millions of LPI translations. These entries are distributed across a small
fixed-size hash table, which will result in massive hash collisions and
extremely long bucket chains.
Traversing tens or hundreds of thousands of entries under a raw spinlock
disables preemption and interrupts for an extended period, which could
lead to soft or hard CPU lockups.
[ ... ]
> @@ -260,7 +530,15 @@ static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its,
> if (IS_ERR(*irq))
> return PTR_ERR(*irq);
>
> - return 0;
> + vgic_get_irq_ref(*irq);
> + ret = its_cache_add_translation(kvm, its, device_id, event_id, *irq);
> + if (ret) {
> + vgic_put_irq(kvm, *irq);
> + vgic_put_irq(kvm, *irq);
> + *irq = NULL;
> + }
> +
> + return ret;
[Severity: Medium]
Should a cache insertion failure cause the valid LPI to be dropped?
If its_cache_add_translation() fails due to memory pressure, this error
handling path drops both the cache reference and the caller's successfully
resolved reference, propagating the error and discarding the interrupt.
Since the translation cache is a best-effort acceleration structure, is it
better to swallow the caching error and allow the caller to proceed with the
validly resolved LPI rather than completely failing the resolution?
> diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
> index c9eaa7897b57e..bf970275868c9 100644
> --- a/include/kvm/arm_vgic.h
> +++ b/include/kvm/arm_vgic.h
[ ... ]
> @@ -590,6 +591,12 @@ struct vgic_v5_its {
> u32 device_id;
> } gen_event_didr;
>
> + /* A hash table with 2^3 buckets */
> + DECLARE_HASHTABLE(translation_cache, 3);
> +
> + /* Protects the translation_cache */
> + raw_spinlock_t translation_cache_lock;
[Severity: High]
Is a 3-bit hash table (8 buckets) sufficient for an unbounded number of
guest-driven translations?
As noted earlier, storing a potentially unbounded number of LPI translations
in 8 buckets causes significant hash collisions. When paired with the
translation_cache_lock raw spinlock, this creates the denial of service
risk mentioned above.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261016155741.979985-1-sascha.bischoff@arm.com?part=8
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH 06/16] KVM: arm64: gic-v5: Add ITS MMIO state emulation
2026-10-16 15:57 ` [PATCH 06/16] KVM: arm64: gic-v5: Add ITS MMIO state emulation Sascha Bischoff
@ 2026-10-16 16:11 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-10-16 16:11 UTC (permalink / raw)
To: Sascha Bischoff, kvmarm, kvm; +Cc: Oliver Upton, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] kvm: arm64: Data race and TOCTOU due to missing its_lock in MMIO handlers
- [High] kvm: arm64: gic-v5: Userspace writes to ITS READ_EVENT_DATAR and ID registers are silently dropped
--
commit cda638a2b9c74f59afeb5648c53ce1083d96d5c3
Author: Sascha Bischoff <sascha.bischoff@arm.com>
KVM: arm64: gic-v5: Add ITS MMIO state emulation
In order to expose a virtual GICv5 ITS to the guest, KVM needs to
emulate the ITS CONFIG_FRAME and its translation-control registers.
Replace the placeholder ITS IODEV region with the GICv5 ITS register
layout and handlers for the architected MMIO state.
> diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c
> index f8184b4ae0ccd..fda75314229a5 100644
> --- a/arch/arm64/kvm/vgic/vgic-its-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-its-v5.c
[ ... ]
> +static void vgic_v5_mmio_write_its_misc(struct kvm *kvm, void *dev, gpa_t addr,
> + unsigned int len, unsigned long val)
> +{
> + struct vgic_v5_its *its = dev;
> + size_t offset = addr & (SZ_64K - 1);
> +
> + switch (offset) {
> + case GICV5_ITS_CR0:
> + its->enabled = FIELD_GET(GICV5_ITS_CR0_ITSEN, val);
> + return;
> + case GICV5_ITS_CR1:
> + its->cr1.sh = FIELD_GET(GICV5_ITS_CR1_SH, val);
> + its->cr1.oc = FIELD_GET(GICV5_ITS_CR1_OC, val);
> + its->cr1.ic = FIELD_GET(GICV5_ITS_CR1_IC, val);
> + its->cr1.dt_ra = FIELD_GET(GICV5_ITS_CR1_DT_RA, val);
> + its->cr1.itt_ra = FIELD_GET(GICV5_ITS_CR1_ITT_RA, val);
> + return;
> + default:
> + return;
> + }
> +}
[Severity: High]
Does this code introduce a data race or TOCTOU vulnerability?
The vgic_v5_mmio_write_its_misc() function modifies the its->enabled flag
and its->cr1 fields without acquiring its->its_lock. Concurrently,
vgic_v5_mmio_write_its_dt_itt() uses guard(mutex)(&its->its_lock) to safely
evaluate !its->enabled and modify table bases.
Allowing lockless modification of its->enabled could bypass the check
if another vCPU is simultaneously writing to translation base registers.
Additionally, this permits torn reads of the multi-field cr1 struct. Should
this function acquire its->its_lock?
[ ... ]
> static const struct vgic_register_region vgic_v5_its_registers[] = {
> - REGISTER_ITS_V5_DESC(0, vgic_v5_mmio_read_its_raz,
> - vgic_v5_mmio_write_its_wi, KVM_VGIC_V5_ITS_SIZE,
> + REGISTER_ITS_V5_DESC(GICV5_ITS_IDR0, vgic_v5_mmio_read_its_misc,
> + vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit),
> + REGISTER_ITS_V5_DESC(GICV5_ITS_IDR1, vgic_v5_mmio_read_its_misc,
> + vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit),
> + REGISTER_ITS_V5_DESC(GICV5_ITS_IDR2, vgic_v5_mmio_read_its_misc,
> + vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit),
[ ... ]
> + REGISTER_ITS_V5_DESC(GICV5_ITS_READ_EVENT_DATAR,
> + vgic_v5_mmio_read_its_dt_itt,
> + vgic_v5_mmio_write_its_wi, 8,
> + VGIC_ACCESS_64bit | VGIC_ACCESS_32bit),
[Severity: High]
Are userspace writes to restore migration state being silently dropped?
Both the ID registers (e.g. IDR0) and READ_EVENT_DATAR are configured
with the vgic_v5_mmio_write_its_wi write-ignore handler. Since there is no
explicit userspace override provided for these registers, a userspace write
will fall back to the guest MMIO write handler, accepting the write and
returning success (0) but discarding the value.
For ID registers, ignoring the write prevents the VMM from detecting
incompatibility across migration. For READ_EVENT_DATAR, the result of an
inflight READ_EVENTR command is lost. Does this violate the ABI contract
that userspace writes must either take effect or return an error?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261016155741.979985-1-sascha.bischoff@arm.com?part=6
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH 01/16] KVM: arm64: vgic: Split out common LPI allocation
2026-10-16 15:57 ` [PATCH 01/16] KVM: arm64: vgic: Split out common LPI allocation Sascha Bischoff
@ 2026-10-16 16:14 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-10-16 16:14 UTC (permalink / raw)
To: Sascha Bischoff, kvmarm, kvm; +Cc: Oliver Upton, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] kvm: data race on irq->group due to unlocked assignment after publication
--
commit 0cabb3a5e012e80ff468a8654506c83304b15486
Author: Sascha Bischoff <sascha.bischoff@arm.com>
KVM: arm64: vgic: Split out common LPI allocation
The GICv3 ITS code currently owns vgic_add_lpi(), even though the
first half of the helper is not ITS specific. It allocates or finds
the VM-wide vgic_irq object in lpi_xa and initializes the common LPI
state before the GICv3 ITS code reads the property table and pending
state.
> diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c
> index e5657cb603567..8e7bee1bae95d 100644
> --- a/arch/arm64/kvm/vgic/vgic-its.c
> +++ b/arch/arm64/kvm/vgic/vgic-its.c
> @@ -66,77 +66,22 @@ static int update_lpi_config(struct kvm *kvm, struct vgic_irq *irq,
[ ... ]
> - irq = kzalloc_obj(struct vgic_irq, GFP_KERNEL_ACCOUNT);
> - if (!irq)
> - return ERR_PTR(-ENOMEM);
[ ... ]
> + irq = vgic_add_lpi(kvm, intid, vcpu);
> + if (IS_ERR(irq))
> + return irq;
>
> - irq->config = VGIC_CONFIG_EDGE;
> - refcount_set(&irq->refcount, 1);
> - irq->intid = intid;
> - irq->target_vcpu = vcpu;
> irq->group = 1;
[Severity: High]
Does this code introduce a data race on irq->group in vgic_its_add_lpi()?
Since vgic_add_lpi() now publishes the object to the globally visible
lpi_xa xarray and drops the locks, a concurrent thread (e.g., another vCPU
writing to GICR_INVLPIR or mapping the same LPI on another ITS) can
retrieve the LPI and access irq->group under irq_lock before we set it here.
This can misroute or drop the LPI if it is evaluated by vgic_irq_cmp()
as Group 0 instead of Group 1. Should we set the group before publishing,
or hold irq->irq_lock when assigning it?
> diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
> index c2726d4c06e6e..bc0171c479a95 100644
> --- a/arch/arm64/kvm/vgic/vgic.c
> +++ b/arch/arm64/kvm/vgic/vgic.c
> @@ -79,6 +79,78 @@ static struct vgic_irq *vgic_get_lpi(struct kvm *kvm, u32 intid)
[ ... ]
> +/*
> + * Creates a new (reference to a) struct vgic_irq for a given LPI.
> + * If this LPI already exists, we increase its refcount and return it.
> + * If this is a "new" LPI, we allocate and initialize a new struct vgic_irq.
> + * This function returns a pointer to the _unlocked_ structure.
> + */
> +struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid,
> + struct kvm_vcpu *vcpu)
> +{
[ ... ]
> + /*
> + * The entry is either empty or contains a dead LPI (refcount=0)
> + * from the deferred release path, pending cleanup by
> + * vgic_release_deleted_lpis(). Evict and free it if present.
> + */
> + oldirq = __xa_store(&dist->lpi_xa, intid, irq,
> + GFP_NOWAIT | __GFP_ACCOUNT);
[Severity: High]
Is it safe to publish the new LPI here while its group field is still
uninitialized (0)?
As mentioned above, doing this in vgic_add_lpi() makes the LPI accessible to
concurrent lookups in vgic_get_irq() before vgic_its_add_lpi() has finished
setting up the group field.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261016155741.979985-1-sascha.bischoff@arm.com?part=1
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH 07/16] KVM: arm64: gic-v5: Add ITS guest table translation emulation
2026-10-16 15:57 ` [PATCH 07/16] KVM: arm64: gic-v5: Add ITS guest table translation emulation Sascha Bischoff
@ 2026-10-16 16:17 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-10-16 16:17 UTC (permalink / raw)
To: Sascha Bischoff, kvmarm, kvm; +Cc: Oliver Upton, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] kvm: typed INTID used directly as XArray index in vgic_add_lpi()
- [Medium] kvm: arm64: gic-v5: Guest can bypass VITS_V5_DEVICE_ID_BITS policy cap
- [Low] kvm: compilation failure due to mismatched types in min() macro
--
commit c4304c740198b40a23d386e3b547cd38e75a6238
Author: Sascha Bischoff <sascha.bischoff@arm.com>
KVM: arm64: gic-v5: Add ITS guest table translation emulation
The GICv5 ITS consumes guest-owned translation tables. Unlike the
GICv3 ITS, KVM does not maintain a separate software device/table
model for GICv5 translations; the guest's DT and ITTs are the
authoritative source of DeviceID/EventID to LPI mappings.
> diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c
> --- a/arch/arm64/kvm/vgic/vgic-its-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-its-v5.c
[ ... ]
> +static int get_itt(struct kvm *kvm, struct vgic_v5_its *its, u32 device_id,
> + gpa_t *itt_base, bool *two_level_itt,
> + unsigned int *event_id_bits, unsigned int *itt_l2sz)
> +{
> + unsigned int dt_l2sz, device_id_bits;
> + bool two_level_dt;
> + gpa_t dt_base;
> +
> + dt_base = its->dt_baser.addr;
> + two_level_dt = its->dt_cfgr.structure;
> + dt_l2sz = its->dt_cfgr.l2sz;
> + device_id_bits = its->dt_cfgr.device_id_bits;
> +
> + if (device_id >= 1UL << device_id_bits) {
[Severity: Medium]
Does this check allow the guest to bypass the VITS_V5_DEVICE_ID_BITS policy
cap?
KVM advertises a maximum of 16 device ID bits (VITS_V5_DEVICE_ID_BITS) and a
single-level DT in IDR1. However, when the guest writes to GICV5_ITS_DT_CFGR,
the fields are stored directly in its->dt_cfgr without being clamped to
these policy limits.
Since this out-of-bounds check compares against the un-clamped guest value
instead of the VITS_V5_DEVICE_ID_BITS cap, the guest could configure a
2-level DT and process Device IDs up to 31 bits, bypassing the advertised
policy cap.
> + kvm_debug("Device ID out of range\n");
> + return -E2BIG;
> + }
[ ... ]
> +static int parse_two_level_itt(struct kvm *kvm, u32 event_id, gpa_t itt_base,
> + unsigned int itt_l2sz, __le64 *itt_l2_entry)
> +{
[ ... ]
> + itt_l2_base = FIELD_GET(GICV5_ITTL1E_L2_ADDR_MASK, val) << 3;
> + l2_event_id_bits =
> + min(FIELD_GET(GICV5_ITTL1E_SPAN, val), itt_split_bits);
[Severity: Low]
Will this min() macro invocation cause a compilation failure due to mismatched
types?
The arguments to min() are FIELD_GET(GICV5_ITTL1E_SPAN, val) and
itt_split_bits. val is a 64-bit u64, making the FIELD_GET evaluation an
unsigned long long, while itt_split_bits is a 32-bit unsigned int.
The Linux kernel's min() macro enforces strict type equality and will cause
a build failure when comparing a 64-bit and 32-bit type. Could we use min_t()
here instead to resolve the mismatch?
> + l2_event_id = event_id % (1UL << l2_event_id_bits);
[ ... ]
> +static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its,
> + u32 device_id, u16 event_id,
> + struct vgic_irq **irq)
> +{
[ ... ]
> + val = le64_to_cpu(itt_l2_entry);
> + if (!FIELD_GET(GICV5_ITTL2E_VALID, val)) {
> + kvm_debug("ITTE is not valid\n");
> + return -EINVAL;
> + }
> +
> + lpi = FIELD_GET(GICV5_ITTL2E_LPI_ID, val);
> +
> + *irq = vgic_add_lpi(kvm, vgic_v5_make_lpi(lpi), NULL);
[Severity: High]
Does this pass a fully typed INTID directly to vgic_add_lpi()?
Here, the hardware LPI ID is extracted and converted to a full typed INTID
using vgic_v5_make_lpi(), which sets the GICv5 type bits in the upper nibble.
This typed INTID is passed to vgic_add_lpi(), which then uses it directly
as the array index for the lpi_xa XArray:
arch/arm64/kvm/vgic/vgic.c:vgic_add_lpi() {
...
ret = xa_reserve_irq(&dist->lpi_xa, intid, GFP_KERNEL_ACCOUNT);
...
}
Using a typed INTID as an array index violates the GICv5 architectural rule
to strip type bits before indexing. It forces the XArray to allocate
unnecessary depth to reach indices over 1 billion, leading to memory waste
and incorrect type dispatch separation.
> + if (IS_ERR(*irq))
> + return PTR_ERR(*irq);
> +
> + return 0;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261016155741.979985-1-sascha.bischoff@arm.com?part=7
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH 14/16] KVM: selftests: Add VGICv5 ITS attribute tests
2026-10-16 15:57 ` [PATCH 14/16] KVM: selftests: Add VGICv5 ITS attribute tests Sascha Bischoff
@ 2026-10-16 16:18 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-10-16 16:18 UTC (permalink / raw)
To: Sascha Bischoff, kvmarm, kvm; +Cc: Oliver Upton, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] kvm: selftests: tautological test for ITS DT_CFGR/DT_BASER updates
- [Low] kvm: selftests: missing idle status register checks for ITS
--
commit c7601fac392a61f968d6064e81cd764b666cb504
Author: Sascha Bischoff <sascha.bischoff@arm.com>
KVM: selftests: Add VGICv5 ITS attribute tests
This commit adds coverage for the VGICv5 ITS device address and ITS_REGS
attribute groups. Userspace needs to be able to discover and save the virtual
ITS register state, while KVM must reject invalid address placement and
malformed register accesses.
> diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
> index d5df742b23001..b7fb002a64f6c 100644
> --- a/tools/testing/selftests/kvm/arm64/vgic_v5.c
> +++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
[ ... ]
> @@ -1287,6 +1339,177 @@ static void test_vgic_v5_irs_regs_attrs(void)
> vm_gic_destroy(&v);
> }
>
> +static void test_vgic_v5_its_attrs(void)
> +{
[ ... ]
> + /* Check MMIO by checking if present, reading, then writing back */
> + for (i = 0; i < ARRAY_SIZE(gic_v5_its_regs); i++) {
> + attr = gic_v5_its_regs[i].attr;
> + ret = __kvm_has_device_attr(v.its_fd,
> + KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
> + attr);
[Severity: Low]
The commit message mentions exercising "idle status register values", but this
loop only reads and writes back the registers without checking their values.
Should there be an explicit assertion that the status registers (like STATUSR,
SYNC_STATUSR, GEN_EVENT_STATUSR) actually read as idle? The corresponding
test_vgic_v5_irs_regs_attrs() test includes these checks for the IRS.
[ ... ]
> + /* Make sure we can restore the vITS's enable state */
> + val = GICV5_ITS_CR0_ITSEN;
> + ret = __kvm_device_attr_set(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
> + GICV5_ITS_CR0, &val);
> + TEST_ASSERT(!ret, "GICv5 ITS_REGS failed to enable ITS");
> +
> + /* And DT CFGR/BASER */
> + val = 0;
> + ret = __kvm_device_attr_set(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
> + GICV5_ITS_DT_CFGR, &val);
> + TEST_ASSERT(!ret, "GICv5 ITS_REGS rejected enabled DT_CFGR update");
> + ret = __kvm_device_attr_get(v.its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
> + GICV5_ITS_DT_CFGR, &val);
> + TEST_ASSERT(!ret && !val, "GICv5 ITS_REGS DT_CFGR restore mismatch");
[Severity: Low]
Is this test effectively verifying that the updates to GICV5_ITS_DT_CFGR work
while the ITS is enabled?
Because val is set to 0 (the default/reset value of the register) before the
write, if the kernel incorrectly discards the write while ITSEN=1, the
register will remain 0 and the read-back will return 0, allowing the test to
pass spuriously. Writing a non-zero valid value would avoid this tautology.
Also, the test writes GICV5_ITS_CR0_ITSEN to CR0 but never reads it back.
Could the test be trying to update the configuration registers on a disabled
ITS if the enable bit didn't actually stick?
> diff --git a/tools/testing/selftests/kvm/include/arm64/gic_v5.h b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
> index 2d455515563e0..22ab78626af5a 100644
> --- a/tools/testing/selftests/kvm/include/arm64/gic_v5.h
> +++ b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
[ ... ]
> +#define GICV5_ITS_CR0_IDLE BIT(1)
[Severity: Low]
Is this macro correct for the ITS? It defines an IDLE bit for CR0, but it
appears to be unused in the test. Looking at the GICv5 architecture, it seems
only the IRS has an IDLE bit in CR0, not the ITS.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261016155741.979985-1-sascha.bischoff@arm.com?part=14
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH 10/16] KVM: arm64: gic-v5: Add ITS userspace register access
2026-10-16 15:57 ` [PATCH 10/16] KVM: arm64: gic-v5: Add ITS userspace register access Sascha Bischoff
@ 2026-10-16 16:19 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-10-16 16:19 UTC (permalink / raw)
To: Sascha Bischoff, kvmarm, kvm; +Cc: Oliver Upton, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] gic-v5: userspace override omits guest-side guard without post-restore validation
- [High] gic-v5: userspace writes to stateful and capability registers are silently discarded
- [Medium] gic-v5: missing state rollback on kvm_io_bus_register_dev failure
- [Medium] kvm: arm64: KVM_HAS_DEVICE_ATTR fails on ITS registers before base address is set
- [Medium] kvm: arm64: gic-v5: userspace writes to ITS ID registers are silently discarded
--
commit 2fb8c28d93170f4ea49aeb17c5091f5d920592c9
Author: Sascha Bischoff <sascha.bischoff@arm.com>
KVM: arm64: gic-v5: Add ITS userspace register access
This commit exposes the GICv5 ITS register frame to userspace for inspection
and restoration around VM reset and migration. It applies userspace access
constraints and adds control handling for reset while preserving ITS
defaults.
> diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c
> index 5920fa1bcb2cf..62c1ca8004007 100644
> --- a/arch/arm64/kvm/vgic/vgic-its-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-its-v5.c
> @@ -851,6 +851,19 @@ static u64 vgic_v5_read_its_dt_baser(struct vgic_v5_its *its)
> its->dt_baser.addr >> GICV5_ITS_DT_BASER_ADDR_SHIFT);
> }
>
> +static int vgic_v5_uaccess_write_its_dt_itt(struct kvm *kvm, void *dev,
> + gpa_t addr, unsigned int len,
> + unsigned long val)
> +{
> + struct vgic_v5_its *its = dev;
> + size_t offset = addr & (SZ_64K - 1);
> +
> + guard(mutex)(&its->its_lock);
> +
> + vgic_v5_write_its_dt_reg(its, offset, val);
> + return 0;
> +}
[Severity: High]
This userspace override in arch/arm64/kvm/vgic/vgic-its-v5.c intentionally
omits the guest-side !its->enabled guard, which makes sense for permitting
out-of-order restoration. However, when a guest-side refusal is omitted, a
final validation step is required at the end of the restore sequence to
re-establish the invariant.
Because KVM_DEV_ARM_VGIC_CTRL_INIT is handled as a no-op below and there
are no RESTORE_TABLES controls exposed for GICv5, KVM never validates the
final configuration. Could this allow userspace to leave the device in an
illegal state (e.g., base address modified while active) that the guest
could never produce?
[ ... ]
> @@ -1018,6 +1031,13 @@ static void vgic_v5_mmio_write_its_wi(struct kvm *kvm, void *dev, gpa_t addr,
> .its_read = rd, .its_write = wr, \
> }
>
> +#define REGISTER_ITS_V5_DESC_UACCESS(off, rd, wr, uwr, length, acc) \
> + { \
> + .reg_offset = off, .len = length, .access_flags = acc, \
> + .its_read = rd, .its_write = wr, \
> + .uaccess_its_write = uwr, \
> + }
> +
> static const struct vgic_register_region vgic_v5_its_registers[] = {
> REGISTER_ITS_V5_DESC(GICV5_ITS_IDR0, vgic_v5_mmio_read_its_misc,
> vgic_v5_mmio_write_its_wi, 4, VGIC_ACCESS_32bit),
[Severity: Medium]
IDR0 is registered without a userspace write override, falling back to the
guest's write-ignore handler vgic_v5_mmio_write_its_wi(). Because userspace
writes are silently discarded here in arch/arm64/kvm/vgic/vgic-its-v5.c, a
VM migrated to a new host will adopt the destination host's physical ITS
capabilities (like pa_range in vgic_v5_its_reset()) rather than retaining
its original configuration.
Will this break migration for heterogeneous clusters since capability
mismatches are swallowed instead of rejected or applied?
[ ... ]
> REGISTER_ITS_V5_DESC(GICV5_ITS_READ_EVENT_DATAR,
> vgic_v5_mmio_read_its_dt_itt,
> vgic_v5_mmio_write_its_wi, 8,
> VGIC_ACCESS_64bit | VGIC_ACCESS_32bit),
[Severity: High]
Stateful registers like READ_EVENT_DATAR also lack a uaccess_its_write
override in vgic_v5_its_registers and use the guest write-ignore handler
vgic_v5_mmio_write_its_wi().
Since the write-ignore handler silently accepts the write and discards the
value, wouldn't pending event data be permanently lost during migration?
[ ... ]
> +static int vgic_v5_its_has_attr_regs(struct kvm_device *dev,
> + struct kvm_device_attr *attr)
> +{
> + struct vgic_v5_its *its = dev->private;
> + const struct vgic_register_region *region;
> + gpa_t offset = attr->attr;
> + int align;
> +
> + if (IS_VGIC_ADDR_UNDEF(its->vgic_v5_its_base))
> + return -ENXIO;
[Severity: Medium]
This runtime state check in vgic_v5_its_has_attr_regs() enforces a base
address requirement during a purely capability-probing ioctl.
Userspace VMMs probing for supported GICv5 ITS registers via
KVM_HAS_DEVICE_ATTR will receive -ENXIO if they probe before configuring
the ITS base address. Could this cause the VMM to incorrectly assume the
registers or device capabilities are unsupported, breaking device
initialization or migration setup? Other VGIC implementations explicitly
omit the base address check in their has_attr handlers to permit capability
probing without prior configuration.
[ ... ]
> +static int vgic_v5_its_attr_regs_access(struct kvm_device *dev,
> + struct kvm_device_attr *attr,
> + u64 *reg, bool is_write)
> +{
...
> + if (is_write) {
> + if (region->uaccess_its_write)
> + ret = region->uaccess_its_write(dev->kvm, its, addr,
> + len, *reg);
> + else
> + region->its_write(dev->kvm, its, addr, len, *reg);
> + } else {
> + *reg = region->its_read(dev->kvm, its, addr, len);
> + }
[Severity: High]
As mentioned above, userspace writes to several registers (IDR0, CR1,
SYNCR, READ_EVENT_DATAR) fall back to the guest-side its_write handler in
vgic_v5_its_attr_regs_access() when no override is provided. Because these
guest handlers silently accept the write and discard the value, mutable
guest state is lost across migration and invalid capability settings are
silently ignored instead of being rejected.
[ ... ]
> +static int vgic_v5_its_ctrl(struct kvm *kvm, struct vgic_v5_its *its, u64 attr)
> +{
> + int ret = 0;
> +
> + if (attr == KVM_DEV_ARM_VGIC_CTRL_INIT)
> + return 0;
[Severity: High]
The initialization/finalization control in vgic_v5_its_ctrl() is a no-op,
meaning no end-of-restore validation is performed to check the invariants
that were bypassed by the userspace overrides (like the !its->enabled
check).
[ ... ]
> +static int vgic_v5_its_set_attr(struct kvm_device *dev,
> + struct kvm_device_attr *attr)
> +{
...
> + ret = vgic_check_iorange(dev->kvm, its->vgic_v5_its_base,
> + addr, SZ_64K, KVM_VGIC_V5_ITS_SIZE);
> + if (ret)
> + return ret;
> +
> + return vgic_v5_register_its_iodev(dev->kvm, its, addr);
[Severity: Medium]
In vgic_v5_register_its_iodev() in arch/arm64/kvm/vgic/vgic-its-v5.c,
its->vgic_v5_its_base is populated with the requested addr before
attempting to register the IO bus device. If kvm_io_bus_register_dev()
fails (e.g. due to ENOMEM), the error path fails to clear
its->vgic_v5_its_base upon failure:
its->vgic_v5_its_base = addr;
...
ret = kvm_io_bus_register_dev(kvm, KVM_MMIO_BUS, iodev->base_addr,
KVM_VGIC_V5_ITS_SIZE, &iodev->dev);
out:
mutex_unlock(&kvm->slots_lock);
return ret;
Will this leave the ITS permanently unusable? Subsequent attempts by
userspace to correctly set the base address will fail with -EBUSY because
IS_VGIC_ADDR_UNDEF() evaluates to false, leaving the ITS permanently
unmapped.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261016155741.979985-1-sascha.bischoff@arm.com?part=10
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH 11/16] irqchip/gic-v5: Enable direct injection in ITS
2026-10-16 15:57 ` [PATCH 11/16] irqchip/gic-v5: Enable direct injection in ITS Sascha Bischoff
@ 2026-10-16 16:22 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-10-16 16:22 UTC (permalink / raw)
To: Sascha Bischoff, kvmarm, kvm; +Cc: Oliver Upton, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] irqchip/gic-v5: Racy ITS MMIO invalidation sequence
- [High] irqchip/gic-v5: missing event drain in set_vcpu_affinity causes cross-VM interrupt
--
commit 4f462ff132c79e74a3a82cf356a72402e7a44799
Author: Sascha Bischoff <sascha.bischoff@arm.com>
irqchip/gic-v5: Enable direct injection in ITS
This commit introduces an irq_chip callback to allow KVM to install and remove
virtual translations for direct interrupt injection. It replaces a host ITTE
with a virtual translation and disables lazy masking while the interrupt is
forwarded.
> diff --git a/drivers/irqchip/irq-gic-v5-its.c b/drivers/irqchip/irq-gic-v5-its.c
> index 28e39b065de0e..7de40a7cff403 100644
> --- a/drivers/irqchip/irq-gic-v5-its.c
> +++ b/drivers/irqchip/irq-gic-v5-its.c
[ ... ]
> @@ -723,6 +765,95 @@ static void gicv5_its_compose_msi_msg(struct irq_data *d, struct msi_msg *msg)
> msi_msg_set_addr(irq_data_get_msi_desc(d), msg, addr);
> }
>
> +#ifdef CONFIG_KVM
> +static int gicv5_its_set_vcpu_affinity(struct irq_data *d, void *int_data)
> +{
[ ... ]
> +invalidate:
> + /* Make the change ITS-visible */
> + dsb(st);
> + ret = gicv5_its_itt_cache_inv(its, dev_id, event_id);
[Severity: High]
Does this new KVM callback expose the gicv5_its_itt_cache_inv() sequence
to concurrent execution?
This callback appears to only be serialized by the per-IRQ lock, but
gicv5_its_itt_cache_inv() writes to the global shared ITS MMIO registers:
drivers/irqchip/irq-gic-v5-its.c:gicv5_its_itt_cache_inv() {
...
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);
}
Without an ITS-wide spinlock, concurrent configuration of different IRQs/vCPUs
from the same or different guests could freely interleave their MMIO writes on
the shared physical ITS.
Could this result in the ITS hardware receiving corrupted invalidation commands
(e.g. invalidating the wrong device/event while leaving another stale), leading
to dropped or misrouted interrupts?
[Severity: High]
Are in-flight events drained here when direct injection is disabled?
When disabling direct injection (where data is NULL), the ITTE is updated to
the host LPI and the ITS cache is invalidated. However, there is no call to
gicv5_its_syncr() or gicv5_irs_syncr() to drain accepted events before
returning.
If KVM subsequently tears down the VM and reuses the VM ID for a new guest,
could an in-flight event reach the IRS and be injected into the new VM's IST
using the old translation?
Disabling forwarding would likely need to drain events before returning to
safely reuse the resources.
> + if (ret) {
> + /* Keep a failed restoration distinguishable from a clean host ITTE. */
> + if (!data)
> + irqd_set_forwarded_to_vcpu(d);
> + return ret;
> + }
> +
> + if (data)
> + irqd_set_forwarded_to_vcpu(d);
> + else
> + irqd_clr_forwarded_to_vcpu(d);
> +
> + return 0;
> +}
> +#endif /* CONFIG_KVM */
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261016155741.979985-1-sascha.bischoff@arm.com?part=11
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH 15/16] KVM: selftests: Add VGICv5 ITS translation cache tests
2026-10-16 15:57 ` [PATCH 15/16] KVM: selftests: Add VGICv5 ITS translation cache tests Sascha Bischoff
@ 2026-10-16 16:22 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-10-16 16:22 UTC (permalink / raw)
To: Sascha Bischoff, kvmarm, kvm; +Cc: Oliver Upton, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] vgic_v5: missing ITS_STATUSR/ITS_GEN_EVENT_STATUSR completion poll
- [High] vgic_v5: priority drop coupled with deactivation in ITS IRQ handler
--
commit fe8f40c11d68589dddb7ff395093f6a131cf59d8
Author: Sascha Bischoff <sascha.bischoff@arm.com>
KVM: selftests: Add VGICv5 ITS translation cache tests
This commit adds end-to-end coverage for VGICv5 ITS translation and cache
invalidation. It verifies that KVM can parse guest-provided linear DT and
ITT state and use it to deliver LPIs through guest GEN_EVENTR and
KVM_SIGNAL_MSI. The test builds a two-VPE VM with IRS, ITS, and an LPI IST.
> diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
> index b7fb002a64f6c..6ea6aef4f0f46 100644
> --- a/tools/testing/selftests/kvm/arm64/vgic_v5.c
> +++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
[ ... ]
> +/* Inject a manually triggered translation via the ITS_GEN_EVENTR */
> +static void gicv5_send_its_gen_event(u32 device_id, u32 event_id)
> +{
> + u64 val;
> +
> + val = FIELD_PREP(GICV5_ITS_GEN_EVENT_DIDR_DEVICEID, device_id);
> + writeq(val, GICV5_ITS_BASE_GVA + GICV5_ITS_GEN_EVENT_DIDR);
> +
> + val = FIELD_PREP(GICV5_ITS_GEN_EVENT_EIDR_EVENTID, event_id);
> + writel(val, GICV5_ITS_BASE_GVA + GICV5_ITS_GEN_EVENT_EIDR);
> +
> + writel(GICV5_ITS_GEN_EVENTR_R |
> + FIELD_PREP(GICV5_ITS_GEN_EVENTR_TARGET_DOMAIN, 0),
> + GICV5_ITS_BASE_GVA + GICV5_ITS_GEN_EVENTR);
[Severity: High]
Does this operation require polling for completion? In gicv5_send_its_gen_event,
the event generation command is issued asynchronously by writing to
GICV5_ITS_GEN_EVENTR without a subsequent poll of the ITS_GEN_EVENT_STATUSR
IDLE bit.
Since the test subsequently yields to the host via GUEST_SYNC and expects the
event to be delivered, failing to wait for the virtual hardware to complete
introduces a race condition. The host might act before the virtual ITS finishes
processing, leading to flaky test execution.
> +}
> +
> +static void gicv5_invalidate_its_event(u32 device_id, u32 event_id)
> +{
> + u64 val;
> +
> + val = FIELD_PREP(GICV5_ITS_DIDR_DEVICEID, device_id);
> + writeq(val, GICV5_ITS_BASE_GVA + GICV5_ITS_DIDR);
> +
> + val = FIELD_PREP(GICV5_ITS_EIDR_EVENTID, event_id);
> + writel(val, GICV5_ITS_BASE_GVA + GICV5_ITS_EIDR);
> +
> + writel(GICV5_ITS_INV_EVENTR_I,
> + GICV5_ITS_BASE_GVA + GICV5_ITS_INV_EVENTR);
[Severity: High]
Should this function poll ITS_STATUSR IDLE after invalidating the event?
In gicv5_invalidate_its_event, the event invalidation command is issued
without a subsequent completion poll. The GICv5 guidelines state that an
ITS table entry invalidated without polling ITS_STATUSR IDLE is a bug, as
the host might act before the virtual ITS finishes processing the
invalidation.
> +}
> +
> +static void gicv5_invalidate_its_device(u32 device_id, u32 event_id_bits)
> +{
> + u64 val;
> +
> + val = FIELD_PREP(GICV5_ITS_DIDR_DEVICEID, device_id);
> + writeq(val, GICV5_ITS_BASE_GVA + GICV5_ITS_DIDR);
> +
> + val = GICV5_ITS_INV_DEVICER_I |
> + FIELD_PREP(GICV5_ITS_INV_DEVICER_EVENTID_BITS, event_id_bits);
> + writel(val, GICV5_ITS_BASE_GVA + GICV5_ITS_INV_DEVICER);
[Severity: High]
Similar to the event invalidation above, should gicv5_invalidate_its_device
wait for the device invalidation to complete by polling ITS_STATUSR IDLE
before returning? Failing to do so can race with the host side.
> +}
[ ... ]
> +static void guest_its_irq_handler(struct ex_regs *regs)
> +{
> + u64 expected_hwirq = gicv5_lpi_hwirq(READ_ONCE(its_expected_lpi));
> + u32 hwirq;
> + u64 ia;
> +
> + ia = gicr_insn(CDIA);
> + if (!GICV5_GICR_CDIA_VALID(ia))
> + return;
> +
> + gsb_ack();
> + isb();
> +
> + hwirq = FIELD_GET(GICV5_GICR_CDIA_INTID, ia);
> + GUEST_ASSERT_EQ(hwirq, expected_hwirq);
> +
> + gic_insn(hwirq, CDDI);
> + gic_insn(0, CDEOI);
[Severity: High]
Can we separate the priority drop from the deactivation in this handler?
In guest_its_irq_handler, the priority drop instruction (CDEOI) is
executed back-to-back with deactivation (CDDI) at the end of the handler.
The GICv5 architecture strictly enforces a split-EOI model where the
priority drop must be performed immediately after the acknowledge and
synchronization barriers, before dispatching to the main handler logic.
Deferring the priority drop until the end blocks all interrupts of equal
or lower priority for the handler's duration.
> +
> + GUEST_SYNC(GUEST_CMD_ITS_LPI_DELIVERED);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261016155741.979985-1-sascha.bischoff@arm.com?part=15
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH 09/16] KVM: arm64: gic-v5: Add ITS MSI injection
2026-10-16 15:57 ` [PATCH 09/16] KVM: arm64: gic-v5: Add ITS MSI injection Sascha Bischoff
@ 2026-10-16 16:23 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-10-16 16:23 UTC (permalink / raw)
To: Sascha Bischoff, kvmarm, kvm; +Cc: Oliver Upton, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] kvm/vgic: raw_spinlock deadlock due to missing IRQ disable in its_cache_add_translation
- [High] kvm/vgic: userspace-triggerable WARN_ON_ONCE causes host DoS in vgic_v5_its_inject_msi
- [High] arm64: vgic-v5: User-triggerable WARN_ON_ONCE in MSI injection
- [High] kvm: arm64: ABI violation on guest-driven MSI mapping errors
- [Medium] kvm: arm64: gic-v5: MSI injection aborted on translation cache allocation failure
- [Medium] arm64: vgic-v5: MSI injection fails if ITS cache insertion fails
--
commit 65afd53542f7c52bea1da09f7e70c85b22c50b33
Author: Sascha Bischoff <sascha.bischoff@arm.com>
KVM: arm64: gic-v5: Add ITS MSI injection
Now that KVM can resolve GICv5 ITS translations from guest memory,
allow userspace and irqfd MSI injection to target a virtual GICv5 ITS.
> diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c
> index e4c0415137605..5920fa1bcb2cf 100644
> --- a/arch/arm64/kvm/vgic/vgic-its-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-its-v5.c
[ ... ]
> static int its_cache_add_translation(struct kvm *kvm, struct vgic_v5_its *its,
> u32 device_id, u16 event_id,
> struct vgic_irq *irq)
> {
> u64 key = ((u64)device_id << 16) | (u64)event_id;
> struct its_cache_translation_entry *entry;
> struct its_cache_translation_entry *old;
> struct hlist_node *tmp;
> HLIST_HEAD(removed);
>
> entry = kzalloc_obj(*entry, GFP_KERNEL);
> if (!entry)
> return -ENOMEM;
>
> entry->key = key;
> entry->irq = irq;
>
> scoped_guard(raw_spinlock, &its->translation_cache_lock) {
[Severity: High]
Could this lock acquisition deadlock?
The translation_cache_lock is a raw_spinlock_t that is also acquired in
hardirq context by its_cache_look_up_translation(). Since
scoped_guard(raw_spinlock) does not disable local interrupts, a hardirq could
preempt this process-context section and attempt to acquire the same lock,
spinning forever.
Should this use the raw_spinlock_irqsave variant?
[ ... ]
> static int its_cache_look_up_translation(struct vgic_v5_its *its,
> u32 device_id, u16 event_id,
> struct vgic_irq **irq)
> {
> u64 key = ((u64)device_id << 16) | (u64)event_id;
> struct its_cache_translation_entry *entry;
>
> guard(raw_spinlock)(&its->translation_cache_lock);
[Severity: High]
Does this also need to disable interrupts?
Since this is called from the fast path (kvm_arch_set_irq_inatomic) which can
run in hardirq context, and the same lock is taken in process context, should
both sides use the irqsave/irqrestore variants to prevent deadlocks?
[ ... ]
> @@ -531,7 +572,8 @@ static int __vgic_v5_its_resolve_lpi(struct kvm *kvm, struct vgic_v5_its *its,
> return PTR_ERR(*irq);
>
> vgic_get_irq_ref(*irq);
> - ret = its_cache_add_translation(kvm, its, device_id, event_id, *irq);
> + ret = its_cache_add_translation(kvm, its, device_id, (u16)event_id,
> + *irq);
> if (ret) {
> vgic_put_irq(kvm, *irq);
> vgic_put_irq(kvm, *irq);
> *irq = NULL;
> }
>
> return ret;
> }
[Severity: Medium]
Is it correct to abort the injection if the translation cache insertion fails?
The translation cache is intended to be a best-effort optimization. If
its_cache_add_translation() fails (e.g., due to -ENOMEM), propagating the
error and dropping the successfully resolved LPI will silently lose the
interrupt, which could hang guest drivers under host memory pressure.
Should the error be swallowed here instead so that injection proceeds?
[ ... ]
> +int vgic_v5_its_inject_msi(struct kvm *kvm, struct kvm_msi *msi)
> +{
> + struct vgic_v5_its *its;
> + struct vgic_irq *irq;
> + bool borrowed_mm = false;
> + int ret;
> +
> + ret = vgic_v5_check_msi(kvm, msi);
> + if (ret)
> + return ret;
> +
> + its = vgic_v5_msi_to_its(kvm, msi);
> + if (IS_ERR(its))
> + return PTR_ERR(its);
> +
> + /* An irqfd cache miss is resolved by a worker outside the VM's mm. */
> + if (current->mm != kvm->mm) {
> + if (WARN_ON_ONCE(current->mm || !(current->flags & PF_KTHREAD)))
> + return -EWOULDBLOCK;
[Severity: High]
Could this WARN_ON_ONCE be triggered by unprivileged userspace?
If a normal user process (other than the VM creator) calls the KVM_SIGNAL_MSI
ioctl, current->mm will not equal kvm->mm, and since it is a user thread,
current->mm is non-NULL and it lacks PF_KTHREAD. Triggering a kernel warning
can crash the host if panic_on_warn is enabled, leading to a Denial of Service.
> +
> + if (!mmget_not_zero(kvm->mm))
> + return -EFAULT;
> +
> + kthread_use_mm(kvm->mm);
> + borrowed_mm = true;
> + }
> +
> + ret = vgic_v5_its_resolve_lpi(kvm, its, msi->devid, msi->data, &irq);
> + if (borrowed_mm) {
> + kthread_unuse_mm(kvm->mm);
> + mmput(kvm->mm);
> + }
> +
> + if (ret)
> + return ret;
[Severity: High]
Does this violate the KVM_SIGNAL_MSI ABI by returning negative error codes?
When the guest provides an invalid mapping (like an out-of-bounds Event ID),
vgic_v5_its_resolve_lpi() returns errors like -E2BIG or -EINVAL. Propagating
these directly to the ioctl caller instead of returning 0 (to indicate the
guest blocked or unmapped the MSI) can cause the VMM to abort, creating a
guest-to-host Denial of Service.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261016155741.979985-1-sascha.bischoff@arm.com?part=9
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH 13/16] KVM: arm64: gic-v5: Implement irq forwarding
2026-10-16 15:57 ` [PATCH 13/16] KVM: arm64: gic-v5: Implement irq forwarding Sascha Bischoff
@ 2026-10-16 16:25 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-10-16 16:25 UTC (permalink / raw)
To: Sascha Bischoff, kvmarm, kvm; +Cc: Oliver Upton, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] kvm: passing full typed INTID to hardware VDPEND instruction
- [High] kvm: use of undefined macro kzalloc_obj
- [High] kvm: ABBA deadlock due to translation_cache_lock acquired without disabling IRQs
- [High] kvm/vgic: TOCTOU race in its_cache_drop_translation disables wrong host IRQ
- [Medium] kvm: data race from lockless read of irq->hw bitfield
- [Low] cleanup: mixing goto-based cleanup with guard() declarations
--
commit 3a376f5ffceb9df2e3023c6def306e4b00a9eba1
Author: Sascha Bischoff <sascha.bischoff@arm.com>
KVM: arm64: gic-v5: Implement irq forwarding
GICv5, like GICv4, supports direct injection of interrupts via the
ITS. Move the existing GICv4 interface behind a common shim that selects
the appropriate implementation. For GICv5 forwarding, the ITS driver
replaces a host ITTE with one targeting a guest. The host no longer
receives that interrupt until forwarding is removed and the host ITTE is
restored.
> diff --git a/arch/arm64/kvm/vgic/vgic-its-v5.c b/arch/arm64/kvm/vgic/vgic-its-v5.c
> index 62c1ca8004007..e71cd67652bae 100644
> --- a/arch/arm64/kvm/vgic/vgic-its-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-its-v5.c
[ ... ]
> @@ -31,7 +32,41 @@ static void its_cache_drop_translation(struct kvm *kvm,
> struct its_cache_translation_entry *entry)
> {
> struct vgic_irq *irq = entry->irq;
> + unsigned long flags;
> + int virq = 0;
> + bool hw = false;
> + 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.
> + */
> + 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;
> + virq = irq->host_irq;
> + hw = irq->hw;
> + }
> + raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
> +
> + if (hw && !WARN_ON_ONCE(gicv5_its_disable_direct_injection(virq))) {
[Severity: High]
Is there a TOCTOU race here when the lock is dropped before calling
gicv5_its_disable_direct_injection?
While the lock is released, a concurrent host unbind operation could invoke
irq_bypass_del_producer, unset the forwarding, and free the host IRQ. The Linux
IRQ number could then be immediately reallocated to a different device. When
this thread resumes, it would unconditionally disable hardware direct injection
for the newly assigned device/VM using the stale virq identifier, potentially
causing lost interrupts.
> + raw_spin_lock_irqsave(&irq->irq_lock, flags);
> + if (irq->hw && irq->host_irq == virq &&
> + !irq->forwarding_owner) {
> + irq->hw = false;
> + irq->host_irq = 0;
> + drop_forwarding_ref = true;
> + }
> + raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
> + }
[ ... ]
> @@ -50,7 +85,8 @@ static void its_cache_drop_removed_translations(struct kvm *kvm,
>
> static int its_cache_add_translation(struct kvm *kvm, struct vgic_v5_its *its,
> u32 device_id, u16 event_id,
> - struct vgic_irq *irq)
> + struct vgic_irq *irq,
> + void **owner)
> {
> u64 key = ((u64)device_id << 16) | (u64)event_id;
> struct its_cache_translation_entry *entry;
> @@ -78,6 +114,8 @@ static int its_cache_add_translation(struct kvm *kvm, struct vgic_v5_its *its,
> + struct its_cache_translation_entry *old;
> + struct hlist_node *tmp;
> + HLIST_HEAD(removed);
>
> - entry = kzalloc(sizeof(*entry), GFP_KERNEL);
> + entry = kzalloc_obj(*entry, GFP_KERNEL);
[Severity: High]
Is kzalloc_obj defined anywhere in the tree? It looks like this might be a
typo for kzalloc(sizeof(*entry), GFP_KERNEL) which will cause compilation
failures.
> if (!entry)
> return -ENOMEM;
>
> entry->key = key;
> entry->irq = irq;
>
> - raw_spin_lock(&its->translation_cache_lock);
> + scoped_guard(raw_spinlock, &its->translation_cache_lock) {
[Severity: High]
Should this use the irqsave variant of scoped_guard? Acquiring
translation_cache_lock in process context without disabling local hardware
interrupts could lead to an ABBA deadlock. If irqfd_wakeup runs in HardIRQ
context on a CPU that already holds this lock, the interrupt handler might
spin forever trying to acquire it.
> hash_for_each_possible_safe(its->translation_cache, old, tmp,
> node, key) {
> if (old->key == key) {
[ ... ]
> @@ -129,7 +167,8 @@ static void its_cache_clear_translations(struct vgic_v5_its *its)
>
> static int its_cache_look_up_translation(struct vgic_v5_its *its,
> u32 device_id, u16 event_id,
> - struct vgic_irq **irq)
> + struct vgic_irq **irq,
> + void **owner)
> {
> u64 key = ((u64)device_id << 16) | (u64)event_id;
> struct its_cache_translation_entry *entry;
>
> - raw_spin_lock(&its->translation_cache_lock);
> + guard(raw_spinlock)(&its->translation_cache_lock);
[Severity: High]
Similarly here, acquiring translation_cache_lock without disabling interrupts
leaves the system vulnerable to the same ABBA deadlock if the lock is
contended by HardIRQ context.
> hash_for_each_possible(its->translation_cache, entry, node, key) {
> if (entry->key == key) {
[ ... ]
> @@ -657,8 +735,8 @@ int vgic_v5_its_inject_cached_translation(struct kvm *kvm, struct kvm_msi *msi)
> static void vgic_v5_its_inject_lpi(struct kvm *kvm, struct vgic_irq *irq)
> {
> kvm_call_hyp(__vgic_v5_vdpend, irq->intid, true,
> kvm->arch.vgic.gicv5_vm.vm_id);
> }
[Severity: High]
Does this pass the full typed INTID directly to the hardware GIC VDPEND
instruction?
GICv5 INTIDs encode the type in bits [31:29]. The architecture limits hardware
instructions to expecting a raw hardware ID. Should this be masked using
vgic_v5_get_hwirq_id(irq->intid) to avoid feeding the type bits into the
hardware operand?
[ ... ]
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index c90e00f56b298..970df3f41cf24 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
[ ... ]
> +int kvm_vgic_v5_set_forwarding(struct kvm *kvm, int virq,
> + struct kvm_kernel_irq_routing_entry *irq_entry)
> +{
> + struct vgic_v5_its *its_data;
> + struct vgic_irq *irq;
> + void *owner;
> + bool hw;
> + int ret;
> +
> + if (!vgic_has_its(kvm))
> + return 0;
> +
> + /* Look up the ITS; bail out if we can't look it up */
> + its_data = vgic_v5_get_its(kvm, irq_entry);
> + if (IS_ERR(its_data))
> + return 0;
> +
> + /*
> + * Look up and cache the translation. Bail out if we failed to look up
> + * the translation at this stage. If the translation becomes valid at a
> + * later stage, we should fall back to software-based injection.
> + */
> + guard(mutex)(&its_data->its_lock);
> +
> + ret = vgic_v5_its_resolve_lpi_locked(kvm, its_data,
> + irq_entry->msi.devid,
> + irq_entry->msi.data, &irq, &owner);
> + if (ret)
> + return 0;
> +
> + scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) {
> + hw = irq->hw;
> + }
> +
> + if (hw)
> + goto out_put_irq;
[Severity: Low]
Does this mix goto-based error handling with scope-based cleanup helpers like
guard() and scoped_guard()? The kernel cleanup guidelines strongly discourage
mixing these in the same function to avoid confusing ownership semantics.
Should this function be standardized to use only one of the approaches?
[ ... ]
> +static struct vgic_irq *vgic_v5_host_irq_get_lpi(struct kvm *kvm, int host_irq)
> +{
> + struct vgic_irq *irq;
> + unsigned long idx;
> +
> + guard(rcu)();
> + xa_for_each(&kvm->arch.vgic.lpi_xa, idx, irq) {
> + if (!irq->hw || irq->host_irq != host_irq)
> + continue;
[Severity: Medium]
Is it safe to locklessly read the irq->hw bitfield here?
This bitfield is modified concurrently by kvm_vgic_v5_set_forwarding and
kvm_vgic_v5_unset_forwarding under irq_lock. Concurrent read and write access
to a C bitfield is undefined behavior and can result in torn reads or
corrupted data.
> +
> + if (!vgic_try_get_irq_ref(irq))
> + return NULL;
> +
> + return irq;
> + }
> +
> + return NULL;
> +}
> +
> +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;
> +
> + if (WARN_ON_ONCE(gicv5_its_disable_direct_injection(virq)))
> + goto out_put_irq;
> +
> + scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) {
[Severity: Low]
This is another instance of mixing goto-based error handling alongside
scoped_guard(). Should we convert this completely to scope-based cleanup or
stick entirely to traditional goto labels?
> + if (irq->hw && irq->host_irq == 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);
> +
> +out_put_irq:
> + vgic_put_irq(kvm, irq);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261016155741.979985-1-sascha.bischoff@arm.com?part=13
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH 16/16] KVM: arm64: gic-v5: Document ITS userspace ABI
2026-10-16 15:57 ` [PATCH 16/16] KVM: arm64: gic-v5: Document ITS userspace ABI Sascha Bischoff
@ 2026-10-16 16:27 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-10-16 16:27 UTC (permalink / raw)
To: Sascha Bischoff, kvmarm, kvm; +Cc: Oliver Upton, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] kvm: arm64: gic-v5: userspace writes to READ_EVENT_DATAR are silently ignored causing state loss
--
commit 3a4606444ec9f90ae2360e066a229c789d9eca15
Author: Sascha Bischoff <sascha.bischoff@arm.com>
KVM: arm64: gic-v5: Document ITS userspace ABI
Document the GICv5 ITS device and its userspace ABI, including the
MMIO address, reset control, register attributes, and MSI requirements.
> diff --git a/Documentation/virt/kvm/devices/arm-vgic-v5-its.rst b/Documentation/virt/kvm/devices/arm-vgic-v5-its.rst
> new file mode 100644
> index 0000000000000..250057e4445d7
> --- /dev/null
> +++ b/Documentation/virt/kvm/devices/arm-vgic-v5-its.rst
[ ... ]
> +KVM_DEV_ARM_VGIC_GRP_ITS_REGS
> +-----------------------------
> +
> + Attributes:
> + The attr field of kvm_device_attr contains the ITS register offset from
> + the control frame base address. kvm_device_attr.addr points to a __u64,
> + regardless of whether the register is 32 or 64 bits wide. A 64-bit
> + register must be addressed at its base offset; userspace cannot access
> + its upper or lower half separately. Guest MMIO may use aligned 32-bit
> + accesses to DT_BASER, DIDR, READ_EVENT_DATAR, and GEN_EVENT_DIDR.
> +
> + The ITS base address must be configured before this group can be used.
> + Writes to read-only registers are ignored. Writes to DT_CFGR and DT_BASER
[Severity: Medium]
Does this design violate the rule that read-only attributes must return an
error on set?
A device attribute documented as read-only whose set path does not return an
error can lead to silent migration state loss. For example, READ_EVENT_DATAR
is a stateful register that holds the result of the READ_EVENTR command. If
userspace restores this state during migration, but the write path returns 0
while ignoring the value, the restored state is silently dropped, corrupting
the guest's view of the command result upon resume.
Looking at the implementation, READ_EVENT_DATAR is defined without a uaccess
write override in vgic_v5_its_registers() and uses vgic_v5_mmio_write_its_wi.
The userspace attribute set path in vgic_v5_its_attr_regs_access() defaults
to calling the guest its_write handler, which invokes the Write Ignore stub
and successfully returns 0 instead of returning -EINVAL or -EPERM.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261016155741.979985-1-sascha.bischoff@arm.com?part=16
^ permalink raw reply [flat|nested] 28+ messages in thread
end of thread, other threads:[~2026-10-16 16:27 UTC | newest]
Thread overview: 28+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-16 15:57 [PATCH 00/16] KVM: arm64: Add GICv5 ITS support Sascha Bischoff
2026-10-16 15:57 ` [PATCH 01/16] KVM: arm64: vgic: Split out common LPI allocation Sascha Bischoff
2026-10-16 16:14 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 02/16] KVM: arm64: vgic: Pass opaque device pointer to ITS MMIO callbacks Sascha Bischoff
2026-10-16 15:57 ` [PATCH 03/16] KVM: arm64: gic-v5: Share CoreSight ID register emulation Sascha Bischoff
2026-10-16 15:57 ` [PATCH 04/16] irqchip/gic-v5: Add ITS MMIO register definitions Sascha Bischoff
2026-10-16 15:57 ` [PATCH 05/16] KVM: arm64: gic-v5: Add ITS device skeleton Sascha Bischoff
2026-10-16 15:57 ` [PATCH 06/16] KVM: arm64: gic-v5: Add ITS MMIO state emulation Sascha Bischoff
2026-10-16 16:11 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 07/16] KVM: arm64: gic-v5: Add ITS guest table translation emulation Sascha Bischoff
2026-10-16 16:17 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 08/16] KVM: arm64: gic-v5: Add ITS translation cache maintenance Sascha Bischoff
2026-10-16 16:10 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 09/16] KVM: arm64: gic-v5: Add ITS MSI injection Sascha Bischoff
2026-10-16 16:23 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 10/16] KVM: arm64: gic-v5: Add ITS userspace register access Sascha Bischoff
2026-10-16 16:19 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 11/16] irqchip/gic-v5: Enable direct injection in ITS Sascha Bischoff
2026-10-16 16:22 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 12/16] KVM: arm64: vgic: Move direct MSI support helper to common MMIO code Sascha Bischoff
2026-10-16 15:57 ` [PATCH 13/16] KVM: arm64: gic-v5: Implement irq forwarding Sascha Bischoff
2026-10-16 16:25 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 14/16] KVM: selftests: Add VGICv5 ITS attribute tests Sascha Bischoff
2026-10-16 16:18 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 15/16] KVM: selftests: Add VGICv5 ITS translation cache tests Sascha Bischoff
2026-10-16 16:22 ` sashiko-bot
2026-10-16 15:57 ` [PATCH 16/16] KVM: arm64: gic-v5: Document ITS userspace ABI Sascha Bischoff
2026-10-16 16:27 ` sashiko-bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.