* [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support
@ 2026-08-07 11:12 Sascha Bischoff
2026-08-07 11:13 ` [PATCH v5 01/49] irqchip/gic-v5: Allow KVM setup without a maintenance IRQ Sascha Bischoff
` (48 more replies)
0 siblings, 49 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:12 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Hi all,
This series builds on the initial vGICv5 support [1] and adds support
for the GICv5 IRS, as described by the GICv5 (EAC0) specification [2].
With this, a GICv5 guest is no longer restricted to PPIs, and can make
use of SPIs and LPIs as well.
With SPIs and LPIs available, this series makes it possible to boot a
full Linux guest on the Arm FVP model [3], using the setup described in
Lorenzo's GICv5 Linux software enablement guide [4]. In addition, GICv5
IPIs are typically implemented as LPIs, so LPI support is what makes
guests with more than one vCPU possible.
The corresponding kvmtool changes are available separately [5]. With
these changes, `--irqchip=gicv5` works now. The `--irqchip=gicv5-its`
configuration does not work with this series, as ITS/MSI support is not
included here.
The IRS is created as part of the vgic-v5 device, and is not a separate
KVM device. This is because implementing the CPU interface alone would
limit a guest to a single vCPU and PPIs only. Hence, the IRS is bundled
into the VGIC, much like the distributor for GICv3.
The series adds the host-side plumbing needed for KVM to interact with
the physical IRS, including VM and VPE table management, VPE residency,
and VPE doorbells. It also adds an emulated IRS MMIO interface for the
guest, together with the UAPI needed for userspace to configure the IRS
address and to save/restore the IRS and IST state.
The main pieces are:
* Host IRS capability discovery and configuration frame plumbing.
* Allocation and management of the GICv5 VM table, VPE table, VMTEs,
VPEs, and host-backed guest IST storage.
* VPE residency and doorbell handling, allowing the IRS to select SPIs
and LPIs for resident VPEs and wake non-resident VPEs when required.
* Emulation of the GICv5 IRS MMIO CONFIG_FRAME for guests.
Virtualisation, MPAM, MEC, and SWERR are not supported and are
RAZ/WI.
* SPI injection support using GIC VDPEND. Once injected, the GICv5
hardware manages the SPI lifecycle.
* Save/restore support for GICv5 EL1 system registers, IRS MMIO state,
and guest ISTs. SPI and LPI IST state is transferred through
userspace-owned buffers using KVM_DEV_ARM_VGIC_GRP_IST.
* Documentation for the new VGICv5 IRS userspace interfaces and the
required save/restore ordering.
* Selftests for the UAPI components, the PPI, SPI, and LPI support, and
save/restore.
This series keeps SPI, LPI, and save/restore support together
intentionally. SPI and LPI support are needed to run realistic guests,
while the save/restore interface is included at the same time so that the
new userspace ABI is complete from the point it is exposed: userspace
should not be able to create VGICv5 state that it cannot also migrate.
The IST migration selftest restores state into a separate VM, re-saves
the SPI and LPI IST images before the destination runs, and compares
them byte-for-byte with the source images. It also validates restored IRS
MMIO state through SPI/LPI delivery, priority, enable, and affinity
behavior.
LPIs are explicitly driven by the guest through the IRS/IST state. This
series does not add direct LPI injection support, MSI support, or GICv5
ITS emulation.
These changes are based on v7.2-rc6. I've pushed the full set of changes
to a branch at [6].
Changes since v4 [7]:
* Rebased onto v7.2-rc6 and addressed review feedback from Marc and
Fuad, together with the Sashiko AI reports for v4. Thanks all!
* Aligned ACPI maintenance-interrupt discovery with GICv3. Broken MADT
descriptions now produce FW_BUG warnings rather than preventing KVM
initialisation, and the interrupt is always registered as
level-sensitive.
* Reworked host IRS capability discovery to cache raw IRS_IDR2,
IRS_IDR3, and IRS_IDR4 values and derive their fields through
accessors. This avoids tying future EL2 code to the decoded layout
of kvm_vgic_global_state.
* Hardened hardware-shared table handling for non-coherent IRS
implementations. IRS operations and CPU table accesses now share a
lock, VPET, VMD and VPED allocations are rounded up to whole cache
lines, descriptors are initialised before publication, and VMT
configuration waits for the IRS to become idle.
* Fixed sparse vCPU ID handling. The VPET and architectural VPE ID now
use the userspace-provided vcpu_id, while doorbells and VPED storage
use the dense internal vcpu_idx. IDs outside the available VPE ID
space are rejected.
* Limited virtual GICv5 implementations to 1024 SPIs so that the
physically contiguous per-SPI state allocation remains within
KMALLOC_MAX_SIZE. The common IRQ routing table accommodates 1024
pins, while route validation retains the existing 988-pin limit for
GICv2 and GICv3.
* Tightened LPI IST lifetime and migration rules. Guest Valid-to-Valid
IRS_IST_BASER writes can no longer change the address, and once a
host LPI IST exists, userspace may replay but not alter
IRS_IST_BASER or IRS_IST_CFGR. LPI IST saves also require the VMTE ID
width to match IRS_IST_CFGR. The IST migration descriptor has moved
into the arm64-specific UAPI header.
* Hardened IRS and doorbell operation. GICv5 probing now fails without
a host LPI domain, commands require a valid doorbell IRQ, running
vCPUs are reloaded only on IRSEN transitions, and WFI doorbells are
requested only while the virtual IRS is enabled.
* The resident, non-resident, and VDPEND nVHE handlers are explicit
no-ops under pKVM. This matches the fix by Fuad for the
already-upstreamed GICv5 hypercalls [8].
* Expanded the selftests to cover the 1024-SPI boundary, live IST
register restrictions, sparse vCPU IDs, and rejection of vCPU IDs
outside the GICv5 VPE ID space.
Thanks for taking the time to look at these changes!
Thanks,
Sascha
[1] https://lore.kernel.org/all/20260319154937.3619520-1-sascha.bischoff@arm.com/
[2] https://developer.arm.com/documentation/aes0070/latest
[3] https://developer.arm.com/documentation/108086/latest
[4] https://linaro.atlassian.net/wiki/x/CQAF-wY
[5] https://lore.kernel.org/all/20260116182606.61856-1-sascha.bischoff@arm.com/
[6] https://gitlab.arm.com/linux-arm/linux-sb/-/tree/gicv5_kvm_irs_support_v5
[7] https://lore.kernel.org/all/20260724104819.1296803-1-sascha.bischoff@arm.com/
[8] https://lore.kernel.org/all/20260806100256.371164-1-fuad.tabba@linux.dev/
Sascha Bischoff (49):
irqchip/gic-v5: Allow KVM setup without a maintenance IRQ
irqchip/gic-v5: Provide OF IRS config frame attrs to KVM
irqchip/gic-v5: Set up gic_kvm_info on ACPI hosts
KVM: arm64: gic-v5: Define remaining IRS MMIO registers
arm64/sysreg: Add GICv5 GIC VDPEND encoding
arm64/sysreg: Update ICC_CR0_EL1 with LINK and LINK_IDLE fields
KVM: arm64: gic-v5: Cache host IRS ID registers
KVM: arm64: gic-v5: Add VPE doorbell domain
KVM: arm64: gic-v5: Create and manage VM and VPE tables
KVM: arm64: gic-v5: Introduce guest IST alloc and management
KVM: arm64: gic-v5: Implement VMT/vIST IRS MMIO Ops
KVM: arm64: gic-v5: Keep GICv5 vCPU limit model-specific
KVM: arm64: gic-v5: Implement VPE IRS MMIO Ops
KVM: arm64: gic-v5: Set up VMTEs and VPE doorbells
KVM: arm64: gic-v5: Add resident/non-resident hyp calls
KVM: arm64: gic-v5: Request doorbells when VPEs enter WFI
KVM: arm64: gic-v5: Introduce struct vgic_v5_irs and IRS base address
KVM: arm64: gic-v5: Add IRS IODEV support to MMIO handlers
KVM: arm64: gic-v5: Add KVM_VGIC_V5_ADDR_TYPE_IRS to UAPI
KVM: arm64: gic-v5: Add GICv5 IRS IODEV and MMIO emulation
KVM: arm64: gic-v5: Initialise per-VM IRS state
KVM: arm64: gic-v5: Register the IRS IODEV
KVM: arm64: gic-v5: Set IRICHPPIDIS based on IRS enable state
KVM: arm64: selftests: Update vGICv5 selftest to set IRS address
KVM: arm64: gic-v5: Add GIC VDPEND hyp call
KVM: arm64: gic: Introduce set_pending_state() to irq_ops
KVM: arm64: gic-v5: Support SPI injection
Documentation: KVM: Extend VGICv5 device attribute docs
KVM: arm64: gic-v5: Add GICv5 SPI injection to irqfd
KVM: arm64: gic-v5: Mask per-vCPU PPI state in
vgic_v5_finalize_ppi_state()
KVM: arm64: gic-v5: Add GICv5 EL1 sysreg userspace accessors
KVM: arm64: gic-v5: Handle userspace accesses to IRS MMIO region
KVM: arm64: gic-v5: Add CoreSight MMIO regs to IRS
KVM: arm64: gic-v5: Add VGICv5 IST save/restore UAPI
KVM: arm64: gic-v5: Implement save/restore mechanisms for ISTs
Documentation: KVM: Document KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS for
VGICv5
Documentation: KVM: Add KVM_DEV_ARM_VGIC_GRP_IRS_REGS to VGICv5 docs
Documentation: KVM: Add docs for KVM_DEV_ARM_VGIC_GRP_IST
Documentation: KVM: Add the VGICv5 IRS save/restore sequences
KVM: selftests: Add VGICv5 IRS address attribute tests
KVM: selftests: Add VGICv5 NR_IRQS attribute tests
KVM: selftests: Add VGICv5 IRS_REGS attribute tests
KVM: selftests: Add VGICv5 IST attribute tests
KVM: selftests: Add VGICv5 USERSPACE_PPIS tests
KVM: selftests: Add VGICv5 CPU sysreg attribute tests
KVM: selftests: Add VGICv5 SPI injection tests
KVM: selftests: Add VGICv5 LPI delivery tests
KVM: selftests: Add VGICv5 IST save/restore coverage
KVM: selftests: Add VGICv5 sparse vCPU IDs test
Documentation/virt/kvm/api.rst | 3 +-
.../virt/kvm/devices/arm-vgic-v5.rst | 271 ++-
arch/arm64/include/asm/kvm_asm.h | 3 +
arch/arm64/include/asm/kvm_host.h | 1 +
arch/arm64/include/asm/kvm_hyp.h | 3 +
arch/arm64/include/asm/sysreg.h | 13 +-
arch/arm64/include/uapi/asm/kvm.h | 15 +
arch/arm64/kvm/Makefile | 3 +-
arch/arm64/kvm/arm.c | 10 +-
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 35 +
arch/arm64/kvm/hyp/vgic-v5-sr.c | 45 +
arch/arm64/kvm/sys_regs.c | 6 +-
arch/arm64/kvm/vgic-sys-reg-v5.c | 519 +++++
arch/arm64/kvm/vgic/vgic-init.c | 150 +-
arch/arm64/kvm/vgic/vgic-irqfd.c | 18 +-
arch/arm64/kvm/vgic/vgic-irs-v5.c | 1184 ++++++++++
arch/arm64/kvm/vgic/vgic-kvm-device.c | 270 ++-
arch/arm64/kvm/vgic/vgic-mmio.c | 6 +
arch/arm64/kvm/vgic/vgic-mmio.h | 2 +
arch/arm64/kvm/vgic/vgic-v5-tables.c | 1860 ++++++++++++++++
arch/arm64/kvm/vgic/vgic-v5-tables.h | 140 ++
arch/arm64/kvm/vgic/vgic-v5.c | 1139 +++++++++-
arch/arm64/kvm/vgic/vgic.c | 37 +-
arch/arm64/kvm/vgic/vgic.h | 21 +
arch/arm64/tools/sysreg | 4 +-
drivers/irqchip/irq-gic-v5-irs.c | 19 +-
drivers/irqchip/irq-gic-v5.c | 112 +-
include/kvm/arm_vgic.h | 210 +-
include/linux/irqchip/arm-gic-v5.h | 256 ++-
include/linux/irqchip/arm-vgic-info.h | 5 +
tools/arch/arm64/include/uapi/asm/kvm.h | 15 +
tools/testing/selftests/kvm/arm64/vgic_v5.c | 1931 ++++++++++++++++-
.../selftests/kvm/include/arm64/gic_v5.h | 105 +
33 files changed, 8208 insertions(+), 203 deletions(-)
create mode 100644 arch/arm64/kvm/vgic-sys-reg-v5.c
create mode 100644 arch/arm64/kvm/vgic/vgic-irs-v5.c
create mode 100644 arch/arm64/kvm/vgic/vgic-v5-tables.c
create mode 100644 arch/arm64/kvm/vgic/vgic-v5-tables.h
--
2.34.1
^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v5 01/49] irqchip/gic-v5: Allow KVM setup without a maintenance IRQ
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
@ 2026-08-07 11:13 ` Sascha Bischoff
2026-08-07 11:13 ` [PATCH v5 02/49] irqchip/gic-v5: Provide OF IRS config frame attrs to KVM Sascha Bischoff
` (47 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:13 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
GICv5 does not require a virtual CPU interface maintenance interrupt
for native GCIE operation. The interrupt is only needed when
FEAT_GCIE_LEGACY is present, as the legacy GICv3 interface still
relies on maintenance IRQ delivery.
Stop rejecting KVM setup solely because the maintenance interrupt is
absent. Parse the interrupt if present, but if none is described and
the system does not advertise FEAT_GCIE_LEGACY, tell KVM that no
maintenance interrupt is required.
This lets native GICv5 KVM support be registered on systems that do
not provide a maintenance interrupt, while requiring a maintenance
interrupt for GICv3-capable systems.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
drivers/irqchip/irq-gic-v5.c | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
index e9d1795235a66..c04523a438cee 100644
--- a/drivers/irqchip/irq-gic-v5.c
+++ b/drivers/irqchip/irq-gic-v5.c
@@ -1143,10 +1143,18 @@ static void __init gic_of_setup_kvm_info(struct device_node *node)
/* GIC Virtual CPU interface maintenance interrupt */
gic_v5_kvm_info.no_maint_irq_mask = false;
gic_v5_kvm_info.maint_irq = irq_of_parse_and_map(node, 0);
- if (!gic_v5_kvm_info.maint_irq) {
- pr_warn("cannot find GICv5 virtual CPU interface maintenance interrupt\n");
- return;
- }
+
+ /*
+ * We require an MI if we have legacy support, but don't, otherwise.
+ * Given that there's an existing flag to convey that an MI isn't
+ * needed, we (ab)use it to tell KVM that the MI isn't needed if we
+ * don't support legacy.
+ *
+ * The check for ARM64_HAS_GICV5_LEGACY explicitly doesn't use
+ * cpus_have_final_cap() here as we run too early.
+ */
+ if (!cpus_have_cap(ARM64_HAS_GICV5_LEGACY) && !gic_v5_kvm_info.maint_irq)
+ gic_v5_kvm_info.no_maint_irq_mask = true;
vgic_set_kvm_info(&gic_v5_kvm_info);
}
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 02/49] irqchip/gic-v5: Provide OF IRS config frame attrs to KVM
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
2026-08-07 11:13 ` [PATCH v5 01/49] irqchip/gic-v5: Allow KVM setup without a maintenance IRQ Sascha Bischoff
@ 2026-08-07 11:13 ` Sascha Bischoff
2026-08-07 11:14 ` [PATCH v5 03/49] irqchip/gic-v5: Set up gic_kvm_info on ACPI hosts Sascha Bischoff
` (46 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:13 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
KVM needs to interact with the host IRS in order to, for example, make
VMs or VPEs valid. There are two potential approaches here. Either the
host irqchip driver can provide an interface, or KVM can interact
directly with the host IRS. The latter of these two is chosen as the
set of MMIO registers that KVM needs to interact with is orthogonal to
the set used by the host irqchip driver (with the exception of some of
the read-only IRS_IDRx registers).
Pass KVM a pointer to an IRS config frame - the config frame belonging
to ANY IRS is fine as long as one IRS's config frame is used
consistently - in struct gic_kvm_info. Additionally, include a flag
telling KVM whether the IRS is coherent or non-coherent in order to
make sure that KVM can do the correct cache state management, if
required.
Only OF (Device Tree) is supported with this change. ACPI is not.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
drivers/irqchip/irq-gic-v5-irs.c | 7 +++++--
drivers/irqchip/irq-gic-v5.c | 5 +++++
include/linux/irqchip/arm-gic-v5.h | 3 +++
include/linux/irqchip/arm-vgic-info.h | 5 +++++
4 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/drivers/irqchip/irq-gic-v5-irs.c b/drivers/irqchip/irq-gic-v5-irs.c
index f3fce0b1e25d9..607e066821b52 100644
--- a/drivers/irqchip/irq-gic-v5-irs.c
+++ b/drivers/irqchip/irq-gic-v5-irs.c
@@ -21,8 +21,6 @@
*/
#define LPI_ID_BITS_LINEAR 12
-#define IRS_FLAGS_NON_COHERENT BIT(0)
-
static DEFINE_PER_CPU_READ_MOSTLY(struct gicv5_irs_chip_data *, per_cpu_irs_data);
static LIST_HEAD(irs_nodes);
@@ -50,6 +48,11 @@ static void irs_writeq_relaxed(struct gicv5_irs_chip_data *irs_data,
writeq_relaxed(val, irs_data->irs_base + reg_offset);
}
+struct gicv5_irs_chip_data *gicv5_irs_get_chip_data(void)
+{
+ return per_cpu(per_cpu_irs_data, 0);
+}
+
/*
* The polling wait (in gicv5_wait_for_op_s_atomic()) on a GIC register
* provides the memory barriers (through MMIO accessors)
diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
index c04523a438cee..e7a7aedcfaf78 100644
--- a/drivers/irqchip/irq-gic-v5.c
+++ b/drivers/irqchip/irq-gic-v5.c
@@ -1128,6 +1128,8 @@ static struct gic_kvm_info gic_v5_kvm_info __initdata;
static void __init gic_of_setup_kvm_info(struct device_node *node)
{
+ struct gicv5_irs_chip_data *irs_data = gicv5_irs_get_chip_data();
+
/*
* If we don't have native GICv5 virtualisation support, then
* we also don't have FEAT_GCIE_LEGACY - the architecture
@@ -1140,6 +1142,9 @@ static void __init gic_of_setup_kvm_info(struct device_node *node)
gic_v5_kvm_info.type = GIC_V5;
+ gic_v5_kvm_info.gicv5_irs.base = irs_data->irs_base;
+ gic_v5_kvm_info.gicv5_irs.non_coherent = !!(irs_data->flags & IRS_FLAGS_NON_COHERENT);
+
/* GIC Virtual CPU interface maintenance interrupt */
gic_v5_kvm_info.no_maint_irq_mask = false;
gic_v5_kvm_info.maint_irq = irq_of_parse_and_map(node, 0);
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index f78787e654f4c..681c5c51207d6 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -330,6 +330,8 @@ struct gicv5_irs_chip_data {
raw_spinlock_t spi_config_lock;
};
+#define IRS_FLAGS_NON_COHERENT BIT(0)
+
static inline int gicv5_wait_for_op_s_atomic(void __iomem *addr, u32 offset,
const char *reg_s, u32 mask,
u32 *val)
@@ -377,6 +379,7 @@ void __init gicv5_free_lpi_domain(void);
int gicv5_irs_of_probe(struct device_node *parent);
int gicv5_irs_acpi_probe(void);
+struct gicv5_irs_chip_data *gicv5_irs_get_chip_data(void);
void gicv5_irs_remove(void);
int gicv5_irs_enable(void);
void gicv5_irs_its_probe(void);
diff --git a/include/linux/irqchip/arm-vgic-info.h b/include/linux/irqchip/arm-vgic-info.h
index 67d9d960273b9..f05370e2debf4 100644
--- a/include/linux/irqchip/arm-vgic-info.h
+++ b/include/linux/irqchip/arm-vgic-info.h
@@ -38,6 +38,11 @@ struct gic_kvm_info {
bool has_v4_1;
/* Deactivation impared, subpar stuff */
bool no_hw_deactivation;
+ /* GICv5 IRS base */
+ struct {
+ void __iomem *base;
+ bool non_coherent;
+ } gicv5_irs;
};
#ifdef CONFIG_KVM
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 03/49] irqchip/gic-v5: Set up gic_kvm_info on ACPI hosts
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
2026-08-07 11:13 ` [PATCH v5 01/49] irqchip/gic-v5: Allow KVM setup without a maintenance IRQ Sascha Bischoff
2026-08-07 11:13 ` [PATCH v5 02/49] irqchip/gic-v5: Provide OF IRS config frame attrs to KVM Sascha Bischoff
@ 2026-08-07 11:14 ` Sascha Bischoff
2026-08-07 13:44 ` Lorenzo Pieralisi
2026-08-07 11:14 ` [PATCH v5 04/49] KVM: arm64: gic-v5: Define remaining IRS MMIO registers Sascha Bischoff
` (45 subsequent siblings)
48 siblings, 1 reply; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:14 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Device-tree based GICv5 probing already passes the IRS details and
maintenance interrupt to KVM, but the ACPI path only initialises the
irqchip and installs the ACPI IRQ model. As a result, KVM never sees
the GICv5 host information required to probe the vGIC on ACPI systems.
Add the ACPI equivalent of the DT KVM setup. Parse the MADT GICC
entries for the maintenance interrupt, retaining the value from the
first relevant entry and warning if later entries disagree. Warn if
firmware describes the interrupt as edge-triggered, but register it as
a level-sensitive, active-high GSI. Pass the resulting IRQ together
with the IRS base and coherency information to KVM.
Native GICv5 does not require a maintenance interrupt unless the
legacy GICv3-compatible CPU interface is present, so preserve the
existing no-maintenance-IRQ handling for that case.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
drivers/irqchip/irq-gic-v5.c | 95 ++++++++++++++++++++++++++++++++++--
1 file changed, 91 insertions(+), 4 deletions(-)
diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
index e7a7aedcfaf78..5b8ea8551d832 100644
--- a/drivers/irqchip/irq-gic-v5.c
+++ b/drivers/irqchip/irq-gic-v5.c
@@ -1126,7 +1126,7 @@ static void gicv5_set_cpuif_idbits(void)
#ifdef CONFIG_KVM
static struct gic_kvm_info gic_v5_kvm_info __initdata;
-static void __init gic_of_setup_kvm_info(struct device_node *node)
+static void __init gic_setup_kvm_info(unsigned int maint_irq)
{
struct gicv5_irs_chip_data *irs_data = gicv5_irs_get_chip_data();
@@ -1137,17 +1137,19 @@ static void __init gic_of_setup_kvm_info(struct device_node *node)
*/
if (!gicv5_global_data.virt_capable) {
pr_info("GIC implementation is not virtualization capable\n");
- return;
+ goto out_dispose_maint_irq;
}
- gic_v5_kvm_info.type = GIC_V5;
+ if (WARN_ON(!irs_data))
+ goto out_dispose_maint_irq;
+ gic_v5_kvm_info.type = GIC_V5;
gic_v5_kvm_info.gicv5_irs.base = irs_data->irs_base;
gic_v5_kvm_info.gicv5_irs.non_coherent = !!(irs_data->flags & IRS_FLAGS_NON_COHERENT);
/* GIC Virtual CPU interface maintenance interrupt */
gic_v5_kvm_info.no_maint_irq_mask = false;
- gic_v5_kvm_info.maint_irq = irq_of_parse_and_map(node, 0);
+ gic_v5_kvm_info.maint_irq = maint_irq;
/*
* We require an MI if we have legacy support, but don't, otherwise.
@@ -1162,11 +1164,95 @@ static void __init gic_of_setup_kvm_info(struct device_node *node)
gic_v5_kvm_info.no_maint_irq_mask = true;
vgic_set_kvm_info(&gic_v5_kvm_info);
+ return;
+
+out_dispose_maint_irq:
+ irq_dispose_mapping(maint_irq);
+}
+
+static void __init gic_of_setup_kvm_info(struct device_node *node)
+{
+ /* GIC Virtual CPU interface maintenance interrupt */
+ gic_setup_kvm_info(irq_of_parse_and_map(node, 0));
+}
+
+#ifdef CONFIG_ACPI
+struct gicv5_acpi_kvm_info {
+ u32 maint_irq;
+};
+
+static struct gicv5_acpi_kvm_info acpi_v5_kvm_info __initdata;
+
+static int __init gic_acpi_parse_virt_madt_gicc(union acpi_subtable_headers *header,
+ const unsigned long end)
+{
+ struct acpi_madt_generic_interrupt *gicc =
+ (struct acpi_madt_generic_interrupt *)header;
+ static int first_madt = true;
+
+ if (!(gicc->flags &
+ (ACPI_MADT_ENABLED | ACPI_MADT_GICC_ONLINE_CAPABLE)))
+ return 0;
+
+ if (gicc->flags & ACPI_MADT_VGIC_IRQ_MODE)
+ pr_warn_once(FW_BUG "MI wrongly advertised as Edge-triggered\n");
+
+ if (first_madt) {
+ first_madt = false;
+
+ acpi_v5_kvm_info.maint_irq = gicc->vgic_interrupt;
+ return 0;
+ }
+
+ if (acpi_v5_kvm_info.maint_irq != gicc->vgic_interrupt)
+ pr_warn_once(FW_BUG "MI not the same on all CPUs\n");
+
+ return 0;
+}
+
+static bool __init gic_acpi_collect_virt_info(void)
+{
+ int count;
+
+ count = acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_INTERRUPT,
+ gic_acpi_parse_virt_madt_gicc, 0);
+
+ return count > 0;
}
+
+static void __init gic_acpi_setup_kvm_info(void)
+{
+ unsigned int maint_irq = 0;
+ int irq;
+
+ if (!gic_acpi_collect_virt_info()) {
+ pr_warn("Unable to get hardware information used for virtualization\n");
+ return;
+ }
+
+ if (acpi_v5_kvm_info.maint_irq) {
+ irq = acpi_register_gsi(NULL, acpi_v5_kvm_info.maint_irq,
+ ACPI_LEVEL_SENSITIVE,
+ ACPI_ACTIVE_HIGH);
+ if (irq > 0)
+ maint_irq = irq;
+ else
+ pr_warn("Failed to register GSI for GICv5 maintenance IRQ\n");
+ }
+
+ gic_setup_kvm_info(maint_irq);
+}
+#endif // CONFIG_ACPI
#else
static inline void __init gic_of_setup_kvm_info(struct device_node *node)
{
}
+
+#ifdef CONFIG_ACPI
+static inline void __init gic_acpi_setup_kvm_info(void)
+{
+}
+#endif
#endif // CONFIG_KVM
static int __init gicv5_init_common(struct fwnode_handle *parent_domain)
@@ -1265,6 +1351,7 @@ static int __init gic_acpi_init(union acpi_subtable_headers *header, const unsig
goto out_irs;
acpi_set_irq_model(ACPI_IRQ_MODEL_GIC_V5, gic_v5_get_gsi_domain_id);
+ gic_acpi_setup_kvm_info();
return 0;
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 04/49] KVM: arm64: gic-v5: Define remaining IRS MMIO registers
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (2 preceding siblings ...)
2026-08-07 11:14 ` [PATCH v5 03/49] irqchip/gic-v5: Set up gic_kvm_info on ACPI hosts Sascha Bischoff
@ 2026-08-07 11:14 ` Sascha Bischoff
2026-08-07 11:15 ` [PATCH v5 05/49] arm64/sysreg: Add GICv5 GIC VDPEND encoding Sascha Bischoff
` (44 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:14 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Complete the set of defined IRS MMIO registers in the GICv5 header
file. Up until now, the set of defined IRS MMIO registers has been
driven by code requirements. However, in order to properly emulate the
IRS MMIO interface in KVM, the full set of IRS MMIO registers needs to
be added.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
include/linux/irqchip/arm-gic-v5.h | 239 +++++++++++++++++++++++++++--
1 file changed, 230 insertions(+), 9 deletions(-)
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index 681c5c51207d6..83cfcd056b250 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -62,20 +62,34 @@
#define GICV5_OUTER_SHARE 0b10
#define GICV5_INNER_SHARE 0b11
+#define GICV5_AIDR_COMPONENT_IRS 0b00
+#define GICV5_AIDR_COMPONENT_ITS 0b01
+#define GICV5_AIDR_COMPONENT_IWB 0b10
+
+#define GICV5_AIDR_ARCH_MAJ_REV_V5 0
+#define GICV5_AIDR_ARCH_MIN_REV_V0 0
+
/*
* IRS registers and tables structures
*/
#define GICV5_IRS_IDR0 0x0000
#define GICV5_IRS_IDR1 0x0004
#define GICV5_IRS_IDR2 0x0008
+#define GICV5_IRS_IDR3 0x000c
+#define GICV5_IRS_IDR4 0x0010
#define GICV5_IRS_IDR5 0x0014
#define GICV5_IRS_IDR6 0x0018
#define GICV5_IRS_IDR7 0x001c
+#define GICV5_IRS_IIDR 0x0040
+#define GICV5_IRS_AIDR 0x0044
#define GICV5_IRS_CR0 0x0080
#define GICV5_IRS_CR1 0x0084
#define GICV5_IRS_SYNCR 0x00c0
#define GICV5_IRS_SYNC_STATUSR 0x00c4
+#define GICV5_IRS_SPI_VMR 0x0100
#define GICV5_IRS_SPI_SELR 0x0108
+#define GICV5_IRS_SPI_DOMAINR 0x010c
+#define GICV5_IRS_SPI_RESAMPLER 0x0110
#define GICV5_IRS_SPI_CFGR 0x0114
#define GICV5_IRS_SPI_STATUSR 0x0118
#define GICV5_IRS_PE_SELR 0x0140
@@ -85,11 +99,51 @@
#define GICV5_IRS_IST_CFGR 0x0190
#define GICV5_IRS_IST_STATUSR 0x0194
#define GICV5_IRS_MAP_L2_ISTR 0x01c0
-
+#define GICV5_IRS_VMT_BASER 0x0200
+#define GICV5_IRS_VMT_CFGR 0x0210
+#define GICV5_IRS_VMT_STATUSR 0x0214
+#define GICV5_IRS_VPE_SELR 0x0240
+#define GICV5_IRS_VPE_DBR 0x0248
+#define GICV5_IRS_VPE_HPPIR 0x0250
+#define GICV5_IRS_VPE_CR0 0x0258
+#define GICV5_IRS_VPE_STATUSR 0x025c
+#define GICV5_IRS_VM_DBR 0x0280
+#define GICV5_IRS_VM_SELR 0x0288
+#define GICV5_IRS_VM_STATUSR 0x028c
+#define GICV5_IRS_VMAP_L2_VMTR 0x02c0
+#define GICV5_IRS_VMAP_VMR 0x02c8
+#define GICV5_IRS_VMAP_VISTR 0x02d0
+#define GICV5_IRS_VMAP_L2_VISTR 0x02d8
+#define GICV5_IRS_VMAP_VPER 0x02e0
+#define GICV5_IRS_SAVE_VMR 0x0300
+#define GICV5_IRS_SAVE_VM_STATUSR 0x0308
+#define GICV5_IRS_MEC_IDR 0x0340
+#define GICV5_IRS_MEC_MECID_R 0x0344
+#define GICV5_IRS_MPAM_IDR 0x0380
+#define GICV5_IRS_MPAM_PARTID_R 0x0384
+#define GICV5_IRS_SWERR_STATUSR 0x03c0
+#define GICV5_IRS_SWERR_SYNDROMER0 0x03c8
+#define GICV5_IRS_SWERR_SYNDROMER1 0x03d0
+
+#define GICV5_IRS_IDR0_IRSID GENMASK(31, 16)
+#define GICV5_IRS_IDR0_SWE BIT(12)
+#define GICV5_IRS_IDR0_MPAM BIT(11)
+#define GICV5_IRS_IDR0_MEC BIT(10)
+#define GICV5_IRS_IDR0_SETLPI BIT(9)
+#define GICV5_IRS_IDR0_VIRT_ONE_N BIT(8)
+#define GICV5_IRS_IDR0_ONE_N BIT(7)
#define GICV5_IRS_IDR0_VIRT BIT(6)
+#define GICV5_IRS_IDR0_PA_RANGE GENMASK(5, 2)
+#define GICV5_IRS_IDR0_INT_DOM GENMASK(1, 0)
+
+#define GICV5_IRS_IDR0_INT_DOM_SECURE 0b00
+#define GICV5_IRS_IDR0_INT_DOM_NON_SECURE 0b01
+#define GICV5_IRS_IDR0_INT_DOM_EL3 0b10
+#define GICV5_IRS_IDR0_INT_DOM_REALM 0b11
#define GICV5_IRS_IDR1_PRIORITY_BITS GENMASK(22, 20)
#define GICV5_IRS_IDR1_IAFFID_BITS GENMASK(19, 16)
+#define GICV5_IRS_IDR1_PE_CNT GENMASK(15, 0)
#define GICV5_IRS_IDR1_PRIORITY_BITS_1BITS 0b000
#define GICV5_IRS_IDR1_PRIORITY_BITS_2BITS 0b001
@@ -105,13 +159,30 @@
#define GICV5_IRS_IDR2_LPI BIT(5)
#define GICV5_IRS_IDR2_ID_BITS GENMASK(4, 0)
+#define GICV5_IRS_IST_L2SZ_SUPPORT_4KB(r) FIELD_GET(BIT(11), (r))
+#define GICV5_IRS_IST_L2SZ_SUPPORT_16KB(r) FIELD_GET(BIT(12), (r))
+#define GICV5_IRS_IST_L2SZ_SUPPORT_64KB(r) FIELD_GET(BIT(13), (r))
+
+#define GICV5_IRS_IDR3_VMT_LEVELS BIT(10)
+#define GICV5_IRS_IDR3_VM_ID_BITS GENMASK(9, 5)
+#define GICV5_IRS_IDR3_VMD_SZ GENMASK(4, 1)
+#define GICV5_IRS_IDR3_VMD BIT(0)
+
+#define GICV5_IRS_IDR4_VPE_ID_BITS GENMASK(9, 6)
+#define GICV5_IRS_IDR4_VPED_SZ GENMASK(5, 0)
+
#define GICV5_IRS_IDR5_SPI_RANGE GENMASK(24, 0)
#define GICV5_IRS_IDR6_SPI_IRS_RANGE GENMASK(24, 0)
#define GICV5_IRS_IDR7_SPI_BASE GENMASK(23, 0)
-#define GICV5_IRS_IST_L2SZ_SUPPORT_4KB(r) FIELD_GET(BIT(11), (r))
-#define GICV5_IRS_IST_L2SZ_SUPPORT_16KB(r) FIELD_GET(BIT(12), (r))
-#define GICV5_IRS_IST_L2SZ_SUPPORT_64KB(r) FIELD_GET(BIT(13), (r))
+#define GICV5_IRS_IIDR_PRODUCT_ID GENMASK(31, 20)
+#define GICV5_IRS_IIDR_VARIANT GENMASK(19, 16)
+#define GICV5_IRS_IIDR_REVISION GENMASK(15, 12)
+#define GICV5_IRS_IIDR_IMPLEMENTER GENMASK(11, 0)
+
+#define GICV5_IRS_AIDR_COMPONENT GENMASK(11, 8)
+#define GICV5_IRS_AIDR_ARCHMAJORREV GENMASK(7, 4)
+#define GICV5_IRS_AIDR_ARCHMINORREV GENMASK(3, 0)
#define GICV5_IRS_CR0_IDLE BIT(1)
#define GICV5_IRS_CR0_IRSEN BIT(0)
@@ -134,21 +205,39 @@
#define GICV5_IRS_SYNC_STATUSR_IDLE BIT(0)
-#define GICV5_IRS_SPI_STATUSR_V BIT(1)
-#define GICV5_IRS_SPI_STATUSR_IDLE BIT(0)
+#define GICV5_IRS_SPI_VMR_VIRT BIT_ULL(63)
+#define GICV5_IRS_SPI_VMR_VM_ID GENMASK_ULL(15, 0)
#define GICV5_IRS_SPI_SELR_ID GENMASK(23, 0)
+#define GICV5_IRS_SPI_DOMAINR_DOMAIN GENMASK(1, 0)
+
+#define GICV5_IRS_SPI_DOMAINR_DOMAIN_SECURE 0b00
+#define GICV5_IRS_SPI_DOMAINR_DOMAIN_NON_SECURE 0b01
+#define GICV5_IRS_SPI_DOMAINR_DOMAIN_EL3 0b10
+#define GICV5_IRS_SPI_DOMAINR_DOMAIN_REALM 0b11
+
+#define GICV5_IRS_SPI_RESAMPLER_ID GENMASK(23, 0)
+
#define GICV5_IRS_SPI_CFGR_TM BIT(0)
+#define GICV5_IRS_SPI_CFGR_TM_EDGE 0b0
+#define GICV5_IRS_SPI_CFGR_TM_LEVEL 0b1
+
+#define GICV5_IRS_SPI_STATUSR_V BIT(1)
+#define GICV5_IRS_SPI_STATUSR_IDLE BIT(0)
+
#define GICV5_IRS_PE_SELR_IAFFID GENMASK(15, 0)
+#define GICV5_IRS_PE_STATUSR_ONLINE BIT(2)
#define GICV5_IRS_PE_STATUSR_V BIT(1)
#define GICV5_IRS_PE_STATUSR_IDLE BIT(0)
#define GICV5_IRS_PE_CR0_DPS BIT(0)
-#define GICV5_IRS_IST_STATUSR_IDLE BIT(0)
+#define GICV5_IRS_IST_BASER_ADDR_MASK GENMASK_ULL(55, 6)
+#define GICV5_IRS_IST_BASER_VALID BIT_ULL(0)
+#define GICV5_IRS_IST_BASER_ADDR_SHIFT 6ULL
#define GICV5_IRS_IST_CFGR_STRUCTURE BIT(16)
#define GICV5_IRS_IST_CFGR_ISTSZ GENMASK(8, 7)
@@ -166,15 +255,111 @@
#define GICV5_IRS_IST_CFGR_L2SZ_16K 0b01
#define GICV5_IRS_IST_CFGR_L2SZ_64K 0b10
-#define GICV5_IRS_IST_BASER_ADDR_MASK GENMASK_ULL(55, 6)
-#define GICV5_IRS_IST_BASER_VALID BIT_ULL(0)
+#define GICV5_IRS_IST_STATUSR_IDLE BIT(0)
#define GICV5_IRS_MAP_L2_ISTR_ID GENMASK(23, 0)
+#define GICV5_IRS_VMT_BASER_ADDR GENMASK_ULL(55, 3)
+#define GICV5_IRS_VMT_BASER_ADDR_SHIFT 3ULL
+#define GICV5_IRS_VMT_BASER_VALID BIT_ULL(0)
+
+#define GICV5_IRS_VMT_CFGR_STRUCTURE_TWO_LEVEL 0b1
+#define GICV5_IRS_VMT_CFGR_STRUCTURE_LINEAR 0b0
+
+#define GICV5_IRS_VMT_CFGR_STRUCTURE BIT(16)
+#define GICV5_IRS_VMT_CFGR_VM_ID_BITS GENMASK(4, 0)
+
+#define GICV5_IRS_VMT_STATUSR_IDLE BIT(0)
+
+#define GICV5_IRS_VPE_SELR_S BIT_ULL(63)
+#define GICV5_IRS_VPE_SELR_VPE_ID GENMASK_ULL(47, 32)
+#define GICV5_IRS_VPE_SELR_VM_ID GENMASK_ULL(15, 0)
+
+#define GICV5_IRS_VPE_DBR_DBV BIT_ULL(63)
+#define GICV5_IRS_VPE_DBR_REQ_DB BIT_ULL(62)
+#define GICV5_IRS_VPE_DBR_DBPM GENMASK_ULL(36, 32)
+#define GICV5_IRS_VPE_DBR_INTID GENMASK_ULL(23, 0)
+
+#define GICV5_IRS_VPE_HPPIR_HPPIV BIT_ULL(32)
+#define GICV5_IRS_VPE_HPPIR_TYPE GENMASK_ULL(31, 29)
+#define GICV5_IRS_VPE_HPPIR_ID GENMASK_ULL(23, 0)
+
+#define GICV5_IRS_VPE_CR0_DPS BIT(0)
+
+#define GICV5_IRS_VPE_STATUSR_V BIT(1)
+#define GICV5_IRS_VPE_STATUSR_IDLE BIT(0)
+
+#define GICV5_IRS_VM_DBR_EN BIT_ULL(63)
+#define GICV5_IRS_VM_DBR_VPE_ID GENMASK_ULL(15, 0)
+
+#define GICV5_IRS_VM_SELR_VM_ID GENMASK(15, 0)
+
+#define GICV5_IRS_VM_STATUSR_V BIT(1)
+#define GICV5_IRS_VM_STATUSR_IDLE BIT(0)
+
+#define GICV5_IRS_VMAP_L2_VMTR_M BIT_ULL(63)
+#define GICV5_IRS_VMAP_L2_VMTR_VM_ID GENMASK_ULL(15, 0)
+
+#define GICV5_IRS_VMAP_VMR_M BIT_ULL(63)
+#define GICV5_IRS_VMAP_VMR_U BIT_ULL(62)
+#define GICV5_IRS_VMAP_VMR_VM_ID GENMASK_ULL(15, 0)
+
+#define GICV5_IRS_VMAP_VISTR_M BIT_ULL(63)
+#define GICV5_IRS_VMAP_VISTR_U BIT_ULL(62)
+#define GICV5_IRS_VMAP_VISTR_VM_ID GENMASK_ULL(47, 32)
+#define GICV5_IRS_VMAP_VISTR_TYPE GENMASK_ULL(31, 29)
+
+#define GICV5_IRS_VMAP_L2_VISTR_M BIT_ULL(63)
+#define GICV5_IRS_VMAP_L2_VISTR_VM_ID GENMASK_ULL(47, 32)
+#define GICV5_IRS_VMAP_L2_VISTR_TYPE GENMASK_ULL(31, 29)
+#define GICV5_IRS_VMAP_L2_VISTR_ID GENMASK_ULL(23, 0)
+
+#define GICV5_IRS_VMAP_VPER_M BIT_ULL(63)
+#define GICV5_IRS_VMAP_VPER_VM_ID GENMASK_ULL(47, 32)
+#define GICV5_IRS_VMAP_VPER_VPE_ID GENMASK_ULL(15, 0)
+
+#define GICV5_IRS_SAVE_VMR_VM_ID GENMASK_ULL(15, 0)
+#define GICV5_IRS_SAVE_VMR_Q BIT_ULL(62)
+#define GICV5_IRS_SAVE_VMR_S BIT_ULL(63)
+
+#define GICV5_IRS_SAVE_VM_STATUSR_IDLE BIT(0)
+#define GICV5_IRS_SAVE_VM_STATUSR_Q BIT(1)
+
+#define GICV5_IRS_MEC_IDR_MECIDSIZE GENMASK(3, 0)
+
+#define GICV5_IRS_MEC_MECID_R_MECID GENMASK(15, 0)
+
+#define GICV5_IRS_MPAM_IDR_HAS_MPAM_SP BIT(24)
+#define GICV5_IRS_MPAM_IDR_PMG_MAX GENMASK(23, 16)
+#define GICV5_IRS_MPAM_IDR_PARTID_MAX GENMASK(15, 0)
+
+#define GICV5_IRS_MPAM_PARTID_R_IDLE BIT(31)
+#define GICV5_IRS_MPAM_PARTID_R_MPAM_SP GENMASK(25, 24)
+#define GICV5_IRS_MPAM_PARTID_R_PMG GENMASK(23, 16)
+#define GICV5_IRS_MPAM_PARTID_R_PARTID GENMASK(15, 0)
+
+#define GICV5_IRS_SWERR_STATUSR_IMP_EC GENMASK_ULL(31, 24)
+#define GICV5_IRS_SWERR_STATUSR_EC GENMASK_ULL(23, 16)
+#define GICV5_IRS_SWERR_STATUSR_OF BIT_ULL(3)
+#define GICV5_IRS_SWERR_STATUSR_S1V BIT_ULL(2)
+#define GICV5_IRS_SWERR_STATUSR_S0V BIT_ULL(1)
+#define GICV5_IRS_SWERR_STATUSR_V BIT_ULL(0)
+
+#define GICV5_IRS_SWERR_SYNDROMER0_VIRTUAL BIT_ULL(63)
+#define GICV5_IRS_SWERR_SYNDROMER0_TYPE GENMASK_ULL(62, 60)
+#define GICV5_IRS_SWERR_SYNDROMER0_ID GENMASK_ULL(55, 32)
+#define GICV5_IRS_SWERR_SYNDROMER0_VM_ID GENMASK_ULL(15, 0)
+
+#define GICV5_IRS_SWERR_SYNDROMER1_ADDR GENMASK_ULL(55, 3)
+
#define GICV5_ISTL1E_VALID BIT_ULL(0)
+#define GICV5_IRS_ISTL1E_SIZE 8UL
#define GICV5_ISTL1E_L2_ADDR_MASK GENMASK_ULL(55, 12)
+#define GICV5_IRS_SETLPIR 0x0000
+#define GICV5_IRS_SETLPIR_ID GENMASK(23, 0)
+
/*
* ITS registers and tables structures
*/
@@ -296,6 +481,42 @@
#define GICV5_GSI_IWB_FRAME_ID GENMASK(28, 16)
#define GICV5_GSI_IWB_WIRE GENMASK(15, 0)
+/*
+ * CoreSight identification registers - ordered by increasing offset.
+ */
+#define GICV5_CORESIGHT_DEVARCH 0xffbc
+#define GICV5_CORESIGHT_PIDR4 0xffd0
+#define GICV5_CORESIGHT_PIDR5 0xffd4
+#define GICV5_CORESIGHT_PIDR6 0xffd8
+#define GICV5_CORESIGHT_PIDR7 0xffdc
+#define GICV5_CORESIGHT_PIDR0 0xffe0
+#define GICV5_CORESIGHT_PIDR1 0xffe4
+#define GICV5_CORESIGHT_PIDR2 0xffe8
+#define GICV5_CORESIGHT_PIDR3 0xffec
+#define GICV5_CORESIGHT_CIDR0 0xfff0
+#define GICV5_CORESIGHT_CIDR1 0xfff4
+#define GICV5_CORESIGHT_CIDR2 0xfff8
+#define GICV5_CORESIGHT_CIDR3 0xfffc
+
+#define GICV5_CORESIGHT_DEVARCH_VAL \
+ (FIELD_PREP(GENMASK(31, 21), 0x23b) | \
+ BIT(20) | \
+ 0x5a19)
+
+#define GICV5_CORESIGHT_PIDR4_JEP106_CONT 0x04
+#define GICV5_CORESIGHT_PIDR5_RES0 0x00
+#define GICV5_CORESIGHT_PIDR6_RES0 0x00
+#define GICV5_CORESIGHT_PIDR7_RES0 0x00
+#define GICV5_CORESIGHT_PIDR0_PART_0 0x4b
+#define GICV5_CORESIGHT_PIDR1_DES_0_PART_1 0xb0
+#define GICV5_CORESIGHT_PIDR2_DES_1 0x0b
+#define GICV5_CORESIGHT_PIDR3_REVAND_CMOD 0x00
+
+#define GICV5_CORESIGHT_CIDR0_VAL 0x0d
+#define GICV5_CORESIGHT_CIDR1_VAL 0xf0
+#define GICV5_CORESIGHT_CIDR2_VAL 0x05
+#define GICV5_CORESIGHT_CIDR3_VAL 0xb1
+
/*
* Global Data structures and functions
*/
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 05/49] arm64/sysreg: Add GICv5 GIC VDPEND encoding
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (3 preceding siblings ...)
2026-08-07 11:14 ` [PATCH v5 04/49] KVM: arm64: gic-v5: Define remaining IRS MMIO registers Sascha Bischoff
@ 2026-08-07 11:15 ` Sascha Bischoff
2026-08-07 11:15 ` [PATCH v5 06/49] arm64/sysreg: Update ICC_CR0_EL1 with LINK and LINK_IDLE fields Sascha Bischoff
` (43 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:15 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Add the encoding for the GIC VDPEND system instruction. This operates
on the virtual interrupt domain, and is used to make interrupts
pending for a VM.
This is part of enabling GICv5 KVM support, and is required for
injection of SPIs and LPIs.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/include/asm/sysreg.h | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
index 7aa08d59d4944..c840b37adb2fb 100644
--- a/arch/arm64/include/asm/sysreg.h
+++ b/arch/arm64/include/asm/sysreg.h
@@ -1040,7 +1040,7 @@
#define GCS_CAP(x) ((((unsigned long)x) & GCS_CAP_ADDR_MASK) | \
GCS_CAP_VALID_TOKEN)
/*
- * Definitions for GICv5 instructions
+ * Definitions for GICv5 instructions for the Current Domain
*/
#define GICV5_OP_GIC_CDAFF sys_insn(1, 0, 12, 1, 3)
#define GICV5_OP_GIC_CDDI sys_insn(1, 0, 12, 2, 0)
@@ -1105,6 +1105,17 @@
#define GICV5_GICR_CDNMIA_TYPE_MASK GENMASK_ULL(31, 29)
#define GICV5_GICR_CDNMIA_ID_MASK GENMASK_ULL(23, 0)
+/*
+ * Definitions for GICv5 instructions for the Virtual Domain
+ */
+#define GICV5_OP_GIC_VDPEND sys_insn(1, 4, 12, 1, 4)
+
+/* Shift and mask definitions for GIC VDPEND */
+#define GICV5_GIC_VDPEND_PENDING_MASK BIT_ULL(63)
+#define GICV5_GIC_VDPEND_VM_MASK GENMASK_ULL(47, 32)
+#define GICV5_GIC_VDPEND_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_VDPEND_ID_MASK GENMASK_ULL(23, 0)
+
#define gicr_insn(insn) read_sysreg_s(GICV5_OP_GICR_##insn)
#define gic_insn(v, insn) write_sysreg_s(v, GICV5_OP_GIC_##insn)
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 06/49] arm64/sysreg: Update ICC_CR0_EL1 with LINK and LINK_IDLE fields
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (4 preceding siblings ...)
2026-08-07 11:15 ` [PATCH v5 05/49] arm64/sysreg: Add GICv5 GIC VDPEND encoding Sascha Bischoff
@ 2026-08-07 11:15 ` Sascha Bischoff
2026-08-07 11:16 ` [PATCH v5 07/49] KVM: arm64: gic-v5: Cache host IRS ID registers Sascha Bischoff
` (42 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:15 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
These fields have been added to the architecture since this register
was added to the generator, and were hence missing.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/tools/sysreg | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index 7cb61aca3797f..03f5e1755a633 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -3736,7 +3736,9 @@ Sysreg ICC_CR0_EL1 3 1 12 0 1
Res0 63:39
Field 38 PID
Field 37:32 IPPT
-Res0 31:1
+Res0 31:3
+Field 2 LINK_IDLE
+Field 1 LINK
Field 0 EN
EndSysreg
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 07/49] KVM: arm64: gic-v5: Cache host IRS ID registers
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (5 preceding siblings ...)
2026-08-07 11:15 ` [PATCH v5 06/49] arm64/sysreg: Update ICC_CR0_EL1 with LINK and LINK_IDLE fields Sascha Bischoff
@ 2026-08-07 11:16 ` Sascha Bischoff
2026-08-07 11:16 ` [PATCH v5 08/49] KVM: arm64: gic-v5: Add VPE doorbell domain Sascha Bischoff
` (41 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:16 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
KVM needs access to the host IRS configuration frame to configure
virtual-machine resources. The frame ID registers describe the
capabilities available to KVM.
Cache IRS_IDR2, IRS_IDR3, and IRS_IDR4 rather than extracting their
individual fields into kvm_vgic_global_state. Accessor helpers derive
the required capabilities from the cached registers.
Use the cached IRS_IDR4 VPE capacity to limit the maximum number of
vCPUs, while retaining VGIC_V5_MAX_CPUS as KVM's own upper bound.
Keeping the raw register values lets KVM adjust the exposed
capabilities when needed, as is done for ICH_VTR_EL2, and avoids tying
future EL2 code to the layout of kvm_vgic_global_state.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-v5.c | 22 +++++++++-
include/kvm/arm_vgic.h | 75 +++++++++++++++++++++++++++++++++++
2 files changed, 95 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index d4789ff3e7402..41ca2098ecb73 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -11,6 +11,7 @@
#include "vgic.h"
#define ppi_caps kvm_vgic_global_state.vgic_v5_ppi_caps
+#define irs_caps kvm_vgic_global_state.vgic_v5_irs_caps
/*
* Not all PPIs are guaranteed to be implemented for GICv5. Deterermine which
@@ -34,6 +35,21 @@ static void vgic_v5_get_implemented_ppis(void)
__assign_bit(GICV5_ARCH_PPI_PMUIRQ, ppi_caps.impl_ppi_mask, system_supports_pmuv3());
}
+static u32 irs_readl_relaxed(const u32 reg_offset)
+{
+ return readl_relaxed(irs_caps.irs_base + reg_offset);
+}
+
+static void vgic_v5_irs_cache_id_regs(const struct gic_kvm_info *info)
+{
+ irs_caps.irs_base = info->gicv5_irs.base;
+ irs_caps.non_coherent = info->gicv5_irs.non_coherent;
+
+ irs_caps.idr2 = irs_readl_relaxed(GICV5_IRS_IDR2);
+ irs_caps.idr3 = irs_readl_relaxed(GICV5_IRS_IDR3);
+ irs_caps.idr4 = irs_readl_relaxed(GICV5_IRS_IDR4);
+}
+
/*
* Probe for a vGICv5 compatible interrupt controller, returning 0 on success.
*/
@@ -61,10 +77,12 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
goto skip_v5;
}
- kvm_vgic_global_state.max_gic_vcpus = VGIC_V5_MAX_CPUS;
-
+ vgic_v5_irs_cache_id_regs(info);
vgic_v5_get_implemented_ppis();
+ kvm_vgic_global_state.max_gic_vcpus = min(vgic_v5_irs_max_vpes(&irs_caps),
+ VGIC_V5_MAX_CPUS);
+
ret = kvm_register_vgic_device(KVM_DEV_TYPE_ARM_VGIC_V5);
if (ret) {
kvm_err("Cannot register GICv5 KVM device.\n");
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index cefddc9c621de..62ecb525557b6 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -130,6 +130,19 @@ enum vgic_type {
VGIC_V5, /* Newer, fancier GICv5 */
};
+struct vgic_v5_irs_caps {
+ /* Base address of the host IRS's CONFIG_FRAME */
+ void __iomem *irs_base;
+
+ /* Raw host IRS ID registers */
+ u32 idr2;
+ u32 idr3;
+ u32 idr4;
+
+ /* Is the IRS coherent with us, or not? */
+ bool non_coherent;
+};
+
/* same for all guests, as depending only on the _host's_ GIC model */
struct vgic_global {
/* type of the host GIC */
@@ -182,6 +195,9 @@ struct vgic_global {
struct {
DECLARE_BITMAP(impl_ppi_mask, VGIC_V5_NR_PRIVATE_IRQS);
} vgic_v5_ppi_caps;
+
+ /* GICv5 IRS capabilities */
+ struct vgic_v5_irs_caps vgic_v5_irs_caps;
};
extern struct vgic_global kvm_vgic_global_state;
@@ -627,6 +643,65 @@ int vgic_v4_load(struct kvm_vcpu *vcpu);
void vgic_v4_commit(struct kvm_vcpu *vcpu);
int vgic_v4_put(struct kvm_vcpu *vcpu);
+static inline u8 vgic_v5_irs_ist_id_bits(const struct vgic_v5_irs_caps *caps)
+{
+ return FIELD_GET(GICV5_IRS_IDR2_ID_BITS, caps->idr2);
+}
+
+static inline u8 vgic_v5_irs_min_lpi_id_bits(const struct vgic_v5_irs_caps *caps)
+{
+ return FIELD_GET(GICV5_IRS_IDR2_MIN_LPI_ID_BITS, caps->idr2);
+}
+
+static inline bool vgic_v5_irs_ist_levels(const struct vgic_v5_irs_caps *caps)
+{
+ return !!(caps->idr2 & GICV5_IRS_IDR2_IST_LEVELS);
+}
+
+static inline u8 vgic_v5_irs_ist_l2sz(const struct vgic_v5_irs_caps *caps)
+{
+ return FIELD_GET(GICV5_IRS_IDR2_IST_L2SZ, caps->idr2);
+}
+
+static inline bool vgic_v5_irs_istmd(const struct vgic_v5_irs_caps *caps)
+{
+ return !!(caps->idr2 & GICV5_IRS_IDR2_ISTMD);
+}
+
+static inline u8 vgic_v5_irs_istmd_sz(const struct vgic_v5_irs_caps *caps)
+{
+ return FIELD_GET(GICV5_IRS_IDR2_ISTMD_SZ, caps->idr2);
+}
+
+static inline u32 vgic_v5_irs_max_vms(const struct vgic_v5_irs_caps *caps)
+{
+ return BIT(FIELD_GET(GICV5_IRS_IDR3_VM_ID_BITS, caps->idr3));
+}
+
+static inline bool vgic_v5_irs_two_level_vmt_support(const struct vgic_v5_irs_caps *caps)
+{
+ return !!(caps->idr3 & GICV5_IRS_IDR3_VMT_LEVELS);
+}
+
+static inline u16 vgic_v5_irs_vmd_size(const struct vgic_v5_irs_caps *caps)
+{
+ if (!(caps->idr3 & GICV5_IRS_IDR3_VMD))
+ return 0;
+
+ return BIT(FIELD_GET(GICV5_IRS_IDR3_VMD_SZ, caps->idr3));
+}
+
+static inline u16 vgic_v5_irs_vped_size(const struct vgic_v5_irs_caps *caps)
+{
+ return BIT(FIELD_GET(GICV5_IRS_IDR4_VPED_SZ, caps->idr4));
+}
+
+static inline u32 vgic_v5_irs_max_vpes(const struct vgic_v5_irs_caps *caps)
+{
+ /* Field stores VPE_ID_BITS - 1 */
+ return BIT(FIELD_GET(GICV5_IRS_IDR4_VPE_ID_BITS, caps->idr4) + 1);
+}
+
int vgic_v5_finalize_ppi_state(struct kvm *kvm);
bool vgic_v5_ppi_queue_irq_unlock(struct kvm *kvm, struct vgic_irq *irq,
unsigned long flags);
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 08/49] KVM: arm64: gic-v5: Add VPE doorbell domain
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (6 preceding siblings ...)
2026-08-07 11:16 ` [PATCH v5 07/49] KVM: arm64: gic-v5: Cache host IRS ID registers Sascha Bischoff
@ 2026-08-07 11:16 ` Sascha Bischoff
2026-08-07 11:17 ` [PATCH v5 09/49] KVM: arm64: gic-v5: Create and manage VM and VPE tables Sascha Bischoff
` (40 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:16 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
VPE doorbells allow the GICv5 hardware to notify KVM when an SPI or
LPI can be signalled to a non-resident VPE. This provides the
mechanism used to wake blocked vcpus once the hardware determines that
the interrupt is eligible to be delivered.
Add support for a per-VM VPE doorbell irq domain. The domain is
created under the GICv5 LPI domain, with one doorbell allocated per
VPE. Store the allocated doorbell base in the VM's GICv5 state so that
later patches can request per-vcpu doorbell IRQs and use them for IRS
commands and wakeups.
Add the per-VPE doorbell state to the GICv5 CPU interface state. The
doorbell IRQ number is populated when the IRQs are requested, and the
db_fired state is used by later patches once doorbell delivery is
wired up.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-init.c | 18 ++--
arch/arm64/kvm/vgic/vgic-v5.c | 144 +++++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic.h | 1 +
include/kvm/arm_vgic.h | 4 +
include/linux/irqchip/arm-gic-v5.h | 2 +
5 files changed, 163 insertions(+), 6 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index 907057881b26a..625d352756fcf 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -492,16 +492,22 @@ static void kvm_vgic_dist_destroy(struct kvm *kvm)
dist->nr_spis = 0;
dist->vgic_dist_base = VGIC_ADDR_UNDEF;
- if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3) {
+ switch (dist->vgic_model) {
+ case KVM_DEV_TYPE_ARM_VGIC_V2:
+ dist->vgic_cpu_base = VGIC_ADDR_UNDEF;
+ break;
+ case KVM_DEV_TYPE_ARM_VGIC_V3:
list_for_each_entry_safe(rdreg, next, &dist->rd_regions, list)
vgic_v3_free_redist_region(kvm, rdreg);
INIT_LIST_HEAD(&dist->rd_regions);
- } else {
- dist->vgic_cpu_base = VGIC_ADDR_UNDEF;
- }
- if (vgic_supports_direct_irqs(kvm))
- vgic_v4_teardown(kvm);
+ if (vgic_supports_direct_irqs(kvm))
+ vgic_v4_teardown(kvm);
+ break;
+ case KVM_DEV_TYPE_ARM_VGIC_V5:
+ vgic_v5_teardown(kvm);
+ break;
+ }
xa_destroy(&dist->lpi_xa);
}
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 41ca2098ecb73..752329fc3d566 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -7,6 +7,7 @@
#include <linux/bitops.h>
#include <linux/irqchip/arm-vgic-info.h>
+#include <linux/irqdomain.h>
#include "vgic.h"
@@ -128,6 +129,139 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
return 0;
}
+/*
+ * This set of irq_chip functions is specific for doorbells.
+ */
+static const struct irq_chip vgic_v5_db_irq_chip = {
+ .name = "GICv5-DB",
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_get_irqchip_state = irq_chip_get_parent_state,
+ .irq_set_irqchip_state = irq_chip_set_parent_state,
+ .flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_SKIP_SET_WAKE |
+ IRQCHIP_MASK_ON_SUSPEND,
+};
+
+static void vgic_v5_irq_db_domain_free(struct irq_domain *domain,
+ unsigned int virq, unsigned int nr_irqs)
+{
+ int i;
+
+ for (i = 0; i < nr_irqs; i++) {
+ struct irq_data *d = irq_domain_get_irq_data(domain, virq + i);
+
+ irq_set_handler(virq + i, NULL);
+ irq_domain_reset_irq_data(d);
+ }
+
+ irq_domain_free_irqs_parent(domain, virq, nr_irqs);
+}
+
+static int vgic_v5_irq_db_domain_alloc(struct irq_domain *domain,
+ unsigned int virq, unsigned int nr_irqs,
+ void *arg)
+{
+ const struct irq_chip *chip = &vgic_v5_db_irq_chip;
+ struct vgic_v5_vm *vm = arg;
+ struct irq_data *irqd;
+ int ret;
+
+ if (!vm) {
+ kvm_err("invalid parameter for doorbell irq allocation\n");
+ return -EINVAL;
+ }
+
+ ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, NULL);
+ if (ret)
+ return ret;
+
+ for (int i = 0; i < nr_irqs; i++) {
+ irq_domain_set_hwirq_and_chip(domain, virq + i, i, chip,
+ domain->host_data);
+ irqd = irq_desc_get_irq_data(irq_to_desc(virq + i));
+ irqd_set_single_target(irqd);
+ }
+
+ return 0;
+}
+
+static const struct irq_domain_ops vgic_v5_irq_db_domain_ops = {
+ .alloc = vgic_v5_irq_db_domain_alloc,
+ .free = vgic_v5_irq_db_domain_free,
+};
+
+static int vgic_v5_create_per_vm_domain(struct kvm *kvm)
+{
+ struct vgic_v5_vm *vm = &kvm->arch.vgic.gicv5_vm;
+ int nr_vcpus = atomic_read(&kvm->online_vcpus);
+ int id = task_pid_nr(current);
+ int ret, db_virq = 0;
+
+ if (!gicv5_global_data.lpi_domain) {
+ kvm_err("LPI domain uninitialized, can't set up KVM Doorbells\n");
+ return -ENODEV;
+ }
+
+ vm->fwnode = irq_domain_alloc_named_id_fwnode("GICv5-vpe-db", id);
+ if (!vm->fwnode)
+ return -ENOMEM;
+
+ /*
+ * KVM per-VM VPE DB domain; child of LPI domain; only ever handles
+ * doorbells. We know how many doorbells we have, and therefore we
+ * create a linear domain.
+ */
+ vm->domain = irq_domain_create_hierarchy(gicv5_global_data.lpi_domain,
+ 0, nr_vcpus, vm->fwnode,
+ &vgic_v5_irq_db_domain_ops, vm);
+ if (!vm->domain) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ db_virq = irq_domain_alloc_irqs(vm->domain, nr_vcpus, NUMA_NO_NODE, vm);
+ if (db_virq <= 0) {
+ ret = db_virq;
+ goto err;
+ }
+
+ kvm->arch.vgic.gicv5_vm.vpe_db_base = db_virq;
+
+ return 0;
+
+err:
+ if (db_virq > 0)
+ irq_domain_free_irqs(db_virq, nr_vcpus);
+ if (vm->domain)
+ irq_domain_remove(vm->domain);
+ if (vm->fwnode)
+ irq_domain_free_fwnode(vm->fwnode);
+
+ kvm->arch.vgic.gicv5_vm.vpe_db_base = 0;
+ vm->domain = NULL;
+ vm->fwnode = NULL;
+
+ return ret;
+}
+
+static void vgic_v5_teardown_per_vm_domain(struct vgic_v5_vm *vm)
+{
+ if (!vm->domain)
+ return;
+
+ if (vm->vpe_db_base) {
+ irq_domain_free_irqs(vm->vpe_db_base, vm->domain->revmap_size);
+ vm->vpe_db_base = 0;
+ }
+
+ irq_domain_remove(vm->domain);
+ irq_domain_free_fwnode(vm->fwnode);
+ vm->domain = NULL;
+ vm->fwnode = NULL;
+}
+
void vgic_v5_reset(struct kvm_vcpu *vcpu)
{
/*
@@ -143,10 +277,16 @@ void vgic_v5_reset(struct kvm_vcpu *vcpu)
vcpu->arch.vgic_cpu.num_pri_bits = 5;
}
+void vgic_v5_teardown(struct kvm *kvm)
+{
+ vgic_v5_teardown_per_vm_domain(&kvm->arch.vgic.gicv5_vm);
+}
+
int vgic_v5_init(struct kvm *kvm)
{
struct kvm_vcpu *vcpu;
unsigned long idx;
+ int ret;
if (vgic_initialized(kvm))
return 0;
@@ -158,6 +298,10 @@ int vgic_v5_init(struct kvm *kvm)
}
}
+ ret = vgic_v5_create_per_vm_domain(kvm);
+ if (ret)
+ return ret;
+
/* We only allow userspace to drive the SW_PPI, if it is implemented. */
bitmap_zero(kvm->arch.vgic.gicv5_vm.userspace_ppis,
VGIC_V5_NR_PRIVATE_IRQS);
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index f45f7e3ec4d6e..f2f5fdc3211d7 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -366,6 +366,7 @@ void vgic_debug_destroy(struct kvm *kvm);
int vgic_v5_probe(const struct gic_kvm_info *info);
void vgic_v5_reset(struct kvm_vcpu *vcpu);
int vgic_v5_init(struct kvm *kvm);
+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);
bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu);
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 62ecb525557b6..6e5aa248f3cfd 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -379,6 +379,10 @@ struct vgic_v5_vm {
* convenient way to do that).
*/
DECLARE_BITMAP(vgic_ppi_hmr, VGIC_V5_NR_PRIVATE_IRQS);
+
+ struct fwnode_handle *fwnode;
+ struct irq_domain *domain;
+ int vpe_db_base;
};
struct vgic_dist {
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index 83cfcd056b250..21c8a69f99bb6 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -613,6 +613,8 @@ void gicv5_irs_syncr(void);
/* Embedded in kvm.arch */
struct gicv5_vpe {
+ int db;
+ bool db_fired;
bool resident;
};
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 09/49] KVM: arm64: gic-v5: Create and manage VM and VPE tables
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (7 preceding siblings ...)
2026-08-07 11:16 ` [PATCH v5 08/49] KVM: arm64: gic-v5: Add VPE doorbell domain Sascha Bischoff
@ 2026-08-07 11:17 ` Sascha Bischoff
2026-08-07 11:17 ` [PATCH v5 10/49] KVM: arm64: gic-v5: Introduce guest IST alloc and management Sascha Bischoff
` (39 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:17 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
GICv5 uses a set of in-memory tables to track and manage VM state.
These must be allocated by the hypervisor and provided to the IRS.
The VMT (Virtual Machine Table) is a linear or two-level table
comprising VMT Entries (VMTEs). Each VMTE describes the state for a
single VM. This state includes things such as the SPI and LPI IST
configuration (coming in a future commit), an implementation-defined
VM Descriptor, and a VPE Table (VPET).
The VPET contains one entry per possible VPE ID belonging to a VM. It
is used to mark a VPE as valid and provide the address of an
implementation-defined VPE Descriptor (VPED), which the hardware uses
to track and manage VPE state.
Allocate each VM's VPEDs as a single dense array indexed by vcpu_idx,
while the VPET remains indexed by the userspace-provided vcpu_id. This
keeps VPED storage proportional to the number of vCPUs even when their
IDs are sparse.
The VMT and VPET are shared with the IRS. On systems with a
non-coherent IRS, cache maintenance operates at cache-line
granularity, while multiple entries can occupy the same cache line.
Use a common lock for CPU accesses to these tables and IRS command
processing so that writing back one entry cannot overwrite an IRS
update to a neighbouring entry.
The implementation-defined VMD and VPED storage is also visible to the
IRS. Round these allocations up to whole cache lines to prevent cache
maintenance from corrupting unrelated slab objects. Initialise the
storage before publishing its addresses to the IRS.
This commit adds support for allocating the VMT and its descriptor
backing state, and for managing VMTEs. The VMTEs can be initialised or
released for reuse. VM IDs are allocated with an IDA, while an XArray
tracks the host-side allocations associated with populated VMTEs.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/Makefile | 2 +-
arch/arm64/kvm/vgic/vgic-init.c | 2 +
arch/arm64/kvm/vgic/vgic-v5-tables.c | 681 +++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic-v5-tables.h | 100 ++++
arch/arm64/kvm/vgic/vgic-v5.c | 19 +
drivers/irqchip/irq-gic-v5-irs.c | 12 +-
include/kvm/arm_vgic.h | 4 +
include/linux/irqchip/arm-gic-v5.h | 14 +-
8 files changed, 824 insertions(+), 10 deletions(-)
create mode 100644 arch/arm64/kvm/vgic/vgic-v5-tables.c
create mode 100644 arch/arm64/kvm/vgic/vgic-v5-tables.h
diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile
index 59612d2f277c1..431de9b145ca1 100644
--- a/arch/arm64/kvm/Makefile
+++ b/arch/arm64/kvm/Makefile
@@ -24,7 +24,7 @@ kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \
vgic/vgic-mmio.o vgic/vgic-mmio-v2.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.o vgic/vgic-v5-tables.o
kvm-$(CONFIG_HW_PERF_EVENTS) += pmu-emul.o pmu.o
kvm-$(CONFIG_ARM64_PTR_AUTH) += pauth.o
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index 625d352756fcf..079a57c2b18f6 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -154,6 +154,8 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
case KVM_DEV_TYPE_ARM_VGIC_V3:
INIT_LIST_HEAD(&kvm->arch.vgic.rd_regions);
break;
+ case KVM_DEV_TYPE_ARM_VGIC_V5:
+ kvm->arch.vgic.gicv5_vm.vm_id = VGIC_V5_VM_ID_INVAL;
}
/*
diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.c b/arch/arm64/kvm/vgic/vgic-v5-tables.c
new file mode 100644
index 0000000000000..7252d48431a5a
--- /dev/null
+++ b/arch/arm64/kvm/vgic/vgic-v5-tables.c
@@ -0,0 +1,681 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025, 2026 Arm Ltd.
+ */
+
+#include <kvm/arm_vgic.h>
+#include <linux/dma-mapping.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/mutex.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/xarray.h>
+#include <asm/kvm_mmu.h>
+
+#include "vgic.h"
+#include "vgic-v5-tables.h"
+
+#define irs_caps kvm_vgic_global_state.vgic_v5_irs_caps
+
+static struct vgic_v5_vmt *vmt_info;
+
+/* Serialises IRS MMIO interaction (commands) and accesses to IRS tables. */
+DEFINE_RAW_SPINLOCK(vgic_v5_irs_lock);
+
+/* Serialises lazy installation of shared second-level VMTs. */
+static DEFINE_MUTEX(vmt_l2_lock);
+
+static DEFINE_XARRAY(vm_info);
+
+/* Level 1 Virtual Machine Table Entry */
+#define GICV5_VMTEL1E_VALID BIT_ULL(0)
+/* Note that there is no shift for the address by design */
+#define GICV5_VMTEL1E_L2_ADDR GENMASK(51, 12)
+
+#define GICV5_VMTEL2E_SIZE 32ULL
+/* An L2 table (two-level VMT) is ALWAYS 4kB! */
+#define GICV5_VMT_L2_TABLE_SIZE 4096ULL
+#define GICV5_VMT_L2_TABLE_ENTRIES (GICV5_VMT_L2_TABLE_SIZE / GICV5_VMTEL2E_SIZE)
+
+/*
+ * As the L2 VMTE is a large data structure, we are splitting it into 4 parts.
+ * We only mask and shift WITHIN each part for simplicity.
+ */
+/* First 64-bit chunk */
+#define GICV5_VMTEL2E_VALID BIT_ULL(0)
+#define GICV5_VMTEL2E_VMD_ADDR_SHIFT 3ULL
+#define GICV5_VMTEL2E_VMD_ADDR GENMASK_ULL(55, 3)
+/* Second 64-bit chunk */
+#define GICV5_VMTEL2E_VPET_ADDR_SHIFT 3ULL
+#define GICV5_VMTEL2E_VPET_ADDR GENMASK_ULL(55, 3)
+#define GICV5_VMTEL2E_VPE_ID_BITS GENMASK_ULL(63, 59)
+/* Third & fourth 64-bit chunks (the encodings are the same for each) */
+#define GICV5_VMTEL2E_IST_VALID BIT_ULL(0)
+#define GICV5_VMTEL2E_IST_L2SZ GENMASK_ULL(2, 1)
+#define GICV5_VMTEL2E_IST_ADDR_SHIFT 6ULL
+#define GICV5_VMTEL2E_IST_ADDR GENMASK_ULL(55, 6)
+#define GICV5_VMTEL2E_IST_ISTSZ GENMASK_ULL(57, 56)
+#define GICV5_VMTEL2E_IST_STRUCTURE BIT_ULL(58)
+#define GICV5_VMTEL2E_IST_ID_BITS GENMASK_ULL(63, 59)
+
+/* Virtual PE Table Entry */
+#define GICV5_VPE_VALID BIT_ULL(0)
+/* Note that there is no shift for the address by design. */
+#define GICV5_VPED_ADDR_SHIFT 3ULL
+#define GICV5_VPED_ADDR GENMASK_ULL(55, 3)
+
+/*
+ * Our IRS might be coherent or non-coherent. If coherent, we can just emit a
+ * DSB to ensure that we're in sync. However, when non-coherent, we need to
+ * manage our cached data explicitly.
+ *
+ * This helper is used to handle both coherent and non-coherent IRSes, and
+ * handles all combinations of cleaning and invalidating to the PoC.
+ */
+static void vgic_v5_clean_inval(void *va, size_t size)
+{
+ unsigned long base = (unsigned long)va;
+
+ dsb(ishst);
+
+ if (kvm_vgic_global_state.vgic_v5_irs_caps.non_coherent)
+ dcache_clean_inval_poc(base, base + size);
+}
+
+/*
+ * Create a linear VM Table. Directly using the number of entries supplied as
+ * the size of an L2 VMTE (32 bytes) guarantees that our allocation is aligned per
+ * the GICv5 requirements for the IRS_VMT_BASER.
+ */
+static int vgic_v5_alloc_vmt_linear(unsigned int num_entries)
+{
+ vmt_info->linear.vmt_base = kzalloc_objs(*vmt_info->linear.vmt_base,
+ num_entries);
+ if (!vmt_info->linear.vmt_base)
+ return -ENOMEM;
+
+ vgic_v5_clean_inval(vmt_info->linear.vmt_base,
+ num_entries * sizeof(struct vmtl2_entry));
+
+ return 0;
+}
+
+/*
+ * Allocate the first level of a two-level VM table. The second-level VM tables
+ * are allocated on demand (by vgic_v5_alloc_l2_vmt()).
+ */
+static int vgic_v5_alloc_vmt_two_level(unsigned int num_entries)
+{
+ /*
+ * Each L2 VMT array is always 4k-sized (covering 128 VMs). This is
+ * mandated by the GICv5 specification (GICv5 EAC0 Specification rule
+ * D_LSPBK). Hence, round up the number of entries to be at least 128
+ * (or the next highest power of two as we give the HW the number of VM
+ * ID bits).
+ */
+ if (num_entries < GICV5_VMT_L2_TABLE_ENTRIES)
+ num_entries = GICV5_VMT_L2_TABLE_ENTRIES;
+ num_entries = roundup_pow_of_two(num_entries);
+
+ vmt_info->l2.num_l1_ents = (num_entries / GICV5_VMT_L2_TABLE_ENTRIES);
+ vmt_info->l2.vmt_base = kzalloc_objs(*vmt_info->l2.vmt_base,
+ vmt_info->l2.num_l1_ents);
+ if (!vmt_info->l2.vmt_base)
+ return -ENOMEM;
+
+ vmt_info->l2.l2ptrs = kzalloc_objs(*vmt_info->l2.l2ptrs,
+ vmt_info->l2.num_l1_ents,
+ GFP_KERNEL);
+ if (!vmt_info->l2.l2ptrs) {
+ kfree(vmt_info->l2.vmt_base);
+ return -ENOMEM;
+ }
+
+ vgic_v5_clean_inval(vmt_info->l2.vmt_base,
+ vmt_info->l2.num_l1_ents * sizeof(vmtl1_entry));
+
+ return 0;
+}
+
+/*
+ * Allocate a second level VMT, if required. This can be called eagerly, and
+ * will only perform the allocation if required.
+ */
+static int vgic_v5_alloc_l2_vmt(struct kvm *kvm)
+{
+ struct kvm_vcpu *vcpu0 = kvm_get_vcpu(kvm, 0);
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ enum gicv5_vcpu_cmd cmd = VMT_L2_MAP;
+ struct vmtl2_entry *l2_table;
+ unsigned int l1_index;
+ int ret;
+
+ /* Nothing to do if we have linear tables! */
+ if (!vmt_info->two_level)
+ return 0;
+
+ if (vm_id == VGIC_V5_VM_ID_INVAL)
+ return -EINVAL;
+
+ /*
+ * We have 4k-sized L2 tables - this is mandated by the spec for
+ * two-level VMTs (GICv5 EAC0 Specification rule D_LSPBK). This means
+ * that we have 128 entries per L1 VMTE.
+ */
+ l1_index = vm_id / GICV5_VMT_L2_TABLE_ENTRIES;
+
+ guard(mutex)(&vmt_l2_lock);
+
+ /* Already valid? Great! */
+ if (vmt_info->l2.l2ptrs[l1_index])
+ return 0;
+
+ l2_table = kzalloc_objs(*l2_table, GICV5_VMT_L2_TABLE_ENTRIES);
+ if (!l2_table)
+ return -ENOMEM;
+
+ /* The VMT is shared between all VMs. */
+ scoped_guard(raw_spinlock_irqsave, &vgic_v5_irs_lock) {
+ vgic_v5_clean_inval(l2_table, GICV5_VMT_L2_TABLE_SIZE);
+ vgic_v5_clean_inval(vmt_info->l2.vmt_base + l1_index,
+ sizeof(vmtl1_entry));
+
+ WRITE_ONCE(vmt_info->l2.vmt_base[l1_index],
+ cpu_to_le64(virt_to_phys(l2_table)));
+
+ vgic_v5_clean_inval(vmt_info->l2.vmt_base + l1_index,
+ sizeof(vmtl1_entry));
+
+ }
+
+ /*
+ * VMAP in the L2 VMT via the IRS. We use any of the VM's CPUs as a
+ * conduit for interacting with the host's IRS. In the current case,
+ * this lets us resolve the VM ID to pass to the hardware.
+ */
+ ret = irq_set_vcpu_affinity(vgic_v5_vpe_db(vcpu0), &cmd);
+
+ /* We've failed to make the L2 VMT valid - things are very broken! */
+ if (ret) {
+ scoped_guard(raw_spinlock_irqsave, &vgic_v5_irs_lock) {
+ /* Remove the pointer from L1 table */
+ WRITE_ONCE(vmt_info->l2.vmt_base[l1_index], 0);
+
+ vgic_v5_clean_inval(vmt_info->l2.vmt_base + l1_index,
+ sizeof(vmtl1_entry));
+ }
+
+ kfree(l2_table);
+ return ret;
+ }
+
+ vmt_info->l2.l2ptrs[l1_index] = l2_table;
+
+ return 0;
+}
+
+/*
+ * Allocate the top-level VMT. This can either be linear or two-level.
+ */
+int vgic_v5_vmt_allocate(unsigned int max_vpes)
+{
+ int ret;
+
+ /* Allocate the tracking structure */
+ vmt_info = kzalloc_obj(*vmt_info, GFP_KERNEL);
+ if (!vmt_info)
+ return -ENOMEM;
+
+ ida_init(&vmt_info->vm_id_ida);
+ vmt_info->max_vpes = max_vpes;
+ vmt_info->vmd_size = vgic_v5_irs_vmd_size(&irs_caps);
+ vmt_info->vped_size = vgic_v5_irs_vped_size(&irs_caps);
+ vmt_info->two_level = vgic_v5_irs_two_level_vmt_support(&irs_caps);
+ vmt_info->num_entries = vgic_v5_irs_max_vms(&irs_caps);
+
+ if (vmt_info->two_level)
+ ret = vgic_v5_alloc_vmt_two_level(vmt_info->num_entries);
+ else
+ ret = vgic_v5_alloc_vmt_linear(vmt_info->num_entries);
+
+ /* If anything failed, free our tracking structure before returning */
+ if (ret) {
+ kfree(vmt_info);
+ vmt_info = NULL;
+ }
+
+ return ret;
+}
+
+/*
+ * Free the VMT and associated tracking structures. This isn't strictly expected
+ * to be called in general operation, but instead exists for completeness.
+ */
+int vgic_v5_vmt_free(void)
+{
+ if (!vmt_info)
+ return 0;
+
+ if (!vmt_info->two_level) {
+ kfree(vmt_info->linear.vmt_base);
+ } else {
+ /* Free the L2 tables; kfree(NULL) is safe */
+ for (int i = 0; i < vmt_info->l2.num_l1_ents; ++i)
+ kfree(vmt_info->l2.l2ptrs[i]);
+ kfree(vmt_info->l2.l2ptrs);
+
+ /* And now free the L1 table */
+ kfree(vmt_info->l2.vmt_base);
+ }
+
+ ida_destroy(&vmt_info->vm_id_ida);
+ kfree(vmt_info);
+ vmt_info = NULL;
+
+ return 0;
+}
+
+/*
+ * Look up a VMT Entry by VM ID.
+ */
+static struct vmtl2_entry *vgic_v5_get_l2_vmte(u32 vm_id)
+{
+ unsigned int l1_index, l2_index;
+ struct vmtl2_entry *l2_table;
+
+ if (vm_id == VGIC_V5_VM_ID_INVAL)
+ return ERR_PTR(-EINVAL);
+
+ if (!vmt_info->two_level)
+ return &vmt_info->linear.vmt_base[vm_id];
+
+ l1_index = vm_id / GICV5_VMT_L2_TABLE_ENTRIES;
+ l2_index = vm_id % GICV5_VMT_L2_TABLE_ENTRIES;
+
+ if (l1_index >= vmt_info->l2.num_l1_ents)
+ return ERR_PTR(-E2BIG);
+
+ if (!vmt_info->l2.l2ptrs[l1_index])
+ return ERR_PTR(-EINVAL);
+
+ l2_table = vmt_info->l2.l2ptrs[l1_index];
+ return &l2_table[l2_index];
+}
+
+/*
+ * Zero a VMT Entry, and flush & invalidate to the PoC, if required.
+ */
+static int vgic_v5_reset_vmte(struct kvm *kvm)
+{
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vmtl2_entry *vmte;
+
+ vmte = vgic_v5_get_l2_vmte(vm_id);
+ if (IS_ERR(vmte))
+ return PTR_ERR(vmte);
+
+ scoped_guard(raw_spinlock_irqsave, &vgic_v5_irs_lock) {
+ /*
+ * The VMT is normal memory shared with the IRS. Invalidate before
+ * rewriting the entry so that cacheline-granular maintenance cannot
+ * later push stale data for neighbouring IRS-visible state back to
+ * memory.
+ */
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+
+ /*
+ * Prevent the compiler from eliding the individual VMTE
+ * stores. Ordering and visibility to the IRS are provided by the
+ * surrounding cache maintenance and command protocol, not by
+ * WRITE_ONCE().
+ *
+ * The same compiler-access constraint applies to READ_ONCE() users in
+ * this file: when inspecting IRS-visible table entries, read the field
+ * exactly once and prevent the compiler from reusing, merging or
+ * tearing the access. Coherency and freshness for non-coherent IRSes
+ * still come from the surrounding cache maintenance.
+ */
+ WRITE_ONCE(vmte->val[0], cpu_to_le64(0ULL));
+ WRITE_ONCE(vmte->val[1], cpu_to_le64(0ULL));
+ WRITE_ONCE(vmte->val[2], cpu_to_le64(0ULL));
+ WRITE_ONCE(vmte->val[3], cpu_to_le64(0ULL));
+
+ /* And make our write visible to the IRS (if non-coherent) */
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+ }
+
+ return 0;
+}
+
+/*
+ * Use the IDA to allocate a new VM ID, and track it in the gicv5_vm data
+ * structure. If we're out of VM IDs, the IDA catches that, and we return the
+ * error (-ENOSPC). If we've previously allocated a VM ID, we catch that too and
+ * return -EBUSY.
+ */
+int vgic_v5_allocate_vm_id(struct kvm *kvm)
+{
+ int id;
+
+ if (kvm->arch.vgic.gicv5_vm.vm_id != VGIC_V5_VM_ID_INVAL)
+ return -EBUSY;
+
+ id = ida_alloc_max(&vmt_info->vm_id_ida, vmt_info->num_entries - 1u,
+ GFP_KERNEL);
+ if (id < 0)
+ return id;
+
+ kvm->arch.vgic.gicv5_vm.vm_id = id;
+
+ return 0;
+}
+
+/*
+ * Release the VM ID to allow it to be reallocated in the future.
+ */
+void vgic_v5_release_vm_id(struct kvm *kvm)
+{
+ if (kvm->arch.vgic.gicv5_vm.vm_id == VGIC_V5_VM_ID_INVAL)
+ return;
+
+ ida_free(&vmt_info->vm_id_ida, kvm->arch.vgic.gicv5_vm.vm_id);
+ kvm->arch.vgic.gicv5_vm.vm_id = VGIC_V5_VM_ID_INVAL;
+}
+
+/*
+ * Initialise an entry in the VMT based on the index of the VM.
+ *
+ * Note: We don't mark the VMTE as valid as this needs to be done by
+ * the hardware.
+ */
+int vgic_v5_vmte_init(struct kvm *kvm)
+{
+ size_t vmd_alloc_size, vpet_alloc_size, vped_alloc_size;
+ void *vped_base = NULL, *vmd = NULL;
+ struct vgic_v5_vm_info *vmi = NULL;
+ u64 tmp, vmte_val0 = 0, vmte_val1;
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ int ret, nr_cpus, nr_vcpus;
+ bool vmi_inserted = false;
+ struct vmtl2_entry *vmte;
+ vpe_entry *vpet = NULL;
+ struct kvm_vcpu *vcpu;
+ u16 max_vpe_id = 0;
+ unsigned long i;
+
+ nr_vcpus = atomic_read(&kvm->online_vcpus);
+ if (nr_vcpus > vmt_info->max_vpes)
+ return -E2BIG;
+
+ /*
+ * If we're using two-level VMTs, L2 is allocated on demand. For linear
+ * VMTs, this is a NOP.
+ */
+ ret = vgic_v5_alloc_l2_vmt(kvm);
+ if (ret)
+ return ret;
+
+ vmte = vgic_v5_get_l2_vmte(vm_id);
+ if (IS_ERR(vmte))
+ return PTR_ERR(vmte);
+
+ /* If the entry is already valid, something went wrong */
+ scoped_guard(raw_spinlock_irqsave, &vgic_v5_irs_lock) {
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+ if (le64_to_cpu(READ_ONCE(vmte->val[0])) & GICV5_VMTEL2E_VALID)
+ return -EINVAL;
+ }
+
+ ret = vgic_v5_reset_vmte(kvm);
+ if (ret)
+ return ret;
+
+ vmi = kzalloc_obj(*vmi);
+ if (!vmi) {
+ ret = -ENOMEM;
+ goto out_fail;
+ }
+
+ ret = xa_insert(&vm_info, vm_id, vmi, GFP_KERNEL);
+ if (ret)
+ goto out_fail;
+ vmi_inserted = true;
+
+ /* Allocate and assign the VM Descriptor, if required. */
+ if (vmt_info->vmd_size != 0) {
+ vmd_alloc_size = round_up(vmt_info->vmd_size,
+ dma_get_cache_alignment());
+ vmd = kzalloc(vmd_alloc_size, GFP_KERNEL);
+ if (!vmd) {
+ ret = -ENOMEM;
+ goto out_fail;
+ }
+
+ /* Stash the VA so we can free it later */
+ vmi->vmd_base = vmd;
+
+ tmp = FIELD_PREP(GICV5_VMTEL2E_VMD_ADDR,
+ virt_to_phys(vmd) >> GICV5_VMTEL2E_VMD_ADDR_SHIFT);
+ vmte_val0 = tmp;
+ }
+
+ /*
+ * Allocate and assign the VPE Table.
+ *
+ * First of all, iterate over all vcpus to find the highest VPE ID we
+ * require - we need to ensure that we have enough storage for all
+ * vcpu_id values that userspace has picked and not just the total
+ * number of vcpus. This gives us the number of VPEs required for the
+ * VM.
+ *
+ * Round up the number of VPEs to a whole power of two as we cannot
+ * describe non-powers-of-two in the VMTE field as it conveys the number
+ * of ID bits used and not the number of vPEs. IRS_IDR1.IAFFID_BITS is
+ * encoded as N - 1, so expose at least one VPE ID bit even for a
+ * single-vCPU VM to keep the views consistent.
+ */
+ kvm_for_each_vcpu(i, vcpu, kvm) {
+ u16 vpe_id = vgic_v5_vpe_id(vcpu);
+
+ if (vpe_id > max_vpe_id)
+ max_vpe_id = vpe_id;
+ }
+
+ nr_cpus = max(2UL, roundup_pow_of_two(max_vpe_id + 1));
+ vmi->vpe_id_bits = fls(nr_cpus) - 1;
+
+ vpet_alloc_size = round_up((size_t)nr_cpus * sizeof(*vpet),
+ dma_get_cache_alignment());
+ vpet = kzalloc(vpet_alloc_size, GFP_KERNEL);
+ if (!vpet) {
+ ret = -ENOMEM;
+ goto out_fail;
+ }
+
+ /* Stash the VA so we can free it later */
+ vmi->vpet_base = vpet;
+
+ tmp = FIELD_PREP(GICV5_VMTEL2E_VPET_ADDR,
+ virt_to_phys(vpet) >> GICV5_VMTEL2E_VPET_ADDR_SHIFT);
+ tmp |= FIELD_PREP(GICV5_VMTEL2E_VPE_ID_BITS, vmi->vpe_id_bits);
+ vmte_val1 = tmp;
+
+ /*
+ * Allocate a dense VPED array indexed by vcpu_idx. The VPET is indexed
+ * by the potentially sparse vcpu_id, but using that ID here would waste
+ * memory. Given that this is not userspace visible, we can cheat a bit
+ * and use the dense index instead. This is the ONLY place that we do
+ * this.
+ *
+ * Round the requested size up to a whole cacheline. kzalloc() gurantees
+ * natural alignment, so we ensure that the cachelines cannot be shared
+ * with unrelated slab objects. VPED and cacheline sizes are powers of
+ * two, so this also preserves the required VPED alignment when a VPED
+ * is larger than a cacheline.
+ */
+ vped_alloc_size = round_up((size_t)nr_vcpus * vmt_info->vped_size,
+ dma_get_cache_alignment());
+ vped_base = kzalloc(vped_alloc_size, GFP_KERNEL);
+ if (!vped_base) {
+ ret = -ENOMEM;
+ goto out_fail;
+ }
+ vmi->vped_base = vped_base;
+
+ if (vmd)
+ vgic_v5_clean_inval(vmd, vmd_alloc_size);
+ vgic_v5_clean_inval(vpet, vpet_alloc_size);
+ vgic_v5_clean_inval(vped_base, vped_alloc_size);
+
+ /* Publish the VMTE while serialising access to the shared VMT. */
+ scoped_guard(raw_spinlock_irqsave, &vgic_v5_irs_lock) {
+ WRITE_ONCE(vmte->val[0], cpu_to_le64(vmte_val0));
+ WRITE_ONCE(vmte->val[1], cpu_to_le64(vmte_val1));
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+ }
+
+ kvm->arch.vgic.gicv5_vm.vmte_allocated = true;
+
+ return 0;
+
+out_fail:
+ /* kfree(NULL) is safe so we can just kfree() at leisure */
+ kfree(vmd);
+ kfree(vpet);
+ kfree(vped_base);
+ if (vmi_inserted)
+ xa_erase(&vm_info, vm_id);
+ kfree(vmi);
+
+ vgic_v5_reset_vmte(kvm);
+
+ return ret;
+}
+
+/*
+ * Release the VMT Entry, freeing up any allocated data structures before
+ * zeroing the VMTE.
+ *
+ * The VMTE must be marked as invalid before it is released.
+ */
+int vgic_v5_vmte_release(struct kvm *kvm)
+{
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vgic_v5_vm_info *vmi;
+ struct vmtl2_entry *vmte;
+ int ret;
+
+ vmte = vgic_v5_get_l2_vmte(vm_id);
+ if (IS_ERR(vmte))
+ return PTR_ERR(vmte);
+
+ /* Reject if the VMTE has not been marked as invalid! */
+ scoped_guard(raw_spinlock_irqsave, &vgic_v5_irs_lock) {
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+ if (le64_to_cpu(READ_ONCE(vmte->val[0])) & GICV5_VMTEL2E_VALID)
+ return -EINVAL;
+ }
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ goto no_vmi;
+
+ kfree(vmi->vped_base);
+ kfree(vmi->vpet_base);
+ kfree(vmi->vmd_base);
+
+ xa_erase(&vm_info, vm_id);
+ kfree(vmi);
+
+no_vmi:
+ /*
+ * If we didn't get far enough into allocating a VMTE to create the VM
+ * info structure, then we just zero the VMTE and move on. There's
+ * nothing else we can realistically do here.
+ */
+ ret = vgic_v5_reset_vmte(kvm);
+ if (ret)
+ return ret;
+
+ kvm->arch.vgic.gicv5_vm.vmte_allocated = false;
+
+ return 0;
+}
+
+/* Provide the preallocated VPE descriptor to the hardware via the VPE Table. */
+int vgic_v5_vmte_alloc_vpe(struct kvm_vcpu *vcpu)
+{
+ u32 vm_id = vgic_v5_vm_id(vcpu->kvm);
+ u16 vpe_id = vgic_v5_vpe_id(vcpu);
+ struct vgic_v5_vm_info *vmi;
+ vpe_entry tmp, *vpet_base;
+ void *vped;
+
+ /* Make sure we're not over what the hardware supports */
+ if (vpe_id >= vmt_info->max_vpes)
+ return -E2BIG;
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -EINVAL;
+
+ if (vpe_id >= 1 << vmi->vpe_id_bits)
+ return -E2BIG;
+
+ vpet_base = vmi->vpet_base;
+
+ /* If the VPETE for this CPU is already valid we've gone wrong */
+ scoped_guard(raw_spinlock_irqsave, &vgic_v5_irs_lock) {
+ vgic_v5_clean_inval(&vpet_base[vpe_id], sizeof(*vpet_base));
+ if (le64_to_cpu(READ_ONCE(vpet_base[vpe_id])) & GICV5_VPE_VALID)
+ return -EBUSY;
+ }
+
+ vped = (u8 *)vmi->vped_base +
+ (size_t)vcpu->vcpu_idx * vmt_info->vped_size;
+
+ tmp = FIELD_PREP(GICV5_VPED_ADDR, virt_to_phys(vped) >> GICV5_VPED_ADDR_SHIFT);
+
+ scoped_guard(raw_spinlock_irqsave, &vgic_v5_irs_lock) {
+ WRITE_ONCE(vpet_base[vpe_id], cpu_to_le64(tmp));
+ vgic_v5_clean_inval(vpet_base + vpe_id, sizeof(vpe_entry));
+ }
+
+ return 0;
+}
+
+/* Clear the VPE's table entry after the VMTE has been made invalid. */
+int vgic_v5_vmte_free_vpe(struct kvm_vcpu *vcpu)
+{
+ u32 vm_id = vgic_v5_vm_id(vcpu->kvm);
+ u16 vpe_id = vgic_v5_vpe_id(vcpu);
+ struct vgic_v5_vm_info *vmi;
+ struct vmtl2_entry *vmte;
+ vpe_entry *vpet_base;
+
+ vmte = vgic_v5_get_l2_vmte(vm_id);
+ if (IS_ERR(vmte))
+ return PTR_ERR(vmte);
+
+ scoped_guard(raw_spinlock_irqsave, &vgic_v5_irs_lock) {
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+ if (le64_to_cpu(READ_ONCE(vmte->val[0])) & GICV5_VMTEL2E_VALID)
+ return -EBUSY;
+ }
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -EINVAL;
+
+ if (vpe_id >= 1 << vmi->vpe_id_bits)
+ return -E2BIG;
+
+ vpet_base = vmi->vpet_base;
+ scoped_guard(raw_spinlock_irqsave, &vgic_v5_irs_lock) {
+ WRITE_ONCE(vpet_base[vpe_id], 0ULL);
+ vgic_v5_clean_inval(vpet_base + vpe_id, sizeof(vpe_entry));
+ }
+
+ return 0;
+}
diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.h b/arch/arm64/kvm/vgic/vgic-v5-tables.h
new file mode 100644
index 0000000000000..962be0c7cd3f6
--- /dev/null
+++ b/arch/arm64/kvm/vgic/vgic-v5-tables.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025, 2026 Arm Ltd.
+ */
+
+#ifndef __KVM_ARM_VGICV5_TABLES_H__
+#define __KVM_ARM_VGICV5_TABLES_H__
+
+#include <linux/spinlock.h>
+#include <linux/idr.h>
+#include <linux/irqchip/arm-gic-v5.h>
+
+/* Level 1 Virtual Machine Table Entry */
+typedef __le64 vmtl1_entry;
+
+/* Level 2 Virtual Machine Table Entry */
+struct vmtl2_entry {
+ __le64 val[4];
+};
+
+/* Virtual PE Table Entry */
+typedef __le64 vpe_entry;
+
+struct vgic_v5_vm_info {
+ void __iomem *vmd_base;
+ vpe_entry __iomem *vpet_base;
+ void *vped_base;
+ u8 vpe_id_bits;
+};
+
+struct vgic_v5_vmt {
+ union {
+ struct {
+ struct vmtl2_entry *vmt_base;
+ unsigned int num_ents;
+ } linear;
+ struct {
+ vmtl1_entry *vmt_base;
+ struct vmtl2_entry **l2ptrs;
+ unsigned int num_l1_ents;
+ } l2;
+ };
+ bool two_level;
+ unsigned int num_entries;
+ unsigned int max_vpes;
+ size_t vmd_size;
+ size_t vped_size;
+ struct ida vm_id_ida;
+};
+
+static inline u32 vgic_v5_vm_id(struct kvm *kvm)
+{
+ return kvm->arch.vgic.gicv5_vm.vm_id;
+}
+
+/*
+ * For vGICv5, we need to consolidate two views:
+ * - The vcpu ID assigned by userspace, which it can assign however it
+ * sees fit.
+ * - The index into the VPET, which is also presented to the guest as
+ * the IAFFID for the VPE in question.
+ *
+ * Ideally, we don't want to intercept the guest setting an interrupt's affinity
+ * - this would somewhat defeat the point of allowing the hardware to manage the
+ * interrupt directly. Therefore, we need to use vcpu->vcpu_id as our VPE ID,
+ * which ensures that both userspace and the GICv5 hardware have a consistent
+ * view.
+ *
+ * This means two things:
+ * - Our VPET might be sparse, depending on the IDs picked by userspace.
+ * - We need to check the vcpu ID provided by userspace, and reject anything
+ * we cannot handle.
+ *
+ * In order to at least ensure consistent usage in the places that we need to
+ * use the VPE ID, we provide this helper.
+ */
+static inline u16 vgic_v5_vpe_id(struct kvm_vcpu *vcpu)
+{
+ return vcpu->vcpu_id;
+}
+
+static inline int vgic_v5_vpe_db(struct kvm_vcpu *vcpu)
+{
+ return vcpu->arch.vgic_cpu.vgic_v5.gicv5_vpe.db;
+}
+
+extern raw_spinlock_t vgic_v5_irs_lock;
+
+int vgic_v5_vmt_allocate(unsigned int max_vpes);
+int vgic_v5_vmt_free(void);
+
+int vgic_v5_allocate_vm_id(struct kvm *kvm);
+void vgic_v5_release_vm_id(struct kvm *kvm);
+
+int vgic_v5_vmte_init(struct kvm *kvm);
+int vgic_v5_vmte_release(struct kvm *kvm);
+int vgic_v5_vmte_alloc_vpe(struct kvm_vcpu *vcpu);
+int vgic_v5_vmte_free_vpe(struct kvm_vcpu *vcpu);
+
+#endif
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 752329fc3d566..4d1d7701ef71d 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -5,10 +5,12 @@
#include <kvm/arm_vgic.h>
+#include <linux/kvm_host.h>
#include <linux/bitops.h>
#include <linux/irqchip/arm-vgic-info.h>
#include <linux/irqdomain.h>
+#include "vgic-v5-tables.h"
#include "vgic.h"
#define ppi_caps kvm_vgic_global_state.vgic_v5_ppi_caps
@@ -129,6 +131,22 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
return 0;
}
+static int vgic_v5_db_set_vcpu_affinity(struct irq_data *data, void *vcpu_info)
+{
+ enum gicv5_vcpu_cmd *cmd = vcpu_info;
+
+ guard(raw_spinlock_irqsave)(&vgic_v5_irs_lock);
+
+ switch (*cmd) {
+ case VMT_L2_MAP:
+ case VMTE_MAKE_VALID:
+ case VMTE_MAKE_INVALID:
+ /* Not yet implemented */
+ default:
+ return -EINVAL;
+ }
+}
+
/*
* This set of irq_chip functions is specific for doorbells.
*/
@@ -140,6 +158,7 @@ static const struct irq_chip vgic_v5_db_irq_chip = {
.irq_set_affinity = irq_chip_set_affinity_parent,
.irq_get_irqchip_state = irq_chip_get_parent_state,
.irq_set_irqchip_state = irq_chip_set_parent_state,
+ .irq_set_vcpu_affinity = vgic_v5_db_set_vcpu_affinity,
.flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_SKIP_SET_WAKE |
IRQCHIP_MASK_ON_SUSPEND,
};
diff --git a/drivers/irqchip/irq-gic-v5-irs.c b/drivers/irqchip/irq-gic-v5-irs.c
index 607e066821b52..70502b07ec8d7 100644
--- a/drivers/irqchip/irq-gic-v5-irs.c
+++ b/drivers/irqchip/irq-gic-v5-irs.c
@@ -269,24 +269,24 @@ int gicv5_irs_iste_alloc(const u32 lpi)
* itself is not supported) again serves to make it easier to find physically
* contiguous blocks of memory.
*/
-static unsigned int gicv5_irs_l2_sz(u32 idr2)
+unsigned int gicv5_irs_l2_sz(u32 l2sz)
{
switch (PAGE_SIZE) {
case SZ_64K:
- if (GICV5_IRS_IST_L2SZ_SUPPORT_64KB(idr2))
+ if (GICV5_IRS_IST_L2SZ_SUPPORT_64KB(l2sz))
return GICV5_IRS_IST_CFGR_L2SZ_64K;
fallthrough;
case SZ_4K:
- if (GICV5_IRS_IST_L2SZ_SUPPORT_4KB(idr2))
+ if (GICV5_IRS_IST_L2SZ_SUPPORT_4KB(l2sz))
return GICV5_IRS_IST_CFGR_L2SZ_4K;
fallthrough;
case SZ_16K:
- if (GICV5_IRS_IST_L2SZ_SUPPORT_16KB(idr2))
+ if (GICV5_IRS_IST_L2SZ_SUPPORT_16KB(l2sz))
return GICV5_IRS_IST_CFGR_L2SZ_16K;
break;
}
- if (GICV5_IRS_IST_L2SZ_SUPPORT_4KB(idr2))
+ if (GICV5_IRS_IST_L2SZ_SUPPORT_4KB(l2sz))
return GICV5_IRS_IST_CFGR_L2SZ_4K;
return GICV5_IRS_IST_CFGR_L2SZ_64K;
@@ -334,7 +334,7 @@ static int __init gicv5_irs_init_ist(struct gicv5_irs_chip_data *irs_data)
lpi_id_bits = min(lpi_id_bits, gicv5_global_data.cpuif_id_bits);
if (two_levels)
- l2sz = gicv5_irs_l2_sz(idr2);
+ l2sz = gicv5_irs_l2_sz(FIELD_GET(GICV5_IRS_IDR2_IST_L2SZ, idr2));
istmd = !!FIELD_GET(GICV5_IRS_IDR2_ISTMD, idr2);
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 6e5aa248f3cfd..7923ff20d9d7d 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -361,6 +361,8 @@ struct vgic_redist_region {
struct list_head list;
};
+#define VGIC_V5_VM_ID_INVAL (-1)
+
struct vgic_v5_vm {
/*
* We only expose a subset of PPIs to the guest. This subset is a
@@ -383,6 +385,8 @@ struct vgic_v5_vm {
struct fwnode_handle *fwnode;
struct irq_domain *domain;
int vpe_db_base;
+ u32 vm_id;
+ bool vmte_allocated;
};
struct vgic_dist {
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index 21c8a69f99bb6..27b13bf2c1e2c 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -159,9 +159,9 @@
#define GICV5_IRS_IDR2_LPI BIT(5)
#define GICV5_IRS_IDR2_ID_BITS GENMASK(4, 0)
-#define GICV5_IRS_IST_L2SZ_SUPPORT_4KB(r) FIELD_GET(BIT(11), (r))
-#define GICV5_IRS_IST_L2SZ_SUPPORT_16KB(r) FIELD_GET(BIT(12), (r))
-#define GICV5_IRS_IST_L2SZ_SUPPORT_64KB(r) FIELD_GET(BIT(13), (r))
+#define GICV5_IRS_IST_L2SZ_SUPPORT_4KB(r) FIELD_GET(BIT(0), (r))
+#define GICV5_IRS_IST_L2SZ_SUPPORT_16KB(r) FIELD_GET(BIT(1), (r))
+#define GICV5_IRS_IST_L2SZ_SUPPORT_64KB(r) FIELD_GET(BIT(2), (r))
#define GICV5_IRS_IDR3_VMT_LEVELS BIT(10)
#define GICV5_IRS_IDR3_VM_ID_BITS GENMASK(9, 5)
@@ -609,6 +609,7 @@ int gicv5_irs_cpu_to_iaffid(int cpu_id, u16 *iaffid);
struct gicv5_irs_chip_data *gicv5_irs_lookup_by_spi_id(u32 spi_id);
int gicv5_spi_irq_set_type(struct irq_data *d, unsigned int type);
int gicv5_irs_iste_alloc(u32 lpi);
+unsigned int gicv5_irs_l2_sz(u32 l2sz);
void gicv5_irs_syncr(void);
/* Embedded in kvm.arch */
@@ -653,4 +654,11 @@ void gicv5_deinit_lpis(void);
void __init gicv5_its_of_probe(struct device_node *parent);
void __init gicv5_its_acpi_probe(void);
+
+enum gicv5_vcpu_cmd {
+ VMT_L2_MAP, /* Map in a L2 VMT - *may* happen on VM init */
+ VMTE_MAKE_VALID, /* Make the VMTE valid */
+ VMTE_MAKE_INVALID, /* Make the VMTE (et al.) invalid */
+};
+
#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 10/49] KVM: arm64: gic-v5: Introduce guest IST alloc and management
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (8 preceding siblings ...)
2026-08-07 11:17 ` [PATCH v5 09/49] KVM: arm64: gic-v5: Create and manage VM and VPE tables Sascha Bischoff
@ 2026-08-07 11:17 ` Sascha Bischoff
2026-08-07 11:18 ` [PATCH v5 11/49] KVM: arm64: gic-v5: Implement VMT/vIST IRS MMIO Ops Sascha Bischoff
` (38 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:17 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
GICv5 guests use Interrupt State Tables (ISTs) to track and manage the
interrupt state for SPIs and LPIs. These ISTs are provided to the
host's IRS via the VMTE.
On a host GICv5 system, SPIs do not require any up-front memory
allocation prior to their use, unlike LPIs which require the OS to
allocate an IST. For a GICv5 guest, the same holds from the guest's
point of view: SPIs should require no explicit memory allocation by
the guest. This means that KVM must provision the memory passed to the
IRS for managing a guest's SPI state.
Introduce the machinery needed to allocate a linear SPI IST. A later
commit will allocate it as part of VM creation, before running the
guest for the first time. The host is responsible for freeing this
memory on guest teardown.
For LPIs, the guest provisions memory for its LPI IST. KVM does not
pass that memory directly to the host IRS. Instead, add support for
allocating a shadow LPI IST and passing that to the IRS through the
VMTE. The LPI IST may be allocated as a two-level structure when
supported and required by the configured LPI ID space, as many more
LPIs are expected than SPIs. The host frees this memory on guest
teardown.
Assigning an IST to a VM requires an IRS operation rather than writing
the Valid bit directly in the VMTE. Extend the doorbell command
interface with the operations needed to make SPI and LPI ISTs valid or
invalid. Together with the existing VMT commands, the command set is:
VMT_L2_MAP - Make a second level VM table valid
VMTE_MAKE_VALID - Make a single VMTE, and hence VM, valid
VMTE_MAKE_INVALID - Make a single VMTE, and hence VM, invalid
SPI_VIST_MAKE_VALID - Make the SPI IST valid
LPI_VIST_MAKE_VALID - Make the LPI IST valid
LPI_VIST_MAKE_INVALID - Make the LPI IST invalid
Once implemented, irq_set_vcpu_affinity() will act as an ioctl-like
interface for issuing commands specific to either a VM or the
particular VPE that the doorbell belongs to. None of these commands
are plumbed through to the host IRS at this stage.
There is intentionally no SPI_VIST_MAKE_INVALID command. The SPI IST
will be allocated as part of VM creation and will remain valid for the
lifetime of the VM. It can be freed after the VMTE has been made
invalid during teardown. The LPI IST, on the other hand, is driven by
the guest, which is free to invalidate and free its LPI IST at any
point.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-v5-tables.c | 551 +++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic-v5-tables.h | 22 ++
arch/arm64/kvm/vgic/vgic-v5.c | 3 +
include/linux/irqchip/arm-gic-v5.h | 3 +
4 files changed, 579 insertions(+)
diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.c b/arch/arm64/kvm/vgic/vgic-v5-tables.c
index 7252d48431a5a..f088006f39674 100644
--- a/arch/arm64/kvm/vgic/vgic-v5-tables.c
+++ b/arch/arm64/kvm/vgic/vgic-v5-tables.c
@@ -65,6 +65,14 @@ static DEFINE_XARRAY(vm_info);
#define GICV5_VPED_ADDR_SHIFT 3ULL
#define GICV5_VPED_ADDR GENMASK_ULL(55, 3)
+/*
+ * The LPI and SPI configuration is stored in the 2nd and 3rd 64-bit chunks of
+ * the VMTE (0-based). We call this a section here in an attempt to simplify the
+ * code.
+ */
+#define GICV5_VMTEL2_LPI_SECTION 2
+#define GICV5_VMTEL2_SPI_SECTION 3
+
/*
* Our IRS might be coherent or non-coherent. If coherent, we can just emit a
* DSB to ensure that we're in sync. However, when non-coherent, we need to
@@ -553,6 +561,25 @@ int vgic_v5_vmte_init(struct kvm *kvm)
return ret;
}
+/*
+ * The following set of forward declarations makes the code layout a *little*
+ * clearer as it lets us keep the IST-related code together.
+ */
+static int vgic_v5_alloc_linear_ist(struct kvm *kvm, bool spi_ist,
+ unsigned int id_bits,
+ unsigned int istsz);
+static int vgic_v5_alloc_l1_ist(struct kvm *kvm, unsigned int id_bits,
+ unsigned int istsz, unsigned int l2_split);
+static int vgic_v5_alloc_l2_ists(struct kvm *kvm, unsigned int id_bits,
+ unsigned int istsz, unsigned int l2_split);
+static int vgic_v5_alloc_two_level_lpi_ist(struct kvm *kvm,
+ unsigned int id_bits,
+ unsigned int istsz,
+ unsigned int l2_split);
+static int vgic_v5_linear_ist_free(struct kvm *kvm, bool spi);
+static int vgic_v5_two_level_ist_free(struct kvm *kvm, bool spi);
+static int vgic_v5_spi_ist_free(struct kvm *kvm);
+
/*
* Release the VMT Entry, freeing up any allocated data structures before
* zeroing the VMTE.
@@ -581,6 +608,22 @@ int vgic_v5_vmte_release(struct kvm *kvm)
if (!vmi)
goto no_vmi;
+ /* If we have an LPI IST, free it */
+ if (vmi->h_lpi_ist) {
+ ret = vgic_v5_lpi_ist_free(kvm);
+ if (ret)
+ return ret;
+ }
+ vmi->h_lpi_ist = NULL;
+
+ /* If we have an SPI IST, free it */
+ if (vmi->h_spi_ist) {
+ ret = vgic_v5_spi_ist_free(kvm);
+ if (ret)
+ return ret;
+ }
+ vmi->h_spi_ist = NULL;
+
kfree(vmi->vped_base);
kfree(vmi->vpet_base);
kfree(vmi->vmd_base);
@@ -679,3 +722,511 @@ int vgic_v5_vmte_free_vpe(struct kvm_vcpu *vcpu)
return 0;
}
+
+/*
+ * Assign an already allocated IST to the VM by populating the fields in the
+ * corresponding VMTE. We re-use this code for both an SPI IST and LPI IST, even
+ * if the paths to reach it might be vastly different.
+ */
+static int vgic_v5_vmte_assign_ist(struct kvm *kvm, phys_addr_t ist_base,
+ bool two_level, unsigned int id_bits,
+ unsigned int l2sz, unsigned int istsz,
+ bool spi_ist)
+{
+ struct kvm_vcpu *vcpu0 = kvm_get_vcpu(kvm, 0);
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ enum gicv5_vcpu_cmd cmd;
+ struct vmtl2_entry *vmte;
+ unsigned int section;
+ u64 tmp;
+ int ret;
+
+ /*
+ * The L2 VMTE comprises four 64-bit "sections", where sections 2 & 3
+ * describe the LPI and SPI ISTs, respectively. Both the LPI and SPI
+ * sections have the same layout, and as we are either operating on SPIs
+ * or LPIs we pick a section of the VMTE to modify up-front.
+ *
+ * See the GICv5 EAC0 Specification 11.2.2 for more details about the
+ * VMTE layout.
+ */
+ section = spi_ist ? GICV5_VMTEL2_SPI_SECTION : GICV5_VMTEL2_LPI_SECTION;
+
+ if (ist_base & ~GICV5_VMTEL2E_IST_ADDR) {
+ pr_err_ratelimited("kvm [%i]: IST misaligned: address 0x%llx, mask 0x%llx\n",
+ task_pid_nr(current), ist_base,
+ GICV5_VMTEL2E_IST_ADDR);
+ return -EINVAL;
+ }
+
+ vmte = vgic_v5_get_l2_vmte(vm_id);
+ if (IS_ERR(vmte))
+ return PTR_ERR(vmte);
+
+ tmp = FIELD_PREP(GICV5_VMTEL2E_IST_L2SZ, l2sz);
+ tmp |= FIELD_PREP(GICV5_VMTEL2E_IST_ADDR,
+ ist_base >> GICV5_VMTEL2E_IST_ADDR_SHIFT);
+ tmp |= FIELD_PREP(GICV5_VMTEL2E_IST_ISTSZ, istsz);
+ tmp |= FIELD_PREP(GICV5_VMTEL2E_IST_ID_BITS, id_bits);
+ if (two_level)
+ tmp |= GICV5_VMTEL2E_IST_STRUCTURE;
+
+ scoped_guard(raw_spinlock_irqsave, &vgic_v5_irs_lock) {
+ /* Bail if already allocated */
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+ if (le64_to_cpu(READ_ONCE(vmte->val[section])) &
+ GICV5_VMTEL2E_IST_VALID)
+ return -EINVAL;
+
+ WRITE_ONCE(vmte->val[section], cpu_to_le64(tmp));
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+ }
+
+ /* Finally, mark the entry as valid */
+ cmd = spi_ist ? SPI_VIST_MAKE_VALID : LPI_VIST_MAKE_VALID;
+ ret = irq_set_vcpu_affinity(vgic_v5_vpe_db(vcpu0), &cmd);
+
+ return ret;
+}
+
+/*
+ * Allocate a Linear IST - always used for SPIs and potentially LPIs.
+ *
+ * The calculation for n has been taken from section 11.2.2 of the GICv5 EAC0
+ * spec.
+ *
+ * NOTE: istsz is the FIELD used by GICv5, not the actual size (or log2() of the
+ * size).
+ */
+static int vgic_v5_alloc_linear_ist(struct kvm *kvm, bool spi_ist,
+ unsigned int id_bits, unsigned int istsz)
+{
+ const size_t n = max(5, id_bits + 1 + istsz);
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vgic_v5_vm_info *vmi;
+ __le64 *ist;
+ u32 l1sz;
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -EINVAL;
+
+ /*
+ * Allocate the IST. We only have one level, so we just use the L2 ISTE.
+ */
+ l1sz = BIT(n + 1);
+ ist = kzalloc(l1sz, GFP_KERNEL_ACCOUNT);
+ if (!ist)
+ return -ENOMEM;
+
+ if (spi_ist) {
+ vmi->h_spi_ist = ist;
+ } else {
+ vmi->h_lpi_ist_structure = false;
+ vmi->h_lpi_ist = ist;
+ }
+
+ vgic_v5_clean_inval(ist, l1sz);
+
+ return 0;
+}
+
+/*
+ * Allocate the first level of a two-level IST - LPI, only.
+ *
+ * The calculation for n has been taken from section 11.2.2 of the GICv5 EAC0
+ * spec.
+ *
+ * NOTE: istsz and l2sz are the FIELDS used by GICv5, not the actual sizes (or
+ * log2() of the sizes).
+ */
+static int vgic_v5_alloc_l1_ist(struct kvm *kvm, unsigned int id_bits,
+ unsigned int istsz, unsigned int l2sz)
+{
+ const size_t n = max(5, id_bits - ((10 - istsz) + (2 * l2sz)) + 3 - 1);
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ const u32 l1_size = BIT(n + 1);
+ struct vgic_v5_vm_info *vmi;
+ __le64 *ist;
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -EINVAL;
+
+ ist = kzalloc(l1_size, GFP_KERNEL_ACCOUNT);
+ if (!ist)
+ return -ENOMEM;
+
+ vmi->h_lpi_ist_structure = true;
+ vmi->h_lpi_ist = ist;
+
+ vgic_v5_clean_inval(ist, l1_size);
+
+ return 0;
+}
+
+/*
+ * Allocate ALL of the second level ISTs for a two-level IST - LPI, only.
+ *
+ * The calculation for n has been taken from section 11.2.2 of the GICv5 EAC0
+ * spec. The l2_size calculation is from section 11.2.3 of the same document.
+ *
+ * NOTE: istsz and l2sz are the FIELDS used by GICv5, not the actual sizes (or
+ * log2() of the sizes).
+ */
+static int vgic_v5_alloc_l2_ists(struct kvm *kvm, unsigned int id_bits,
+ unsigned int istsz, unsigned int l2sz)
+{
+ const size_t n = max(5, id_bits - ((10 - istsz) + (2 * l2sz)) + 3 - 1);
+ const int l1_entries = BIT(n + 1) / GICV5_IRS_ISTL1E_SIZE;
+ const size_t l2_size = BIT(11 + (2 * l2sz) + 1);
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vgic_v5_vm_info *vmi;
+ __le64 *l2ist;
+ __le64 *l1ist;
+ int index;
+ u64 val;
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -EINVAL;
+
+ l1ist = vmi->h_lpi_ist;
+
+ /*
+ * Allocate the storage for the pointers to the L2 ISTs (used when
+ * freeing later).
+ */
+ vmi->h_lpi_l2_ists = kzalloc_objs(*vmi->h_lpi_l2_ists, l1_entries,
+ GFP_KERNEL_ACCOUNT);
+ if (!vmi->h_lpi_l2_ists)
+ return -ENOMEM;
+
+ /* Allocate the L2 IST for each L1 IST entry */
+ for (index = 0; index < l1_entries; ++index) {
+ l2ist = kzalloc(l2_size, GFP_KERNEL_ACCOUNT);
+ if (!l2ist) {
+ while (--index >= 0)
+ kfree(vmi->h_lpi_l2_ists[index]);
+
+ kfree(vmi->h_lpi_l2_ists);
+ vmi->h_lpi_l2_ists = NULL;
+
+ return -ENOMEM;
+ }
+
+ /*
+ * We are not doing on-demand allocation of the L2 ISTs, and are
+ * instead provisioning the whole IST up front. This means that
+ * we are able to mark the L2 ISTs as valid in the L1 ISTEs as
+ * the overall IST is not yet valid.
+ */
+ val = (virt_to_phys(l2ist) & GICV5_ISTL1E_L2_ADDR_MASK) |
+ GICV5_ISTL1E_VALID;
+ l1ist[index] = cpu_to_le64(val);
+
+ vmi->h_lpi_l2_ists[index] = l2ist;
+
+ vgic_v5_clean_inval(l2ist, l2_size);
+ }
+
+ /* Handle CMOs for the whole L1 IST in one go */
+ vgic_v5_clean_inval(l1ist, l1_entries * sizeof(*l1ist));
+
+ return 0;
+}
+
+/* Allocate a two-level IST - LPIs, only */
+static int vgic_v5_alloc_two_level_lpi_ist(struct kvm *kvm, unsigned int id_bits,
+ unsigned int istsz, unsigned int l2sz)
+{
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vgic_v5_vm_info *vmi;
+ int ret;
+
+ /*
+ * Allocate the L1 IST first, then all of the L2s. Everything
+ * is preallocated and we do no on-demand IST allocation. This
+ * is to avoid needing to track if and when the guest is doing
+ * on-demand IST allocation.
+ */
+ ret = vgic_v5_alloc_l1_ist(kvm, id_bits, istsz, l2sz);
+ if (ret)
+ return ret;
+
+ ret = vgic_v5_alloc_l2_ists(kvm, id_bits, istsz, l2sz);
+ if (ret) {
+ /* Free the L1 IST again */
+ vmi = xa_load(&vm_info, vm_id);
+ kfree(vmi->h_lpi_ist);
+ vmi->h_lpi_ist = 0;
+
+ return ret;
+ }
+
+ return 0;
+}
+
+static void vgic_v5_free_allocated_lpi_ist(struct vgic_v5_vm_info *vmi,
+ unsigned int id_bits,
+ unsigned int istsz,
+ unsigned int l2sz)
+{
+ if (!vmi->h_lpi_ist_structure) {
+ kfree(vmi->h_lpi_ist);
+ vmi->h_lpi_ist = NULL;
+ return;
+ }
+
+ if (vmi->h_lpi_l2_ists) {
+ const size_t n = max(5, id_bits - ((10 - istsz) + (2 * l2sz)) + 3 - 1);
+ const int l1_entries = BIT(n + 1) / GICV5_IRS_ISTL1E_SIZE;
+ int index;
+
+ for (index = 0; index < l1_entries; ++index)
+ kfree(vmi->h_lpi_l2_ists[index]);
+
+ kfree(vmi->h_lpi_l2_ists);
+ vmi->h_lpi_l2_ists = NULL;
+ }
+
+ kfree(vmi->h_lpi_ist);
+ vmi->h_lpi_ist = NULL;
+}
+
+static void vgic_v5_free_allocated_spi_ist(struct kvm *kvm)
+{
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vgic_v5_vm_info *vmi;
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return;
+
+ kfree(vmi->h_spi_ist);
+ vmi->h_spi_ist = NULL;
+}
+
+/*
+ * Free a Linear IST. Can only happen once the VM is dead.
+ */
+static int vgic_v5_linear_ist_free(struct kvm *kvm, bool spi)
+{
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vmtl2_entry *vmte;
+ struct vgic_v5_vm_info *vmi;
+ int section;
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -EINVAL;
+
+ vmte = vgic_v5_get_l2_vmte(vm_id);
+ if (IS_ERR(vmte))
+ return PTR_ERR(vmte);
+
+ if (spi) {
+ section = GICV5_VMTEL2_SPI_SECTION;
+ vgic_v5_free_allocated_spi_ist(kvm);
+ } else {
+ section = GICV5_VMTEL2_LPI_SECTION;
+ vgic_v5_free_allocated_lpi_ist(vmi, 0, 0, 0);
+ }
+
+ /* The VM should be dead here, so we can just zero the VMT section */
+ scoped_guard(raw_spinlock_irqsave, &vgic_v5_irs_lock) {
+ WRITE_ONCE(vmte->val[section], cpu_to_le64(0));
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+ }
+
+ return 0;
+}
+
+/*
+ * Free a Two-Level IST. Can only happen once the VM is dead.
+ */
+static int vgic_v5_two_level_ist_free(struct kvm *kvm, bool spi)
+{
+ unsigned int id_bits, istsz, l2sz;
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vgic_v5_vm_info *vmi;
+ struct vmtl2_entry *vmte;
+ u64 tmp;
+ int section;
+
+ /* We don't create two-level SPI ISTs, so freeing is a bad idea! */
+ if (spi)
+ return -EINVAL;
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -EINVAL;
+
+ section = GICV5_VMTEL2_LPI_SECTION;
+
+ if (!vmi->h_lpi_ist_structure)
+ return -EINVAL;
+
+ vmte = vgic_v5_get_l2_vmte(vm_id);
+ if (IS_ERR(vmte))
+ return PTR_ERR(vmte);
+
+ scoped_guard(raw_spinlock_irqsave, &vgic_v5_irs_lock) {
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+ tmp = le64_to_cpu(READ_ONCE(vmte->val[section]));
+ }
+
+ id_bits = FIELD_GET(GICV5_VMTEL2E_IST_ID_BITS, tmp);
+ istsz = FIELD_GET(GICV5_VMTEL2E_IST_ISTSZ, tmp);
+ l2sz = FIELD_GET(GICV5_VMTEL2E_IST_L2SZ, tmp);
+
+ vgic_v5_free_allocated_lpi_ist(vmi, id_bits, istsz, l2sz);
+
+ /* The VM must be dead, so we can just zero the VMT section */
+ scoped_guard(raw_spinlock_irqsave, &vgic_v5_irs_lock) {
+ WRITE_ONCE(vmte->val[section], cpu_to_le64(0));
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+ }
+
+ return 0;
+}
+
+/* Helper to determine ISTE size based on metadata requirements */
+static unsigned int vgic_v5_ist_istsz(unsigned int id_bits)
+{
+ if (!vgic_v5_irs_istmd(&irs_caps))
+ return GICV5_IRS_IST_CFGR_ISTSZ_4;
+
+ if (id_bits >= vgic_v5_irs_istmd_sz(&irs_caps))
+ return GICV5_IRS_IST_CFGR_ISTSZ_16;
+
+ return GICV5_IRS_IST_CFGR_ISTSZ_8;
+}
+
+/*
+ * Allocate an IST for SPIs.
+ *
+ * We don't anticipate a large number of SPIs being allocated. Therefore, we
+ * always allocate a Linear IST for SPIs. This will need to be revisited should
+ * that assumption no longer hold.
+ */
+int vgic_v5_spi_ist_alloc(struct kvm *kvm, unsigned int id_bits)
+{
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vgic_v5_vm_info *vmi;
+ phys_addr_t base_addr;
+ unsigned int istsz;
+ int ret;
+
+ lockdep_assert_held(&kvm->arch.config_lock);
+
+ istsz = vgic_v5_ist_istsz(id_bits);
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -EINVAL;
+
+ if (vmi->h_spi_ist)
+ return -EBUSY;
+
+ ret = vgic_v5_alloc_linear_ist(kvm, true, id_bits, istsz);
+ if (ret)
+ return ret;
+ base_addr = virt_to_phys(vmi->h_spi_ist);
+
+ ret = vgic_v5_vmte_assign_ist(kvm, base_addr, false, id_bits, 0, istsz,
+ true);
+ if (ret) {
+ vgic_v5_free_allocated_spi_ist(kvm);
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * Free the IST for SPIs. Should only happen once the VM is dead.
+ */
+static int vgic_v5_spi_ist_free(struct kvm *kvm)
+{
+ lockdep_assert_held(&kvm->arch.config_lock);
+
+ return vgic_v5_linear_ist_free(kvm, true);
+}
+
+/*
+ * Allocate an IST for LPIs.
+ *
+ * Unlike with SPIs, we anticipate that the guest will allocate a relatively
+ * large number of LPIs. Therefore, while we support doing a linear LPI IST, it
+ * is expected that LPI ISTs will be two-level.
+ */
+int vgic_v5_lpi_ist_alloc(struct kvm *kvm, unsigned int id_bits)
+{
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vgic_v5_vm_info *vmi;
+ unsigned int istsz, l2sz;
+ phys_addr_t phys_addr;
+ bool two_level;
+ int ret;
+
+ lockdep_assert_held(&kvm->arch.config_lock);
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -EINVAL;
+
+ if (vmi->h_lpi_ist)
+ return -EBUSY;
+
+ istsz = vgic_v5_ist_istsz(id_bits);
+ l2sz = gicv5_irs_l2_sz(vgic_v5_irs_ist_l2sz(&irs_caps));
+
+ /*
+ * Determine if we want to create a Linear or a Two-Level IST.
+ *
+ * A two-level IST is only required when a single L2 IST cannot cover
+ * the requested ID space. This depends on the L2 IST size selected for
+ * the IRS, not PAGE_SIZE. Using PAGE_SIZE here would switch to
+ * two-level too early when the selected L2 IST is larger than a page,
+ * and the allocation sizing arithmetic would underflow.
+ */
+ two_level = vgic_v5_irs_ist_levels(&irs_caps) &&
+ id_bits > ((10 - istsz) + (2 * l2sz));
+
+ if (!two_level)
+ ret = vgic_v5_alloc_linear_ist(kvm, false /* LPIs, not SPIs */,
+ id_bits, istsz);
+ else
+ ret = vgic_v5_alloc_two_level_lpi_ist(kvm, id_bits, istsz,
+ l2sz);
+
+ if (ret)
+ return ret;
+
+ phys_addr = virt_to_phys(vmi->h_lpi_ist);
+ ret = vgic_v5_vmte_assign_ist(kvm, phys_addr, two_level, id_bits, l2sz,
+ istsz, false);
+ if (ret)
+ vgic_v5_free_allocated_lpi_ist(vmi, id_bits, istsz, l2sz);
+
+ return ret;
+}
+
+/* Free the LPI IST again */
+int vgic_v5_lpi_ist_free(struct kvm *kvm)
+{
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vgic_v5_vm_info *vmi;
+
+ lockdep_assert_held(&kvm->arch.config_lock);
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -ENXIO;
+
+ if (!vmi->h_lpi_ist_structure)
+ return vgic_v5_linear_ist_free(kvm, false);
+ else
+ return vgic_v5_two_level_ist_free(kvm, false);
+}
diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.h b/arch/arm64/kvm/vgic/vgic-v5-tables.h
index 962be0c7cd3f6..a711a247cc6be 100644
--- a/arch/arm64/kvm/vgic/vgic-v5-tables.h
+++ b/arch/arm64/kvm/vgic/vgic-v5-tables.h
@@ -26,6 +26,24 @@ struct vgic_v5_vm_info {
vpe_entry __iomem *vpet_base;
void *vped_base;
u8 vpe_id_bits;
+
+ /*
+ * Both the LPI and SPI ISTs are allocated by the hypervisor. While it
+ * would be possible to track and access them by iterating over the ISTs
+ * themselves, it makes more sense to store pointers to the ISTs.
+ *
+ * The LPI IST can either be two-level or linear. Hence, we keep track
+ * of the structure. If it is two-level, we retain pointers to the L1
+ * IST and to each L2 IST array. If it is linear, we just store the base
+ * address of the IST array.
+ *
+ * The SPI IST is linear, and therefore we just store the base address
+ * of the SPI IST array.
+ */
+ bool h_lpi_ist_structure;
+ __le64 *h_lpi_ist;
+ __le64 **h_lpi_l2_ists;
+ __le64 *h_spi_ist;
};
struct vgic_v5_vmt {
@@ -97,4 +115,8 @@ int vgic_v5_vmte_release(struct kvm *kvm);
int vgic_v5_vmte_alloc_vpe(struct kvm_vcpu *vcpu);
int vgic_v5_vmte_free_vpe(struct kvm_vcpu *vcpu);
+int vgic_v5_spi_ist_alloc(struct kvm *kvm, unsigned int id_bits);
+int vgic_v5_lpi_ist_alloc(struct kvm *kvm, unsigned int id_bits);
+int vgic_v5_lpi_ist_free(struct kvm *kvm);
+
#endif
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 4d1d7701ef71d..34f197830611e 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -141,6 +141,9 @@ static int vgic_v5_db_set_vcpu_affinity(struct irq_data *data, void *vcpu_info)
case VMT_L2_MAP:
case VMTE_MAKE_VALID:
case VMTE_MAKE_INVALID:
+ case SPI_VIST_MAKE_VALID:
+ case LPI_VIST_MAKE_VALID:
+ case LPI_VIST_MAKE_INVALID:
/* Not yet implemented */
default:
return -EINVAL;
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index 27b13bf2c1e2c..74152d22de070 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -659,6 +659,9 @@ enum gicv5_vcpu_cmd {
VMT_L2_MAP, /* Map in a L2 VMT - *may* happen on VM init */
VMTE_MAKE_VALID, /* Make the VMTE valid */
VMTE_MAKE_INVALID, /* Make the VMTE (et al.) invalid */
+ SPI_VIST_MAKE_VALID, /* No corresponding invalid */
+ LPI_VIST_MAKE_VALID, /* Triggered by a guest */
+ LPI_VIST_MAKE_INVALID, /* Triggered by a guest */
};
#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 11/49] KVM: arm64: gic-v5: Implement VMT/vIST IRS MMIO Ops
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (9 preceding siblings ...)
2026-08-07 11:17 ` [PATCH v5 10/49] KVM: arm64: gic-v5: Introduce guest IST alloc and management Sascha Bischoff
@ 2026-08-07 11:18 ` Sascha Bischoff
2026-08-07 11:18 ` [PATCH v5 12/49] KVM: arm64: gic-v5: Keep GICv5 vCPU limit model-specific Sascha Bischoff
` (37 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:18 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
GICv5 has rules about which fields of a VMTE (or L1 VMT) may be
directly written by the host once the table is valid. This ensures
that no stale state is cached by the hardware, and provides a clear
interface for making VMs, ISTs, etc. valid.
The hypervisor is responsible for populating the VMTE for a VM.
However, it is not permitted to write the Valid bit, as the VM table
is already valid. Instead, the VM is made valid via an IRS MMIO
operation. The same applies to the ISTs: they must be made valid via
the host IRS.
This commit adds support for:
* Making level 1 VMTs valid, allowing for dynamic level 2 array
allocation
* Making VMTEs, and hence VMs, valid or invalid
* Making SPI ISTs valid, and LPI ISTs valid or invalid, for a specific
VM
As part of this commit, the following vcpu_affinity-based commands are
plumbed in:
VMT_L2_MAP - Make a second level VM table valid
VMTE_MAKE_VALID - Make a single VMTE, and hence VM, valid
VMTE_MAKE_INVALID - Make a single VMTE, and hence VM, invalid
SPI_VIST_MAKE_VALID - Make the SPI IST valid
LPI_VIST_MAKE_VALID - Make the LPI IST valid
LPI_VIST_MAKE_INVALID - Make the LPI IST invalid
The lack of SPI_VIST_MAKE_INVALID is intentional.
When successfully probing for a GICv5, allocate the VMT and make it
valid through the IRS MMIO interface. Treat failures while allocating
or assigning the VMT as hard GICv5 probe failures. At that point the
IRS VM table state is a prerequisite for vGICv5 operation, and falling
back to the legacy path would leave the host without a valid GICv5 VM
table setup.
Conversely, if vGICv5 registration succeeds but registration of the
optional legacy vGICv3 device fails, retain the working vGICv5 device
rather than failing the entire probe.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-v5-tables.c | 56 +++++---
arch/arm64/kvm/vgic/vgic-v5-tables.h | 1 +
arch/arm64/kvm/vgic/vgic-v5.c | 192 ++++++++++++++++++++++++++-
3 files changed, 225 insertions(+), 24 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.c b/arch/arm64/kvm/vgic/vgic-v5-tables.c
index f088006f39674..b143f15fd7d47 100644
--- a/arch/arm64/kvm/vgic/vgic-v5-tables.c
+++ b/arch/arm64/kvm/vgic/vgic-v5-tables.c
@@ -73,6 +73,21 @@ static DEFINE_XARRAY(vm_info);
#define GICV5_VMTEL2_LPI_SECTION 2
#define GICV5_VMTEL2_SPI_SECTION 3
+static int vgic_v5_alloc_linear_ist(struct kvm *kvm, bool spi_ist,
+ unsigned int id_bits,
+ unsigned int istsz);
+static int vgic_v5_alloc_l1_ist(struct kvm *kvm, unsigned int id_bits,
+ unsigned int istsz, unsigned int l2_split);
+static int vgic_v5_alloc_l2_ists(struct kvm *kvm, unsigned int id_bits,
+ unsigned int istsz, unsigned int l2_split);
+static int vgic_v5_alloc_two_level_lpi_ist(struct kvm *kvm,
+ unsigned int id_bits,
+ unsigned int istsz,
+ unsigned int l2_split);
+static int vgic_v5_linear_ist_free(struct kvm *kvm, bool spi);
+static int vgic_v5_two_level_ist_free(struct kvm *kvm, bool spi);
+static int vgic_v5_spi_ist_free(struct kvm *kvm);
+
/*
* Our IRS might be coherent or non-coherent. If coherent, we can just emit a
* DSB to ensure that we're in sync. However, when non-coherent, we need to
@@ -561,25 +576,6 @@ int vgic_v5_vmte_init(struct kvm *kvm)
return ret;
}
-/*
- * The following set of forward declarations makes the code layout a *little*
- * clearer as it lets us keep the IST-related code together.
- */
-static int vgic_v5_alloc_linear_ist(struct kvm *kvm, bool spi_ist,
- unsigned int id_bits,
- unsigned int istsz);
-static int vgic_v5_alloc_l1_ist(struct kvm *kvm, unsigned int id_bits,
- unsigned int istsz, unsigned int l2_split);
-static int vgic_v5_alloc_l2_ists(struct kvm *kvm, unsigned int id_bits,
- unsigned int istsz, unsigned int l2_split);
-static int vgic_v5_alloc_two_level_lpi_ist(struct kvm *kvm,
- unsigned int id_bits,
- unsigned int istsz,
- unsigned int l2_split);
-static int vgic_v5_linear_ist_free(struct kvm *kvm, bool spi);
-static int vgic_v5_two_level_ist_free(struct kvm *kvm, bool spi);
-static int vgic_v5_spi_ist_free(struct kvm *kvm);
-
/*
* Release the VMT Entry, freeing up any allocated data structures before
* zeroing the VMTE.
@@ -723,6 +719,18 @@ int vgic_v5_vmte_free_vpe(struct kvm_vcpu *vcpu)
return 0;
}
+phys_addr_t vgic_v5_get_vmt_base(void)
+{
+ phys_addr_t vmt_base;
+
+ if (!vmt_info->two_level)
+ vmt_base = virt_to_phys(vmt_info->linear.vmt_base);
+ else
+ vmt_base = virt_to_phys(vmt_info->l2.vmt_base);
+
+ return vmt_base;
+}
+
/*
* Assign an already allocated IST to the VM by populating the fields in the
* corresponding VMTE. We re-use this code for both an SPI IST and LPI IST, even
@@ -785,8 +793,16 @@ static int vgic_v5_vmte_assign_ist(struct kvm *kvm, phys_addr_t ist_base,
/* Finally, mark the entry as valid */
cmd = spi_ist ? SPI_VIST_MAKE_VALID : LPI_VIST_MAKE_VALID;
ret = irq_set_vcpu_affinity(vgic_v5_vpe_db(vcpu0), &cmd);
+ if (ret) {
+ scoped_guard(raw_spinlock_irqsave, &vgic_v5_irs_lock) {
+ WRITE_ONCE(vmte->val[section], 0ULL);
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+ }
- return ret;
+ return ret;
+ }
+
+ return 0;
}
/*
diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.h b/arch/arm64/kvm/vgic/vgic-v5-tables.h
index a711a247cc6be..38b6f5269a2aa 100644
--- a/arch/arm64/kvm/vgic/vgic-v5-tables.h
+++ b/arch/arm64/kvm/vgic/vgic-v5-tables.h
@@ -106,6 +106,7 @@ extern raw_spinlock_t vgic_v5_irs_lock;
int vgic_v5_vmt_allocate(unsigned int max_vpes);
int vgic_v5_vmt_free(void);
+phys_addr_t vgic_v5_get_vmt_base(void);
int vgic_v5_allocate_vm_id(struct kvm *kvm);
void vgic_v5_release_vm_id(struct kvm *kvm);
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 34f197830611e..89d39b287a615 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -16,6 +16,9 @@
#define ppi_caps kvm_vgic_global_state.vgic_v5_ppi_caps
#define irs_caps kvm_vgic_global_state.vgic_v5_irs_caps
+static int vgic_v5_irs_assign_vmt(bool two_level, u8 vm_id_bits, phys_addr_t vmt_base);
+static int vgic_v5_irs_clear_vmt(void);
+
/*
* Not all PPIs are guaranteed to be implemented for GICv5. Deterermine which
* ones are, and generate a mask.
@@ -43,6 +46,21 @@ static u32 irs_readl_relaxed(const u32 reg_offset)
return readl_relaxed(irs_caps.irs_base + reg_offset);
}
+static void irs_writel_relaxed(const u32 val, const u32 reg_offset)
+{
+ writel_relaxed(val, irs_caps.irs_base + reg_offset);
+}
+
+static u64 irs_readq_relaxed(const u32 reg_offset)
+{
+ return readq_relaxed(irs_caps.irs_base + reg_offset);
+}
+
+static void irs_writeq_relaxed(const u64 val, const u32 reg_offset)
+{
+ writeq_relaxed(val, irs_caps.irs_base + reg_offset);
+}
+
static void vgic_v5_irs_cache_id_regs(const struct gic_kvm_info *info)
{
irs_caps.irs_base = info->gicv5_irs.base;
@@ -63,6 +81,7 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
int ret;
kvm_vgic_global_state.type = VGIC_V5;
+ kvm_vgic_global_state.max_gic_vcpus = VGIC_V5_MAX_CPUS;
kvm_vgic_global_state.vcpu_base = 0;
kvm_vgic_global_state.vctrl_base = NULL;
@@ -83,13 +102,52 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
vgic_v5_irs_cache_id_regs(info);
vgic_v5_get_implemented_ppis();
+ /*
+ * Even if the HW supports more per-VM vCPUs, artificially cap as we
+ * can't use them all.
+ */
kvm_vgic_global_state.max_gic_vcpus = min(vgic_v5_irs_max_vpes(&irs_caps),
VGIC_V5_MAX_CPUS);
+ /*
+ * GICv5 requires a set of tables to be allocated in order to manage
+ * VMs. We allocate them in advance here, which alas means that we
+ * already have to make a decisions regarding the maximum number of VMs
+ * we want to run. For now, we match the maximum number offered by the
+ * hardware, but this might not be a wise choice in the long term.
+ */
+ ret = vgic_v5_vmt_allocate(kvm_vgic_global_state.max_gic_vcpus);
+ if (ret) {
+ kvm_err("Failed to allocate the GICv5 VM tables; no GICv5 support\n");
+ return -ENODEV;
+ }
+
+ /*
+ * We've now allocated the VM table, but the host's IRS doesn't know
+ * about it yet. Provide the base address of the VMT to the IRS, as well
+ * as the number of ID bits that it covers and the structure used
+ * (linear/two-level).
+ */
+ ret = vgic_v5_irs_assign_vmt(vgic_v5_irs_two_level_vmt_support(&irs_caps),
+ ilog2(vgic_v5_irs_max_vms(&irs_caps)),
+ vgic_v5_get_vmt_base());
+ if (ret) {
+ kvm_err("Failed to assign the GICv5 VM tables to the IRS; no GICv5 support\n");
+ if (!vgic_v5_irs_clear_vmt())
+ vgic_v5_vmt_free();
+ return -ENODEV;
+ }
+
ret = kvm_register_vgic_device(KVM_DEV_TYPE_ARM_VGIC_V5);
if (ret) {
kvm_err("Cannot register GICv5 KVM device.\n");
- goto skip_v5;
+ /*
+ * Don't free the VMT itself if the hardware still has a valid
+ * pointer to it.
+ */
+ if (!vgic_v5_irs_clear_vmt())
+ vgic_v5_vmt_free();
+ return -ENODEV;
}
v5_registered = true;
@@ -116,12 +174,13 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
ret = kvm_register_vgic_device(KVM_DEV_TYPE_ARM_VGIC_V3);
if (ret) {
kvm_err("Cannot register GICv3-legacy KVM device.\n");
- return ret;
+ /* vGICv5 should still work */
+ return v5_registered ? 0 : ret;
}
/* We potentially limit the max VCPUs further than we need to here */
kvm_vgic_global_state.max_gic_vcpus = min(VGIC_V3_MAX_CPUS,
- VGIC_V5_MAX_CPUS);
+ kvm_vgic_global_state.max_gic_vcpus);
static_branch_enable(&kvm_vgic_global_state.gicv3_cpuif);
kvm_info("GCIE legacy system register CPU interface\n");
@@ -131,20 +190,145 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
return 0;
}
+/*
+ * Wait for completion of a change in any of IRS_VMT_BASER, IRS_VMAP_L2_VMTR,
+ * IRS_VMAP_VMR, IRS_VMAP_VPER, IRS_VMAP_VISTR, IRS_VMAP_L2_VISTR.
+ */
+static int vgic_v5_irs_wait_for_vm_op(void)
+{
+ return gicv5_wait_for_op_atomic(irs_caps.irs_base,
+ GICV5_IRS_VMT_STATUSR,
+ GICV5_IRS_VMT_STATUSR_IDLE,
+ NULL);
+}
+
+static int vgic_v5_irs_write_vm_mmio_reg(u64 val, u32 offset)
+{
+ int ret;
+
+ lockdep_assert_held(&vgic_v5_irs_lock);
+
+ /* Make sure that we are idle to begin with */
+ ret = vgic_v5_irs_wait_for_vm_op();
+ if (ret)
+ return ret;
+
+ irs_writeq_relaxed(val, offset);
+
+ return vgic_v5_irs_wait_for_vm_op();
+}
+
+static int vgic_v5_irs_assign_vmt(bool two_level, u8 vm_id_bits,
+ phys_addr_t vmt_base)
+{
+ u64 vmt_baser;
+ u32 vmt_cfgr;
+ int ret;
+
+ guard(raw_spinlock_irqsave)(&vgic_v5_irs_lock);
+
+ ret = vgic_v5_irs_wait_for_vm_op();
+ if (ret)
+ return ret;
+
+ vmt_baser = irs_readq_relaxed(GICV5_IRS_VMT_BASER);
+ if (!!FIELD_GET(GICV5_IRS_VMT_BASER_VALID, vmt_baser))
+ return -EBUSY;
+
+ vmt_cfgr = FIELD_PREP(GICV5_IRS_VMT_CFGR_VM_ID_BITS, vm_id_bits);
+ if (two_level)
+ vmt_cfgr |= FIELD_PREP(GICV5_IRS_VMT_CFGR_STRUCTURE,
+ GICV5_IRS_VMT_CFGR_STRUCTURE_TWO_LEVEL);
+
+ irs_writel_relaxed(vmt_cfgr, GICV5_IRS_VMT_CFGR);
+
+ /* The base address is intentionally only masked and not shifted */
+ vmt_baser = FIELD_PREP(GICV5_IRS_VMT_BASER_VALID, true) |
+ (vmt_base & GICV5_IRS_VMT_BASER_ADDR);
+ irs_writeq_relaxed(vmt_baser, GICV5_IRS_VMT_BASER);
+
+ return vgic_v5_irs_wait_for_vm_op();
+}
+
+static int vgic_v5_irs_clear_vmt(void)
+{
+ guard(raw_spinlock_irqsave)(&vgic_v5_irs_lock);
+
+ return vgic_v5_irs_write_vm_mmio_reg(0, GICV5_IRS_VMT_BASER);
+}
+
+static int vgic_v5_irs_vmap_l2_vmt(u16 vm_id)
+{
+ u64 val = FIELD_PREP(GICV5_IRS_VMAP_L2_VMTR_VM_ID, vm_id) |
+ GICV5_IRS_VMAP_L2_VMTR_M;
+
+ return vgic_v5_irs_write_vm_mmio_reg(val, GICV5_IRS_VMAP_L2_VMTR);
+}
+
+static int __vgic_v5_irs_vmap_vm(u16 vm_id, bool unmap)
+{
+ u64 val = FIELD_PREP(GICV5_IRS_VMAP_VMR_VM_ID, vm_id) |
+ FIELD_PREP(GICV5_IRS_VMAP_VMR_U, unmap) |
+ GICV5_IRS_VMAP_VMR_M;
+
+ return vgic_v5_irs_write_vm_mmio_reg(val, GICV5_IRS_VMAP_VMR);
+}
+
+static int vgic_v5_irs_set_vm_valid(u16 vm_id)
+{
+ return __vgic_v5_irs_vmap_vm(vm_id, false);
+}
+
+static int vgic_v5_irs_set_vm_invalid(u16 vm_id)
+{
+ return __vgic_v5_irs_vmap_vm(vm_id, true);
+}
+
+static int __vgic_v5_irs_update_vist_validity(u16 vm_id, bool spi_ist, bool unmap)
+{
+ u8 type = spi_ist ? 0b011 : 0b010;
+ u64 val = FIELD_PREP(GICV5_IRS_VMAP_VISTR_TYPE, type) |
+ FIELD_PREP(GICV5_IRS_VMAP_VISTR_VM_ID, vm_id) |
+ FIELD_PREP(GICV5_IRS_VMAP_VISTR_U, unmap) |
+ GICV5_IRS_VMAP_VISTR_M;
+
+ return vgic_v5_irs_write_vm_mmio_reg(val, GICV5_IRS_VMAP_VISTR);
+}
+
+static int vgic_v5_irs_set_vist_valid(u16 vm_id, bool spi_ist)
+{
+ return __vgic_v5_irs_update_vist_validity(vm_id, spi_ist, false);
+}
+
+/*
+ * LPI ISTs can be invalidated explicitly. SPI ISTs are invalidated by making
+ * the VMTE invalid during teardown.
+ */
+static int vgic_v5_irs_set_vist_invalid(u16 vm_id, bool spi_ist)
+{
+ return __vgic_v5_irs_update_vist_validity(vm_id, spi_ist, true);
+}
+
static int vgic_v5_db_set_vcpu_affinity(struct irq_data *data, void *vcpu_info)
{
+ struct vgic_v5_vm *vm = data->domain->host_data;
enum gicv5_vcpu_cmd *cmd = vcpu_info;
guard(raw_spinlock_irqsave)(&vgic_v5_irs_lock);
switch (*cmd) {
case VMT_L2_MAP:
+ return vgic_v5_irs_vmap_l2_vmt(vm->vm_id);
case VMTE_MAKE_VALID:
+ return vgic_v5_irs_set_vm_valid(vm->vm_id);
case VMTE_MAKE_INVALID:
+ return vgic_v5_irs_set_vm_invalid(vm->vm_id);
case SPI_VIST_MAKE_VALID:
+ return vgic_v5_irs_set_vist_valid(vm->vm_id, true);
case LPI_VIST_MAKE_VALID:
+ return vgic_v5_irs_set_vist_valid(vm->vm_id, false);
case LPI_VIST_MAKE_INVALID:
- /* Not yet implemented */
+ return vgic_v5_irs_set_vist_invalid(vm->vm_id, false);
default:
return -EINVAL;
}
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 12/49] KVM: arm64: gic-v5: Keep GICv5 vCPU limit model-specific
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (10 preceding siblings ...)
2026-08-07 11:18 ` [PATCH v5 11/49] KVM: arm64: gic-v5: Implement VMT/vIST IRS MMIO Ops Sascha Bischoff
@ 2026-08-07 11:18 ` Sascha Bischoff
2026-08-07 11:19 ` [PATCH v5 13/49] KVM: arm64: gic-v5: Implement VPE IRS MMIO Ops Sascha Bischoff
` (36 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:18 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
A GICv5 host with FEAT_GCIE_LEGACY can expose either a native vGICv5
or a vGICv3 device. These models do not necessarily have the same vCPU
limit: the native GICv5 limit is probed from the IRS VPE capacity,
while the GICv3 limit remains the fixed KVM vGICv3 limit.
Keep the IRS-derived limit separately for vGICv5 creation. The
pre-VGIC KVM_CAP_MAX_VCPUS value continues to expose the largest limit
among the still-selectable models, and kvm_vgic_create() clamps the VM
to the limit of the VGIC model userspace actually selected.
GICv5 uses the userspace-provided vcpu_id as the index into the VPET,
and therefore as the VPE ID visible to the guest. Enforce the GICv5
limit against individual vCPU IDs as well as the number of vCPUs.
Reject vGICv5 creation if an existing vCPU ID cannot be represented,
and reject an out-of-range vCPU created after userspace has selected
vGICv5.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-init.c | 53 ++++++++++++++++++++++++++-------
arch/arm64/kvm/vgic/vgic-v5.c | 16 +++++-----
include/kvm/arm_vgic.h | 16 +++++++---
3 files changed, 64 insertions(+), 21 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index 079a57c2b18f6..8e1e844ed6e93 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -129,13 +129,34 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
}
ret = 0;
- if (type == KVM_DEV_TYPE_ARM_VGIC_V2)
+ switch (type) {
+ case KVM_DEV_TYPE_ARM_VGIC_V2:
kvm->max_vcpus = VGIC_V2_MAX_CPUS;
- else if (type == KVM_DEV_TYPE_ARM_VGIC_V3)
+ break;
+ case KVM_DEV_TYPE_ARM_VGIC_V3:
kvm->max_vcpus = VGIC_V3_MAX_CPUS;
- else if (type == KVM_DEV_TYPE_ARM_VGIC_V5)
- kvm->max_vcpus = min(VGIC_V5_MAX_CPUS,
- kvm_vgic_global_state.max_gic_vcpus);
+ break;
+ case KVM_DEV_TYPE_ARM_VGIC_V5:
+ kvm->max_vcpus = kvm_vgic_global_state.max_gicv5_vcpus;
+
+ /*
+ * We use the userspace-allocated vcpu_id as the index into the
+ * GICv5 VPE table. This ensures that userspace's view and the
+ * guest's view of IAFFIDs remains consistent - GICv5's virtual
+ * IAFFID is the VPE ID in that VM's VPET.
+ *
+ * Reject vGICv5 creation if we have already got any vCPUs that
+ * we cannot back with a GICv5 VPE in the VPET.
+ */
+ kvm_for_each_vcpu(i, vcpu, kvm) {
+ if (vcpu->vcpu_id >= kvm->max_vcpus) {
+ ret = -E2BIG;
+ goto out_unlock;
+ }
+ }
+
+ break;
+ }
if (atomic_read(&kvm->online_vcpus) > kvm->max_vcpus) {
ret = -E2BIG;
@@ -395,15 +416,27 @@ int kvm_vgic_vcpu_init(struct kvm_vcpu *vcpu)
if (ret)
return ret;
- /*
- * If we are creating a VCPU with a GICv3 we must also register the
- * KVM io device for the redistributor that belongs to this VCPU.
- */
- if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3) {
+ switch (dist->vgic_model) {
+ case KVM_DEV_TYPE_ARM_VGIC_V3:
+ /*
+ * If we are creating a VCPU with a GICv3 we must also register
+ * the KVM io device for the redistributor that belongs to this
+ * VCPU.
+ */
mutex_lock(&vcpu->kvm->slots_lock);
ret = vgic_register_redist_iodev(vcpu);
mutex_unlock(&vcpu->kvm->slots_lock);
+ break;
+ case KVM_DEV_TYPE_ARM_VGIC_V5:
+ /*
+ * Ensure that it is possible to represent the
+ * userspace-allocated vcpu_id in the hardware-limited (or
+ * KVM-capped) VPE table used by GICv5.
+ */
+ if (vcpu->vcpu_id >= kvm_vgic_global_state.max_gicv5_vcpus)
+ return -EINVAL;
}
+
return ret;
}
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 89d39b287a615..b9257e0554b73 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -81,7 +81,8 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
int ret;
kvm_vgic_global_state.type = VGIC_V5;
- kvm_vgic_global_state.max_gic_vcpus = VGIC_V5_MAX_CPUS;
+ kvm_vgic_global_state.max_gic_vcpus = 0;
+ kvm_vgic_global_state.max_gicv5_vcpus = 0;
kvm_vgic_global_state.vcpu_base = 0;
kvm_vgic_global_state.vctrl_base = NULL;
@@ -106,8 +107,8 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
* Even if the HW supports more per-VM vCPUs, artificially cap as we
* can't use them all.
*/
- kvm_vgic_global_state.max_gic_vcpus = min(vgic_v5_irs_max_vpes(&irs_caps),
- VGIC_V5_MAX_CPUS);
+ kvm_vgic_global_state.max_gicv5_vcpus = min(vgic_v5_irs_max_vpes(&irs_caps),
+ VGIC_V5_MAX_CPUS);
/*
* GICv5 requires a set of tables to be allocated in order to manage
@@ -116,7 +117,7 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
* we want to run. For now, we match the maximum number offered by the
* hardware, but this might not be a wise choice in the long term.
*/
- ret = vgic_v5_vmt_allocate(kvm_vgic_global_state.max_gic_vcpus);
+ ret = vgic_v5_vmt_allocate(kvm_vgic_global_state.max_gicv5_vcpus);
if (ret) {
kvm_err("Failed to allocate the GICv5 VM tables; no GICv5 support\n");
return -ENODEV;
@@ -151,6 +152,8 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
}
v5_registered = true;
+ kvm_vgic_global_state.max_gic_vcpus =
+ kvm_vgic_global_state.max_gicv5_vcpus;
kvm_info("GCIE system register CPU interface\n");
skip_v5:
@@ -178,9 +181,8 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
return v5_registered ? 0 : ret;
}
- /* We potentially limit the max VCPUs further than we need to here */
- kvm_vgic_global_state.max_gic_vcpus = min(VGIC_V3_MAX_CPUS,
- kvm_vgic_global_state.max_gic_vcpus);
+ kvm_vgic_global_state.max_gic_vcpus = max(kvm_vgic_global_state.max_gic_vcpus,
+ VGIC_V3_MAX_CPUS);
static_branch_enable(&kvm_vgic_global_state.gicv3_cpuif);
kvm_info("GCIE legacy system register CPU interface\n");
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 7923ff20d9d7d..6d0cc38efa673 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -170,9 +170,16 @@ struct vgic_global {
/* Maintenance IRQ number */
unsigned int maint_irq;
- /* maximum number of VCPUs allowed (GICv2 limits us to 8) */
+ /*
+ * Maximum number of VCPUs exposed before userspace has selected a
+ * VGIC model. Individual VGIC models can impose a lower limit
+ * (GICv2 limits us to 8).
+ */
int max_gic_vcpus;
+ /* Maximum number of VCPUs allowed for a GICv5 VM. */
+ int max_gicv5_vcpus;
+
/* Only needed for the legacy KVM_CREATE_IRQCHIP */
bool can_emulate_gicv2;
@@ -622,10 +629,11 @@ void kvm_vgic_process_async_update(struct kvm_vcpu *vcpu);
void vgic_v3_dispatch_sgi(struct kvm_vcpu *vcpu, u64 reg, bool allow_group1);
/**
- * kvm_vgic_get_max_vcpus - Get the maximum number of VCPUs allowed by HW
+ * kvm_vgic_get_max_vcpus - Get the pre-VGIC-selection VCPU limit
*
- * The host's GIC naturally limits the maximum amount of VCPUs a guest
- * can use.
+ * Userspace can query KVM_CAP_MAX_VCPUS before selecting a VGIC model, so
+ * expose the highest model-specific limit and let kvm_vgic_create() enforce
+ * the selected model's actual limit.
*/
static inline int kvm_vgic_get_max_vcpus(void)
{
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 13/49] KVM: arm64: gic-v5: Implement VPE IRS MMIO Ops
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (11 preceding siblings ...)
2026-08-07 11:18 ` [PATCH v5 12/49] KVM: arm64: gic-v5: Keep GICv5 vCPU limit model-specific Sascha Bischoff
@ 2026-08-07 11:19 ` Sascha Bischoff
2026-08-07 11:19 ` [PATCH v5 14/49] KVM: arm64: gic-v5: Set up VMTEs and VPE doorbells Sascha Bischoff
` (35 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:19 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Introduce interfaces to make VPEs valid, and to configure them, via
the host's IRS. As with the other valid bits in the GICv5 VM tables,
VPEs cannot be made valid directly, and instead are made valid via an
IRS MMIO Op.
Additionally, some of the VPE configuration takes place via the IRS
MMIO interface too (via the IRS_VPE_CR0, IRS_VPE_DBR). VPE doorbells
are, for example, configured via this interface.
The existing VPE-doorbell-based commands are extended with:
VPE_MAKE_VALID - Make the VPE valid in the VPET
Note: There is no VPE_MAKE_INVALID as VPEs are only made invalid on
teardown, at which point the whole VMTE is marked as invalid. Hence,
it is not required.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-v5.c | 84 ++++++++++++++++++++++++++++++
include/linux/irqchip/arm-gic-v5.h | 1 +
2 files changed, 85 insertions(+)
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index b9257e0554b73..2121f67d3b59c 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -204,6 +204,18 @@ static int vgic_v5_irs_wait_for_vm_op(void)
NULL);
}
+/*
+ * Wait for completion of a change in any of IRS_VPE_SELR, IRS_VPE_DBR,
+ * IRS_VPE_CR0.
+ */
+static int vgic_v5_irs_wait_for_vpe_op(void)
+{
+ return gicv5_wait_for_op_atomic(irs_caps.irs_base,
+ GICV5_IRS_VPE_STATUSR,
+ GICV5_IRS_VPE_STATUSR_IDLE,
+ NULL);
+}
+
static int vgic_v5_irs_write_vm_mmio_reg(u64 val, u32 offset)
{
int ret;
@@ -311,10 +323,73 @@ static int vgic_v5_irs_set_vist_invalid(u16 vm_id, bool spi_ist)
return __vgic_v5_irs_update_vist_validity(vm_id, spi_ist, true);
}
+static int vgic_v5_irs_set_up_vpe(u16 vm_id, u16 vpe_id,
+ irq_hw_number_t db_hwirq)
+{
+ u64 vmap_vper, dbr, selr;
+ u32 statusr, cr0;
+ int ret;
+
+ lockdep_assert_held(&vgic_v5_irs_lock);
+
+ /* Make sure that we are idle to begin with */
+ ret = vgic_v5_irs_wait_for_vm_op();
+ if (ret)
+ return ret;
+
+ /* Mark the VPE as valid */
+ vmap_vper = FIELD_PREP(GICV5_IRS_VMAP_VPER_VPE_ID, vpe_id) |
+ FIELD_PREP(GICV5_IRS_VMAP_VPER_VM_ID, vm_id) |
+ GICV5_IRS_VMAP_VPER_M;
+ irs_writeq_relaxed(vmap_vper, GICV5_IRS_VMAP_VPER);
+
+ /* Wait for the VPE to be marked valid in the VPET */
+ ret = vgic_v5_irs_wait_for_vm_op();
+ if (ret)
+ return ret;
+
+ selr = FIELD_PREP(GICV5_IRS_VPE_SELR_VPE_ID, vpe_id) |
+ FIELD_PREP(GICV5_IRS_VPE_SELR_VM_ID, vm_id) |
+ GICV5_IRS_VPE_SELR_S;
+ irs_writeq_relaxed(selr, GICV5_IRS_VPE_SELR);
+
+ ret = vgic_v5_irs_wait_for_vpe_op();
+ if (ret)
+ return ret;
+
+ statusr = irs_readl_relaxed(GICV5_IRS_VPE_STATUSR);
+ if (!FIELD_GET(GICV5_IRS_VPE_STATUSR_V, statusr))
+ return -EINVAL;
+
+ /* Set targeted only routing (disable 1ofN vPE selection) */
+ cr0 = GICV5_IRS_VPE_CR0_DPS;
+ irs_writel_relaxed(cr0, GICV5_IRS_VPE_CR0);
+
+ ret = vgic_v5_irs_wait_for_vpe_op();
+ if (ret)
+ return ret;
+
+ /*
+ * The VPE has not yet run. Therefore, make sure that all interrupts
+ * will generate a doorbell.
+ */
+ dbr = FIELD_PREP(GICV5_IRS_VPE_DBR_INTID, db_hwirq) |
+ GICV5_IRS_VPE_DBR_DBV;
+ irs_writeq_relaxed(dbr, GICV5_IRS_VPE_DBR);
+
+ ret = vgic_v5_irs_wait_for_vpe_op();
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
static int vgic_v5_db_set_vcpu_affinity(struct irq_data *data, void *vcpu_info)
{
struct vgic_v5_vm *vm = data->domain->host_data;
enum gicv5_vcpu_cmd *cmd = vcpu_info;
+ /* Our VPE ID is the index within the doorbell domain */
+ u16 vpe_id = data->hwirq;
guard(raw_spinlock_irqsave)(&vgic_v5_irs_lock);
@@ -325,6 +400,15 @@ static int vgic_v5_db_set_vcpu_affinity(struct irq_data *data, void *vcpu_info)
return vgic_v5_irs_set_vm_valid(vm->vm_id);
case VMTE_MAKE_INVALID:
return vgic_v5_irs_set_vm_invalid(vm->vm_id);
+ case VPE_MAKE_VALID:
+ /*
+ * We need the actual LPI ID which lives in the top-most parent
+ * domain. This hwirq won't include the type (LPI) but that's
+ * not required for the IRS_VPE_DBR.
+ */
+ while (data->parent_data)
+ data = data->parent_data;
+ return vgic_v5_irs_set_up_vpe(vm->vm_id, vpe_id, data->hwirq);
case SPI_VIST_MAKE_VALID:
return vgic_v5_irs_set_vist_valid(vm->vm_id, true);
case LPI_VIST_MAKE_VALID:
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index 74152d22de070..8b6c497d3b590 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -659,6 +659,7 @@ enum gicv5_vcpu_cmd {
VMT_L2_MAP, /* Map in a L2 VMT - *may* happen on VM init */
VMTE_MAKE_VALID, /* Make the VMTE valid */
VMTE_MAKE_INVALID, /* Make the VMTE (et al.) invalid */
+ VPE_MAKE_VALID, /* No corresponding invalid */
SPI_VIST_MAKE_VALID, /* No corresponding invalid */
LPI_VIST_MAKE_VALID, /* Triggered by a guest */
LPI_VIST_MAKE_INVALID, /* Triggered by a guest */
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 14/49] KVM: arm64: gic-v5: Set up VMTEs and VPE doorbells
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (12 preceding siblings ...)
2026-08-07 11:19 ` [PATCH v5 13/49] KVM: arm64: gic-v5: Implement VPE IRS MMIO Ops Sascha Bischoff
@ 2026-08-07 11:19 ` Sascha Bischoff
2026-08-07 11:20 ` [PATCH v5 15/49] KVM: arm64: gic-v5: Add resident/non-resident hyp calls Sascha Bischoff
` (34 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:19 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
A GICv5 VM needs a VM table entry before it can use SPIs and LPIs,
which are backed by the host IRS. The VM table itself is created at
probe time, but each VM still needs to claim and populate one VMTE
before it can use those interrupts.
VPE doorbells are allocated from the host LPI irq domain. Without that
domain, KVM cannot issue IRS commands or receive doorbell wakeups.
Fail the GICv5 KVM probe if the host driver did not create an LPI
domain.
Allocate a VM ID during vgic_v5_init(). The VM ID is also the index
into the VM table, so allocating it selects the VMTE slot that will be
used for the lifetime of the VM.
Create a per-VM VPE doorbell irq domain, allocate one doorbell
interrupt per vCPU, request the interrupts, and keep the doorbell IRQ
number in the vCPU's GICv5 state. The doorbell handler marks the VPE
doorbell as fired, raises KVM_REQ_IRQ_PENDING, and kicks the target
vCPU so that KVM can re-evaluate pending interrupt state.
The doorbell domain indexes its interrupts using the dense vcpu_idx,
while GICv5 uses the userspace-provided vcpu_id as the VPE ID. Store a
backpointer to the VM and use it to resolve the vCPU before issuing
VPE-specific IRS commands. This preserves the userspace VPE ID when
vCPU IDs are sparse.
With the VM ID and doorbells in place, initialise the VMTE backing
state, including the VM descriptor, VPE table, and preallocated VPED
storage. The doorbells have to exist before making the VMTE valid, as
they provide the IRQ-side conduit used by the IRS commands. Make the
VMTE valid via the IRS, then populate the VPETE for each vCPU.
Add vgic_v5_teardown() to unwind the state in the reverse order. Make
the VMTE invalid, clear the per-vCPU VPETEs, release the VMTE backing
state, free the doorbell IRQs and irq domain, and finally release the
VM ID so that the VMTE slot can be reused by a later VM. If hardware
invalidation or VMTE release fails, still free the software doorbells
and domain, but keep the VM ID allocated so that the VMTE slot is not
reused while hardware-visible state may remain.
On init failure, call the same teardown path so that partially created
state is unwound consistently.
As part of resetting vCPUs, mark them as valid in the VM's VPE table.
This informs the IRS that a specific VPE may be made resident. Without
this, the IRS will treat the VPE as invalid.
Also introduce vgic_v5_send_command(), a wrapper around the VPE
doorbells. It takes a struct kvm_vcpu pointer and the command to run,
and invokes the function bound to that command through the vCPU's
doorbell.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-v5.c | 228 +++++++++++++++++++++++++++++-----
include/kvm/arm_vgic.h | 2 +
2 files changed, 197 insertions(+), 33 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 2121f67d3b59c..f15a8309d7a93 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -100,6 +100,11 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
goto skip_v5;
}
+ if (!gicv5_global_data.lpi_domain) {
+ kvm_err("GICv5 LPI domain unavailable\n");
+ return -ENODEV;
+ }
+
vgic_v5_irs_cache_id_regs(info);
vgic_v5_get_implemented_ppis();
@@ -384,12 +389,35 @@ static int vgic_v5_irs_set_up_vpe(u16 vm_id, u16 vpe_id,
return 0;
}
+static irqreturn_t db_handler(int irq, void *data)
+{
+ struct kvm_vcpu *vcpu = data;
+
+ WRITE_ONCE(vcpu->arch.vgic_cpu.vgic_v5.gicv5_vpe.db_fired, true);
+
+ kvm_make_request(KVM_REQ_IRQ_PENDING, vcpu);
+ kvm_vcpu_kick(vcpu);
+
+ return IRQ_HANDLED;
+}
+
+static int vgic_v5_send_command(struct kvm_vcpu *vcpu, enum gicv5_vcpu_cmd cmd)
+{
+ int irq = vgic_v5_vpe_db(vcpu);
+
+ if (!irq)
+ return -ENXIO;
+
+ return irq_set_vcpu_affinity(irq, &cmd);
+}
+
static int vgic_v5_db_set_vcpu_affinity(struct irq_data *data, void *vcpu_info)
{
struct vgic_v5_vm *vm = data->domain->host_data;
enum gicv5_vcpu_cmd *cmd = vcpu_info;
- /* Our VPE ID is the index within the doorbell domain */
- u16 vpe_id = data->hwirq;
+ unsigned int vcpu_idx = data->hwirq;
+ struct kvm_vcpu *vcpu;
+ u16 vpe_id;
guard(raw_spinlock_irqsave)(&vgic_v5_irs_lock);
@@ -401,6 +429,17 @@ static int vgic_v5_db_set_vcpu_affinity(struct irq_data *data, void *vcpu_info)
case VMTE_MAKE_INVALID:
return vgic_v5_irs_set_vm_invalid(vm->vm_id);
case VPE_MAKE_VALID:
+ /*
+ * The index in the doorbell domain aligns with our flat
+ * vcpu_idx index. However, we need the actual VPE ID, which
+ * means we first need to resolve the actual vcpu.
+ */
+ vcpu = kvm_get_vcpu(vm->kvm, vcpu_idx);
+ if (!vcpu)
+ return -EINVAL;
+
+ vpe_id = vgic_v5_vpe_id(vcpu);
+
/*
* We need the actual LPI ID which lives in the top-most parent
* domain. This hwirq won't include the type (LPI) but that's
@@ -456,15 +495,9 @@ static int vgic_v5_irq_db_domain_alloc(struct irq_domain *domain,
void *arg)
{
const struct irq_chip *chip = &vgic_v5_db_irq_chip;
- struct vgic_v5_vm *vm = arg;
struct irq_data *irqd;
int ret;
- if (!vm) {
- kvm_err("invalid parameter for doorbell irq allocation\n");
- return -EINVAL;
- }
-
ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, NULL);
if (ret)
return ret;
@@ -491,20 +524,10 @@ static int vgic_v5_create_per_vm_domain(struct kvm *kvm)
int id = task_pid_nr(current);
int ret, db_virq = 0;
- if (!gicv5_global_data.lpi_domain) {
- kvm_err("LPI domain uninitialized, can't set up KVM Doorbells\n");
- return -ENODEV;
- }
-
vm->fwnode = irq_domain_alloc_named_id_fwnode("GICv5-vpe-db", id);
if (!vm->fwnode)
return -ENOMEM;
- /*
- * KVM per-VM VPE DB domain; child of LPI domain; only ever handles
- * doorbells. We know how many doorbells we have, and therefore we
- * create a linear domain.
- */
vm->domain = irq_domain_create_hierarchy(gicv5_global_data.lpi_domain,
0, nr_vcpus, vm->fwnode,
&vgic_v5_irq_db_domain_ops, vm);
@@ -543,11 +566,6 @@ static void vgic_v5_teardown_per_vm_domain(struct vgic_v5_vm *vm)
if (!vm->domain)
return;
- if (vm->vpe_db_base) {
- irq_domain_free_irqs(vm->vpe_db_base, vm->domain->revmap_size);
- vm->vpe_db_base = 0;
- }
-
irq_domain_remove(vm->domain);
irq_domain_free_fwnode(vm->fwnode);
vm->domain = NULL;
@@ -567,33 +585,121 @@ void vgic_v5_reset(struct kvm_vcpu *vcpu)
* CPUIF (but potentially fewer in the IRS).
*/
vcpu->arch.vgic_cpu.num_pri_bits = 5;
+
+ /* Make the VPE valid in the VPET */
+ if (WARN_ON(vgic_v5_send_command(vcpu, VPE_MAKE_VALID)))
+ return;
+}
+
+static void vgic_v5_free_doorbells(struct kvm *kvm, unsigned int nr_dbs)
+{
+ struct vgic_v5_vm *vm = &kvm->arch.vgic.gicv5_vm;
+ struct kvm_vcpu *vcpu;
+ unsigned long i;
+ int db;
+
+ for (i = 0; i < nr_dbs; i++) {
+ vcpu = kvm_get_vcpu(kvm, i);
+ db = vgic_v5_vpe_db(vcpu);
+ if (!db)
+ continue;
+
+ free_irq(db, vcpu);
+ vcpu->arch.vgic_cpu.vgic_v5.gicv5_vpe.db = 0;
+ }
+
+ if (vm->vpe_db_base) {
+ irq_domain_free_irqs(vm->vpe_db_base,
+ atomic_read(&kvm->online_vcpus));
+ vm->vpe_db_base = 0;
+ }
}
void vgic_v5_teardown(struct kvm *kvm)
{
+ struct vgic_dist *dist = &kvm->arch.vgic;
+ struct kvm_vcpu *vcpu, *vcpu0;
+ bool release_vm_id = true;
+ unsigned long i;
+ int rc;
+
+ lockdep_assert_held(&kvm->arch.config_lock);
+
+ /*
+ * If the VM's ID isn't valid, then we either failed init very early or
+ * we've been called a second time. Nothing to do here in either case.
+ */
+ if (kvm->arch.vgic.gicv5_vm.vm_id == VGIC_V5_VM_ID_INVAL)
+ return;
+
+ if (kvm->arch.vgic.gicv5_vm.vmte_allocated) {
+ /* Make the VM invalid */
+ vcpu0 = kvm_get_vcpu(kvm, 0);
+ rc = vgic_v5_send_command(vcpu0, VMTE_MAKE_INVALID);
+ if (rc) {
+ kvm_err("could not make VMTE invalid\n");
+ release_vm_id = false;
+ goto out_free_doorbells;
+ }
+
+ kvm_for_each_vcpu(i, vcpu, kvm) {
+ if (vgic_v5_vmte_free_vpe(vcpu)) {
+ kvm_err("Failed to free VPE\n");
+ release_vm_id = false;
+ }
+ }
+
+ if (vgic_v5_vmte_release(kvm)) {
+ kvm_err("Failed to release VM 0x%x\n", dist->gicv5_vm.vm_id);
+ release_vm_id = false;
+ }
+ }
+
+out_free_doorbells:
+ vgic_v5_free_doorbells(kvm, atomic_read(&kvm->online_vcpus));
vgic_v5_teardown_per_vm_domain(&kvm->arch.vgic.gicv5_vm);
+
+ /*
+ * We only release the VM ID itself if we didn't fail earlier. It does
+ * mean that we might lose the VM ID (and associated VMTE, etc), but
+ * given that we've failed to tear them down correctly there's no way to
+ * safely reuse them. The VM ID allocating IDA will make sure we don't
+ * accidentally reuse this partially torn down state.
+ */
+ if (release_vm_id)
+ vgic_v5_release_vm_id(kvm);
}
+/*
+ * Claim and populate a VMTE (optionally making a new L2 VMT valid), create VPE
+ * doorbells, allocate VPET and populate for each VPE.
+ *
+ * Note: We do need to put the cart before the horse here. The VPE doorbells are
+ * our conduit for communication with the IRS, which means we need to have those
+ * before making the VMTE valid.
+ *
+ * On failure, we clean up in the teardown path (vgic_v5_teardown()).
+ */
int vgic_v5_init(struct kvm *kvm)
{
- struct kvm_vcpu *vcpu;
- unsigned long idx;
- int ret;
+ struct kvm_vcpu *vcpu, *vcpu0;
+ int nr_vcpus, ret = 0;
+ unsigned int db_virq;
+ unsigned long i;
- if (vgic_initialized(kvm))
- return 0;
+ lockdep_assert_held(&kvm->arch.config_lock);
+
+ nr_vcpus = atomic_read(&kvm->online_vcpus);
+ if (nr_vcpus == 0)
+ return -ENODEV;
- kvm_for_each_vcpu(idx, vcpu, kvm) {
+ kvm_for_each_vcpu(i, vcpu, kvm) {
if (vcpu_has_nv(vcpu)) {
kvm_err("Nested GICv5 VMs are currently unsupported\n");
return -EINVAL;
}
}
- ret = vgic_v5_create_per_vm_domain(kvm);
- if (ret)
- return ret;
-
/* We only allow userspace to drive the SW_PPI, if it is implemented. */
bitmap_zero(kvm->arch.vgic.gicv5_vm.userspace_ppis,
VGIC_V5_NR_PRIVATE_IRQS);
@@ -602,7 +708,63 @@ int vgic_v5_init(struct kvm *kvm)
kvm->arch.vgic.gicv5_vm.userspace_ppis,
ppi_caps.impl_ppi_mask, VGIC_V5_NR_PRIVATE_IRQS);
+ ret = vgic_v5_allocate_vm_id(kvm);
+ if (ret)
+ return ret;
+
+ /*
+ * Stash a backpointer to struct kvm. It is required to resolve the VPE
+ * ID from the doorbell index, which matches the flat vcpu_idx and not
+ * the vcpu_id.
+ */
+ kvm->arch.vgic.gicv5_vm.kvm = kvm;
+
+ ret = vgic_v5_create_per_vm_domain(kvm);
+ if (ret)
+ goto err;
+
+ db_virq = kvm->arch.vgic.gicv5_vm.vpe_db_base;
+ kvm_for_each_vcpu(i, vcpu, kvm) {
+ ret = request_irq(db_virq + i, db_handler, 0, "vcpu", vcpu);
+ if (ret)
+ goto err;
+
+ /* Stash it with the VCPU for easy retrieval */
+ vcpu->arch.vgic_cpu.vgic_v5.gicv5_vpe.db = db_virq + i;
+ }
+
+ /* Populate VMTE (with VPET and VM descriptor) */
+ ret = vgic_v5_vmte_init(kvm);
+ if (ret)
+ goto err;
+
+ /* We pick the first vcpu to make the VMTE valid - any would do */
+ vcpu0 = kvm_get_vcpu(kvm, 0);
+ ret = vgic_v5_send_command(vcpu0, VMTE_MAKE_VALID);
+ if (ret)
+ goto err;
+
+ /* Populate the VPETE for each VPE. */
+ kvm_for_each_vcpu(i, vcpu, kvm) {
+ ret = vgic_v5_vmte_alloc_vpe(vcpu);
+ if (ret)
+ goto err;
+ }
+
return 0;
+
+err:
+ /*
+ * Explicitly tear everything down on failure. The teardown function is
+ * written to handle any partial state we might have, so we don't need
+ * to do any clean-up first. Teardown will be called a second time on VM
+ * destruction, but that's fine - it is better to leave things in a
+ * clean state now, and doubly so because userspace could actually go
+ * and retry init.
+ */
+ vgic_v5_teardown(kvm);
+
+ return ret;
}
int vgic_v5_map_resources(struct kvm *kvm)
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 6d0cc38efa673..7fece54ff0925 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -371,6 +371,8 @@ struct vgic_redist_region {
#define VGIC_V5_VM_ID_INVAL (-1)
struct vgic_v5_vm {
+ struct kvm *kvm;
+
/*
* We only expose a subset of PPIs to the guest. This subset is a
* combination of the PPIs that are actually implemented and what we
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 15/49] KVM: arm64: gic-v5: Add resident/non-resident hyp calls
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (13 preceding siblings ...)
2026-08-07 11:19 ` [PATCH v5 14/49] KVM: arm64: gic-v5: Set up VMTEs and VPE doorbells Sascha Bischoff
@ 2026-08-07 11:20 ` Sascha Bischoff
2026-08-07 11:20 ` [PATCH v5 16/49] KVM: arm64: gic-v5: Request doorbells when VPEs enter WFI Sascha Bischoff
` (33 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:20 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
GICv5 introduces the concept of VPE residency - a VPE can be either
resident or non-resident. When the VPE is resident, the IRS is allowed
to select interrupts that target that VPE (or the VM) as the HPPI
(Highest Priority Pending Interrupt). As the IRS handles both SPIs and
LPIs, these will only be picked as the IRS's HPPI when a VPE is
resident.
A GICv5 VPE is made resident by writing ICH_CONTEXTR_EL2 with
ICH_CONTEXTR_EL2.V set, together with valid VM and VPE IDs. This
informs the IRS that a specific VPE is running, and that it can begin
HPPI selection for that VPE. Making a VPE non-resident (by making the
ICH_CONTEXTR_EL2 invalid) informs the IRS that the VPE is no longer
running, and it stops HPPI selection for it.
This change introduces two new hyp calls - one to make a VPE resident
and its counterpart to make a VPE non-resident. As part of making a
VPE resident, the resulting ICH_CONTEXTR_EL2.F bit is checked to catch
residency faults. Such a fault indicates a broken VM/VPE setup, so
warn and mark the VM dead.
Both of these new hypercalls are explicitly no-ops with pKVM as we
currently don't support the combination of GICv5 and pKVM.
Furthermore, this change extends vgic_v5_load() and vgic_v5_put() to
make the VPEs resident and non-resident, respectively. Hence, the VPE
is considered resident for the entire load-to-put interval.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/include/asm/kvm_asm.h | 2 ++
arch/arm64/include/asm/kvm_hyp.h | 2 ++
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 22 ++++++++++++++++++++++
arch/arm64/kvm/hyp/vgic-v5-sr.c | 26 ++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic-v5.c | 16 ++++++++++++++--
include/kvm/arm_vgic.h | 3 +++
6 files changed, 69 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_asm.h b/arch/arm64/include/asm/kvm_asm.h
index 043495f7fc78b..d9ff9c2999aa7 100644
--- a/arch/arm64/include/asm/kvm_asm.h
+++ b/arch/arm64/include/asm/kvm_asm.h
@@ -87,6 +87,8 @@ enum __kvm_host_smccc_func {
__KVM_HOST_SMCCC_FUNC___tracing_write_event,
__KVM_HOST_SMCCC_FUNC___vgic_v3_save_aprs,
__KVM_HOST_SMCCC_FUNC___vgic_v3_restore_vmcr_aprs,
+ __KVM_HOST_SMCCC_FUNC___vgic_v5_make_resident,
+ __KVM_HOST_SMCCC_FUNC___vgic_v5_make_non_resident,
__KVM_HOST_SMCCC_FUNC___vgic_v5_save_apr,
__KVM_HOST_SMCCC_FUNC___vgic_v5_restore_vmcr_apr,
diff --git a/arch/arm64/include/asm/kvm_hyp.h b/arch/arm64/include/asm/kvm_hyp.h
index 4974492744cc8..8fcbbff719cce 100644
--- a/arch/arm64/include/asm/kvm_hyp.h
+++ b/arch/arm64/include/asm/kvm_hyp.h
@@ -88,6 +88,8 @@ void __vgic_v3_restore_vmcr_aprs(struct vgic_v3_cpu_if *cpu_if);
int __vgic_v3_perform_cpuif_access(struct kvm_vcpu *vcpu);
/* GICv5 */
+void __vgic_v5_make_resident(struct vgic_v5_cpu_if *cpu_if);
+void __vgic_v5_make_non_resident(struct vgic_v5_cpu_if *cpu_if);
void __vgic_v5_save_apr(struct vgic_v5_cpu_if *cpu_if);
void __vgic_v5_restore_vmcr_apr(struct vgic_v5_cpu_if *cpu_if);
/* No hypercalls for the following */
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index d3df96ed8ba42..7fdcc6bb6f594 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -691,6 +691,26 @@ static void handle___tracing_write_event(struct kvm_cpu_context *host_ctxt)
trace_selftest(id);
}
+static void handle___vgic_v5_make_resident(struct kvm_cpu_context *host_ctxt)
+{
+ DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
+
+ if (unlikely(is_protected_kvm_enabled()))
+ return;
+
+ __vgic_v5_make_resident(kern_hyp_va(cpu_if));
+}
+
+static void handle___vgic_v5_make_non_resident(struct kvm_cpu_context *host_ctxt)
+{
+ DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
+
+ if (unlikely(is_protected_kvm_enabled()))
+ return;
+
+ __vgic_v5_make_non_resident(kern_hyp_va(cpu_if));
+}
+
static void handle___vgic_v5_save_apr(struct kvm_cpu_context *host_ctxt)
{
DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
@@ -738,6 +758,8 @@ static const hcall_t host_hcall[] = {
HANDLE_FUNC(__tracing_write_event),
HANDLE_FUNC(__vgic_v3_save_aprs),
HANDLE_FUNC(__vgic_v3_restore_vmcr_aprs),
+ HANDLE_FUNC(__vgic_v5_make_resident),
+ HANDLE_FUNC(__vgic_v5_make_non_resident),
HANDLE_FUNC(__vgic_v5_save_apr),
HANDLE_FUNC(__vgic_v5_restore_vmcr_apr),
diff --git a/arch/arm64/kvm/hyp/vgic-v5-sr.c b/arch/arm64/kvm/hyp/vgic-v5-sr.c
index 6d69dfe89a96c..f064045a31aee 100644
--- a/arch/arm64/kvm/hyp/vgic-v5-sr.c
+++ b/arch/arm64/kvm/hyp/vgic-v5-sr.c
@@ -7,6 +7,32 @@
#include <asm/kvm_hyp.h>
+void __vgic_v5_make_resident(struct vgic_v5_cpu_if *cpu_if)
+{
+ write_sysreg_s(cpu_if->vgic_contextr, SYS_ICH_CONTEXTR_EL2);
+ isb();
+
+ /* Catch any faults */
+ cpu_if->vgic_contextr = read_sysreg_s(SYS_ICH_CONTEXTR_EL2);
+ if (!!FIELD_GET(ICH_CONTEXTR_EL2_F, cpu_if->vgic_contextr))
+ return;
+
+ cpu_if->gicv5_vpe.resident = true;
+}
+
+void __vgic_v5_make_non_resident(struct vgic_v5_cpu_if *cpu_if)
+{
+ /*
+ * Make as non-resident before actually making non-resident. Avoids race
+ * with doorbell arriving.
+ */
+ cpu_if->gicv5_vpe.resident = false;
+ dsb(st);
+
+ write_sysreg_s(cpu_if->vgic_contextr, SYS_ICH_CONTEXTR_EL2);
+ isb();
+}
+
void __vgic_v5_save_apr(struct vgic_v5_cpu_if *cpu_if)
{
cpu_if->vgic_apr = read_sysreg_s(SYS_ICH_APR_EL2);
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index f15a8309d7a93..c36ae9e07ae26 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -1058,6 +1058,8 @@ void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu)
void vgic_v5_load(struct kvm_vcpu *vcpu)
{
struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+ u16 vm = vgic_v5_vm_id(vcpu->kvm);
+ u16 vpe = vgic_v5_vpe_id(vcpu);
/*
* On the WFI path, vgic_load is called a second time. The first is when
@@ -1070,7 +1072,15 @@ void vgic_v5_load(struct kvm_vcpu *vcpu)
kvm_call_hyp(__vgic_v5_restore_vmcr_apr, cpu_if);
- cpu_if->gicv5_vpe.resident = true;
+ cpu_if->vgic_contextr = FIELD_PREP(ICH_CONTEXTR_EL2_V, true) |
+ FIELD_PREP(ICH_CONTEXTR_EL2_VPE, vpe) |
+ FIELD_PREP(ICH_CONTEXTR_EL2_VM, vm);
+
+ kvm_call_hyp(__vgic_v5_make_resident, cpu_if);
+
+ /* Failed to make the VPE resident? Bang! */
+ if (WARN_ON(!!FIELD_GET(ICH_CONTEXTR_EL2_F, cpu_if->vgic_contextr)))
+ kvm_vm_dead(vcpu->kvm);
}
void vgic_v5_put(struct kvm_vcpu *vcpu)
@@ -1088,7 +1098,9 @@ void vgic_v5_put(struct kvm_vcpu *vcpu)
kvm_call_hyp(__vgic_v5_save_apr, cpu_if);
- cpu_if->gicv5_vpe.resident = false;
+ cpu_if->vgic_contextr = 0;
+
+ kvm_call_hyp(__vgic_v5_make_non_resident, cpu_if);
/* The shadow priority is only updated on entering WFI */
if (vcpu_get_flag(vcpu, IN_WFI))
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 7fece54ff0925..9d509cb90ab22 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -525,6 +525,9 @@ struct vgic_v5_cpu_if {
*/
u64 vgic_icsr;
+ /* The contextr used to make VPEs resident and non-resident */
+ u64 vgic_contextr;
+
struct gicv5_vpe gicv5_vpe;
};
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 16/49] KVM: arm64: gic-v5: Request doorbells when VPEs enter WFI
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (14 preceding siblings ...)
2026-08-07 11:20 ` [PATCH v5 15/49] KVM: arm64: gic-v5: Add resident/non-resident hyp calls Sascha Bischoff
@ 2026-08-07 11:20 ` Sascha Bischoff
2026-08-07 11:21 ` [PATCH v5 17/49] KVM: arm64: gic-v5: Introduce struct vgic_v5_irs and IRS base address Sascha Bischoff
` (32 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:20 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
When a GICv5 VPE is made non-resident as part of the vcpu entering
WFI, request a VPE doorbell so that KVM can be notified when a
suitable SPI or LPI becomes pending for that VPE.
Program the doorbell priority mask, DBPM, from the effective virtual
priority mask before making the VPE non-resident. DBPM is the priority
threshold used by the GICv5 hardware to decide whether a pending SPI
or LPI is allowed to signal the VPE doorbell. This allows hardware to
signal the doorbell only for interrupts that the vcpu can actually
take, and avoids waking it for interrupts masked by the guest priority
state. If no interrupt can be signalled to the vcpu, leave the
doorbell request clear.
Make the doorbell interrupt affine to the current CPU before
requesting it. This nudges the wakeup back towards the CPU that last
ran the vcpu, where the relevant state is more likely to be cache-hot,
while also spreading doorbell interrupts across host PEs as different
vcpus enter WFI on different CPUs.
Clear stale db_fired state before making the VPE non-resident. Any
previous doorbell notification has already been consumed by this
point, and clearing it before the non-resident transition ensures that
a newly fired doorbell is observed.
Finally, teach kvm_vgic_vcpu_pending_irq() to report pending work for
a GICv5 vcpu when its VPE doorbell has fired, in addition to the
existing pending-PPI check.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/hyp/vgic-v5-sr.c | 9 ++++++++
arch/arm64/kvm/vgic/vgic-v5.c | 40 +++++++++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic.c | 6 ++++-
3 files changed, 54 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/kvm/hyp/vgic-v5-sr.c b/arch/arm64/kvm/hyp/vgic-v5-sr.c
index f064045a31aee..46992a6c2cacb 100644
--- a/arch/arm64/kvm/hyp/vgic-v5-sr.c
+++ b/arch/arm64/kvm/hyp/vgic-v5-sr.c
@@ -22,6 +22,15 @@ void __vgic_v5_make_resident(struct vgic_v5_cpu_if *cpu_if)
void __vgic_v5_make_non_resident(struct vgic_v5_cpu_if *cpu_if)
{
+ /*
+ * Clear the db_fired state to ensure that we're ready for the next
+ * doorbell when it is requested. If a doorbell firing caused us to
+ * enter the guest, then we've already consumed that state at this
+ * point, so this is safe to clear. Use WRITE_ONCE() to ensure we're not
+ * racing with the doorbell firing and setting the state true again.
+ */
+ WRITE_ONCE(cpu_if->gicv5_vpe.db_fired, false);
+
/*
* Make as non-resident before actually making non-resident. Avoids race
* with doorbell arriving.
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index c36ae9e07ae26..2932f3cfbe8f4 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -1099,6 +1099,46 @@ void vgic_v5_put(struct kvm_vcpu *vcpu)
kvm_call_hyp(__vgic_v5_save_apr, cpu_if);
cpu_if->vgic_contextr = 0;
+ if (vcpu_get_flag(vcpu, IN_WFI)) {
+ u32 priority_mask;
+ int dbpm;
+
+ /*
+ * Find the virtual running priority and use this to calculate
+ * the doorbell priority mask. We combine the highest active
+ * priority and the CPU's priority mask. The guest can't handle
+ * interrupts with priorities less than or equal to the virtual
+ * running priority, so there's literally no point in waking the
+ * guest for these.
+ *
+ * The priority needs to be higher than the mask to signal, so
+ * pick the next higher priority (subtract 1).
+ */
+ priority_mask = vgic_v5_get_effective_priority_mask(vcpu);
+
+ /*
+ * Request a doorbell *unless* the priority is 0, indicating
+ * that no interrupt can wake the CPU up.
+ */
+ if (priority_mask) {
+ int db_irq = vgic_v5_vpe_db(vcpu);
+ struct irq_data *d = irq_get_irq_data(db_irq);
+ const struct cpumask *aff = irq_data_get_effective_affinity_mask(d);
+ int cpu = smp_processor_id();
+
+ dbpm = priority_mask - 1;
+ cpu_if->vgic_contextr = FIELD_PREP(ICH_CONTEXTR_EL2_DB, 1) |
+ FIELD_PREP(ICH_CONTEXTR_EL2_DBPM, dbpm);
+
+ /*
+ * Make the doorbell affine to this CPU, if it isn't
+ * already. Actively check the cpumask first as it is
+ * cheaper than changing the affinity every time.
+ */
+ if (!cpumask_test_cpu(cpu, aff))
+ WARN_ON(irq_set_affinity(db_irq, cpumask_of(cpu)));
+ }
+ }
kvm_call_hyp(__vgic_v5_make_non_resident, cpu_if);
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index 74bace10a22ed..45af0287d6587 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -1233,8 +1233,12 @@ int kvm_vgic_vcpu_pending_irq(struct kvm_vcpu *vcpu)
unsigned long flags;
struct vgic_vmcr vmcr;
- if (vgic_is_v5(vcpu->kvm))
+ if (vgic_is_v5(vcpu->kvm)) {
+ if (READ_ONCE(vcpu->arch.vgic_cpu.vgic_v5.gicv5_vpe.db_fired))
+ return true;
+
return vgic_v5_has_pending_ppi(vcpu);
+ }
if (!vcpu->kvm->arch.vgic.enabled)
return false;
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 17/49] KVM: arm64: gic-v5: Introduce struct vgic_v5_irs and IRS base address
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (15 preceding siblings ...)
2026-08-07 11:20 ` [PATCH v5 16/49] KVM: arm64: gic-v5: Request doorbells when VPEs enter WFI Sascha Bischoff
@ 2026-08-07 11:21 ` Sascha Bischoff
2026-08-07 11:21 ` [PATCH v5 18/49] KVM: arm64: gic-v5: Add IRS IODEV support to MMIO handlers Sascha Bischoff
` (31 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:21 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
In order to properly emulate the operation of the IRS from KVM, we
require storage for the MMIO register state. This change introduces
struct vgic_v5_irs, and adds a pointer to it to the struct vgic_dist.
This new data structure contains the storage for IRS MMIO state that
is required for emulating the MMIO interface in KVM. This provides
persistent storage, and a way to track data across MMIO writes, e.g.,
selecting an SPI and updating the configuration of it is two MMIO
writes.
Note that only a pointer to the data structure is added to struct
vgic_dist as this new structure is very large, and hence it makes
sense to dynamically allocate it and just provide a pointer to
retrieve it in struct vgic_dist.
In addition to adding a structure to store the MMIO state for the IRS,
we add the base address in GPA space to struct vgic_v5_irs.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
include/kvm/arm_vgic.h | 86 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 86 insertions(+)
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 9d509cb90ab22..9c5e789978cc1 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -398,6 +398,87 @@ struct vgic_v5_vm {
bool vmte_allocated;
};
+/*** GICv5 ***/
+struct vgic_v5_irs {
+ /* base addresses in guest physical address space: */
+ gpa_t vgic_v5_irs_base;
+
+ struct vgic_io_device iodev;
+ struct kvm_device *dev;
+
+ /* IRS state - used for registers etc */
+ struct {
+ u8 domain;
+ u8 pa_range;
+ bool virt;
+ bool setlpi;
+ bool mec;
+ bool mpam;
+ bool swe;
+ u16 irs_id;
+ } idr0;
+
+ struct {
+ /* PE_CNT is populated from online_vcpus at runtime */
+ u8 priority_bits;
+ } idr1;
+
+ struct {
+ u8 id_bits;
+ u8 min_lpi_id_bits;
+ bool ist_levels;
+ u8 ist_l2sz;
+ bool istmd;
+ u8 istmd_sz;
+ } idr2;
+
+ struct {
+ u32 spi_range;
+ } idr5;
+
+ struct {
+ u32 spi_irs_range;
+ } idr6;
+
+ struct {
+ u32 spi_base;
+ } idr7;
+
+ struct {
+ u8 sh;
+ u8 oc;
+ u8 ic;
+ bool ist_ra;
+ bool ist_wa;
+ bool vmt_ra;
+ bool vpet_ra;
+ bool vmd_ra;
+ bool vmd_wa;
+ bool vped_ra;
+ bool vped_wa;
+ } cr1;
+
+ struct {
+ u32 id;
+ } spi_selr;
+
+ struct {
+ u32 iaffid;
+ } pe_selr;
+
+ struct {
+ u8 lpi_id_bits;
+ u8 l2sz;
+ u8 istsz;
+ bool structure;
+ } ist_cfgr;
+
+ struct {
+ bool valid;
+ u64 addr;
+ } ist_baser;
+};
+
struct vgic_dist {
bool in_kernel;
bool ready;
@@ -475,6 +556,11 @@ struct vgic_dist {
* GICv5 per-VM data.
*/
struct vgic_v5_vm gicv5_vm;
+
+ /*
+ * GICv5 IRS data. Dynamically allocated due to the size.
+ */
+ struct vgic_v5_irs *vgic_v5_irs_data;
};
struct vgic_v2_cpu_if {
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 18/49] KVM: arm64: gic-v5: Add IRS IODEV support to MMIO handlers
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (16 preceding siblings ...)
2026-08-07 11:21 ` [PATCH v5 17/49] KVM: arm64: gic-v5: Introduce struct vgic_v5_irs and IRS base address Sascha Bischoff
@ 2026-08-07 11:21 ` Sascha Bischoff
2026-08-07 11:22 ` [PATCH v5 19/49] KVM: arm64: gic-v5: Add KVM_VGIC_V5_ADDR_TYPE_IRS to UAPI Sascha Bischoff
` (30 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:21 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
In order to support proper VMs (that support more than just PPIs) for
GICv5, it is important to emulate the GICv5 IRS too. The IRS includes
an MMIO interface which is used to interact with and configure the
IRS.
As part of providing the emulated IRS MMIO interface in KVM, extend
enum iodev_type to include a GICv5 IRS device, and extend the MMIO
code to handle reads and writes to that type of IO device. This will
allow the creation of a GICv5 IRS IO Device in KVM.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-mmio.c | 6 ++++++
arch/arm64/kvm/vgic/vgic-mmio.h | 2 ++
include/kvm/arm_vgic.h | 3 ++-
3 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/kvm/vgic/vgic-mmio.c b/arch/arm64/kvm/vgic/vgic-mmio.c
index 74d76dec97304..fddb9da0403d5 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio.c
+++ b/arch/arm64/kvm/vgic/vgic-mmio.c
@@ -1065,6 +1065,9 @@ static int dispatch_mmio_read(struct kvm_vcpu *vcpu, struct kvm_io_device *dev,
case IODEV_ITS:
data = region->its_read(vcpu->kvm, iodev->its, addr, len);
break;
+ case IODEV_GICV5_IRS:
+ data = region->read(vcpu, addr, len);
+ break;
}
vgic_data_host_to_mmio_bus(val, len, data);
@@ -1095,6 +1098,9 @@ static int dispatch_mmio_write(struct kvm_vcpu *vcpu, struct kvm_io_device *dev,
case IODEV_ITS:
region->its_write(vcpu->kvm, iodev->its, addr, len, data);
break;
+ case IODEV_GICV5_IRS:
+ region->write(vcpu, addr, len, data);
+ break;
}
return 0;
diff --git a/arch/arm64/kvm/vgic/vgic-mmio.h b/arch/arm64/kvm/vgic/vgic-mmio.h
index 50dc80220b0f3..38ed730d68ac3 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio.h
+++ b/arch/arm64/kvm/vgic/vgic-mmio.h
@@ -217,6 +217,8 @@ 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);
+
u64 vgic_sanitise_outer_cacheability(u64 reg);
u64 vgic_sanitise_inner_cacheability(u64 reg);
u64 vgic_sanitise_shareability(u64 reg);
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 9c5e789978cc1..da15570b11292 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -310,7 +310,8 @@ enum iodev_type {
IODEV_CPUIF,
IODEV_DIST,
IODEV_REDIST,
- IODEV_ITS
+ IODEV_ITS,
+ IODEV_GICV5_IRS
};
struct vgic_io_device {
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 19/49] KVM: arm64: gic-v5: Add KVM_VGIC_V5_ADDR_TYPE_IRS to UAPI
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (17 preceding siblings ...)
2026-08-07 11:21 ` [PATCH v5 18/49] KVM: arm64: gic-v5: Add IRS IODEV support to MMIO handlers Sascha Bischoff
@ 2026-08-07 11:22 ` Sascha Bischoff
2026-08-07 11:22 ` [PATCH v5 20/49] KVM: arm64: gic-v5: Add GICv5 IRS IODEV and MMIO emulation Sascha Bischoff
` (29 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Define the UAPI address type used by userspace to describe the
location of the emulated IRS in guest physical address space, together
with the size reserved for that region.
As per the GICv5 specification, the IRS has one CONFIG_FRAME and
optionally one SETLPI_FRAME per interrupt domain. Within a KVM VM we
are only concerned with one interrupt domain. Each of these frames is
64kB in size, so reserve 2x64kB of contiguous memory in the GPA space
for a GICv5 IRS.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/include/uapi/asm/kvm.h | 5 +++++
tools/arch/arm64/include/uapi/asm/kvm.h | 5 +++++
2 files changed, 10 insertions(+)
diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
index 1c13bfa2d38aa..d1b2ca317f586 100644
--- a/arch/arm64/include/uapi/asm/kvm.h
+++ b/arch/arm64/include/uapi/asm/kvm.h
@@ -97,6 +97,11 @@ struct kvm_regs {
#define KVM_VGIC_V3_REDIST_SIZE (2 * SZ_64K)
#define KVM_VGIC_V3_ITS_SIZE (2 * SZ_64K)
+/* Supported VGICv5 address types */
+#define KVM_VGIC_V5_ADDR_TYPE_IRS 6
+
+#define KVM_VGIC_V5_IRS_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 */
#define KVM_ARM_VCPU_PSCI_0_2 2 /* CPU uses PSCI v0.2 */
diff --git a/tools/arch/arm64/include/uapi/asm/kvm.h b/tools/arch/arm64/include/uapi/asm/kvm.h
index 1c13bfa2d38aa..d1b2ca317f586 100644
--- a/tools/arch/arm64/include/uapi/asm/kvm.h
+++ b/tools/arch/arm64/include/uapi/asm/kvm.h
@@ -97,6 +97,11 @@ struct kvm_regs {
#define KVM_VGIC_V3_REDIST_SIZE (2 * SZ_64K)
#define KVM_VGIC_V3_ITS_SIZE (2 * SZ_64K)
+/* Supported VGICv5 address types */
+#define KVM_VGIC_V5_ADDR_TYPE_IRS 6
+
+#define KVM_VGIC_V5_IRS_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 */
#define KVM_ARM_VCPU_PSCI_0_2 2 /* CPU uses PSCI v0.2 */
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 20/49] KVM: arm64: gic-v5: Add GICv5 IRS IODEV and MMIO emulation
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (18 preceding siblings ...)
2026-08-07 11:22 ` [PATCH v5 19/49] KVM: arm64: gic-v5: Add KVM_VGIC_V5_ADDR_TYPE_IRS to UAPI Sascha Bischoff
@ 2026-08-07 11:22 ` Sascha Bischoff
2026-08-07 11:23 ` [PATCH v5 21/49] KVM: arm64: gic-v5: Initialise per-VM IRS state Sascha Bischoff
` (28 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
In order to properly support GICv5-based VMs in KVM, emulate the
CONFIG_FRAME for a virtual IRS. This emulation needs to handle guest
accesses to the MMIO region and mimic the behaviour of a real IRS.
Introduce an IODEV for the GICv5 IRS and an associated initialisation
function that sets up the SPIs and initial IRS state. The MMIO
emulation allows the guest to query the IRS_IDx registers, manipulate
SPIs, configure ISTs, and so forth.
The emulation tracks selector state across MMIO accesses. For example,
a guest writes IRS_PE_SELR to select a PE by IAFFID. This is the VPE
ID for a VM, but the guest does not know this. If the guest reads
IRS_PE_STATUSR, KVM checks whether that IAFFID selects a valid VPE and
sets the V bit accordingly. IRS_PE_CR0 is accepted as write-ignored
because KVM does not support 1-of-N routing.
The same selector and status register model is exposed for SPIs.
Track the state of IRS_CR0.IRSEN and introduce KVM_REQ_RELOAD_GICv5 to
reload the GICv5 context of running vCPUs when it changes. Only make
the request when the enable state changes, avoiding unnecessary IPIs
for writes that leave it unchanged.
The LPI IST requires KVM to perform actions on behalf of the guest.
Treat changes to IRS_IST_BASER.Valid as the lifetime of the guest's
IST. On an Invalid-to-Valid transition, validate IRS_IST_CFGR,
allocate a shadow host IST, and assign it to the physical IRS through
the VMTE. On a Valid-to-Invalid transition, invalidate and free the
host IST. Ignore guest address changes while the BASER remains valid,
and prevent changes to IRS_IST_CFGR during that time.
As far as the guest is concerned, the IST memory it provided is being
used by the hardware, but the physical IRS uses the host shadow IST
instead.
This change provides the core IRS IODEV and MMIO emulation, but does
not plumb the device into the rest of KVM yet. The CoreSight
identification registers are added separately.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/include/asm/kvm_host.h | 1 +
arch/arm64/kvm/Makefile | 2 +-
arch/arm64/kvm/arm.c | 8 +
arch/arm64/kvm/vgic/vgic-irs-v5.c | 765 +++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic-v5-tables.c | 16 +
arch/arm64/kvm/vgic/vgic-v5-tables.h | 1 +
arch/arm64/kvm/vgic/vgic.h | 2 +
include/kvm/arm_vgic.h | 1 +
8 files changed, 795 insertions(+), 1 deletion(-)
create mode 100644 arch/arm64/kvm/vgic/vgic-irs-v5.c
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index bae2c4f92ef5c..b04dcc9a73c28 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -55,6 +55,7 @@
#define KVM_REQ_GUEST_HYP_IRQ_PENDING KVM_ARCH_REQ(9)
#define KVM_REQ_MAP_L1_VNCR_EL2 KVM_ARCH_REQ(10)
#define KVM_REQ_VGIC_PROCESS_UPDATE KVM_ARCH_REQ(11)
+#define KVM_REQ_RELOAD_GICv5 KVM_ARCH_REQ(12)
#define KVM_DIRTY_LOG_MANUAL_CAPS (KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE | \
KVM_DIRTY_LOG_INITIALLY_SET)
diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile
index 431de9b145ca1..92dda57c08766 100644
--- a/arch/arm64/kvm/Makefile
+++ b/arch/arm64/kvm/Makefile
@@ -24,7 +24,7 @@ kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \
vgic/vgic-mmio.o vgic/vgic-mmio-v2.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-v5.o vgic/vgic-v5-tables.o vgic/vgic-irs-v5.o
kvm-$(CONFIG_HW_PERF_EVENTS) += pmu-emul.o pmu.o
kvm-$(CONFIG_ARM64_PTR_AUTH) += pauth.o
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 9a6c72a186727..908a058314117 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -1151,6 +1151,14 @@ static int check_vcpu_requests(struct kvm_vcpu *vcpu)
preempt_enable();
}
+ if (kvm_check_request(KVM_REQ_RELOAD_GICv5, vcpu)) {
+ /* The IRS enable bit was changed */
+ preempt_disable();
+ vgic_v5_put(vcpu);
+ vgic_v5_load(vcpu);
+ preempt_enable();
+ }
+
if (kvm_check_request(KVM_REQ_RELOAD_PMU, vcpu))
kvm_vcpu_reload_pmu(vcpu);
diff --git a/arch/arm64/kvm/vgic/vgic-irs-v5.c b/arch/arm64/kvm/vgic/vgic-irs-v5.c
new file mode 100644
index 0000000000000..c6280795ab551
--- /dev/null
+++ b/arch/arm64/kvm/vgic/vgic-irs-v5.c
@@ -0,0 +1,765 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 ARM Limited, All Rights Reserved.
+ */
+#include <linux/bitops.h>
+#include <linux/bsearch.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/kvm.h>
+#include <linux/kvm_host.h>
+#include <kvm/iodev.h>
+#include <kvm/arm_arch_timer.h>
+#include <kvm/arm_vgic.h>
+
+#include "vgic.h"
+#include "vgic-mmio.h"
+#include "vgic-v5-tables.h"
+
+#define irs_caps kvm_vgic_global_state.vgic_v5_irs_caps
+
+static struct vgic_dist *vgic_v5_get_vgic(struct kvm_vcpu *vcpu)
+{
+ return &vcpu->kvm->arch.vgic;
+}
+
+static struct vgic_v5_irs *vgic_v5_get_irs(struct kvm_vcpu *vcpu)
+{
+ return vcpu->kvm->arch.vgic.vgic_v5_irs_data;
+}
+
+static unsigned long vgic_v5_mmio_read_irs_misc(struct kvm_vcpu *vcpu,
+ gpa_t addr, unsigned int len)
+{
+ struct vgic_v5_irs *irs = vgic_v5_get_irs(vcpu);
+ const size_t offset = addr & (SZ_64K - 1);
+ struct kvm_vcpu *target_vcpu;
+ u8 vpe_id_bits;
+ u64 value = 0;
+
+ switch (offset) {
+ case GICV5_IRS_IDR0:
+ value = FIELD_PREP(GICV5_IRS_IDR0_INT_DOM, irs->idr0.domain);
+ value |= FIELD_PREP(GICV5_IRS_IDR0_PA_RANGE, irs->idr0.pa_range);
+ if (irs->idr0.virt)
+ value |= GICV5_IRS_IDR0_VIRT;
+ if (irs->idr0.setlpi)
+ value |= GICV5_IRS_IDR0_SETLPI;
+ if (irs->idr0.mec)
+ value |= GICV5_IRS_IDR0_MEC;
+ if (irs->idr0.mpam)
+ value |= GICV5_IRS_IDR0_MPAM;
+ if (irs->idr0.swe)
+ value |= GICV5_IRS_IDR0_SWE;
+ value |= FIELD_PREP(GICV5_IRS_IDR0_IRSID, irs->idr0.irs_id);
+ break;
+ case GICV5_IRS_IDR1:
+ value = FIELD_PREP(GICV5_IRS_IDR1_PE_CNT,
+ atomic_read(&vcpu->kvm->online_vcpus));
+ /*
+ * IRS_IDR1 encodes IAFFID_BITS as N - 1.
+ */
+ vpe_id_bits = vgic_v5_vmte_vpe_id_bits(vcpu);
+ value |= FIELD_PREP(GICV5_IRS_IDR1_IAFFID_BITS, vpe_id_bits - 1);
+ value |= FIELD_PREP(GICV5_IRS_IDR1_PRIORITY_BITS, irs->idr1.priority_bits);
+ break;
+ case GICV5_IRS_IDR2:
+ value = FIELD_PREP(GICV5_IRS_IDR2_ISTMD_SZ, irs->idr2.istmd_sz);
+ if (irs->idr2.istmd)
+ value |= GICV5_IRS_IDR2_ISTMD;
+ value |= FIELD_PREP(GICV5_IRS_IDR2_IST_L2SZ, irs->idr2.ist_l2sz);
+ if (irs->idr2.ist_levels)
+ value |= GICV5_IRS_IDR2_IST_LEVELS;
+ value |= FIELD_PREP(GICV5_IRS_IDR2_MIN_LPI_ID_BITS, irs->idr2.min_lpi_id_bits);
+ value |= GICV5_IRS_IDR2_LPI;
+ value |= FIELD_PREP(GICV5_IRS_IDR2_ID_BITS, irs->idr2.id_bits);
+ break;
+ case GICV5_IRS_IDR5:
+ value = FIELD_PREP(GICV5_IRS_IDR5_SPI_RANGE, irs->idr5.spi_range);
+ break;
+ case GICV5_IRS_IDR6:
+ value = FIELD_PREP(GICV5_IRS_IDR6_SPI_IRS_RANGE, irs->idr6.spi_irs_range);
+ break;
+ case GICV5_IRS_IDR7:
+ value = FIELD_PREP(GICV5_IRS_IDR7_SPI_BASE, irs->idr7.spi_base);
+ break;
+ case GICV5_IRS_IIDR:
+ /* Revision, Variant, ProductID are implementation defined */
+ value = FIELD_PREP(GICV5_IRS_IIDR_PRODUCT_ID, PRODUCT_ID_KVM);
+ value |= FIELD_PREP(GICV5_IRS_IIDR_VARIANT, 0);
+ value |= FIELD_PREP(GICV5_IRS_IIDR_REVISION, 0);
+ value |= FIELD_PREP(GICV5_IRS_IIDR_IMPLEMENTER, IMPLEMENTER_ARM);
+ break;
+ case GICV5_IRS_AIDR:
+ value = FIELD_PREP(GICV5_IRS_AIDR_COMPONENT,
+ GICV5_AIDR_COMPONENT_IRS);
+ value |= FIELD_PREP(GICV5_IRS_AIDR_ARCHMAJORREV,
+ GICV5_AIDR_ARCH_MAJ_REV_V5);
+ value |= FIELD_PREP(GICV5_IRS_AIDR_ARCHMINORREV,
+ GICV5_AIDR_ARCH_MIN_REV_V0);
+ break;
+ case GICV5_IRS_CR0:
+ /*
+ * The IRS is ALWAYS idle as we handle things instantaneously
+ * from a guest's viewpoint.
+ */
+ value = GICV5_IRS_CR0_IDLE;
+ if (READ_ONCE(vcpu->kvm->arch.vgic.enabled))
+ value |= GICV5_IRS_CR0_IRSEN;
+ break;
+ case GICV5_IRS_CR1:
+ if (irs->cr1.vped_wa)
+ value |= GICV5_IRS_CR1_VPED_WA;
+ if (irs->cr1.vped_ra)
+ value |= GICV5_IRS_CR1_VPED_RA;
+ if (irs->cr1.vmd_wa)
+ value |= GICV5_IRS_CR1_VMD_WA;
+ if (irs->cr1.vmd_ra)
+ value |= GICV5_IRS_CR1_VMD_RA;
+ if (irs->cr1.vpet_ra)
+ value |= GICV5_IRS_CR1_VPET_RA;
+ if (irs->cr1.vmt_ra)
+ value |= GICV5_IRS_CR1_VMT_RA;
+ if (irs->cr1.ist_wa)
+ value |= GICV5_IRS_CR1_IST_WA;
+ if (irs->cr1.ist_ra)
+ value |= GICV5_IRS_CR1_IST_RA;
+ value |= FIELD_PREP(GICV5_IRS_CR1_IC, irs->cr1.ic);
+ value |= FIELD_PREP(GICV5_IRS_CR1_OC, irs->cr1.oc);
+ value |= FIELD_PREP(GICV5_IRS_CR1_SH, irs->cr1.sh);
+ break;
+ case GICV5_IRS_SYNC_STATUSR:
+ value = GICV5_IRS_SYNC_STATUSR_IDLE;
+ break;
+ case GICV5_IRS_PE_SELR:
+ value = FIELD_PREP(GICV5_IRS_PE_SELR_IAFFID,
+ READ_ONCE(irs->pe_selr.iaffid));
+ break;
+ case GICV5_IRS_PE_STATUSR:
+ /* We assume that the PE is Online if present. Always IDLE too */
+ value = GICV5_IRS_PE_STATUSR_IDLE;
+
+ /* Set ONLINE and V if IAFFID selects a present PE */
+ if (kvm_get_vcpu_by_id(vcpu->kvm,
+ READ_ONCE(irs->pe_selr.iaffid))) {
+ value |= GICV5_IRS_PE_STATUSR_ONLINE;
+ value |= GICV5_IRS_PE_STATUSR_V;
+ }
+ break;
+ case GICV5_IRS_PE_CR0: {
+ u32 iaffid = READ_ONCE(irs->pe_selr.iaffid);
+
+ /*
+ * Make sure that we are doing something reasonable first.
+ * Remember, the IAFFID is the same as the VPE_ID
+ */
+ target_vcpu = kvm_get_vcpu_by_id(vcpu->kvm, iaffid);
+ if (!target_vcpu) {
+ kvm_debug("Guest programmed invalid IAFFID (0x%x) into the IRS_PE_SELR\n",
+ iaffid);
+ break;
+ }
+
+ value = GICV5_IRS_PE_CR0_DPS;
+ break;
+ }
+ default:
+ return 0;
+ }
+
+ return value;
+}
+
+static void vgic_v5_mmio_write_irs_misc(struct kvm_vcpu *vcpu, gpa_t addr,
+ unsigned int len, unsigned long val)
+{
+ struct vgic_v5_irs *irs = vgic_v5_get_irs(vcpu);
+ struct vgic_dist *vgic = vgic_v5_get_vgic(vcpu);
+ const size_t offset = addr & (SZ_64K - 1);
+
+ switch (offset) {
+ case GICV5_IRS_CR0:
+ scoped_guard(mutex, &vcpu->kvm->arch.config_lock) {
+ bool was_enabled = vgic->enabled;
+
+ WRITE_ONCE(vgic->enabled, !!(val & GICV5_IRS_CR0_IRSEN));
+
+ /* Reload other vCPUs on an edge */
+ if (was_enabled != !!(val & GICV5_IRS_CR0_IRSEN))
+ kvm_make_all_cpus_request(vcpu->kvm,
+ KVM_REQ_RELOAD_GICv5);
+ }
+ return;
+ case GICV5_IRS_CR1:
+ irs->cr1.sh = FIELD_GET(GICV5_IRS_CR1_SH, val);
+ irs->cr1.oc = FIELD_GET(GICV5_IRS_CR1_OC, val);
+ irs->cr1.ic = FIELD_GET(GICV5_IRS_CR1_IC, val);
+ irs->cr1.ist_ra = !!(val & GICV5_IRS_CR1_IST_RA);
+ irs->cr1.ist_wa = !!(val & GICV5_IRS_CR1_IST_WA);
+ irs->cr1.vmt_ra = !!(val & GICV5_IRS_CR1_VMT_RA);
+ irs->cr1.vpet_ra = !!(val & GICV5_IRS_CR1_VPET_RA);
+ irs->cr1.vmd_ra = !!(val & GICV5_IRS_CR1_VMD_RA);
+ irs->cr1.vmd_wa = !!(val & GICV5_IRS_CR1_VMD_WA);
+ irs->cr1.vped_ra = !!(val & GICV5_IRS_CR1_VPED_RA);
+ irs->cr1.vped_wa = !!(val & GICV5_IRS_CR1_VPED_WA);
+ return;
+ case GICV5_IRS_PE_SELR:
+ WRITE_ONCE(irs->pe_selr.iaffid,
+ FIELD_GET(GICV5_IRS_PE_SELR_IAFFID, val));
+ return;
+ case GICV5_IRS_PE_CR0:
+ /*
+ * We actually have nothing to do here as we don't support
+ * 1-of-N routing. The only thing that the guest can correctly
+ * write here is 0x1. However, there's no way to fault if it
+ * writes something else. This is effectively a WI in our case,
+ * but we keep it here for the purposes of documenting it.
+ */
+ return;
+ default:
+ return;
+ }
+}
+
+static bool vgic_v5_is_spi_selr_valid(struct vgic_v5_irs *irs, u32 id)
+{
+ /* Invalid - we don't have any SPIs at all */
+ if (irs->idr5.spi_range == 0)
+ return false;
+
+ /* Invalid - we don't have any on this IRS */
+ if (irs->idr6.spi_irs_range == 0)
+ return false;
+
+ /* Invalid - ID is less than min */
+ if (id < irs->idr7.spi_base)
+ return false;
+
+ /* Invalid - ID is greater than max */
+ if (id >= (irs->idr7.spi_base + irs->idr6.spi_irs_range))
+ return false;
+
+ return true;
+}
+
+static unsigned long vgic_v5_mmio_read_irs_spi(struct kvm_vcpu *vcpu,
+ gpa_t addr, unsigned int len)
+{
+ struct vgic_v5_irs *irs = vgic_v5_get_irs(vcpu);
+ const size_t offset = addr & (SZ_64K - 1);
+ struct vgic_irq *irq;
+ u64 value = 0;
+
+ switch (offset) {
+ case GICV5_IRS_SPI_SELR:
+ /* Return whatever was last written */
+ value = FIELD_PREP(GICV5_IRS_SPI_SELR_ID, READ_ONCE(irs->spi_selr.id));
+ break;
+ case GICV5_IRS_SPI_STATUSR:
+ /* We assume that we can always claim to be idle */
+ value = GICV5_IRS_SPI_STATUSR_IDLE;
+ if (vgic_v5_is_spi_selr_valid(irs, READ_ONCE(irs->spi_selr.id)))
+ value |= GICV5_IRS_SPI_STATUSR_V;
+ break;
+ case GICV5_IRS_SPI_DOMAINR:
+ value = FIELD_PREP(GICV5_IRS_SPI_DOMAINR_DOMAIN,
+ GICV5_IRS_SPI_DOMAINR_DOMAIN_NON_SECURE);
+ break;
+ case GICV5_IRS_SPI_CFGR: {
+ u32 id = READ_ONCE(irs->spi_selr.id);
+
+ if (!vgic_v5_is_spi_selr_valid(irs, id)) {
+ /* Fault with IRS_SPI_SELR; return 0*/
+ value = 0;
+ break;
+ }
+
+ irq = vgic_get_irq(vcpu->kvm, vgic_v5_make_spi(id));
+ if (!irq) {
+ kvm_debug_ratelimited("Guest trying to access SPI not backed by KVM\n");
+ value = 0;
+ break;
+ }
+
+ scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) {
+ if (irq->config == VGIC_CONFIG_LEVEL)
+ value = GICV5_IRS_SPI_CFGR_TM;
+ }
+
+ vgic_put_irq(vcpu->kvm, irq);
+
+ break;
+ }
+ default:
+ return 0;
+ }
+
+ return value;
+}
+
+static void vgic_v5_mmio_write_irs_spi(struct kvm_vcpu *vcpu, gpa_t addr,
+ unsigned int len, unsigned long val)
+{
+ struct vgic_v5_irs *irs = vgic_v5_get_irs(vcpu);
+ const size_t offset = addr & (SZ_64K - 1);
+ struct vgic_irq *irq;
+
+ switch (offset) {
+ case GICV5_IRS_SPI_SELR:
+ WRITE_ONCE(irs->spi_selr.id,
+ FIELD_GET(GICV5_IRS_SPI_SELR_ID, val));
+ return;
+ case GICV5_IRS_SPI_CFGR: {
+ u32 id = READ_ONCE(irs->spi_selr.id);
+
+ if (!vgic_v5_is_spi_selr_valid(irs, id))
+ return;
+
+ /*
+ * Find KVM's representation of the interrupt - we need to make
+ * sure that KVM's view agrees with the guest's, else interrupt
+ * injection won't work properly for level-triggered interrupts
+ * (we fail to handle the clearing of the pending state if KVM
+ * thinks that the interrupt is edge-triggered, which is the
+ * default.)
+ */
+ irq = vgic_get_irq(vcpu->kvm, vgic_v5_make_spi(id));
+ if (!irq)
+ return;
+
+ scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) {
+ if (val & GICV5_IRS_SPI_CFGR_TM)
+ irq->config = VGIC_CONFIG_LEVEL;
+ else
+ irq->config = VGIC_CONFIG_EDGE;
+ }
+
+ vgic_put_irq(vcpu->kvm, irq);
+
+ return;
+ }
+ default:
+ return;
+ }
+}
+
+static bool vgic_v5_ist_cfgr_valid(struct vgic_v5_irs *irs)
+{
+ unsigned int expected_istsz;
+
+ if (irs->ist_cfgr.lpi_id_bits < irs->idr2.min_lpi_id_bits ||
+ irs->ist_cfgr.lpi_id_bits > irs->idr2.id_bits)
+ return false;
+
+ if (!irs->idr2.istmd)
+ expected_istsz = GICV5_IRS_IST_CFGR_ISTSZ_4;
+ else if (irs->ist_cfgr.lpi_id_bits >= irs->idr2.istmd_sz)
+ expected_istsz = GICV5_IRS_IST_CFGR_ISTSZ_16;
+ else
+ expected_istsz = GICV5_IRS_IST_CFGR_ISTSZ_8;
+
+ if (irs->ist_cfgr.istsz != expected_istsz)
+ return false;
+
+ if (irs->ist_cfgr.structure && !irs->idr2.ist_levels)
+ return false;
+
+ if (!irs->ist_cfgr.structure)
+ return true;
+
+ return irs->ist_cfgr.l2sz == irs->idr2.ist_l2sz;
+}
+
+static unsigned long vgic_v5_mmio_read_irs_ist(struct kvm_vcpu *vcpu,
+ gpa_t addr, unsigned int len)
+{
+ struct vgic_v5_irs *irs = vgic_v5_get_irs(vcpu);
+ const size_t offset = addr & (SZ_64K - 1);
+ u64 value = 0;
+
+ switch (offset) {
+ case GICV5_IRS_IST_STATUSR:
+ return GICV5_IRS_IST_STATUSR_IDLE;
+ case GICV5_IRS_IST_CFGR:
+ if (irs->ist_cfgr.structure)
+ value |= GICV5_IRS_IST_CFGR_STRUCTURE;
+ value |= FIELD_PREP(GICV5_IRS_IST_CFGR_ISTSZ, irs->ist_cfgr.istsz);
+ value |= FIELD_PREP(GICV5_IRS_IST_CFGR_L2SZ, irs->ist_cfgr.l2sz);
+ value |= FIELD_PREP(GICV5_IRS_IST_CFGR_LPI_ID_BITS, irs->ist_cfgr.lpi_id_bits);
+ break;
+ case GICV5_IRS_IST_BASER:
+ value = FIELD_PREP(GICV5_IRS_IST_BASER_ADDR_MASK,
+ irs->ist_baser.addr >> GICV5_IRS_IST_BASER_ADDR_SHIFT);
+ if (irs->ist_baser.valid)
+ value |= GICV5_IRS_IST_BASER_VALID;
+ break;
+ default:
+ return 0;
+ }
+
+ return value;
+}
+
+static void vgic_v5_mmio_write_irs_ist(struct kvm_vcpu *vcpu, gpa_t addr,
+ unsigned int len, unsigned long val)
+{
+ struct vgic_v5_irs *irs = vgic_v5_get_irs(vcpu);
+ const size_t offset = addr & (SZ_64K - 1);
+ enum gicv5_vcpu_cmd cmd = LPI_VIST_MAKE_INVALID;
+
+ switch (offset) {
+ case GICV5_IRS_IST_CFGR:
+ scoped_guard(mutex, &vcpu->kvm->arch.config_lock) {
+ if (irs->ist_baser.valid)
+ return;
+
+ irs->ist_cfgr.lpi_id_bits = FIELD_GET(GICV5_IRS_IST_CFGR_LPI_ID_BITS, val);
+ irs->ist_cfgr.l2sz = FIELD_GET(GICV5_IRS_IST_CFGR_L2SZ, val);
+ irs->ist_cfgr.istsz = FIELD_GET(GICV5_IRS_IST_CFGR_ISTSZ, val);
+ irs->ist_cfgr.structure = !!(val & GICV5_IRS_IST_CFGR_STRUCTURE);
+ }
+ return;
+ case GICV5_IRS_IST_BASER: {
+ bool valid = !!(val & GICV5_IRS_IST_BASER_VALID);
+
+ guard(mutex)(&vcpu->kvm->arch.config_lock);
+
+ /* The address cannot be changed while the IST is valid. */
+ if (irs->ist_baser.valid && valid)
+ return;
+
+ /* Valid -> Invalid */
+ if (irs->ist_baser.valid && !valid) {
+ /* Make the LPI IST invalid and then ... */
+ if (irq_set_vcpu_affinity(vgic_v5_vpe_db(vcpu), &cmd))
+ break;
+
+ /*
+ * ... free the host IST if we successfully marked the
+ * IST as invalid. Frankly, if we failed to make the
+ * guest's IST as invalid, we're cooked because it means
+ * that the IRS may still be using the memory that we
+ * want to free. Hence, we leave it allocated and skip
+ * the clearing of valid bit in the baser.
+ */
+ vgic_v5_lpi_ist_free(vcpu->kvm);
+ } else if (!irs->ist_baser.valid && valid) { /* Invalid -> Valid */
+ if (!vgic_v5_ist_cfgr_valid(irs))
+ break;
+
+ if (vgic_v5_lpi_ist_alloc(vcpu->kvm, irs->ist_cfgr.lpi_id_bits))
+ break;
+ }
+
+ /* Now that we've handled the edges, update the valid bit and addr */
+ irs->ist_baser.valid = !!(val & GICV5_IRS_IST_BASER_VALID);
+ irs->ist_baser.addr = FIELD_GET(GICV5_IRS_IST_BASER_ADDR_MASK, val)
+ << GICV5_IRS_IST_BASER_ADDR_SHIFT;
+
+ return;
+ }
+ default:
+ return;
+ }
+}
+
+static const struct vgic_register_region vgic_v5_irs_registers[] = {
+ /*
+ * This is the IRS_CONFIG_FRAME.
+ */
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR0, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR1, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR2, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR3, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR4, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR5, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR6, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR7, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IIDR, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_AIDR, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_CR0, vgic_v5_mmio_read_irs_misc,
+ vgic_v5_mmio_write_irs_misc, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_CR1, vgic_v5_mmio_read_irs_misc,
+ vgic_v5_mmio_write_irs_misc, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SYNCR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SYNC_STATUSR,
+ vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SPI_VMR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8,
+ VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SPI_SELR, vgic_v5_mmio_read_irs_spi,
+ vgic_v5_mmio_write_irs_spi, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SPI_DOMAINR, vgic_v5_mmio_read_irs_spi,
+ vgic_v5_mmio_write_irs_spi, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SPI_RESAMPLER, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SPI_CFGR, vgic_v5_mmio_read_irs_spi,
+ vgic_v5_mmio_write_irs_spi, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SPI_STATUSR,
+ vgic_v5_mmio_read_irs_spi, vgic_mmio_write_wi,
+ 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_PE_SELR, vgic_v5_mmio_read_irs_misc,
+ vgic_v5_mmio_write_irs_misc, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_PE_STATUSR,
+ vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_PE_CR0, vgic_v5_mmio_read_irs_misc,
+ vgic_v5_mmio_write_irs_misc, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IST_BASER, vgic_v5_mmio_read_irs_ist,
+ vgic_v5_mmio_write_irs_ist, 8,
+ VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IST_CFGR, vgic_v5_mmio_read_irs_ist,
+ vgic_v5_mmio_write_irs_ist, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IST_STATUSR,
+ vgic_v5_mmio_read_irs_ist, vgic_mmio_write_wi,
+ 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_MAP_L2_ISTR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+
+ /*
+ * The following registers are only for running VMs. They are not yet
+ * supported as we don't currently support nested, so expose them as
+ * read-as-zero/write-ignored.
+ */
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VMT_BASER, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VMT_CFGR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VMT_STATUSR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VPE_SELR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VPE_DBR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VPE_HPPIR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VPE_CR0, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VPE_STATUSR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VM_DBR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VM_SELR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VM_STATUSR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VMAP_L2_VMTR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VMAP_VMR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VMAP_VISTR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VMAP_L2_VISTR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VMAP_VPER, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SAVE_VMR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SAVE_VM_STATUSR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+
+ /* MEC, MPAM, SWERR - all unimplemented */
+
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_MEC_IDR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_MEC_MECID_R, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_MPAM_IDR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_MPAM_PARTID_R, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SWERR_STATUSR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SWERR_SYNDROMER0,
+ vgic_mmio_read_raz, vgic_mmio_write_wi, 8,
+ VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SWERR_SYNDROMER1,
+ vgic_mmio_read_raz, vgic_mmio_write_wi, 8,
+ VGIC_ACCESS_64bit),
+};
+
+unsigned int vgic_v5_init_irs_iodev(struct vgic_io_device *dev)
+{
+ dev->regions = vgic_v5_irs_registers;
+ dev->nr_regions = ARRAY_SIZE(vgic_v5_irs_registers);
+
+ kvm_iodevice_init(&dev->dev, &kvm_io_gic_ops);
+
+ /* We represent both of the IRS frames back to back, so this is 128K */
+ return KVM_VGIC_V5_IRS_SIZE;
+}
+
+int vgic_v5_register_irs_iodev(struct kvm *kvm, gpa_t irs_base_address)
+{
+ struct vgic_io_device *io_device = &kvm->arch.vgic.vgic_v5_irs_data->iodev;
+ unsigned int len;
+
+ /*
+ * Design choice: Force MMIO region to be 64k aligned. Simplifies
+ * pulling out registers.
+ */
+ if (!IS_ALIGNED(irs_base_address, SZ_64K))
+ return -EINVAL;
+
+ len = vgic_v5_init_irs_iodev(io_device);
+
+ io_device->base_addr = irs_base_address;
+ io_device->iodev_type = IODEV_GICV5_IRS;
+ io_device->redist_vcpu = NULL;
+
+ return kvm_io_bus_register_dev(kvm, KVM_MMIO_BUS, irs_base_address, len,
+ &io_device->dev);
+}
+
+/**
+ * kvm_vgic_v5_irs_init: initialize the IRS data structures
+ * @kvm: kvm struct pointer
+ * @nr_spis: number of spis, frozen by caller
+ */
+int kvm_vgic_v5_irs_init(struct kvm *kvm, unsigned int nr_spis)
+{
+ struct vgic_dist *dist = &kvm->arch.vgic;
+ struct vgic_v5_irs *irs = dist->vgic_v5_irs_data;
+ struct kvm_vcpu *vcpu0 = kvm_get_vcpu(kvm, 0);
+ size_t nr_spi_bits;
+ u64 mmfr0;
+ int ret, i;
+
+ /*
+ * We (KVM) allocate an Interrupt State Table (IST) for SPIs. The
+ * hardware mandates that lower 6 bits of the address are 0. Each ISTE
+ * is 4 bytes in size (or larger if metadata storage is required), so 16
+ * entries would be enough for alignment. We require userspace to
+ * provide at least 32 SPIs, so we always meet alignment requirements.
+ */
+ if (nr_spis) {
+ dist->spis = kcalloc(nr_spis, sizeof(struct vgic_irq),
+ GFP_KERNEL_ACCOUNT);
+ if (!dist->spis)
+ return -ENOMEM;
+
+ /*
+ * In the following code we do not take the irq struct lock since
+ * no other action on irq structs can happen while the VGIC is
+ * not initialized yet.
+ */
+ for (i = 0; i < nr_spis; i++) {
+ struct vgic_irq *irq = &dist->spis[i];
+
+ irq->intid = vgic_v5_make_spi(i);
+ INIT_LIST_HEAD(&irq->ap_list);
+ raw_spin_lock_init(&irq->irq_lock);
+ irq->vcpu = NULL;
+ irq->target_vcpu = vcpu0;
+ refcount_set(&irq->refcount, 0);
+ /*
+ * The guest controls the enable state, and again it is
+ * directly handled by the hardware. From our point of
+ * view it is always enabled.
+ */
+ irq->enabled = 1;
+ }
+
+ nr_spi_bits = fls(roundup_pow_of_two(nr_spis)) - 1;
+
+ ret = vgic_v5_spi_ist_alloc(kvm, nr_spi_bits);
+ if (ret) {
+ kfree(dist->spis);
+ dist->spis = NULL;
+ return ret;
+ }
+ }
+
+ /* Set sane initial state for the IRS MMIO registers */
+
+ irs->idr0.domain = GICV5_IRS_IDR0_INT_DOM_NON_SECURE;
+
+ mmfr0 = read_sanitised_ftr_reg(SYS_ID_AA64MMFR0_EL1);
+ irs->idr0.pa_range = cpuid_feature_extract_unsigned_field(mmfr0,
+ ID_AA64MMFR0_EL1_PARANGE_SHIFT);
+
+ irs->idr0.virt = 0;
+ irs->idr0.setlpi = 0;
+ irs->idr0.mec = 0;
+ irs->idr0.mpam = 0;
+ irs->idr0.swe = 0;
+ irs->idr0.irs_id = 0;
+
+ irs->idr1.priority_bits = gicv5_global_data.irs_pri_bits - 1;
+
+ /*
+ * Support 16-bits of ID space for the IRS. This should be sufficient
+ * for most applications, and the CPUIF is guaranteed to have at least
+ * 16-bits of ID space support (we actually present 16-bits there, even
+ * if the hardware supports more). Warn if the hardware doesn't support
+ * 16 bits, and use the smaller value. YMMV!
+ *
+ * As for the minimum number of ID bits, we match the hardware's
+ * capability.
+ */
+ if (vgic_v5_irs_ist_id_bits(&irs_caps) < 16)
+ pr_warn("Host IRS supports fewer than 16 ID bits for ISTs (%u)\n",
+ vgic_v5_irs_ist_id_bits(&irs_caps));
+
+ irs->idr2.id_bits = min(16, vgic_v5_irs_ist_id_bits(&irs_caps));
+ irs->idr2.min_lpi_id_bits = vgic_v5_irs_min_lpi_id_bits(&irs_caps);
+
+ /* Only allow the guest to create Linear ISTs - simplifies Save/Restore */
+ irs->idr2.ist_levels = 0;
+ irs->idr2.ist_l2sz = GICV5_IRS_IST_CFGR_L2SZ_4K;
+ irs->idr2.istmd = 0;
+ irs->idr2.istmd_sz = 0;
+
+ /* We have a single IRS, only. All SPIs reside here! */
+ irs->idr5.spi_range = nr_spis;
+ irs->idr6.spi_irs_range = nr_spis;
+ irs->idr7.spi_base = 0;
+
+ irs->cr1.sh = 0;
+ irs->cr1.oc = 0;
+ irs->cr1.ic = 0;
+ irs->cr1.ist_ra = 0;
+ irs->cr1.ist_wa = 0;
+ irs->cr1.vmt_ra = 0;
+ irs->cr1.vpet_ra = 0;
+ irs->cr1.vmd_ra = 0;
+ irs->cr1.vmd_wa = 0;
+ irs->cr1.vped_ra = 0;
+ irs->cr1.vped_wa = 0;
+
+ irs->spi_selr.id = -1;
+
+ irs->pe_selr.iaffid = -1;
+
+ irs->ist_cfgr.lpi_id_bits = 0;
+ irs->ist_cfgr.l2sz = 0;
+ irs->ist_cfgr.istsz = 0;
+ irs->ist_cfgr.structure = 0;
+
+ irs->ist_baser.valid = 0;
+ irs->ist_baser.addr = 0;
+
+ return 0;
+}
diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.c b/arch/arm64/kvm/vgic/vgic-v5-tables.c
index b143f15fd7d47..fa2ced036f7cd 100644
--- a/arch/arm64/kvm/vgic/vgic-v5-tables.c
+++ b/arch/arm64/kvm/vgic/vgic-v5-tables.c
@@ -642,6 +642,22 @@ int vgic_v5_vmte_release(struct kvm *kvm)
return 0;
}
+/*
+ * Provide a way for the IRS MMIO emulation to correctly populate the number of
+ * IAFFID bits (which correspond to our vpe_id_bits.
+ */
+u8 vgic_v5_vmte_vpe_id_bits(struct kvm_vcpu *vcpu)
+{
+ u32 vm_id = vgic_v5_vm_id(vcpu->kvm);
+ struct vgic_v5_vm_info *vmi;
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return 0;
+
+ return vmi->vpe_id_bits;
+}
+
/* Provide the preallocated VPE descriptor to the hardware via the VPE Table. */
int vgic_v5_vmte_alloc_vpe(struct kvm_vcpu *vcpu)
{
diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.h b/arch/arm64/kvm/vgic/vgic-v5-tables.h
index 38b6f5269a2aa..e28d39d59f7fb 100644
--- a/arch/arm64/kvm/vgic/vgic-v5-tables.h
+++ b/arch/arm64/kvm/vgic/vgic-v5-tables.h
@@ -113,6 +113,7 @@ void vgic_v5_release_vm_id(struct kvm *kvm);
int vgic_v5_vmte_init(struct kvm *kvm);
int vgic_v5_vmte_release(struct kvm *kvm);
+u8 vgic_v5_vmte_vpe_id_bits(struct kvm_vcpu *vcpu);
int vgic_v5_vmte_alloc_vpe(struct kvm_vcpu *vcpu);
int vgic_v5_vmte_free_vpe(struct kvm_vcpu *vcpu);
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index f2f5fdc3211d7..282278e4a6c19 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -366,6 +366,7 @@ void vgic_debug_destroy(struct kvm *kvm);
int vgic_v5_probe(const struct gic_kvm_info *info);
void vgic_v5_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);
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);
@@ -378,6 +379,7 @@ void vgic_v5_set_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcr);
void vgic_v5_get_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcr);
void vgic_v5_restore_state(struct kvm_vcpu *vcpu);
void vgic_v5_save_state(struct kvm_vcpu *vcpu);
+int vgic_v5_register_irs_iodev(struct kvm *kvm, gpa_t irs_base_address);
#define for_each_visible_v5_ppi(__i, __k) \
for_each_set_bit(__i, (__k)->arch.vgic.gicv5_vm.vgic_ppi_mask, VGIC_V5_NR_PRIVATE_IRQS)
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index da15570b11292..81a1f33b051a2 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -40,6 +40,7 @@
* in KVM for now. At a future stage, this can be bumped up to 128, if required.
*/
#define VGIC_V5_NR_PRIVATE_IRQS 64
+#define VGIC_V5_DEFAULT_NR_SPIS 32
#define is_v5_type(t, i) (FIELD_GET(GICV5_HWIRQ_TYPE, (i)) == (t))
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 21/49] KVM: arm64: gic-v5: Initialise per-VM IRS state
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (19 preceding siblings ...)
2026-08-07 11:22 ` [PATCH v5 20/49] KVM: arm64: gic-v5: Add GICv5 IRS IODEV and MMIO emulation Sascha Bischoff
@ 2026-08-07 11:23 ` Sascha Bischoff
2026-08-07 11:23 ` [PATCH v5 22/49] KVM: arm64: gic-v5: Register the IRS IODEV Sascha Bischoff
` (27 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:23 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
A virtual GICv5 needs an emulated IRS in addition to the host IRS
state used to back VMTEs, VPEs, and ISTs. Without this, KVM can only
provide the CPU-local PPI state and cannot expose the IRS-backed SPI
and LPI configuration expected by a GICv5 guest.
Allocate the per-VM emulated IRS state when creating a virtual GICv5,
and initialise it from vgic_v5_init(). If userspace has not provided a
number of SPIs, use the GICv5 default of 32. The IRS init path
allocates the SPI state, initialises the virtual IRS register state,
and creates the backing SPI IST when SPIs are present.
Keep the per-VM IRS object alive for the lifetime of the virtual
GICv5. vgic_v5_teardown() only unwinds resources allocated by
vgic_v5_init(), so failed initialisation can be retried, while
kvm_vgic_dist_destroy() frees the IRS object during final VGIC
destruction.
This gives virtual GICv5s the IRS backing required for SPIs and LPIs,
rather than being limited to PPIs only. Further patches add support
for SPI injection and lifecycle tracking.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-init.c | 59 +++++++++++++++++++++++----------
arch/arm64/kvm/vgic/vgic-v5.c | 8 ++++-
2 files changed, 49 insertions(+), 18 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index 8e1e844ed6e93..698bca671ffae 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -191,28 +191,48 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
break;
}
- if (ret) {
- kvm_for_each_vcpu(i, vcpu, kvm) {
- struct vgic_cpu *vgic_cpu = &vcpu->arch.vgic_cpu;
- kfree(vgic_cpu->private_irqs);
- vgic_cpu->private_irqs = NULL;
- }
-
- kvm->arch.vgic.vgic_model = 0;
- goto out_unlock;
- }
+ if (ret)
+ goto out_free_private_irqs;
if (type == KVM_DEV_TYPE_ARM_VGIC_V3)
kvm->arch.vgic.nassgicap = system_supports_direct_sgis();
- /*
- * We now know that we have a GICv5. The Arch Timer PPI interrupts may
- * have been initialised at this stage, but will have done so assuming
- * that we have an older GIC, meaning that the IntIDs won't be
- * correct. We init them again, and this time they will be correct.
- */
- if (type == KVM_DEV_TYPE_ARM_VGIC_V5)
+ if (type == KVM_DEV_TYPE_ARM_VGIC_V5) {
+ /* Allocate a vIRS for GICv5 systems */
+ kvm->arch.vgic.vgic_v5_irs_data = kzalloc_obj(struct vgic_v5_irs,
+ GFP_KERNEL_ACCOUNT);
+ if (!kvm->arch.vgic.vgic_v5_irs_data) {
+ ret = -ENOMEM;
+ goto out_free_private_irqs;
+ }
+
+ /*
+ * Initialization happens later, for now just explicitly
+ * disable the device and undef its base address.
+ */
+ kvm->arch.vgic.vgic_v5_irs_data->vgic_v5_irs_base = VGIC_ADDR_UNDEF;
+
+ /*
+ * We now know that we have a GICv5. The Arch Timer PPI
+ * interrupts may have been initialised at this stage, but will
+ * have done so assuming that we have an older GIC, meaning that
+ * the IntIDs won't be correct. We init them again, and this
+ * time they will be correct.
+ */
kvm_timer_init_vm(kvm);
+ }
+
+ goto out_unlock;
+
+out_free_private_irqs:
+ kvm_for_each_vcpu(i, vcpu, kvm) {
+ struct vgic_cpu *vgic_cpu = &vcpu->arch.vgic_cpu;
+
+ kfree(vgic_cpu->private_irqs);
+ vgic_cpu->private_irqs = NULL;
+ }
+
+ kvm->arch.vgic.vgic_model = 0;
out_unlock:
mutex_unlock(&kvm->arch.config_lock);
@@ -496,6 +516,9 @@ int vgic_init(struct kvm *kvm)
return ret;
}
} else {
+ if (!dist->nr_spis)
+ dist->nr_spis = VGIC_V5_DEFAULT_NR_SPIS;
+
ret = vgic_v5_init(kvm);
if (ret)
return ret;
@@ -541,6 +564,8 @@ static void kvm_vgic_dist_destroy(struct kvm *kvm)
break;
case KVM_DEV_TYPE_ARM_VGIC_V5:
vgic_v5_teardown(kvm);
+ kfree(dist->vgic_v5_irs_data);
+ dist->vgic_v5_irs_data = NULL;
break;
}
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 2932f3cfbe8f4..6a7dabf90dd1b 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -672,7 +672,8 @@ void vgic_v5_teardown(struct kvm *kvm)
/*
* Claim and populate a VMTE (optionally making a new L2 VMT valid), create VPE
- * doorbells, allocate VPET and populate for each VPE.
+ * doorbells, allocate VPET and populate for each VPE. Finally, we also init the
+ * vIRS, which means allocating and making the virtual SPI IST valid.
*
* Note: We do need to put the cart before the horse here. The VPE doorbells are
* our conduit for communication with the IRS, which means we need to have those
@@ -751,6 +752,11 @@ int vgic_v5_init(struct kvm *kvm)
goto err;
}
+ /* Init IRS (and alloc SPI IST) */
+ ret = kvm_vgic_v5_irs_init(kvm, kvm->arch.vgic.nr_spis);
+ if (ret)
+ goto err;
+
return 0;
err:
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 22/49] KVM: arm64: gic-v5: Register the IRS IODEV
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (20 preceding siblings ...)
2026-08-07 11:23 ` [PATCH v5 21/49] KVM: arm64: gic-v5: Initialise per-VM IRS state Sascha Bischoff
@ 2026-08-07 11:23 ` Sascha Bischoff
2026-08-07 11:24 ` [PATCH v5 23/49] KVM: arm64: gic-v5: Set IRICHPPIDIS based on IRS enable state Sascha Bischoff
` (26 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:23 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Now that we have an emulated IRS, it needs to be registered, which
ensures that guest accesses to the MMIO regions handled by the device
are handled appropriately in KVM. Therefore, as part of
vgic_map_resources, the GICv5 IRS IODEV is registered. If the address
for the IRS is not provided, bail out reporting an error - this is not
a supported config.
As part of this change, expose setting the address of the emulated IRS
via KVM_VGIC_V5_ADDR_TYPE_IRS to userspace. Also allow userspace to
set the number of SPIs handled by the emulated GICv5 implementation,
using a GICv5-specific SPI count rather than the legacy total
interrupt count.
Limit the configurable range to 32 through 1024 SPIs, in multiples of
32. KVM keeps one struct vgic_irq per SPI in a physically contiguous
allocation, and allowing the full 16-bit KVM_IRQ_LINE SPI namespace
would make that allocation exceed KMALLOC_MAX_SIZE on common arm64
configurations.
The default routing has one IRQCHIP route per configured SPI, so size
the common IRQ routing table for the largest GICv5 configuration. The
model-specific routing validation retains the 988-pin limit for GICv2
and GICv3.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-init.c | 18 ++++-
arch/arm64/kvm/vgic/vgic-kvm-device.c | 106 ++++++++++++++++++--------
include/kvm/arm_vgic.h | 8 +-
3 files changed, 97 insertions(+), 35 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index 698bca671ffae..5b5d8a4da988d 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -686,9 +686,8 @@ int vgic_lazy_init(struct kvm *kvm)
int kvm_vgic_map_resources(struct kvm *kvm)
{
struct vgic_dist *dist = &kvm->arch.vgic;
- bool needs_dist = true;
enum vgic_type type;
- gpa_t dist_base;
+ gpa_t dist_base, irs_base;
int ret = 0;
if (likely(smp_load_acquire(&dist->ready)))
@@ -711,13 +710,12 @@ int kvm_vgic_map_resources(struct kvm *kvm)
} else {
ret = vgic_v5_map_resources(kvm);
type = VGIC_V5;
- needs_dist = false;
}
if (ret)
goto out;
- if (needs_dist) {
+ if (type != VGIC_V5) {
dist_base = dist->vgic_dist_base;
mutex_unlock(&kvm->arch.config_lock);
@@ -727,7 +725,19 @@ int kvm_vgic_map_resources(struct kvm *kvm)
goto out_slots;
}
} else {
+ irs_base = dist->vgic_v5_irs_data->vgic_v5_irs_base;
mutex_unlock(&kvm->arch.config_lock);
+
+ if (IS_VGIC_ADDR_UNDEF(irs_base)) {
+ ret = -ENXIO;
+ goto out_slots;
+ }
+
+ ret = vgic_v5_register_irs_iodev(kvm, irs_base);
+ if (ret) {
+ kvm_err("Unable to register VGIC IRS MMIO regions\n");
+ goto out_slots;
+ }
}
smp_store_release(&dist->ready, true);
diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c
index 90be99443df3b..8c2c6886af4e3 100644
--- a/arch/arm64/kvm/vgic/vgic-kvm-device.c
+++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c
@@ -181,6 +181,14 @@ static int kvm_vgic_addr(struct kvm *kvm, struct kvm_device_attr *attr, bool wri
addr |= (u64)rdreg->count << KVM_VGIC_V3_RDIST_COUNT_SHIFT;
goto out;
}
+ case KVM_VGIC_V5_ADDR_TYPE_IRS:
+ r = vgic_check_type(kvm, KVM_DEV_TYPE_ARM_VGIC_V5);
+ if (r)
+ break;
+ addr_ptr = &vgic->vgic_v5_irs_data->vgic_v5_irs_base;
+ alignment = SZ_64K;
+ size = KVM_VGIC_V5_IRS_SIZE;
+ break;
default:
r = -ENODEV;
}
@@ -224,31 +232,48 @@ static int vgic_set_common_attr(struct kvm_device *dev,
if (get_user(val, uaddr))
return -EFAULT;
- /*
- * We require:
- * - at least 32 SPIs on top of the 16 SGIs and 16 PPIs
- * - at most 1024 interrupts
- * - a multiple of 32 interrupts
- */
- if (val < (VGIC_NR_PRIVATE_IRQS + 32) ||
- val > VGIC_MAX_RESERVED ||
- (val & 31))
- return -EINVAL;
+ if (!vgic_is_v5(dev->kvm)) {
+ /*
+ * We require:
+ * - at least 32 SPIs on top of the 16 SGIs and 16 PPIs
+ * - at most 1024 interrupts
+ * - a multiple of 32 interrupts
+ */
+ if (val < (VGIC_NR_PRIVATE_IRQS + 32) ||
+ val > VGIC_MAX_RESERVED || (val & 31))
+ return -EINVAL;
- mutex_lock(&dev->kvm->arch.config_lock);
+ mutex_lock(&dev->kvm->arch.config_lock);
- /*
- * Either userspace has already configured NR_IRQS or
- * the vgic has already been initialized and vgic_init()
- * supplied a default amount of SPIs.
- */
- if (dev->kvm->arch.vgic.nr_spis)
- ret = -EBUSY;
- else
- dev->kvm->arch.vgic.nr_spis =
- val - VGIC_NR_PRIVATE_IRQS;
+ /*
+ * Either userspace has already configured NR_IRQS or
+ * the vgic has already been initialized and vgic_init()
+ * supplied a default amount of SPIs.
+ */
+ if (dev->kvm->arch.vgic.nr_spis)
+ ret = -EBUSY;
+ else
+ dev->kvm->arch.vgic.nr_spis =
+ val - VGIC_NR_PRIVATE_IRQS;
- mutex_unlock(&dev->kvm->arch.config_lock);
+ mutex_unlock(&dev->kvm->arch.config_lock);
+ } else {
+ /*
+ * GICv5 reports a number of SPIs, not a total number of
+ * interrupts. Require a multiple of 32 SPIs.
+ */
+ if (val < VGIC_V5_DEFAULT_NR_SPIS ||
+ val > VGIC_V5_MAX_NR_SPIS ||
+ (val & 31))
+ return -EINVAL;
+
+ mutex_lock(&dev->kvm->arch.config_lock);
+ if (vgic_initialized(dev->kvm) || dev->kvm->arch.vgic.nr_spis)
+ ret = -EBUSY;
+ else
+ dev->kvm->arch.vgic.nr_spis = val;
+ mutex_unlock(&dev->kvm->arch.config_lock);
+ }
return ret;
}
@@ -299,9 +324,14 @@ static int vgic_get_common_attr(struct kvm_device *dev,
return (r == -ENODEV) ? -ENXIO : r;
case KVM_DEV_ARM_VGIC_GRP_NR_IRQS: {
u32 __user *uaddr = (u32 __user *)(long)attr->addr;
-
- r = put_user(dev->kvm->arch.vgic.nr_spis +
- VGIC_NR_PRIVATE_IRQS, uaddr);
+ /* Older GICs */
+ if (!vgic_is_v5(dev->kvm)) {
+ r = put_user(dev->kvm->arch.vgic.nr_spis +
+ VGIC_NR_PRIVATE_IRQS,
+ uaddr);
+ } else {
+ r = put_user(dev->kvm->arch.vgic.nr_spis, uaddr);
+ }
break;
}
}
@@ -748,21 +778,25 @@ static int vgic_v5_set_attr(struct kvm_device *dev,
{
switch (attr->group) {
case KVM_DEV_ARM_VGIC_GRP_ADDR:
+ break;
case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
- case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
return -ENXIO;
+ case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
+ break;
case KVM_DEV_ARM_VGIC_GRP_CTRL:
switch (attr->attr) {
case KVM_DEV_ARM_VGIC_CTRL_INIT:
- return vgic_set_common_attr(dev, attr);
+ break;
case KVM_DEV_ARM_VGIC_USERSPACE_PPIS:
default:
return -ENXIO;
}
+ break;
default:
return -ENXIO;
}
+ return vgic_set_common_attr(dev, attr);
}
static int vgic_v5_get_attr(struct kvm_device *dev,
@@ -770,21 +804,26 @@ static int vgic_v5_get_attr(struct kvm_device *dev,
{
switch (attr->group) {
case KVM_DEV_ARM_VGIC_GRP_ADDR:
+ break;
case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
- case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
return -ENXIO;
+ case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
+ break;
case KVM_DEV_ARM_VGIC_GRP_CTRL:
switch (attr->attr) {
case KVM_DEV_ARM_VGIC_CTRL_INIT:
- return vgic_get_common_attr(dev, attr);
+ break;
case KVM_DEV_ARM_VGIC_USERSPACE_PPIS:
return vgic_v5_get_userspace_ppis(dev, attr);
default:
return -ENXIO;
}
+ break;
default:
return -ENXIO;
}
+
+ return vgic_get_common_attr(dev, attr);
}
static int vgic_v5_has_attr(struct kvm_device *dev,
@@ -792,15 +831,22 @@ static int vgic_v5_has_attr(struct kvm_device *dev,
{
switch (attr->group) {
case KVM_DEV_ARM_VGIC_GRP_ADDR:
+ switch (attr->attr) {
+ case KVM_VGIC_V5_ADDR_TYPE_IRS:
+ return 0;
+ }
+ return -ENXIO;
case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
- case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
return -ENXIO;
+ case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
+ return 0;
case KVM_DEV_ARM_VGIC_GRP_CTRL:
switch (attr->attr) {
case KVM_DEV_ARM_VGIC_CTRL_INIT:
return 0;
case KVM_DEV_ARM_VGIC_USERSPACE_PPIS:
return 0;
+ case KVM_DEV_ARM_VGIC_SAVE_PENDING_TABLES:
default:
return -ENXIO;
}
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 81a1f33b051a2..2c7f1274dcc2c 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -31,7 +31,12 @@
#define VGIC_MAX_SPI 1019
#define VGIC_MAX_RESERVED 1023
#define VGIC_MIN_LPI 8192
-#define KVM_IRQCHIP_NUM_PINS (1020 - 32)
+
+/*
+ * The common IRQ routing table must accommodate all GIC types. GICv2 and
+ * GICv3 need 988 pins, while GICv5 needs 1024 pins.
+ */
+#define KVM_IRQCHIP_NUM_PINS 1024
/*
* GICv5 supports 128 PPIs, but only the first 64 are architected. We only
@@ -41,6 +46,7 @@
*/
#define VGIC_V5_NR_PRIVATE_IRQS 64
#define VGIC_V5_DEFAULT_NR_SPIS 32
+#define VGIC_V5_MAX_NR_SPIS BIT(10)
#define is_v5_type(t, i) (FIELD_GET(GICV5_HWIRQ_TYPE, (i)) == (t))
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 23/49] KVM: arm64: gic-v5: Set IRICHPPIDIS based on IRS enable state
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (21 preceding siblings ...)
2026-08-07 11:23 ` [PATCH v5 22/49] KVM: arm64: gic-v5: Register the IRS IODEV Sascha Bischoff
@ 2026-08-07 11:24 ` Sascha Bischoff
2026-08-07 11:24 ` [PATCH v5 24/49] KVM: arm64: selftests: Update vGICv5 selftest to set IRS address Sascha Bischoff
` (25 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:24 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
The GICv5 ICH_CONTEXTR_EL2 register has the IRICHPPIDIS field, which
allows the hypervisor to disable HPPI selection for SPIs and LPIs.
Use this to emulate the guest enabling and disabling the IRS.
Program IRICHPPIDIS from the virtual IRS enable state when loading a
VPE. This prevents SPIs and LPIs from being delivered until the guest
enables the emulated IRS, matching the behaviour of real hardware.
Likewise, when making a VPE non-resident as it enters WFI, only
calculate the doorbell priority mask and request a doorbell if the
virtual IRS is enabled. A disabled IRS cannot select an SPI or LPI to
wake the VPE, so neither the priority calculation nor a doorbell
request serves any purpose in that state.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-v5.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 6a7dabf90dd1b..b401e80d74354 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -1063,6 +1063,7 @@ void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu)
void vgic_v5_load(struct kvm_vcpu *vcpu)
{
+ bool irichppidis = !READ_ONCE(vcpu->kvm->arch.vgic.enabled);
struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
u16 vm = vgic_v5_vm_id(vcpu->kvm);
u16 vpe = vgic_v5_vpe_id(vcpu);
@@ -1079,6 +1080,7 @@ void vgic_v5_load(struct kvm_vcpu *vcpu)
kvm_call_hyp(__vgic_v5_restore_vmcr_apr, cpu_if);
cpu_if->vgic_contextr = FIELD_PREP(ICH_CONTEXTR_EL2_V, true) |
+ FIELD_PREP(ICH_CONTEXTR_EL2_IRICHPPIDIS, irichppidis) |
FIELD_PREP(ICH_CONTEXTR_EL2_VPE, vpe) |
FIELD_PREP(ICH_CONTEXTR_EL2_VM, vm);
@@ -1105,7 +1107,10 @@ void vgic_v5_put(struct kvm_vcpu *vcpu)
kvm_call_hyp(__vgic_v5_save_apr, cpu_if);
cpu_if->vgic_contextr = 0;
- if (vcpu_get_flag(vcpu, IN_WFI)) {
+
+ /* Request a doorbell if entering WFI, unless the IRS is disabled */
+ if (vcpu_get_flag(vcpu, IN_WFI) &&
+ READ_ONCE(vcpu->kvm->arch.vgic.enabled)) {
u32 priority_mask;
int dbpm;
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 24/49] KVM: arm64: selftests: Update vGICv5 selftest to set IRS address
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (22 preceding siblings ...)
2026-08-07 11:24 ` [PATCH v5 23/49] KVM: arm64: gic-v5: Set IRICHPPIDIS based on IRS enable state Sascha Bischoff
@ 2026-08-07 11:24 ` Sascha Bischoff
2026-08-07 11:25 ` [PATCH v5 25/49] KVM: arm64: gic-v5: Add GIC VDPEND hyp call Sascha Bischoff
` (24 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:24 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
This selftest was added before the GICv5 IRS was supported in
KVM. Therefore, there was no address to set, and the specific UAPI
didn't even exist.
Now that the IRS is supported, and setting its address is mandatory
before VGIC resources are mapped, set the emulated IRS GPA before
initialising the VGIC. Running a GICv5 VM will fail if userspace has
not provided the IRS address before the first vCPU run.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
tools/testing/selftests/kvm/arm64/vgic_v5.c | 6 ++++++
tools/testing/selftests/kvm/include/arm64/gic_v5.h | 3 +++
2 files changed, 9 insertions(+)
diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
index 96cfd6bb32f6f..19039a8940568 100644
--- a/tools/testing/selftests/kvm/arm64/vgic_v5.c
+++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
@@ -100,6 +100,7 @@ static void test_vgic_v5_ppis(u32 gic_dev_type)
struct ucall uc;
u64 user_ppis[2];
struct vm_gic v;
+ uint64_t attr;
int ret, i;
v.gic_dev_type = gic_dev_type;
@@ -116,6 +117,11 @@ static void test_vgic_v5_ppis(u32 gic_dev_type)
for (i = 0; i < NR_VCPUS; i++)
vcpu_init_descriptor_tables(vcpus[i]);
+ /* Set the address of the IRS before initialising the GIC */
+ attr = GICV5_IRS_CONFIG_BASE_GPA;
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &attr);
+
kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
diff --git a/tools/testing/selftests/kvm/include/arm64/gic_v5.h b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
index eb523d9277cf1..c388df8f2a2b4 100644
--- a/tools/testing/selftests/kvm/include/arm64/gic_v5.h
+++ b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
@@ -10,6 +10,9 @@
#include "processor.h"
+/* GIC component base address is guest PA space */
+#define GICV5_IRS_CONFIG_BASE_GPA 0x8000000ULL
+
/*
* Definitions for GICv5 instructions for the Current Domain
*/
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 25/49] KVM: arm64: gic-v5: Add GIC VDPEND hyp call
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (23 preceding siblings ...)
2026-08-07 11:24 ` [PATCH v5 24/49] KVM: arm64: selftests: Update vGICv5 selftest to set IRS address Sascha Bischoff
@ 2026-08-07 11:25 ` Sascha Bischoff
2026-08-07 11:25 ` [PATCH v5 26/49] KVM: arm64: gic: Introduce set_pending_state() to irq_ops Sascha Bischoff
` (23 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:25 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
With PPIs, their state is injected via the ICH_PPI_x_EL2 system
registers. For SPIs and LPIs, there are no such registers as these
would limit the number of interrupts significantly. Instead, SPI and
LPI pending state can be managed from the hypervisor using the GIC
VDPEND instruction. This provides a way to set an SPI or LPI for a VM
as pending or non-pending, i.e., to inject interrupts into a guest.
This system instruction is only executable from EL2, and therefore
must be wrapped in a hypercall for NVHE/hVHE configurations.
With pKVM this hypercall is explicitly a no-op as we currently don't
support the combination of GICv5 and pKVM.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/include/asm/kvm_asm.h | 1 +
arch/arm64/include/asm/kvm_hyp.h | 1 +
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 13 +++++++++++++
arch/arm64/kvm/hyp/vgic-v5-sr.c | 10 ++++++++++
4 files changed, 25 insertions(+)
diff --git a/arch/arm64/include/asm/kvm_asm.h b/arch/arm64/include/asm/kvm_asm.h
index d9ff9c2999aa7..36808ed0e9afe 100644
--- a/arch/arm64/include/asm/kvm_asm.h
+++ b/arch/arm64/include/asm/kvm_asm.h
@@ -89,6 +89,7 @@ enum __kvm_host_smccc_func {
__KVM_HOST_SMCCC_FUNC___vgic_v3_restore_vmcr_aprs,
__KVM_HOST_SMCCC_FUNC___vgic_v5_make_resident,
__KVM_HOST_SMCCC_FUNC___vgic_v5_make_non_resident,
+ __KVM_HOST_SMCCC_FUNC___vgic_v5_vdpend,
__KVM_HOST_SMCCC_FUNC___vgic_v5_save_apr,
__KVM_HOST_SMCCC_FUNC___vgic_v5_restore_vmcr_apr,
diff --git a/arch/arm64/include/asm/kvm_hyp.h b/arch/arm64/include/asm/kvm_hyp.h
index 8fcbbff719cce..8e2f0280c0cc5 100644
--- a/arch/arm64/include/asm/kvm_hyp.h
+++ b/arch/arm64/include/asm/kvm_hyp.h
@@ -97,6 +97,7 @@ void __vgic_v5_save_ppi_state(struct vgic_v5_cpu_if *cpu_if);
void __vgic_v5_restore_ppi_state(struct vgic_v5_cpu_if *cpu_if);
void __vgic_v5_save_state(struct vgic_v5_cpu_if *cpu_if);
void __vgic_v5_restore_state(struct vgic_v5_cpu_if *cpu_if);
+void __vgic_v5_vdpend(u32 intid, bool pending, u16 vm);
#ifdef __KVM_NVHE_HYPERVISOR__
void __timer_enable_traps(struct kvm_vcpu *vcpu);
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index 7fdcc6bb6f594..a372702aaf5a8 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -725,6 +725,18 @@ static void handle___vgic_v5_restore_vmcr_apr(struct kvm_cpu_context *host_ctxt)
__vgic_v5_restore_vmcr_apr(kern_hyp_va(cpu_if));
}
+static void handle___vgic_v5_vdpend(struct kvm_cpu_context *host_ctxt)
+{
+ DECLARE_REG(u32, intid, host_ctxt, 1);
+ DECLARE_REG(bool, pending, host_ctxt, 2);
+ DECLARE_REG(u16, vm, host_ctxt, 3);
+
+ if (unlikely(is_protected_kvm_enabled()))
+ return;
+
+ __vgic_v5_vdpend(intid, pending, vm);
+}
+
typedef void (*hcall_t)(struct kvm_cpu_context *);
#define HANDLE_FUNC(x) [__KVM_HOST_SMCCC_FUNC_##x] = (hcall_t)handle_##x
@@ -760,6 +772,7 @@ static const hcall_t host_hcall[] = {
HANDLE_FUNC(__vgic_v3_restore_vmcr_aprs),
HANDLE_FUNC(__vgic_v5_make_resident),
HANDLE_FUNC(__vgic_v5_make_non_resident),
+ HANDLE_FUNC(__vgic_v5_vdpend),
HANDLE_FUNC(__vgic_v5_save_apr),
HANDLE_FUNC(__vgic_v5_restore_vmcr_apr),
diff --git a/arch/arm64/kvm/hyp/vgic-v5-sr.c b/arch/arm64/kvm/hyp/vgic-v5-sr.c
index 46992a6c2cacb..94746317ca79f 100644
--- a/arch/arm64/kvm/hyp/vgic-v5-sr.c
+++ b/arch/arm64/kvm/hyp/vgic-v5-sr.c
@@ -149,3 +149,13 @@ void __vgic_v5_restore_state(struct vgic_v5_cpu_if *cpu_if)
{
write_sysreg_s(cpu_if->vgic_icsr, SYS_ICC_ICSR_EL1);
}
+
+void __vgic_v5_vdpend(u32 intid, bool pending, u16 vm)
+{
+ u64 value;
+
+ value = intid & (GICV5_GIC_VDPEND_ID_MASK | GICV5_GIC_VDPEND_TYPE_MASK);
+ value |= FIELD_PREP(GICV5_GIC_VDPEND_PENDING_MASK, pending);
+ value |= FIELD_PREP(GICV5_GIC_VDPEND_VM_MASK, vm);
+ gic_insn(value, VDPEND);
+}
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 26/49] KVM: arm64: gic: Introduce set_pending_state() to irq_ops
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (24 preceding siblings ...)
2026-08-07 11:25 ` [PATCH v5 25/49] KVM: arm64: gic-v5: Add GIC VDPEND hyp call Sascha Bischoff
@ 2026-08-07 11:25 ` Sascha Bischoff
2026-08-07 11:26 ` [PATCH v5 27/49] KVM: arm64: gic-v5: Support SPI injection Sascha Bischoff
` (22 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:25 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
There are times, such as with GICv5 SPIs and LPIs, where the hardware
itself manages parts of the interrupt lifecycle. This means that
pending state can be directly communicated to the hardware instead of
being represented only in the VGIC shadow state.
In order to accommodate cases where the hardware handles pending state
directly, add a new set_pending_state() function pointer to
irq_ops. The intent is for this to be used after the VGIC shadow
pending state has changed, allowing the backend to mirror the updated
state into hardware.
This new function is plumbed into kvm_vgic_inject_irq(), and is only
called if irq_ops are provided and this function pointer is explicitly
set. In the general case, this has no effect.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic.c | 3 +++
include/kvm/arm_vgic.h | 6 ++++++
2 files changed, 9 insertions(+)
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index 45af0287d6587..8f1994ec37311 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -562,6 +562,9 @@ int kvm_vgic_inject_irq(struct kvm *kvm, struct kvm_vcpu *vcpu,
else
irq->pending_latch = true;
+ if (irq->ops && irq->ops->set_pending_state)
+ WARN_ON_ONCE(!irq->ops->set_pending_state(vcpu, irq));
+
vgic_queue_irq_unlock(kvm, irq, flags);
vgic_put_irq(kvm, irq);
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 2c7f1274dcc2c..9f16ee63d5763 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -246,6 +246,12 @@ struct irq_ops {
*/
bool (*get_input_level)(int vintid);
+ /*
+ * Function pointer to directly update hardware pending state after the
+ * VGIC shadow pending state has changed.
+ */
+ bool (*set_pending_state)(struct kvm_vcpu *vcpu, struct vgic_irq *irq);
+
/*
* Function pointer to override the queuing of an IRQ.
*/
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 27/49] KVM: arm64: gic-v5: Support SPI injection
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (25 preceding siblings ...)
2026-08-07 11:25 ` [PATCH v5 26/49] KVM: arm64: gic: Introduce set_pending_state() to irq_ops Sascha Bischoff
@ 2026-08-07 11:26 ` Sascha Bischoff
2026-08-07 11:26 ` [PATCH v5 28/49] Documentation: KVM: Extend VGICv5 device attribute docs Sascha Bischoff
` (21 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:26 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
GICv5 SPI lifecycle is handled by the GICv5 hardware once the pending
state has been injected.
This change adds support for injecting SPIs to the core VGIC code and
GICv5 code. First of all, allow GICv5 SPIs to be looked up by ID via
vgic_get_irq(). Previously, only PPIs were supported.
Two irq_ops are used for GICv5 SPI injection. The set_pending_state()
irq_op is used to inject the SPI's pending state into the guest. The
queue_irq_unlock() irq_op releases the IRQ lock without queueing the
SPI on a per-vCPU AP list, as GICv5 does not track in-flight SPI state
in KVM. Tracking the affinity and lifecycle of individual SPIs in
software would negate much of the benefit of their lifecycle being
hardware managed.
Limit injection to the configured per-VM SPI range. Since GICv5 SPI
IDs start at zero, KVM_IRQ_LINE accepts IDs from zero through one less
than the configured number of SPIs, up to 1023. Update the
KVM_IRQ_LINE documentation accordingly.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
Documentation/virt/kvm/api.rst | 3 ++-
arch/arm64/kvm/vgic/vgic-irs-v5.c | 1 +
arch/arm64/kvm/vgic/vgic-v5.c | 41 +++++++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic.c | 28 +++++++++++++++------
arch/arm64/kvm/vgic/vgic.h | 2 ++
5 files changed, 66 insertions(+), 9 deletions(-)
diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index e3003a241d5b0..8647980e81c1f 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -909,7 +909,8 @@ The irq_type field has the following values:
- KVM_ARM_IRQ_TYPE_SPI:
in-kernel GICv2/GICv3: SPI, irq_id between 32 and 1019 (incl.)
(the vcpu_index field is ignored)
- in-kernel GICv5: SPI, irq_id between 0 and 65535 (incl.)
+ in-kernel GICv5: SPI, irq_id between 0 and the configured
+ number of SPIs minus one (1023 maximum)
- KVM_ARM_IRQ_TYPE_PPI:
in-kernel GICv2/GICv3: PPI, irq_id between 16 and 31 (incl.)
in-kernel GICv5: PPI, irq_id between 0 and 127 (incl.)
diff --git a/arch/arm64/kvm/vgic/vgic-irs-v5.c b/arch/arm64/kvm/vgic/vgic-irs-v5.c
index c6280795ab551..095046c7959fa 100644
--- a/arch/arm64/kvm/vgic/vgic-irs-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-irs-v5.c
@@ -680,6 +680,7 @@ int kvm_vgic_v5_irs_init(struct kvm *kvm, unsigned int nr_spis)
* view it is always enabled.
*/
irq->enabled = 1;
+ vgic_v5_set_spi_ops(irq);
}
nr_spi_bits = fls(roundup_pow_of_two(nr_spis)) - 1;
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index b401e80d74354..62e069c21367a 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -1061,6 +1061,47 @@ void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu)
VGIC_V5_NR_PRIVATE_IRQS);
}
+static bool vgic_v5_set_spi_pending_state(struct kvm_vcpu *vcpu,
+ struct vgic_irq *irq)
+{
+ vgic_v5_set_irq_pend(irq->target_vcpu, irq);
+ return true;
+}
+
+static bool vgic_v5_spi_queue_irq_unlock(struct kvm *kvm,
+ struct vgic_irq *irq,
+ unsigned long flags)
+ __releases(&irq->irq_lock)
+{
+ lockdep_assert_held(&irq->irq_lock);
+
+ raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
+ return true;
+}
+
+static const struct irq_ops vgic_v5_spi_irq_ops = {
+ .set_pending_state = vgic_v5_set_spi_pending_state,
+ .queue_irq_unlock = vgic_v5_spi_queue_irq_unlock,
+};
+
+void vgic_v5_set_spi_ops(struct vgic_irq *irq)
+{
+ if (WARN_ON(!irq) || WARN_ON(irq->ops))
+ return;
+
+ irq->ops = &vgic_v5_spi_irq_ops;
+}
+
+/* Set the pending state for GICv5 SPIs and LPIs */
+void vgic_v5_set_irq_pend(struct kvm_vcpu *vcpu, struct vgic_irq *irq)
+{
+ if (WARN_ON(__irq_is_ppi(KVM_DEV_TYPE_ARM_VGIC_V5, irq->intid)))
+ return;
+
+ kvm_call_hyp(__vgic_v5_vdpend, irq->intid, irq_is_pending(irq),
+ vcpu->kvm->arch.vgic.gicv5_vm.vm_id);
+}
+
void vgic_v5_load(struct kvm_vcpu *vcpu)
{
bool irichppidis = !READ_ONCE(vcpu->kvm->arch.vgic.enabled);
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index 8f1994ec37311..5f10c0d9aa1c4 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -86,19 +86,31 @@ static struct vgic_irq *vgic_get_lpi(struct kvm *kvm, u32 intid)
*/
struct vgic_irq *vgic_get_irq(struct kvm *kvm, u32 intid)
{
- /* Non-private IRQs are not yet implemented for GICv5 */
- if (vgic_is_v5(kvm))
- return NULL;
+ enum kvm_device_type type = kvm->arch.vgic.vgic_model;
/* SPIs */
- if (intid >= VGIC_NR_PRIVATE_IRQS &&
- intid < (kvm->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS)) {
- intid = array_index_nospec(intid, kvm->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS);
- return &kvm->arch.vgic.spis[intid - VGIC_NR_PRIVATE_IRQS];
+ if (__irq_is_spi(type, intid)) {
+ switch (type) {
+ case KVM_DEV_TYPE_ARM_VGIC_V5:
+ intid = vgic_v5_get_hwirq_id(intid);
+
+ if (intid >= kvm->arch.vgic.nr_spis)
+ return NULL;
+
+ intid = array_index_nospec(intid, kvm->arch.vgic.nr_spis);
+ return &kvm->arch.vgic.spis[intid];
+ default: {
+ u32 max_intid = kvm->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS;
+
+ if (intid < max_intid) {
+ intid = array_index_nospec(intid, max_intid);
+ return &kvm->arch.vgic.spis[intid - VGIC_NR_PRIVATE_IRQS];
+ }
+ }}
}
/* LPIs */
- if (irq_is_lpi(kvm, intid))
+ if (__irq_is_lpi(type, intid))
return vgic_get_lpi(kvm, intid);
return NULL;
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 282278e4a6c19..107737c77e4d5 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -370,6 +370,8 @@ int kvm_vgic_v5_irs_init(struct kvm *kvm, unsigned int nr_spis);
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);
+void vgic_v5_set_spi_ops(struct vgic_irq *irq);
+void vgic_v5_set_irq_pend(struct kvm_vcpu *vcpu, struct vgic_irq *irq);
bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu);
void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu);
void vgic_v5_fold_ppi_state(struct kvm_vcpu *vcpu);
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 28/49] Documentation: KVM: Extend VGICv5 device attribute docs
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (26 preceding siblings ...)
2026-08-07 11:26 ` [PATCH v5 27/49] KVM: arm64: gic-v5: Support SPI injection Sascha Bischoff
@ 2026-08-07 11:26 ` Sascha Bischoff
2026-08-07 11:27 ` [PATCH v5 29/49] KVM: arm64: gic-v5: Add GICv5 SPI injection to irqfd Sascha Bischoff
` (20 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:26 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
The VGICv5 documentation still describes the device as supporting PPIs
only. Update the overview to cover PPI, SPI, and LPI interrupts. PPIs
and SPIs can be injected by in-kernel devices or through KVM_IRQ_LINE,
while LPI pending state is driven by the guest and handled in hardware
through the LPI IST.
Document the GICv5 IRS address attribute and its GPA layout
requirements. The IRS region must be 64K aligned and covers a 128K
range.
Also document the GICv5-specific interpretation of
KVM_DEV_ARM_VGIC_GRP_NR_IRQS. Unlike GICv2 and GICv3, the value
describes only the number of SPIs exposed by the IRS and does not
include SGIs or PPIs. The supported range is 32 through 1024 SPIs in
multiples of 32, and defaults to 32 when userspace leaves it unset.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
.../virt/kvm/devices/arm-vgic-v5.rst | 35 ++++++++++++++++---
1 file changed, 31 insertions(+), 4 deletions(-)
diff --git a/Documentation/virt/kvm/devices/arm-vgic-v5.rst b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
index 70b9162755c7e..c782ea4d4609c 100644
--- a/Documentation/virt/kvm/devices/arm-vgic-v5.rst
+++ b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
@@ -12,12 +12,39 @@ Only one VGIC instance may be instantiated through this API. The created VGIC
will act as the VM interrupt controller, requiring emulated user-space devices
to inject interrupts to the VGIC instead of directly to CPUs.
-Creating a guest GICv5 device requires a GICv5 host. The current VGICv5 device
-only supports PPI interrupts. These can either be injected from emulated
-in-kernel devices (such as the Arch Timer, or PMU), or via the KVM_IRQ_LINE
-ioctl.
+Creating a guest GICv5 device requires a GICv5 host. The VGICv5 device supports
+PPI, SPI, and LPI interrupts. The PPI and SPI interrupts can either be injected
+from emulated in-kernel devices (such as the Arch Timer, or PMU), or via the
+KVM_IRQ_LINE ioctl. LPIs are not externally injected, but are handled in
+hardware via the LPI IST. Their pending state is driven directly by the guest.
Groups:
+ KVM_DEV_ARM_VGIC_GRP_ADDR
+ Attributes:
+
+ KVM_VGIC_V5_ADDR_TYPE_IRS (rw, 64-bit)
+ Base address in the guest physical address space of the GICv5 IRS
+ (Interrupt Routing Service) register mappings. Only valid for
+ KVM_DEV_TYPE_ARM_VGIC_V5. This address needs to be 64K aligned and the
+ region covers 128 KByte - the IRS has a CONFIG_FRAME and a SETLPI_FRAME,
+ each of which is 64 KBytes in size.
+
+ Setting the address of the IRS in GPA space is mandatory before VGIC
+ resources are mapped, as the IRS is responsible for handling SPIs and
+ LPIs. Failure to set the IRS address before the first vCPU run results in
+ an error.
+
+ KVM_DEV_ARM_VGIC_GRP_NR_IRQS
+ Attributes:
+
+ A value describing the number of SPIs for this GIC instance. This is
+ GICv5-specific: unlike GICv2/v3, the value does not include SGIs or PPIs.
+ The value ranges from 32 to KVM's VGICv5 maximum of 1024 SPIs, in
+ increments of 32. If userspace does not set this attribute, KVM uses 32
+ SPIs by default.
+
+ kvm_device_attr.addr points to a __u32 value.
+
KVM_DEV_ARM_VGIC_GRP_CTRL
Attributes:
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 29/49] KVM: arm64: gic-v5: Add GICv5 SPI injection to irqfd
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (27 preceding siblings ...)
2026-08-07 11:26 ` [PATCH v5 28/49] Documentation: KVM: Extend VGICv5 device attribute docs Sascha Bischoff
@ 2026-08-07 11:27 ` Sascha Bischoff
2026-08-07 11:27 ` [PATCH v5 30/49] KVM: arm64: gic-v5: Mask per-vCPU PPI state in vgic_v5_finalize_ppi_state() Sascha Bischoff
` (19 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:27 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Now that there is support for GICv5 SPIs in KVM, update
vgic_irqfd_set_irq() to translate irqchip pins into GICv5 SPI IntIDs
before injecting them.
Validate IRQCHIP route pins against the GIC model. GICv2 and GICv3
only support 988 pins for SPIs 32 through 1019, while KVM supports up
to 1024 GICv5 SPI pins. The common routing table is sized for the
largest supported range.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-irqfd.c | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-irqfd.c b/arch/arm64/kvm/vgic/vgic-irqfd.c
index 19a1094536e6a..ee1a1b214f03c 100644
--- a/arch/arm64/kvm/vgic/vgic-irqfd.c
+++ b/arch/arm64/kvm/vgic/vgic-irqfd.c
@@ -19,9 +19,14 @@ static int vgic_irqfd_set_irq(struct kvm_kernel_irq_routing_entry *e,
struct kvm *kvm, int irq_source_id,
int level, bool line_status)
{
- unsigned int spi_id = e->irqchip.pin + VGIC_NR_PRIVATE_IRQS;
+ unsigned int spi_id;
int ret;
+ if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
+ spi_id = vgic_v5_make_spi(e->irqchip.pin);
+ else
+ spi_id = e->irqchip.pin + VGIC_NR_PRIVATE_IRQS;
+
if (!vgic_valid_spi(kvm, spi_id))
return -EINVAL;
@@ -45,6 +50,13 @@ int kvm_set_routing_entry(struct kvm *kvm,
struct kvm_kernel_irq_routing_entry *e,
const struct kvm_irq_routing_entry *ue)
{
+ /*
+ * The common routing table reserves enough entries for all GICv5 SPI
+ * pins. KVM limits GICv5 to 1024 SPI pins, while GICv2 and GICv3
+ * only support SPIs 32..1019, leaving 988 pins.
+ */
+ unsigned int max_irqchip_pins = vgic_is_v5(kvm) ?
+ VGIC_V5_MAX_NR_SPIS : VGIC_MAX_SPI - VGIC_NR_PRIVATE_IRQS + 1;
int r = -EINVAL;
switch (ue->type) {
@@ -52,8 +64,8 @@ int kvm_set_routing_entry(struct kvm *kvm,
e->set = vgic_irqfd_set_irq;
e->irqchip.irqchip = ue->u.irqchip.irqchip;
e->irqchip.pin = ue->u.irqchip.pin;
- if ((e->irqchip.pin >= KVM_IRQCHIP_NUM_PINS) ||
- (e->irqchip.irqchip >= KVM_NR_IRQCHIPS))
+ if (e->irqchip.pin >= max_irqchip_pins ||
+ e->irqchip.irqchip >= KVM_NR_IRQCHIPS)
goto out;
break;
case KVM_IRQ_ROUTING_MSI:
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 30/49] KVM: arm64: gic-v5: Mask per-vCPU PPI state in vgic_v5_finalize_ppi_state()
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (28 preceding siblings ...)
2026-08-07 11:27 ` [PATCH v5 29/49] KVM: arm64: gic-v5: Add GICv5 SPI injection to irqfd Sascha Bischoff
@ 2026-08-07 11:27 ` Sascha Bischoff
2026-08-07 11:28 ` [PATCH v5 31/49] KVM: arm64: gic-v5: Add GICv5 EL1 sysreg userspace accessors Sascha Bischoff
` (18 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:27 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Only a subset of the possible PPIs are exposed to a guest when running
with a vGICv5. First of all, only the architected PPIs are considered
by KVM. Secondly, only a set of those is exposed to a guest: those
corresponding to devices that KVM emulates, such as the timers and
PMU, and the GICv5 SW_PPI.
The finalisation of exposed PPIs happens on first vCPU run, as this is
the first time when the full set of exposed devices is known. At this
stage a mask is calculated, and this mask is applied both to hide
non-exposed PPI state from the guest and to reduce overhead when
iterating over the PPIs.
While preparing userspace access to the GICv5 system registers, it
became apparent that restoring the GICv5 PPI registers can result in a
mismatch between the state supplied by userspace and the state KVM
intends to expose. Userspace can provide Enable, Active, and Pending
state for PPIs that KVM has chosen to hide from the guest.
Userspace must restore PPI state before any vCPU runs. The userspace
access path added subsequently enforces this ordering. Rework
vgic_v5_finalize_ppi_state() to calculate the mask of exposed PPIs and
clear any state belonging to non-exposed PPIs. This ensures that only
the state KVM intends to expose is visible to the guest.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/arm.c | 2 +-
arch/arm64/kvm/vgic/vgic-v5.c | 71 +++++++++++++++++++++++++----------
include/kvm/arm_vgic.h | 2 +-
3 files changed, 53 insertions(+), 22 deletions(-)
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 908a058314117..c56e2a007b2e7 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -974,7 +974,7 @@ int kvm_arch_vcpu_run_pid_change(struct kvm_vcpu *vcpu)
return ret;
}
- ret = vgic_v5_finalize_ppi_state(kvm);
+ ret = vgic_v5_finalize_ppi_state(vcpu);
if (ret)
return ret;
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 62e069c21367a..beabc5980b2c1 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -781,9 +781,10 @@ int vgic_v5_map_resources(struct kvm *kvm)
return 0;
}
-int vgic_v5_finalize_ppi_state(struct kvm *kvm)
+int vgic_v5_finalize_ppi_state(struct kvm_vcpu *vcpu)
{
- struct kvm_vcpu *vcpu0;
+ struct kvm *kvm = vcpu->kvm;
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
int i;
if (!vgic_is_v5(kvm))
@@ -792,35 +793,65 @@ int vgic_v5_finalize_ppi_state(struct kvm *kvm)
guard(mutex)(&kvm->arch.config_lock);
/*
- * If SW_PPI has been advertised, then we know we already
- * initialised the whole thing, and we can return early. Yes,
- * this is pretty hackish as far as state tracking goes...
+ * Discover the set of PPIs that are exposed to the guest once per VM.
+ * Once known, apply that mask to each VCPU's restored PPI state as the
+ * VCPUs are first run.
*/
- if (test_bit(GICV5_ARCH_PPI_SW_PPI, kvm->arch.vgic.gicv5_vm.vgic_ppi_mask))
- return 0;
-
- /* The PPI state for all VCPUs should be the same. Pick the first. */
- vcpu0 = kvm_get_vcpu(kvm, 0);
+ if (!test_bit(GICV5_ARCH_PPI_SW_PPI, kvm->arch.vgic.gicv5_vm.vgic_ppi_mask)) {
+ bitmap_zero(kvm->arch.vgic.gicv5_vm.vgic_ppi_mask,
+ VGIC_V5_NR_PRIVATE_IRQS);
+ bitmap_zero(kvm->arch.vgic.gicv5_vm.vgic_ppi_hmr,
+ VGIC_V5_NR_PRIVATE_IRQS);
+
+ for_each_set_bit(i, ppi_caps.impl_ppi_mask, VGIC_V5_NR_PRIVATE_IRQS) {
+ const u32 intid = vgic_v5_make_ppi(i);
+ struct vgic_irq *irq;
+
+ irq = vgic_get_vcpu_irq(vcpu, intid);
+
+ /* Expose PPIs with an owner or the SW_PPI, only */
+ scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) {
+ if (irq->owner || i == GICV5_ARCH_PPI_SW_PPI) {
+ __set_bit(i, kvm->arch.vgic.gicv5_vm.vgic_ppi_mask);
+ __assign_bit(i, kvm->arch.vgic.gicv5_vm.vgic_ppi_hmr,
+ irq->config == VGIC_CONFIG_LEVEL);
+ }
+ }
- bitmap_zero(kvm->arch.vgic.gicv5_vm.vgic_ppi_mask, VGIC_V5_NR_PRIVATE_IRQS);
- bitmap_zero(kvm->arch.vgic.gicv5_vm.vgic_ppi_hmr, VGIC_V5_NR_PRIVATE_IRQS);
+ vgic_put_irq(kvm, irq);
+ }
+ }
- for_each_set_bit(i, ppi_caps.impl_ppi_mask, VGIC_V5_NR_PRIVATE_IRQS) {
+ /*
+ * Apply the mask to Enable, Active. Skip pending as that's calculated
+ * on guest entry.
+ */
+ bitmap_and(cpu_if->vgic_ppi_enabler, cpu_if->vgic_ppi_enabler,
+ kvm->arch.vgic.gicv5_vm.vgic_ppi_mask, VGIC_V5_NR_PRIVATE_IRQS);
+ bitmap_and(cpu_if->vgic_ppi_activer, cpu_if->vgic_ppi_activer,
+ kvm->arch.vgic.gicv5_vm.vgic_ppi_mask, VGIC_V5_NR_PRIVATE_IRQS);
+
+ /* Also update the vgic_irqs */
+ for (i = 0; i < VGIC_V5_NR_PRIVATE_IRQS; i++) {
+ bool visible = test_bit(i, kvm->arch.vgic.gicv5_vm.vgic_ppi_mask);
const u32 intid = vgic_v5_make_ppi(i);
struct vgic_irq *irq;
- irq = vgic_get_vcpu_irq(vcpu0, intid);
+ irq = vgic_get_vcpu_irq(vcpu, intid);
- /* Expose PPIs with an owner or the SW_PPI, only */
scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) {
- if (irq->owner || i == GICV5_ARCH_PPI_SW_PPI) {
- __set_bit(i, kvm->arch.vgic.gicv5_vm.vgic_ppi_mask);
- __assign_bit(i, kvm->arch.vgic.gicv5_vm.vgic_ppi_hmr,
- irq->config == VGIC_CONFIG_LEVEL);
+ if (!visible) {
+ irq->enabled = false;
+ irq->active = false;
+ irq->pending_latch = false;
+ irq->line_level = false;
+ } else {
+ irq->enabled = test_bit(i, cpu_if->vgic_ppi_enabler);
+ irq->active = test_bit(i, cpu_if->vgic_ppi_activer);
}
}
- vgic_put_irq(vcpu0->kvm, irq);
+ vgic_put_irq(kvm, irq);
}
return 0;
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 9f16ee63d5763..b6526c678a5a2 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -823,7 +823,7 @@ static inline u32 vgic_v5_irs_max_vpes(const struct vgic_v5_irs_caps *caps)
return BIT(FIELD_GET(GICV5_IRS_IDR4_VPE_ID_BITS, caps->idr4) + 1);
}
-int vgic_v5_finalize_ppi_state(struct kvm *kvm);
+int vgic_v5_finalize_ppi_state(struct kvm_vcpu *vcpu);
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);
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 31/49] KVM: arm64: gic-v5: Add GICv5 EL1 sysreg userspace accessors
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (29 preceding siblings ...)
2026-08-07 11:27 ` [PATCH v5 30/49] KVM: arm64: gic-v5: Mask per-vCPU PPI state in vgic_v5_finalize_ppi_state() Sascha Bischoff
@ 2026-08-07 11:28 ` Sascha Bischoff
2026-08-07 11:28 ` [PATCH v5 32/49] KVM: arm64: gic-v5: Handle userspace accesses to IRS MMIO region Sascha Bischoff
` (17 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:28 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Now that KVM is able to run meaningful VMs with GICv5, it is important
to be able to save and restore the GICv5 state in order to support VM
migration.
Add functions to handle userspace reads and writes of the GICv5 EL1
system registers. Some architected register values are reconstructed
from KVM's internal representation rather than stored as raw register
state. For example, ICC_CR0_EL1.En and ICC_PCR_EL1.Priority are backed
by fields in the saved virtual VMCR, while the PPI Enable, Active, and
Pending registers are assembled from per-interrupt vgic_irq state.
Userspace writes are translated back into the corresponding internal
representation.
Wire the accessors into KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS for the GICv5
device, reusing the existing GICv3 CPU sysreg UAPI encoding. Require
the VGIC to be initialised before accessing the register state.
Allow userspace to read the state after a vCPU has run, but reject
writes once any vCPU has run. This ensures that userspace restores the
GICv5 CPU interface state before KVM finalises it for guest execution.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/Makefile | 3 +-
arch/arm64/kvm/sys_regs.c | 6 +-
arch/arm64/kvm/vgic-sys-reg-v5.c | 519 ++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic-kvm-device.c | 124 +++++-
arch/arm64/kvm/vgic/vgic.h | 7 +
5 files changed, 651 insertions(+), 8 deletions(-)
create mode 100644 arch/arm64/kvm/vgic-sys-reg-v5.c
diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile
index 92dda57c08766..7aaeeb84e788e 100644
--- a/arch/arm64/kvm/Makefile
+++ b/arch/arm64/kvm/Makefile
@@ -24,7 +24,8 @@ kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \
vgic/vgic-mmio.o vgic/vgic-mmio-v2.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-v5.o vgic/vgic-v5-tables.o vgic/vgic-irs-v5.o \
+ vgic-sys-reg-v5.o
kvm-$(CONFIG_HW_PERF_EVENTS) += pmu-emul.o pmu.o
kvm-$(CONFIG_ARM64_PTR_AUTH) += pauth.o
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index 5d5c579d45790..ba5918413f164 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -5827,7 +5827,7 @@ int kvm_finalize_sys_regs(struct kvm_vcpu *vcpu)
int __init kvm_sys_reg_table_init(void)
{
- const struct sys_reg_desc *gicv3_regs;
+ const struct sys_reg_desc *gicv3_regs, *gicv5_regs;
bool valid = true;
unsigned int i, sz;
int ret = 0;
@@ -5840,8 +5840,12 @@ int __init kvm_sys_reg_table_init(void)
valid &= check_sysreg_table(cp15_64_regs, ARRAY_SIZE(cp15_64_regs), false);
valid &= check_sysreg_table(sys_insn_descs, ARRAY_SIZE(sys_insn_descs), false);
+ /* The GICv3 system registers... */
gicv3_regs = vgic_v3_get_sysreg_table(&sz);
valid &= check_sysreg_table(gicv3_regs, sz, false);
+ /* ...and the GICv5 system registers. */
+ gicv5_regs = vgic_v5_get_sysreg_table(&sz);
+ valid &= check_sysreg_table(gicv5_regs, sz, false);
if (!valid)
return -EINVAL;
diff --git a/arch/arm64/kvm/vgic-sys-reg-v5.c b/arch/arm64/kvm/vgic-sys-reg-v5.c
new file mode 100644
index 0000000000000..6b255eacbf60d
--- /dev/null
+++ b/arch/arm64/kvm/vgic-sys-reg-v5.c
@@ -0,0 +1,519 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025, 2026 Arm Ltd.
+ */
+
+/*
+ * VGICv5 system registers handling functions for AArch64 mode
+ */
+
+#include <linux/irqchip/arm-gic-v5.h>
+
+#include <linux/kvm.h>
+#include <linux/kvm_host.h>
+#include <linux/wordpart.h>
+
+#include <asm/kvm_emulate.h>
+
+#include "vgic/vgic.h"
+#include "sys_regs.h"
+
+#define ICC_PPI_PRIORITYR_PRIORITY_MASK REPEAT_BYTE(0x1f)
+
+static int set_gic_apr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
+ u64 val)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+
+ /* The upper 32 bits are RES0 */
+ cpu_if->vgic_apr = val & ~ICC_APR_EL1_RES0;
+
+ return 0;
+}
+
+static int get_gic_apr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
+ u64 *val)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+
+ *val = cpu_if->vgic_apr;
+
+ return 0;
+}
+
+static int set_gic_cr0(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
+ u64 val)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+
+ /*
+ * We only support setting the ICC_CR0_EL1.En bit, which is actually
+ * stored in the VMCR.
+ */
+ FIELD_MODIFY(FEAT_GCIE_ICH_VMCR_EL2_EN, &cpu_if->vgic_vmcr,
+ FIELD_GET(ICC_CR0_EL1_EN, val));
+
+ return 0;
+}
+
+static int get_gic_cr0(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
+ u64 *val)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+
+ /*
+ * PID only applies if EL3 is present. Same applies to IPPT. Hence,
+ * those fields are always presented as 0.
+ *
+ * We always present the link as connected and idle:
+ * (LINK = 1, LINK_IDLE = 1).
+ */
+ *val = FIELD_PREP(ICC_CR0_EL1_EN,
+ FIELD_GET(FEAT_GCIE_ICH_VMCR_EL2_EN, cpu_if->vgic_vmcr));
+ *val |= ICC_CR0_EL1_LINK_MASK;
+ *val |= ICC_CR0_EL1_LINK_IDLE_MASK;
+
+ return 0;
+}
+
+static int set_gic_pcr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
+ u64 val)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+
+ /* Set the VPMR field in the VMCR */
+ FIELD_MODIFY(FEAT_GCIE_ICH_VMCR_EL2_VPMR, &cpu_if->vgic_vmcr,
+ FIELD_GET(ICC_PCR_EL1_PRIORITY, val));
+
+ return 0;
+}
+
+static int get_gic_pcr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
+ u64 *val)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+
+ *val = FIELD_PREP(ICC_PCR_EL1_PRIORITY,
+ FIELD_GET(FEAT_GCIE_ICH_VMCR_EL2_VPMR, cpu_if->vgic_vmcr));
+
+ return 0;
+}
+
+static int set_gic_icsr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
+ u64 val)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+
+ cpu_if->vgic_icsr = val & ~ICC_ICSR_EL1_RES0;
+
+ return 0;
+}
+
+static int get_gic_icsr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
+ u64 *val)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+
+ *val = cpu_if->vgic_icsr;
+
+ return 0;
+}
+
+/*
+ * Helper macro to iterate over a range of PPIs and execute some code (to either
+ * extract or set the vgic_irq state). This is used when `get`-ing the PPI
+ * ENABLER, ACTIVER, PENDR and when setting the PRIORITYR state.
+ *
+ * vcpu: Pointer to struct kvm_vcpu (to which these PPIs belong)
+ * r: The register index. 0 or 1 for all except PRIORITYR (which is 0-15)
+ * nr: The number of PPIs iterated over. 64 for all but PRIORITYR (which is 8)
+ * code: The code snippet to execute for each vgic_irq
+ */
+#define for_ppi_state(vcpu, r, nr, code) \
+ do { \
+ struct kvm_vcpu *__vcpu = (vcpu); \
+ int __r = (r); \
+ int __nr = (nr); \
+ \
+ for (int i = 0; i < __nr; i++) { \
+ u32 id = vgic_v5_make_ppi(__r * __nr + i); \
+ struct vgic_irq *irq; \
+ \
+ irq = vgic_get_vcpu_irq(__vcpu, id); \
+ scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) { \
+ code; \
+ } \
+ vgic_put_irq(__vcpu->kvm, irq); \
+ } \
+ } while (0)
+
+static int set_gic_ppi_enabler(struct kvm_vcpu *vcpu,
+ const struct sys_reg_desc *r, u64 val)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+ int i, start, end, reg = r->Op2 % 2;
+
+ /*
+ * If we're only handling architected PPIs and the guest writes to the
+ * enable for the non-architected PPIs, we just return as there's
+ * nothing to do at all. We don't even allocate the storage for them in
+ * this case.
+ */
+ if (VGIC_V5_NR_PRIVATE_IRQS == 64 && reg == 1)
+ return 0;
+
+ /*
+ * Merge the raw guest write into our bitmap at an offset of either 0 or
+ * 64.
+ *
+ * Note that there is *NO* masking applied - the enable state is written
+ * unfiltered. The assumption is that userspace uses this interface to
+ * set initial state before the guest runs, and then the exposed PPI
+ * mask is applied later, when vgic_v5_finalize_ppi_state() runs on
+ * first entry to each vCPU. If userspace chooses to set the enabler
+ * state later, it is fully capable of breaking the illusion we provided
+ * to the guest by exposing register state (and PPIs) to the guest that
+ * were not initially exposed. Good luck!
+ */
+ bitmap_write(cpu_if->vgic_ppi_enabler, val, 64 * reg, 64);
+
+ /*
+ * Sync the change in enable states to the vgic_irqs for the written
+ * register slice.
+ */
+ start = reg * 64;
+ end = min(start + 64, VGIC_V5_NR_PRIVATE_IRQS);
+ for (i = start; i < end; i++) {
+ u32 intid = vgic_v5_make_ppi(i);
+ struct vgic_irq *irq;
+
+ irq = vgic_get_vcpu_irq(vcpu, intid);
+
+ scoped_guard(raw_spinlock_irqsave, &irq->irq_lock)
+ irq->enabled = test_bit(i, cpu_if->vgic_ppi_enabler);
+
+ vgic_put_irq(vcpu->kvm, irq);
+ }
+
+ return 0;
+}
+
+static int get_gic_ppi_enabler(struct kvm_vcpu *vcpu,
+ const struct sys_reg_desc *r, u64 *val)
+{
+ unsigned long enabler = 0;
+ int reg = r->Op2 % 2;
+
+ /* If we only support architected PPIs, return 0 */
+ if (VGIC_V5_NR_PRIVATE_IRQS == 64 && reg == 1) {
+ *val = 0;
+ return 0;
+ }
+
+ /* Iterate over each struct vgic_irq to build the ENABLER value. */
+ for_ppi_state(vcpu, reg, 64, __assign_bit(i % 64, &enabler, irq->enabled));
+
+ *val = enabler;
+
+ return 0;
+}
+
+static int set_gic_ppi_activer(struct kvm_vcpu *vcpu,
+ const struct sys_reg_desc *r, u64 val)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+ int i, start, end, reg = r->Op2 % 2;
+
+ if (VGIC_V5_NR_PRIVATE_IRQS == 64 && reg == 1)
+ return 0;
+
+ /*
+ * Store the raw guest write. The exposed PPI mask is applied later,
+ * when vgic_v5_finalize_ppi_state() runs on first entry to each
+ * vCPU. See comment on set_gic_ppi_enabler() for details.
+ */
+ bitmap_write(cpu_if->vgic_ppi_activer, val, 64 * reg, 64);
+
+ start = reg * 64;
+ end = min(start + 64, VGIC_V5_NR_PRIVATE_IRQS);
+ for (i = start; i < end; i++) {
+ u32 intid = vgic_v5_make_ppi(i);
+ struct vgic_irq *irq;
+
+ irq = vgic_get_vcpu_irq(vcpu, intid);
+
+ scoped_guard(raw_spinlock_irqsave, &irq->irq_lock)
+ irq->active = test_bit(i, cpu_if->vgic_ppi_activer);
+
+ vgic_put_irq(vcpu->kvm, irq);
+ }
+
+ return 0;
+}
+
+static int get_gic_ppi_activer(struct kvm_vcpu *vcpu,
+ const struct sys_reg_desc *r, u64 *val)
+{
+ unsigned long activer = 0;
+ int reg = r->Op2 % 2;
+
+ /* If we only support architected PPIs, return 0 */
+ if (VGIC_V5_NR_PRIVATE_IRQS == 64 && reg == 1) {
+ *val = 0;
+ return 0;
+ }
+
+ /* Iterate over each struct vgic_irq to build the ACTIVER value. */
+ for_ppi_state(vcpu, reg, 64, __assign_bit(i % 64, &activer, irq->active));
+
+ *val = activer;
+
+ return 0;
+}
+
+static int set_gic_ppi_pendr(struct kvm_vcpu *vcpu,
+ const struct sys_reg_desc *r, u64 val)
+{
+ int i, start, end, reg = r->Op2 % 2;
+
+ /* If we only support architected PPIs, return */
+ if (VGIC_V5_NR_PRIVATE_IRQS == 64 && reg == 1)
+ return 0;
+
+ /*
+ * Update each struct vgic_irq with the pending state, treating Level
+ * and Edge interrupts differently. The exposed PPI mask is applied
+ * later, when vgic_v5_finalize_ppi_state() runs on first entry to each
+ * vCPU. See comment on set_gic_ppi_enabler() for details.
+ */
+ start = reg * 64;
+ end = min(start + 64, VGIC_V5_NR_PRIVATE_IRQS);
+ for (i = start; i < end; i++) {
+ u32 intid = vgic_v5_make_ppi(i);
+ struct vgic_irq *irq;
+
+ irq = vgic_get_vcpu_irq(vcpu, intid);
+
+ scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) {
+ bool level = !!(val & BIT_ULL(i - start));
+
+ if (irq->config == VGIC_CONFIG_LEVEL)
+ irq->line_level = level;
+ else
+ irq->pending_latch = level;
+ }
+
+ vgic_put_irq(vcpu->kvm, irq);
+ }
+
+ /*
+ * The pending state is generated from the vgic_irqs on each guest
+ * entry. Therefore, we don't store the raw value written anywhere in
+ * the case of userspace PPI_PENDRx_EL1 writes.
+ */
+
+ return 0;
+}
+
+static int get_gic_ppi_pendr(struct kvm_vcpu *vcpu,
+ const struct sys_reg_desc *r, u64 *val)
+{
+ unsigned long pendr = 0;
+ int reg = r->Op2 % 2;
+
+ /* If we only support architected PPIs, return 0 */
+ if (VGIC_V5_NR_PRIVATE_IRQS == 64 && reg == 1) {
+ *val = 0;
+ return 0;
+ }
+
+ /* Iterate over each struct vgic_irq to build the PENDR value. */
+ for_ppi_state(vcpu, reg, 64, {
+ if (irq_is_pending(irq))
+ __assign_bit(i % 64, &pendr, 1);
+ });
+
+ *val = pendr;
+
+ return 0;
+}
+
+static int set_gic_ppi_priorityr(struct kvm_vcpu *vcpu,
+ const struct sys_reg_desc *r, u64 val)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+ int reg = ((r->CRm & 0x1) << 3) + r->Op2;
+
+ /* If we only support architected PPIs, return */
+ if (VGIC_V5_NR_PRIVATE_IRQS == 64 && reg > 7)
+ return 0;
+
+ val &= ICC_PPI_PRIORITYR_PRIORITY_MASK;
+
+ /*
+ * Although priorities are not regularly synced back to the vgic_irq
+ * state, they are explicitly synced back here. This is to ensure that
+ * any pending PPIs are evaluated correctly when first running the guest
+ * after setting the state.
+ */
+ for_ppi_state(vcpu, reg, 8,
+ irq->priority = (u8)(val >> (8 * i));
+ );
+
+ /*
+ * Update the state that will be written to the ICH_PPI_PRIORITYRx_EL2
+ * on next guest entry.
+ */
+ cpu_if->vgic_ppi_priorityr[reg] = val;
+
+ return 0;
+}
+
+static int get_gic_ppi_priorityr(struct kvm_vcpu *vcpu,
+ const struct sys_reg_desc *r, u64 *val)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+ int reg = ((r->CRm & 0x1) << 3) + r->Op2;
+
+ /* If we only support architected PPIs, return 0 */
+ if (VGIC_V5_NR_PRIVATE_IRQS == 64 && reg > 7) {
+ *val = 0;
+ return 0;
+ }
+
+ /*
+ * The priorities are only synced back to the vgic_irq state when the
+ * vcpu is entering WFI (KVM only needs to know the priorities when
+ * evaluating if there are pending PPI interrupts for a vcpu). The raw
+ * register ICH_PPI_PRIORITYRx_EL1 state is simply saved and restored
+ * blindly. This state is just returned as it contains the most recent
+ * priorities written by the guest.
+ */
+ *val = cpu_if->vgic_ppi_priorityr[reg];
+
+ return 0;
+}
+
+/*
+ * The following registers are NOT supported:
+ *
+ * - ICC_HAPR_EL1
+ * The value of this is directly generated by the GICv5 hardware based on
+ * the ICC_APR_EL1 when the guest is running.
+ * - ICC_IAFFIDR_EL1
+ * The IAFFID for a GICv5 VPE is the same as the VPE ID, which is the index
+ * into the in-memory VPE Table. This is not configurable, and instead we
+ * rely on userspace recreating the VPEs in the same order prior to
+ * restoring guest state.
+ * - ICC_PPI_CACTIVER<n>_EL1
+ * Only raw state writes are supported via the S(et) variant.
+ * - ICC_PPI_CPENDR<n>_EL1
+ * Only raw state writes are supported via the S(et) variant.
+ */
+static const struct sys_reg_desc gic_v5_icc_reg_descs[] = {
+ { SYS_DESC(SYS_ICC_ICSR_EL1),
+ .set_user = set_gic_icsr, .get_user = get_gic_icsr, },
+ { SYS_DESC(SYS_ICC_PPI_ENABLER0_EL1),
+ .set_user = set_gic_ppi_enabler, .get_user = get_gic_ppi_enabler, },
+ { SYS_DESC(SYS_ICC_PPI_ENABLER1_EL1),
+ .set_user = set_gic_ppi_enabler, .get_user = get_gic_ppi_enabler, },
+ /*
+ * Only ICC_SACTIVER<n>_EL1 is exposed to the guest. This is treated as
+ * a *RAW* write of register state for writes.
+ */
+ { SYS_DESC(SYS_ICC_PPI_SACTIVER0_EL1),
+ .set_user = set_gic_ppi_activer, .get_user = get_gic_ppi_activer, },
+ { SYS_DESC(SYS_ICC_PPI_SACTIVER1_EL1),
+ .set_user = set_gic_ppi_activer, .get_user = get_gic_ppi_activer, },
+ /*
+ * Only ICC_SPENDR<n>_EL1 is exposed to the guest. This is treated as
+ * a *RAW* write of register state for writes.
+ */
+ { SYS_DESC(SYS_ICC_PPI_SPENDR0_EL1),
+ .set_user = set_gic_ppi_pendr, .get_user = get_gic_ppi_pendr, },
+ { SYS_DESC(SYS_ICC_PPI_SPENDR1_EL1),
+ .set_user = set_gic_ppi_pendr, .get_user = get_gic_ppi_pendr, },
+ { SYS_DESC(SYS_ICC_PPI_PRIORITYR0_EL1),
+ .set_user = set_gic_ppi_priorityr, .get_user = get_gic_ppi_priorityr, },
+ { SYS_DESC(SYS_ICC_PPI_PRIORITYR1_EL1),
+ .set_user = set_gic_ppi_priorityr, .get_user = get_gic_ppi_priorityr, },
+ { SYS_DESC(SYS_ICC_PPI_PRIORITYR2_EL1),
+ .set_user = set_gic_ppi_priorityr, .get_user = get_gic_ppi_priorityr, },
+ { SYS_DESC(SYS_ICC_PPI_PRIORITYR3_EL1),
+ .set_user = set_gic_ppi_priorityr, .get_user = get_gic_ppi_priorityr, },
+ { SYS_DESC(SYS_ICC_PPI_PRIORITYR4_EL1),
+ .set_user = set_gic_ppi_priorityr, .get_user = get_gic_ppi_priorityr, },
+ { SYS_DESC(SYS_ICC_PPI_PRIORITYR5_EL1),
+ .set_user = set_gic_ppi_priorityr, .get_user = get_gic_ppi_priorityr, },
+ { SYS_DESC(SYS_ICC_PPI_PRIORITYR6_EL1),
+ .set_user = set_gic_ppi_priorityr, .get_user = get_gic_ppi_priorityr, },
+ { SYS_DESC(SYS_ICC_PPI_PRIORITYR7_EL1),
+ .set_user = set_gic_ppi_priorityr, .get_user = get_gic_ppi_priorityr, },
+ { SYS_DESC(SYS_ICC_PPI_PRIORITYR8_EL1),
+ .set_user = set_gic_ppi_priorityr, .get_user = get_gic_ppi_priorityr, },
+ { SYS_DESC(SYS_ICC_PPI_PRIORITYR9_EL1),
+ .set_user = set_gic_ppi_priorityr, .get_user = get_gic_ppi_priorityr, },
+ { SYS_DESC(SYS_ICC_PPI_PRIORITYR10_EL1),
+ .set_user = set_gic_ppi_priorityr, .get_user = get_gic_ppi_priorityr, },
+ { SYS_DESC(SYS_ICC_PPI_PRIORITYR11_EL1),
+ .set_user = set_gic_ppi_priorityr, .get_user = get_gic_ppi_priorityr, },
+ { SYS_DESC(SYS_ICC_PPI_PRIORITYR12_EL1),
+ .set_user = set_gic_ppi_priorityr, .get_user = get_gic_ppi_priorityr, },
+ { SYS_DESC(SYS_ICC_PPI_PRIORITYR13_EL1),
+ .set_user = set_gic_ppi_priorityr, .get_user = get_gic_ppi_priorityr, },
+ { SYS_DESC(SYS_ICC_PPI_PRIORITYR14_EL1),
+ .set_user = set_gic_ppi_priorityr, .get_user = get_gic_ppi_priorityr, },
+ { SYS_DESC(SYS_ICC_PPI_PRIORITYR15_EL1),
+ .set_user = set_gic_ppi_priorityr, .get_user = get_gic_ppi_priorityr, },
+ { SYS_DESC(SYS_ICC_APR_EL1),
+ .set_user = set_gic_apr, .get_user = get_gic_apr, },
+ { SYS_DESC(SYS_ICC_CR0_EL1),
+ .set_user = set_gic_cr0, .get_user = get_gic_cr0, },
+ { SYS_DESC(SYS_ICC_PCR_EL1),
+ .set_user = set_gic_pcr, .get_user = get_gic_pcr, },
+};
+
+const struct sys_reg_desc *vgic_v5_get_sysreg_table(unsigned int *sz)
+{
+ *sz = ARRAY_SIZE(gic_v5_icc_reg_descs);
+ return gic_v5_icc_reg_descs;
+}
+
+static u64 attr_to_id(u64 attr)
+{
+ return ARM64_SYS_REG(FIELD_GET(KVM_REG_ARM_VGIC_SYSREG_OP0_MASK, attr),
+ FIELD_GET(KVM_REG_ARM_VGIC_SYSREG_OP1_MASK, attr),
+ FIELD_GET(KVM_REG_ARM_VGIC_SYSREG_CRN_MASK, attr),
+ FIELD_GET(KVM_REG_ARM_VGIC_SYSREG_CRM_MASK, attr),
+ FIELD_GET(KVM_REG_ARM_VGIC_SYSREG_OP2_MASK, attr));
+}
+
+int vgic_v5_has_cpu_sysregs_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
+{
+ const struct sys_reg_desc *r;
+
+ r = get_reg_by_id(attr_to_id(attr->attr), gic_v5_icc_reg_descs,
+ ARRAY_SIZE(gic_v5_icc_reg_descs));
+
+ if (r && !sysreg_hidden(vcpu, r))
+ return 0;
+
+ return -ENXIO;
+}
+
+int vgic_v5_cpu_sysregs_uaccess(struct kvm_vcpu *vcpu,
+ struct kvm_device_attr *attr,
+ bool is_write)
+{
+ struct kvm_one_reg reg = {
+ .id = attr_to_id(attr->attr),
+ .addr = attr->addr,
+ };
+
+ if (is_write)
+ return kvm_sys_reg_set_user(vcpu, ®, gic_v5_icc_reg_descs,
+ ARRAY_SIZE(gic_v5_icc_reg_descs));
+ else
+ return kvm_sys_reg_get_user(vcpu, ®, gic_v5_icc_reg_descs,
+ ARRAY_SIZE(gic_v5_icc_reg_descs));
+}
diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c
index 8c2c6886af4e3..599ab4efc1db5 100644
--- a/arch/arm64/kvm/vgic/vgic-kvm-device.c
+++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c
@@ -542,7 +542,7 @@ int vgic_v3_parse_attr(struct kvm_device *dev, struct kvm_device_attr *attr,
* Allow access to certain ID-like registers prior to VGIC initialization,
* thereby allowing the VMM to provision the features / sizing of the VGIC.
*/
-static bool reg_allowed_pre_init(struct kvm_device_attr *attr)
+static bool v3_reg_allowed_pre_init(struct kvm_device_attr *attr)
{
if (attr->group != KVM_DEV_ARM_VGIC_GRP_DIST_REGS)
return false;
@@ -605,7 +605,7 @@ static int vgic_v3_attr_regs_access(struct kvm_device *dev,
mutex_lock(&dev->kvm->arch.config_lock);
- if (!(vgic_initialized(dev->kvm) || reg_allowed_pre_init(attr))) {
+ if (!(vgic_initialized(dev->kvm) || v3_reg_allowed_pre_init(attr))) {
ret = -EBUSY;
goto out;
}
@@ -773,6 +773,110 @@ static int vgic_v5_get_userspace_ppis(struct kvm_device *dev,
return ret;
}
+int vgic_v5_parse_attr(struct kvm_device *dev, struct kvm_device_attr *attr,
+ struct vgic_reg_attr *reg_attr)
+{
+ unsigned long vgic_mpidr, mpidr_reg;
+
+ switch (attr->group) {
+ case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
+ vgic_mpidr = (attr->attr & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) >>
+ KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT;
+
+ mpidr_reg = VGIC_TO_MPIDR(vgic_mpidr);
+ reg_attr->vcpu = kvm_mpidr_to_vcpu(dev->kvm, mpidr_reg);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!reg_attr->vcpu)
+ return -EINVAL;
+
+ reg_attr->addr = attr->attr & KVM_DEV_ARM_VGIC_OFFSET_MASK;
+
+ return 0;
+}
+
+/*
+ * Some registers can potentially be read before the core GIC & IRS has been
+ * initialised. Right now, everything is required to be post-init.
+ */
+static bool v5_reg_allowed_pre_init(struct kvm_device_attr *attr)
+{
+ return false;
+}
+
+static bool vgic_v5_vm_has_run_once(struct kvm *kvm)
+{
+ struct kvm_vcpu *vcpu;
+ unsigned long i;
+
+ kvm_for_each_vcpu(i, vcpu, kvm) {
+ if (vcpu_has_run_once(vcpu))
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * vgic_v5_attr_regs_access - allows user space to access VGIC v5 state
+ *
+ * @dev: kvm device handle
+ * @attr: kvm device attribute
+ * @is_write: true if userspace is writing a register
+ */
+static int vgic_v5_attr_regs_access(struct kvm_device *dev,
+ struct kvm_device_attr *attr,
+ bool is_write)
+{
+ struct vgic_reg_attr reg_attr;
+ struct kvm_vcpu *vcpu;
+ int ret;
+
+ ret = vgic_v5_parse_attr(dev, attr, ®_attr);
+ if (ret)
+ return ret;
+
+ vcpu = reg_attr.vcpu;
+
+ 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 (!(vgic_initialized(dev->kvm) || v5_reg_allowed_pre_init(attr))) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ if (is_write && vgic_v5_vm_has_run_once(dev->kvm)) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ switch (attr->group) {
+ case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
+ ret = vgic_v5_cpu_sysregs_uaccess(vcpu, attr, is_write);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+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_set_attr(struct kvm_device *dev,
struct kvm_device_attr *attr)
{
@@ -780,7 +884,7 @@ static int vgic_v5_set_attr(struct kvm_device *dev,
case KVM_DEV_ARM_VGIC_GRP_ADDR:
break;
case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
- return -ENXIO;
+ return vgic_v5_attr_regs_access(dev, attr, true);
case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
break;
case KVM_DEV_ARM_VGIC_GRP_CTRL:
@@ -806,7 +910,7 @@ static int vgic_v5_get_attr(struct kvm_device *dev,
case KVM_DEV_ARM_VGIC_GRP_ADDR:
break;
case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
- return -ENXIO;
+ return vgic_v5_attr_regs_access(dev, attr, false);
case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
break;
case KVM_DEV_ARM_VGIC_GRP_CTRL:
@@ -836,8 +940,16 @@ static int vgic_v5_has_attr(struct kvm_device *dev,
return 0;
}
return -ENXIO;
- case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
- return -ENXIO;
+ case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS: {
+ struct vgic_reg_attr reg_attr;
+ int ret;
+
+ ret = vgic_v5_parse_attr(dev, attr, ®_attr);
+ if (ret)
+ return ret;
+
+ return vgic_v5_has_cpu_sysregs_attr(reg_attr.vcpu, attr);
+ }
case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
return 0;
case KVM_DEV_ARM_VGIC_GRP_CTRL:
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 107737c77e4d5..4f9ded224d613 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -252,6 +252,8 @@ struct ap_list_summary {
#define irqs_active_outside_lrs(s) \
((s)->nr_act && irqs_outside_lrs(s))
+int vgic_v5_parse_attr(struct kvm_device *dev, struct kvm_device_attr *attr,
+ struct vgic_reg_attr *reg_attr);
int vgic_v3_parse_attr(struct kvm_device *dev, struct kvm_device_attr *attr,
struct vgic_reg_attr *reg_attr);
int vgic_v2_parse_attr(struct kvm_device *dev, struct kvm_device_attr *attr,
@@ -383,6 +385,11 @@ void vgic_v5_restore_state(struct kvm_vcpu *vcpu);
void vgic_v5_save_state(struct kvm_vcpu *vcpu);
int vgic_v5_register_irs_iodev(struct kvm *kvm, gpa_t irs_base_address);
+int vgic_v5_cpu_sysregs_uaccess(struct kvm_vcpu *vcpu,
+ struct kvm_device_attr *attr, bool is_write);
+int vgic_v5_has_cpu_sysregs_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
+const struct sys_reg_desc *vgic_v5_get_sysreg_table(unsigned int *sz);
+
#define for_each_visible_v5_ppi(__i, __k) \
for_each_set_bit(__i, (__k)->arch.vgic.gicv5_vm.vgic_ppi_mask, VGIC_V5_NR_PRIVATE_IRQS)
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 32/49] KVM: arm64: gic-v5: Handle userspace accesses to IRS MMIO region
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (30 preceding siblings ...)
2026-08-07 11:28 ` [PATCH v5 31/49] KVM: arm64: gic-v5: Add GICv5 EL1 sysreg userspace accessors Sascha Bischoff
@ 2026-08-07 11:28 ` Sascha Bischoff
2026-08-07 11:29 ` [PATCH v5 33/49] KVM: arm64: gic-v5: Add CoreSight MMIO regs to IRS Sascha Bischoff
` (16 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:28 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
As part of saving and restoring the state of a GICv5-based system,
userspace must save and restore the IRS MMIO registers. These include
important information such as the guest IST configuration, and KVM
must present consistent state to the guest after migration.
Introduce KVM_DEV_ARM_VGIC_GRP_IRS_REGS and provide accessors to read
and write the virtual IRS register state. This is modelled on the
GICv3 ITS register interface, as the migration requirements are
broadly the same.
Reuse the guest MMIO handlers where userspace and guest accesses have
the same semantics. Add userspace-specific handling where restoring a
register image must not trigger the operation associated with a guest
MMIO write.
Validate restored ID register fields against the capabilities KVM and
the host can support. Restore the emulated IST configuration without
allocating or freeing a host IST, and report operation status
registers as idle. Accept writes to IRS_SPI_CFGR, IRS_IIDR, and
IRS_AIDR without changing their state, allowing userspace to replay
the values it previously read.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/include/uapi/asm/kvm.h | 1 +
arch/arm64/kvm/vgic/vgic-irs-v5.c | 447 ++++++++++++++++++++----
arch/arm64/kvm/vgic/vgic-kvm-device.c | 55 ++-
arch/arm64/kvm/vgic/vgic.h | 4 +
tools/arch/arm64/include/uapi/asm/kvm.h | 1 +
5 files changed, 423 insertions(+), 85 deletions(-)
diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
index d1b2ca317f586..710a0d267347d 100644
--- a/arch/arm64/include/uapi/asm/kvm.h
+++ b/arch/arm64/include/uapi/asm/kvm.h
@@ -422,6 +422,7 @@ enum {
#define KVM_DEV_ARM_VGIC_GRP_LEVEL_INFO 7
#define KVM_DEV_ARM_VGIC_GRP_ITS_REGS 8
#define KVM_DEV_ARM_VGIC_GRP_MAINT_IRQ 9
+#define KVM_DEV_ARM_VGIC_GRP_IRS_REGS 10
#define KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_SHIFT 10
#define KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_MASK \
(0x3fffffULL << KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_SHIFT)
diff --git a/arch/arm64/kvm/vgic/vgic-irs-v5.c b/arch/arm64/kvm/vgic/vgic-irs-v5.c
index 095046c7959fa..212066b8d7ed5 100644
--- a/arch/arm64/kvm/vgic/vgic-irs-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-irs-v5.c
@@ -400,12 +400,70 @@ static unsigned long vgic_v5_mmio_read_irs_ist(struct kvm_vcpu *vcpu,
return value;
}
+static bool vgic_v5_ist_baser_matches(const struct vgic_v5_irs *irs,
+ unsigned long val)
+{
+ u64 addr = FIELD_GET(GICV5_IRS_IST_BASER_ADDR_MASK, val)
+ << GICV5_IRS_IST_BASER_ADDR_SHIFT;
+ bool valid = !!(val & GICV5_IRS_IST_BASER_VALID);
+
+ return irs->ist_baser.addr == addr && irs->ist_baser.valid == valid;
+}
+
+static void vgic_v5_update_irs_ist_baser(struct vgic_v5_irs *irs,
+ unsigned long val)
+{
+ irs->ist_baser.valid = !!(val & GICV5_IRS_IST_BASER_VALID);
+ irs->ist_baser.addr = FIELD_GET(GICV5_IRS_IST_BASER_ADDR_MASK, val)
+ << GICV5_IRS_IST_BASER_ADDR_SHIFT;
+}
+
+static int vgic_v5_write_irs_ist_baser(struct kvm_vcpu *vcpu, unsigned long val)
+{
+ struct vgic_v5_irs *irs = vgic_v5_get_irs(vcpu);
+ enum gicv5_vcpu_cmd cmd = LPI_VIST_MAKE_INVALID;
+ bool valid = !!(val & GICV5_IRS_IST_BASER_VALID);
+ int rc;
+
+ /* The address cannot be changed while the IST is valid. */
+ if (irs->ist_baser.valid && valid)
+ return 0;
+
+ /* Valid -> Invalid */
+ if (irs->ist_baser.valid && !valid) {
+ /* Make the LPI IST invalid and then ... */
+ rc = irq_set_vcpu_affinity(vgic_v5_vpe_db(vcpu), &cmd);
+ if (rc)
+ return rc;
+
+ vgic_v5_update_irs_ist_baser(irs, val);
+
+ /*
+ * ... reflect that in the emulated BASER before freeing the
+ * host IST. If the free fails, the guest-visible valid bit
+ * still matches the hardware state.
+ */
+ return vgic_v5_lpi_ist_free(vcpu->kvm);
+ } else if (!irs->ist_baser.valid && valid) { /* Invalid -> Valid */
+ if (!vgic_v5_ist_cfgr_valid(irs))
+ return -EINVAL;
+
+ rc = vgic_v5_lpi_ist_alloc(vcpu->kvm, irs->ist_cfgr.lpi_id_bits);
+ if (rc)
+ return rc;
+ }
+
+ /* Now that we've handled the edges, update the valid bit and addr */
+ vgic_v5_update_irs_ist_baser(irs, val);
+
+ return 0;
+}
+
static void vgic_v5_mmio_write_irs_ist(struct kvm_vcpu *vcpu, gpa_t addr,
unsigned int len, unsigned long val)
{
struct vgic_v5_irs *irs = vgic_v5_get_irs(vcpu);
const size_t offset = addr & (SZ_64K - 1);
- enum gicv5_vcpu_cmd cmd = LPI_VIST_MAKE_INVALID;
switch (offset) {
case GICV5_IRS_IST_CFGR:
@@ -420,85 +478,222 @@ static void vgic_v5_mmio_write_irs_ist(struct kvm_vcpu *vcpu, gpa_t addr,
}
return;
case GICV5_IRS_IST_BASER: {
- bool valid = !!(val & GICV5_IRS_IST_BASER_VALID);
+ scoped_guard(mutex, &vcpu->kvm->arch.config_lock)
+ vgic_v5_write_irs_ist_baser(vcpu, val);
+ return;
+ }
+ default:
+ return;
+ }
+}
- guard(mutex)(&vcpu->kvm->arch.config_lock);
+static unsigned long vgic_v5_mmio_uaccess_read_irs_status(struct kvm_vcpu *vcpu,
+ gpa_t addr,
+ unsigned int len)
+{
+ const size_t offset = addr & (SZ_64K - 1);
- /* The address cannot be changed while the IST is valid. */
- if (irs->ist_baser.valid && valid)
- return;
+ switch (offset) {
+ case GICV5_IRS_SYNC_STATUSR:
+ return GICV5_IRS_SYNC_STATUSR_IDLE;
+ case GICV5_IRS_SPI_STATUSR:
+ return GICV5_IRS_SPI_STATUSR_IDLE;
+ case GICV5_IRS_PE_STATUSR:
+ return GICV5_IRS_PE_STATUSR_IDLE;
+ case GICV5_IRS_IST_STATUSR:
+ return GICV5_IRS_IST_STATUSR_IDLE;
+ default:
+ return 0;
+ }
+}
- /* Valid -> Invalid */
- if (irs->ist_baser.valid && !valid) {
- /* Make the LPI IST invalid and then ... */
- if (irq_set_vcpu_affinity(vgic_v5_vpe_db(vcpu), &cmd))
- break;
+static int vgic_v5_mmio_uaccess_write_irs(struct kvm_vcpu *vcpu, gpa_t addr,
+ unsigned int len, unsigned long val)
+{
+ struct vgic_dist *vgic = &vcpu->kvm->arch.vgic;
+ struct vgic_v5_irs *irs_data = vgic->vgic_v5_irs_data;
+ size_t offset = addr & (SZ_64K - 1);
- /*
- * ... free the host IST if we successfully marked the
- * IST as invalid. Frankly, if we failed to make the
- * guest's IST as invalid, we're cooked because it means
- * that the IRS may still be using the memory that we
- * want to free. Hence, we leave it allocated and skip
- * the clearing of valid bit in the baser.
- */
- vgic_v5_lpi_ist_free(vcpu->kvm);
- } else if (!irs->ist_baser.valid && valid) { /* Invalid -> Valid */
- if (!vgic_v5_ist_cfgr_valid(irs))
- break;
+ /*
+ * The following registers are ONLY settable via uaccesses. The guest
+ * cannot write them!
+ */
- if (vgic_v5_lpi_ist_alloc(vcpu->kvm, irs->ist_cfgr.lpi_id_bits))
- break;
- }
+ switch (offset) {
+ case GICV5_IRS_IDR0:
+ if (FIELD_GET(GICV5_IRS_IDR0_INT_DOM, val) !=
+ GICV5_IRS_IDR0_INT_DOM_NON_SECURE)
+ return -EINVAL;
+
+ if ((val & GICV5_IRS_IDR0_VIRT) ||
+ (val & GICV5_IRS_IDR0_ONE_N) ||
+ (val & GICV5_IRS_IDR0_VIRT_ONE_N) ||
+ (val & GICV5_IRS_IDR0_SETLPI) ||
+ (val & GICV5_IRS_IDR0_MEC) ||
+ (val & GICV5_IRS_IDR0_MPAM) ||
+ (val & GICV5_IRS_IDR0_SWE))
+ return -EINVAL;
+
+ irs_data->idr0.domain = FIELD_GET(GICV5_IRS_IDR0_INT_DOM, val);
+ irs_data->idr0.pa_range = FIELD_GET(GICV5_IRS_IDR0_PA_RANGE, val);
+ irs_data->idr0.virt = !!(val & GICV5_IRS_IDR0_VIRT);
+ irs_data->idr0.setlpi = !!(val & GICV5_IRS_IDR0_SETLPI);
+ irs_data->idr0.mec = !!(val & GICV5_IRS_IDR0_MEC);
+ irs_data->idr0.mpam = !!(val & GICV5_IRS_IDR0_MPAM);
+ irs_data->idr0.swe = !!(val & GICV5_IRS_IDR0_SWE);
+ irs_data->idr0.irs_id = FIELD_GET(GICV5_IRS_IDR0_IRSID, val);
+ break;
+ case GICV5_IRS_IDR1: {
+ unsigned int iaffid_bits, priority_bits;
+ u8 vpe_id_bits;
- /* Now that we've handled the edges, update the valid bit and addr */
- irs->ist_baser.valid = !!(val & GICV5_IRS_IST_BASER_VALID);
- irs->ist_baser.addr = FIELD_GET(GICV5_IRS_IST_BASER_ADDR_MASK, val)
- << GICV5_IRS_IST_BASER_ADDR_SHIFT;
+ /* Ignore writes to PE_CNT as this is populated from num vcpus */
+ iaffid_bits = FIELD_GET(GICV5_IRS_IDR1_IAFFID_BITS, val);
+ priority_bits = FIELD_GET(GICV5_IRS_IDR1_PRIORITY_BITS, val);
- return;
+ /* IAFFID_BITS is derived from the VMTE and encoded as N - 1. */
+ vpe_id_bits = vgic_v5_vmte_vpe_id_bits(vcpu);
+ if (iaffid_bits != vpe_id_bits - 1)
+ return -EINVAL;
+
+ if (priority_bits > gicv5_global_data.irs_pri_bits - 1)
+ return -EINVAL;
+
+ irs_data->idr1.priority_bits = priority_bits;
+ break;
}
+ case GICV5_IRS_IDR2:
+ /* We always support LPIs */
+ if (!(val & GICV5_IRS_IDR2_LPI))
+ return -EINVAL;
+
+ /* We only support LPIs with linear, non-metadata guest ISTs */
+ if (val & GICV5_IRS_IDR2_IST_LEVELS)
+ return -EINVAL;
+
+ if ((val & GICV5_IRS_IDR2_ISTMD) ||
+ FIELD_GET(GICV5_IRS_IDR2_ISTMD_SZ, val))
+ return -EINVAL;
+
+ /* We can't present more bits than we have support for in HW */
+ if (FIELD_GET(GICV5_IRS_IDR2_ID_BITS, val) >
+ vgic_v5_irs_ist_id_bits(&irs_caps))
+ return -EINVAL;
+
+ /* Min LPI ID bits must be greater than or equal to the HW */
+ if (FIELD_GET(GICV5_IRS_IDR2_MIN_LPI_ID_BITS, val) <
+ vgic_v5_irs_min_lpi_id_bits(&irs_caps))
+ return -EINVAL;
+
+ if (FIELD_GET(GICV5_IRS_IDR2_MIN_LPI_ID_BITS, val) >
+ FIELD_GET(GICV5_IRS_IDR2_ID_BITS, val))
+ return -EINVAL;
+
+ irs_data->idr2.istmd_sz = FIELD_GET(GICV5_IRS_IDR2_ISTMD_SZ, val);
+ irs_data->idr2.istmd = !!(val & GICV5_IRS_IDR2_ISTMD);
+ irs_data->idr2.ist_l2sz = FIELD_GET(GICV5_IRS_IDR2_IST_L2SZ, val);
+ irs_data->idr2.ist_levels = !!(val & GICV5_IRS_IDR2_IST_LEVELS);
+ irs_data->idr2.min_lpi_id_bits = FIELD_GET(GICV5_IRS_IDR2_MIN_LPI_ID_BITS, val);
+ irs_data->idr2.id_bits = FIELD_GET(GICV5_IRS_IDR2_ID_BITS, val);
+ break;
+ case GICV5_IRS_IDR5:
+ if (FIELD_GET(GICV5_IRS_IDR5_SPI_RANGE, val) != irs_data->idr5.spi_range)
+ return -EINVAL;
+ break;
+ case GICV5_IRS_IDR6:
+ if (FIELD_GET(GICV5_IRS_IDR6_SPI_IRS_RANGE, val) != irs_data->idr6.spi_irs_range)
+ return -EINVAL;
+ break;
+ case GICV5_IRS_IDR7:
+ if (FIELD_GET(GICV5_IRS_IDR7_SPI_BASE, val) != irs_data->idr7.spi_base)
+ return -EINVAL;
+ break;
+ case GICV5_IRS_IST_BASER:
+ if (irs_data->ist_baser.valid &&
+ !vgic_v5_ist_baser_matches(irs_data, val))
+ return -EINVAL;
+
+ vgic_v5_update_irs_ist_baser(irs_data, val);
+ break;
+ case GICV5_IRS_IST_CFGR:
+ irs_data->ist_cfgr.lpi_id_bits = FIELD_GET(GICV5_IRS_IST_CFGR_LPI_ID_BITS, val);
+ irs_data->ist_cfgr.l2sz = FIELD_GET(GICV5_IRS_IST_CFGR_L2SZ, val);
+ irs_data->ist_cfgr.istsz = FIELD_GET(GICV5_IRS_IST_CFGR_ISTSZ, val);
+ irs_data->ist_cfgr.structure = !!(val & GICV5_IRS_IST_CFGR_STRUCTURE);
+ break;
+ case GICV5_IRS_CR0:
+ vgic->enabled = !!(val & GICV5_IRS_CR0_IRSEN);
+ break;
+ case GICV5_IRS_SPI_CFGR:
+ break;
+ case GICV5_IRS_IIDR:
+ fallthrough;
+ case GICV5_IRS_AIDR:
+ break;
default:
- return;
+ return -EINVAL;
}
+
+ return 0;
}
static const struct vgic_register_region vgic_v5_irs_registers[] = {
/*
* This is the IRS_CONFIG_FRAME.
*/
- REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR0, vgic_v5_mmio_read_irs_misc,
- vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR1, vgic_v5_mmio_read_irs_misc,
- vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR2, vgic_v5_mmio_read_irs_misc,
- vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH_UACCESS(GICV5_IRS_IDR0, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, NULL,
+ vgic_v5_mmio_uaccess_write_irs, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH_UACCESS(GICV5_IRS_IDR1, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, NULL,
+ vgic_v5_mmio_uaccess_write_irs, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH_UACCESS(GICV5_IRS_IDR2, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, NULL,
+ vgic_v5_mmio_uaccess_write_irs, 4,
+ VGIC_ACCESS_32bit),
REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR3, vgic_mmio_read_raz,
vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR4, vgic_mmio_read_raz,
vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR5, vgic_v5_mmio_read_irs_misc,
- vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR6, vgic_v5_mmio_read_irs_misc,
- vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR7, vgic_v5_mmio_read_irs_misc,
- vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IIDR, vgic_v5_mmio_read_irs_misc,
- vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_IRS_AIDR, vgic_v5_mmio_read_irs_misc,
- vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_IRS_CR0, vgic_v5_mmio_read_irs_misc,
- vgic_v5_mmio_write_irs_misc, 4,
- VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH_UACCESS(GICV5_IRS_IDR5, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, NULL,
+ vgic_v5_mmio_uaccess_write_irs, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH_UACCESS(GICV5_IRS_IDR6, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, NULL,
+ vgic_v5_mmio_uaccess_write_irs, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH_UACCESS(GICV5_IRS_IDR7, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, NULL,
+ vgic_v5_mmio_uaccess_write_irs, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH_UACCESS(GICV5_IRS_IIDR, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, NULL,
+ vgic_v5_mmio_uaccess_write_irs, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH_UACCESS(GICV5_IRS_AIDR, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, NULL,
+ vgic_v5_mmio_uaccess_write_irs, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH_UACCESS(GICV5_IRS_CR0,
+ vgic_v5_mmio_read_irs_misc,
+ vgic_v5_mmio_write_irs_misc, NULL,
+ vgic_v5_mmio_uaccess_write_irs, 4,
+ VGIC_ACCESS_32bit),
REGISTER_DESC_WITH_LENGTH(GICV5_IRS_CR1, vgic_v5_mmio_read_irs_misc,
vgic_v5_mmio_write_irs_misc, 4,
VGIC_ACCESS_32bit),
REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SYNCR, vgic_mmio_read_raz,
vgic_mmio_write_wi, 4,
VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SYNC_STATUSR,
- vgic_v5_mmio_read_irs_misc,
- vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH_UACCESS(GICV5_IRS_SYNC_STATUSR,
+ vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi,
+ vgic_v5_mmio_uaccess_read_irs_status,
+ vgic_mmio_uaccess_write_wi, 4,
+ VGIC_ACCESS_32bit),
REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SPI_VMR, vgic_mmio_read_raz,
vgic_mmio_write_wi, 8,
VGIC_ACCESS_64bit),
@@ -506,35 +701,50 @@ static const struct vgic_register_region vgic_v5_irs_registers[] = {
vgic_v5_mmio_write_irs_spi, 4,
VGIC_ACCESS_32bit),
REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SPI_DOMAINR, vgic_v5_mmio_read_irs_spi,
- vgic_v5_mmio_write_irs_spi, 4,
- VGIC_ACCESS_32bit),
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SPI_RESAMPLER, vgic_mmio_read_raz,
vgic_mmio_write_wi, 4,
VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SPI_CFGR, vgic_v5_mmio_read_irs_spi,
- vgic_v5_mmio_write_irs_spi, 4,
- VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SPI_STATUSR,
- vgic_v5_mmio_read_irs_spi, vgic_mmio_write_wi,
- 4, VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_IRS_PE_SELR, vgic_v5_mmio_read_irs_misc,
+ REGISTER_DESC_WITH_LENGTH_UACCESS(GICV5_IRS_SPI_CFGR,
+ vgic_v5_mmio_read_irs_spi,
+ vgic_v5_mmio_write_irs_spi, NULL,
+ vgic_v5_mmio_uaccess_write_irs, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH_UACCESS(GICV5_IRS_SPI_STATUSR,
+ vgic_v5_mmio_read_irs_spi,
+ vgic_mmio_write_wi,
+ vgic_v5_mmio_uaccess_read_irs_status,
+ vgic_mmio_uaccess_write_wi, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_PE_SELR,
+ vgic_v5_mmio_read_irs_misc,
vgic_v5_mmio_write_irs_misc, 4,
VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_IRS_PE_STATUSR,
- vgic_v5_mmio_read_irs_misc,
- vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH_UACCESS(GICV5_IRS_PE_STATUSR,
+ vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi,
+ vgic_v5_mmio_uaccess_read_irs_status,
+ vgic_mmio_uaccess_write_wi, 4,
+ VGIC_ACCESS_32bit),
REGISTER_DESC_WITH_LENGTH(GICV5_IRS_PE_CR0, vgic_v5_mmio_read_irs_misc,
vgic_v5_mmio_write_irs_misc, 4,
VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IST_BASER, vgic_v5_mmio_read_irs_ist,
- vgic_v5_mmio_write_irs_ist, 8,
- VGIC_ACCESS_64bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IST_CFGR, vgic_v5_mmio_read_irs_ist,
- vgic_v5_mmio_write_irs_ist, 4,
- VGIC_ACCESS_32bit),
- REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IST_STATUSR,
- vgic_v5_mmio_read_irs_ist, vgic_mmio_write_wi,
- 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH_UACCESS(GICV5_IRS_IST_BASER,
+ vgic_v5_mmio_read_irs_ist,
+ vgic_v5_mmio_write_irs_ist, NULL,
+ vgic_v5_mmio_uaccess_write_irs, 8,
+ VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH_UACCESS(GICV5_IRS_IST_CFGR,
+ vgic_v5_mmio_read_irs_ist,
+ vgic_v5_mmio_write_irs_ist, NULL,
+ vgic_v5_mmio_uaccess_write_irs, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH_UACCESS(GICV5_IRS_IST_STATUSR,
+ vgic_v5_mmio_read_irs_ist,
+ vgic_mmio_write_wi,
+ vgic_v5_mmio_uaccess_read_irs_status,
+ vgic_mmio_uaccess_write_wi, 4,
+ VGIC_ACCESS_32bit),
REGISTER_DESC_WITH_LENGTH(GICV5_IRS_MAP_L2_ISTR, vgic_mmio_read_raz,
vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
@@ -764,3 +974,90 @@ int kvm_vgic_v5_irs_init(struct kvm *kvm, unsigned int nr_spis)
return 0;
}
+
+int vgic_v5_has_attr_regs(struct kvm_device *dev, struct kvm_device_attr *attr)
+{
+ const struct vgic_register_region *region;
+ struct vgic_reg_attr reg_attr;
+ struct kvm_vcpu *vcpu;
+ gpa_t addr, offset;
+ int ret, align;
+
+ ret = vgic_v5_parse_attr(dev, attr, ®_attr);
+ if (ret)
+ return ret;
+
+ vcpu = reg_attr.vcpu;
+ addr = reg_attr.addr;
+
+ if (attr->group == KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS)
+ return vgic_v5_has_cpu_sysregs_attr(vcpu, attr);
+
+ offset = attr->attr;
+
+ region = vgic_find_mmio_region(vgic_v5_irs_registers,
+ ARRAY_SIZE(vgic_v5_irs_registers),
+ offset);
+ if (!region)
+ return -ENXIO;
+
+ align = region->access_flags & VGIC_ACCESS_64bit ? 0x7 : 0x3;
+ if (offset & align)
+ return -EINVAL;
+
+ return 0;
+}
+
+/*
+ * Access the IRS MMIO Regs. Relevant locks have been taken by the calling code.
+ */
+int vgic_v5_irs_attr_regs_access(struct kvm_device *dev,
+ struct kvm_device_attr *attr,
+ u64 *reg, bool is_write)
+{
+ const struct vgic_register_region *region;
+ gpa_t addr, offset;
+ unsigned int len;
+ int align, ret = 0;
+
+ offset = attr->attr;
+
+ if (IS_VGIC_ADDR_UNDEF(dev->kvm->arch.vgic.vgic_v5_irs_data->vgic_v5_irs_base))
+ return -ENXIO;
+
+ region = vgic_find_mmio_region(vgic_v5_irs_registers,
+ ARRAY_SIZE(vgic_v5_irs_registers),
+ offset);
+ if (!region)
+ return -ENXIO;
+
+ /*
+ * Although the spec supports upper/lower 32-bit accesses to
+ * 64-bit IRS registers, the userspace ABI requires 64-bit
+ * accesses to all 64-bit wide registers. We therefore only
+ * support 32-bit accesses to 32-bit-wide registers.
+ */
+ align = region->access_flags & VGIC_ACCESS_64bit ? 0x7 : 0x3;
+ len = region->access_flags & VGIC_ACCESS_64bit ? 8 : 4;
+
+ if (offset & align)
+ return -EINVAL;
+
+ addr = dev->kvm->arch.vgic.vgic_v5_irs_data->vgic_v5_irs_base + offset;
+
+ if (is_write) {
+ if (region->uaccess_write)
+ ret = region->uaccess_write(kvm_get_vcpu(dev->kvm, 0),
+ addr, len, *reg);
+ else
+ region->write(kvm_get_vcpu(dev->kvm, 0), addr, len, *reg);
+ } else {
+ if (region->uaccess_read)
+ *reg = region->uaccess_read(kvm_get_vcpu(dev->kvm, 0),
+ addr, len);
+ else
+ *reg = region->read(kvm_get_vcpu(dev->kvm, 0), addr, len);
+ }
+
+ return ret;
+}
diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c
index 599ab4efc1db5..f1f1fcb08161f 100644
--- a/arch/arm64/kvm/vgic/vgic-kvm-device.c
+++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c
@@ -786,6 +786,9 @@ int vgic_v5_parse_attr(struct kvm_device *dev, struct kvm_device_attr *attr,
mpidr_reg = VGIC_TO_MPIDR(vgic_mpidr);
reg_attr->vcpu = kvm_mpidr_to_vcpu(dev->kvm, mpidr_reg);
break;
+ case KVM_DEV_ARM_VGIC_GRP_IRS_REGS:
+ reg_attr->vcpu = kvm_get_vcpu(dev->kvm, 0);
+ break;
default:
return -EINVAL;
}
@@ -831,8 +834,11 @@ static int vgic_v5_attr_regs_access(struct kvm_device *dev,
struct kvm_device_attr *attr,
bool is_write)
{
+ u64 __user *uaddr = (u64 __user *)(unsigned long)attr->addr;
struct vgic_reg_attr reg_attr;
struct kvm_vcpu *vcpu;
+ bool uaccess;
+ u64 val;
int ret;
ret = vgic_v5_parse_attr(dev, attr, ®_attr);
@@ -841,6 +847,22 @@ static int vgic_v5_attr_regs_access(struct kvm_device *dev,
vcpu = reg_attr.vcpu;
+ switch (attr->group) {
+ case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
+ /* Sysregs uaccess is performed by the sysreg handling code */
+ uaccess = false;
+ break;
+ case KVM_DEV_ARM_VGIC_GRP_IRS_REGS:
+ fallthrough;
+ default:
+ uaccess = true;
+ }
+
+ if (uaccess && is_write) {
+ if (get_user(val, uaddr))
+ return -EFAULT;
+ }
+
mutex_lock(&dev->kvm->lock);
if (kvm_trylock_all_vcpus(dev->kvm)) {
@@ -864,6 +886,18 @@ static int vgic_v5_attr_regs_access(struct kvm_device *dev,
case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
ret = vgic_v5_cpu_sysregs_uaccess(vcpu, attr, is_write);
break;
+ case KVM_DEV_ARM_VGIC_GRP_IRS_REGS:
+ /*
+ * The IRS registers are a mixture of 32-bit and 64-bit
+ * registers. Internally, we always perform the correctly sized
+ * access, but the UAPI is defined in such a way that we are
+ * always provided a __u64 by userspace. When userspace writes,
+ * the upper 32-bits are ignored for 32-bit accesses, and on a
+ * read any 32-bit accesses are written back to user memory
+ * using the full 64-bits.
+ */
+ ret = vgic_v5_irs_attr_regs_access(dev, attr, &val, is_write);
+ break;
default:
ret = -EINVAL;
break;
@@ -874,6 +908,9 @@ static int vgic_v5_attr_regs_access(struct kvm_device *dev,
kvm_unlock_all_vcpus(dev->kvm);
mutex_unlock(&dev->kvm->lock);
+ if (!ret && uaccess && !is_write)
+ ret = put_user(val, uaddr);
+
return ret;
}
@@ -883,6 +920,8 @@ static int vgic_v5_set_attr(struct kvm_device *dev,
switch (attr->group) {
case KVM_DEV_ARM_VGIC_GRP_ADDR:
break;
+ case KVM_DEV_ARM_VGIC_GRP_IRS_REGS:
+ fallthrough;
case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
return vgic_v5_attr_regs_access(dev, attr, true);
case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
@@ -909,6 +948,8 @@ static int vgic_v5_get_attr(struct kvm_device *dev,
switch (attr->group) {
case KVM_DEV_ARM_VGIC_GRP_ADDR:
break;
+ case KVM_DEV_ARM_VGIC_GRP_IRS_REGS:
+ fallthrough;
case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
return vgic_v5_attr_regs_access(dev, attr, false);
case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
@@ -940,16 +981,10 @@ static int vgic_v5_has_attr(struct kvm_device *dev,
return 0;
}
return -ENXIO;
- case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS: {
- struct vgic_reg_attr reg_attr;
- int ret;
-
- ret = vgic_v5_parse_attr(dev, attr, ®_attr);
- if (ret)
- return ret;
-
- return vgic_v5_has_cpu_sysregs_attr(reg_attr.vcpu, attr);
- }
+ case KVM_DEV_ARM_VGIC_GRP_IRS_REGS:
+ fallthrough;
+ case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
+ return vgic_v5_has_attr_regs(dev, attr);
case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
return 0;
case KVM_DEV_ARM_VGIC_GRP_CTRL:
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 4f9ded224d613..7536e9a13086e 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -389,6 +389,10 @@ int vgic_v5_cpu_sysregs_uaccess(struct kvm_vcpu *vcpu,
struct kvm_device_attr *attr, bool is_write);
int vgic_v5_has_cpu_sysregs_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
const struct sys_reg_desc *vgic_v5_get_sysreg_table(unsigned int *sz);
+int vgic_v5_irs_attr_regs_access(struct kvm_device *dev,
+ struct kvm_device_attr *attr,
+ u64 *reg, bool is_write);
+int vgic_v5_has_attr_regs(struct kvm_device *dev, struct kvm_device_attr *attr);
#define for_each_visible_v5_ppi(__i, __k) \
for_each_set_bit(__i, (__k)->arch.vgic.gicv5_vm.vgic_ppi_mask, VGIC_V5_NR_PRIVATE_IRQS)
diff --git a/tools/arch/arm64/include/uapi/asm/kvm.h b/tools/arch/arm64/include/uapi/asm/kvm.h
index d1b2ca317f586..710a0d267347d 100644
--- a/tools/arch/arm64/include/uapi/asm/kvm.h
+++ b/tools/arch/arm64/include/uapi/asm/kvm.h
@@ -422,6 +422,7 @@ enum {
#define KVM_DEV_ARM_VGIC_GRP_LEVEL_INFO 7
#define KVM_DEV_ARM_VGIC_GRP_ITS_REGS 8
#define KVM_DEV_ARM_VGIC_GRP_MAINT_IRQ 9
+#define KVM_DEV_ARM_VGIC_GRP_IRS_REGS 10
#define KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_SHIFT 10
#define KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_MASK \
(0x3fffffULL << KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_SHIFT)
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 33/49] KVM: arm64: gic-v5: Add CoreSight MMIO regs to IRS
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (31 preceding siblings ...)
2026-08-07 11:28 ` [PATCH v5 32/49] KVM: arm64: gic-v5: Handle userspace accesses to IRS MMIO region Sascha Bischoff
@ 2026-08-07 11:29 ` Sascha Bischoff
2026-08-07 11:29 ` [PATCH v5 34/49] KVM: arm64: gic-v5: Add VGICv5 IST save/restore UAPI Sascha Bischoff
` (15 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:29 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Expose the CoreSight identification registers in the emulated GICv5
IRS MMIO region. These registers are read-only from the guest's point
of view, so return the architectural identification values and ignore
writes.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-irs-v5.c | 78 +++++++++++++++++++++++++++++++
1 file changed, 78 insertions(+)
diff --git a/arch/arm64/kvm/vgic/vgic-irs-v5.c b/arch/arm64/kvm/vgic/vgic-irs-v5.c
index 212066b8d7ed5..22f8ce3b7c83a 100644
--- a/arch/arm64/kvm/vgic/vgic-irs-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-irs-v5.c
@@ -487,6 +487,43 @@ 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);
+
+ 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;
+ }
+}
+
static unsigned long vgic_v5_mmio_uaccess_read_irs_status(struct kvm_vcpu *vcpu,
gpa_t addr,
unsigned int len)
@@ -808,6 +845,47 @@ static const struct vgic_register_region vgic_v5_irs_registers[] = {
REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SWERR_SYNDROMER1,
vgic_mmio_read_raz, vgic_mmio_write_wi, 8,
VGIC_ACCESS_64bit),
+
+ /* 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_ACCESS_32bit),
};
unsigned int vgic_v5_init_irs_iodev(struct vgic_io_device *dev)
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 34/49] KVM: arm64: gic-v5: Add VGICv5 IST save/restore UAPI
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (32 preceding siblings ...)
2026-08-07 11:29 ` [PATCH v5 33/49] KVM: arm64: gic-v5: Add CoreSight MMIO regs to IRS Sascha Bischoff
@ 2026-08-07 11:29 ` Sascha Bischoff
2026-08-07 11:30 ` [PATCH v5 35/49] KVM: arm64: gic-v5: Implement save/restore mechanisms for ISTs Sascha Bischoff
` (14 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:29 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Introduce KVM_DEV_ARM_VGIC_GRP_IST as the VGICv5 device attribute
group used to transfer SPI and LPI IST state between KVM and userspace
during migration.
The group uses struct kvm_vgic_v5_ist as a descriptor for the
userspace-owned SPI and LPI IST buffers. Each buffer contains the
architected 32-bit ISTE state for the corresponding interrupt number
space, while the host IST layout remains private to KVM.
The descriptor is architecture-specific, as it is only the payload of
an ARM VGIC device attribute.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/include/uapi/asm/kvm.h | 9 +++++++++
tools/arch/arm64/include/uapi/asm/kvm.h | 9 +++++++++
2 files changed, 18 insertions(+)
diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
index 710a0d267347d..9998252ea8ec8 100644
--- a/arch/arm64/include/uapi/asm/kvm.h
+++ b/arch/arm64/include/uapi/asm/kvm.h
@@ -423,6 +423,15 @@ enum {
#define KVM_DEV_ARM_VGIC_GRP_ITS_REGS 8
#define KVM_DEV_ARM_VGIC_GRP_MAINT_IRQ 9
#define KVM_DEV_ARM_VGIC_GRP_IRS_REGS 10
+#define KVM_DEV_ARM_VGIC_GRP_IST 11
+
+struct kvm_vgic_v5_ist {
+ __u64 spi_ist_addr;
+ __u64 spi_ist_size;
+ __u64 lpi_ist_addr;
+ __u64 lpi_ist_size;
+};
+
#define KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_SHIFT 10
#define KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_MASK \
(0x3fffffULL << KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_SHIFT)
diff --git a/tools/arch/arm64/include/uapi/asm/kvm.h b/tools/arch/arm64/include/uapi/asm/kvm.h
index 710a0d267347d..9998252ea8ec8 100644
--- a/tools/arch/arm64/include/uapi/asm/kvm.h
+++ b/tools/arch/arm64/include/uapi/asm/kvm.h
@@ -423,6 +423,15 @@ enum {
#define KVM_DEV_ARM_VGIC_GRP_ITS_REGS 8
#define KVM_DEV_ARM_VGIC_GRP_MAINT_IRQ 9
#define KVM_DEV_ARM_VGIC_GRP_IRS_REGS 10
+#define KVM_DEV_ARM_VGIC_GRP_IST 11
+
+struct kvm_vgic_v5_ist {
+ __u64 spi_ist_addr;
+ __u64 spi_ist_size;
+ __u64 lpi_ist_addr;
+ __u64 lpi_ist_size;
+};
+
#define KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_SHIFT 10
#define KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_MASK \
(0x3fffffULL << KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_SHIFT)
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 35/49] KVM: arm64: gic-v5: Implement save/restore mechanisms for ISTs
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (33 preceding siblings ...)
2026-08-07 11:29 ` [PATCH v5 34/49] KVM: arm64: gic-v5: Add VGICv5 IST save/restore UAPI Sascha Bischoff
@ 2026-08-07 11:30 ` Sascha Bischoff
2026-08-07 11:30 ` [PATCH v5 36/49] Documentation: KVM: Document KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS for VGICv5 Sascha Bischoff
` (13 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:30 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
When running a GICv5 VM, there are up to two ISTs that must be saved
or restored when migrating a VM.
The SPI IST is allocated by the hypervisor, as the guest presumes the
memory for the SPI state is allocated by the hardware. The LPI IST is
also shadowed in KVM when the guest enables LPIs, so the guest's LPI
IST memory is not used directly by the physical GICv5 hardware.
As both in-use ISTs are backed by host allocations, userspace provides
migration storage for both tables through KVM_DEV_ARM_VGIC_GRP_IST.
The userspace descriptor supplies separate SPI and LPI buffers, each
containing the architected 32-bit ISTE state for the corresponding
interrupt number space. If the guest has not configured an LPI IST,
userspace must omit the LPI buffer.
On save, acquire every vCPU mutex, returning -EBUSY if any vCPU is
already running. Holding these locks blocks KVM_RUN while the IST
state is exported. Use IRS_SAVE_VMR to write the IRS's internal state
back to the ISTs and check that the VM remained quiescent. After
copying each IST, issue a Q-only IRS_SAVE_VMR operation to update
IRS_SAVE_VM_STATUSR.Q and repeat the check. If the VM has not remained
quiescent since the save began, propagate an error to userspace so
that the save can be retried without losing incoming interrupt state.
On restore, reject the operation if any vCPU has already run. Validate
the userspace buffers and, if the restored IRS state describes an LPI
IST, allocate the shadow host IST while the VMTE is still valid. This
allows the IRS operation that assigns the IST to update the VMTE.
Then make the VMTE invalid before copying the SPI and LPI IST state
from the userspace-provided buffers, and make it valid again once the
copy is complete.
As part of restoring the ISTs, track pending interrupts and clear
their pending state from the restored host ISTs. Once the VM is valid
again, make those interrupts pending through the GIC VDPEND system
instruction.
Once a host LPI IST has been allocated, the guest-visible
IRS_IST_BASER and IRS_IST_CFGR state describes that allocation.
Userspace may replay values with the same defined fields, but KVM
rejects changes while the host IST exists. During save, also require
the VMTE IST_ID_BITS value to match IRS_IST_CFGR.LPI_ID_BITS so that
userspace buffer validation and the host IST walk use the same number
of entries.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-irs-v5.c | 47 +-
arch/arm64/kvm/vgic/vgic-kvm-device.c | 13 +
arch/arm64/kvm/vgic/vgic-v5-tables.c | 596 ++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic-v5-tables.h | 16 +
arch/arm64/kvm/vgic/vgic-v5.c | 330 +++++++++++++-
arch/arm64/kvm/vgic/vgic.h | 5 +
6 files changed, 1001 insertions(+), 6 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-irs-v5.c b/arch/arm64/kvm/vgic/vgic-irs-v5.c
index 22f8ce3b7c83a..72eef5737c8c4 100644
--- a/arch/arm64/kvm/vgic/vgic-irs-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-irs-v5.c
@@ -370,6 +370,20 @@ static bool vgic_v5_ist_cfgr_valid(struct vgic_v5_irs *irs)
return irs->ist_cfgr.l2sz == irs->idr2.ist_l2sz;
}
+static bool vgic_v5_ist_cfgr_matches(const struct vgic_v5_irs *irs,
+ unsigned long val)
+{
+ u8 lpi_id_bits = FIELD_GET(GICV5_IRS_IST_CFGR_LPI_ID_BITS, val);
+ u8 l2sz = FIELD_GET(GICV5_IRS_IST_CFGR_L2SZ, val);
+ u8 istsz = FIELD_GET(GICV5_IRS_IST_CFGR_ISTSZ, val);
+ bool structure = !!(val & GICV5_IRS_IST_CFGR_STRUCTURE);
+
+ return irs->ist_cfgr.lpi_id_bits == lpi_id_bits &&
+ irs->ist_cfgr.l2sz == l2sz &&
+ irs->ist_cfgr.istsz == istsz &&
+ irs->ist_cfgr.structure == structure;
+}
+
static unsigned long vgic_v5_mmio_read_irs_ist(struct kvm_vcpu *vcpu,
gpa_t addr, unsigned int len)
{
@@ -550,6 +564,7 @@ static int vgic_v5_mmio_uaccess_write_irs(struct kvm_vcpu *vcpu, gpa_t addr,
struct vgic_dist *vgic = &vcpu->kvm->arch.vgic;
struct vgic_v5_irs *irs_data = vgic->vgic_v5_irs_data;
size_t offset = addr & (SZ_64K - 1);
+ int ret;
/*
* The following registers are ONLY settable via uaccesses. The guest
@@ -646,13 +661,23 @@ static int vgic_v5_mmio_uaccess_write_irs(struct kvm_vcpu *vcpu, gpa_t addr,
return -EINVAL;
break;
case GICV5_IRS_IST_BASER:
- if (irs_data->ist_baser.valid &&
- !vgic_v5_ist_baser_matches(irs_data, val))
+ ret = vgic_v5_lpi_ist_exists(vcpu->kvm);
+ if (ret < 0)
+ return ret;
+
+ if (ret && !vgic_v5_ist_baser_matches(irs_data, val))
return -EINVAL;
vgic_v5_update_irs_ist_baser(irs_data, val);
break;
case GICV5_IRS_IST_CFGR:
+ ret = vgic_v5_lpi_ist_exists(vcpu->kvm);
+ if (ret < 0)
+ return ret;
+
+ if (ret && !vgic_v5_ist_cfgr_matches(irs_data, val))
+ return -EINVAL;
+
irs_data->ist_cfgr.lpi_id_bits = FIELD_GET(GICV5_IRS_IST_CFGR_LPI_ID_BITS, val);
irs_data->ist_cfgr.l2sz = FIELD_GET(GICV5_IRS_IST_CFGR_L2SZ, val);
irs_data->ist_cfgr.istsz = FIELD_GET(GICV5_IRS_IST_CFGR_ISTSZ, val);
@@ -1053,6 +1078,24 @@ int kvm_vgic_v5_irs_init(struct kvm *kvm, unsigned int nr_spis)
return 0;
}
+int vgic_v5_irs_lpi_ist_id_bits(struct kvm *kvm, unsigned int *id_bits)
+{
+ struct vgic_v5_irs *irs = kvm->arch.vgic.vgic_v5_irs_data;
+
+ if (!irs)
+ return -ENXIO;
+
+ if (!irs->ist_baser.valid)
+ return 0;
+
+ if (!vgic_v5_ist_cfgr_valid(irs))
+ return -EINVAL;
+
+ *id_bits = irs->ist_cfgr.lpi_id_bits;
+
+ return 1;
+}
+
int vgic_v5_has_attr_regs(struct kvm_device *dev, struct kvm_device_attr *attr)
{
const struct vgic_register_region *region;
diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c
index f1f1fcb08161f..1c205cb1361fd 100644
--- a/arch/arm64/kvm/vgic/vgic-kvm-device.c
+++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c
@@ -920,6 +920,11 @@ static int vgic_v5_set_attr(struct kvm_device *dev,
switch (attr->group) {
case KVM_DEV_ARM_VGIC_GRP_ADDR:
break;
+ case KVM_DEV_ARM_VGIC_GRP_IST:
+ if (attr->attr)
+ return -ENXIO;
+
+ return vgic_v5_irs_restore_ists(dev->kvm, attr);
case KVM_DEV_ARM_VGIC_GRP_IRS_REGS:
fallthrough;
case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
@@ -948,6 +953,11 @@ static int vgic_v5_get_attr(struct kvm_device *dev,
switch (attr->group) {
case KVM_DEV_ARM_VGIC_GRP_ADDR:
break;
+ case KVM_DEV_ARM_VGIC_GRP_IST:
+ if (attr->attr)
+ return -ENXIO;
+
+ return vgic_v5_irs_save_ists(dev->kvm, attr);
case KVM_DEV_ARM_VGIC_GRP_IRS_REGS:
fallthrough;
case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
@@ -997,6 +1007,9 @@ static int vgic_v5_has_attr(struct kvm_device *dev,
default:
return -ENXIO;
}
+ break;
+ case KVM_DEV_ARM_VGIC_GRP_IST:
+ return attr->attr ? -ENXIO : 0;
default:
return -ENXIO;
}
diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.c b/arch/arm64/kvm/vgic/vgic-v5-tables.c
index fa2ced036f7cd..201d2d5025dea 100644
--- a/arch/arm64/kvm/vgic/vgic-v5-tables.c
+++ b/arch/arm64/kvm/vgic/vgic-v5-tables.c
@@ -10,6 +10,7 @@
#include <linux/mutex.h>
#include <linux/sizes.h>
#include <linux/slab.h>
+#include <linux/uaccess.h>
#include <linux/xarray.h>
#include <asm/kvm_mmu.h>
@@ -65,6 +66,20 @@ static DEFINE_XARRAY(vm_info);
#define GICV5_VPED_ADDR_SHIFT 3ULL
#define GICV5_VPED_ADDR GENMASK_ULL(55, 3)
+/* L2 Interrupt State Table Entry */
+#define GICV5_ISTL2E_PENDING BIT(0)
+#define GICV5_ISTL2E_ACTIVE BIT(1)
+#define GICV5_ISTL2E_HM BIT(2)
+#define GICV5_ISTL2E_ENABLE BIT(3)
+#define GICV5_ISTL2E_IRM BIT(4)
+#define GICV5_ISTL2E_HWU GENMASK(10, 9)
+#define GICV5_ISTL2E_PRIORITY GENMASK(15, 11)
+#define GICV5_ISTL2E_IAFFID GENMASK(31, 16)
+
+#define GICV5_ISTE_SIZE(istsz) BIT((istsz) + 2)
+#define GICV5_LINEAR_IST_SIZE(id_bits, istsz) \
+ (BIT(id_bits) * GICV5_ISTE_SIZE(istsz))
+
/*
* The LPI and SPI configuration is stored in the 2nd and 3rd 64-bit chunks of
* the VMTE (0-based). We call this a section here in an attempt to simplify the
@@ -73,6 +88,26 @@ static DEFINE_XARRAY(vm_info);
#define GICV5_VMTEL2_LPI_SECTION 2
#define GICV5_VMTEL2_SPI_SECTION 3
+struct vgic_v5_ist_desc {
+ struct vgic_v5_vm_info *vmi;
+ void *base;
+ unsigned int id_bits;
+ unsigned int istsz;
+ unsigned int l2sz;
+ size_t iste_size;
+ bool present;
+};
+
+struct vgic_v5_two_level_ist_shape {
+ size_t l1_entries;
+ size_t l2_entries;
+};
+
+struct vgic_v5_pending_irq {
+ u32 irq;
+ struct list_head next;
+};
+
static int vgic_v5_alloc_linear_ist(struct kvm *kvm, bool spi_ist,
unsigned int id_bits,
unsigned int istsz);
@@ -106,6 +141,22 @@ static void vgic_v5_clean_inval(void *va, size_t size)
dcache_clean_inval_poc(base, base + size);
}
+static void vgic_v5_drain_pending_irqs(struct kvm *kvm,
+ struct vgic_v5_vm_info *vmi,
+ bool reinject)
+{
+ struct vgic_v5_pending_irq *pirq, *tmp;
+
+ list_for_each_entry_safe(pirq, tmp, &vmi->pending_irqs, next) {
+ if (reinject)
+ kvm_call_hyp(__vgic_v5_vdpend, pirq->irq, true,
+ vgic_v5_vm_id(kvm));
+
+ list_del(&pirq->next);
+ kfree(pirq);
+ }
+}
+
/*
* Create a linear VM Table. Directly using the number of entries supplied as
* the size of an L2 VMTE (32 bytes) guarantees that our allocation is aligned per
@@ -465,6 +516,13 @@ int vgic_v5_vmte_init(struct kvm *kvm)
goto out_fail;
vmi_inserted = true;
+ /*
+ * If we are restoring the state of a guest, we need to re-inject any
+ * IRQs that were pending when the state of the guest was originally
+ * saved. We use the pending_irqs list for this.
+ */
+ INIT_LIST_HEAD(&vmi->pending_irqs);
+
/* Allocate and assign the VM Descriptor, if required. */
if (vmt_info->vmd_size != 0) {
vmd_alloc_size = round_up(vmt_info->vmd_size,
@@ -604,6 +662,9 @@ int vgic_v5_vmte_release(struct kvm *kvm)
if (!vmi)
goto no_vmi;
+ /* Unlikely, but possible. Avoid leaking the memory. */
+ vgic_v5_drain_pending_irqs(kvm, vmi, false);
+
/* If we have an LPI IST, free it */
if (vmi->h_lpi_ist) {
ret = vgic_v5_lpi_ist_free(kvm);
@@ -1186,6 +1247,18 @@ static int vgic_v5_spi_ist_free(struct kvm *kvm)
return vgic_v5_linear_ist_free(kvm, true);
}
+int vgic_v5_lpi_ist_exists(struct kvm *kvm)
+{
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vgic_v5_vm_info *vmi;
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -ENXIO;
+
+ return !!vmi->h_lpi_ist;
+}
+
/*
* Allocate an IST for LPIs.
*
@@ -1262,3 +1335,526 @@ int vgic_v5_lpi_ist_free(struct kvm *kvm)
else
return vgic_v5_two_level_ist_free(kvm, false);
}
+
+static struct vgic_v5_two_level_ist_shape
+vgic_v5_two_level_ist_shape(const struct vgic_v5_ist_desc *ist)
+{
+ struct vgic_v5_two_level_ist_shape shape;
+ size_t l2bits, n;
+
+ l2bits = (10 - ist->istsz) + (2 * ist->l2sz);
+ n = max(2, ist->id_bits - l2bits + 3 - 1);
+
+ shape.l1_entries = BIT(n + 1) / GICV5_IRS_ISTL1E_SIZE;
+ shape.l2_entries = BIT(l2bits);
+
+ return shape;
+}
+
+static int vgic_v5_read_vm_ist_desc(struct kvm *kvm, unsigned int section,
+ struct vgic_v5_ist_desc *ist)
+{
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vmtl2_entry *vmte;
+ u64 vmte_ist_section;
+
+ vmte = vgic_v5_get_l2_vmte(vm_id);
+ if (IS_ERR(vmte))
+ return PTR_ERR(vmte);
+
+ scoped_guard(raw_spinlock_irqsave, &vgic_v5_irs_lock) {
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+ vmte_ist_section = le64_to_cpu(READ_ONCE(vmte->val[section]));
+ }
+
+ ist->id_bits = FIELD_GET(GICV5_VMTEL2E_IST_ID_BITS, vmte_ist_section);
+ ist->istsz = FIELD_GET(GICV5_VMTEL2E_IST_ISTSZ, vmte_ist_section);
+ ist->l2sz = FIELD_GET(GICV5_VMTEL2E_IST_L2SZ, vmte_ist_section);
+ ist->iste_size = GICV5_ISTE_SIZE(ist->istsz);
+
+ return !!(vmte_ist_section & GICV5_VMTEL2E_IST_VALID);
+}
+
+static int vgic_v5_get_spi_ist_desc(struct kvm *kvm,
+ struct vgic_v5_ist_desc *ist)
+{
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ int ret;
+
+ memset(ist, 0, sizeof(*ist));
+
+ ist->vmi = xa_load(&vm_info, vm_id);
+ if (!ist->vmi)
+ return -ENXIO;
+
+ ret = vgic_v5_read_vm_ist_desc(kvm, GICV5_VMTEL2_SPI_SECTION, ist);
+ if (ret < 0)
+ return ret;
+
+ ist->base = ist->vmi->h_spi_ist;
+ if (!ret || !ist->base)
+ return -ENXIO;
+
+ ist->present = true;
+ return 0;
+}
+
+static int vgic_v5_get_lpi_ist_desc(struct kvm *kvm,
+ struct vgic_v5_ist_desc *ist)
+{
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ bool guest_valid, host_valid;
+ int ret;
+
+ memset(ist, 0, sizeof(*ist));
+
+ ist->vmi = xa_load(&vm_info, vm_id);
+ if (WARN_ON_ONCE(!ist->vmi))
+ return -ENXIO;
+
+ ret = vgic_v5_read_vm_ist_desc(kvm, GICV5_VMTEL2_LPI_SECTION, ist);
+ if (ret < 0)
+ return ret;
+
+ host_valid = ret;
+ guest_valid = kvm->arch.vgic.vgic_v5_irs_data->ist_baser.valid;
+ ist->base = ist->vmi->h_lpi_ist;
+
+ /* If there is no IST to save/restore, return without error. */
+ if (!guest_valid && !host_valid && !ist->base)
+ return 0;
+
+ /* Mismatched combination of valid state */
+ if (!guest_valid || !host_valid || !ist->base)
+ return -ENXIO;
+
+ if (ist->vmi->h_lpi_ist_structure && !ist->vmi->h_lpi_l2_ists)
+ return -ENXIO;
+
+ ist->present = true;
+ return 0;
+}
+
+/*
+ * Save a linear host IST to userspace memory.
+ *
+ * Only the architected 32-bit ISTE state is stored. Metadata is skipped when
+ * striding through the host IST.
+ */
+static int vgic_v5_save_linear_ist(const struct vgic_v5_ist_desc *ist,
+ u32 __user *uaddr, size_t nr_entries)
+{
+ __le32 h_iste;
+ size_t index;
+ int ret;
+
+ vgic_v5_clean_inval(ist->base,
+ GICV5_LINEAR_IST_SIZE(ist->id_bits, ist->istsz));
+
+ for (index = 0; index < nr_entries; index++) {
+ __le32 *h_iste_addr = ist->base + index * ist->iste_size;
+
+ h_iste = READ_ONCE(*h_iste_addr);
+ ret = put_user(h_iste, uaddr);
+ if (ret)
+ return ret;
+
+ uaddr++;
+ }
+
+ return 0;
+}
+
+/*
+ * Save a two-level host IST to userspace memory.
+ *
+ * Only the architected 32-bit ISTE state is stored. Metadata is skipped when
+ * striding through the host IST.
+ */
+static int vgic_v5_save_two_level_ist(const struct vgic_v5_ist_desc *ist,
+ u32 __user *uaddr)
+{
+ struct vgic_v5_two_level_ist_shape shape;
+ size_t h_l1_index, h_l2_index;
+ void *h_l2_ist_base;
+ __le32 h_iste;
+ int ret;
+
+ shape = vgic_v5_two_level_ist_shape(ist);
+
+ vgic_v5_clean_inval(ist->base,
+ shape.l1_entries * sizeof(*ist->vmi->h_lpi_ist));
+
+ for (h_l1_index = 0; h_l1_index < shape.l1_entries; h_l1_index++) {
+ u64 l1_iste;
+
+ /*
+ * Host L2 ISTs are preallocated. Any invalid L1 entry means the
+ * host IST state is inconsistent.
+ */
+ l1_iste = le64_to_cpu(READ_ONCE(ist->vmi->h_lpi_ist[h_l1_index]));
+ if (!FIELD_GET(GICV5_ISTL1E_VALID, l1_iste))
+ return -ENXIO;
+
+ h_l2_ist_base = ist->vmi->h_lpi_l2_ists[h_l1_index];
+ if (!h_l2_ist_base)
+ return -ENXIO;
+
+ vgic_v5_clean_inval(h_l2_ist_base,
+ shape.l2_entries * ist->iste_size);
+
+ for (h_l2_index = 0; h_l2_index < shape.l2_entries; h_l2_index++) {
+ h_iste = *(__le32 *)(h_l2_ist_base +
+ h_l2_index * ist->iste_size);
+
+ ret = put_user(h_iste, uaddr);
+ if (ret)
+ return ret;
+
+ uaddr++;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Save the SPI IST to userspace-provided memory.
+ */
+int vgic_v5_save_spi_ist(struct kvm *kvm, struct kvm_vgic_v5_ist *ist_attr)
+{
+ struct vgic_v5_ist_desc ist;
+ u32 __user *uaddr;
+ int ret;
+
+ ret = vgic_v5_get_spi_ist_desc(kvm, &ist);
+ if (ret)
+ return ret;
+
+ uaddr = (u32 __user *)(unsigned long)ist_attr->spi_ist_addr;
+
+ /* The host SPI IST is always linear. */
+ return vgic_v5_save_linear_ist(&ist, uaddr,
+ kvm->arch.vgic.nr_spis);
+}
+
+/*
+ * Save the LPI IST to userspace memory.
+ *
+ * The LPI IST may be linear or two-level, so host iteration depends on the
+ * allocated host shape.
+ */
+int vgic_v5_save_lpi_ist(struct kvm *kvm, struct kvm_vgic_v5_ist *ist_attr)
+{
+ struct vgic_v5_ist_desc ist;
+ u32 __user *uaddr;
+ int ret;
+
+ ret = vgic_v5_get_lpi_ist_desc(kvm, &ist);
+ if (ret)
+ return ret;
+
+ if (!ist.present)
+ return 0;
+
+ /*
+ * Userspace sized the buffer from the guest-visible configuration.
+ * Refuse to walk a host IST with a different number of entries.
+ */
+ if (ist.id_bits !=
+ kvm->arch.vgic.vgic_v5_irs_data->ist_cfgr.lpi_id_bits)
+ return -EINVAL;
+
+ uaddr = (u32 __user *)(unsigned long)ist_attr->lpi_ist_addr;
+
+ if (!ist.vmi->h_lpi_ist_structure)
+ return vgic_v5_save_linear_ist(&ist, uaddr,
+ BIT(ist.id_bits));
+
+ return vgic_v5_save_two_level_ist(&ist, uaddr);
+}
+
+/*
+ * Track any SPIs and LPIs that were marked as pending at the point where the
+ * IST was restored.
+ *
+ * Restored pending state is cleared from the host ISTE and replayed with VDPEND
+ * before the VM first runs.
+ */
+static int vgic_v5_track_pending_irq(struct list_head *pending_irqs, u32 intid,
+ u32 type)
+{
+ struct vgic_v5_pending_irq *pirq;
+
+ pirq = kzalloc_obj(*pirq, GFP_KERNEL);
+ if (!pirq)
+ return -ENOMEM;
+
+ /* Encode the interrupt as a GICv5 IntID. */
+ pirq->irq = FIELD_PREP(GICV5_HWIRQ_TYPE, type) |
+ FIELD_PREP(GICV5_HWIRQ_ID, intid);
+
+ INIT_LIST_HEAD(&pirq->next);
+ list_add_tail(&pirq->next, pending_irqs);
+
+ return 0;
+}
+
+/*
+ * Process and sanitise each restored ISTE.
+ *
+ * HWU is for hardware use and must not survive migration. Pending state is
+ * tracked, cleared from the ISTE, and replayed before the VM first runs.
+ */
+static int vgic_v5_process_iste(__le32 *iste, struct list_head *pending_irqs,
+ u32 intid, u32 type)
+{
+ u32 iste_data = le32_to_cpu(READ_ONCE(*iste));
+ int ret;
+
+ /* Pending state is replayed later with VDPEND. */
+ if (iste_data & GICV5_ISTL2E_PENDING) {
+ ret = vgic_v5_track_pending_irq(pending_irqs, intid, type);
+ if (ret)
+ return ret;
+ }
+
+ iste_data &= ~GICV5_ISTL2E_PENDING;
+ iste_data &= ~GICV5_ISTL2E_HWU;
+
+ WRITE_ONCE(*iste, cpu_to_le32(iste_data));
+
+ return 0;
+}
+
+static void vgic_v5_restore_spi_config(struct kvm *kvm, __le32 iste, u32 spi)
+{
+ u32 iste_data = le32_to_cpu(iste);
+ bool pending = iste_data & GICV5_ISTL2E_PENDING;
+ struct vgic_irq *irq;
+ unsigned long flags;
+
+ irq = vgic_get_irq(kvm, vgic_v5_make_spi(spi));
+ if (WARN_ON_ONCE(!irq))
+ return;
+
+ raw_spin_lock_irqsave(&irq->irq_lock, flags);
+
+ if (iste_data & GICV5_ISTL2E_HM)
+ irq->config = VGIC_CONFIG_LEVEL;
+ else
+ irq->config = VGIC_CONFIG_EDGE;
+
+ if (irq->config == VGIC_CONFIG_EDGE)
+ irq->pending_latch = pending;
+ else if (pending)
+ irq->pending_latch = true;
+ else if (!irq->active)
+ irq->pending_latch = false;
+
+ raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
+ vgic_put_irq(kvm, irq);
+}
+
+static int vgic_v5_restore_ist_entry(struct kvm *kvm,
+ const struct vgic_v5_ist_desc *ist,
+ void *h_iste_addr, __le32 h_iste,
+ u32 intid, u32 intid_type)
+{
+ __le32 raw_iste = h_iste;
+ int ret;
+
+ /*
+ * Sanitise the IST, clearing HWU & pending fields. Pending state is
+ * later replayed via GIC VDPEND.
+ */
+ ret = vgic_v5_process_iste(&h_iste, &ist->vmi->pending_irqs,
+ intid, intid_type);
+ if (ret)
+ return ret;
+
+ if (intid_type == GICV5_HWIRQ_TYPE_SPI)
+ vgic_v5_restore_spi_config(kvm, raw_iste, intid);
+
+ /*
+ * Zero the full ISTE (incl metadata), and write back the non-metadata
+ * region, only.
+ */
+ memset(h_iste_addr, 0, ist->iste_size);
+ WRITE_ONCE(*(__le32 *)h_iste_addr, h_iste);
+ vgic_v5_clean_inval(h_iste_addr, ist->iste_size);
+
+ return 0;
+}
+
+/*
+ * Restore a userspace IST image to a linear host IST.
+ *
+ * The userspace IST image is a linear array of 32-bit ISTEs.
+ */
+static int vgic_v5_restore_linear_ist(struct kvm *kvm,
+ const struct vgic_v5_ist_desc *ist,
+ u32 __user *uaddr, size_t nr_entries,
+ u32 intid_type)
+{
+ __le32 h_iste;
+ size_t index;
+ int ret;
+
+ for (index = 0; index < nr_entries; index++) {
+ void *h_iste_addr = ist->base + index * ist->iste_size;
+
+ ret = get_user(h_iste, uaddr);
+ if (ret)
+ return ret;
+
+ ret = vgic_v5_restore_ist_entry(kvm, ist, h_iste_addr,
+ h_iste, index, intid_type);
+ if (ret)
+ return ret;
+
+ uaddr++;
+ }
+
+ return 0;
+}
+
+/*
+ * Restore a userspace IST image to a two-level host IST.
+ *
+ * The userspace IST image is a linear array of 32-bit ISTEs.
+ */
+static int vgic_v5_restore_two_level_ist(struct kvm *kvm,
+ const struct vgic_v5_ist_desc *ist,
+ u32 __user *uaddr, u32 intid_type)
+{
+ struct vgic_v5_two_level_ist_shape shape;
+ size_t h_l1_index, h_l2_index;
+ void *h_l2_ist_base;
+ __le32 h_iste;
+ int ret;
+
+ shape = vgic_v5_two_level_ist_shape(ist);
+
+ vgic_v5_clean_inval(ist->vmi->h_lpi_ist,
+ shape.l1_entries * sizeof(*ist->vmi->h_lpi_ist));
+
+ for (h_l1_index = 0; h_l1_index < shape.l1_entries; ++h_l1_index) {
+ u64 l1_iste;
+
+ /*
+ * Host L2 ISTs are preallocated. Any invalid L1 entry means the
+ * host IST state is inconsistent.
+ */
+ l1_iste = le64_to_cpu(READ_ONCE(ist->vmi->h_lpi_ist[h_l1_index]));
+ if (!FIELD_GET(GICV5_ISTL1E_VALID, l1_iste))
+ return -ENXIO;
+
+ h_l2_ist_base = ist->vmi->h_lpi_l2_ists[h_l1_index];
+ if (!h_l2_ist_base)
+ return -ENXIO;
+
+ for (h_l2_index = 0; h_l2_index < shape.l2_entries; h_l2_index++) {
+ void *h_iste_addr = h_l2_ist_base +
+ h_l2_index * ist->iste_size;
+ u32 intid = h_l1_index * shape.l2_entries + h_l2_index;
+
+ ret = get_user(h_iste, uaddr);
+ if (ret)
+ return ret;
+
+ ret = vgic_v5_restore_ist_entry(kvm, ist, h_iste_addr,
+ h_iste, intid,
+ intid_type);
+ if (ret)
+ return ret;
+
+ uaddr++;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Restore the SPI IST from userspace-provided buffer to the host-allocated IST.
+ */
+int vgic_v5_restore_spi_ist(struct kvm *kvm, struct kvm_vgic_v5_ist *ist_attr)
+{
+ u32 __user *uaddr;
+ struct vgic_v5_ist_desc ist;
+ int ret;
+
+ ret = vgic_v5_get_spi_ist_desc(kvm, &ist);
+ if (ret)
+ return ret;
+
+ uaddr = (u32 __user *)(unsigned long)ist_attr->spi_ist_addr;
+
+ /* The host SPI IST is always linear. */
+ return vgic_v5_restore_linear_ist(kvm, &ist, uaddr,
+ kvm->arch.vgic.nr_spis,
+ GICV5_HWIRQ_TYPE_SPI);
+}
+
+/*
+ * Restore the LPI IST from userspace memory to the host-allocated LPI IST.
+ *
+ * The host LPI IST may be linear or two-level, so host iteration depends on the
+ * host IST's shape.
+ */
+int vgic_v5_restore_lpi_ist(struct kvm *kvm, struct kvm_vgic_v5_ist *ist_attr)
+{
+ u32 __user *uaddr;
+ struct vgic_v5_ist_desc ist;
+ int ret;
+
+ ret = vgic_v5_get_lpi_ist_desc(kvm, &ist);
+ if (ret)
+ return ret;
+
+ if (!ist.present)
+ return 0;
+
+ uaddr = (u32 __user *)(unsigned long)ist_attr->lpi_ist_addr;
+
+ if (!ist.vmi->h_lpi_ist_structure)
+ return vgic_v5_restore_linear_ist(kvm, &ist, uaddr,
+ BIT(ist.id_bits),
+ GICV5_HWIRQ_TYPE_LPI);
+
+ return vgic_v5_restore_two_level_ist(kvm, &ist, uaddr,
+ GICV5_HWIRQ_TYPE_LPI);
+}
+
+/*
+ * Process the pending IRQs removing them from the list and optionally injecting
+ * them.
+ */
+static int vgic_v5_process_pending_irqs(struct kvm *kvm, bool inject)
+{
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vgic_v5_vm_info *vmi;
+
+ lockdep_assert_held(&kvm->arch.config_lock);
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -ENXIO;
+
+ vgic_v5_drain_pending_irqs(kvm, vmi, inject);
+
+ return 0;
+}
+
+/* Replay pending state that was cleared while restoring guest IST state. */
+int vgic_v5_restore_pending_irqs(struct kvm *kvm)
+{
+ return vgic_v5_process_pending_irqs(kvm, true);
+}
+
+/* Drop pending state collected by a failed IST restore. */
+void vgic_v5_discard_pending_irqs(struct kvm *kvm)
+{
+ vgic_v5_process_pending_irqs(kvm, false);
+}
diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.h b/arch/arm64/kvm/vgic/vgic-v5-tables.h
index e28d39d59f7fb..b2ffbe68c05b4 100644
--- a/arch/arm64/kvm/vgic/vgic-v5-tables.h
+++ b/arch/arm64/kvm/vgic/vgic-v5-tables.h
@@ -9,6 +9,7 @@
#include <linux/spinlock.h>
#include <linux/idr.h>
#include <linux/irqchip/arm-gic-v5.h>
+#include <linux/list.h>
/* Level 1 Virtual Machine Table Entry */
typedef __le64 vmtl1_entry;
@@ -44,6 +45,9 @@ struct vgic_v5_vm_info {
__le64 *h_lpi_ist;
__le64 **h_lpi_l2_ists;
__le64 *h_spi_ist;
+
+ /* Tracking of pending interrupts as part of IST restore */
+ struct list_head pending_irqs;
};
struct vgic_v5_vmt {
@@ -118,7 +122,19 @@ int vgic_v5_vmte_alloc_vpe(struct kvm_vcpu *vcpu);
int vgic_v5_vmte_free_vpe(struct kvm_vcpu *vcpu);
int vgic_v5_spi_ist_alloc(struct kvm *kvm, unsigned int id_bits);
+int vgic_v5_lpi_ist_exists(struct kvm *kvm);
int vgic_v5_lpi_ist_alloc(struct kvm *kvm, unsigned int id_bits);
int vgic_v5_lpi_ist_free(struct kvm *kvm);
+int vgic_v5_save_spi_ist(struct kvm *kvm,
+ struct kvm_vgic_v5_ist *ist_attr);
+int vgic_v5_save_lpi_ist(struct kvm *kvm,
+ struct kvm_vgic_v5_ist *ist_attr);
+int vgic_v5_restore_spi_ist(struct kvm *kvm,
+ struct kvm_vgic_v5_ist *ist_attr);
+int vgic_v5_restore_lpi_ist(struct kvm *kvm,
+ struct kvm_vgic_v5_ist *ist_attr);
+int vgic_v5_restore_pending_irqs(struct kvm *kvm);
+void vgic_v5_discard_pending_irqs(struct kvm *kvm);
+
#endif
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index beabc5980b2c1..1d360a25639a7 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -5,10 +5,11 @@
#include <kvm/arm_vgic.h>
-#include <linux/kvm_host.h>
#include <linux/bitops.h>
#include <linux/irqchip/arm-vgic-info.h>
#include <linux/irqdomain.h>
+#include <linux/kvm_host.h>
+#include <linux/uaccess.h>
#include "vgic-v5-tables.h"
#include "vgic.h"
@@ -221,6 +222,17 @@ static int vgic_v5_irs_wait_for_vpe_op(void)
NULL);
}
+/*
+ * Wait for a write to IRS_SAVE_VMR to complete.
+ */
+static int vgic_v5_irs_wait_for_save_vm_op(u32 *statusr)
+{
+ return gicv5_wait_for_op_atomic(irs_caps.irs_base,
+ GICV5_IRS_SAVE_VM_STATUSR,
+ GICV5_IRS_SAVE_VM_STATUSR_IDLE,
+ statusr);
+}
+
static int vgic_v5_irs_write_vm_mmio_reg(u64 val, u32 offset)
{
int ret;
@@ -389,6 +401,27 @@ static int vgic_v5_irs_set_up_vpe(u16 vm_id, u16 vpe_id,
return 0;
}
+static int vgic_v5_irs_save_vm_op(u16 vm_id, bool save, u32 *statusr)
+{
+ u64 save_vmr;
+ int ret;
+
+ save_vmr = FIELD_PREP(GICV5_IRS_SAVE_VMR_VM_ID, vm_id);
+ save_vmr |= GICV5_IRS_SAVE_VMR_Q;
+ save_vmr |= FIELD_PREP(GICV5_IRS_SAVE_VMR_S, save);
+
+ guard(raw_spinlock_irqsave)(&vgic_v5_irs_lock);
+
+ /* Make sure that we are idle to begin with. */
+ ret = vgic_v5_irs_wait_for_save_vm_op(NULL);
+ if (ret)
+ return ret;
+
+ irs_writeq_relaxed(save_vmr, GICV5_IRS_SAVE_VMR);
+
+ return vgic_v5_irs_wait_for_save_vm_op(statusr);
+}
+
static irqreturn_t db_handler(int irq, void *data)
{
struct kvm_vcpu *vcpu = data;
@@ -1099,9 +1132,9 @@ static bool vgic_v5_set_spi_pending_state(struct kvm_vcpu *vcpu,
return true;
}
-static bool vgic_v5_spi_queue_irq_unlock(struct kvm *kvm,
- struct vgic_irq *irq,
- unsigned long flags)
+bool vgic_v5_spi_queue_irq_unlock(struct kvm *kvm,
+ struct vgic_irq *irq,
+ unsigned long flags)
__releases(&irq->irq_lock)
{
lockdep_assert_held(&irq->irq_lock);
@@ -1267,3 +1300,292 @@ void vgic_v5_save_state(struct kvm_vcpu *vcpu)
__vgic_v5_save_ppi_state(cpu_if);
dsb(sy);
}
+
+static int vgic_v5_irs_status_is_quiesced(u32 statusr)
+{
+ if (statusr & GICV5_IRS_SAVE_VM_STATUSR_Q)
+ return 0;
+
+ return -EBUSY;
+}
+
+static int vgic_v5_irs_is_quiesced(u16 vm_id)
+{
+ u32 statusr;
+ int ret;
+
+ ret = vgic_v5_irs_save_vm_op(vm_id, false, &statusr);
+ if (ret)
+ return ret;
+
+ return vgic_v5_irs_status_is_quiesced(statusr);
+}
+
+static int vgic_v5_copy_ist_attr(struct kvm_device_attr *attr,
+ struct kvm_vgic_v5_ist *ist_attr)
+{
+ void __user *uaddr = (void __user *)(unsigned long)attr->addr;
+
+ if (!uaddr)
+ return -EINVAL;
+
+ if (copy_from_user(ist_attr, uaddr, sizeof(*ist_attr)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int vgic_v5_validate_ist_user_buffer(__u64 addr, __u64 size,
+ size_t expected)
+{
+ if (!addr || size != expected)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int vgic_v5_validate_ist_attr(struct kvm *kvm,
+ const struct kvm_vgic_v5_ist *ist_attr)
+{
+ unsigned int id_bits;
+ int ret;
+
+ /* We always have SPIs to save */
+ ret = vgic_v5_validate_ist_user_buffer(ist_attr->spi_ist_addr,
+ ist_attr->spi_ist_size,
+ kvm->arch.vgic.nr_spis * sizeof(__u32));
+ if (ret)
+ return ret;
+
+ /* We don't always have LPIs to save */
+ ret = vgic_v5_irs_lpi_ist_id_bits(kvm, &id_bits);
+ if (ret < 0)
+ return ret;
+
+ /* No LPI IST */
+ if (!ret) {
+ if (ist_attr->lpi_ist_addr || ist_attr->lpi_ist_size)
+ return -EINVAL;
+
+ return 0;
+ }
+
+ return vgic_v5_validate_ist_user_buffer(ist_attr->lpi_ist_addr,
+ ist_attr->lpi_ist_size,
+ BIT(id_bits) * sizeof(__u32));
+}
+
+int vgic_v5_irs_save_ists(struct kvm *kvm, struct kvm_device_attr *attr)
+{
+ struct kvm_vgic_v5_ist ist_attr;
+ u16 vm_id = vgic_v5_vm_id(kvm);
+ u32 statusr;
+ int ret = 0;
+
+ mutex_lock(&kvm->lock);
+
+ if (kvm_trylock_all_vcpus(kvm)) {
+ mutex_unlock(&kvm->lock);
+ return -EBUSY;
+ }
+
+ mutex_lock(&kvm->arch.config_lock);
+
+ if (!vgic_initialized(kvm)) {
+ ret = -EBUSY;
+ goto out_unlock;
+ }
+
+ ret = vgic_v5_copy_ist_attr(attr, &ist_attr);
+ if (ret)
+ goto out_unlock;
+
+ ret = vgic_v5_validate_ist_attr(kvm, &ist_attr);
+ if (ret)
+ goto out_unlock;
+
+ ret = vgic_v5_irs_save_vm_op(vm_id, true, &statusr);
+ if (ret) {
+ kvm_err("Failed to save GICv5 IRS VM state: %d\n", ret);
+ goto out_unlock;
+ }
+
+ ret = vgic_v5_irs_status_is_quiesced(statusr);
+ if (ret)
+ goto out_unlock;
+
+ /* Save the SPI IST to the userspace buffer. */
+ ret = vgic_v5_save_spi_ist(kvm, &ist_attr);
+ if (ret)
+ goto out_unlock;
+
+ ret = vgic_v5_irs_is_quiesced(vm_id);
+ if (ret)
+ goto out_unlock;
+
+ /* Save the LPI IST to the userspace buffer. */
+ ret = vgic_v5_save_lpi_ist(kvm, &ist_attr);
+ if (ret)
+ goto out_unlock;
+
+ ret = vgic_v5_irs_is_quiesced(vm_id);
+ if (ret)
+ goto out_unlock;
+
+out_unlock:
+ mutex_unlock(&kvm->arch.config_lock);
+ kvm_unlock_all_vcpus(kvm);
+ mutex_unlock(&kvm->lock);
+
+ return ret;
+}
+
+/* Allocate the LPI IST to restore into */
+static int vgic_v5_restore_lpi_ist_alloc(struct kvm *kvm, bool *allocated)
+{
+ unsigned int id_bits;
+ int ret;
+
+ *allocated = false;
+
+ ret = vgic_v5_irs_lpi_ist_id_bits(kvm, &id_bits);
+ if (ret <= 0)
+ return ret;
+
+ ret = vgic_v5_lpi_ist_alloc(kvm, id_bits);
+ if (ret)
+ return ret;
+
+ *allocated = true;
+
+ return 0;
+}
+
+/*
+ * Clean up the LPI IST if we allocated it, and restore the VMTE to the
+ * original, valid state.
+ */
+static void vgic_v5_restore_cleanup(struct kvm *kvm,
+ struct kvm_vcpu *vcpu,
+ bool lpi_ist_allocated)
+{
+ /*
+ * We are on the restore failure path, so we do a best-effort
+ * cleanup. These commands might fail, but at this stage this is the
+ * best we can realistically do.
+ */
+ if (lpi_ist_allocated) {
+ if (!vgic_v5_send_command(vcpu, VMTE_MAKE_INVALID))
+ vgic_v5_lpi_ist_free(kvm);
+ }
+
+ vgic_v5_send_command(vcpu, VMTE_MAKE_VALID);
+}
+
+int vgic_v5_irs_restore_ists(struct kvm *kvm, struct kvm_device_attr *attr)
+{
+ bool lpi_ist_allocated = false, vmte_invalid = false;
+ struct kvm_vcpu *vcpu0 = kvm_get_vcpu(kvm, 0);
+ struct kvm_vgic_v5_ist ist_attr;
+ int ret = 0;
+
+ mutex_lock(&kvm->lock);
+
+ if (kvm_trylock_all_vcpus(kvm)) {
+ mutex_unlock(&kvm->lock);
+ return -EBUSY;
+ }
+
+ mutex_lock(&kvm->arch.config_lock);
+
+ if (!vgic_initialized(kvm)) {
+ ret = -EBUSY;
+ goto out_unlock;
+ }
+
+ if (kvm_vm_has_ran_once(kvm)) {
+ ret = -EBUSY;
+ goto out_unlock;
+ }
+
+ ret = vgic_v5_copy_ist_attr(attr, &ist_attr);
+ if (ret)
+ goto out_unlock;
+
+ ret = vgic_v5_validate_ist_attr(kvm, &ist_attr);
+ if (ret)
+ goto out_unlock;
+
+ ret = vgic_v5_lpi_ist_exists(kvm);
+ if (ret) {
+ if (ret > 0)
+ ret = -EBUSY;
+ goto out_unlock;
+ }
+
+ /*
+ * If the guest has previously allocated an IST (which we check based on
+ * the IRS_IST_BASER), extract the number of LPI ID bits from the
+ * IRS_IST_CFGR. Else, do nothing.
+ *
+ * We do this before making the VMTE invalid as we rely on
+ * IRS_VMAP_VISTR to mark the IST as valid in the VMTE. This can only
+ * happen while the VMTE is valid.
+ */
+ ret = vgic_v5_restore_lpi_ist_alloc(kvm, &lpi_ist_allocated);
+ if (ret)
+ goto out_unlock;
+
+ /*
+ * Host ISTs are updated while the VMTE is invalid, so the GIC cannot
+ * observe partially restored state.
+ */
+ ret = vgic_v5_send_command(vcpu0, VMTE_MAKE_INVALID);
+ if (ret) {
+ /*
+ * If invalidation fails, the restore cannot safely update host
+ * IST state.
+ */
+ goto out_unlock;
+ }
+ vmte_invalid = true;
+
+ /* Restore the SPI IST from the userspace buffer. */
+ ret = vgic_v5_restore_spi_ist(kvm, &ist_attr);
+ if (ret)
+ goto out_unlock;
+
+ /* Restore the LPI IST from the userspace buffer. */
+ if (lpi_ist_allocated) {
+ ret = vgic_v5_restore_lpi_ist(kvm, &ist_attr);
+ if (ret)
+ goto out_unlock;
+ }
+
+ /* And make the VM Valid again */
+ ret = vgic_v5_send_command(vcpu0, VMTE_MAKE_VALID);
+ if (ret)
+ goto out_unlock;
+ vmte_invalid = false;
+
+ /*
+ * As part of restoring the ISTs, and previously pending interrupts have
+ * been tracked and made non-pending. Now that the ISTs have been
+ * restored, and the VM is valid again, restore the pending interrupts.
+ */
+ ret = vgic_v5_restore_pending_irqs(kvm);
+ if (ret)
+ goto out_unlock;
+
+out_unlock:
+ if (ret && (vmte_invalid || lpi_ist_allocated)) {
+ vgic_v5_discard_pending_irqs(kvm);
+ vgic_v5_restore_cleanup(kvm, vcpu0, lpi_ist_allocated);
+ }
+
+ mutex_unlock(&kvm->arch.config_lock);
+ kvm_unlock_all_vcpus(kvm);
+ mutex_unlock(&kvm->lock);
+
+ return ret;
+}
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 7536e9a13086e..cb673da96ec55 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -373,6 +373,8 @@ 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);
void vgic_v5_set_spi_ops(struct vgic_irq *irq);
+bool vgic_v5_spi_queue_irq_unlock(struct kvm *kvm, struct vgic_irq *irq,
+ unsigned long flags);
void vgic_v5_set_irq_pend(struct kvm_vcpu *vcpu, struct vgic_irq *irq);
bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu);
void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu);
@@ -384,11 +386,14 @@ void vgic_v5_get_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcr);
void vgic_v5_restore_state(struct kvm_vcpu *vcpu);
void vgic_v5_save_state(struct kvm_vcpu *vcpu);
int vgic_v5_register_irs_iodev(struct kvm *kvm, gpa_t irs_base_address);
+int vgic_v5_irs_lpi_ist_id_bits(struct kvm *kvm, unsigned int *id_bits);
int vgic_v5_cpu_sysregs_uaccess(struct kvm_vcpu *vcpu,
struct kvm_device_attr *attr, bool is_write);
int vgic_v5_has_cpu_sysregs_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
const struct sys_reg_desc *vgic_v5_get_sysreg_table(unsigned int *sz);
+int vgic_v5_irs_save_ists(struct kvm *kvm, struct kvm_device_attr *attr);
+int vgic_v5_irs_restore_ists(struct kvm *kvm, struct kvm_device_attr *attr);
int vgic_v5_irs_attr_regs_access(struct kvm_device *dev,
struct kvm_device_attr *attr,
u64 *reg, bool is_write);
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 36/49] Documentation: KVM: Document KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS for VGICv5
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (34 preceding siblings ...)
2026-08-07 11:30 ` [PATCH v5 35/49] KVM: arm64: gic-v5: Implement save/restore mechanisms for ISTs Sascha Bischoff
@ 2026-08-07 11:30 ` Sascha Bischoff
2026-08-07 11:31 ` [PATCH v5 37/49] Documentation: KVM: Add KVM_DEV_ARM_VGIC_GRP_IRS_REGS to VGICv5 docs Sascha Bischoff
` (12 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:30 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
The virtual GICv5 adopts the same mechanism as GICv3 for userspace
reads and writes of the system registers, albeit operating on a
different set of registers, of course.
Document KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS for GICv5 in the VGICv5
documentation, explicitly calling out the registers it operates
on. The main body of documentation has been directly copied from the
VGICv3 documentation as it has identical operation.
One key thing to note is that for two sets of GICv5 registers - those
pertaining to Active and Pending state - the operation of the
interface is different to how the actual registers operate. Both of
these registers have C and S variants (to set and clear bits) in
hardware. However for this interface, we ONLY implement the S variant,
AND treat it as a raw write. This simplifies the act of reading or
writing the state.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
.../virt/kvm/devices/arm-vgic-v5.rst | 74 +++++++++++++++++++
1 file changed, 74 insertions(+)
diff --git a/Documentation/virt/kvm/devices/arm-vgic-v5.rst b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
index c782ea4d4609c..7e41d6699538f 100644
--- a/Documentation/virt/kvm/devices/arm-vgic-v5.rst
+++ b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
@@ -75,3 +75,77 @@ Groups:
-EFAULT Invalid guest ram access
-EBUSY One or more VCPUS are running
======= ========================================================
+
+ KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS
+ Attributes:
+
+ The attr field of kvm_device_attr encodes two values::
+
+ bits: | 63 .... 32 | 31 .... 16 | 15 .... 0 |
+ values: | mpidr | RES | instr |
+
+ The mpidr field encodes the CPU ID based on the affinity information in the
+ architecture defined MPIDR, and the field is encoded as follows::
+
+ | 63 .... 56 | 55 .... 48 | 47 .... 40 | 39 .... 32 |
+ | Aff3 | Aff2 | Aff1 | Aff0 |
+
+ The instr field encodes the system register to access based on the fields
+ defined in the A64 instruction set encoding for system register access
+ (RES means the bits are reserved for future use and should be zero)::
+
+ | 15 ... 14 | 13 ... 11 | 10 ... 7 | 6 ... 3 | 2 ... 0 |
+ | Op 0 | Op1 | CRn | CRm | Op2 |
+
+ All system regs accessed through this API are (rw, 64-bit) and
+ kvm_device_attr.addr points to a __u64 value.
+
+ KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS accesses the CPU interface registers for the
+ CPU specified by the mpidr field.
+
+ The available registers are:
+
+ ======================= ===================================================
+ ICC_ICSR_EL1
+ ICC_PPI_ENABLER0_EL1
+ ICC_PPI_ENABLER1_EL1
+ ICC_PPI_SACTIVER0_EL1 ICC_PPI_CACTIVER0_EL1 is not supported. Writes to
+ ICC_PPI_SACTIVER0_EL1 are treated as RAW writes of
+ the underlying state.
+ ICC_PPI_SACTIVER1_EL1 ICC_PPI_CACTIVER1_EL1 is not supported. Writes to
+ ICC_PPI_SACTIVER1_EL1 are treated as RAW writes of
+ the underlying state.
+ ICC_PPI_SPENDR0_EL1 ICC_PPI_CPENDR0_EL1 is not supported. Writes to
+ ICC_PPI_SPENDR0_EL1 are treated as RAW writes of
+ the underlying state.
+ ICC_PPI_SPENDR1_EL1 ICC_PPI_CPENDR1_EL1 is not supported. Writes to
+ ICC_PPI_SPENDR1_EL1 are treated as RAW writes of
+ the underlying state.
+ ICC_PPI_PRIORITYR0_EL1
+ ICC_PPI_PRIORITYR1_EL1
+ ICC_PPI_PRIORITYR2_EL1
+ ICC_PPI_PRIORITYR3_EL1
+ ICC_PPI_PRIORITYR4_EL1
+ ICC_PPI_PRIORITYR5_EL1
+ ICC_PPI_PRIORITYR6_EL1
+ ICC_PPI_PRIORITYR7_EL1
+ ICC_PPI_PRIORITYR8_EL1
+ ICC_PPI_PRIORITYR9_EL1
+ ICC_PPI_PRIORITYR10_EL1
+ ICC_PPI_PRIORITYR11_EL1
+ ICC_PPI_PRIORITYR12_EL1
+ ICC_PPI_PRIORITYR13_EL1
+ ICC_PPI_PRIORITYR14_EL1
+ ICC_PPI_PRIORITYR15_EL1
+ ICC_APR_EL1
+ ICC_CR0_EL1
+ ICC_PCR_EL1
+ ======================= ===================================================
+
+ Errors:
+
+ ======= =============================================================
+ -ENXIO Getting or setting this register is not supported
+ -EBUSY VCPU is running, or write attempted after a VCPU has run
+ -EINVAL Invalid mpidr or register value supplied
+ ======= =============================================================
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 37/49] Documentation: KVM: Add KVM_DEV_ARM_VGIC_GRP_IRS_REGS to VGICv5 docs
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (35 preceding siblings ...)
2026-08-07 11:30 ` [PATCH v5 36/49] Documentation: KVM: Document KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS for VGICv5 Sascha Bischoff
@ 2026-08-07 11:31 ` Sascha Bischoff
2026-08-07 11:31 ` [PATCH v5 38/49] Documentation: KVM: Add docs for KVM_DEV_ARM_VGIC_GRP_IST Sascha Bischoff
` (11 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:31 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Document the KVM_DEV_ARM_VGIC_GRP_IRS_REGS attribute group used to
read and write the virtual IRS's MMIO register state. This provides a
GICv5-specific interface for state that is conceptually similar to the
VGICv3 ITS register interface, but uses IRS terminology instead of
ITS.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
.../virt/kvm/devices/arm-vgic-v5.rst | 47 +++++++++++++++++++
1 file changed, 47 insertions(+)
diff --git a/Documentation/virt/kvm/devices/arm-vgic-v5.rst b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
index 7e41d6699538f..6dd3ff555ba02 100644
--- a/Documentation/virt/kvm/devices/arm-vgic-v5.rst
+++ b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
@@ -149,3 +149,50 @@ Groups:
-EBUSY VCPU is running, or write attempted after a VCPU has run
-EINVAL Invalid mpidr or register value supplied
======= =============================================================
+
+
+KVM_DEV_ARM_VGIC_GRP_IRS_REGS
+ Attributes:
+ The attr field of kvm_device_attr encodes the offset of the IRS register,
+ relative to the IRS CONFIG_FRAME base address. This is the address that
+ was provided via KVM_VGIC_V5_ADDR_TYPE_IRS when creating VGICv5 in the
+ first place.
+
+ kvm_device_attr.addr points to a __u64 value whatever the width
+ of the addressed register (32/64 bits). 64 bit registers can only
+ be accessed with full length.
+
+ Writes to read-only registers are ignored by the kernel except for
+ IRS_IDR0 - IRS_IDR2 and IRS_IDR5 - IRS_IDR7, which are sanity checked to
+ ensure that they match a sane config.
+
+ IRS_IDR3 and IRS_IDR4 are RAZ/WI, as nested virtualization is not
+ supported.
+
+ For registers without dedicated userspace accessors, getting or setting a
+ register uses the same emulated MMIO handlers as guest reads/writes.
+ Dedicated userspace accessors may instead save or restore migration state
+ without triggering guest-visible side effects. For example, restoring
+ IRS_IST_BASER only restores the emulated register state; any host LPI IST
+ allocation based on the restored IRS_IST_CFGR and IRS_IST_BASER state
+ happens when KVM_DEV_ARM_VGIC_GRP_IST is restored.
+
+ Once an LPI IST has been allocated, IRS_IST_CFGR is immutable. Userspace
+ may write back its current value, but setting a different value is
+ rejected with -EINVAL.
+
+ Once an LPI IST has been allocated, IRS_IST_BASER is immutable.
+ Userspace may write back its current value, but setting a different value
+ is rejected with -EINVAL.
+
+ Errors:
+
+ ======= =================================================================
+ -ENXIO Offset does not correspond to any supported register
+ -EFAULT Invalid user pointer for attr->addr
+ -EINVAL Offset is not 32-bit aligned for 32-bit MMIO registers, not
+ 64-bit aligned for 64-bit registers, or the supplied register
+ value is not compatible with the configured VGICv5 IRS state
+ -EBUSY VGIC is not initialized, one or more VCPUs are running, or a
+ write is attempted after a VCPU has run
+ ======= =================================================================
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 38/49] Documentation: KVM: Add docs for KVM_DEV_ARM_VGIC_GRP_IST
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (36 preceding siblings ...)
2026-08-07 11:31 ` [PATCH v5 37/49] Documentation: KVM: Add KVM_DEV_ARM_VGIC_GRP_IRS_REGS to VGICv5 docs Sascha Bischoff
@ 2026-08-07 11:31 ` Sascha Bischoff
2026-08-07 11:32 ` [PATCH v5 39/49] Documentation: KVM: Add the VGICv5 IRS save/restore sequences Sascha Bischoff
` (10 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:31 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Document the IST save/restore userspace interface for the VGICv5
device, KVM_DEV_ARM_VGIC_GRP_IST.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
.../virt/kvm/devices/arm-vgic-v5.rst | 66 +++++++++++++++++++
1 file changed, 66 insertions(+)
diff --git a/Documentation/virt/kvm/devices/arm-vgic-v5.rst b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
index 6dd3ff555ba02..fc400f4fbb65b 100644
--- a/Documentation/virt/kvm/devices/arm-vgic-v5.rst
+++ b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
@@ -196,3 +196,69 @@ KVM_DEV_ARM_VGIC_GRP_IRS_REGS
-EBUSY VGIC is not initialized, one or more VCPUs are running, or a
write is attempted after a VCPU has run
======= =================================================================
+
+ KVM_DEV_ARM_VGIC_GRP_IST
+ Attributes:
+ This interface is used to either save the state of the IRS's Interrupt
+ State Tables (ISTs), or to restore them. A get operation saves IST state,
+ and a set operation restores IST state. kvm_device_attr.attr is reserved
+ and must be zero.
+
+ The VGIC must be initialized before using this interface. Restore must be
+ performed before the VM has run. For restore, userspace must have already
+ restored the IRS state needed to describe any guest LPI IST.
+
+ Saving first asks the IRS to save and quiesce the VM so that interrupt
+ state has been written back to the ISTs. KVM checks that the VM remains
+ quiesced while copying out the SPI and LPI IST state.
+
+ kvm_device_attr.addr points to a struct kvm_vgic_v5_ist::
+
+ struct kvm_vgic_v5_ist {
+ __u64 spi_ist_addr;
+ __u64 spi_ist_size;
+ __u64 lpi_ist_addr;
+ __u64 lpi_ist_size;
+ };
+
+ The SPI and LPI IST buffers contain one little-endian 32-bit IST entry per
+ interrupt, in interrupt number order. Only the architected 32-bit ISTE
+ state is exposed to userspace; host IST layout and metadata are private to
+ KVM.
+
+ The SPI IST buffer is required and its size must be:
+
+ nr_spis * sizeof(__u32)
+
+ where nr_spis is the value returned by KVM_DEV_ARM_VGIC_GRP_NR_IRQS for
+ the VGICv5 device. For VGICv5 this value is the number of SPIs, not the
+ total number of interrupts.
+
+ The LPI IST buffer is required if the guest's IRS_IST_CFGR and
+ IRS_IST_BASER state describes a valid LPI IST, and its size must be:
+
+ BIT(lpi_id_bits) * sizeof(__u32)
+
+ where lpi_id_bits is the LPI ID width configured in IRS_IST_CFGR. If no
+ LPI IST is configured, lpi_ist_addr and lpi_ist_size must both be zero.
+
+ Errors:
+
+ =========== ============================================================
+ -EBUSY One or more VCPUs are running, the VGIC is not initialized,
+ restore was requested after the VM has run, an LPI IST
+ already exists, or the save operation completed but the VM
+ did not remain quiesced
+ -EINVAL attr->addr is NULL, the userspace IST descriptor is missing
+ a required buffer, supplies a non-zero LPI buffer when no
+ LPI IST is configured, has a buffer size that does not match
+ the configured VGICv5 IST state, or an internal VM table
+ operation rejected the VM state
+ -EFAULT Invalid user pointer for attr->addr, spi_ist_addr, or
+ lpi_ist_addr
+ -ENXIO Required per-VM VGICv5/IST backing state is missing or
+ inconsistent
+ -ENOMEM Restoring IST state failed while allocating the host LPI IST
+ or tracking pending interrupts
+ -ETIMEDOUT An IRS save/VM operation timed out
+ =========== ============================================================
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 39/49] Documentation: KVM: Add the VGICv5 IRS save/restore sequences
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (37 preceding siblings ...)
2026-08-07 11:31 ` [PATCH v5 38/49] Documentation: KVM: Add docs for KVM_DEV_ARM_VGIC_GRP_IST Sascha Bischoff
@ 2026-08-07 11:32 ` Sascha Bischoff
2026-08-07 11:32 ` [PATCH v5 40/49] KVM: selftests: Add VGICv5 IRS address attribute tests Sascha Bischoff
` (9 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:32 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Document the ordering requirements for saving and restoring the GICv5
IRS state.
The IST state is exposed through KVM_DEV_ARM_VGIC_GRP_IST and is
copied to or from userspace-provided buffers. Saving the ISTs
therefore does not update guest memory, so userspace does not need to
serialize guest memory after saving the IST state.
On restore, userspace must create the vCPUs, configure the IRS
address, restore the number of SPIs, and initialize the VGIC before
restoring IRS state. The IRS MMIO registers may be restored in any
order, but the final IRS_IDR* state and the IRS_IST_CFGR/IRS_IST_BASER
state that describes any LPI IST must be restored before
KVM_DEV_ARM_VGIC_GRP_IST. KVM uses that state to validate the
userspace IST buffers and allocate any host LPI IST.
The IRS_*_STATUSR registers are observational state in KVM. Userspace
may save them for validation or debugging, but they are not required
as restore input and do not need to be replayed.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
.../virt/kvm/devices/arm-vgic-v5.rst | 49 +++++++++++++++++++
1 file changed, 49 insertions(+)
diff --git a/Documentation/virt/kvm/devices/arm-vgic-v5.rst b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
index fc400f4fbb65b..45ad25604dfa9 100644
--- a/Documentation/virt/kvm/devices/arm-vgic-v5.rst
+++ b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
@@ -262,3 +262,52 @@ KVM_DEV_ARM_VGIC_GRP_IRS_REGS
or tracking pending interrupts
-ETIMEDOUT An IRS save/VM operation timed out
=========== ============================================================
+
+IRS Save Sequence:
+------------------
+
+The following operations are required when saving the virtual GICv5 IRS:
+
+a) Save the ISTs by issuing KVM_GET_DEVICE_ATTR on KVM_DEV_ARM_VGIC_GRP_IST.
+b) Save the IRS MMIO register state by issuing KVM_GET_DEVICE_ATTR on
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS.
+
+These two steps may be performed in either order. Saving the ISTs writes all
+IST state to userspace-provided buffers and does not update guest memory.
+
+IRS Restore Sequence:
+---------------------
+
+The following ordering must be followed when restoring the virtual GICv5 and
+IRS:
+
+a) Create vCPUs.
+b) Provide the IRS base address by issuing KVM_SET_DEVICE_ATTR on
+ KVM_DEV_ARM_VGIC_GRP_ADDR
+c) Restore the number of SPIs by issuing KVM_SET_DEVICE_ATTR on
+ KVM_DEV_ARM_VGIC_GRP_NR_IRQS.
+d) Initialise the GIC - this sets up the default state and creates the SPI
+ IST - by issuing KVM_SET_DEVICE_ATTR on KVM_DEV_ARM_VGIC_GRP_CTRL with
+ KVM_DEV_ARM_VGIC_CTRL_INIT
+e) Restore the IRS MMIO register state by issuing KVM_SET_DEVICE_ATTR on
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS. The IRS registers may be restored in any
+ order, but any IRS_IDR* state and the IRS_IST_CFGR/IRS_IST_BASER state
+ describing an LPI IST must be restored before restoring the ISTs. KVM uses
+ the restored IRS_IST_CFGR and IRS_IST_BASER state to allocate the LPI IST
+ during the following step.
+f) Restore the ISTs by issuing KVM_SET_DEVICE_ATTR on
+ KVM_DEV_ARM_VGIC_GRP_IST.
+
+The number of SPIs must be restored before VGIC initialization because
+initialization allocates the SPI state and fixes the SPI range exposed by the
+IRS ID registers.
+
+The IRS's MMIO registers must be restored prior to restoring the ISTs as these
+are used to convey the number of LPIs the guest has configured to KVM.
+
+The various ``*_STATUSR`` registers are observational state in the current KVM
+implementation. Userspace may save them for validation or debugging purposes,
+but they are not required as restore input and do not need to be replayed during
+restore.
+
+Then vCPUs can be started.
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 40/49] KVM: selftests: Add VGICv5 IRS address attribute tests
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (38 preceding siblings ...)
2026-08-07 11:32 ` [PATCH v5 39/49] Documentation: KVM: Add the VGICv5 IRS save/restore sequences Sascha Bischoff
@ 2026-08-07 11:32 ` Sascha Bischoff
2026-08-07 11:33 ` [PATCH v5 41/49] KVM: selftests: Add VGICv5 NR_IRQS " Sascha Bischoff
` (8 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:32 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Add coverage for the VGICv5 IRS address attribute. The address is the
base of the guest-visible MMIO region used to configure the virtual
IRS, so userspace must provide a valid base address before the VM can
run.
Verify that KVM reports the attribute, returns VGIC_ADDR_UNDEF before
it is configured, rejects misaligned and out-of-IPA-range addresses,
accepts a valid base, rejects a second base, and fails vCPU entry if
the VGIC is initialized without an IRS address.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
tools/testing/selftests/kvm/arm64/vgic_v5.c | 121 ++++++++++++++++--
.../selftests/kvm/include/arm64/gic_v5.h | 3 +
2 files changed, 110 insertions(+), 14 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
index 19039a8940568..51ea2fe7141ad 100644
--- a/tools/testing/selftests/kvm/arm64/vgic_v5.c
+++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
@@ -14,6 +14,8 @@
#define NR_VCPUS 1
+static u64 max_phys_size;
+
struct vm_gic {
struct kvm_vm *vm;
int gic_fd;
@@ -25,6 +27,104 @@ struct vm_gic {
#define GUEST_CMD_IS_AWAKE 12
#define GUEST_CMD_IS_READY 13
+/* we don't want to assert on run execution, hence that helper */
+static int run_vcpu(struct kvm_vcpu *vcpu)
+{
+ return __vcpu_run(vcpu) ? -errno : 0;
+}
+
+static void vm_gic_destroy(struct vm_gic *v)
+{
+ close(v->gic_fd);
+ kvm_vm_free(v->vm);
+}
+
+struct vgic_region_attr {
+ u64 attr;
+ u64 size;
+ u64 alignment;
+};
+
+static const struct vgic_region_attr gic_v5_irs_region = {
+ .attr = KVM_VGIC_V5_ADDR_TYPE_IRS,
+ .size = GICV5_IRS_SIZE,
+ .alignment = GICV5_IRS_ALIGN,
+};
+
+static void test_vgic_v5_create(void)
+{
+ struct kvm_vcpu *vcpu;
+ struct vm_gic v;
+ u64 addr;
+ int ret;
+
+ v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
+ v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
+
+ /* Check existing group/attributes */
+ kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR, gic_v5_irs_region.attr);
+
+ /* check non existing attribute */
+ ret = __kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR, -1);
+ TEST_ASSERT(ret && errno == ENXIO, "attribute not supported");
+
+ /* get IRS base address before setting*/
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &addr);
+ TEST_ASSERT(!ret && addr == (-1ULL), "GICv5 IRS returns VGIC_ADDR_UNDEF");
+
+ /* misaligned IRS address settings */
+ addr = gic_v5_irs_region.alignment / 0x10;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &addr);
+ TEST_ASSERT(ret && errno == EINVAL, "GIC IRS base not aligned");
+
+ /* out of range address */
+ addr = max_phys_size;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &addr);
+ TEST_ASSERT(ret && errno == E2BIG, "IRS address beyond IPA limit");
+
+ /* Space for half an IRS (an IRS is: 2 * irs.alignment). */
+ addr = max_phys_size - gic_v5_irs_region.alignment;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &addr);
+ TEST_ASSERT(ret && errno == E2BIG,
+ "half of the IRS is beyond IPA limit");
+
+ /* set IRS base address @0x0*/
+ addr = 0x00000;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &addr);
+ TEST_ASSERT(!ret, "GICv5 IRS base correctly set");
+
+ /* get IRS base address */
+ addr = 0xbad;
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &addr);
+ TEST_ASSERT(!ret && addr == 0, "GICv5 IRS base correctly set");
+
+ /* Attempt to create a second IRS region */
+ addr = 0xE0000;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &addr);
+ TEST_ASSERT(ret && errno == EEXIST, "GICv5 IRS base set again");
+
+ vm_gic_destroy(&v);
+
+ /* Try running a VM without ever setting the IRS base addr */
+ 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);
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+ ret = run_vcpu(vcpu);
+ TEST_ASSERT(ret && errno == ENXIO, "GICv5 IRS base not set");
+
+ vm_gic_destroy(&v);
+}
+
static void guest_irq_handler(struct ex_regs *regs)
{
bool valid;
@@ -81,19 +181,6 @@ static void guest_code(void)
while (1);
}
-
-/* we don't want to assert on run execution, hence that helper */
-static int run_vcpu(struct kvm_vcpu *vcpu)
-{
- return __vcpu_run(vcpu) ? -errno : 0;
-}
-
-static void vm_gic_destroy(struct vm_gic *v)
-{
- close(v->gic_fd);
- kvm_vm_free(v->vm);
-}
-
static void test_vgic_v5_ppis(u32 gic_dev_type)
{
struct kvm_vcpu *vcpus[NR_VCPUS];
@@ -207,13 +294,19 @@ int test_kvm_device(u32 gic_dev_type)
void run_tests(u32 gic_dev_type)
{
+ pr_info("Test VGICv5 Creation & Setup\n");
+ test_vgic_v5_create();
+
pr_info("Test VGICv5 PPIs\n");
test_vgic_v5_ppis(gic_dev_type);
}
int main(int ac, char **av)
{
- int ret;
+ int pa_bits, ret;
+
+ pa_bits = vm_guest_mode_params[VM_MODE_DEFAULT].pa_bits;
+ max_phys_size = 1ULL << pa_bits;
test_disable_default_vgic();
diff --git a/tools/testing/selftests/kvm/include/arm64/gic_v5.h b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
index c388df8f2a2b4..41b698b3952f3 100644
--- a/tools/testing/selftests/kvm/include/arm64/gic_v5.h
+++ b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
@@ -13,6 +13,9 @@
/* GIC component base address is guest PA space */
#define GICV5_IRS_CONFIG_BASE_GPA 0x8000000ULL
+#define GICV5_IRS_SIZE 0x20000
+#define GICV5_IRS_ALIGN 0x10000
+
/*
* Definitions for GICv5 instructions for the Current Domain
*/
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 41/49] KVM: selftests: Add VGICv5 NR_IRQS attribute tests
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (39 preceding siblings ...)
2026-08-07 11:32 ` [PATCH v5 40/49] KVM: selftests: Add VGICv5 IRS address attribute tests Sascha Bischoff
@ 2026-08-07 11:33 ` Sascha Bischoff
2026-08-07 11:33 ` [PATCH v5 42/49] KVM: selftests: Add VGICv5 IRS_REGS " Sascha Bischoff
` (7 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:33 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Add coverage for the VGICv5 NR_IRQS attribute. VGICv5 exposes a
userspace-selectable SPI count, and KVM must reject values outside its
supported range or values that would change the interrupt layout after
it has been fixed.
Verify that the attribute defaults to zero before initialisation,
rejects too few SPIs, non-32-aligned counts, and a 32-aligned count
beyond the 1024-SPI maximum. Also verify that the maximum count is
accepted and can be initialised, that the selected count is reported,
that initialisation selects the default count, and that subsequent
changes are rejected.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
tools/testing/selftests/kvm/arm64/vgic_v5.c | 190 +++++++++++++++-----
1 file changed, 142 insertions(+), 48 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
index 51ea2fe7141ad..f4c0d2c003bb4 100644
--- a/tools/testing/selftests/kvm/arm64/vgic_v5.c
+++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
@@ -13,6 +13,8 @@
#include "vgic.h"
#define NR_VCPUS 1
+#define VGIC_V5_DEFAULT_NR_SPIS 32
+#define VGIC_V5_MAX_NR_SPIS BIT(16)
static u64 max_phys_size;
@@ -27,6 +29,63 @@ struct vm_gic {
#define GUEST_CMD_IS_AWAKE 12
#define GUEST_CMD_IS_READY 13
+static void guest_irq_handler(struct ex_regs *regs)
+{
+ bool valid;
+ u32 hwirq;
+ u64 ia;
+ static int count;
+
+ /*
+ * We have pending interrupts. Should never actually enter WFI
+ * here!
+ */
+ wfi();
+ GUEST_SYNC(GUEST_CMD_IS_AWAKE);
+
+ ia = gicr_insn(CDIA);
+ valid = GICV5_GICR_CDIA_VALID(ia);
+
+ GUEST_SYNC(GUEST_CMD_IRQ_CDIA);
+
+ if (!valid)
+ return;
+
+ gsb_ack();
+ isb();
+
+ hwirq = FIELD_GET(GICV5_GICR_CDIA_INTID, ia);
+
+ gic_insn(hwirq, CDDI);
+ gic_insn(0, CDEOI);
+
+ GUEST_SYNC(GUEST_CMD_IRQ_DIEOI);
+
+ if (++count >= 2)
+ GUEST_DONE();
+
+ /* Ask for the next interrupt to be injected */
+ GUEST_SYNC(GUEST_CMD_IS_READY);
+}
+
+static void guest_code(void)
+{
+ local_irq_disable();
+
+ gicv5_cpu_enable_interrupts();
+ local_irq_enable();
+
+ /* Enable the SW_PPI (3) */
+ write_sysreg_s(BIT_ULL(3), SYS_ICC_PPI_ENABLER0_EL1);
+
+ /* Ask for the first interrupt to be injected */
+ GUEST_SYNC(GUEST_CMD_IS_READY);
+
+ /* Loop forever waiting for interrupts */
+ for (;;)
+ cpu_relax();
+}
+
/* we don't want to assert on run execution, hence that helper */
static int run_vcpu(struct kvm_vcpu *vcpu)
{
@@ -51,7 +110,7 @@ static const struct vgic_region_attr gic_v5_irs_region = {
.alignment = GICV5_IRS_ALIGN,
};
-static void test_vgic_v5_create(void)
+static void test_vgic_v5_addr_attrs(void)
{
struct kvm_vcpu *vcpu;
struct vm_gic v;
@@ -125,60 +184,92 @@ static void test_vgic_v5_create(void)
vm_gic_destroy(&v);
}
-static void guest_irq_handler(struct ex_regs *regs)
+static void test_vgic_v5_nr_irqs_attrs(void)
{
- bool valid;
- u32 hwirq;
- u64 ia;
- static int count;
-
- /*
- * We have pending interrupts. Should never actually enter WFI
- * here!
- */
- wfi();
- GUEST_SYNC(GUEST_CMD_IS_AWAKE);
-
- ia = gicr_insn(CDIA);
- valid = GICV5_GICR_CDIA_VALID(ia);
-
- GUEST_SYNC(GUEST_CMD_IRQ_CDIA);
-
- if (!valid)
- return;
-
- gsb_ack();
- isb();
-
- hwirq = FIELD_GET(GICV5_GICR_CDIA_INTID, ia);
-
- gic_insn(hwirq, CDDI);
- gic_insn(0, CDEOI);
+ const u32 max_nr_spis = BIT(10);
+ struct kvm_vcpu *vcpu;
+ struct vm_gic v;
+ u32 nr_irqs;
+ int ret;
- GUEST_SYNC(GUEST_CMD_IRQ_DIEOI);
+ v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
+ v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
- if (++count >= 2)
- GUEST_DONE();
+ /* Check existing group/attribute */
+ kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS, 0);
+
+ /* Before userspace sets NR_IRQS, no SPI count has been selected. */
+ nr_irqs = 0xbad;
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
+ 0, &nr_irqs);
+ TEST_ASSERT(!ret && nr_irqs == 0, "GICv5 NR_IRQS defaults to 0 before init");
+
+ /* Too few SPIs */
+ nr_irqs = VGIC_V5_DEFAULT_NR_SPIS - 1;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
+ 0, &nr_irqs);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 NR_IRQS below minimum");
+
+ /* Not a multiple of 32 */
+ nr_irqs = VGIC_V5_DEFAULT_NR_SPIS + 1;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
+ 0, &nr_irqs);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 NR_IRQS not 32-aligned");
+
+ /* Larger than KVM's supported VGICv5 SPI count */
+ nr_irqs = max_nr_spis + 32;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
+ 0, &nr_irqs);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 NR_IRQS above maximum");
+
+ /* Valid custom SPI count */
+ nr_irqs = max_nr_spis;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
+ 0, &nr_irqs);
+ TEST_ASSERT(!ret, "GICv5 NR_IRQS accepts valid custom SPI count");
+
+ nr_irqs = 0xbad;
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
+ 0, &nr_irqs);
+ TEST_ASSERT(!ret && nr_irqs == max_nr_spis,
+ "GICv5 NR_IRQS returns SPI count only");
+
+ /* A second successful configuration attempt must be rejected. */
+ nr_irqs = VGIC_V5_DEFAULT_NR_SPIS;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
+ 0, &nr_irqs);
+ TEST_ASSERT(ret && errno == EBUSY, "GICv5 NR_IRQS set twice");
+
+ /* The maximum supported count must also initialize successfully. */
+ vcpu = vm_vcpu_add(v.vm, 0, NULL);
+ TEST_ASSERT(vcpu, "Failed to create vCPU");
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
- /* Ask for the next interrupt to be injected */
- GUEST_SYNC(GUEST_CMD_IS_READY);
-}
+ vm_gic_destroy(&v);
-static void guest_code(void)
-{
- local_irq_disable();
+ /* If userspace does not set NR_IRQS, init selects the default. */
+ 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);
+ TEST_ASSERT(vcpu, "Failed to create vCPU");
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
- gicv5_cpu_enable_interrupts();
- local_irq_enable();
+ nr_irqs = 0xbad;
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
+ 0, &nr_irqs);
+ TEST_ASSERT(!ret && nr_irqs == VGIC_V5_DEFAULT_NR_SPIS,
+ "GICv5 NR_IRQS defaults to 32 SPIs after init");
- /* Enable the SW_PPI (3) */
- write_sysreg_s(BIT_ULL(3), SYS_ICC_PPI_ENABLER0_EL1);
+ nr_irqs = VGIC_V5_DEFAULT_NR_SPIS * 2;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
+ 0, &nr_irqs);
+ TEST_ASSERT(ret && errno == EBUSY, "GICv5 NR_IRQS set after init");
- /* Ask for the first interrupt to be injected */
- GUEST_SYNC(GUEST_CMD_IS_READY);
+ vm_gic_destroy(&v);
- /* Loop forever waiting for interrupts */
- while (1);
}
static void test_vgic_v5_ppis(u32 gic_dev_type)
@@ -294,8 +385,11 @@ int test_kvm_device(u32 gic_dev_type)
void run_tests(u32 gic_dev_type)
{
- pr_info("Test VGICv5 Creation & Setup\n");
- test_vgic_v5_create();
+ pr_info("Test VGICv5 address attrs\n");
+ test_vgic_v5_addr_attrs();
+
+ pr_info("Test VGICv5 NR_IRQS attrs\n");
+ test_vgic_v5_nr_irqs_attrs();
pr_info("Test VGICv5 PPIs\n");
test_vgic_v5_ppis(gic_dev_type);
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 42/49] KVM: selftests: Add VGICv5 IRS_REGS attribute tests
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (40 preceding siblings ...)
2026-08-07 11:33 ` [PATCH v5 41/49] KVM: selftests: Add VGICv5 NR_IRQS " Sascha Bischoff
@ 2026-08-07 11:33 ` Sascha Bischoff
2026-08-07 11:34 ` [PATCH v5 43/49] KVM: selftests: Add VGICv5 IST " Sascha Bischoff
` (6 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:33 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Add coverage for the VGICv5 IRS_REGS attribute group. Migration needs
a userspace-visible view of the virtual IRS register state, but KVM
must expose only valid registers and reject state that cannot be
restored safely.
Verify that KVM_HAS_DEVICE_ATTR reports the IRS_REGS group before the
IRS base is configured, while register reads and writes are rejected
before VGIC initialisation. Once the VGIC is initialised, verify that
all exposed registers can be read and their values written back.
Check that bad offsets, incorrect alignment, and bad userspace
pointers are rejected. Also check ID register restore validation, idle
status register values, and IRS_IST_BASER restore handling.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
tools/testing/selftests/kvm/arm64/vgic_v5.c | 255 ++++++++++++++++++
.../selftests/kvm/include/arm64/gic_v5.h | 72 +++++
2 files changed, 327 insertions(+)
diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
index f4c0d2c003bb4..54fa13906990b 100644
--- a/tools/testing/selftests/kvm/arm64/vgic_v5.c
+++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
@@ -18,6 +18,8 @@
static u64 max_phys_size;
+#define VGIC_V5_TEST_IST_BASER_GPA 0x10000000ULL
+
struct vm_gic {
struct kvm_vm *vm;
int gic_fd;
@@ -110,6 +112,72 @@ static const struct vgic_region_attr gic_v5_irs_region = {
.alignment = GICV5_IRS_ALIGN,
};
+struct vgic_irs_reg_attr {
+ const char *name;
+ u64 attr;
+};
+
+#define IRS_REG(r) \
+ { \
+ .name = #r, \
+ .attr = r, \
+ }
+
+static const struct vgic_irs_reg_attr gic_v5_irs_regs[] = {
+ IRS_REG(GICV5_IRS_IDR0),
+ IRS_REG(GICV5_IRS_IDR1),
+ IRS_REG(GICV5_IRS_IDR2),
+ IRS_REG(GICV5_IRS_IDR3),
+ IRS_REG(GICV5_IRS_IDR4),
+ IRS_REG(GICV5_IRS_IDR5),
+ IRS_REG(GICV5_IRS_IDR6),
+ IRS_REG(GICV5_IRS_IDR7),
+ IRS_REG(GICV5_IRS_IIDR),
+ IRS_REG(GICV5_IRS_AIDR),
+ IRS_REG(GICV5_IRS_CR0),
+ IRS_REG(GICV5_IRS_CR1),
+ IRS_REG(GICV5_IRS_SYNCR),
+ IRS_REG(GICV5_IRS_SYNC_STATUSR),
+ IRS_REG(GICV5_IRS_SPI_VMR),
+ IRS_REG(GICV5_IRS_SPI_SELR),
+ IRS_REG(GICV5_IRS_SPI_DOMAINR),
+ IRS_REG(GICV5_IRS_SPI_RESAMPLER),
+ IRS_REG(GICV5_IRS_SPI_CFGR),
+ IRS_REG(GICV5_IRS_SPI_STATUSR),
+ IRS_REG(GICV5_IRS_PE_SELR),
+ IRS_REG(GICV5_IRS_PE_STATUSR),
+ IRS_REG(GICV5_IRS_PE_CR0),
+ IRS_REG(GICV5_IRS_IST_BASER),
+ IRS_REG(GICV5_IRS_IST_CFGR),
+ IRS_REG(GICV5_IRS_IST_STATUSR),
+ IRS_REG(GICV5_IRS_MAP_L2_ISTR),
+ IRS_REG(GICV5_IRS_VMT_BASER),
+ IRS_REG(GICV5_IRS_VMT_CFGR),
+ IRS_REG(GICV5_IRS_VMT_STATUSR),
+ IRS_REG(GICV5_IRS_VPE_SELR),
+ IRS_REG(GICV5_IRS_VPE_DBR),
+ IRS_REG(GICV5_IRS_VPE_HPPIR),
+ IRS_REG(GICV5_IRS_VPE_CR0),
+ IRS_REG(GICV5_IRS_VPE_STATUSR),
+ IRS_REG(GICV5_IRS_VM_DBR),
+ IRS_REG(GICV5_IRS_VM_SELR),
+ IRS_REG(GICV5_IRS_VM_STATUSR),
+ IRS_REG(GICV5_IRS_VMAP_L2_VMTR),
+ IRS_REG(GICV5_IRS_VMAP_VMR),
+ IRS_REG(GICV5_IRS_VMAP_VISTR),
+ IRS_REG(GICV5_IRS_VMAP_L2_VISTR),
+ IRS_REG(GICV5_IRS_VMAP_VPER),
+ IRS_REG(GICV5_IRS_SAVE_VMR),
+ IRS_REG(GICV5_IRS_SAVE_VM_STATUSR),
+ IRS_REG(GICV5_IRS_MEC_IDR),
+ IRS_REG(GICV5_IRS_MEC_MECID_R),
+ IRS_REG(GICV5_IRS_MPAM_IDR),
+ IRS_REG(GICV5_IRS_MPAM_PARTID_R),
+ IRS_REG(GICV5_IRS_SWERR_STATUSR),
+ IRS_REG(GICV5_IRS_SWERR_SYNDROMER0),
+ IRS_REG(GICV5_IRS_SWERR_SYNDROMER1),
+};
+
static void test_vgic_v5_addr_attrs(void)
{
struct kvm_vcpu *vcpu;
@@ -272,6 +340,189 @@ static void test_vgic_v5_nr_irqs_attrs(void)
}
+static void test_vgic_v5_irs_regs_attrs(void)
+{
+ struct kvm_vcpu *vcpu;
+ struct vm_gic v;
+ u64 attr, val;
+ int ret, i;
+
+ v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ 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);
+ TEST_ASSERT(vcpu, "Failed to create vCPU");
+
+ /* IRS_REGS attributes can be probed before the IRS base is set. */
+ kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR0);
+
+ attr = GICV5_IRS_CONFIG_BASE_GPA;
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &attr);
+
+ /* Check existing group/attribute */
+ kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR0);
+
+ /* IRS_REGS are not accessible before the VGIC is initialized. */
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR0, &val);
+ TEST_ASSERT(ret && errno == EBUSY, "GICv5 IRS_REGS get before init");
+
+ val = 0;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR0, &val);
+ TEST_ASSERT(ret && errno == EBUSY, "GICv5 IRS_REGS set before init");
+
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ /* Read all supported IRS regs and write the value back. */
+ for (i = 0; i < ARRAY_SIZE(gic_v5_irs_regs); i++) {
+ attr = gic_v5_irs_regs[i].attr;
+ ret = __kvm_has_device_attr(v.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ attr);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS missing %s",
+ gic_v5_irs_regs[i].name);
+
+ val = 0xbad;
+ ret = __kvm_device_attr_get(v.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ attr, &val);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS get failed for %s",
+ gic_v5_irs_regs[i].name);
+
+ ret = __kvm_device_attr_set(v.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ attr, &val);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS set failed for %s",
+ gic_v5_irs_regs[i].name);
+ }
+
+ /* Check bad offsets */
+ attr = 0x10000;
+ ret = __kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ attr);
+ TEST_ASSERT(ret && errno == ENXIO, "GICv5 IRS_REGS accepted bad offset");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ attr, &val);
+ TEST_ASSERT(ret && errno == ENXIO, "GICv5 IRS_REGS get bad offset");
+
+ /* Check alignment for 32-bit and 64-bit IRS regs. */
+ attr = GICV5_IRS_IDR0 + 2;
+ ret = __kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IRS_REGS accepted unaligned 32-bit attr");
+
+ attr = GICV5_IRS_IST_BASER + 4;
+ ret = __kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IRS_REGS accepted unaligned 64-bit attr");
+
+ /* Check bad user pointers */
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR0, NULL);
+ TEST_ASSERT(ret && errno == EFAULT, "GICv5 IRS_REGS get with bad pointer");
+
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR0, NULL);
+ TEST_ASSERT(ret && errno == EFAULT, "GICv5 IRS_REGS set with bad pointer");
+
+ /* ID restore validation rejects unsupported values. */
+ val = FIELD_PREP(GICV5_IRS_IDR0_INT_DOM, GICV5_IRS_IDR0_INT_DOM_SECURE);
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR0, &val);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IRS_REGS accepted bad IDR0 domain");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR0, &val);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS get IDR0 failed");
+ val |= GICV5_IRS_IDR0_SETLPI;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR0, &val);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IRS_REGS accepted unsupported IDR0");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR1, &val);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS get IDR1 failed");
+ val |= FIELD_PREP(GICV5_IRS_IDR1_PRIORITY_BITS, 0x7);
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR1, &val);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IRS_REGS accepted bad IDR1");
+
+ val = 0;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR2, &val);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IRS_REGS accepted bad IDR2");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR5, &val);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS get IDR5 failed");
+ val++;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR5, &val);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IRS_REGS accepted bad IDR5");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR6, &val);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS get IDR6 failed");
+ val++;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR6, &val);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IRS_REGS accepted bad IDR6");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR7, &val);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS get IDR7 failed");
+ val++;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR7, &val);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IRS_REGS accepted bad IDR7");
+
+ /* Status registers read as idle through userspace accessors. */
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_SYNC_STATUSR, &val);
+ TEST_ASSERT(!ret && val == GICV5_IRS_SYNC_STATUSR_IDLE,
+ "GICv5 IRS_REGS SYNC_STATUSR is not idle");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_SPI_STATUSR, &val);
+ TEST_ASSERT(!ret && val == GICV5_IRS_SPI_STATUSR_IDLE,
+ "GICv5 IRS_REGS SPI_STATUSR is not idle");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_PE_STATUSR, &val);
+ TEST_ASSERT(!ret && val == GICV5_IRS_PE_STATUSR_IDLE,
+ "GICv5 IRS_REGS PE_STATUSR is not idle");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_STATUSR, &val);
+ TEST_ASSERT(!ret && val == GICV5_IRS_IST_STATUSR_IDLE,
+ "GICv5 IRS_REGS IST_STATUSR is not idle");
+
+ /*
+ * Userspace restores IST_BASER without allocating or accessing an IST.
+ * Supply a synthetic, aligned guest address only to verify the register
+ * round trip.
+ */
+ val = FIELD_PREP(GICV5_IRS_IST_BASER_ADDR_MASK,
+ VGIC_V5_TEST_IST_BASER_GPA >> GICV5_IRS_IST_BASER_ADDR_SHIFT);
+ val |= GICV5_IRS_IST_BASER_VALID;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_BASER, &val);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS failed to restore IST_BASER");
+
+ attr = 0xbad;
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_BASER, &attr);
+ TEST_ASSERT(!ret && attr == val, "GICv5 IRS_REGS IST_BASER restore mismatch");
+
+ vm_gic_destroy(&v);
+}
+
static void test_vgic_v5_ppis(u32 gic_dev_type)
{
struct kvm_vcpu *vcpus[NR_VCPUS];
@@ -391,6 +642,10 @@ void run_tests(u32 gic_dev_type)
pr_info("Test VGICv5 NR_IRQS attrs\n");
test_vgic_v5_nr_irqs_attrs();
+ pr_info("Test VGICv5 IRS_REGS attrs\n");
+ test_vgic_v5_irs_regs_attrs();
+
+
pr_info("Test VGICv5 PPIs\n");
test_vgic_v5_ppis(gic_dev_type);
}
diff --git a/tools/testing/selftests/kvm/include/arm64/gic_v5.h b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
index 41b698b3952f3..0ad6dcc3d510b 100644
--- a/tools/testing/selftests/kvm/include/arm64/gic_v5.h
+++ b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
@@ -16,6 +16,78 @@
#define GICV5_IRS_SIZE 0x20000
#define GICV5_IRS_ALIGN 0x10000
+/*
+ * IRS register offsets. Keep this selftest copy scoped to the offsets and
+ * fields exercised by the userspace API tests.
+ */
+#define GICV5_IRS_IDR0 0x0000
+#define GICV5_IRS_IDR1 0x0004
+#define GICV5_IRS_IDR2 0x0008
+#define GICV5_IRS_IDR3 0x000c
+#define GICV5_IRS_IDR4 0x0010
+#define GICV5_IRS_IDR5 0x0014
+#define GICV5_IRS_IDR6 0x0018
+#define GICV5_IRS_IDR7 0x001c
+#define GICV5_IRS_IIDR 0x0040
+#define GICV5_IRS_AIDR 0x0044
+#define GICV5_IRS_CR0 0x0080
+#define GICV5_IRS_CR1 0x0084
+#define GICV5_IRS_SYNCR 0x00c0
+#define GICV5_IRS_SYNC_STATUSR 0x00c4
+#define GICV5_IRS_SPI_VMR 0x0100
+#define GICV5_IRS_SPI_SELR 0x0108
+#define GICV5_IRS_SPI_DOMAINR 0x010c
+#define GICV5_IRS_SPI_RESAMPLER 0x0110
+#define GICV5_IRS_SPI_CFGR 0x0114
+#define GICV5_IRS_SPI_STATUSR 0x0118
+#define GICV5_IRS_PE_SELR 0x0140
+#define GICV5_IRS_PE_STATUSR 0x0144
+#define GICV5_IRS_PE_CR0 0x0148
+#define GICV5_IRS_IST_BASER 0x0180
+#define GICV5_IRS_IST_CFGR 0x0190
+#define GICV5_IRS_IST_STATUSR 0x0194
+#define GICV5_IRS_MAP_L2_ISTR 0x01c0
+#define GICV5_IRS_VMT_BASER 0x0200
+#define GICV5_IRS_VMT_CFGR 0x0210
+#define GICV5_IRS_VMT_STATUSR 0x0214
+#define GICV5_IRS_VPE_SELR 0x0240
+#define GICV5_IRS_VPE_DBR 0x0248
+#define GICV5_IRS_VPE_HPPIR 0x0250
+#define GICV5_IRS_VPE_CR0 0x0258
+#define GICV5_IRS_VPE_STATUSR 0x025c
+#define GICV5_IRS_VM_DBR 0x0280
+#define GICV5_IRS_VM_SELR 0x0288
+#define GICV5_IRS_VM_STATUSR 0x028c
+#define GICV5_IRS_VMAP_L2_VMTR 0x02c0
+#define GICV5_IRS_VMAP_VMR 0x02c8
+#define GICV5_IRS_VMAP_VISTR 0x02d0
+#define GICV5_IRS_VMAP_L2_VISTR 0x02d8
+#define GICV5_IRS_VMAP_VPER 0x02e0
+#define GICV5_IRS_SAVE_VMR 0x0300
+#define GICV5_IRS_SAVE_VM_STATUSR 0x0308
+#define GICV5_IRS_MEC_IDR 0x0340
+#define GICV5_IRS_MEC_MECID_R 0x0344
+#define GICV5_IRS_MPAM_IDR 0x0380
+#define GICV5_IRS_MPAM_PARTID_R 0x0384
+#define GICV5_IRS_SWERR_STATUSR 0x03c0
+#define GICV5_IRS_SWERR_SYNDROMER0 0x03c8
+#define GICV5_IRS_SWERR_SYNDROMER1 0x03d0
+
+#define GICV5_IRS_IDR0_SETLPI BIT(9)
+#define GICV5_IRS_IDR0_INT_DOM GENMASK(1, 0)
+#define GICV5_IRS_IDR0_INT_DOM_SECURE 0b00
+
+#define GICV5_IRS_IDR1_PRIORITY_BITS GENMASK(22, 20)
+
+#define GICV5_IRS_SYNC_STATUSR_IDLE BIT(0)
+#define GICV5_IRS_SPI_STATUSR_IDLE BIT(0)
+#define GICV5_IRS_PE_STATUSR_IDLE BIT(0)
+#define GICV5_IRS_IST_STATUSR_IDLE BIT(0)
+
+#define GICV5_IRS_IST_BASER_ADDR_MASK GENMASK_ULL(55, 6)
+#define GICV5_IRS_IST_BASER_VALID BIT_ULL(0)
+#define GICV5_IRS_IST_BASER_ADDR_SHIFT 6ULL
+
/*
* Definitions for GICv5 instructions for the Current Domain
*/
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 43/49] KVM: selftests: Add VGICv5 IST attribute tests
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (41 preceding siblings ...)
2026-08-07 11:33 ` [PATCH v5 42/49] KVM: selftests: Add VGICv5 IRS_REGS " Sascha Bischoff
@ 2026-08-07 11:34 ` Sascha Bischoff
2026-08-07 11:35 ` [PATCH v5 44/49] KVM: selftests: Add VGICv5 USERSPACE_PPIS tests Sascha Bischoff
` (5 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:34 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Add API coverage for the VGICv5 IST attribute group. This does not
test that IST save/restore preserves interrupt state; it only checks
the attribute plumbing and the cases KVM must reject before the
end-to-end restore path can rely on the interface.
Verify that KVM reports the IST attribute group, rejects unknown
attributes, rejects save/restore before initialization, rejects bad
userspace pointers, and rejects IST restore after the VM has run.
Also cover the userspace IST descriptor validation: a VM with SPIs
must provide a SPI IST buffer of the expected size, and LPI storage
must not be supplied when no LPI IST is configured.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
tools/testing/selftests/kvm/arm64/vgic_v5.c | 122 ++++++++++++++++++++
1 file changed, 122 insertions(+)
diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
index 54fa13906990b..e053b01bb5be3 100644
--- a/tools/testing/selftests/kvm/arm64/vgic_v5.c
+++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
@@ -31,6 +31,17 @@ struct vm_gic {
#define GUEST_CMD_IS_AWAKE 12
#define GUEST_CMD_IS_READY 13
+static struct kvm_vgic_v5_ist vgic_v5_ist_attr(void *spi_ist, size_t spi_size,
+ void *lpi_ist, size_t lpi_size)
+{
+ return (struct kvm_vgic_v5_ist) {
+ .spi_ist_addr = (uintptr_t)spi_ist,
+ .spi_ist_size = spi_size,
+ .lpi_ist_addr = (uintptr_t)lpi_ist,
+ .lpi_ist_size = lpi_size,
+ };
+}
+
static void guest_irq_handler(struct ex_regs *regs)
{
bool valid;
@@ -523,6 +534,114 @@ static void test_vgic_v5_irs_regs_attrs(void)
vm_gic_destroy(&v);
}
+static void test_vgic_v5_ist_attrs(void)
+{
+ struct kvm_vgic_v5_ist ist_attr;
+ struct kvm_vcpu *vcpu;
+ struct vm_gic v;
+ u32 spi_ist[VGIC_V5_DEFAULT_NR_SPIS];
+ u64 attr;
+ int ret;
+
+ v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ 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);
+ TEST_ASSERT(vcpu, "Failed to create vCPU");
+
+ /* Check existing group/attribute */
+ kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST, 0);
+
+ /* Check non-existing attribute */
+ ret = __kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST, 1);
+ TEST_ASSERT(ret && errno == ENXIO, "GICv5 IST accepted bad attr");
+
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist), NULL, 0);
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 1, &ist_attr);
+ TEST_ASSERT(ret && errno == ENXIO, "GICv5 IST get accepted bad attr");
+
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 1, &ist_attr);
+ TEST_ASSERT(ret && errno == ENXIO, "GICv5 IST set accepted bad attr");
+
+ attr = GICV5_IRS_CONFIG_BASE_GPA;
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &attr);
+
+ /* IST save/restore is not accessible before the VGIC is initialized. */
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EBUSY, "GICv5 IST get before init");
+
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EBUSY, "GICv5 IST set before init");
+
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ /* A VM with SPIs must provide a userspace IST descriptor. */
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, NULL);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST get accepted NULL descriptor");
+
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, NULL);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST set accepted NULL descriptor");
+
+ /* Check bad userspace IST descriptors. */
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, (void *)1);
+ TEST_ASSERT(ret && errno == EFAULT, "GICv5 IST get with bad descriptor");
+
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, (void *)1);
+ TEST_ASSERT(ret && errno == EFAULT, "GICv5 IST set with bad descriptor");
+
+ /* Check missing and incorrectly sized SPI IST buffers. */
+ ist_attr = vgic_v5_ist_attr(NULL, 0, NULL, 0);
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST get accepted missing SPI buffer");
+
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist) - sizeof(__u32),
+ NULL, 0);
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST set accepted bad SPI size");
+
+ /* LPI storage must be absent when no LPI IST is configured. */
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist), spi_ist,
+ sizeof(__u32));
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST get accepted unexpected LPI buffer");
+
+ vm_gic_destroy(&v);
+
+ /* IST restore is rejected after the VM has run. */
+ 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);
+
+ attr = GICV5_IRS_CONFIG_BASE_GPA;
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &attr);
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ ret = run_vcpu(vcpu);
+ TEST_ASSERT(!ret, "Failed to run GICv5 VM before IST restore test");
+
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist), NULL, 0);
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EBUSY, "GICv5 IST restore after run");
+
+ vm_gic_destroy(&v);
+}
+
static void test_vgic_v5_ppis(u32 gic_dev_type)
{
struct kvm_vcpu *vcpus[NR_VCPUS];
@@ -645,6 +764,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 IST attrs\n");
+ test_vgic_v5_ist_attrs();
+
pr_info("Test VGICv5 PPIs\n");
test_vgic_v5_ppis(gic_dev_type);
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 44/49] KVM: selftests: Add VGICv5 USERSPACE_PPIS tests
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (42 preceding siblings ...)
2026-08-07 11:34 ` [PATCH v5 43/49] KVM: selftests: Add VGICv5 IST " Sascha Bischoff
@ 2026-08-07 11:35 ` Sascha Bischoff
2026-08-07 11:35 ` [PATCH v5 45/49] KVM: selftests: Add VGICv5 CPU sysreg attribute tests Sascha Bischoff
` (4 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:35 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Add coverage for the VGICv5 USERSPACE_PPIS control attribute. VGICv5
only allows userspace to drive PPIs that KVM explicitly advertises, so
the selftest should verify the discovery path and the corresponding
KVM_IRQ_LINE rejection cases.
Verify that KVM reports the control attribute and that the returned
bitmap contains SW_PPI. Also check that the upper 64 bits are zero, as
KVM only exposes architected PPIs (the lower 64 entries). Finally,
verify that writes, bad userspace pointers, PPIs outside the
advertised bitmap, and the first out-of-range PPI number are rejected.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
tools/testing/selftests/kvm/arm64/vgic_v5.c | 70 +++++++++++++++++++++
1 file changed, 70 insertions(+)
diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
index e053b01bb5be3..53aad3d32f08e 100644
--- a/tools/testing/selftests/kvm/arm64/vgic_v5.c
+++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
@@ -13,6 +13,7 @@
#include "vgic.h"
#define NR_VCPUS 1
+#define VGIC_V5_NR_PRIVATE_IRQS 64
#define VGIC_V5_DEFAULT_NR_SPIS 32
#define VGIC_V5_MAX_NR_SPIS BIT(16)
@@ -111,6 +112,17 @@ static void vm_gic_destroy(struct vm_gic *v)
kvm_vm_free(v->vm);
}
+static u32 vgic_v5_irq_line_payload(u32 type, u32 num)
+{
+ return (type << KVM_ARM_IRQ_TYPE_SHIFT) |
+ FIELD_PREP(KVM_ARM_IRQ_NUM_MASK, num);
+}
+
+static int __vgic_v5_irq_line(struct kvm_vm *vm, u32 type, u32 num, int level)
+{
+ return _kvm_irq_line(vm, vgic_v5_irq_line_payload(type, num), level);
+}
+
struct vgic_region_attr {
u64 attr;
u64 size;
@@ -642,6 +654,47 @@ static void test_vgic_v5_ist_attrs(void)
vm_gic_destroy(&v);
}
+static void test_vgic_v5_userspace_ppis_attrs(void)
+{
+ struct kvm_vcpu *vcpu;
+ struct vm_gic v;
+ u64 user_ppis[2];
+ int ret;
+
+ v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ 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);
+ TEST_ASSERT(vcpu, "Failed to create vCPU");
+
+ kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT);
+ kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_USERSPACE_PPIS);
+
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ user_ppis[0] = 0;
+ user_ppis[1] = 0xbad;
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_USERSPACE_PPIS, user_ppis);
+ TEST_ASSERT(!ret, "GICv5 USERSPACE_PPIS get failed");
+ TEST_ASSERT(user_ppis[0] & BIT(GICV5_ARCH_PPI_SW_PPI),
+ "GICv5 USERSPACE_PPIS does not expose SW_PPI");
+ TEST_ASSERT(!user_ppis[1], "GICv5 USERSPACE_PPIS upper word is not zero");
+
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_USERSPACE_PPIS, user_ppis);
+ TEST_ASSERT(ret && errno == ENXIO, "GICv5 USERSPACE_PPIS set accepted");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_USERSPACE_PPIS, NULL);
+ TEST_ASSERT(ret && errno == EFAULT, "GICv5 USERSPACE_PPIS get with bad pointer");
+
+ vm_gic_destroy(&v);
+}
+
static void test_vgic_v5_ppis(u32 gic_dev_type)
{
struct kvm_vcpu *vcpus[NR_VCPUS];
@@ -681,6 +734,20 @@ static void test_vgic_v5_ppis(u32 gic_dev_type)
TEST_ASSERT(user_ppis[0] & BIT(GICV5_ARCH_PPI_SW_PPI),
"SW_PPI is not drivable by userspace");
+ /* PPIs not explicitly exposed to userspace must be rejected. */
+ for (i = 0; i < VGIC_V5_NR_PRIVATE_IRQS; i++) {
+ if (user_ppis[i / 64] & BIT_ULL(i % 64))
+ continue;
+
+ ret = __vgic_v5_irq_line(v.vm, KVM_ARM_IRQ_TYPE_PPI, i, 1);
+ TEST_ASSERT(ret && errno == EINVAL,
+ "GICv5 accepted non-userspace PPI %d", i);
+ }
+
+ ret = __vgic_v5_irq_line(v.vm, KVM_ARM_IRQ_TYPE_PPI,
+ VGIC_V5_NR_PRIVATE_IRQS, 1);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 accepted out-of-range PPI");
+
while (1) {
ret = run_vcpu(vcpus[0]);
if (ret)
@@ -767,6 +834,9 @@ void run_tests(u32 gic_dev_type)
pr_info("Test VGICv5 IST attrs\n");
test_vgic_v5_ist_attrs();
+ pr_info("Test VGICv5 userspace PPI attrs\n");
+ test_vgic_v5_userspace_ppis_attrs();
+
pr_info("Test VGICv5 PPIs\n");
test_vgic_v5_ppis(gic_dev_type);
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 45/49] KVM: selftests: Add VGICv5 CPU sysreg attribute tests
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (43 preceding siblings ...)
2026-08-07 11:35 ` [PATCH v5 44/49] KVM: selftests: Add VGICv5 USERSPACE_PPIS tests Sascha Bischoff
@ 2026-08-07 11:35 ` Sascha Bischoff
2026-08-07 11:36 ` [PATCH v5 46/49] KVM: selftests: Add VGICv5 SPI injection tests Sascha Bischoff
` (3 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:35 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Add coverage for the VGICv5 CPU_SYSREGS attribute group. The
vCPU-local CPU interface registers are part of the VGICv5 userspace
ABI, so KVM must expose the supported registers and reject invalid
register or vCPU selectors.
Verify that CPU_SYSREGS access is rejected before VGIC initialization,
that all exposed VGICv5 CPU interface registers can be read and
written back, and that unsupported sysregs, invalid MPIDRs and bad
userspace pointers are rejected.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
tools/testing/selftests/kvm/arm64/vgic_v5.c | 137 ++++++++++++++++++++
1 file changed, 137 insertions(+)
diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
index 53aad3d32f08e..e99e24812052c 100644
--- a/tools/testing/selftests/kvm/arm64/vgic_v5.c
+++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
@@ -135,11 +135,29 @@ static const struct vgic_region_attr gic_v5_irs_region = {
.alignment = GICV5_IRS_ALIGN,
};
+struct vgic_sysreg_attr {
+ const char *name;
+ u32 encoding;
+};
+
struct vgic_irs_reg_attr {
const char *name;
u64 attr;
};
+#define PACK_SR(r) \
+ ((sys_reg_Op0(r) << 14) | \
+ (sys_reg_Op1(r) << 11) | \
+ (sys_reg_CRn(r) << 7) | \
+ (sys_reg_CRm(r) << 3) | \
+ (sys_reg_Op2(r)))
+
+#define SR(r) \
+ { \
+ .name = #r, \
+ .encoding = r, \
+ }
+
#define IRS_REG(r) \
{ \
.name = #r, \
@@ -201,6 +219,35 @@ static const struct vgic_irs_reg_attr gic_v5_irs_regs[] = {
IRS_REG(GICV5_IRS_SWERR_SYNDROMER1),
};
+static const struct vgic_sysreg_attr gic_v5_cpu_sysregs[] = {
+ SR(SYS_ICC_ICSR_EL1),
+ SR(SYS_ICC_PPI_ENABLER0_EL1),
+ SR(SYS_ICC_PPI_ENABLER1_EL1),
+ SR(SYS_ICC_PPI_SACTIVER0_EL1),
+ SR(SYS_ICC_PPI_SACTIVER1_EL1),
+ SR(SYS_ICC_PPI_SPENDR0_EL1),
+ SR(SYS_ICC_PPI_SPENDR1_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR0_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR1_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR2_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR3_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR4_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR5_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR6_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR7_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR8_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR9_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR10_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR11_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR12_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR13_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR14_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR15_EL1),
+ SR(SYS_ICC_APR_EL1),
+ SR(SYS_ICC_CR0_EL1),
+ SR(SYS_ICC_PCR_EL1),
+};
+
static void test_vgic_v5_addr_attrs(void)
{
struct kvm_vcpu *vcpu;
@@ -695,6 +742,93 @@ static void test_vgic_v5_userspace_ppis_attrs(void)
vm_gic_destroy(&v);
}
+static void test_vgic_v5_cpu_sysreg_attrs(void)
+{
+ struct kvm_vcpu *vcpu;
+ struct vm_gic v;
+ u64 attr, val;
+ int ret, i;
+
+ v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ 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);
+ TEST_ASSERT(vcpu, "Failed to create vCPU");
+
+ /* Check existing group/attribute */
+ attr = PACK_SR(SYS_ICC_CR0_EL1);
+ kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS, attr);
+
+ /* CPU sysregs are not accessible before the VGIC is initialized. */
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr, &val);
+ TEST_ASSERT(ret && errno == EBUSY, "GICv5 CPU_SYSREGS get before init");
+
+ val = 0;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr, &val);
+ TEST_ASSERT(ret && errno == EBUSY, "GICv5 CPU_SYSREGS set before init");
+
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ /* Read all exposed CPU sysregs and write the value back. */
+ for (i = 0; i < ARRAY_SIZE(gic_v5_cpu_sysregs); i++) {
+ attr = PACK_SR(gic_v5_cpu_sysregs[i].encoding);
+ ret = __kvm_has_device_attr(v.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr);
+ TEST_ASSERT(!ret, "GICv5 CPU_SYSREGS missing %s",
+ gic_v5_cpu_sysregs[i].name);
+
+ val = 0xbad;
+ ret = __kvm_device_attr_get(v.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr, &val);
+ TEST_ASSERT(!ret, "GICv5 CPU_SYSREGS get failed for %s",
+ gic_v5_cpu_sysregs[i].name);
+
+ ret = __kvm_device_attr_set(v.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr, &val);
+ TEST_ASSERT(!ret, "GICv5 CPU_SYSREGS set failed for %s",
+ gic_v5_cpu_sysregs[i].name);
+ }
+
+ /* Check non existent GICv3 sysreg */
+ attr = PACK_SR(SYS_ICC_CTLR_EL1);
+ ret = __kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr);
+ TEST_ASSERT(ret && errno == ENXIO, "GICv5 CPU_SYSREGS accepted bad sysreg");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr, &val);
+ TEST_ASSERT(ret && errno == ENOENT, "GICv5 CPU_SYSREGS get bad sysreg");
+
+ /* Check non existing vCPU */
+ attr = PACK_SR(SYS_ICC_CR0_EL1) |
+ (1ULL << KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT);
+ ret = __kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 CPU_SYSREGS accepted bad MPIDR");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr, &val);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 CPU_SYSREGS get bad MPIDR");
+
+ /* Check bad user pointers */
+ attr = PACK_SR(SYS_ICC_CR0_EL1);
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr, NULL);
+ TEST_ASSERT(ret && errno == EFAULT, "GICv5 CPU_SYSREGS get with bad pointer");
+
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr, NULL);
+ TEST_ASSERT(ret && errno == EFAULT, "GICv5 CPU_SYSREGS set with bad pointer");
+
+ vm_gic_destroy(&v);
+}
+
static void test_vgic_v5_ppis(u32 gic_dev_type)
{
struct kvm_vcpu *vcpus[NR_VCPUS];
@@ -837,6 +971,9 @@ void run_tests(u32 gic_dev_type)
pr_info("Test VGICv5 userspace PPI attrs\n");
test_vgic_v5_userspace_ppis_attrs();
+ pr_info("Test VGICv5 CPU sysreg attrs\n");
+ test_vgic_v5_cpu_sysreg_attrs();
+
pr_info("Test VGICv5 PPIs\n");
test_vgic_v5_ppis(gic_dev_type);
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 46/49] KVM: selftests: Add VGICv5 SPI injection tests
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (44 preceding siblings ...)
2026-08-07 11:35 ` [PATCH v5 45/49] KVM: selftests: Add VGICv5 CPU sysreg attribute tests Sascha Bischoff
@ 2026-08-07 11:36 ` Sascha Bischoff
2026-08-07 11:36 ` [PATCH v5 47/49] KVM: selftests: Add VGICv5 LPI delivery tests Sascha Bischoff
` (2 subsequent siblings)
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:36 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Add VGICv5 SPI delivery coverage through KVM_IRQ_LINE. SPIs are
configured through the virtual IRS and delivered through the GICv5 CPU
interface, so the test needs to exercise both the userspace injection
path and the guest-side IRS programming path.
Map the virtual IRS into the guest, enable the IRS, configure SPI
trigger mode and target VPE from the guest, and check delivery of a
default SPI and SPI 1023, the last SPI in the maximum configured
range. This also exercises VGIC initialization and the IRS selector
at KVM's upper bound. Verify that invalid SPI numbers are rejected,
that lowering an edge-triggered SPI after it is pending does not
recall it, and that level-sensitive SPIs are delivered.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
tools/testing/selftests/kvm/arm64/vgic_v5.c | 267 +++++++++++++++++-
.../selftests/kvm/include/arm64/gic_v5.h | 11 +
2 files changed, 268 insertions(+), 10 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
index e99e24812052c..23fbd2db50c43 100644
--- a/tools/testing/selftests/kvm/arm64/vgic_v5.c
+++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
@@ -15,7 +15,9 @@
#define NR_VCPUS 1
#define VGIC_V5_NR_PRIVATE_IRQS 64
#define VGIC_V5_DEFAULT_NR_SPIS 32
-#define VGIC_V5_MAX_NR_SPIS BIT(16)
+#define VGIC_V5_MAX_NR_SPIS BIT(10)
+#define VGIC_V5_IRS_SIZE 0x20000
+#define VGIC_V5_IRS_WAIT_RETRIES 1000000
static u64 max_phys_size;
@@ -43,7 +45,71 @@ static struct kvm_vgic_v5_ist vgic_v5_ist_attr(void *spi_ist, size_t spi_size,
};
}
-static void guest_irq_handler(struct ex_regs *regs)
+static u32 spi_line_expected;
+static bool spi_line_level_sensitive;
+
+static u64 gicv5_spi_hwirq(u32 spi)
+{
+ return FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_SPI) |
+ FIELD_PREP(GICV5_HWIRQ_ID, spi);
+}
+
+static void gicv5_setup_and_enable_hwirq(u64 hwirq, u32 target_vpe)
+{
+ u64 val;
+
+ val = hwirq | FIELD_PREP(GICV5_GIC_CDPRI_PRIORITY_MASK,
+ GICV5_IRQ_DEFAULT_PRI);
+ gic_insn(val, CDPRI);
+
+ val = hwirq | FIELD_PREP(GICV5_GIC_CDAFF_IAFFID_MASK, target_vpe);
+ gic_insn(val, CDAFF);
+
+ gic_insn(hwirq, CDEN);
+}
+
+static void gicv5_enable_spi(u32 spi, u32 target_vpe)
+{
+ gicv5_setup_and_enable_hwirq(gicv5_spi_hwirq(spi), target_vpe);
+}
+
+static void gicv5_wait_for_irs_idle(u32 reg, u32 idle)
+{
+ int i;
+
+ for (i = 0; i < VGIC_V5_IRS_WAIT_RETRIES; i++) {
+ if (readl(GICV5_IRS_CONFIG_BASE_GVA + reg) & idle)
+ return;
+
+ cpu_relax();
+ }
+
+ GUEST_FAIL("IRS operation did not become idle");
+}
+
+static void gicv5_configure_spi(u32 spi, bool level)
+{
+ u32 val;
+
+ val = FIELD_PREP(GICV5_IRS_SPI_SELR_ID, spi);
+ writel(val, GICV5_IRS_CONFIG_BASE_GVA + GICV5_IRS_SPI_SELR);
+ gicv5_wait_for_irs_idle(GICV5_IRS_SPI_STATUSR,
+ GICV5_IRS_SPI_STATUSR_IDLE);
+
+ val = level ? GICV5_IRS_SPI_CFGR_TM : 0;
+ writel(val, GICV5_IRS_CONFIG_BASE_GVA + GICV5_IRS_SPI_CFGR);
+ gicv5_wait_for_irs_idle(GICV5_IRS_SPI_STATUSR,
+ GICV5_IRS_SPI_STATUSR_IDLE);
+}
+
+static void gicv5_enable_irs(void)
+{
+ writel(GICV5_IRS_CR0_IRSEN,
+ GICV5_IRS_CONFIG_BASE_GVA + GICV5_IRS_CR0);
+ gicv5_wait_for_irs_idle(GICV5_IRS_CR0, GICV5_IRS_CR0_IDLE);
+}
+
+static void guest_ppi_irq_handler(struct ex_regs *regs)
{
bool valid;
u32 hwirq;
@@ -100,6 +166,49 @@ static void guest_code(void)
cpu_relax();
}
+static void guest_spi_irq_handler(struct ex_regs *regs)
+{
+ bool valid;
+ u32 hwirq;
+ u64 ia;
+
+ ia = gicr_insn(CDIA);
+ valid = GICV5_GICR_CDIA_VALID(ia);
+
+ if (!valid)
+ return;
+
+ gsb_ack();
+ isb();
+
+ hwirq = FIELD_GET(GICV5_GICR_CDIA_INTID, ia);
+
+ GUEST_ASSERT_EQ(hwirq, gicv5_spi_hwirq(READ_ONCE(spi_line_expected)));
+
+ gic_insn(hwirq, CDDI);
+ gic_insn(0, CDEOI);
+
+ GUEST_DONE();
+}
+
+static void guest_spi_line_code(void)
+{
+ local_irq_disable();
+
+ gicv5_enable_irs();
+ gicv5_cpu_enable_interrupts();
+ gicv5_configure_spi(READ_ONCE(spi_line_expected),
+ READ_ONCE(spi_line_level_sensitive));
+ gicv5_enable_spi(READ_ONCE(spi_line_expected), 0);
+
+ local_irq_enable();
+
+ GUEST_SYNC(GUEST_CMD_IS_READY);
+
+ while (1)
+ wfi();
+}
+
/* we don't want to assert on run execution, hence that helper */
static int run_vcpu(struct kvm_vcpu *vcpu)
{
@@ -112,6 +221,17 @@ static void vm_gic_destroy(struct vm_gic *v)
kvm_vm_free(v->vm);
}
+static void vgic_v5_map_irs(struct kvm_vm *vm)
+{
+ unsigned int nr_irs_pages;
+
+ nr_irs_pages = vm_calc_num_guest_pages(vm->mode, VGIC_V5_IRS_SIZE);
+
+ /* Map the IRS at VA == IPA so guest MMIO writes hit the IRS IODEV. */
+ virt_map(vm, GICV5_IRS_CONFIG_BASE_GPA,
+ GICV5_IRS_CONFIG_BASE_GPA, nr_irs_pages);
+}
+
static u32 vgic_v5_irq_line_payload(u32 type, u32 num)
{
return (type << KVM_ARM_IRQ_TYPE_SHIFT) |
@@ -123,6 +243,93 @@ static int __vgic_v5_irq_line(struct kvm_vm *vm, u32 type, u32 num, int level)
return _kvm_irq_line(vm, vgic_v5_irq_line_payload(type, num), level);
}
+static void vgic_v5_spi_line(struct kvm_vm *vm, u32 spi, int level)
+{
+ int ret = __vgic_v5_irq_line(vm, KVM_ARM_IRQ_TYPE_SPI, spi, level);
+
+ TEST_ASSERT(!ret, "KVM_IRQ_LINE failed for SPI %u level %d",
+ spi, level);
+}
+
+static void vgic_v5_spi_line_vm_create(struct vm_gic *v,
+ struct kvm_vcpu **vcpu,
+ u32 nr_spis)
+{
+ u64 attr;
+
+ v->gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ 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);
+ TEST_ASSERT(*vcpu, "Failed to create vCPU");
+
+ vm_init_descriptor_tables(v->vm);
+ vm_install_exception_handler(v->vm, VECTOR_IRQ_CURRENT,
+ guest_spi_irq_handler);
+ vcpu_init_descriptor_tables(*vcpu);
+
+ kvm_device_attr_set(v->gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS, 0,
+ &nr_spis);
+
+ attr = GICV5_IRS_CONFIG_BASE_GPA;
+ kvm_device_attr_set(v->gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &attr);
+ vgic_v5_map_irs(v->vm);
+ kvm_device_attr_set(v->gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+}
+
+static void vgic_v5_run_spi_line_test(u32 nr_spis, u32 expected_spi,
+ bool level_sensitive,
+ bool lower_before_run)
+{
+ struct kvm_vcpu *vcpu;
+ struct vm_gic v;
+ struct ucall uc;
+ int ret;
+
+ spi_line_expected = expected_spi;
+ spi_line_level_sensitive = level_sensitive;
+
+ vgic_v5_spi_line_vm_create(&v, &vcpu, nr_spis);
+
+ sync_global_to_guest(v.vm, spi_line_expected);
+ sync_global_to_guest(v.vm, spi_line_level_sensitive);
+
+ ret = run_vcpu(vcpu);
+ TEST_ASSERT(!ret, "Failed to run GICv5 IRQ_LINE VM");
+ TEST_ASSERT(get_ucall(vcpu, &uc) == UCALL_SYNC &&
+ uc.args[1] == GUEST_CMD_IS_READY,
+ "GICv5 IRQ_LINE guest did not become ready");
+
+ vgic_v5_spi_line(v.vm, expected_spi, 1);
+ /*
+ * For edge-triggered SPIs, a following low transition must be ignored:
+ * once the edge has made the SPI pending, it cannot be recalled. This
+ * allows us to test that.
+ */
+ if (lower_before_run)
+ vgic_v5_spi_line(v.vm, expected_spi, 0);
+
+ while (1) {
+ ret = run_vcpu(vcpu);
+ TEST_ASSERT(!ret, "Failed to run GICv5 IRQ_LINE VM");
+
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ case UCALL_DONE:
+ goto done;
+ default:
+ TEST_FAIL("Unknown ucall %lu", uc.cmd);
+ }
+ }
+
+done:
+ vm_gic_destroy(&v);
+}
+
struct vgic_region_attr {
u64 attr;
u64 size;
@@ -324,7 +531,6 @@ static void test_vgic_v5_addr_attrs(void)
static void test_vgic_v5_nr_irqs_attrs(void)
{
- const u32 max_nr_spis = BIT(10);
struct kvm_vcpu *vcpu;
struct vm_gic v;
u32 nr_irqs;
@@ -356,13 +562,13 @@ static void test_vgic_v5_nr_irqs_attrs(void)
TEST_ASSERT(ret && errno == EINVAL, "GICv5 NR_IRQS not 32-aligned");
/* Larger than KVM's supported VGICv5 SPI count */
- nr_irqs = max_nr_spis + 32;
+ nr_irqs = VGIC_V5_MAX_NR_SPIS + 32;
ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
0, &nr_irqs);
TEST_ASSERT(ret && errno == EINVAL, "GICv5 NR_IRQS above maximum");
/* Valid custom SPI count */
- nr_irqs = max_nr_spis;
+ nr_irqs = VGIC_V5_MAX_NR_SPIS;
ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
0, &nr_irqs);
TEST_ASSERT(!ret, "GICv5 NR_IRQS accepts valid custom SPI count");
@@ -370,7 +576,7 @@ static void test_vgic_v5_nr_irqs_attrs(void)
nr_irqs = 0xbad;
ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
0, &nr_irqs);
- TEST_ASSERT(!ret && nr_irqs == max_nr_spis,
+ TEST_ASSERT(!ret && nr_irqs == VGIC_V5_MAX_NR_SPIS,
"GICv5 NR_IRQS returns SPI count only");
/* A second successful configuration attempt must be rejected. */
@@ -847,7 +1053,8 @@ static void test_vgic_v5_ppis(u32 gic_dev_type)
vcpus[i] = vm_vcpu_add(v.vm, i, guest_code);
vm_init_descriptor_tables(v.vm);
- vm_install_exception_handler(v.vm, VECTOR_IRQ_CURRENT, guest_irq_handler);
+ vm_install_exception_handler(v.vm, VECTOR_IRQ_CURRENT,
+ guest_ppi_irq_handler);
for (i = 0; i < NR_VCPUS; i++)
vcpu_init_descriptor_tables(vcpus[i]);
@@ -903,9 +1110,9 @@ static void test_vgic_v5_ppis(u32 gic_dev_type)
kvm_irq_line(v.vm, irq, level);
} else if (uc.args[1] == GUEST_CMD_IS_AWAKE) {
- pr_info("Guest skipping WFI due to pending IRQ\n");
+ pr_debug("Guest skipping WFI due to pending IRQ\n");
} else if (uc.args[1] == GUEST_CMD_IRQ_CDIA) {
- pr_info("Guest acknowledged IRQ\n");
+ pr_debug("Guest acknowledged IRQ\n");
}
continue;
@@ -925,6 +1132,44 @@ static void test_vgic_v5_ppis(u32 gic_dev_type)
vm_gic_destroy(&v);
}
+static void test_vgic_v5_spis(void)
+{
+ struct kvm_vcpu *vcpu;
+ struct vm_gic v;
+ int ret;
+
+ /* Default NR_IRQS exposes 32 SPIs, numbered 0..31 in KVM_IRQ_LINE. */
+ vgic_v5_spi_line_vm_create(&v, &vcpu, VGIC_V5_DEFAULT_NR_SPIS);
+ ret = __vgic_v5_irq_line(v.vm, KVM_ARM_IRQ_TYPE_SPI,
+ VGIC_V5_DEFAULT_NR_SPIS, 1);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 accepted first invalid SPI");
+
+ vm_gic_destroy(&v);
+
+ /* Basic SPI injection through KVM_IRQ_LINE. */
+ vgic_v5_run_spi_line_test(VGIC_V5_DEFAULT_NR_SPIS, 0, false, false);
+
+ /* The last valid SPI in the maximum configured range is injectable. */
+ vgic_v5_run_spi_line_test(VGIC_V5_MAX_NR_SPIS,
+ VGIC_V5_MAX_NR_SPIS - 1, false, false);
+
+ vgic_v5_spi_line_vm_create(&v, &vcpu, VGIC_V5_MAX_NR_SPIS);
+ ret = __vgic_v5_irq_line(v.vm, KVM_ARM_IRQ_TYPE_SPI,
+ VGIC_V5_MAX_NR_SPIS, 1);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 accepted configured invalid SPI");
+ vm_gic_destroy(&v);
+
+ /*
+ * Edge SPIs remain pending after the line is lowered. The low
+ * transition is injected before the guest runs and must be ignored:
+ * once an edge interrupt is pending, lowering the line cannot recall it.
+ */
+ vgic_v5_run_spi_line_test(VGIC_V5_DEFAULT_NR_SPIS, 1, false, true);
+
+ /* Level SPIs can be raised and delivered through KVM_IRQ_LINE. */
+ vgic_v5_run_spi_line_test(VGIC_V5_DEFAULT_NR_SPIS, 2, true, false);
+}
+
/*
* Returns 0 if it's possible to create GIC device of a given type (V5).
*/
@@ -974,9 +1219,11 @@ void run_tests(u32 gic_dev_type)
pr_info("Test VGICv5 CPU sysreg attrs\n");
test_vgic_v5_cpu_sysreg_attrs();
-
pr_info("Test VGICv5 PPIs\n");
test_vgic_v5_ppis(gic_dev_type);
+
+ pr_info("Test VGICv5 SPIs\n");
+ test_vgic_v5_spis();
}
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 0ad6dcc3d510b..091d9dbd3c01d 100644
--- a/tools/testing/selftests/kvm/include/arm64/gic_v5.h
+++ b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
@@ -12,6 +12,7 @@
/* 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_IRS_SIZE 0x20000
#define GICV5_IRS_ALIGN 0x10000
@@ -79,6 +80,12 @@
#define GICV5_IRS_IDR1_PRIORITY_BITS GENMASK(22, 20)
+#define GICV5_IRS_CR0_IDLE BIT(1)
+#define GICV5_IRS_CR0_IRSEN BIT(0)
+
+#define GICV5_IRS_SPI_SELR_ID GENMASK(23, 0)
+#define GICV5_IRS_SPI_CFGR_TM BIT(0)
+
#define GICV5_IRS_SYNC_STATUSR_IDLE BIT(0)
#define GICV5_IRS_SPI_STATUSR_IDLE BIT(0)
#define GICV5_IRS_PE_STATUSR_IDLE BIT(0)
@@ -155,6 +162,10 @@
#define GICV5_GICR_CDNMIA_TYPE_MASK GENMASK_ULL(31, 29)
#define GICV5_GICR_CDNMIA_ID_MASK GENMASK_ULL(23, 0)
+#define GICV5_HWIRQ_TYPE GENMASK_ULL(31, 29)
+#define GICV5_HWIRQ_ID GENMASK_ULL(23, 0)
+#define GICV5_HWIRQ_TYPE_SPI 0x3
+
#define gicr_insn(insn) read_sysreg_s(GICV5_OP_GICR_##insn)
#define gic_insn(v, insn) write_sysreg_s(v, GICV5_OP_GIC_##insn)
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 47/49] KVM: selftests: Add VGICv5 LPI delivery tests
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (45 preceding siblings ...)
2026-08-07 11:36 ` [PATCH v5 46/49] KVM: selftests: Add VGICv5 SPI injection tests Sascha Bischoff
@ 2026-08-07 11:36 ` Sascha Bischoff
2026-08-07 11:37 ` [PATCH v5 48/49] KVM: selftests: Add VGICv5 IST save/restore coverage Sascha Bischoff
2026-08-07 11:37 ` [PATCH v5 49/49] KVM: selftests: Add VGICv5 sparse vCPU IDs test Sascha Bischoff
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:36 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Add a two-VPE VGICv5 LPI delivery test. LPIs depend on an IST whose
base is provided by the guest through IRS_IST_BASER, which causes KVM
to allocate and provide an LPI IST to the hardware. Exercise the
virtual IRS ID registers and IST base configuration path before
checking interrupt delivery.
Back the guest's LPI IST with a dedicated userspace memslot and
identity-map it into the guest at the same IPA and VA. This lets the
guest provide that address to the emulated IRS through IRS_IST_BASER
while the host test still allocates and owns the backing memory.
Have VPE0 enable the IRS, derive a valid linear LPI IST configuration
from the virtual IRS ID registers, provide the IST base through
IRS_IST_BASER, configure one LPI for each VPE, and send an LPI to
VPE1. While the BASER is valid, attempt to change its address and
verify that the write is ignored. VPE1 consumes the first LPI and
replies with a second, which VPE0 consumes before completing the test.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
tools/testing/selftests/kvm/arm64/vgic_v5.c | 247 +++++++++++++++++-
.../selftests/kvm/include/arm64/gic_v5.h | 16 ++
2 files changed, 261 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
index 23fbd2db50c43..71bda5661c74e 100644
--- a/tools/testing/selftests/kvm/arm64/vgic_v5.c
+++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
@@ -13,11 +13,17 @@
#include "vgic.h"
#define NR_VCPUS 1
+#define VGIC_V5_LPI_NR_VCPUS 2
+#define VGIC_V5_LPI_MEMSLOT 1
#define VGIC_V5_NR_PRIVATE_IRQS 64
#define VGIC_V5_DEFAULT_NR_SPIS 32
#define VGIC_V5_MAX_NR_SPIS BIT(10)
#define VGIC_V5_IRS_SIZE 0x20000
#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 LPI_TEST_TO_VPE1 0
+#define LPI_TEST_TO_VPE0 1
static u64 max_phys_size;
@@ -33,6 +39,8 @@ struct vm_gic {
#define GUEST_CMD_IRQ_DIEOI 11
#define GUEST_CMD_IS_AWAKE 12
#define GUEST_CMD_IS_READY 13
+#define GUEST_CMD_LPI_SENT 14
+#define GUEST_CMD_LPI_REPLIED 15
static struct kvm_vgic_v5_ist vgic_v5_ist_attr(void *spi_ist, size_t spi_size,
void *lpi_ist, size_t lpi_size)
@@ -47,11 +55,22 @@ 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 u64 gicv5_hwirq(u32 type, u32 intid)
+{
+ return FIELD_PREP(GICV5_HWIRQ_TYPE, type) |
+ FIELD_PREP(GICV5_HWIRQ_ID, intid);
+}
+
+static u64 gicv5_lpi_hwirq(u32 lpi)
+{
+ return gicv5_hwirq(GICV5_HWIRQ_TYPE_LPI, lpi);
+}
static u64 gicv5_spi_hwirq(u32 spi)
{
- return FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_SPI) |
- FIELD_PREP(GICV5_HWIRQ_ID, spi);
+ return gicv5_hwirq(GICV5_HWIRQ_TYPE_SPI, spi);
}
static void gicv5_setup_and_enable_hwirq(u64 hwirq, u32 target_vpe)
@@ -73,6 +92,18 @@ static void gicv5_enable_spi(u32 spi, u32 target_vpe)
gicv5_setup_and_enable_hwirq(gicv5_spi_hwirq(spi), target_vpe);
}
+static void gicv5_enable_lpi(u32 lpi, u32 target_vpe)
+{
+ gicv5_setup_and_enable_hwirq(gicv5_lpi_hwirq(lpi), target_vpe);
+}
+
+static void gicv5_send_lpi(u32 lpi)
+{
+ u64 hwirq = gicv5_lpi_hwirq(lpi);
+
+ gic_insn(hwirq | GICV5_GIC_CDPEND_PENDING_MASK, CDPEND);
+}
+
static void gicv5_wait_for_irs_idle(u32 reg, u32 idle)
{
int i;
@@ -102,6 +133,58 @@ static void gicv5_configure_spi(u32 spi, bool level)
GICV5_IRS_SPI_STATUSR_IDLE);
}
+static u32 gicv5_lpi_istsz(u32 idr2, u32 lpi_id_bits)
+{
+ if (!(idr2 & GICV5_IRS_IDR2_ISTMD))
+ return GICV5_IRS_IST_CFGR_ISTSZ_4;
+
+ if (lpi_id_bits >= FIELD_GET(GICV5_IRS_IDR2_ISTMD_SZ, idr2))
+ return GICV5_IRS_IST_CFGR_ISTSZ_16;
+
+ return GICV5_IRS_IST_CFGR_ISTSZ_8;
+}
+
+static void gicv5_configure_lpi_ist(void)
+{
+ u32 idr2, min_lpi_id_bits, lpi_id_bits, id_bits, istsz;
+ u64 val;
+
+ idr2 = readl(GICV5_IRS_CONFIG_BASE_GVA + GICV5_IRS_IDR2);
+ GUEST_ASSERT(idr2 & GICV5_IRS_IDR2_LPI);
+
+ min_lpi_id_bits = FIELD_GET(GICV5_IRS_IDR2_MIN_LPI_ID_BITS, idr2);
+ id_bits = FIELD_GET(GICV5_IRS_IDR2_ID_BITS, idr2);
+ /* Default to 32 LPIs, unless the min requirement is higher */
+ lpi_id_bits = max(5U, min_lpi_id_bits);
+ GUEST_ASSERT(lpi_id_bits <= id_bits);
+
+ istsz = gicv5_lpi_istsz(idr2, lpi_id_bits);
+ /*
+ * We allocate a fixed-size IST buffer on the host to avoid dynamic
+ * allocation from the guest. Make sure it fits the linear IST described
+ * by IDR2 before programming IRS_IST_BASER.
+ */
+ GUEST_ASSERT((BIT_ULL(lpi_id_bits) << (istsz + 2)) <= VGIC_V5_LPI_IST_SIZE);
+
+ val = FIELD_PREP(GICV5_IRS_IST_CFGR_LPI_ID_BITS, lpi_id_bits) |
+ FIELD_PREP(GICV5_IRS_IST_CFGR_ISTSZ, istsz);
+ writel(val, GICV5_IRS_CONFIG_BASE_GVA + GICV5_IRS_IST_CFGR);
+
+ val = FIELD_PREP(GICV5_IRS_IST_BASER_ADDR_MASK,
+ VGIC_V5_LPI_IST_BASE_GPA >> GICV5_IRS_IST_BASER_ADDR_SHIFT);
+ val |= GICV5_IRS_IST_BASER_VALID;
+ writeq(val, GICV5_IRS_CONFIG_BASE_GVA + GICV5_IRS_IST_BASER);
+
+ GUEST_ASSERT_EQ(readl(GICV5_IRS_CONFIG_BASE_GVA + GICV5_IRS_IST_STATUSR),
+ GICV5_IRS_IST_STATUSR_IDLE);
+
+ /* The address is immutable while the BASER is valid. */
+ writeq(val ^ FIELD_PREP(GICV5_IRS_IST_BASER_ADDR_MASK, 1),
+ GICV5_IRS_CONFIG_BASE_GVA + GICV5_IRS_IST_BASER);
+ GUEST_ASSERT_EQ(readq(GICV5_IRS_CONFIG_BASE_GVA +
+ GICV5_IRS_IST_BASER), val);
+}
+
static void gicv5_enable_irs(void)
{
writel(GICV5_IRS_CR0_IRSEN,
@@ -109,6 +192,12 @@ static void gicv5_enable_irs(void)
gicv5_wait_for_irs_idle(GICV5_IRS_CR0, GICV5_IRS_CR0_IDLE);
}
+static void gicv5_configure_test_lpis(void)
+{
+ gicv5_enable_lpi(LPI_TEST_TO_VPE1, 1);
+ gicv5_enable_lpi(LPI_TEST_TO_VPE0, 0);
+}
+
static void guest_ppi_irq_handler(struct ex_regs *regs)
{
bool valid;
@@ -209,6 +298,67 @@ static void guest_spi_line_code(void)
wfi();
}
+static void guest_lpi_irq_handler(struct ex_regs *regs)
+{
+ u32 vcpu_id = guest_get_vcpuid();
+ u32 expected_lpi = vcpu_id ? LPI_TEST_TO_VPE1 : LPI_TEST_TO_VPE0;
+ u64 expected_hwirq = gicv5_lpi_hwirq(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);
+
+ if (vcpu_id) {
+ gicv5_send_lpi(LPI_TEST_TO_VPE0);
+ GUEST_SYNC(GUEST_CMD_LPI_REPLIED);
+ while (1)
+ wfi();
+ }
+
+ GUEST_DONE();
+}
+
+static void guest_lpi_code(void)
+{
+ u32 vcpu_id = guest_get_vcpuid();
+
+ local_irq_disable();
+
+ if (!vcpu_id) {
+ gicv5_enable_irs();
+ gicv5_configure_lpi_ist();
+ gicv5_configure_test_lpis();
+ WRITE_ONCE(lpi_ist_ready, true);
+ }
+
+ /* Go bang if we run VCPU1 before VCPU0 */
+ GUEST_ASSERT(READ_ONCE(lpi_ist_ready));
+
+ gicv5_cpu_enable_interrupts();
+ local_irq_enable();
+
+ GUEST_SYNC(GUEST_CMD_IS_READY);
+
+ if (!vcpu_id) {
+ gicv5_send_lpi(LPI_TEST_TO_VPE1);
+ GUEST_SYNC(GUEST_CMD_LPI_SENT);
+ }
+
+ while (1)
+ wfi();
+}
+
/* we don't want to assert on run execution, hence that helper */
static int run_vcpu(struct kvm_vcpu *vcpu)
{
@@ -279,6 +429,48 @@ static void vgic_v5_spi_line_vm_create(struct vm_gic *v,
KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
}
+static void vgic_v5_lpi_vm_create(struct vm_gic *v,
+ struct kvm_vcpu *vcpus[VGIC_V5_LPI_NR_VCPUS])
+{
+ unsigned int nr_lpi_ist_pages;
+ u64 attr;
+ int i;
+
+ v->gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ 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);
+
+ for (i = 0; i < VGIC_V5_LPI_NR_VCPUS; i++) {
+ vcpus[i] = vm_vcpu_add(v->vm, i, guest_lpi_code);
+ TEST_ASSERT(vcpus[i], "Failed to create vCPU");
+ }
+
+ vm_init_descriptor_tables(v->vm);
+ vm_install_exception_handler(v->vm, VECTOR_IRQ_CURRENT,
+ guest_lpi_irq_handler);
+
+ for (i = 0; i < VGIC_V5_LPI_NR_VCPUS; i++)
+ vcpu_init_descriptor_tables(vcpus[i]);
+
+ attr = GICV5_IRS_CONFIG_BASE_GPA;
+ kvm_device_attr_set(v->gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &attr);
+ vgic_v5_map_irs(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,
+ VGIC_V5_LPI_IST_BASE_GPA,
+ VGIC_V5_LPI_MEMSLOT,
+ nr_lpi_ist_pages, 0);
+ /* Map the IST at VA == IPA so the guest can program the same BASER. */
+ virt_map(v->vm, VGIC_V5_LPI_IST_BASE_GPA,
+ VGIC_V5_LPI_IST_BASE_GPA, nr_lpi_ist_pages);
+
+ kvm_device_attr_set(v->gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+}
+
static void vgic_v5_run_spi_line_test(u32 nr_spis, u32 expected_spi,
bool level_sensitive,
bool lower_before_run)
@@ -1170,6 +1362,54 @@ static void test_vgic_v5_spis(void)
vgic_v5_run_spi_line_test(VGIC_V5_DEFAULT_NR_SPIS, 2, true, false);
}
+static void test_vgic_v5_lpis(void)
+{
+ struct kvm_vcpu *vcpus[VGIC_V5_LPI_NR_VCPUS];
+ struct ucall uc;
+ struct vm_gic v;
+ int ret;
+
+ lpi_ist_ready = false;
+ vgic_v5_lpi_vm_create(&v, vcpus);
+ sync_global_to_guest(v.vm, lpi_ist_ready);
+
+ /* VPE0 programs a linear LPI IST from the virtual IRS ID registers. */
+ ret = run_vcpu(vcpus[0]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 LPI vCPU0");
+ TEST_ASSERT(get_ucall(vcpus[0], &uc) == UCALL_SYNC &&
+ uc.args[1] == GUEST_CMD_IS_READY,
+ "GICv5 LPI vCPU0 did not become ready");
+
+ /* VPE1 waits for the shared LPI setup and enables its CPU interface. */
+ ret = run_vcpu(vcpus[1]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 LPI vCPU1");
+ TEST_ASSERT(get_ucall(vcpus[1], &uc) == UCALL_SYNC &&
+ uc.args[1] == GUEST_CMD_IS_READY,
+ "GICv5 LPI vCPU1 did not become ready");
+
+ /* VPE0 sends an LPI to VPE1. */
+ ret = run_vcpu(vcpus[0]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 LPI vCPU0 sender");
+ TEST_ASSERT(get_ucall(vcpus[0], &uc) == UCALL_SYNC &&
+ uc.args[1] == GUEST_CMD_LPI_SENT,
+ "GICv5 LPI vCPU0 did not send LPI");
+
+ /* VPE1 consumes the LPI and replies with another LPI. */
+ ret = run_vcpu(vcpus[1]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 LPI vCPU1 receiver");
+ TEST_ASSERT(get_ucall(vcpus[1], &uc) == UCALL_SYNC &&
+ uc.args[1] == GUEST_CMD_LPI_REPLIED,
+ "GICv5 LPI vCPU1 did not reply");
+
+ /* VPE0 consumes the reply LPI and ends the test. */
+ ret = run_vcpu(vcpus[0]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 LPI vCPU0 receiver");
+ TEST_ASSERT(get_ucall(vcpus[0], &uc) == UCALL_DONE,
+ "GICv5 LPI vCPU0 did not complete");
+
+ vm_gic_destroy(&v);
+}
+
/*
* Returns 0 if it's possible to create GIC device of a given type (V5).
*/
@@ -1224,6 +1464,9 @@ void run_tests(u32 gic_dev_type)
pr_info("Test VGICv5 SPIs\n");
test_vgic_v5_spis();
+
+ pr_info("Test VGICv5 LPIs\n");
+ test_vgic_v5_lpis();
}
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 091d9dbd3c01d..2d455515563e0 100644
--- a/tools/testing/selftests/kvm/include/arm64/gic_v5.h
+++ b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
@@ -80,6 +80,14 @@
#define GICV5_IRS_IDR1_PRIORITY_BITS GENMASK(22, 20)
+#define GICV5_IRS_IDR2_ISTMD_SZ GENMASK(19, 15)
+#define GICV5_IRS_IDR2_ISTMD BIT(14)
+#define GICV5_IRS_IDR2_IST_L2SZ GENMASK(13, 11)
+#define GICV5_IRS_IDR2_IST_LEVELS BIT(10)
+#define GICV5_IRS_IDR2_MIN_LPI_ID_BITS GENMASK(9, 6)
+#define GICV5_IRS_IDR2_LPI BIT(5)
+#define GICV5_IRS_IDR2_ID_BITS GENMASK(4, 0)
+
#define GICV5_IRS_CR0_IDLE BIT(1)
#define GICV5_IRS_CR0_IRSEN BIT(0)
@@ -94,6 +102,13 @@
#define GICV5_IRS_IST_BASER_ADDR_MASK GENMASK_ULL(55, 6)
#define GICV5_IRS_IST_BASER_VALID BIT_ULL(0)
#define GICV5_IRS_IST_BASER_ADDR_SHIFT 6ULL
+#define GICV5_IRS_IST_CFGR_STRUCTURE BIT(16)
+#define GICV5_IRS_IST_CFGR_ISTSZ GENMASK(8, 7)
+#define GICV5_IRS_IST_CFGR_L2SZ GENMASK(6, 5)
+#define GICV5_IRS_IST_CFGR_LPI_ID_BITS GENMASK(4, 0)
+#define GICV5_IRS_IST_CFGR_ISTSZ_4 0b00
+#define GICV5_IRS_IST_CFGR_ISTSZ_8 0b01
+#define GICV5_IRS_IST_CFGR_ISTSZ_16 0b10
/*
* Definitions for GICv5 instructions for the Current Domain
@@ -164,6 +179,7 @@
#define GICV5_HWIRQ_TYPE GENMASK_ULL(31, 29)
#define GICV5_HWIRQ_ID GENMASK_ULL(23, 0)
+#define GICV5_HWIRQ_TYPE_LPI 0x2
#define GICV5_HWIRQ_TYPE_SPI 0x3
#define gicr_insn(insn) read_sysreg_s(GICV5_OP_GICR_##insn)
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 48/49] KVM: selftests: Add VGICv5 IST save/restore coverage
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (46 preceding siblings ...)
2026-08-07 11:36 ` [PATCH v5 47/49] KVM: selftests: Add VGICv5 LPI delivery tests Sascha Bischoff
@ 2026-08-07 11:37 ` Sascha Bischoff
2026-08-07 11:37 ` [PATCH v5 49/49] KVM: selftests: Add VGICv5 sparse vCPU IDs test Sascha Bischoff
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:37 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
Add end-to-end VGICv5 IST save/restore coverage. The earlier IST
attribute test checks only API rejection cases; this test verifies
that interrupt state saved from the ISTs can be restored into a fresh
VM before first run and then consumed by the guest.
Build a source VM that configures SPI and LPI enable, priority, and
affinity state from the guest. Inject selected SPIs from userspace,
save the IRS register state, and save both the SPI and LPI IST images
through KVM_DEV_ARM_VGIC_GRP_IST using userspace-provided buffers.
Create a restore VM and verify that IST restore is rejected before the
IRS state has been restored. Restore the IRS registers, validate bad
or missing LPI storage, and restore the saved SPI and LPI IST images.
Before the host LPI IST is allocated, verify that userspace can change
or invalidate IRS_IST_BASER. Once the IST images have been restored
and the host LPI IST exists, verify that matching IRS_IST_BASER and
IRS_IST_CFGR values can be replayed, while changing either register or
clearing IRS_IST_BASER.Valid is rejected.
Before running any destination vCPU, save the ISTs again and compare
the re-saved buffers against the original saved buffers so that
dropped serialised state fails independently of interrupt delivery.
Finally, run the restored guest and check pending and enabled SPI and
LPI state across two VPEs, priority ordering through ICC_PCR_EL1,
delivery of an interrupt that was pending but disabled at save time,
and affinity for interrupts injected after restore.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
tools/testing/selftests/kvm/arm64/vgic_v5.c | 506 +++++++++++++++++++-
1 file changed, 501 insertions(+), 5 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
index 71bda5661c74e..47e147bc14b87 100644
--- a/tools/testing/selftests/kvm/arm64/vgic_v5.c
+++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
@@ -24,6 +24,14 @@
#define VGIC_V5_LPI_IST_SIZE 0x10000
#define LPI_TEST_TO_VPE1 0
#define LPI_TEST_TO_VPE0 1
+#define RESTORE_TEST_LPI_PENDING 0
+#define RESTORE_TEST_LPI_ENABLED 1
+#define RESTORE_TEST_SPI_PRIO_HIGH 0
+#define RESTORE_TEST_SPI_PRIO_LOW 1
+#define RESTORE_TEST_SPI_PENDING_DISABLED 2
+#define RESTORE_TEST_SPI_ENABLED 3
+#define VGIC_V5_IST_PRIO_HIGH 0x08
+#define VGIC_V5_IST_PRIO_LOW 0x10
static u64 max_phys_size;
@@ -41,6 +49,9 @@ struct vm_gic {
#define GUEST_CMD_IS_READY 13
#define GUEST_CMD_LPI_SENT 14
#define GUEST_CMD_LPI_REPLIED 15
+#define GUEST_CMD_IST_SOURCE_READY 16
+#define GUEST_CMD_IST_SPI_INJECT_READY 17
+#define GUEST_CMD_IST_LPI_INJECT_READY 18
static struct kvm_vgic_v5_ist vgic_v5_ist_attr(void *spi_ist, size_t spi_size,
void *lpi_ist, size_t lpi_size)
@@ -56,6 +67,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 ist_restore_irq_count[VGIC_V5_LPI_NR_VCPUS];
static u64 gicv5_hwirq(u32 type, u32 intid)
{
@@ -87,6 +99,27 @@ static void gicv5_setup_and_enable_hwirq(u64 hwirq, u32 target_vpe)
gic_insn(hwirq, CDEN);
}
+static void gicv5_set_hwirq_priority(u64 hwirq, u32 priority)
+{
+ u64 val;
+
+ val = hwirq | FIELD_PREP(GICV5_GIC_CDPRI_PRIORITY_MASK, priority);
+ gic_insn(val, CDPRI);
+}
+
+static void gicv5_set_hwirq_affinity(u64 hwirq, u32 target_vpe)
+{
+ u64 val;
+
+ val = hwirq | FIELD_PREP(GICV5_GIC_CDAFF_IAFFID_MASK, target_vpe);
+ gic_insn(val, CDAFF);
+}
+
+static void gicv5_enable_hwirq(u64 hwirq)
+{
+ gic_insn(hwirq, CDEN);
+}
+
static void gicv5_enable_spi(u32 spi, u32 target_vpe)
{
gicv5_setup_and_enable_hwirq(gicv5_spi_hwirq(spi), target_vpe);
@@ -198,6 +231,15 @@ static void gicv5_configure_test_lpis(void)
gicv5_enable_lpi(LPI_TEST_TO_VPE0, 0);
}
+static void gicv5_set_pcr(u32 priority)
+{
+ u64 pcr;
+
+ pcr = FIELD_PREP(ICC_PCR_EL1_PRIORITY, priority);
+ write_sysreg_s(pcr, SYS_ICC_PCR_EL1);
+ isb();
+}
+
static void guest_ppi_irq_handler(struct ex_regs *regs)
{
bool valid;
@@ -329,6 +371,11 @@ static void guest_lpi_irq_handler(struct ex_regs *regs)
GUEST_DONE();
}
+static void guest_unexpected_irq_handler(struct ex_regs *regs)
+{
+ GUEST_FAIL("Unexpected IRQ");
+}
+
static void guest_lpi_code(void)
{
u32 vcpu_id = guest_get_vcpuid();
@@ -359,6 +406,165 @@ static void guest_lpi_code(void)
wfi();
}
+static void restore_test_setup_hwirq(u64 hwirq, u32 target_vpe,
+ u32 priority, bool enable)
+{
+ gicv5_set_hwirq_priority(hwirq, priority);
+ gicv5_set_hwirq_affinity(hwirq, target_vpe);
+
+ if (enable)
+ gicv5_enable_hwirq(hwirq);
+}
+
+/*
+ * The source VM configures SPI and LPI state for the IST save/restore test.
+ * It leaves interrupts disabled, allowing userspace to save the state before
+ * the restore VM enables interrupts and checks delivery.
+ */
+static void guest_ist_source_code(void)
+{
+ local_irq_disable();
+
+ gicv5_enable_irs();
+ gicv5_configure_lpi_ist();
+
+ restore_test_setup_hwirq(gicv5_lpi_hwirq(RESTORE_TEST_LPI_PENDING),
+ 1, VGIC_V5_IST_PRIO_HIGH, true);
+ restore_test_setup_hwirq(gicv5_lpi_hwirq(RESTORE_TEST_LPI_ENABLED),
+ 1, VGIC_V5_IST_PRIO_HIGH, true);
+ restore_test_setup_hwirq(gicv5_spi_hwirq(RESTORE_TEST_SPI_PRIO_HIGH),
+ 0, VGIC_V5_IST_PRIO_HIGH, true);
+ restore_test_setup_hwirq(gicv5_spi_hwirq(RESTORE_TEST_SPI_PRIO_LOW),
+ 0, VGIC_V5_IST_PRIO_LOW, true);
+ restore_test_setup_hwirq(gicv5_spi_hwirq(RESTORE_TEST_SPI_ENABLED),
+ 0, VGIC_V5_IST_PRIO_HIGH, true);
+ restore_test_setup_hwirq(gicv5_spi_hwirq(RESTORE_TEST_SPI_PENDING_DISABLED),
+ 0, VGIC_V5_IST_PRIO_HIGH, false);
+
+ gicv5_send_lpi(RESTORE_TEST_LPI_PENDING);
+
+ GUEST_SYNC(GUEST_CMD_IST_SOURCE_READY);
+
+ while (1)
+ wfi();
+}
+
+/*
+ * This is the restore VM for the IST save/restore test. The IRS regs and ISTs
+ * have been restored before this code runs. Based on which VPE is running, a
+ * different set of interrupts is expected. We check that we get what we
+ * expected before terminating the test.
+ *
+ * The expected interrupts for VPE0 are:
+ * - High-priority SPI, pending and enabled before save.
+ * - Low-priority SPI, pending and enabled before save. This is only delivered
+ * after the guest lowers the masked priority via ICC_PCR_EL1.
+ * - SPI that was pending but disabled before save. This is only delivered
+ * after the guest enables it after restore.
+ * - SPI that was enabled but not pending before save. Userspace injects this
+ * after restore to check that the enable state and affinity were restored.
+ *
+ * The expected interrupts for VPE1 are:
+ * - LPI that was pending and enabled before save.
+ * - LPI that was enabled but not pending before save. The guest injects this
+ * after restore to check that the enable state and affinity were restored.
+ */
+static void guest_ist_restore_irq_handler(struct ex_regs *regs)
+{
+ u32 vcpu_id = guest_get_vcpuid();
+ u32 count = ist_restore_irq_count[vcpu_id];
+ u64 hwirq, expected_hwirq = 0;
+ u64 ia;
+
+ ia = gicr_insn(CDIA);
+ if (!GICV5_GICR_CDIA_VALID(ia))
+ return;
+
+ gsb_ack();
+ isb();
+
+ hwirq = FIELD_GET(GICV5_GICR_CDIA_INTID, ia);
+
+ if (!vcpu_id) {
+ switch (count) {
+ case 0:
+ expected_hwirq = gicv5_spi_hwirq(RESTORE_TEST_SPI_PRIO_HIGH);
+ break;
+ case 1:
+ expected_hwirq = gicv5_spi_hwirq(RESTORE_TEST_SPI_PRIO_LOW);
+ break;
+ case 2:
+ expected_hwirq = gicv5_spi_hwirq(RESTORE_TEST_SPI_PENDING_DISABLED);
+ break;
+ case 3:
+ expected_hwirq = gicv5_spi_hwirq(RESTORE_TEST_SPI_ENABLED);
+ break;
+ default:
+ GUEST_FAIL("Unexpected VPE0 IST restore interrupt");
+ }
+ } else {
+ switch (count) {
+ case 0:
+ expected_hwirq = gicv5_lpi_hwirq(RESTORE_TEST_LPI_PENDING);
+ break;
+ case 1:
+ expected_hwirq = gicv5_lpi_hwirq(RESTORE_TEST_LPI_ENABLED);
+ break;
+ default:
+ GUEST_FAIL("Unexpected VPE1 IST restore interrupt");
+ }
+ }
+
+ GUEST_ASSERT_EQ(hwirq, expected_hwirq);
+
+ gic_insn(hwirq, CDDI);
+ gic_insn(0, CDEOI);
+ ist_restore_irq_count[vcpu_id]++;
+
+ if (!vcpu_id) {
+ switch (count) {
+ case 0:
+ gicv5_set_pcr(VGIC_V5_IST_PRIO_LOW);
+ break;
+ case 1:
+ gicv5_enable_hwirq(
+ gicv5_spi_hwirq(RESTORE_TEST_SPI_PENDING_DISABLED));
+ break;
+ case 2:
+ GUEST_SYNC(GUEST_CMD_IST_SPI_INJECT_READY);
+ break;
+ case 3:
+ GUEST_DONE();
+ }
+ } else {
+ switch (count) {
+ case 0:
+ gicv5_send_lpi(RESTORE_TEST_LPI_ENABLED);
+ GUEST_SYNC(GUEST_CMD_IST_LPI_INJECT_READY);
+ break;
+ case 1:
+ GUEST_DONE();
+ }
+ }
+}
+
+static void guest_ist_restore_code(void)
+{
+ local_irq_disable();
+
+ gicv5_cpu_enable_interrupts();
+
+ if (!guest_get_vcpuid())
+ gicv5_set_pcr(VGIC_V5_IST_PRIO_HIGH);
+
+ GUEST_SYNC(GUEST_CMD_IS_READY);
+
+ local_irq_enable();
+
+ while (1)
+ wfi();
+}
+
/* we don't want to assert on run execution, hence that helper */
static int run_vcpu(struct kvm_vcpu *vcpu)
{
@@ -429,8 +635,10 @@ static void vgic_v5_spi_line_vm_create(struct vm_gic *v,
KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
}
-static void vgic_v5_lpi_vm_create(struct vm_gic *v,
- struct kvm_vcpu *vcpus[VGIC_V5_LPI_NR_VCPUS])
+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 *))
{
unsigned int nr_lpi_ist_pages;
u64 attr;
@@ -441,13 +649,13 @@ static void vgic_v5_lpi_vm_create(struct vm_gic *v,
v->gic_fd = kvm_create_device(v->vm, v->gic_dev_type);
for (i = 0; i < VGIC_V5_LPI_NR_VCPUS; i++) {
- vcpus[i] = vm_vcpu_add(v->vm, i, guest_lpi_code);
+ vcpus[i] = vm_vcpu_add(v->vm, i, guest_code);
TEST_ASSERT(vcpus[i], "Failed to create vCPU");
}
vm_init_descriptor_tables(v->vm);
vm_install_exception_handler(v->vm, VECTOR_IRQ_CURRENT,
- guest_lpi_irq_handler);
+ irq_handler);
for (i = 0; i < VGIC_V5_LPI_NR_VCPUS; i++)
vcpu_init_descriptor_tables(vcpus[i]);
@@ -463,7 +671,10 @@ static void vgic_v5_lpi_vm_create(struct vm_gic *v,
VGIC_V5_LPI_IST_BASE_GPA,
VGIC_V5_LPI_MEMSLOT,
nr_lpi_ist_pages, 0);
- /* Map the IST at VA == IPA so the guest can program the same BASER. */
+ /*
+ * Map the IST at VA == IPA so the guest can program the same BASER
+ * address.
+ */
virt_map(v->vm, VGIC_V5_LPI_IST_BASE_GPA,
VGIC_V5_LPI_IST_BASE_GPA, nr_lpi_ist_pages);
@@ -471,6 +682,21 @@ static void vgic_v5_lpi_vm_create(struct vm_gic *v,
KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
}
+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);
+}
+
+static void vgic_v5_ist_restore_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 *))
+{
+ vgic_v5_lpi_ist_vm_create(v, vcpus, guest_code, irq_handler);
+}
+
static void vgic_v5_run_spi_line_test(u32 nr_spis, u32 expected_spi,
bool level_sensitive,
bool lower_before_run)
@@ -522,6 +748,15 @@ static void vgic_v5_run_spi_line_test(u32 nr_spis, u32 expected_spi,
vm_gic_destroy(&v);
}
+static void vgic_v5_expect_sync(struct kvm_vcpu *vcpu, u64 cmd,
+ const char *msg)
+{
+ struct ucall uc;
+
+ TEST_ASSERT(get_ucall(vcpu, &uc) == UCALL_SYNC && uc.args[1] == cmd,
+ "%s", msg);
+}
+
struct vgic_region_attr {
u64 attr;
u64 size;
@@ -647,6 +882,52 @@ static const struct vgic_sysreg_attr gic_v5_cpu_sysregs[] = {
SR(SYS_ICC_PCR_EL1),
};
+static void vgic_v5_save_irs_regs(struct vm_gic *v, u64 *regs)
+{
+ int ret, i;
+
+ for (i = 0; i < ARRAY_SIZE(gic_v5_irs_regs); i++) {
+ ret = __kvm_device_attr_get(v->gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ gic_v5_irs_regs[i].attr,
+ ®s[i]);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS save failed for %s",
+ gic_v5_irs_regs[i].name);
+ }
+}
+
+static void vgic_v5_restore_irs_regs(struct vm_gic *v, u64 *regs)
+{
+ int ret, i;
+
+ for (i = 0; i < ARRAY_SIZE(gic_v5_irs_regs); i++) {
+ ret = __kvm_device_attr_set(v->gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ gic_v5_irs_regs[i].attr,
+ ®s[i]);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS restore failed for %s",
+ gic_v5_irs_regs[i].name);
+ }
+}
+
+static u64 vgic_v5_saved_irs_reg(u64 *regs, u64 attr)
+{
+ for (int i = 0; i < ARRAY_SIZE(gic_v5_irs_regs); i++) {
+ if (gic_v5_irs_regs[i].attr == attr)
+ return regs[i];
+ }
+
+ TEST_FAIL("GICv5 IRS register 0x%lx was not saved", attr);
+}
+
+static size_t vgic_v5_lpi_ist_state_size(u64 *irs_regs)
+{
+ u64 cfgr = vgic_v5_saved_irs_reg(irs_regs, GICV5_IRS_IST_CFGR);
+ u32 lpi_id_bits = FIELD_GET(GICV5_IRS_IST_CFGR_LPI_ID_BITS, cfgr);
+
+ return BIT(lpi_id_bits) * sizeof(__u32);
+}
+
static void test_vgic_v5_addr_attrs(void)
{
struct kvm_vcpu *vcpu;
@@ -1410,6 +1691,218 @@ static void test_vgic_v5_lpis(void)
vm_gic_destroy(&v);
}
+static void test_vgic_v5_ist_save_restore(void)
+{
+ static u32 lpi_ist[VGIC_V5_LPI_IST_SIZE / sizeof(u32)];
+ static u32 lpi_ist_resave[VGIC_V5_LPI_IST_SIZE / sizeof(u32)];
+ static u32 spi_ist[VGIC_V5_DEFAULT_NR_SPIS];
+ static u32 spi_ist_resave[VGIC_V5_DEFAULT_NR_SPIS];
+ static u64 irs_regs[ARRAY_SIZE(gic_v5_irs_regs)];
+ struct kvm_vcpu *vcpus[VGIC_V5_LPI_NR_VCPUS];
+ struct kvm_vgic_v5_ist ist_attr;
+ struct vm_gic src, dst;
+ u64 ist_baser, bad_ist_baser;
+ u64 ist_cfgr, bad_ist_cfgr;
+ size_t lpi_ist_size;
+ int ret;
+
+ memset(ist_restore_irq_count, 0, sizeof(ist_restore_irq_count));
+
+ vgic_v5_ist_restore_vm_create(&src, vcpus, guest_ist_source_code,
+ guest_unexpected_irq_handler);
+
+ ret = run_vcpu(vcpus[0]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 IST save source VM");
+ vgic_v5_expect_sync(vcpus[0], GUEST_CMD_IST_SOURCE_READY,
+ "GICv5 IST source guest did not become ready");
+
+ /*
+ * The guest configured enable/priority/affinity state. Make selected
+ * SPIs pending from userspace so the SPI IST save path sees them.
+ */
+ vgic_v5_spi_line(src.vm, RESTORE_TEST_SPI_PRIO_HIGH, 1);
+ vgic_v5_spi_line(src.vm, RESTORE_TEST_SPI_PRIO_LOW, 1);
+ vgic_v5_spi_line(src.vm, RESTORE_TEST_SPI_PENDING_DISABLED, 1);
+
+ vgic_v5_save_irs_regs(&src, irs_regs);
+ lpi_ist_size = vgic_v5_lpi_ist_state_size(irs_regs);
+
+ /*
+ * VCPU0 has configured the initial state. KVM must reject missing or
+ * incorrectly sized LPI storage before accepting the complete IST image.
+ */
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist), NULL, 0);
+ ret = __kvm_device_attr_get(src.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST save accepted missing LPI buffer");
+
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist), lpi_ist,
+ lpi_ist_size - sizeof(__u32));
+ ret = __kvm_device_attr_get(src.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST save accepted bad LPI size");
+
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist), lpi_ist,
+ lpi_ist_size);
+ ret = __kvm_device_attr_get(src.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(!ret, "GICv5 IST save failed");
+
+ vm_gic_destroy(&src);
+
+ /* Create the guest that we are restoring state into */
+ memset(ist_restore_irq_count, 0, sizeof(ist_restore_irq_count));
+ vgic_v5_ist_restore_vm_create(&dst, vcpus, guest_ist_restore_code,
+ guest_ist_restore_irq_handler);
+ sync_global_to_guest(dst.vm, ist_restore_irq_count);
+
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist), lpi_ist,
+ lpi_ist_size);
+ ret = __kvm_device_attr_set(dst.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST restore before IRS state");
+
+ vgic_v5_restore_irs_regs(&dst, irs_regs);
+
+ /* BASER remains mutable until the host LPI IST has been allocated. */
+ ist_baser = vgic_v5_saved_irs_reg(irs_regs, GICV5_IRS_IST_BASER);
+ bad_ist_baser = ist_baser ^
+ FIELD_PREP(GICV5_IRS_IST_BASER_ADDR_MASK, 1);
+ ret = __kvm_device_attr_set(dst.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_BASER, &bad_ist_baser);
+ TEST_ASSERT(!ret, "GICv5 rejected changed unallocated IST_BASER");
+
+ bad_ist_baser = ist_baser & ~GICV5_IRS_IST_BASER_VALID;
+ ret = __kvm_device_attr_set(dst.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_BASER, &bad_ist_baser);
+ TEST_ASSERT(!ret, "GICv5 rejected invalidated unallocated IST_BASER");
+
+ ret = __kvm_device_attr_set(dst.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_BASER, &ist_baser);
+ TEST_ASSERT(!ret, "GICv5 failed to restore IST_BASER");
+
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist), NULL, 0);
+ ret = __kvm_device_attr_set(dst.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST restore accepted missing LPI buffer");
+
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist), lpi_ist,
+ lpi_ist_size - sizeof(__u32));
+ ret = __kvm_device_attr_set(dst.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST restore accepted bad LPI size");
+
+ /* Restore the VMM-owned SPI and LPI IST images. */
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist), lpi_ist,
+ lpi_ist_size);
+ ret = __kvm_device_attr_set(dst.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(!ret, "GICv5 IST restore failed");
+
+ /* A live IST makes BASER and the guest-visible configuration immutable. */
+ ret = __kvm_device_attr_set(dst.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_BASER, &ist_baser);
+ TEST_ASSERT(!ret, "GICv5 rejected unchanged live IST_BASER");
+
+ bad_ist_baser = ist_baser ^
+ FIELD_PREP(GICV5_IRS_IST_BASER_ADDR_MASK, 1);
+ ret = __kvm_device_attr_set(dst.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_BASER, &bad_ist_baser);
+ TEST_ASSERT(ret && errno == EINVAL,
+ "GICv5 accepted changed live IST_BASER");
+
+ bad_ist_baser = ist_baser & ~GICV5_IRS_IST_BASER_VALID;
+ ret = __kvm_device_attr_set(dst.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_BASER, &bad_ist_baser);
+ TEST_ASSERT(ret && errno == EINVAL,
+ "GICv5 accepted invalidated live IST_BASER");
+
+ ist_cfgr = vgic_v5_saved_irs_reg(irs_regs, GICV5_IRS_IST_CFGR);
+ ret = __kvm_device_attr_set(dst.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_CFGR, &ist_cfgr);
+ TEST_ASSERT(!ret, "GICv5 rejected unchanged live IST_CFGR");
+
+ bad_ist_cfgr = ist_cfgr ^
+ FIELD_PREP(GICV5_IRS_IST_CFGR_LPI_ID_BITS, 1);
+ ret = __kvm_device_attr_set(dst.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_CFGR, &bad_ist_cfgr);
+ TEST_ASSERT(ret && errno == EINVAL,
+ "GICv5 accepted changed live IST_CFGR");
+
+ /*
+ * Re-save the restored IST image before the destination VM runs, so a
+ * dropped serialized field fails independently of later interrupt
+ * delivery.
+ */
+ ist_attr = vgic_v5_ist_attr(spi_ist_resave, sizeof(spi_ist_resave),
+ lpi_ist_resave, lpi_ist_size);
+ ret = __kvm_device_attr_get(dst.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(!ret, "GICv5 IST re-save failed");
+ TEST_ASSERT(!memcmp(spi_ist, spi_ist_resave, sizeof(spi_ist)),
+ "GICv5 SPI IST state changed across restore");
+ TEST_ASSERT(!memcmp(lpi_ist, lpi_ist_resave, lpi_ist_size),
+ "GICv5 LPI IST state changed across restore");
+
+ /*
+ * At this stage, the guest's IST state should match what was saved. Run
+ * each vcpu once to let it enable interrupts, etc.
+ */
+ ret = run_vcpu(vcpus[0]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 IST restore vCPU0");
+ vgic_v5_expect_sync(vcpus[0], GUEST_CMD_IS_READY,
+ "GICv5 IST restore vCPU0 did not become ready");
+
+ ret = run_vcpu(vcpus[1]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 IST restore vCPU1");
+ vgic_v5_expect_sync(vcpus[1], GUEST_CMD_IS_READY,
+ "GICv5 IST restore vCPU1 did not become ready");
+
+ /*
+ * VPE0 consumes two pending SPIs in restored priority order, then
+ * enables a restored pending-but-disabled SPI.
+ */
+ ret = run_vcpu(vcpus[0]);
+ TEST_ASSERT(!ret, "Failed to consume restored GICv5 SPIs");
+ vgic_v5_expect_sync(vcpus[0], GUEST_CMD_IST_SPI_INJECT_READY,
+ "GICv5 IST restore vCPU0 did not consume restored SPIs");
+
+ /*
+ * This SPI was enabled before save, but not pending. Inject it after
+ * restore to prove enable, priority and affinity were restored.
+ */
+ vgic_v5_spi_line(dst.vm, RESTORE_TEST_SPI_ENABLED, 1);
+
+ ret = run_vcpu(vcpus[0]);
+ TEST_ASSERT(!ret, "Failed to consume post-restore GICv5 SPI");
+ TEST_ASSERT(get_ucall(vcpus[0], NULL) == UCALL_DONE,
+ "GICv5 IST restore vCPU0 did not complete");
+
+ /*
+ * VPE1 consumes a restored pending LPI, then pends another LPI that
+ * was enabled before save but not pending.
+ */
+ ret = run_vcpu(vcpus[1]);
+ TEST_ASSERT(!ret, "Failed to consume restored GICv5 LPI");
+ vgic_v5_expect_sync(vcpus[1], GUEST_CMD_IST_LPI_INJECT_READY,
+ "GICv5 IST restore vCPU1 did not consume restored LPI");
+
+ ret = run_vcpu(vcpus[1]);
+ TEST_ASSERT(!ret, "Failed to consume post-restore GICv5 LPI");
+ TEST_ASSERT(get_ucall(vcpus[1], NULL) == UCALL_DONE,
+ "GICv5 IST restore vCPU1 did not complete");
+
+ vm_gic_destroy(&dst);
+}
+
/*
* Returns 0 if it's possible to create GIC device of a given type (V5).
*/
@@ -1467,6 +1960,9 @@ void run_tests(u32 gic_dev_type)
pr_info("Test VGICv5 LPIs\n");
test_vgic_v5_lpis();
+
+ pr_info("Test VGICv5 IST save/restore\n");
+ test_vgic_v5_ist_save_restore();
}
int main(int ac, char **av)
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v5 49/49] KVM: selftests: Add VGICv5 sparse vCPU IDs test
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (47 preceding siblings ...)
2026-08-07 11:37 ` [PATCH v5 48/49] KVM: selftests: Add VGICv5 IST save/restore coverage Sascha Bischoff
@ 2026-08-07 11:37 ` Sascha Bischoff
48 siblings, 0 replies; 51+ messages in thread
From: Sascha Bischoff @ 2026-08-07 11:37 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, fuad.tabba@linux.dev
GICv5 uses the userspace-provided vCPU ID as the VPE ID. This must
work when vCPU IDs are sparse and differ from the dense internal vCPU
index.
Add a test that creates vCPUs with IDs 17 and 3, verifies that a vCPU
with ID 512 is rejected after a GICv5 has been created, and runs both
vCPUs to check that ICC_IAFFIDR_EL1 reports the correct VPE IDs on
each.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
tools/testing/selftests/kvm/arm64/vgic_v5.c | 70 +++++++++++++++++++++
1 file changed, 70 insertions(+)
diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
index 47e147bc14b87..3eef2d2da595f 100644
--- a/tools/testing/selftests/kvm/arm64/vgic_v5.c
+++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
@@ -14,6 +14,10 @@
#define NR_VCPUS 1
#define VGIC_V5_LPI_NR_VCPUS 2
+#define VGIC_V5_SPARSE_NR_VCPUS 2
+#define VGIC_V5_SPARSE_VCPU0_ID 17
+#define VGIC_V5_SPARSE_VCPU1_ID 3
+#define VGIC_V5_SPARSE_TOO_BIG_ID 512
#define VGIC_V5_LPI_MEMSLOT 1
#define VGIC_V5_NR_PRIVATE_IRQS 64
#define VGIC_V5_DEFAULT_NR_SPIS 32
@@ -297,6 +301,13 @@ static void guest_code(void)
cpu_relax();
}
+static void guest_iaffid_code(void)
+{
+ GUEST_SYNC(FIELD_GET(ICC_IAFFIDR_EL1_IAFFID,
+ read_sysreg_s(SYS_ICC_IAFFIDR_EL1)));
+ GUEST_DONE();
+}
+
static void guest_spi_irq_handler(struct ex_regs *regs)
{
bool valid;
@@ -1508,6 +1519,62 @@ static void test_vgic_v5_cpu_sysreg_attrs(void)
vm_gic_destroy(&v);
}
+static void test_vgic_v5_sparse_vcpu_ids(void)
+{
+ struct kvm_vcpu *vcpus[VGIC_V5_SPARSE_NR_VCPUS];
+ const u32 vcpu_ids[VGIC_V5_SPARSE_NR_VCPUS] = {
+ VGIC_V5_SPARSE_VCPU0_ID,
+ VGIC_V5_SPARSE_VCPU1_ID,
+ };
+ int ret, i;
+ struct ucall uc;
+ struct vm_gic v;
+ u64 attr;
+
+ v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ 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);
+
+ /*
+ * A GICv5 VM cannot create a vCPU outside the VPE ID range. We've
+ * already created a GICv5, so it should reject VCPU creation.
+ */
+ ret = __vm_ioctl(v.vm, KVM_CREATE_VCPU,
+ (void *)(unsigned long)VGIC_V5_SPARSE_TOO_BIG_ID);
+ TEST_ASSERT(ret < 0 && errno == EINVAL,
+ "GICv5 accepted vCPU ID %u", VGIC_V5_SPARSE_TOO_BIG_ID);
+
+ /* Now, create two vCPUs with legal, albeit sparse, IDs */
+ for (i = 0; i < ARRAY_SIZE(vcpus); i++) {
+ vcpus[i] = vm_vcpu_add(v.vm, vcpu_ids[i], guest_iaffid_code);
+ TEST_ASSERT(vcpus[i], "Failed to create vCPU %u", vcpu_ids[i]);
+ }
+
+ attr = GICV5_IRS_CONFIG_BASE_GPA;
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &attr);
+ vgic_v5_map_irs(v.vm);
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ /* Check both are runnable, and read back their correct IAFFIDs */
+ for (i = 0; i < ARRAY_SIZE(vcpus); i++) {
+ /* IAFFID is the VPE ID and must match the userspace vCPU ID. */
+ ret = run_vcpu(vcpus[i]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 vCPU %u", vcpu_ids[i]);
+ TEST_ASSERT(get_ucall(vcpus[i], &uc) == UCALL_SYNC &&
+ uc.args[1] == vcpu_ids[i],
+ "GICv5 vCPU %u IAFFID mismatch", vcpu_ids[i]);
+
+ ret = run_vcpu(vcpus[i]);
+ TEST_ASSERT(!ret, "Failed to complete GICv5 vCPU %u", vcpu_ids[i]);
+ TEST_ASSERT(get_ucall(vcpus[i], NULL) == UCALL_DONE,
+ "GICv5 vCPU %u did not complete", vcpu_ids[i]);
+ }
+
+ vm_gic_destroy(&v);
+}
+
static void test_vgic_v5_ppis(u32 gic_dev_type)
{
struct kvm_vcpu *vcpus[NR_VCPUS];
@@ -1952,6 +2019,9 @@ void run_tests(u32 gic_dev_type)
pr_info("Test VGICv5 CPU sysreg attrs\n");
test_vgic_v5_cpu_sysreg_attrs();
+ pr_info("Test VGICv5 sparse vCPU IDs\n");
+ test_vgic_v5_sparse_vcpu_ids();
+
pr_info("Test VGICv5 PPIs\n");
test_vgic_v5_ppis(gic_dev_type);
--
2.34.1
^ permalink raw reply related [flat|nested] 51+ messages in thread
* Re: [PATCH v5 03/49] irqchip/gic-v5: Set up gic_kvm_info on ACPI hosts
2026-08-07 11:14 ` [PATCH v5 03/49] irqchip/gic-v5: Set up gic_kvm_info on ACPI hosts Sascha Bischoff
@ 2026-08-07 13:44 ` Lorenzo Pieralisi
0 siblings, 0 replies; 51+ messages in thread
From: Lorenzo Pieralisi @ 2026-08-07 13:44 UTC (permalink / raw)
To: Sascha Bischoff
Cc: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, nd, maz@kernel.org, oliver.upton@linux.dev,
Joey Gouly, Suzuki Poulose, yuzenghui@huawei.com,
peter.maydell@linaro.org, Timothy Hayes, fuad.tabba@linux.dev
On Fri, Aug 07, 2026 at 11:14:07AM +0000, Sascha Bischoff wrote:
> Device-tree based GICv5 probing already passes the IRS details and
> maintenance interrupt to KVM, but the ACPI path only initialises the
> irqchip and installs the ACPI IRQ model. As a result, KVM never sees
> the GICv5 host information required to probe the vGIC on ACPI systems.
>
> Add the ACPI equivalent of the DT KVM setup. Parse the MADT GICC
> entries for the maintenance interrupt, retaining the value from the
> first relevant entry and warning if later entries disagree. Warn if
> firmware describes the interrupt as edge-triggered, but register it as
> a level-sensitive, active-high GSI. Pass the resulting IRQ together
> with the IRS base and coherency information to KVM.
>
> Native GICv5 does not require a maintenance interrupt unless the
> legacy GICv3-compatible CPU interface is present, so preserve the
> existing no-maintenance-IRQ handling for that case.
>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> ---
> drivers/irqchip/irq-gic-v5.c | 95 ++++++++++++++++++++++++++++++++++--
> 1 file changed, 91 insertions(+), 4 deletions(-)
Just a bunch of nits below.
> diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
> index e7a7aedcfaf78..5b8ea8551d832 100644
> --- a/drivers/irqchip/irq-gic-v5.c
> +++ b/drivers/irqchip/irq-gic-v5.c
> @@ -1126,7 +1126,7 @@ static void gicv5_set_cpuif_idbits(void)
> #ifdef CONFIG_KVM
> static struct gic_kvm_info gic_v5_kvm_info __initdata;
>
> -static void __init gic_of_setup_kvm_info(struct device_node *node)
> +static void __init gic_setup_kvm_info(unsigned int maint_irq)
> {
> struct gicv5_irs_chip_data *irs_data = gicv5_irs_get_chip_data();
>
> @@ -1137,17 +1137,19 @@ static void __init gic_of_setup_kvm_info(struct device_node *node)
> */
> if (!gicv5_global_data.virt_capable) {
> pr_info("GIC implementation is not virtualization capable\n");
> - return;
> + goto out_dispose_maint_irq;
Isn't it cleaner to dispose the mapping in the caller (by returning an
error value) rather than here ?
I appreciate this keeps the IRQ mapping disposal in one place but it
decouples the allocation from the failure path.
> }
>
> - gic_v5_kvm_info.type = GIC_V5;
> + if (WARN_ON(!irs_data))
> + goto out_dispose_maint_irq;
>
> + gic_v5_kvm_info.type = GIC_V5;
> gic_v5_kvm_info.gicv5_irs.base = irs_data->irs_base;
> gic_v5_kvm_info.gicv5_irs.non_coherent = !!(irs_data->flags & IRS_FLAGS_NON_COHERENT);
>
> /* GIC Virtual CPU interface maintenance interrupt */
> gic_v5_kvm_info.no_maint_irq_mask = false;
> - gic_v5_kvm_info.maint_irq = irq_of_parse_and_map(node, 0);
> + gic_v5_kvm_info.maint_irq = maint_irq;
>
> /*
> * We require an MI if we have legacy support, but don't, otherwise.
> @@ -1162,11 +1164,95 @@ static void __init gic_of_setup_kvm_info(struct device_node *node)
> gic_v5_kvm_info.no_maint_irq_mask = true;
>
> vgic_set_kvm_info(&gic_v5_kvm_info);
> + return;
> +
> +out_dispose_maint_irq:
> + irq_dispose_mapping(maint_irq);
> +}
> +
> +static void __init gic_of_setup_kvm_info(struct device_node *node)
> +{
> + /* GIC Virtual CPU interface maintenance interrupt */
> + gic_setup_kvm_info(irq_of_parse_and_map(node, 0));
> +}
> +
> +#ifdef CONFIG_ACPI
> +struct gicv5_acpi_kvm_info {
> + u32 maint_irq;
> +};
> +
> +static struct gicv5_acpi_kvm_info acpi_v5_kvm_info __initdata;
> +
> +static int __init gic_acpi_parse_virt_madt_gicc(union acpi_subtable_headers *header,
> + const unsigned long end)
> +{
> + struct acpi_madt_generic_interrupt *gicc =
> + (struct acpi_madt_generic_interrupt *)header;
> + static int first_madt = true;
> +
> + if (!(gicc->flags &
> + (ACPI_MADT_ENABLED | ACPI_MADT_GICC_ONLINE_CAPABLE)))
> + return 0;
This would count as a valid parsed entry even though you don't retrieve the
maintanance interrupt number, more below.
> +
> + if (gicc->flags & ACPI_MADT_VGIC_IRQ_MODE)
> + pr_warn_once(FW_BUG "MI wrongly advertised as Edge-triggered\n");
> +
> + if (first_madt) {
> + first_madt = false;
> +
> + acpi_v5_kvm_info.maint_irq = gicc->vgic_interrupt;
> + return 0;
> + }
> +
> + if (acpi_v5_kvm_info.maint_irq != gicc->vgic_interrupt)
> + pr_warn_once(FW_BUG "MI not the same on all CPUs\n");
> +
> + return 0;
> +}
> +
> +static bool __init gic_acpi_collect_virt_info(void)
> +{
> + int count;
> +
> + count = acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_INTERRUPT,
> + gic_acpi_parse_virt_madt_gicc, 0);
> +
> + return count > 0;
> }
> +
> +static void __init gic_acpi_setup_kvm_info(void)
> +{
> + unsigned int maint_irq = 0;
> + int irq;
> +
> + if (!gic_acpi_collect_virt_info()) {
> + pr_warn("Unable to get hardware information used for virtualization\n");
> + return;
> + }
Nit: gic_acpi_collect_virt_info() might return true and you still don't get
the info you need. Now, at least one GICC entry should be enabled and online
capable (we are booting on a core after all but never overestimate ACPI tables :))
so this is theory but I thought I would mention it.
> +
> + if (acpi_v5_kvm_info.maint_irq) {
> + irq = acpi_register_gsi(NULL, acpi_v5_kvm_info.maint_irq,
> + ACPI_LEVEL_SENSITIVE,
> + ACPI_ACTIVE_HIGH);
> + if (irq > 0)
> + maint_irq = irq;
> + else
> + pr_warn("Failed to register GSI for GICv5 maintenance IRQ\n");
Another nit: do we really need the maint_irq variable ?
Thanks,
Lorenzo
> + }
> +
> + gic_setup_kvm_info(maint_irq);
> +}
> +#endif // CONFIG_ACPI
> #else
> static inline void __init gic_of_setup_kvm_info(struct device_node *node)
> {
> }
> +
> +#ifdef CONFIG_ACPI
> +static inline void __init gic_acpi_setup_kvm_info(void)
> +{
> +}
> +#endif
> #endif // CONFIG_KVM
>
> static int __init gicv5_init_common(struct fwnode_handle *parent_domain)
> @@ -1265,6 +1351,7 @@ static int __init gic_acpi_init(union acpi_subtable_headers *header, const unsig
> goto out_irs;
>
> acpi_set_irq_model(ACPI_IRQ_MODEL_GIC_V5, gic_v5_get_gsi_domain_id);
> + gic_acpi_setup_kvm_info();
>
> return 0;
>
> --
> 2.34.1
^ permalink raw reply [flat|nested] 51+ messages in thread
end of thread, other threads:[~2026-08-07 13:44 UTC | newest]
Thread overview: 51+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-07 11:12 [PATCH v5 00/49] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
2026-08-07 11:13 ` [PATCH v5 01/49] irqchip/gic-v5: Allow KVM setup without a maintenance IRQ Sascha Bischoff
2026-08-07 11:13 ` [PATCH v5 02/49] irqchip/gic-v5: Provide OF IRS config frame attrs to KVM Sascha Bischoff
2026-08-07 11:14 ` [PATCH v5 03/49] irqchip/gic-v5: Set up gic_kvm_info on ACPI hosts Sascha Bischoff
2026-08-07 13:44 ` Lorenzo Pieralisi
2026-08-07 11:14 ` [PATCH v5 04/49] KVM: arm64: gic-v5: Define remaining IRS MMIO registers Sascha Bischoff
2026-08-07 11:15 ` [PATCH v5 05/49] arm64/sysreg: Add GICv5 GIC VDPEND encoding Sascha Bischoff
2026-08-07 11:15 ` [PATCH v5 06/49] arm64/sysreg: Update ICC_CR0_EL1 with LINK and LINK_IDLE fields Sascha Bischoff
2026-08-07 11:16 ` [PATCH v5 07/49] KVM: arm64: gic-v5: Cache host IRS ID registers Sascha Bischoff
2026-08-07 11:16 ` [PATCH v5 08/49] KVM: arm64: gic-v5: Add VPE doorbell domain Sascha Bischoff
2026-08-07 11:17 ` [PATCH v5 09/49] KVM: arm64: gic-v5: Create and manage VM and VPE tables Sascha Bischoff
2026-08-07 11:17 ` [PATCH v5 10/49] KVM: arm64: gic-v5: Introduce guest IST alloc and management Sascha Bischoff
2026-08-07 11:18 ` [PATCH v5 11/49] KVM: arm64: gic-v5: Implement VMT/vIST IRS MMIO Ops Sascha Bischoff
2026-08-07 11:18 ` [PATCH v5 12/49] KVM: arm64: gic-v5: Keep GICv5 vCPU limit model-specific Sascha Bischoff
2026-08-07 11:19 ` [PATCH v5 13/49] KVM: arm64: gic-v5: Implement VPE IRS MMIO Ops Sascha Bischoff
2026-08-07 11:19 ` [PATCH v5 14/49] KVM: arm64: gic-v5: Set up VMTEs and VPE doorbells Sascha Bischoff
2026-08-07 11:20 ` [PATCH v5 15/49] KVM: arm64: gic-v5: Add resident/non-resident hyp calls Sascha Bischoff
2026-08-07 11:20 ` [PATCH v5 16/49] KVM: arm64: gic-v5: Request doorbells when VPEs enter WFI Sascha Bischoff
2026-08-07 11:21 ` [PATCH v5 17/49] KVM: arm64: gic-v5: Introduce struct vgic_v5_irs and IRS base address Sascha Bischoff
2026-08-07 11:21 ` [PATCH v5 18/49] KVM: arm64: gic-v5: Add IRS IODEV support to MMIO handlers Sascha Bischoff
2026-08-07 11:22 ` [PATCH v5 19/49] KVM: arm64: gic-v5: Add KVM_VGIC_V5_ADDR_TYPE_IRS to UAPI Sascha Bischoff
2026-08-07 11:22 ` [PATCH v5 20/49] KVM: arm64: gic-v5: Add GICv5 IRS IODEV and MMIO emulation Sascha Bischoff
2026-08-07 11:23 ` [PATCH v5 21/49] KVM: arm64: gic-v5: Initialise per-VM IRS state Sascha Bischoff
2026-08-07 11:23 ` [PATCH v5 22/49] KVM: arm64: gic-v5: Register the IRS IODEV Sascha Bischoff
2026-08-07 11:24 ` [PATCH v5 23/49] KVM: arm64: gic-v5: Set IRICHPPIDIS based on IRS enable state Sascha Bischoff
2026-08-07 11:24 ` [PATCH v5 24/49] KVM: arm64: selftests: Update vGICv5 selftest to set IRS address Sascha Bischoff
2026-08-07 11:25 ` [PATCH v5 25/49] KVM: arm64: gic-v5: Add GIC VDPEND hyp call Sascha Bischoff
2026-08-07 11:25 ` [PATCH v5 26/49] KVM: arm64: gic: Introduce set_pending_state() to irq_ops Sascha Bischoff
2026-08-07 11:26 ` [PATCH v5 27/49] KVM: arm64: gic-v5: Support SPI injection Sascha Bischoff
2026-08-07 11:26 ` [PATCH v5 28/49] Documentation: KVM: Extend VGICv5 device attribute docs Sascha Bischoff
2026-08-07 11:27 ` [PATCH v5 29/49] KVM: arm64: gic-v5: Add GICv5 SPI injection to irqfd Sascha Bischoff
2026-08-07 11:27 ` [PATCH v5 30/49] KVM: arm64: gic-v5: Mask per-vCPU PPI state in vgic_v5_finalize_ppi_state() Sascha Bischoff
2026-08-07 11:28 ` [PATCH v5 31/49] KVM: arm64: gic-v5: Add GICv5 EL1 sysreg userspace accessors Sascha Bischoff
2026-08-07 11:28 ` [PATCH v5 32/49] KVM: arm64: gic-v5: Handle userspace accesses to IRS MMIO region Sascha Bischoff
2026-08-07 11:29 ` [PATCH v5 33/49] KVM: arm64: gic-v5: Add CoreSight MMIO regs to IRS Sascha Bischoff
2026-08-07 11:29 ` [PATCH v5 34/49] KVM: arm64: gic-v5: Add VGICv5 IST save/restore UAPI Sascha Bischoff
2026-08-07 11:30 ` [PATCH v5 35/49] KVM: arm64: gic-v5: Implement save/restore mechanisms for ISTs Sascha Bischoff
2026-08-07 11:30 ` [PATCH v5 36/49] Documentation: KVM: Document KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS for VGICv5 Sascha Bischoff
2026-08-07 11:31 ` [PATCH v5 37/49] Documentation: KVM: Add KVM_DEV_ARM_VGIC_GRP_IRS_REGS to VGICv5 docs Sascha Bischoff
2026-08-07 11:31 ` [PATCH v5 38/49] Documentation: KVM: Add docs for KVM_DEV_ARM_VGIC_GRP_IST Sascha Bischoff
2026-08-07 11:32 ` [PATCH v5 39/49] Documentation: KVM: Add the VGICv5 IRS save/restore sequences Sascha Bischoff
2026-08-07 11:32 ` [PATCH v5 40/49] KVM: selftests: Add VGICv5 IRS address attribute tests Sascha Bischoff
2026-08-07 11:33 ` [PATCH v5 41/49] KVM: selftests: Add VGICv5 NR_IRQS " Sascha Bischoff
2026-08-07 11:33 ` [PATCH v5 42/49] KVM: selftests: Add VGICv5 IRS_REGS " Sascha Bischoff
2026-08-07 11:34 ` [PATCH v5 43/49] KVM: selftests: Add VGICv5 IST " Sascha Bischoff
2026-08-07 11:35 ` [PATCH v5 44/49] KVM: selftests: Add VGICv5 USERSPACE_PPIS tests Sascha Bischoff
2026-08-07 11:35 ` [PATCH v5 45/49] KVM: selftests: Add VGICv5 CPU sysreg attribute tests Sascha Bischoff
2026-08-07 11:36 ` [PATCH v5 46/49] KVM: selftests: Add VGICv5 SPI injection tests Sascha Bischoff
2026-08-07 11:36 ` [PATCH v5 47/49] KVM: selftests: Add VGICv5 LPI delivery tests Sascha Bischoff
2026-08-07 11:37 ` [PATCH v5 48/49] KVM: selftests: Add VGICv5 IST save/restore coverage Sascha Bischoff
2026-08-07 11:37 ` [PATCH v5 49/49] KVM: selftests: Add VGICv5 sparse vCPU IDs test Sascha Bischoff
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).