linux-arm-kernel.lists.infradead.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support
@ 2025-12-19 15:52 Sascha Bischoff
  2025-12-19 15:52 ` [PATCH v2 03/36] arm64/sysreg: Drop ICH_HFGRTR_EL2.ICC_HAPR_EL1 and make RES1 Sascha Bischoff
                   ` (36 more replies)
  0 siblings, 37 replies; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

This is the second version of the patch series to add the virtual
GICv5 [1] device (vgic_v5). Only PPIs are supported by this initial
series, and the vgic_v5 implementation is restricted to the CPU
interface, only. Further patch series are to follow in due course, and
will add support for SPIs, LPIs, the GICv5 IRS, and the GICv5 ITS.

The first version of this series can be found at [2].

The noteworthy changes since V1 of this series are:

1. Added detection of implemented PPIs on a GICv5 host at boot time.
2. Added masking for PPIs that are presented to guests. Only PPIs with
   owners and the SW_PPI (if present) are exposed.
3. Added trapping and masking for all guest writes to the writable
   ICC_PPI_x_EL1 registers. The writes are masked with the subset of
   PPIs exposed to the guest. This ensures that the guest cannot
   discover PPIs that are not intentionally exposed to it.
4. Added an new UAPI to allow userspace to query which PPIs can be
   driven via KVM_IRQ_LINE. For the time being, only the SW_ PPI is
   exposed for guest control.
5. Interrupt type checks are now re-worked to be more readable and
   scalable. Thanks, Marc.

I have addressed some, but alas not all (see below), review comments
against v1 of the series. Thanks a lot Marc, Joey, and Lorenzo!

I'm posting V2 even though I've yet to address all review comments as
I shall be out of office for the next 2 weeks. Therefore, I wanted to
make sure that the latest version was available for anyone to take a
look. Any outstanding and new comments will be addressed on my return.

The main outstanding changes are:

1. Rework the PPI save/restore mechanisms to remove the _entry/_exit
   from the vcpu, and instead use per-cpu data structures.
2. PPI injection needs clean up around shadow state tracking an
   manipulation.
3. PPI state tracking needs to be heaviliy optimised to reduce the
   number of locks taken and PPIs iterated over. This is now possible
   with the introduction of the masks, but remains to be implemented.
4. Allow for sparse PPI state storage. Given that most of the 128
   potential PPIs will never be used with a guest, it is extremely
   wasteful to allocate storage for them.

These changes are based on v6.19-rc1. As before, the first commit has
been cherry-picked from Marc's VTCR sanitisation series [3].

For those that are interested in the overall direction of the GICv5
KVM support, Marc Zyngier has very kindly agreed to host the full
*WIP* set of GICv5 KVM patches which can be found at [4]. These are
not intended for review, and require some serious clean up, but should
give a rough idea of what is still to come.

Thanks all for the feedback so far and any more you have,
Sascha

[1] https://developer.arm.com/documentation/aes0070/latest
[2] https://lore.kernel.org/all/20251212152215.675767-1-sascha.bischoff@arm.com/
[3] https://lore.kernel.org/all/20251210173024.561160-1-maz@kernel.org/
[4] https://git.kernel.org/pub/scm/linux/kernel/git/maz/arm-platforms.git/log/?h=kvm-arm64/gicv5-full

Marc Zyngier (1):
  KVM: arm64: Account for RES1 bits in DECLARE_FEAT_MAP() and co

Sascha Bischoff (35):
  KVM: arm64: gic-v3: Switch vGIC-v3 to use generated ICH_VMCR_EL2
  arm64/sysreg: Drop ICH_HFGRTR_EL2.ICC_HAPR_EL1 and make RES1
  arm64/sysreg: Add remaining GICv5 ICC_ & ICH_ sysregs for KVM support
  arm64/sysreg: Add GICR CDNMIA encoding
  KVM: arm64: gic-v5: Add ARM_VGIC_V5 device to KVM headers
  KVM: arm64: gic: Introduce interrupt type helpers
  KVM: arm64: Introduce kvm_call_hyp_nvhe_res()
  KVM: arm64: gic-v5: Detect implemented PPIs on boot
  KVM: arm64: gic-v5: Sanitize ID_AA64PFR2_EL1.GCIE
  KVM: arm64: gic-v5: Support GICv5 FGTs & FGUs
  KVM: arm64: gic-v5: Add emulation for ICC_IAFFIDR_EL1 accesses
  KVM: arm64: gic: Set vgic_model before initing private IRQs
  KVM: arm64: gic-v5: Add vgic-v5 save/restore hyp interface
  KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore
  KVM: arm64: gic-v5: Implement direct injection of PPIs
  KVM: arm64: gic: Introduce irq_queue and set_pending_state to irq_ops
  KVM: arm64: gic-v5: Implement PPI interrupt injection
  KVM: arm64: gic-v5: Check for pending PPIs
  KVM: arm64: gic-v5: Init Private IRQs (PPIs) for GICv5
  KVM: arm64: gic-v5: Finalize GICv5 PPIs and generate mask
  KVM: arm64: gic-v5: Trap and mask guest PPI register accesses
  KVM: arm64: gic-v5: Support GICv5 interrupts with KVM_IRQ_LINE
  KVM: arm64: gic-v5: Create, init vgic_v5
  KVM: arm64: gic-v5: Reset vcpu state
  KVM: arm64: gic-v5: Bump arch timer for GICv5
  KVM: arm64: gic-v5: Mandate architected PPI for PMU emulation on GICv5
  KVM: arm64: gic: Hide GICv5 for protected guests
  KVM: arm64: gic-v5: Hide FEAT_GCIE from NV GICv5 guests
  KVM: arm64: gic-v5: Introduce kvm_arm_vgic_v5_ops and register them
  KVM: arm64: gic-v5: Set ICH_VCTLR_EL2.En on boot
  irqchip/gic-v5: Check if impl is virt capable
  KVM: arm64: gic-v5: Probe for GICv5 device
  Documentation: KVM: Introduce documentation for VGICv5
  KVM: arm64: selftests: Introduce a minimal GICv5 PPI selftest
  KVM: arm64: gic-v5: Communicate userspace-drivable PPIs via a UAPI

 Documentation/virt/kvm/api.rst                |   6 +-
 .../virt/kvm/devices/arm-vgic-v5.rst          |  50 ++
 Documentation/virt/kvm/devices/index.rst      |   1 +
 arch/arm64/include/asm/el2_setup.h            |   3 +-
 arch/arm64/include/asm/kvm_asm.h              |   5 +
 arch/arm64/include/asm/kvm_host.h             |  35 +-
 arch/arm64/include/asm/kvm_hyp.h              |  10 +
 arch/arm64/include/asm/sysreg.h               |  28 +-
 arch/arm64/include/asm/vncr_mapping.h         |   3 +
 arch/arm64/include/uapi/asm/kvm.h             |   1 +
 arch/arm64/kvm/arch_timer.c                   | 112 +++-
 arch/arm64/kvm/arm.c                          |  29 +-
 arch/arm64/kvm/config.c                       | 145 ++++-
 arch/arm64/kvm/emulate-nested.c               | 123 +++-
 arch/arm64/kvm/hyp/include/hyp/switch.h       |  27 +
 arch/arm64/kvm/hyp/nvhe/Makefile              |   2 +-
 arch/arm64/kvm/hyp/nvhe/hyp-main.c            |  43 ++
 arch/arm64/kvm/hyp/nvhe/switch.c              |  15 +
 arch/arm64/kvm/hyp/nvhe/sys_regs.c            |   8 +
 arch/arm64/kvm/hyp/vgic-v3-sr.c               |  64 +-
 arch/arm64/kvm/hyp/vgic-v5-sr.c               | 146 +++++
 arch/arm64/kvm/hyp/vhe/Makefile               |   2 +-
 arch/arm64/kvm/nested.c                       |   5 +
 arch/arm64/kvm/pmu-emul.c                     |  21 +-
 arch/arm64/kvm/sys_regs.c                     | 190 +++++-
 arch/arm64/kvm/vgic/vgic-init.c               | 123 +++-
 arch/arm64/kvm/vgic/vgic-kvm-device.c         |  99 ++-
 arch/arm64/kvm/vgic/vgic-mmio.c               |  28 +-
 arch/arm64/kvm/vgic/vgic-v3-nested.c          |   8 +-
 arch/arm64/kvm/vgic/vgic-v3.c                 |  48 +-
 arch/arm64/kvm/vgic/vgic-v5.c                 | 571 +++++++++++++++++-
 arch/arm64/kvm/vgic/vgic.c                    | 125 +++-
 arch/arm64/kvm/vgic/vgic.h                    |  70 ++-
 arch/arm64/tools/sysreg                       | 482 ++++++++++++++-
 drivers/irqchip/irq-gic-v5-irs.c              |   4 +
 drivers/irqchip/irq-gic-v5.c                  |  10 +
 include/kvm/arm_arch_timer.h                  |   7 +-
 include/kvm/arm_pmu.h                         |   5 +-
 include/kvm/arm_vgic.h                        | 160 ++++-
 include/linux/irqchip/arm-gic-v5.h            |  15 +
 include/linux/kvm_host.h                      |   1 +
 include/uapi/linux/kvm.h                      |   2 +
 tools/arch/arm64/include/uapi/asm/kvm.h       |   1 +
 tools/include/uapi/linux/kvm.h                |   2 +
 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 tools/testing/selftests/kvm/arm64/vgic_v5.c   | 248 ++++++++
 .../selftests/kvm/include/arm64/gic_v5.h      | 148 +++++
 47 files changed, 2965 insertions(+), 267 deletions(-)
 create mode 100644 Documentation/virt/kvm/devices/arm-vgic-v5.rst
 create mode 100644 arch/arm64/kvm/hyp/vgic-v5-sr.c
 create mode 100644 tools/testing/selftests/kvm/arm64/vgic_v5.c
 create mode 100644 tools/testing/selftests/kvm/include/arm64/gic_v5.h

-- 
2.34.1


^ permalink raw reply	[flat|nested] 100+ messages in thread

* [PATCH v2 01/36] KVM: arm64: Account for RES1 bits in DECLARE_FEAT_MAP() and co
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
  2025-12-19 15:52 ` [PATCH v2 03/36] arm64/sysreg: Drop ICH_HFGRTR_EL2.ICC_HAPR_EL1 and make RES1 Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-06 17:23   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 02/36] KVM: arm64: gic-v3: Switch vGIC-v3 to use generated ICH_VMCR_EL2 Sascha Bischoff
                   ` (34 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

From: Marc Zyngier <maz@kernel.org>

None of the registers we manage in the feature dependency infrastructure
so far has any RES1 bit. This is about to change, as VTCR_EL2 has
its bit 31 being RES1.

In order to not fail the consistency checks by not describing a bit,
add RES1 bits to the set of immutable bits. This requires some extra
surgery for the FGT handling, as we now need to track RES1 bits there
as well.

There are no RES1 FGT bits *yet*. Watch this space.

Signed-off-by: Marc Zyngier <maz@kernel.org>
---
 arch/arm64/include/asm/kvm_host.h |  1 +
 arch/arm64/kvm/config.c           | 25 +++++++-------
 arch/arm64/kvm/emulate-nested.c   | 55 +++++++++++++++++--------------
 3 files changed, 45 insertions(+), 36 deletions(-)

diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index ac7f970c78830..b552a1e03848c 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -638,6 +638,7 @@ struct fgt_masks {
 	u64		mask;
 	u64		nmask;
 	u64		res0;
+	u64		res1;
 };
 
 extern struct fgt_masks hfgrtr_masks;
diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
index 24bb3f36e9d59..3845b188551b6 100644
--- a/arch/arm64/kvm/config.c
+++ b/arch/arm64/kvm/config.c
@@ -16,14 +16,14 @@
  */
 struct reg_bits_to_feat_map {
 	union {
-		u64	bits;
-		u64	*res0p;
+		u64		 bits;
+		struct fgt_masks *masks;
 	};
 
 #define	NEVER_FGU	BIT(0)	/* Can trap, but never UNDEF */
 #define	CALL_FUNC	BIT(1)	/* Needs to evaluate tons of crap */
 #define	FIXED_VALUE	BIT(2)	/* RAZ/WI or RAO/WI in KVM */
-#define	RES0_POINTER	BIT(3)	/* Pointer to RES0 value instead of bits */
+#define	MASKS_POINTER	BIT(3)	/* Pointer to fgt_masks struct instead of bits */
 
 	unsigned long	flags;
 
@@ -92,8 +92,8 @@ struct reg_feat_map_desc {
 #define NEEDS_FEAT_FIXED(m, ...)			\
 	__NEEDS_FEAT_FLAG(m, FIXED_VALUE, bits, __VA_ARGS__, 0)
 
-#define NEEDS_FEAT_RES0(p, ...)				\
-	__NEEDS_FEAT_FLAG(p, RES0_POINTER, res0p, __VA_ARGS__)
+#define NEEDS_FEAT_MASKS(p, ...)				\
+	__NEEDS_FEAT_FLAG(p, MASKS_POINTER, masks, __VA_ARGS__)
 
 /*
  * Declare the dependency between a set of bits and a set of features,
@@ -109,19 +109,20 @@ struct reg_feat_map_desc {
 #define DECLARE_FEAT_MAP(n, r, m, f)					\
 	struct reg_feat_map_desc n = {					\
 		.name			= #r,				\
-		.feat_map		= NEEDS_FEAT(~r##_RES0, f), 	\
+		.feat_map		= NEEDS_FEAT(~(r##_RES0 |	\
+						       r##_RES1), f),	\
 		.bit_feat_map		= m,				\
 		.bit_feat_map_sz	= ARRAY_SIZE(m),		\
 	}
 
 /*
  * Specialised version of the above for FGT registers that have their
- * RES0 masks described as struct fgt_masks.
+ * RESx masks described as struct fgt_masks.
  */
 #define DECLARE_FEAT_MAP_FGT(n, msk, m, f)				\
 	struct reg_feat_map_desc n = {					\
 		.name			= #msk,				\
-		.feat_map		= NEEDS_FEAT_RES0(&msk.res0, f),\
+		.feat_map		= NEEDS_FEAT_MASKS(&msk, f),	\
 		.bit_feat_map		= m,				\
 		.bit_feat_map_sz	= ARRAY_SIZE(m),		\
 	}
@@ -1168,21 +1169,21 @@ static const DECLARE_FEAT_MAP(mdcr_el2_desc, MDCR_EL2,
 			      mdcr_el2_feat_map, FEAT_AA64EL2);
 
 static void __init check_feat_map(const struct reg_bits_to_feat_map *map,
-				  int map_size, u64 res0, const char *str)
+				  int map_size, u64 resx, const char *str)
 {
 	u64 mask = 0;
 
 	for (int i = 0; i < map_size; i++)
 		mask |= map[i].bits;
 
-	if (mask != ~res0)
+	if (mask != ~resx)
 		kvm_err("Undefined %s behaviour, bits %016llx\n",
-			str, mask ^ ~res0);
+			str, mask ^ ~resx);
 }
 
 static u64 reg_feat_map_bits(const struct reg_bits_to_feat_map *map)
 {
-	return map->flags & RES0_POINTER ? ~(*map->res0p) : map->bits;
+	return map->flags & MASKS_POINTER ? (map->masks->mask | map->masks->nmask) : map->bits;
 }
 
 static void __init check_reg_desc(const struct reg_feat_map_desc *r)
diff --git a/arch/arm64/kvm/emulate-nested.c b/arch/arm64/kvm/emulate-nested.c
index 834f13fb1fb7d..75d49f83342a5 100644
--- a/arch/arm64/kvm/emulate-nested.c
+++ b/arch/arm64/kvm/emulate-nested.c
@@ -2105,23 +2105,24 @@ static u32 encoding_next(u32 encoding)
 }
 
 #define FGT_MASKS(__n, __m)						\
-	struct fgt_masks __n = { .str = #__m, .res0 = __m, }
-
-FGT_MASKS(hfgrtr_masks, HFGRTR_EL2_RES0);
-FGT_MASKS(hfgwtr_masks, HFGWTR_EL2_RES0);
-FGT_MASKS(hfgitr_masks, HFGITR_EL2_RES0);
-FGT_MASKS(hdfgrtr_masks, HDFGRTR_EL2_RES0);
-FGT_MASKS(hdfgwtr_masks, HDFGWTR_EL2_RES0);
-FGT_MASKS(hafgrtr_masks, HAFGRTR_EL2_RES0);
-FGT_MASKS(hfgrtr2_masks, HFGRTR2_EL2_RES0);
-FGT_MASKS(hfgwtr2_masks, HFGWTR2_EL2_RES0);
-FGT_MASKS(hfgitr2_masks, HFGITR2_EL2_RES0);
-FGT_MASKS(hdfgrtr2_masks, HDFGRTR2_EL2_RES0);
-FGT_MASKS(hdfgwtr2_masks, HDFGWTR2_EL2_RES0);
+	struct fgt_masks __n = { .str = #__m, .res0 = __m ## _RES0, .res1 = __m ## _RES1 }
+
+FGT_MASKS(hfgrtr_masks, HFGRTR_EL2);
+FGT_MASKS(hfgwtr_masks, HFGWTR_EL2);
+FGT_MASKS(hfgitr_masks, HFGITR_EL2);
+FGT_MASKS(hdfgrtr_masks, HDFGRTR_EL2);
+FGT_MASKS(hdfgwtr_masks, HDFGWTR_EL2);
+FGT_MASKS(hafgrtr_masks, HAFGRTR_EL2);
+FGT_MASKS(hfgrtr2_masks, HFGRTR2_EL2);
+FGT_MASKS(hfgwtr2_masks, HFGWTR2_EL2);
+FGT_MASKS(hfgitr2_masks, HFGITR2_EL2);
+FGT_MASKS(hdfgrtr2_masks, HDFGRTR2_EL2);
+FGT_MASKS(hdfgwtr2_masks, HDFGWTR2_EL2);
 
 static __init bool aggregate_fgt(union trap_config tc)
 {
 	struct fgt_masks *rmasks, *wmasks;
+	u64 rresx, wresx;
 
 	switch (tc.fgt) {
 	case HFGRTR_GROUP:
@@ -2154,24 +2155,27 @@ static __init bool aggregate_fgt(union trap_config tc)
 		break;
 	}
 
+	rresx = rmasks->res0 | rmasks->res1;
+	if (wmasks)
+		wresx = wmasks->res0 | wmasks->res1;
+
 	/*
 	 * A bit can be reserved in either the R or W register, but
 	 * not both.
 	 */
-	if ((BIT(tc.bit) & rmasks->res0) &&
-	    (!wmasks || (BIT(tc.bit) & wmasks->res0)))
+	if ((BIT(tc.bit) & rresx) && (!wmasks || (BIT(tc.bit) & wresx)))
 		return false;
 
 	if (tc.pol)
-		rmasks->mask |= BIT(tc.bit) & ~rmasks->res0;
+		rmasks->mask |= BIT(tc.bit) & ~rresx;
 	else
-		rmasks->nmask |= BIT(tc.bit) & ~rmasks->res0;
+		rmasks->nmask |= BIT(tc.bit) & ~rresx;
 
 	if (wmasks) {
 		if (tc.pol)
-			wmasks->mask |= BIT(tc.bit) & ~wmasks->res0;
+			wmasks->mask |= BIT(tc.bit) & ~wresx;
 		else
-			wmasks->nmask |= BIT(tc.bit) & ~wmasks->res0;
+			wmasks->nmask |= BIT(tc.bit) & ~wresx;
 	}
 
 	return true;
@@ -2180,7 +2184,6 @@ static __init bool aggregate_fgt(union trap_config tc)
 static __init int check_fgt_masks(struct fgt_masks *masks)
 {
 	unsigned long duplicate = masks->mask & masks->nmask;
-	u64 res0 = masks->res0;
 	int ret = 0;
 
 	if (duplicate) {
@@ -2194,10 +2197,14 @@ static __init int check_fgt_masks(struct fgt_masks *masks)
 		ret = -EINVAL;
 	}
 
-	masks->res0 = ~(masks->mask | masks->nmask);
-	if (masks->res0 != res0)
-		kvm_info("Implicit %s = %016llx, expecting %016llx\n",
-			 masks->str, masks->res0, res0);
+	if ((masks->res0 | masks->res1 | masks->mask | masks->nmask) != GENMASK(63, 0) ||
+	    (masks->res0 & masks->res1)  || (masks->res0 & masks->mask) ||
+	    (masks->res0 & masks->nmask) || (masks->res1 & masks->mask)  ||
+	    (masks->res1 & masks->nmask) || (masks->mask & masks->nmask)) {
+		kvm_info("Inconsistent masks for %s (%016llx, %016llx, %016llx, %016llx)\n",
+			 masks->str, masks->res0, masks->res1, masks->mask, masks->nmask);
+		masks->res0 = ~(masks->res1 | masks->mask | masks->nmask);
+	}
 
 	return ret;
 }
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 03/36] arm64/sysreg: Drop ICH_HFGRTR_EL2.ICC_HAPR_EL1 and make RES1
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2025-12-19 15:52 ` [PATCH v2 01/36] KVM: arm64: Account for RES1 bits in DECLARE_FEAT_MAP() and co Sascha Bischoff
                   ` (35 subsequent siblings)
  36 siblings, 0 replies; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

The GICv5 architecture is dropping the ICC_HAPR_EL1 and ICV_HAPR_EL1
system registers. These registers were never added to the sysregs, but
the traps for them were.

Drop the trap bit from the ICH_HFGRTR_EL2 and make it Res1 as per the
upcoming GICv5 spec change. Additionally, update the EL2 setup code to
not attempt to set that bit.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/include/asm/el2_setup.h | 1 -
 arch/arm64/tools/sysreg            | 2 +-
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/arch/arm64/include/asm/el2_setup.h b/arch/arm64/include/asm/el2_setup.h
index cacd20df1786e..07c12f4a69b41 100644
--- a/arch/arm64/include/asm/el2_setup.h
+++ b/arch/arm64/include/asm/el2_setup.h
@@ -225,7 +225,6 @@
 		     ICH_HFGRTR_EL2_ICC_ICSR_EL1		| \
 		     ICH_HFGRTR_EL2_ICC_PCR_EL1			| \
 		     ICH_HFGRTR_EL2_ICC_HPPIR_EL1		| \
-		     ICH_HFGRTR_EL2_ICC_HAPR_EL1		| \
 		     ICH_HFGRTR_EL2_ICC_CR0_EL1			| \
 		     ICH_HFGRTR_EL2_ICC_IDRn_EL1		| \
 		     ICH_HFGRTR_EL2_ICC_APR_EL1)
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index 8921b51866d64..dab5bfe8c9686 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -4579,7 +4579,7 @@ Field	7	ICC_IAFFIDR_EL1
 Field	6	ICC_ICSR_EL1
 Field	5	ICC_PCR_EL1
 Field	4	ICC_HPPIR_EL1
-Field	3	ICC_HAPR_EL1
+Res1	3
 Field	2	ICC_CR0_EL1
 Field	1	ICC_IDRn_EL1
 Field	0	ICC_APR_EL1
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 02/36] KVM: arm64: gic-v3: Switch vGIC-v3 to use generated ICH_VMCR_EL2
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
  2025-12-19 15:52 ` [PATCH v2 03/36] arm64/sysreg: Drop ICH_HFGRTR_EL2.ICC_HAPR_EL1 and make RES1 Sascha Bischoff
  2025-12-19 15:52 ` [PATCH v2 01/36] KVM: arm64: Account for RES1 bits in DECLARE_FEAT_MAP() and co Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-06 18:00   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 04/36] arm64/sysreg: Add remaining GICv5 ICC_ & ICH_ sysregs for KVM support Sascha Bischoff
                   ` (33 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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, Sascha Bischoff

From: Sascha Bischoff <Sascha.Bischoff@arm.com>

The VGIC-v3 code relied on hand-written definitions for the
ICH_VMCR_EL2 register. This register, and the associated fields, is
now generated as part of the sysreg framework. Move to using the
generated definitions instead of the hand-written ones.

There are no functional changes as part of this change.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/include/asm/sysreg.h      | 21 ---------
 arch/arm64/kvm/hyp/vgic-v3-sr.c      | 64 ++++++++++++----------------
 arch/arm64/kvm/vgic/vgic-v3-nested.c |  8 ++--
 arch/arm64/kvm/vgic/vgic-v3.c        | 48 ++++++++++-----------
 4 files changed, 54 insertions(+), 87 deletions(-)

diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
index 9df51accbb025..b3b8b8cd7bf1e 100644
--- a/arch/arm64/include/asm/sysreg.h
+++ b/arch/arm64/include/asm/sysreg.h
@@ -560,7 +560,6 @@
 #define SYS_ICC_SRE_EL2			sys_reg(3, 4, 12, 9, 5)
 #define SYS_ICH_EISR_EL2		sys_reg(3, 4, 12, 11, 3)
 #define SYS_ICH_ELRSR_EL2		sys_reg(3, 4, 12, 11, 5)
-#define SYS_ICH_VMCR_EL2		sys_reg(3, 4, 12, 11, 7)
 
 #define __SYS__LR0_EL2(x)		sys_reg(3, 4, 12, 12, x)
 #define SYS_ICH_LR0_EL2			__SYS__LR0_EL2(0)
@@ -988,26 +987,6 @@
 #define ICH_LR_PRIORITY_SHIFT	48
 #define ICH_LR_PRIORITY_MASK	(0xffULL << ICH_LR_PRIORITY_SHIFT)
 
-/* ICH_VMCR_EL2 bit definitions */
-#define ICH_VMCR_ACK_CTL_SHIFT	2
-#define ICH_VMCR_ACK_CTL_MASK	(1 << ICH_VMCR_ACK_CTL_SHIFT)
-#define ICH_VMCR_FIQ_EN_SHIFT	3
-#define ICH_VMCR_FIQ_EN_MASK	(1 << ICH_VMCR_FIQ_EN_SHIFT)
-#define ICH_VMCR_CBPR_SHIFT	4
-#define ICH_VMCR_CBPR_MASK	(1 << ICH_VMCR_CBPR_SHIFT)
-#define ICH_VMCR_EOIM_SHIFT	9
-#define ICH_VMCR_EOIM_MASK	(1 << ICH_VMCR_EOIM_SHIFT)
-#define ICH_VMCR_BPR1_SHIFT	18
-#define ICH_VMCR_BPR1_MASK	(7 << ICH_VMCR_BPR1_SHIFT)
-#define ICH_VMCR_BPR0_SHIFT	21
-#define ICH_VMCR_BPR0_MASK	(7 << ICH_VMCR_BPR0_SHIFT)
-#define ICH_VMCR_PMR_SHIFT	24
-#define ICH_VMCR_PMR_MASK	(0xffUL << ICH_VMCR_PMR_SHIFT)
-#define ICH_VMCR_ENG0_SHIFT	0
-#define ICH_VMCR_ENG0_MASK	(1 << ICH_VMCR_ENG0_SHIFT)
-#define ICH_VMCR_ENG1_SHIFT	1
-#define ICH_VMCR_ENG1_MASK	(1 << ICH_VMCR_ENG1_SHIFT)
-
 /*
  * Permission Indirection Extension (PIE) permission encodings.
  * Encodings with the _O suffix, have overlays applied (Permission Overlay Extension).
diff --git a/arch/arm64/kvm/hyp/vgic-v3-sr.c b/arch/arm64/kvm/hyp/vgic-v3-sr.c
index 0b670a033fd87..298101434fc75 100644
--- a/arch/arm64/kvm/hyp/vgic-v3-sr.c
+++ b/arch/arm64/kvm/hyp/vgic-v3-sr.c
@@ -569,11 +569,11 @@ static int __vgic_v3_highest_priority_lr(struct kvm_vcpu *vcpu, u32 vmcr,
 			continue;
 
 		/* Group-0 interrupt, but Group-0 disabled? */
-		if (!(val & ICH_LR_GROUP) && !(vmcr & ICH_VMCR_ENG0_MASK))
+		if (!(val & ICH_LR_GROUP) && !(vmcr & ICH_VMCR_EL2_VENG0_MASK))
 			continue;
 
 		/* Group-1 interrupt, but Group-1 disabled? */
-		if ((val & ICH_LR_GROUP) && !(vmcr & ICH_VMCR_ENG1_MASK))
+		if ((val & ICH_LR_GROUP) && !(vmcr & ICH_VMCR_EL2_VENG1_MASK))
 			continue;
 
 		/* Not the highest priority? */
@@ -646,19 +646,19 @@ static int __vgic_v3_get_highest_active_priority(void)
 
 static unsigned int __vgic_v3_get_bpr0(u32 vmcr)
 {
-	return (vmcr & ICH_VMCR_BPR0_MASK) >> ICH_VMCR_BPR0_SHIFT;
+	return FIELD_GET(ICH_VMCR_EL2_VBPR0, vmcr);
 }
 
 static unsigned int __vgic_v3_get_bpr1(u32 vmcr)
 {
 	unsigned int bpr;
 
-	if (vmcr & ICH_VMCR_CBPR_MASK) {
+	if (vmcr & ICH_VMCR_EL2_VCBPR_MASK) {
 		bpr = __vgic_v3_get_bpr0(vmcr);
 		if (bpr < 7)
 			bpr++;
 	} else {
-		bpr = (vmcr & ICH_VMCR_BPR1_MASK) >> ICH_VMCR_BPR1_SHIFT;
+		bpr = FIELD_GET(ICH_VMCR_EL2_VBPR1, vmcr);
 	}
 
 	return bpr;
@@ -758,7 +758,7 @@ static void __vgic_v3_read_iar(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
 	if (grp != !!(lr_val & ICH_LR_GROUP))
 		goto spurious;
 
-	pmr = (vmcr & ICH_VMCR_PMR_MASK) >> ICH_VMCR_PMR_SHIFT;
+	pmr = FIELD_GET(ICH_VMCR_EL2_VPMR, vmcr);
 	lr_prio = (lr_val & ICH_LR_PRIORITY_MASK) >> ICH_LR_PRIORITY_SHIFT;
 	if (pmr <= lr_prio)
 		goto spurious;
@@ -806,7 +806,7 @@ static int ___vgic_v3_write_dir(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
 	int lr;
 
 	/* EOImode == 0, nothing to be done here */
-	if (!(vmcr & ICH_VMCR_EOIM_MASK))
+	if (!(vmcr & ICH_VMCR_EL2_VEOIM_MASK))
 		return 1;
 
 	/* No deactivate to be performed on an LPI */
@@ -849,7 +849,7 @@ static void __vgic_v3_write_eoir(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
 	}
 
 	/* EOImode == 1 and not an LPI, nothing to be done here */
-	if ((vmcr & ICH_VMCR_EOIM_MASK) && !(vid >= VGIC_MIN_LPI))
+	if ((vmcr & ICH_VMCR_EL2_VEOIM_MASK) && !(vid >= VGIC_MIN_LPI))
 		return;
 
 	lr_prio = (lr_val & ICH_LR_PRIORITY_MASK) >> ICH_LR_PRIORITY_SHIFT;
@@ -865,12 +865,12 @@ static void __vgic_v3_write_eoir(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
 
 static void __vgic_v3_read_igrpen0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
 {
-	vcpu_set_reg(vcpu, rt, !!(vmcr & ICH_VMCR_ENG0_MASK));
+	vcpu_set_reg(vcpu, rt, vmcr & ICH_VMCR_EL2_VENG0_MASK);
 }
 
 static void __vgic_v3_read_igrpen1(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
 {
-	vcpu_set_reg(vcpu, rt, !!(vmcr & ICH_VMCR_ENG1_MASK));
+	vcpu_set_reg(vcpu, rt, vmcr & ICH_VMCR_EL2_VENG1_MASK);
 }
 
 static void __vgic_v3_write_igrpen0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
@@ -878,9 +878,9 @@ static void __vgic_v3_write_igrpen0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
 	u64 val = vcpu_get_reg(vcpu, rt);
 
 	if (val & 1)
-		vmcr |= ICH_VMCR_ENG0_MASK;
+		vmcr |= ICH_VMCR_EL2_VENG0_MASK;
 	else
-		vmcr &= ~ICH_VMCR_ENG0_MASK;
+		vmcr &= ~ICH_VMCR_EL2_VENG0_MASK;
 
 	__vgic_v3_write_vmcr(vmcr);
 }
@@ -890,9 +890,9 @@ static void __vgic_v3_write_igrpen1(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
 	u64 val = vcpu_get_reg(vcpu, rt);
 
 	if (val & 1)
-		vmcr |= ICH_VMCR_ENG1_MASK;
+		vmcr |= ICH_VMCR_EL2_VENG1_MASK;
 	else
-		vmcr &= ~ICH_VMCR_ENG1_MASK;
+		vmcr &= ~ICH_VMCR_EL2_VENG1_MASK;
 
 	__vgic_v3_write_vmcr(vmcr);
 }
@@ -916,10 +916,8 @@ static void __vgic_v3_write_bpr0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
 	if (val < bpr_min)
 		val = bpr_min;
 
-	val <<= ICH_VMCR_BPR0_SHIFT;
-	val &= ICH_VMCR_BPR0_MASK;
-	vmcr &= ~ICH_VMCR_BPR0_MASK;
-	vmcr |= val;
+	vmcr &= ~ICH_VMCR_EL2_VBPR0_MASK;
+	vmcr |= FIELD_PREP(ICH_VMCR_EL2_VBPR0, val);
 
 	__vgic_v3_write_vmcr(vmcr);
 }
@@ -929,17 +927,15 @@ static void __vgic_v3_write_bpr1(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
 	u64 val = vcpu_get_reg(vcpu, rt);
 	u8 bpr_min = __vgic_v3_bpr_min();
 
-	if (vmcr & ICH_VMCR_CBPR_MASK)
+	if (FIELD_GET(ICH_VMCR_EL2_VCBPR_MASK, val))
 		return;
 
 	/* Enforce BPR limiting */
 	if (val < bpr_min)
 		val = bpr_min;
 
-	val <<= ICH_VMCR_BPR1_SHIFT;
-	val &= ICH_VMCR_BPR1_MASK;
-	vmcr &= ~ICH_VMCR_BPR1_MASK;
-	vmcr |= val;
+	vmcr &= ~ICH_VMCR_EL2_VBPR1_MASK;
+	vmcr |= FIELD_PREP(ICH_VMCR_EL2_VBPR1, val);
 
 	__vgic_v3_write_vmcr(vmcr);
 }
@@ -1029,19 +1025,15 @@ static void __vgic_v3_read_hppir(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
 
 static void __vgic_v3_read_pmr(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
 {
-	vmcr &= ICH_VMCR_PMR_MASK;
-	vmcr >>= ICH_VMCR_PMR_SHIFT;
-	vcpu_set_reg(vcpu, rt, vmcr);
+	vcpu_set_reg(vcpu, rt, FIELD_GET(ICH_VMCR_EL2_VPMR, vmcr));
 }
 
 static void __vgic_v3_write_pmr(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
 {
 	u32 val = vcpu_get_reg(vcpu, rt);
 
-	val <<= ICH_VMCR_PMR_SHIFT;
-	val &= ICH_VMCR_PMR_MASK;
-	vmcr &= ~ICH_VMCR_PMR_MASK;
-	vmcr |= val;
+	vmcr &= ~ICH_VMCR_EL2_VPMR_MASK;
+	vmcr |= FIELD_PREP(ICH_VMCR_EL2_VPMR, val);
 
 	write_gicreg(vmcr, ICH_VMCR_EL2);
 }
@@ -1064,9 +1056,9 @@ static void __vgic_v3_read_ctlr(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
 	/* A3V */
 	val |= ((vtr >> 21) & 1) << ICC_CTLR_EL1_A3V_SHIFT;
 	/* EOImode */
-	val |= ((vmcr & ICH_VMCR_EOIM_MASK) >> ICH_VMCR_EOIM_SHIFT) << ICC_CTLR_EL1_EOImode_SHIFT;
+	val |= FIELD_GET(ICH_VMCR_EL2_VEOIM, vmcr) << ICC_CTLR_EL1_EOImode_SHIFT;
 	/* CBPR */
-	val |= (vmcr & ICH_VMCR_CBPR_MASK) >> ICH_VMCR_CBPR_SHIFT;
+	val |= FIELD_GET(ICH_VMCR_EL2_VCBPR, vmcr);
 
 	vcpu_set_reg(vcpu, rt, val);
 }
@@ -1076,14 +1068,14 @@ static void __vgic_v3_write_ctlr(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
 	u32 val = vcpu_get_reg(vcpu, rt);
 
 	if (val & ICC_CTLR_EL1_CBPR_MASK)
-		vmcr |= ICH_VMCR_CBPR_MASK;
+		vmcr |= ICH_VMCR_EL2_VCBPR_MASK;
 	else
-		vmcr &= ~ICH_VMCR_CBPR_MASK;
+		vmcr &= ~ICH_VMCR_EL2_VCBPR_MASK;
 
 	if (val & ICC_CTLR_EL1_EOImode_MASK)
-		vmcr |= ICH_VMCR_EOIM_MASK;
+		vmcr |= ICH_VMCR_EL2_VEOIM_MASK;
 	else
-		vmcr &= ~ICH_VMCR_EOIM_MASK;
+		vmcr &= ~ICH_VMCR_EL2_VEOIM_MASK;
 
 	write_gicreg(vmcr, ICH_VMCR_EL2);
 }
diff --git a/arch/arm64/kvm/vgic/vgic-v3-nested.c b/arch/arm64/kvm/vgic/vgic-v3-nested.c
index 61b44f3f2bf14..c9e35ec671173 100644
--- a/arch/arm64/kvm/vgic/vgic-v3-nested.c
+++ b/arch/arm64/kvm/vgic/vgic-v3-nested.c
@@ -202,16 +202,16 @@ u64 vgic_v3_get_misr(struct kvm_vcpu *vcpu)
 	if ((hcr & ICH_HCR_EL2_NPIE) && !mi_state.pend)
 		reg |= ICH_MISR_EL2_NP;
 
-	if ((hcr & ICH_HCR_EL2_VGrp0EIE) && (vmcr & ICH_VMCR_ENG0_MASK))
+	if ((hcr & ICH_HCR_EL2_VGrp0EIE) && (vmcr & ICH_VMCR_EL2_VENG0_MASK))
 		reg |= ICH_MISR_EL2_VGrp0E;
 
-	if ((hcr & ICH_HCR_EL2_VGrp0DIE) && !(vmcr & ICH_VMCR_ENG0_MASK))
+	if ((hcr & ICH_HCR_EL2_VGrp0DIE) && !(vmcr & ICH_VMCR_EL2_VENG0_MASK))
 		reg |= ICH_MISR_EL2_VGrp0D;
 
-	if ((hcr & ICH_HCR_EL2_VGrp1EIE) && (vmcr & ICH_VMCR_ENG1_MASK))
+	if ((hcr & ICH_HCR_EL2_VGrp1EIE) && (vmcr & ICH_VMCR_EL2_VENG1_MASK))
 		reg |= ICH_MISR_EL2_VGrp1E;
 
-	if ((hcr & ICH_HCR_EL2_VGrp1DIE) && !(vmcr & ICH_VMCR_ENG1_MASK))
+	if ((hcr & ICH_HCR_EL2_VGrp1DIE) && !(vmcr & ICH_VMCR_EL2_VENG1_MASK))
 		reg |= ICH_MISR_EL2_VGrp1D;
 
 	return reg;
diff --git a/arch/arm64/kvm/vgic/vgic-v3.c b/arch/arm64/kvm/vgic/vgic-v3.c
index 1d6dd1b545bdd..2afc041672311 100644
--- a/arch/arm64/kvm/vgic/vgic-v3.c
+++ b/arch/arm64/kvm/vgic/vgic-v3.c
@@ -41,9 +41,9 @@ void vgic_v3_configure_hcr(struct kvm_vcpu *vcpu,
 	if (!als->nr_sgi)
 		cpuif->vgic_hcr |= ICH_HCR_EL2_vSGIEOICount;
 
-	cpuif->vgic_hcr |= (cpuif->vgic_vmcr & ICH_VMCR_ENG0_MASK) ?
+	cpuif->vgic_hcr |= (cpuif->vgic_vmcr & ICH_VMCR_EL2_VENG0_MASK) ?
 		ICH_HCR_EL2_VGrp0DIE : ICH_HCR_EL2_VGrp0EIE;
-	cpuif->vgic_hcr |= (cpuif->vgic_vmcr & ICH_VMCR_ENG1_MASK) ?
+	cpuif->vgic_hcr |= (cpuif->vgic_vmcr & ICH_VMCR_EL2_VENG1_MASK) ?
 		ICH_HCR_EL2_VGrp1DIE : ICH_HCR_EL2_VGrp1EIE;
 
 	/*
@@ -215,7 +215,7 @@ void vgic_v3_deactivate(struct kvm_vcpu *vcpu, u64 val)
 	 * We only deal with DIR when EOIMode==1, and only for SGI,
 	 * PPI or SPI.
 	 */
-	if (!(cpuif->vgic_vmcr & ICH_VMCR_EOIM_MASK) ||
+	if (!(cpuif->vgic_vmcr & ICH_VMCR_EL2_VEOIM_MASK) ||
 	    val >= vcpu->kvm->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS)
 		return;
 
@@ -408,25 +408,23 @@ void vgic_v3_set_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcrp)
 	u32 vmcr;
 
 	if (model == KVM_DEV_TYPE_ARM_VGIC_V2) {
-		vmcr = (vmcrp->ackctl << ICH_VMCR_ACK_CTL_SHIFT) &
-			ICH_VMCR_ACK_CTL_MASK;
-		vmcr |= (vmcrp->fiqen << ICH_VMCR_FIQ_EN_SHIFT) &
-			ICH_VMCR_FIQ_EN_MASK;
+		vmcr = FIELD_PREP(ICH_VMCR_EL2_VAckCtl, vmcrp->ackctl);
+		vmcr |= FIELD_PREP(ICH_VMCR_EL2_VFIQEn, vmcrp->fiqen);
 	} else {
 		/*
 		 * When emulating GICv3 on GICv3 with SRE=1 on the
 		 * VFIQEn bit is RES1 and the VAckCtl bit is RES0.
 		 */
-		vmcr = ICH_VMCR_FIQ_EN_MASK;
+		vmcr = ICH_VMCR_EL2_VFIQEn_MASK;
 	}
 
-	vmcr |= (vmcrp->cbpr << ICH_VMCR_CBPR_SHIFT) & ICH_VMCR_CBPR_MASK;
-	vmcr |= (vmcrp->eoim << ICH_VMCR_EOIM_SHIFT) & ICH_VMCR_EOIM_MASK;
-	vmcr |= (vmcrp->abpr << ICH_VMCR_BPR1_SHIFT) & ICH_VMCR_BPR1_MASK;
-	vmcr |= (vmcrp->bpr << ICH_VMCR_BPR0_SHIFT) & ICH_VMCR_BPR0_MASK;
-	vmcr |= (vmcrp->pmr << ICH_VMCR_PMR_SHIFT) & ICH_VMCR_PMR_MASK;
-	vmcr |= (vmcrp->grpen0 << ICH_VMCR_ENG0_SHIFT) & ICH_VMCR_ENG0_MASK;
-	vmcr |= (vmcrp->grpen1 << ICH_VMCR_ENG1_SHIFT) & ICH_VMCR_ENG1_MASK;
+	vmcr |= FIELD_PREP(ICH_VMCR_EL2_VCBPR, vmcrp->cbpr);
+	vmcr |= FIELD_PREP(ICH_VMCR_EL2_VEOIM, vmcrp->eoim);
+	vmcr |= FIELD_PREP(ICH_VMCR_EL2_VBPR1, vmcrp->abpr);
+	vmcr |= FIELD_PREP(ICH_VMCR_EL2_VBPR0, vmcrp->bpr);
+	vmcr |= FIELD_PREP(ICH_VMCR_EL2_VPMR, vmcrp->pmr);
+	vmcr |= FIELD_PREP(ICH_VMCR_EL2_VENG0, vmcrp->grpen0);
+	vmcr |= FIELD_PREP(ICH_VMCR_EL2_VENG1, vmcrp->grpen1);
 
 	cpu_if->vgic_vmcr = vmcr;
 }
@@ -440,10 +438,8 @@ void vgic_v3_get_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcrp)
 	vmcr = cpu_if->vgic_vmcr;
 
 	if (model == KVM_DEV_TYPE_ARM_VGIC_V2) {
-		vmcrp->ackctl = (vmcr & ICH_VMCR_ACK_CTL_MASK) >>
-			ICH_VMCR_ACK_CTL_SHIFT;
-		vmcrp->fiqen = (vmcr & ICH_VMCR_FIQ_EN_MASK) >>
-			ICH_VMCR_FIQ_EN_SHIFT;
+		vmcrp->ackctl = FIELD_GET(ICH_VMCR_EL2_VAckCtl, vmcr);
+		vmcrp->fiqen = FIELD_GET(ICH_VMCR_EL2_VFIQEn, vmcr);
 	} else {
 		/*
 		 * When emulating GICv3 on GICv3 with SRE=1 on the
@@ -453,13 +449,13 @@ void vgic_v3_get_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcrp)
 		vmcrp->ackctl = 0;
 	}
 
-	vmcrp->cbpr = (vmcr & ICH_VMCR_CBPR_MASK) >> ICH_VMCR_CBPR_SHIFT;
-	vmcrp->eoim = (vmcr & ICH_VMCR_EOIM_MASK) >> ICH_VMCR_EOIM_SHIFT;
-	vmcrp->abpr = (vmcr & ICH_VMCR_BPR1_MASK) >> ICH_VMCR_BPR1_SHIFT;
-	vmcrp->bpr  = (vmcr & ICH_VMCR_BPR0_MASK) >> ICH_VMCR_BPR0_SHIFT;
-	vmcrp->pmr  = (vmcr & ICH_VMCR_PMR_MASK) >> ICH_VMCR_PMR_SHIFT;
-	vmcrp->grpen0 = (vmcr & ICH_VMCR_ENG0_MASK) >> ICH_VMCR_ENG0_SHIFT;
-	vmcrp->grpen1 = (vmcr & ICH_VMCR_ENG1_MASK) >> ICH_VMCR_ENG1_SHIFT;
+	vmcrp->cbpr = FIELD_GET(ICH_VMCR_EL2_VCBPR, vmcr);
+	vmcrp->eoim = FIELD_GET(ICH_VMCR_EL2_VEOIM, vmcr);
+	vmcrp->abpr = FIELD_GET(ICH_VMCR_EL2_VBPR1, vmcr);
+	vmcrp->bpr  = FIELD_GET(ICH_VMCR_EL2_VBPR0, vmcr);
+	vmcrp->pmr  = FIELD_GET(ICH_VMCR_EL2_VPMR, vmcr);
+	vmcrp->grpen0 = FIELD_GET(ICH_VMCR_EL2_VENG0, vmcr);
+	vmcrp->grpen1 = FIELD_GET(ICH_VMCR_EL2_VENG1, vmcr);
 }
 
 #define INITIAL_PENDBASER_VALUE						  \
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 05/36] arm64/sysreg: Add GICR CDNMIA encoding
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (4 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 06/36] KVM: arm64: gic-v5: Add ARM_VGIC_V5 device to KVM headers Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-06 18:08   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 09/36] KVM: arm64: gic-v5: Detect implemented PPIs on boot Sascha Bischoff
                   ` (30 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

The encoding for the GICR CDNMIA system instruction is thus far unused
(and shall remain unused for the time being). However, in order to
plumb the FGTs into KVM correctly, KVM needs to be made aware of the
encoding of this system instruction.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/include/asm/sysreg.h | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
index b3b8b8cd7bf1e..e99acb6dbd5d8 100644
--- a/arch/arm64/include/asm/sysreg.h
+++ b/arch/arm64/include/asm/sysreg.h
@@ -1059,6 +1059,7 @@
 #define GICV5_OP_GIC_CDPRI		sys_insn(1, 0, 12, 1, 2)
 #define GICV5_OP_GIC_CDRCFG		sys_insn(1, 0, 12, 1, 5)
 #define GICV5_OP_GICR_CDIA		sys_insn(1, 0, 12, 3, 0)
+#define GICV5_OP_GICR_CDNMIA		sys_insn(1, 0, 12, 3, 1)
 
 /* Definitions for GIC CDAFF */
 #define GICV5_GIC_CDAFF_IAFFID_MASK	GENMASK_ULL(47, 32)
@@ -1105,6 +1106,12 @@
 #define GICV5_GIC_CDIA_TYPE_MASK	GENMASK_ULL(31, 29)
 #define GICV5_GIC_CDIA_ID_MASK		GENMASK_ULL(23, 0)
 
+/* Definitions for GICR CDNMIA */
+#define GICV5_GIC_CDNMIA_VALID_MASK	BIT_ULL(32)
+#define GICV5_GICR_CDNMIA_VALID(r)	FIELD_GET(GICV5_GIC_CDNMIA_VALID_MASK, r)
+#define GICV5_GIC_CDNMIA_TYPE_MASK	GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDNMIA_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] 100+ messages in thread

* [PATCH v2 06/36] KVM: arm64: gic-v5: Add ARM_VGIC_V5 device to KVM headers
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (3 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 04/36] arm64/sysreg: Add remaining GICv5 ICC_ & ICH_ sysregs for KVM support Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2025-12-19 15:52 ` [PATCH v2 05/36] arm64/sysreg: Add GICR CDNMIA encoding Sascha Bischoff
                   ` (31 subsequent siblings)
  36 siblings, 0 replies; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

This is the base GICv5 device which is to be used with the
KVM_CREATE_DEVICE ioctl to create a GICv5-based vgic.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 include/uapi/linux/kvm.h       | 2 ++
 tools/include/uapi/linux/kvm.h | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index dddb781b0507d..f7dabbf17e1a7 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1209,6 +1209,8 @@ enum kvm_device_type {
 #define KVM_DEV_TYPE_LOONGARCH_EIOINTC	KVM_DEV_TYPE_LOONGARCH_EIOINTC
 	KVM_DEV_TYPE_LOONGARCH_PCHPIC,
 #define KVM_DEV_TYPE_LOONGARCH_PCHPIC	KVM_DEV_TYPE_LOONGARCH_PCHPIC
+	KVM_DEV_TYPE_ARM_VGIC_V5,
+#define KVM_DEV_TYPE_ARM_VGIC_V5	KVM_DEV_TYPE_ARM_VGIC_V5
 
 	KVM_DEV_TYPE_MAX,
 
diff --git a/tools/include/uapi/linux/kvm.h b/tools/include/uapi/linux/kvm.h
index 52f6000ab0208..8303124973e2a 100644
--- a/tools/include/uapi/linux/kvm.h
+++ b/tools/include/uapi/linux/kvm.h
@@ -1198,6 +1198,8 @@ enum kvm_device_type {
 #define KVM_DEV_TYPE_LOONGARCH_EIOINTC	KVM_DEV_TYPE_LOONGARCH_EIOINTC
 	KVM_DEV_TYPE_LOONGARCH_PCHPIC,
 #define KVM_DEV_TYPE_LOONGARCH_PCHPIC	KVM_DEV_TYPE_LOONGARCH_PCHPIC
+	KVM_DEV_TYPE_ARM_VGIC_V5,
+#define KVM_DEV_TYPE_ARM_VGIC_V5	KVM_DEV_TYPE_ARM_VGIC_V5
 
 	KVM_DEV_TYPE_MAX,
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 04/36] arm64/sysreg: Add remaining GICv5 ICC_ & ICH_ sysregs for KVM support
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (2 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 02/36] KVM: arm64: gic-v3: Switch vGIC-v3 to use generated ICH_VMCR_EL2 Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-06 18:28   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 06/36] KVM: arm64: gic-v5: Add ARM_VGIC_V5 device to KVM headers Sascha Bischoff
                   ` (32 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

Add the GICv5 system registers required to support native GICv5 guests
with KVM. Many of the GICv5 sysregs have already been added as part of
the host GICv5 driver, keeping this set relatively small. The
registers added in this change complete the set by adding those
required by KVM either directly (ICH_) or indirectly (FGTs for the
ICC_ sysregs).

The following system registers and their fields are added:

	ICC_APR_EL1
	ICC_HPPIR_EL1
	ICC_IAFFIDR_EL1
	ICH_APR_EL2
	ICH_CONTEXTR_EL2
	ICH_PPI_ACTIVER<n>_EL2
	ICH_PPI_DVI<n>_EL2
	ICH_PPI_ENABLER<n>_EL2
	ICH_PPI_PENDR<n>_EL2
	ICH_PPI_PRIORITYR<n>_EL2

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/tools/sysreg | 480 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 480 insertions(+)

diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index dab5bfe8c9686..2f44a568ebf4e 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -3248,6 +3248,14 @@ UnsignedEnum	3:0	ID_BITS
 EndEnum
 EndSysreg
 
+Sysreg	ICC_HPPIR_EL1	3	0	12	10	3
+Res0	63:33
+Field	32	HPPIV
+Field	31:29	TYPE
+Res0	28:24
+Field	23:0	ID
+EndSysreg
+
 Sysreg	ICC_ICSR_EL1	3	0	12	10	4
 Res0	63:48
 Field	47:32	IAFFID
@@ -3262,6 +3270,11 @@ Field	1	Enabled
 Field	0	F
 EndSysreg
 
+Sysreg	ICC_IAFFIDR_EL1	3	0	12	10	5
+Res0	63:16
+Field	15:0	IAFFID
+EndSysreg
+
 SysregFields	ICC_PPI_ENABLERx_EL1
 Field	63	EN63
 Field	62	EN62
@@ -3668,6 +3681,42 @@ Res0	14:12
 Field	11:0	AFFINITY
 EndSysreg
 
+Sysreg	ICC_APR_EL1	3	1	12	0	0
+Res0	63:32
+Field	31	P31
+Field	30	P30
+Field	29	P29
+Field	28	P28
+Field	27	P27
+Field	26	P26
+Field	25	P25
+Field	24	P24
+Field	23	P23
+Field	22	P22
+Field	21	P21
+Field	20	P20
+Field	19	P19
+Field	18	P18
+Field	17	P17
+Field	16	P16
+Field	15	P15
+Field	14	P14
+Field	13	P13
+Field	12	P12
+Field	11	P11
+Field	10	P10
+Field	9	P9
+Field	8	P8
+Field	7	P7
+Field	6	P6
+Field	5	P5
+Field	4	P4
+Field	3	P3
+Field	2	P2
+Field	1	P1
+Field	0	P0
+EndSysreg
+
 Sysreg	ICC_CR0_EL1	3	1	12	0	1
 Res0	63:39
 Field	38	PID
@@ -4567,6 +4616,42 @@ Field	31:16	PhyPARTID29
 Field	15:0	PhyPARTID28
 EndSysreg
 
+Sysreg	ICH_APR_EL2	3	4	12	8	4
+Res0	63:32
+Field	31	P31
+Field	30	P30
+Field	29	P29
+Field	28	P28
+Field	27	P27
+Field	26	P26
+Field	25	P25
+Field	24	P24
+Field	23	P23
+Field	22	P22
+Field	21	P21
+Field	20	P20
+Field	19	P19
+Field	18	P18
+Field	17	P17
+Field	16	P16
+Field	15	P15
+Field	14	P14
+Field	13	P13
+Field	12	P12
+Field	11	P11
+Field	10	P10
+Field	9	P9
+Field	8	P8
+Field	7	P7
+Field	6	P6
+Field	5	P5
+Field	4	P4
+Field	3	P3
+Field	2	P2
+Field	1	P1
+Field	0	P0
+EndSysreg
+
 Sysreg	ICH_HFGRTR_EL2	3	4	12	9	4
 Res0	63:21
 Field	20	ICC_PPI_ACTIVERn_EL1
@@ -4615,6 +4700,306 @@ Field	1	GICCDDIS
 Field	0	GICCDEN
 EndSysreg
 
+SysregFields	ICH_PPI_DVIRx_EL2
+Field	63	DVI63
+Field	62	DVI62
+Field	61	DVI61
+Field	60	DVI60
+Field	59	DVI59
+Field	58	DVI58
+Field	57	DVI57
+Field	56	DVI56
+Field	55	DVI55
+Field	54	DVI54
+Field	53	DVI53
+Field	52	DVI52
+Field	51	DVI51
+Field	50	DVI50
+Field	49	DVI49
+Field	48	DVI48
+Field	47	DVI47
+Field	46	DVI46
+Field	45	DVI45
+Field	44	DVI44
+Field	43	DVI43
+Field	42	DVI42
+Field	41	DVI41
+Field	40	DVI40
+Field	39	DVI39
+Field	38	DVI38
+Field	37	DVI37
+Field	36	DVI36
+Field	35	DVI35
+Field	34	DVI34
+Field	33	DVI33
+Field	32	DVI32
+Field	31	DVI31
+Field	30	DVI30
+Field	29	DVI29
+Field	28	DVI28
+Field	27	DVI27
+Field	26	DVI26
+Field	25	DVI25
+Field	24	DVI24
+Field	23	DVI23
+Field	22	DVI22
+Field	21	DVI21
+Field	20	DVI20
+Field	19	DVI19
+Field	18	DVI18
+Field	17	DVI17
+Field	16	DVI16
+Field	15	DVI15
+Field	14	DVI14
+Field	13	DVI13
+Field	12	DVI12
+Field	11	DVI11
+Field	10	DVI10
+Field	9	DVI9
+Field	8	DVI8
+Field	7	DVI7
+Field	6	DVI6
+Field	5	DVI5
+Field	4	DVI4
+Field	3	DVI3
+Field	2	DVI2
+Field	1	DVI1
+Field	0	DVI0
+EndSysregFields
+
+Sysreg	ICH_PPI_DVIR0_EL2	3	4	12	10	0
+Fields ICH_PPI_DVIx_EL2
+EndSysreg
+
+Sysreg	ICH_PPI_DVIR1_EL2	3	4	12	10	1
+Fields ICH_PPI_DVIx_EL2
+EndSysreg
+
+SysregFields	ICH_PPI_ENABLERx_EL2
+Field	63	EN63
+Field	62	EN62
+Field	61	EN61
+Field	60	EN60
+Field	59	EN59
+Field	58	EN58
+Field	57	EN57
+Field	56	EN56
+Field	55	EN55
+Field	54	EN54
+Field	53	EN53
+Field	52	EN52
+Field	51	EN51
+Field	50	EN50
+Field	49	EN49
+Field	48	EN48
+Field	47	EN47
+Field	46	EN46
+Field	45	EN45
+Field	44	EN44
+Field	43	EN43
+Field	42	EN42
+Field	41	EN41
+Field	40	EN40
+Field	39	EN39
+Field	38	EN38
+Field	37	EN37
+Field	36	EN36
+Field	35	EN35
+Field	34	EN34
+Field	33	EN33
+Field	32	EN32
+Field	31	EN31
+Field	30	EN30
+Field	29	EN29
+Field	28	EN28
+Field	27	EN27
+Field	26	EN26
+Field	25	EN25
+Field	24	EN24
+Field	23	EN23
+Field	22	EN22
+Field	21	EN21
+Field	20	EN20
+Field	19	EN19
+Field	18	EN18
+Field	17	EN17
+Field	16	EN16
+Field	15	EN15
+Field	14	EN14
+Field	13	EN13
+Field	12	EN12
+Field	11	EN11
+Field	10	EN10
+Field	9	EN9
+Field	8	EN8
+Field	7	EN7
+Field	6	EN6
+Field	5	EN5
+Field	4	EN4
+Field	3	EN3
+Field	2	EN2
+Field	1	EN1
+Field	0	EN0
+EndSysregFields
+
+Sysreg	ICH_PPI_ENABLER0_EL2	3	4	12	10	2
+Fields ICH_PPI_ENABLERx_EL2
+EndSysreg
+
+Sysreg	ICH_PPI_ENABLER1_EL2	3	4	12	10	3
+Fields ICH_PPI_ENABLERx_EL2
+EndSysreg
+
+SysregFields	ICH_PPI_PENDRx_EL2
+Field	63	PEND63
+Field	62	PEND62
+Field	61	PEND61
+Field	60	PEND60
+Field	59	PEND59
+Field	58	PEND58
+Field	57	PEND57
+Field	56	PEND56
+Field	55	PEND55
+Field	54	PEND54
+Field	53	PEND53
+Field	52	PEND52
+Field	51	PEND51
+Field	50	PEND50
+Field	49	PEND49
+Field	48	PEND48
+Field	47	PEND47
+Field	46	PEND46
+Field	45	PEND45
+Field	44	PEND44
+Field	43	PEND43
+Field	42	PEND42
+Field	41	PEND41
+Field	40	PEND40
+Field	39	PEND39
+Field	38	PEND38
+Field	37	PEND37
+Field	36	PEND36
+Field	35	PEND35
+Field	34	PEND34
+Field	33	PEND33
+Field	32	PEND32
+Field	31	PEND31
+Field	30	PEND30
+Field	29	PEND29
+Field	28	PEND28
+Field	27	PEND27
+Field	26	PEND26
+Field	25	PEND25
+Field	24	PEND24
+Field	23	PEND23
+Field	22	PEND22
+Field	21	PEND21
+Field	20	PEND20
+Field	19	PEND19
+Field	18	PEND18
+Field	17	PEND17
+Field	16	PEND16
+Field	15	PEND15
+Field	14	PEND14
+Field	13	PEND13
+Field	12	PEND12
+Field	11	PEND11
+Field	10	PEND10
+Field	9	PEND9
+Field	8	PEND8
+Field	7	PEND7
+Field	6	PEND6
+Field	5	PEND5
+Field	4	PEND4
+Field	3	PEND3
+Field	2	PEND2
+Field	1	PEND1
+Field	0	PEND0
+EndSysregFields
+
+Sysreg	ICH_PPI_PENDR0_EL2	3	4	12	10	4
+Fields ICH_PPI_PENDRx_EL2
+EndSysreg
+
+Sysreg	ICH_PPI_PENDR1_EL2	3	4	12	10	5
+Fields ICH_PPI_PENDRx_EL2
+EndSysreg
+
+SysregFields	ICH_PPI_ACTIVERx_EL2
+Field	63	ACTIVE63
+Field	62	ACTIVE62
+Field	61	ACTIVE61
+Field	60	ACTIVE60
+Field	59	ACTIVE59
+Field	58	ACTIVE58
+Field	57	ACTIVE57
+Field	56	ACTIVE56
+Field	55	ACTIVE55
+Field	54	ACTIVE54
+Field	53	ACTIVE53
+Field	52	ACTIVE52
+Field	51	ACTIVE51
+Field	50	ACTIVE50
+Field	49	ACTIVE49
+Field	48	ACTIVE48
+Field	47	ACTIVE47
+Field	46	ACTIVE46
+Field	45	ACTIVE45
+Field	44	ACTIVE44
+Field	43	ACTIVE43
+Field	42	ACTIVE42
+Field	41	ACTIVE41
+Field	40	ACTIVE40
+Field	39	ACTIVE39
+Field	38	ACTIVE38
+Field	37	ACTIVE37
+Field	36	ACTIVE36
+Field	35	ACTIVE35
+Field	34	ACTIVE34
+Field	33	ACTIVE33
+Field	32	ACTIVE32
+Field	31	ACTIVE31
+Field	30	ACTIVE30
+Field	29	ACTIVE29
+Field	28	ACTIVE28
+Field	27	ACTIVE27
+Field	26	ACTIVE26
+Field	25	ACTIVE25
+Field	24	ACTIVE24
+Field	23	ACTIVE23
+Field	22	ACTIVE22
+Field	21	ACTIVE21
+Field	20	ACTIVE20
+Field	19	ACTIVE19
+Field	18	ACTIVE18
+Field	17	ACTIVE17
+Field	16	ACTIVE16
+Field	15	ACTIVE15
+Field	14	ACTIVE14
+Field	13	ACTIVE13
+Field	12	ACTIVE12
+Field	11	ACTIVE11
+Field	10	ACTIVE10
+Field	9	ACTIVE9
+Field	8	ACTIVE8
+Field	7	ACTIVE7
+Field	6	ACTIVE6
+Field	5	ACTIVE5
+Field	4	ACTIVE4
+Field	3	ACTIVE3
+Field	2	ACTIVE2
+Field	1	ACTIVE1
+Field	0	ACTIVE0
+EndSysregFields
+
+Sysreg	ICH_PPI_ACTIVER0_EL2	3	4	12	10	6
+Fields ICH_PPI_ACTIVERx_EL2
+EndSysreg
+
+Sysreg	ICH_PPI_ACTIVER1_EL2	3	4	12	10	7
+Fields ICH_PPI_ACTIVERx_EL2
+EndSysreg
+
 Sysreg	ICH_HCR_EL2	3	4	12	11	0
 Res0	63:32
 Field	31:27	EOIcount
@@ -4669,6 +5054,18 @@ Field	1	V3
 Field	0	En
 EndSysreg
 
+Sysreg	ICH_CONTEXTR_EL2	3	4	12	11	6
+Field	63	V
+Field	62	F
+Field	61	IRICHPPIDIS
+Field	60	DB
+Field	59:55	DBPM
+Res0	54:48
+Field	47:32	VPE
+Res0	31:16
+Field	15:0	VM
+EndSysreg
+
 Sysreg	ICH_VMCR_EL2	3	4	12	11	7
 Prefix	FEAT_GCIE
 Res0	63:32
@@ -4690,6 +5087,89 @@ Field	1	VENG1
 Field	0	VENG0
 EndSysreg
 
+SysregFields	ICH_PPI_PRIORITYRx_EL2
+Res0	63:61
+Field	60:56	Priority7
+Res0	55:53
+Field	52:48	Priority6
+Res0	47:45
+Field	44:40	Priority5
+Res0	39:37
+Field	36:32	Priority4
+Res0	31:29
+Field	28:24	Priority3
+Res0	23:21
+Field	20:16	Priority2
+Res0	15:13
+Field	12:8	Priority1
+Res0	7:5
+Field	4:0	Priority0
+EndSysregFields
+
+Sysreg	ICH_PPI_PRIORITYR0_EL2	3	4	12	14	0
+Fields	ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg	ICH_PPI_PRIORITYR1_EL2	3	4	12	14	1
+Fields	ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg	ICH_PPI_PRIORITYR2_EL2	3	4	12	14	2
+Fields	ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg	ICH_PPI_PRIORITYR3_EL2	3	4	12	14	3
+Fields	ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg	ICH_PPI_PRIORITYR4_EL2	3	4	12	14	4
+Fields	ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg	ICH_PPI_PRIORITYR5_EL2	3	4	12	14	5
+Fields	ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg	ICH_PPI_PRIORITYR6_EL2	3	4	12	14	6
+Fields	ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg	ICH_PPI_PRIORITYR7_EL2	3	4	12	14	7
+Fields	ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg	ICH_PPI_PRIORITYR8_EL2	3	4	12	15	0
+Fields	ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg	ICH_PPI_PRIORITYR9_EL2	3	4	12	15	1
+Fields	ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg	ICH_PPI_PRIORITYR10_EL2	3	4	12	15	2
+Fields	ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg	ICH_PPI_PRIORITYR11_EL2	3	4	12	15	3
+Fields	ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg	ICH_PPI_PRIORITYR12_EL2	3	4	12	15	4
+Fields	ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg	ICH_PPI_PRIORITYR13_EL2	3	4	12	15	5
+Fields	ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg	ICH_PPI_PRIORITYR14_EL2	3	4	12	15	6
+Fields	ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg	ICH_PPI_PRIORITYR15_EL2	3	4	12	15	7
+Fields	ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
 Sysreg	CONTEXTIDR_EL2	3	4	13	0	1
 Fields	CONTEXTIDR_ELx
 EndSysreg
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 08/36] KVM: arm64: Introduce kvm_call_hyp_nvhe_res()
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (6 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 09/36] KVM: arm64: gic-v5: Detect implemented PPIs on boot Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 10:30   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 07/36] KVM: arm64: gic: Introduce interrupt type helpers Sascha Bischoff
                   ` (28 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

There are times when it is desirable to have more than one return
value for a hyp call. Split out kvm_call_hyp_nvhe_res from
kvm_call_hyp_nvhe such that it is possible to directly provide struct
arm_smccc_res from the calling code. Thereby, additional return values
can be stored in res.a2, etc.

Suggested-by: Marc Zyngier <maz@kernel.org>
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/include/asm/kvm_host.h | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index b552a1e03848c..971b153b0a3fa 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -1208,14 +1208,19 @@ void kvm_arm_resume_guest(struct kvm *kvm);
 #define vcpu_has_run_once(vcpu)	(!!READ_ONCE((vcpu)->pid))
 
 #ifndef __KVM_NVHE_HYPERVISOR__
-#define kvm_call_hyp_nvhe(f, ...)						\
+#define kvm_call_hyp_nvhe_res(res, f, ...)				\
 	({								\
-		struct arm_smccc_res res;				\
-									\
+		struct arm_smccc_res *__res = res;			\
 		arm_smccc_1_1_hvc(KVM_HOST_SMCCC_FUNC(f),		\
-				  ##__VA_ARGS__, &res);			\
-		WARN_ON(res.a0 != SMCCC_RET_SUCCESS);			\
+				  ##__VA_ARGS__, __res);		\
+		WARN_ON(__res->a0 != SMCCC_RET_SUCCESS);		\
+	})
+
+#define kvm_call_hyp_nvhe(f, ...)					\
+	({								\
+		struct arm_smccc_res res;				\
 									\
+		kvm_call_hyp_nvhe_res(&res, f, ##__VA_ARGS__);		\
 		res.a1;							\
 	})
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 09/36] KVM: arm64: gic-v5: Detect implemented PPIs on boot
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (5 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 05/36] arm64/sysreg: Add GICR CDNMIA encoding Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-06 18:34   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 08/36] KVM: arm64: Introduce kvm_call_hyp_nvhe_res() Sascha Bischoff
                   ` (29 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

It is expected that most GICv5 implementations will only implement a
subset of the PPIs. GICv5 supports up to 128 PPIs in total, where the
first 64 are architecturally defined, and the second 64 are
implementation defined. This limitation applies to both physical and
virtual PPIs as the same set is implemented in each case, and
therefore KVM needs to determine a mask of implemented PPIs during
early KVM init.

The check involves writing all of the ICH_PPI_ENABLERx_EL2 bits and
reading those back again to determine which are stateful. If the bits
are stateful, the PPIs are implemented. Else, they are not.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/include/asm/kvm_asm.h   |  1 +
 arch/arm64/include/asm/kvm_hyp.h   |  3 +++
 arch/arm64/kvm/hyp/nvhe/Makefile   |  2 +-
 arch/arm64/kvm/hyp/nvhe/hyp-main.c | 11 +++++++++++
 arch/arm64/kvm/hyp/vgic-v5-sr.c    | 27 +++++++++++++++++++++++++++
 arch/arm64/kvm/hyp/vhe/Makefile    |  2 +-
 arch/arm64/kvm/vgic/vgic-init.c    |  4 ++++
 arch/arm64/kvm/vgic/vgic-v5.c      | 29 +++++++++++++++++++++++++++++
 arch/arm64/kvm/vgic/vgic.h         |  1 +
 include/kvm/arm_vgic.h             |  5 +++++
 10 files changed, 83 insertions(+), 2 deletions(-)
 create mode 100644 arch/arm64/kvm/hyp/vgic-v5-sr.c

diff --git a/arch/arm64/include/asm/kvm_asm.h b/arch/arm64/include/asm/kvm_asm.h
index a1ad12c72ebf1..ada752e97c6aa 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___pkvm_vcpu_load,
 	__KVM_HOST_SMCCC_FUNC___pkvm_vcpu_put,
 	__KVM_HOST_SMCCC_FUNC___pkvm_tlb_flush_vmid,
+	__KVM_HOST_SMCCC_FUNC___vgic_v5_detect_ppis,
 };
 
 #define DECLARE_KVM_VHE_SYM(sym)	extern char sym[]
diff --git a/arch/arm64/include/asm/kvm_hyp.h b/arch/arm64/include/asm/kvm_hyp.h
index 76ce2b94bd97e..80e5491de8e1e 100644
--- a/arch/arm64/include/asm/kvm_hyp.h
+++ b/arch/arm64/include/asm/kvm_hyp.h
@@ -87,6 +87,9 @@ void __vgic_v3_save_aprs(struct vgic_v3_cpu_if *cpu_if);
 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_detect_ppis(u64 *ppi);
+
 #ifdef __KVM_NVHE_HYPERVISOR__
 void __timer_enable_traps(struct kvm_vcpu *vcpu);
 void __timer_disable_traps(struct kvm_vcpu *vcpu);
diff --git a/arch/arm64/kvm/hyp/nvhe/Makefile b/arch/arm64/kvm/hyp/nvhe/Makefile
index a244ec25f8c5b..84a3bf96def6b 100644
--- a/arch/arm64/kvm/hyp/nvhe/Makefile
+++ b/arch/arm64/kvm/hyp/nvhe/Makefile
@@ -26,7 +26,7 @@ hyp-obj-y := timer-sr.o sysreg-sr.o debug-sr.o switch.o tlb.o hyp-init.o host.o
 	 hyp-main.o hyp-smp.o psci-relay.o early_alloc.o page_alloc.o \
 	 cache.o setup.o mm.o mem_protect.o sys_regs.o pkvm.o stacktrace.o ffa.o
 hyp-obj-y += ../vgic-v3-sr.o ../aarch32.o ../vgic-v2-cpuif-proxy.o ../entry.o \
-	 ../fpsimd.o ../hyp-entry.o ../exception.o ../pgtable.o
+	 ../fpsimd.o ../hyp-entry.o ../exception.o ../pgtable.o ../vgic-v5-sr.o
 hyp-obj-y += ../../../kernel/smccc-call.o
 hyp-obj-$(CONFIG_LIST_HARDENED) += list_debug.o
 hyp-obj-y += $(lib-objs)
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index a7c689152f686..3d1d0807be6db 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -586,6 +586,16 @@ static void handle___pkvm_teardown_vm(struct kvm_cpu_context *host_ctxt)
 	cpu_reg(host_ctxt, 1) = __pkvm_teardown_vm(handle);
 }
 
+static void handle___vgic_v5_detect_ppis(struct kvm_cpu_context *host_ctxt)
+{
+	u64 ppi[2];
+
+	__vgic_v5_detect_ppis(ppi);
+
+	cpu_reg(host_ctxt, 1) = ppi[0];
+	cpu_reg(host_ctxt, 2) = ppi[1];
+}
+
 typedef void (*hcall_t)(struct kvm_cpu_context *);
 
 #define HANDLE_FUNC(x)	[__KVM_HOST_SMCCC_FUNC_##x] = (hcall_t)handle_##x
@@ -627,6 +637,7 @@ static const hcall_t host_hcall[] = {
 	HANDLE_FUNC(__pkvm_vcpu_load),
 	HANDLE_FUNC(__pkvm_vcpu_put),
 	HANDLE_FUNC(__pkvm_tlb_flush_vmid),
+	HANDLE_FUNC(__vgic_v5_detect_ppis),
 };
 
 static void handle_host_hcall(struct kvm_cpu_context *host_ctxt)
diff --git a/arch/arm64/kvm/hyp/vgic-v5-sr.c b/arch/arm64/kvm/hyp/vgic-v5-sr.c
new file mode 100644
index 0000000000000..4b088588757ea
--- /dev/null
+++ b/arch/arm64/kvm/hyp/vgic-v5-sr.c
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 - ARM Ltd
+ */
+
+#include <linux/irqchip/arm-gic-v5.h>
+
+#include <asm/kvm_hyp.h>
+
+void __vgic_v5_detect_ppis(u64 *impl_ppi_mask)
+{
+	/* Disable DVI for all PPIs */
+	write_sysreg_s(0, SYS_ICH_PPI_DVIR0_EL2);
+	write_sysreg_s(0, SYS_ICH_PPI_DVIR1_EL2);
+
+	/* Write all 1s to the PPI enable regs */
+	write_sysreg_s(GENMASK_ULL(63, 0), SYS_ICH_PPI_ENABLER0_EL2);
+	write_sysreg_s(GENMASK_ULL(63, 0), SYS_ICH_PPI_ENABLER1_EL2);
+
+	/* Read back to figure out which are stateful */
+	impl_ppi_mask[0] = read_sysreg_s(SYS_ICH_PPI_ENABLER0_EL2);
+	impl_ppi_mask[1] = read_sysreg_s(SYS_ICH_PPI_ENABLER1_EL2);
+
+	/* Disable them all again! */
+	write_sysreg_s(0, SYS_ICH_PPI_ENABLER0_EL2);
+	write_sysreg_s(0, SYS_ICH_PPI_ENABLER1_EL2);
+}
diff --git a/arch/arm64/kvm/hyp/vhe/Makefile b/arch/arm64/kvm/hyp/vhe/Makefile
index afc4aed9231ac..9695328bbd96e 100644
--- a/arch/arm64/kvm/hyp/vhe/Makefile
+++ b/arch/arm64/kvm/hyp/vhe/Makefile
@@ -10,4 +10,4 @@ CFLAGS_switch.o += -Wno-override-init
 
 obj-y := timer-sr.o sysreg-sr.o debug-sr.o switch.o tlb.o
 obj-y += ../vgic-v3-sr.o ../aarch32.o ../vgic-v2-cpuif-proxy.o ../entry.o \
-	 ../fpsimd.o ../hyp-entry.o ../exception.o
+	 ../fpsimd.o ../hyp-entry.o ../exception.o ../vgic-v5-sr.o
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index dc9f9db310264..c602f24bab1bb 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -750,5 +750,9 @@ int kvm_vgic_hyp_init(void)
 	}
 
 	kvm_info("vgic interrupt IRQ%d\n", kvm_vgic_global_state.maint_irq);
+
+	/* Always safe to call */
+	vgic_v5_get_implemented_ppis();
+
 	return 0;
 }
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 2d3811f4e1174..1fe1790f1f874 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -5,6 +5,8 @@
 
 #include "vgic.h"
 
+static struct vgic_v5_ppi_caps *ppi_caps;
+
 /*
  * Probe for a vGICv5 compatible interrupt controller, returning 0 on success.
  * Currently only supports GICv3-based VMs on a GICv5 host, and hence only
@@ -50,3 +52,30 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
 
 	return 0;
 }
+
+/*
+ * Not all PPIs are guaranteed to be implemented for
+ * GICv5. Deterermine which ones are, and generate a mask. This is
+ * called early in boot, so we can just write directly to the ICH_PPI*
+ * regs and have no state to preserve.
+ */
+void vgic_v5_get_implemented_ppis(void)
+{
+	if (!cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF))
+		return;
+
+	/* Never freed again */
+	ppi_caps = kzalloc(sizeof(*ppi_caps), GFP_KERNEL);
+	if (!ppi_caps)
+		return;
+
+	if (!has_vhe()) {
+		struct arm_smccc_res res;
+
+		kvm_call_hyp_nvhe_res(&res, __vgic_v5_detect_ppis);
+		ppi_caps->impl_ppi_mask[0] = res.a1;
+		ppi_caps->impl_ppi_mask[1] = res.a2;
+	} else {
+		__vgic_v5_detect_ppis(ppi_caps->impl_ppi_mask);
+	}
+}
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 5f0fc96b4dc29..15f6afe6b75e1 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -362,6 +362,7 @@ void vgic_debug_init(struct kvm *kvm);
 void vgic_debug_destroy(struct kvm *kvm);
 
 int vgic_v5_probe(const struct gic_kvm_info *info);
+void vgic_v5_get_implemented_ppis(void);
 
 static inline int vgic_v3_max_apr_idx(struct kvm_vcpu *vcpu)
 {
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 6778f676eaf08..c7786a2607ecd 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -414,6 +414,11 @@ struct vgic_v3_cpu_if {
 	unsigned int used_lrs;
 };
 
+/* What PPI capabilities does a GICv5 host have */
+struct vgic_v5_ppi_caps {
+	u64	impl_ppi_mask[2];
+};
+
 struct vgic_cpu {
 	/* CPU vif control registers for world switch */
 	union {
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 07/36] KVM: arm64: gic: Introduce interrupt type helpers
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (7 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 08/36] KVM: arm64: Introduce kvm_call_hyp_nvhe_res() Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-06 14:51   ` Joey Gouly
  2026-01-06 18:43   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 12/36] KVM: arm64: gic-v5: Add emulation for ICC_IAFFIDR_EL1 accesses Sascha Bischoff
                   ` (27 subsequent siblings)
  36 siblings, 2 replies; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

GICv5 has moved from using interrupt ranges for different interrupt
types to using some of the upper bits of the interrupt ID to denote
the interrupt type. This is not compatible with older GICs (which rely
on ranges of interrupts to determine the type), and hence a set of
helpers is introduced. These helpers take a struct kvm*, and use the
vgic model to determine how to interpret the interrupt ID.

Helpers are introduced for PPIs, SPIs, and LPIs. Additionally, a
helper is introduced to determine if an interrupt is private - SGIs
and PPIs for older GICs, and PPIs only for GICv5.

The helpers are plumbed into the core vgic code, as well as the Arch
Timer and PMU code.

There should be no functional changes as part of this change.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/kvm/arch_timer.c           |  2 +-
 arch/arm64/kvm/pmu-emul.c             |  7 ++-
 arch/arm64/kvm/vgic/vgic-kvm-device.c |  2 +-
 arch/arm64/kvm/vgic/vgic.c            | 14 ++---
 include/kvm/arm_vgic.h                | 82 +++++++++++++++++++++++++--
 5 files changed, 91 insertions(+), 16 deletions(-)

diff --git a/arch/arm64/kvm/arch_timer.c b/arch/arm64/kvm/arch_timer.c
index 99a07972068d1..6f033f6644219 100644
--- a/arch/arm64/kvm/arch_timer.c
+++ b/arch/arm64/kvm/arch_timer.c
@@ -1598,7 +1598,7 @@ int kvm_arm_timer_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
 	if (get_user(irq, uaddr))
 		return -EFAULT;
 
-	if (!(irq_is_ppi(irq)))
+	if (!(irq_is_ppi(vcpu->kvm, irq)))
 		return -EINVAL;
 
 	mutex_lock(&vcpu->kvm->arch.config_lock);
diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
index b03dbda7f1ab9..afc838ea2503e 100644
--- a/arch/arm64/kvm/pmu-emul.c
+++ b/arch/arm64/kvm/pmu-emul.c
@@ -939,7 +939,8 @@ int kvm_arm_pmu_v3_enable(struct kvm_vcpu *vcpu)
 		 * number against the dimensions of the vgic and make sure
 		 * it's valid.
 		 */
-		if (!irq_is_ppi(irq) && !vgic_valid_spi(vcpu->kvm, irq))
+		if (!irq_is_ppi(vcpu->kvm, irq) &&
+		    !vgic_valid_spi(vcpu->kvm, irq))
 			return -EINVAL;
 	} else if (kvm_arm_pmu_irq_initialized(vcpu)) {
 		   return -EINVAL;
@@ -991,7 +992,7 @@ static bool pmu_irq_is_valid(struct kvm *kvm, int irq)
 		if (!kvm_arm_pmu_irq_initialized(vcpu))
 			continue;
 
-		if (irq_is_ppi(irq)) {
+		if (irq_is_ppi(vcpu->kvm, irq)) {
 			if (vcpu->arch.pmu.irq_num != irq)
 				return false;
 		} else {
@@ -1142,7 +1143,7 @@ int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
 			return -EFAULT;
 
 		/* The PMU overflow interrupt can be a PPI or a valid SPI. */
-		if (!(irq_is_ppi(irq) || irq_is_spi(irq)))
+		if (!(irq_is_ppi(vcpu->kvm, irq) || irq_is_spi(vcpu->kvm, irq)))
 			return -EINVAL;
 
 		if (!pmu_irq_is_valid(kvm, irq))
diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c
index 3d1a776b716d7..b12ba99a423e5 100644
--- a/arch/arm64/kvm/vgic/vgic-kvm-device.c
+++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c
@@ -639,7 +639,7 @@ static int vgic_v3_set_attr(struct kvm_device *dev,
 		if (vgic_initialized(dev->kvm))
 			return -EBUSY;
 
-		if (!irq_is_ppi(val))
+		if (!irq_is_ppi(dev->kvm, val))
 			return -EINVAL;
 
 		dev->kvm->arch.vgic.mi_intid = val;
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index 430aa98888fda..2c0e8803342e2 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -94,7 +94,7 @@ struct vgic_irq *vgic_get_irq(struct kvm *kvm, u32 intid)
 	}
 
 	/* LPIs */
-	if (intid >= VGIC_MIN_LPI)
+	if (irq_is_lpi(kvm, intid))
 		return vgic_get_lpi(kvm, intid);
 
 	return NULL;
@@ -123,7 +123,7 @@ static void vgic_release_lpi_locked(struct vgic_dist *dist, struct vgic_irq *irq
 
 static __must_check bool __vgic_put_irq(struct kvm *kvm, struct vgic_irq *irq)
 {
-	if (irq->intid < VGIC_MIN_LPI)
+	if (!irq_is_lpi(kvm, irq->intid))
 		return false;
 
 	return refcount_dec_and_test(&irq->refcount);
@@ -148,7 +148,7 @@ void vgic_put_irq(struct kvm *kvm, struct vgic_irq *irq)
 	 * Acquire/release it early on lockdep kernels to make locking issues
 	 * in rare release paths a bit more obvious.
 	 */
-	if (IS_ENABLED(CONFIG_LOCKDEP) && irq->intid >= VGIC_MIN_LPI) {
+	if (IS_ENABLED(CONFIG_LOCKDEP) && irq_is_lpi(kvm, irq->intid)) {
 		guard(spinlock_irqsave)(&dist->lpi_xa.xa_lock);
 	}
 
@@ -186,7 +186,7 @@ void vgic_flush_pending_lpis(struct kvm_vcpu *vcpu)
 	raw_spin_lock_irqsave(&vgic_cpu->ap_list_lock, flags);
 
 	list_for_each_entry_safe(irq, tmp, &vgic_cpu->ap_list_head, ap_list) {
-		if (irq->intid >= VGIC_MIN_LPI) {
+		if (irq_is_lpi(vcpu->kvm, irq->intid)) {
 			raw_spin_lock(&irq->irq_lock);
 			list_del(&irq->ap_list);
 			irq->vcpu = NULL;
@@ -521,12 +521,12 @@ int kvm_vgic_inject_irq(struct kvm *kvm, struct kvm_vcpu *vcpu,
 	if (ret)
 		return ret;
 
-	if (!vcpu && intid < VGIC_NR_PRIVATE_IRQS)
+	if (!vcpu && irq_is_private(kvm, intid))
 		return -EINVAL;
 
 	trace_vgic_update_irq_pending(vcpu ? vcpu->vcpu_idx : 0, intid, level);
 
-	if (intid < VGIC_NR_PRIVATE_IRQS)
+	if (irq_is_private(kvm, intid))
 		irq = vgic_get_vcpu_irq(vcpu, intid);
 	else
 		irq = vgic_get_irq(kvm, intid);
@@ -685,7 +685,7 @@ int kvm_vgic_set_owner(struct kvm_vcpu *vcpu, unsigned int intid, void *owner)
 		return -EAGAIN;
 
 	/* SGIs and LPIs cannot be wired up to any device */
-	if (!irq_is_ppi(intid) && !vgic_valid_spi(vcpu->kvm, intid))
+	if (!irq_is_ppi(vcpu->kvm, intid) && !vgic_valid_spi(vcpu->kvm, intid))
 		return -EINVAL;
 
 	irq = vgic_get_vcpu_irq(vcpu, intid);
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index b261fb3968d03..6778f676eaf08 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -19,6 +19,7 @@
 #include <linux/jump_label.h>
 
 #include <linux/irqchip/arm-gic-v4.h>
+#include <linux/irqchip/arm-gic-v5.h>
 
 #define VGIC_V3_MAX_CPUS	512
 #define VGIC_V2_MAX_CPUS	8
@@ -31,9 +32,78 @@
 #define VGIC_MIN_LPI		8192
 #define KVM_IRQCHIP_NUM_PINS	(1020 - 32)
 
-#define irq_is_ppi(irq) ((irq) >= VGIC_NR_SGIS && (irq) < VGIC_NR_PRIVATE_IRQS)
-#define irq_is_spi(irq) ((irq) >= VGIC_NR_PRIVATE_IRQS && \
-			 (irq) <= VGIC_MAX_SPI)
+#define is_v5_type(t, i)	(FIELD_GET(GICV5_HWIRQ_TYPE, (i)) == (t))
+
+#define __irq_is_sgi(t, i)						\
+	({								\
+		bool __ret;						\
+									\
+		switch (t) {						\
+		case KVM_DEV_TYPE_ARM_VGIC_V5:				\
+			__ret = false;					\
+			break;						\
+		default:						\
+			__ret  = (i) < VGIC_NR_SGIS;			\
+		}							\
+									\
+		__ret;							\
+	})
+
+#define __irq_is_ppi(t, i)						\
+	({								\
+		bool __ret;						\
+									\
+		switch (t) {						\
+		case KVM_DEV_TYPE_ARM_VGIC_V5:				\
+			__ret = is_v5_type(GICV5_HWIRQ_TYPE_PPI, (i));	\
+			break;						\
+		default:						\
+			__ret  = (i) >= VGIC_NR_SGIS;			\
+			__ret &= (i) < VGIC_NR_PRIVATE_IRQS;		\
+		}							\
+									\
+		__ret;							\
+	})
+
+#define __irq_is_spi(t, i)						\
+	({								\
+		bool __ret;						\
+									\
+		switch (t) {						\
+		case KVM_DEV_TYPE_ARM_VGIC_V5:				\
+			__ret = is_v5_type(GICV5_HWIRQ_TYPE_SPI, (i));	\
+			break;						\
+		default:						\
+			__ret  = (i) <= VGIC_MAX_SPI;			\
+			__ret &= (i) >= VGIC_NR_PRIVATE_IRQS;		\
+		}							\
+									\
+		__ret;							\
+	})
+
+#define __irq_is_lpi(t, i)						\
+	({								\
+		bool __ret;						\
+									\
+		switch (t) {						\
+		case KVM_DEV_TYPE_ARM_VGIC_V5:				\
+			__ret = is_v5_type(GICV5_HWIRQ_TYPE_LPI, (i));	\
+			break;						\
+		default:						\
+			__ret  = (i) >= 8192;				\
+		}							\
+									\
+		__ret;							\
+	})
+
+#define irq_is_sgi(k, i) __irq_is_sgi((k)->arch.vgic.vgic_model, i)
+#define irq_is_ppi(k, i) __irq_is_ppi((k)->arch.vgic.vgic_model, i)
+#define irq_is_spi(k, i) __irq_is_spi((k)->arch.vgic.vgic_model, i)
+#define irq_is_lpi(k, i) __irq_is_lpi((k)->arch.vgic.vgic_model, i)
+
+#define irq_is_private(k, i) (irq_is_ppi(k, i) || irq_is_sgi(k, i))
+
+#define vgic_is_v5(k) ((k)->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
 
 enum vgic_type {
 	VGIC_V2,		/* Good ol' GICv2 */
@@ -418,8 +488,12 @@ u64 vgic_v3_get_misr(struct kvm_vcpu *vcpu);
 
 #define irqchip_in_kernel(k)	(!!((k)->arch.vgic.in_kernel))
 #define vgic_initialized(k)	((k)->arch.vgic.initialized)
-#define vgic_valid_spi(k, i)	(((i) >= VGIC_NR_PRIVATE_IRQS) && \
+#define vgic_valid_nv5_spi(k, i)	(((i) >= VGIC_NR_PRIVATE_IRQS) && \
 			((i) < (k)->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS))
+#define vgic_valid_v5_spi(k, i)	(irq_is_spi(k, i) && \
+				 (FIELD_GET(GICV5_HWIRQ_ID, i) < (k)->arch.vgic.nr_spis))
+#define vgic_valid_spi(k, i) (vgic_is_v5(k) ?				\
+			      vgic_valid_v5_spi(k, i) : vgic_valid_nv5_spi(k, i))
 
 bool kvm_vcpu_has_pending_irqs(struct kvm_vcpu *vcpu);
 void kvm_vgic_sync_hwstate(struct kvm_vcpu *vcpu);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 12/36] KVM: arm64: gic-v5: Add emulation for ICC_IAFFIDR_EL1 accesses
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (8 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 07/36] KVM: arm64: gic: Introduce interrupt type helpers Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 11:10   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 11/36] KVM: arm64: gic-v5: Support GICv5 FGTs & FGUs Sascha Bischoff
                   ` (26 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

GICv5 doesn't provide an ICV_IAFFIDR_EL1 or ICH_IAFFIDR_EL2 for
providing the IAFFID to the guest. A guest access to the
ICC_IAFFIDR_EL1 must therefore be trapped and emulated to avoid the
guest accessing the host's ICC_IAFFIDR_EL1.

The virtual IAFFID is provided to the guest when it reads
ICC_IAFFIDR_EL1 (which always traps back to the hypervisor). Writes are
rightly ignored. KVM treats the GICv5 VPEID, the virtual IAFFID, and
the vcpu_id as the same, and so the vcpu_id is returned.

The trapping for the ICC_IAFFIDR_EL1 is always enabled when in a guest
context.

Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/kvm/config.c   | 10 +++++++++-
 arch/arm64/kvm/sys_regs.c | 16 ++++++++++++++++
 2 files changed, 25 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
index 5f57dc07cc482..eb0c6f4d95b6d 100644
--- a/arch/arm64/kvm/config.c
+++ b/arch/arm64/kvm/config.c
@@ -1582,6 +1582,14 @@ static void __compute_hdfgwtr(struct kvm_vcpu *vcpu)
 		*vcpu_fgt(vcpu, HDFGWTR_EL2) |= HDFGWTR_EL2_MDSCR_EL1;
 }
 
+static void __compute_ich_hfgrtr(struct kvm_vcpu *vcpu)
+{
+	__compute_fgt(vcpu, ICH_HFGRTR_EL2);
+
+	/* ICC_IAFFIDR_EL1 *always* needs to be trapped when running a guest */
+	*vcpu_fgt(vcpu, ICH_HFGRTR_EL2) &= ~ICH_HFGRTR_EL2_ICC_IAFFIDR_EL1;
+}
+
 void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
 {
 	if (!cpus_have_final_cap(ARM64_HAS_FGT))
@@ -1607,7 +1615,7 @@ void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
 	if (!cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF))
 		return;
 
-	__compute_fgt(vcpu, ICH_HFGRTR_EL2);
+	__compute_ich_hfgrtr(vcpu);
 	__compute_fgt(vcpu, ICH_HFGWTR_EL2);
 	__compute_fgt(vcpu, ICH_HFGITR_EL2);
 }
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index fbbd7b6ff6507..383ada0d75922 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -681,6 +681,21 @@ static bool access_gic_dir(struct kvm_vcpu *vcpu,
 	return true;
 }
 
+static bool access_gicv5_iaffid(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
+				const struct sys_reg_desc *r)
+{
+	if (p->is_write)
+		return ignore_write(vcpu, p);
+
+	/*
+	 * For GICv5 VMs, the IAFFID value is the same as the VPE ID. The VPE ID
+	 * is the same as the VCPU's ID.
+	 */
+	p->regval = FIELD_PREP(ICC_IAFFIDR_EL1_IAFFID, vcpu->vcpu_id);
+
+	return true;
+}
+
 static bool trap_raz_wi(struct kvm_vcpu *vcpu,
 			struct sys_reg_params *p,
 			const struct sys_reg_desc *r)
@@ -3411,6 +3426,7 @@ static const struct sys_reg_desc sys_reg_descs[] = {
 	{ SYS_DESC(SYS_ICC_AP1R1_EL1), undef_access },
 	{ SYS_DESC(SYS_ICC_AP1R2_EL1), undef_access },
 	{ SYS_DESC(SYS_ICC_AP1R3_EL1), undef_access },
+	{ SYS_DESC(SYS_ICC_IAFFIDR_EL1), access_gicv5_iaffid },
 	{ SYS_DESC(SYS_ICC_DIR_EL1), access_gic_dir },
 	{ SYS_DESC(SYS_ICC_RPR_EL1), undef_access },
 	{ SYS_DESC(SYS_ICC_SGI1R_EL1), access_gic_sgi },
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 10/36] KVM: arm64: gic-v5: Sanitize ID_AA64PFR2_EL1.GCIE
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (10 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 11/36] KVM: arm64: gic-v5: Support GICv5 FGTs & FGUs Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 10:58   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 13/36] KVM: arm64: gic: Set vgic_model before initing private IRQs Sascha Bischoff
                   ` (24 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

Set the guest's view of the GCIE field to IMP when running a GICv5 VM,
NI otherwise. Reject any writes to the register that try to do
anything but set GCIE to IMP when running a GICv5 VM.

As part of this change, we also introduce vgic_is_v5(kvm), in order to
check if the guest is a GICv5-native VM. We're also required to extend
vgic_is_v3_compat to check for the actual vgic_model. This has one
potential issue - if any of the vgic_is_v* checks are used prior to
setting the vgic_model (that is, before kvm_vgic_create) then
vgic_model will be set to 0, which can result in a false-positive.

Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/kvm/sys_regs.c  | 39 ++++++++++++++++++++++++++++++--------
 arch/arm64/kvm/vgic/vgic.h | 10 +++++++++-
 2 files changed, 40 insertions(+), 9 deletions(-)

diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index c8fd7c6a12a13..a065f8939bc8f 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -1758,6 +1758,7 @@ static u8 pmuver_to_perfmon(u8 pmuver)
 
 static u64 sanitise_id_aa64pfr0_el1(const struct kvm_vcpu *vcpu, u64 val);
 static u64 sanitise_id_aa64pfr1_el1(const struct kvm_vcpu *vcpu, u64 val);
+static u64 sanitise_id_aa64pfr2_el1(const struct kvm_vcpu *vcpu, u64 val);
 static u64 sanitise_id_aa64dfr0_el1(const struct kvm_vcpu *vcpu, u64 val);
 
 /* Read a sanitised cpufeature ID register by sys_reg_desc */
@@ -1783,10 +1784,7 @@ static u64 __kvm_read_sanitised_id_reg(const struct kvm_vcpu *vcpu,
 		val = sanitise_id_aa64pfr1_el1(vcpu, val);
 		break;
 	case SYS_ID_AA64PFR2_EL1:
-		val &= ID_AA64PFR2_EL1_FPMR |
-			(kvm_has_mte(vcpu->kvm) ?
-			 ID_AA64PFR2_EL1_MTEFAR | ID_AA64PFR2_EL1_MTESTOREONLY :
-			 0);
+		val = sanitise_id_aa64pfr2_el1(vcpu, val);
 		break;
 	case SYS_ID_AA64ISAR1_EL1:
 		if (!vcpu_has_ptrauth(vcpu))
@@ -2024,6 +2022,20 @@ static u64 sanitise_id_aa64pfr1_el1(const struct kvm_vcpu *vcpu, u64 val)
 	return val;
 }
 
+static u64 sanitise_id_aa64pfr2_el1(const struct kvm_vcpu *vcpu, u64 val)
+{
+	val &= ID_AA64PFR2_EL1_FPMR |
+		(kvm_has_mte(vcpu->kvm) ?
+			ID_AA64PFR2_EL1_MTEFAR | ID_AA64PFR2_EL1_MTESTOREONLY : 0);
+
+	if (vgic_is_v5(vcpu->kvm)) {
+		val &= ~ID_AA64PFR2_EL1_GCIE_MASK;
+		val |= SYS_FIELD_PREP_ENUM(ID_AA64PFR2_EL1, GCIE, IMP);
+	}
+
+	return val;
+}
+
 static u64 sanitise_id_aa64dfr0_el1(const struct kvm_vcpu *vcpu, u64 val)
 {
 	val = ID_REG_LIMIT_FIELD_ENUM(val, ID_AA64DFR0_EL1, DebugVer, V8P8);
@@ -2221,6 +2233,16 @@ static int set_id_aa64pfr1_el1(struct kvm_vcpu *vcpu,
 	return set_id_reg(vcpu, rd, user_val);
 }
 
+static int set_id_aa64pfr2_el1(struct kvm_vcpu *vcpu,
+			       const struct sys_reg_desc *rd, u64 user_val)
+{
+	if (vgic_is_v5(vcpu->kvm) &&
+	    FIELD_GET(ID_AA64PFR2_EL1_GCIE_MASK, user_val) != ID_AA64PFR2_EL1_GCIE_IMP)
+		return -EINVAL;
+
+	return set_id_reg(vcpu, rd, user_val);
+}
+
 /*
  * Allow userspace to de-feature a stage-2 translation granule but prevent it
  * from claiming the impossible.
@@ -3202,10 +3224,11 @@ static const struct sys_reg_desc sys_reg_descs[] = {
 				       ID_AA64PFR1_EL1_RES0 |
 				       ID_AA64PFR1_EL1_MPAM_frac |
 				       ID_AA64PFR1_EL1_MTE)),
-	ID_WRITABLE(ID_AA64PFR2_EL1,
-		    ID_AA64PFR2_EL1_FPMR |
-		    ID_AA64PFR2_EL1_MTEFAR |
-		    ID_AA64PFR2_EL1_MTESTOREONLY),
+	ID_FILTERED(ID_AA64PFR2_EL1, id_aa64pfr2_el1,
+		    ~(ID_AA64PFR2_EL1_FPMR |
+		      ID_AA64PFR2_EL1_MTEFAR |
+		      ID_AA64PFR2_EL1_MTESTOREONLY |
+		      ID_AA64PFR2_EL1_GCIE)),
 	ID_UNALLOCATED(4,3),
 	ID_WRITABLE(ID_AA64ZFR0_EL1, ~ID_AA64ZFR0_EL1_RES0),
 	ID_HIDDEN(ID_AA64SMFR0_EL1),
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 15f6afe6b75e1..eb16184c14cc5 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -456,8 +456,16 @@ void vgic_v3_nested_update_mi(struct kvm_vcpu *vcpu);
 
 static inline bool vgic_is_v3_compat(struct kvm *kvm)
 {
+	/*
+	 * We need to be careful here. This could be called early,
+	 * which means that there is no vgic_model set. For the time
+	 * being, fall back to assuming that we're trying run a legacy
+	 * VM in that case, which keeps existing software happy. Long
+	 * term, this will need to be revisited a little.
+	 */
 	return cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF) &&
-		kvm_vgic_global_state.has_gcie_v3_compat;
+		kvm_vgic_global_state.has_gcie_v3_compat &&
+		kvm->arch.vgic.vgic_model != KVM_DEV_TYPE_ARM_VGIC_V5;
 }
 
 static inline bool vgic_is_v3(struct kvm *kvm)
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 11/36] KVM: arm64: gic-v5: Support GICv5 FGTs & FGUs
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (9 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 12/36] KVM: arm64: gic-v5: Add emulation for ICC_IAFFIDR_EL1 accesses Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 11:19   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 10/36] KVM: arm64: gic-v5: Sanitize ID_AA64PFR2_EL1.GCIE Sascha Bischoff
                   ` (25 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

Extend the existing FGT/FGU infrastructure to include the GICv5 trap
registers (ICH_HFGRTR_EL2, ICH_HFGWTR_EL2, ICH_HFGITR_EL2). This
involves mapping the trap registers and their bits to the
corresponding feature that introduces them (FEAT_GCIE for all, in this
case), and mapping each trap bit to the system register/instruction
controlled by it.

As of this change, none of the GICv5 instructions or register accesses
are being trapped.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/include/asm/kvm_host.h       | 19 +++++
 arch/arm64/include/asm/vncr_mapping.h   |  3 +
 arch/arm64/kvm/arm.c                    |  3 +
 arch/arm64/kvm/config.c                 | 94 ++++++++++++++++++++++++-
 arch/arm64/kvm/emulate-nested.c         | 68 ++++++++++++++++++
 arch/arm64/kvm/hyp/include/hyp/switch.h | 27 +++++++
 arch/arm64/kvm/hyp/nvhe/switch.c        |  3 +
 arch/arm64/kvm/sys_regs.c               |  2 +
 8 files changed, 218 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 971b153b0a3fa..f08c333d8b113 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -287,6 +287,9 @@ enum fgt_group_id {
 	HDFGRTR2_GROUP,
 	HDFGWTR2_GROUP = HDFGRTR2_GROUP,
 	HFGITR2_GROUP,
+	ICH_HFGRTR_GROUP,
+	ICH_HFGWTR_GROUP = ICH_HFGRTR_GROUP,
+	ICH_HFGITR_GROUP,
 
 	/* Must be last */
 	__NR_FGT_GROUP_IDS__
@@ -623,6 +626,10 @@ enum vcpu_sysreg {
 	VNCR(ICH_HCR_EL2),
 	VNCR(ICH_VMCR_EL2),
 
+	VNCR(ICH_HFGRTR_EL2),
+	VNCR(ICH_HFGWTR_EL2),
+	VNCR(ICH_HFGITR_EL2),
+
 	NR_SYS_REGS	/* Nothing after this line! */
 };
 
@@ -652,6 +659,9 @@ extern struct fgt_masks hfgwtr2_masks;
 extern struct fgt_masks hfgitr2_masks;
 extern struct fgt_masks hdfgrtr2_masks;
 extern struct fgt_masks hdfgwtr2_masks;
+extern struct fgt_masks ich_hfgrtr_masks;
+extern struct fgt_masks ich_hfgwtr_masks;
+extern struct fgt_masks ich_hfgitr_masks;
 
 extern struct fgt_masks kvm_nvhe_sym(hfgrtr_masks);
 extern struct fgt_masks kvm_nvhe_sym(hfgwtr_masks);
@@ -664,6 +674,9 @@ extern struct fgt_masks kvm_nvhe_sym(hfgwtr2_masks);
 extern struct fgt_masks kvm_nvhe_sym(hfgitr2_masks);
 extern struct fgt_masks kvm_nvhe_sym(hdfgrtr2_masks);
 extern struct fgt_masks kvm_nvhe_sym(hdfgwtr2_masks);
+extern struct fgt_masks kvm_nvhe_sym(ich_hfgrtr_masks);
+extern struct fgt_masks kvm_nvhe_sym(ich_hfgwtr_masks);
+extern struct fgt_masks kvm_nvhe_sym(ich_hfgitr_masks);
 
 struct kvm_cpu_context {
 	struct user_pt_regs regs;	/* sp = sp_el0 */
@@ -1637,6 +1650,11 @@ static __always_inline enum fgt_group_id __fgt_reg_to_group_id(enum vcpu_sysreg
 	case HDFGRTR2_EL2:
 	case HDFGWTR2_EL2:
 		return HDFGRTR2_GROUP;
+	case ICH_HFGRTR_EL2:
+	case ICH_HFGWTR_EL2:
+		return ICH_HFGRTR_GROUP;
+	case ICH_HFGITR_EL2:
+		return ICH_HFGITR_GROUP;
 	default:
 		BUILD_BUG_ON(1);
 	}
@@ -1651,6 +1669,7 @@ static __always_inline enum fgt_group_id __fgt_reg_to_group_id(enum vcpu_sysreg
 		case HDFGWTR_EL2:					\
 		case HFGWTR2_EL2:					\
 		case HDFGWTR2_EL2:					\
+		case ICH_HFGWTR_EL2:					\
 			p = &(vcpu)->arch.fgt[id].w;			\
 			break;						\
 		default:						\
diff --git a/arch/arm64/include/asm/vncr_mapping.h b/arch/arm64/include/asm/vncr_mapping.h
index c2485a862e690..14366d35ce82f 100644
--- a/arch/arm64/include/asm/vncr_mapping.h
+++ b/arch/arm64/include/asm/vncr_mapping.h
@@ -108,5 +108,8 @@
 #define VNCR_MPAMVPM5_EL2       0x968
 #define VNCR_MPAMVPM6_EL2       0x970
 #define VNCR_MPAMVPM7_EL2       0x978
+#define VNCR_ICH_HFGITR_EL2	0xB10
+#define VNCR_ICH_HFGRTR_EL2	0xB18
+#define VNCR_ICH_HFGWTR_EL2	0xB20
 
 #endif /* __ARM64_VNCR_MAPPING_H__ */
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 4f80da0c0d1de..b7cf9d86aabb7 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -2474,6 +2474,9 @@ static void kvm_hyp_init_symbols(void)
 	kvm_nvhe_sym(hfgitr2_masks) = hfgitr2_masks;
 	kvm_nvhe_sym(hdfgrtr2_masks)= hdfgrtr2_masks;
 	kvm_nvhe_sym(hdfgwtr2_masks)= hdfgwtr2_masks;
+	kvm_nvhe_sym(ich_hfgrtr_masks) = ich_hfgrtr_masks;
+	kvm_nvhe_sym(ich_hfgwtr_masks) = ich_hfgwtr_masks;
+	kvm_nvhe_sym(ich_hfgitr_masks) = ich_hfgitr_masks;
 
 	/*
 	 * Flush entire BSS since part of its data containing init symbols is read
diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
index 3845b188551b6..5f57dc07cc482 100644
--- a/arch/arm64/kvm/config.c
+++ b/arch/arm64/kvm/config.c
@@ -219,6 +219,7 @@ struct reg_feat_map_desc {
 #define FEAT_FGT2		ID_AA64MMFR0_EL1, FGT, FGT2
 #define FEAT_MTPMU		ID_AA64DFR0_EL1, MTPMU, IMP
 #define FEAT_HCX		ID_AA64MMFR1_EL1, HCX, IMP
+#define FEAT_GCIE		ID_AA64PFR2_EL1, GCIE, IMP
 
 static bool not_feat_aa64el3(struct kvm *kvm)
 {
@@ -1168,6 +1169,58 @@ static const struct reg_bits_to_feat_map mdcr_el2_feat_map[] = {
 static const DECLARE_FEAT_MAP(mdcr_el2_desc, MDCR_EL2,
 			      mdcr_el2_feat_map, FEAT_AA64EL2);
 
+static const struct reg_bits_to_feat_map ich_hfgrtr_feat_map[] = {
+	NEEDS_FEAT(ICH_HFGRTR_EL2_ICC_APR_EL1 |
+		   ICH_HFGRTR_EL2_ICC_IDRn_EL1 |
+		   ICH_HFGRTR_EL2_ICC_CR0_EL1 |
+		   ICH_HFGRTR_EL2_ICC_HPPIR_EL1 |
+		   ICH_HFGRTR_EL2_ICC_PCR_EL1 |
+		   ICH_HFGRTR_EL2_ICC_ICSR_EL1 |
+		   ICH_HFGRTR_EL2_ICC_IAFFIDR_EL1 |
+		   ICH_HFGRTR_EL2_ICC_PPI_HMRn_EL1 |
+		   ICH_HFGRTR_EL2_ICC_PPI_ENABLERn_EL1 |
+		   ICH_HFGRTR_EL2_ICC_PPI_PENDRn_EL1 |
+		   ICH_HFGRTR_EL2_ICC_PPI_PRIORITYRn_EL1 |
+		   ICH_HFGRTR_EL2_ICC_PPI_ACTIVERn_EL1,
+		   FEAT_GCIE),
+};
+
+static const DECLARE_FEAT_MAP_FGT(ich_hfgrtr_desc, ich_hfgrtr_masks,
+				  ich_hfgrtr_feat_map, FEAT_GCIE);
+
+static const struct reg_bits_to_feat_map ich_hfgwtr_feat_map[] = {
+	NEEDS_FEAT(ICH_HFGWTR_EL2_ICC_APR_EL1 |
+		   ICH_HFGWTR_EL2_ICC_CR0_EL1 |
+		   ICH_HFGWTR_EL2_ICC_PCR_EL1 |
+		   ICH_HFGWTR_EL2_ICC_ICSR_EL1 |
+		   ICH_HFGWTR_EL2_ICC_PPI_ENABLERn_EL1 |
+		   ICH_HFGWTR_EL2_ICC_PPI_PENDRn_EL1 |
+		   ICH_HFGWTR_EL2_ICC_PPI_PRIORITYRn_EL1 |
+		   ICH_HFGWTR_EL2_ICC_PPI_ACTIVERn_EL1,
+		   FEAT_GCIE),
+};
+
+static const DECLARE_FEAT_MAP_FGT(ich_hfgwtr_desc, ich_hfgwtr_masks,
+				  ich_hfgwtr_feat_map, FEAT_GCIE);
+
+static const struct reg_bits_to_feat_map ich_hfgitr_feat_map[] = {
+	NEEDS_FEAT(ICH_HFGITR_EL2_GICCDEN |
+		   ICH_HFGITR_EL2_GICCDDIS |
+		   ICH_HFGITR_EL2_GICCDPRI |
+		   ICH_HFGITR_EL2_GICCDAFF |
+		   ICH_HFGITR_EL2_GICCDPEND |
+		   ICH_HFGITR_EL2_GICCDRCFG |
+		   ICH_HFGITR_EL2_GICCDHM |
+		   ICH_HFGITR_EL2_GICCDEOI |
+		   ICH_HFGITR_EL2_GICCDDI |
+		   ICH_HFGITR_EL2_GICRCDIA |
+		   ICH_HFGITR_EL2_GICRCDNMIA,
+		   FEAT_GCIE),
+};
+
+static const DECLARE_FEAT_MAP_FGT(ich_hfgitr_desc, ich_hfgitr_masks,
+				  ich_hfgitr_feat_map, FEAT_GCIE);
+
 static void __init check_feat_map(const struct reg_bits_to_feat_map *map,
 				  int map_size, u64 resx, const char *str)
 {
@@ -1211,6 +1264,9 @@ void __init check_feature_map(void)
 	check_reg_desc(&tcr2_el2_desc);
 	check_reg_desc(&sctlr_el1_desc);
 	check_reg_desc(&mdcr_el2_desc);
+	check_reg_desc(&ich_hfgrtr_desc);
+	check_reg_desc(&ich_hfgwtr_desc);
+	check_reg_desc(&ich_hfgitr_desc);
 }
 
 static bool idreg_feat_match(struct kvm *kvm, const struct reg_bits_to_feat_map *map)
@@ -1342,6 +1398,16 @@ void compute_fgu(struct kvm *kvm, enum fgt_group_id fgt)
 		val |= compute_reg_res0_bits(kvm, &hdfgwtr2_desc,
 					     0, NEVER_FGU);
 		break;
+	case ICH_HFGRTR_GROUP:
+		val |= compute_reg_res0_bits(kvm, &ich_hfgrtr_desc,
+					     0, NEVER_FGU);
+		val |= compute_reg_res0_bits(kvm, &ich_hfgwtr_desc,
+					     0, NEVER_FGU);
+		break;
+	case ICH_HFGITR_GROUP:
+		val |= compute_reg_res0_bits(kvm, &ich_hfgitr_desc,
+					     0, NEVER_FGU);
+		break;
 	default:
 		BUG();
 	}
@@ -1425,6 +1491,18 @@ void get_reg_fixed_bits(struct kvm *kvm, enum vcpu_sysreg reg, u64 *res0, u64 *r
 		*res0 = compute_reg_res0_bits(kvm, &mdcr_el2_desc, 0, 0);
 		*res1 = MDCR_EL2_RES1;
 		break;
+	case ICH_HFGRTR_EL2:
+		*res0 = compute_reg_res0_bits(kvm, &ich_hfgrtr_desc, 0, 0);
+		*res1 = ICH_HFGRTR_EL2_RES1;
+		break;
+	case ICH_HFGWTR_EL2:
+		*res0 = compute_reg_res0_bits(kvm, &ich_hfgwtr_desc, 0, 0);
+		*res1 = ICH_HFGWTR_EL2_RES1;
+		break;
+	case ICH_HFGITR_EL2:
+		*res0 = compute_reg_res0_bits(kvm, &ich_hfgitr_desc, 0, 0);
+		*res1 = ICH_HFGITR_EL2_RES1;
+		break;
 	default:
 		WARN_ON_ONCE(1);
 		*res0 = *res1 = 0;
@@ -1457,6 +1535,12 @@ static __always_inline struct fgt_masks *__fgt_reg_to_masks(enum vcpu_sysreg reg
 		return &hdfgrtr2_masks;
 	case HDFGWTR2_EL2:
 		return &hdfgwtr2_masks;
+	case ICH_HFGRTR_EL2:
+		return &ich_hfgrtr_masks;
+	case ICH_HFGWTR_EL2:
+		return &ich_hfgwtr_masks;
+	case ICH_HFGITR_EL2:
+		return &ich_hfgitr_masks;
 	default:
 		BUILD_BUG_ON(1);
 	}
@@ -1511,11 +1595,19 @@ void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
 	__compute_fgt(vcpu, HAFGRTR_EL2);
 
 	if (!cpus_have_final_cap(ARM64_HAS_FGT2))
-		return;
+		goto skip_fgt2;
 
 	__compute_fgt(vcpu, HFGRTR2_EL2);
 	__compute_fgt(vcpu, HFGWTR2_EL2);
 	__compute_fgt(vcpu, HFGITR2_EL2);
 	__compute_fgt(vcpu, HDFGRTR2_EL2);
 	__compute_fgt(vcpu, HDFGWTR2_EL2);
+
+skip_fgt2:
+	if (!cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF))
+		return;
+
+	__compute_fgt(vcpu, ICH_HFGRTR_EL2);
+	__compute_fgt(vcpu, ICH_HFGWTR_EL2);
+	__compute_fgt(vcpu, ICH_HFGITR_EL2);
 }
diff --git a/arch/arm64/kvm/emulate-nested.c b/arch/arm64/kvm/emulate-nested.c
index 75d49f83342a5..de316bdf90d46 100644
--- a/arch/arm64/kvm/emulate-nested.c
+++ b/arch/arm64/kvm/emulate-nested.c
@@ -2044,6 +2044,60 @@ static const struct encoding_to_trap_config encoding_to_fgt[] __initconst = {
 	SR_FGT(SYS_AMEVCNTR0_EL0(2),	HAFGRTR, AMEVCNTR02_EL0, 1),
 	SR_FGT(SYS_AMEVCNTR0_EL0(1),	HAFGRTR, AMEVCNTR01_EL0, 1),
 	SR_FGT(SYS_AMEVCNTR0_EL0(0),	HAFGRTR, AMEVCNTR00_EL0, 1),
+
+	/*
+	 * ICH_HFGRTR_EL2 & ICH_HFGWTR_EL2
+	 */
+	SR_FGT(SYS_ICC_APR_EL1,			ICH_HFGRTR, ICC_APR_EL1, 0),
+	SR_FGT(SYS_ICC_IDR0_EL1,		ICH_HFGRTR, ICC_IDRn_EL1, 0),
+	SR_FGT(SYS_ICC_CR0_EL1,			ICH_HFGRTR, ICC_CR0_EL1, 0),
+	SR_FGT(SYS_ICC_HPPIR_EL1,		ICH_HFGRTR, ICC_HPPIR_EL1, 0),
+	SR_FGT(SYS_ICC_PCR_EL1,			ICH_HFGRTR, ICC_PCR_EL1, 0),
+	SR_FGT(SYS_ICC_ICSR_EL1,		ICH_HFGRTR, ICC_ICSR_EL1, 0),
+	SR_FGT(SYS_ICC_IAFFIDR_EL1,		ICH_HFGRTR, ICC_IAFFIDR_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_HMR0_EL1,		ICH_HFGRTR, ICC_PPI_HMRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_HMR1_EL1,		ICH_HFGRTR, ICC_PPI_HMRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_ENABLER0_EL1,	ICH_HFGRTR, ICC_PPI_ENABLERn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_ENABLER1_EL1,	ICH_HFGRTR, ICC_PPI_ENABLERn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_CPENDR0_EL1,		ICH_HFGRTR, ICC_PPI_PENDRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_CPENDR1_EL1,		ICH_HFGRTR, ICC_PPI_PENDRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_SPENDR0_EL1,		ICH_HFGRTR, ICC_PPI_PENDRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_SPENDR1_EL1,		ICH_HFGRTR, ICC_PPI_PENDRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_PRIORITYR0_EL1,	ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_PRIORITYR1_EL1,	ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_PRIORITYR2_EL1,	ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_PRIORITYR3_EL1,	ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_PRIORITYR4_EL1,	ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_PRIORITYR5_EL1,	ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_PRIORITYR6_EL1,	ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_PRIORITYR7_EL1,	ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_PRIORITYR8_EL1,	ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_PRIORITYR9_EL1,	ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_PRIORITYR10_EL1,	ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_PRIORITYR11_EL1,	ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_PRIORITYR12_EL1,	ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_PRIORITYR13_EL1,	ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_PRIORITYR14_EL1,	ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_PRIORITYR15_EL1,	ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_CACTIVER0_EL1,	ICH_HFGRTR, ICC_PPI_ACTIVERn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_CACTIVER1_EL1,	ICH_HFGRTR, ICC_PPI_ACTIVERn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_SACTIVER0_EL1,	ICH_HFGRTR, ICC_PPI_ACTIVERn_EL1, 0),
+	SR_FGT(SYS_ICC_PPI_SACTIVER1_EL1,	ICH_HFGRTR, ICC_PPI_ACTIVERn_EL1, 0),
+
+	/*
+	 * ICH_HFGITR_EL2
+	 */
+	SR_FGT(GICV5_OP_GIC_CDEN,	ICH_HFGITR, GICCDEN, 0),
+	SR_FGT(GICV5_OP_GIC_CDDIS,	ICH_HFGITR, GICCDDIS, 0),
+	SR_FGT(GICV5_OP_GIC_CDPRI,	ICH_HFGITR, GICCDPRI, 0),
+	SR_FGT(GICV5_OP_GIC_CDAFF,	ICH_HFGITR, GICCDAFF, 0),
+	SR_FGT(GICV5_OP_GIC_CDPEND,	ICH_HFGITR, GICCDPEND, 0),
+	SR_FGT(GICV5_OP_GIC_CDRCFG,	ICH_HFGITR, GICCDRCFG, 0),
+	SR_FGT(GICV5_OP_GIC_CDHM,	ICH_HFGITR, GICCDHM, 0),
+	SR_FGT(GICV5_OP_GIC_CDEOI,	ICH_HFGITR, GICCDEOI, 0),
+	SR_FGT(GICV5_OP_GIC_CDDI,	ICH_HFGITR, GICCDDI, 0),
+	SR_FGT(GICV5_OP_GICR_CDIA,	ICH_HFGITR, GICRCDIA, 0),
+	SR_FGT(GICV5_OP_GICR_CDNMIA,	ICH_HFGITR, GICRCDNMIA, 0),
 };
 
 /*
@@ -2118,6 +2172,9 @@ FGT_MASKS(hfgwtr2_masks, HFGWTR2_EL2);
 FGT_MASKS(hfgitr2_masks, HFGITR2_EL2);
 FGT_MASKS(hdfgrtr2_masks, HDFGRTR2_EL2);
 FGT_MASKS(hdfgwtr2_masks, HDFGWTR2_EL2);
+FGT_MASKS(ich_hfgrtr_masks, ICH_HFGRTR_EL2);
+FGT_MASKS(ich_hfgwtr_masks, ICH_HFGWTR_EL2);
+FGT_MASKS(ich_hfgitr_masks, ICH_HFGITR_EL2);
 
 static __init bool aggregate_fgt(union trap_config tc)
 {
@@ -2153,6 +2210,14 @@ static __init bool aggregate_fgt(union trap_config tc)
 		rmasks = &hfgitr2_masks;
 		wmasks = NULL;
 		break;
+	case ICH_HFGRTR_GROUP:
+		rmasks = &ich_hfgrtr_masks;
+		wmasks = &ich_hfgwtr_masks;
+		break;
+	case ICH_HFGITR_GROUP:
+		rmasks = &ich_hfgitr_masks;
+		wmasks = NULL;
+		break;
 	}
 
 	rresx = rmasks->res0 | rmasks->res1;
@@ -2223,6 +2288,9 @@ static __init int check_all_fgt_masks(int ret)
 		&hfgitr2_masks,
 		&hdfgrtr2_masks,
 		&hdfgwtr2_masks,
+		&ich_hfgrtr_masks,
+		&ich_hfgwtr_masks,
+		&ich_hfgitr_masks,
 	};
 	int err = 0;
 
diff --git a/arch/arm64/kvm/hyp/include/hyp/switch.h b/arch/arm64/kvm/hyp/include/hyp/switch.h
index c5d5e5b86eaf0..14805336725f5 100644
--- a/arch/arm64/kvm/hyp/include/hyp/switch.h
+++ b/arch/arm64/kvm/hyp/include/hyp/switch.h
@@ -235,6 +235,18 @@ static inline void __activate_traps_hfgxtr(struct kvm_vcpu *vcpu)
 	__activate_fgt(hctxt, vcpu, HDFGWTR2_EL2);
 }
 
+static inline void __activate_traps_ich_hfgxtr(struct kvm_vcpu *vcpu)
+{
+	struct kvm_cpu_context *hctxt = host_data_ptr(host_ctxt);
+
+	if (!cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF))
+		return;
+
+	__activate_fgt(hctxt, vcpu, ICH_HFGRTR_EL2);
+	__activate_fgt(hctxt, vcpu, ICH_HFGWTR_EL2);
+	__activate_fgt(hctxt, vcpu, ICH_HFGITR_EL2);
+}
+
 #define __deactivate_fgt(htcxt, vcpu, reg)				\
 	do {								\
 		write_sysreg_s(ctxt_sys_reg(hctxt, reg),		\
@@ -267,6 +279,19 @@ static inline void __deactivate_traps_hfgxtr(struct kvm_vcpu *vcpu)
 	__deactivate_fgt(hctxt, vcpu, HDFGWTR2_EL2);
 }
 
+static inline void __deactivate_traps_ich_hfgxtr(struct kvm_vcpu *vcpu)
+{
+	struct kvm_cpu_context *hctxt = host_data_ptr(host_ctxt);
+
+	if (!cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF))
+		return;
+
+	__deactivate_fgt(hctxt, vcpu, ICH_HFGRTR_EL2);
+	__deactivate_fgt(hctxt, vcpu, ICH_HFGWTR_EL2);
+	__deactivate_fgt(hctxt, vcpu, ICH_HFGITR_EL2);
+
+}
+
 static inline void  __activate_traps_mpam(struct kvm_vcpu *vcpu)
 {
 	u64 r = MPAM2_EL2_TRAPMPAM0EL1 | MPAM2_EL2_TRAPMPAM1EL1;
@@ -330,6 +355,7 @@ static inline void __activate_traps_common(struct kvm_vcpu *vcpu)
 	}
 
 	__activate_traps_hfgxtr(vcpu);
+	__activate_traps_ich_hfgxtr(vcpu);
 	__activate_traps_mpam(vcpu);
 }
 
@@ -347,6 +373,7 @@ static inline void __deactivate_traps_common(struct kvm_vcpu *vcpu)
 		write_sysreg_s(ctxt_sys_reg(hctxt, HCRX_EL2), SYS_HCRX_EL2);
 
 	__deactivate_traps_hfgxtr(vcpu);
+	__deactivate_traps_ich_hfgxtr(vcpu);
 	__deactivate_traps_mpam();
 }
 
diff --git a/arch/arm64/kvm/hyp/nvhe/switch.c b/arch/arm64/kvm/hyp/nvhe/switch.c
index d3b9ec8a7c283..c23e22ffac080 100644
--- a/arch/arm64/kvm/hyp/nvhe/switch.c
+++ b/arch/arm64/kvm/hyp/nvhe/switch.c
@@ -44,6 +44,9 @@ struct fgt_masks hfgwtr2_masks;
 struct fgt_masks hfgitr2_masks;
 struct fgt_masks hdfgrtr2_masks;
 struct fgt_masks hdfgwtr2_masks;
+struct fgt_masks ich_hfgrtr_masks;
+struct fgt_masks ich_hfgwtr_masks;
+struct fgt_masks ich_hfgitr_masks;
 
 extern void kvm_nvhe_prepare_backtrace(unsigned long fp, unsigned long pc);
 
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index a065f8939bc8f..fbbd7b6ff6507 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -5630,6 +5630,8 @@ void kvm_calculate_traps(struct kvm_vcpu *vcpu)
 	compute_fgu(kvm, HFGRTR2_GROUP);
 	compute_fgu(kvm, HFGITR2_GROUP);
 	compute_fgu(kvm, HDFGRTR2_GROUP);
+	compute_fgu(kvm, ICH_HFGRTR_GROUP);
+	compute_fgu(kvm, ICH_HFGITR_GROUP);
 
 	set_bit(KVM_ARCH_FLAG_FGU_INITIALIZED, &kvm->arch.flags);
 out:
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 13/36] KVM: arm64: gic: Set vgic_model before initing private IRQs
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (11 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 10/36] KVM: arm64: gic-v5: Sanitize ID_AA64PFR2_EL1.GCIE Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 11:24   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 14/36] KVM: arm64: gic-v5: Add vgic-v5 save/restore hyp interface Sascha Bischoff
                   ` (23 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

Different GIC types require the private IRQs to be initialised
differently. GICv5 is the culprit as it supports both a different
number of private IRQs, and all of these are PPIs (there are no
SGIs). Moreover, as GICv5 uses the top bits of the interrupt ID to
encode the type, the intid also needs to computed differently.

Up until now, the GIC model has been set after initialising the
private IRQs for a VCPU. Move this earlier to ensure that the GIC
model is available when configuring the private IRQs.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/kvm/vgic/vgic-init.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index c602f24bab1bb..bcc2c79f7833c 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -140,6 +140,12 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
 		goto out_unlock;
 	}
 
+	kvm->arch.vgic.in_kernel = true;
+	kvm->arch.vgic.vgic_model = type;
+	kvm->arch.vgic.implementation_rev = KVM_VGIC_IMP_REV_LATEST;
+
+	kvm->arch.vgic.vgic_dist_base = VGIC_ADDR_UNDEF;
+
 	kvm_for_each_vcpu(i, vcpu, kvm) {
 		ret = vgic_allocate_private_irqs_locked(vcpu, type);
 		if (ret)
@@ -156,12 +162,6 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
 		goto out_unlock;
 	}
 
-	kvm->arch.vgic.in_kernel = true;
-	kvm->arch.vgic.vgic_model = type;
-	kvm->arch.vgic.implementation_rev = KVM_VGIC_IMP_REV_LATEST;
-
-	kvm->arch.vgic.vgic_dist_base = VGIC_ADDR_UNDEF;
-
 	aa64pfr0 = kvm_read_vm_id_reg(kvm, SYS_ID_AA64PFR0_EL1) & ~ID_AA64PFR0_EL1_GIC;
 	pfr1 = kvm_read_vm_id_reg(kvm, SYS_ID_PFR1_EL1) & ~ID_PFR1_EL1_GIC;
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 14/36] KVM: arm64: gic-v5: Add vgic-v5 save/restore hyp interface
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (12 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 13/36] KVM: arm64: gic: Set vgic_model before initing private IRQs Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2025-12-19 15:52 ` [PATCH v2 16/36] KVM: arm64: gic-v5: Implement direct injection of PPIs Sascha Bischoff
                   ` (22 subsequent siblings)
  36 siblings, 0 replies; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

Introduce hyp functions to save/restore the following GICv5 state:

* ICC_ICSR_EL1
* ICH_APR_EL2
* ICH_PPI_ACTIVERx_EL2
* ICH_PPI_DVIRx_EL2
* ICH_PPI_ENABLERx_EL2
* ICH_PPI_PENDRRx_EL2
* ICH_PPI_PRIORITYRx_EL2
* ICH_VMCR_EL2

All of these are saved/restored to/from the KVM vgic_v5 CPUIF shadow
state.

The ICSR must be save/restored as this register is shared between host
and guest. Therefore, to avoid leaking host state to the guest, this
must be saved and restored. Moreover, as this can by used by the host
at any time, it must be save/restored eagerly. Note: the host state is
not preserved as the host should only use this register when
preemption is disabled.

As part of restoring the ICH_VMCR_EL2 and ICH_APR_EL2, GICv3-compat
mode is also disabled by setting the ICH_VCTLR_EL2.V3 bit to 0. The
correspoinding GICv3-compat mode enable is part of the VMCR & APR
restore for a GICv3 guest as it only takes effect when actually
running a guest.

Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/include/asm/kvm_asm.h   |   4 +
 arch/arm64/include/asm/kvm_hyp.h   |   6 ++
 arch/arm64/kvm/hyp/nvhe/hyp-main.c |  32 ++++++++
 arch/arm64/kvm/hyp/vgic-v5-sr.c    | 119 +++++++++++++++++++++++++++++
 include/kvm/arm_vgic.h             |  33 ++++++++
 5 files changed, 194 insertions(+)

diff --git a/arch/arm64/include/asm/kvm_asm.h b/arch/arm64/include/asm/kvm_asm.h
index ada752e97c6aa..328be86d36d51 100644
--- a/arch/arm64/include/asm/kvm_asm.h
+++ b/arch/arm64/include/asm/kvm_asm.h
@@ -90,6 +90,10 @@ enum __kvm_host_smccc_func {
 	__KVM_HOST_SMCCC_FUNC___pkvm_vcpu_put,
 	__KVM_HOST_SMCCC_FUNC___pkvm_tlb_flush_vmid,
 	__KVM_HOST_SMCCC_FUNC___vgic_v5_detect_ppis,
+	__KVM_HOST_SMCCC_FUNC___vgic_v5_save_apr,
+	__KVM_HOST_SMCCC_FUNC___vgic_v5_restore_vmcr_apr,
+	__KVM_HOST_SMCCC_FUNC___vgic_v5_save_ppi_state,
+	__KVM_HOST_SMCCC_FUNC___vgic_v5_restore_ppi_state,
 };
 
 #define DECLARE_KVM_VHE_SYM(sym)	extern char sym[]
diff --git a/arch/arm64/include/asm/kvm_hyp.h b/arch/arm64/include/asm/kvm_hyp.h
index 80e5491de8e1e..c965f4e178cee 100644
--- a/arch/arm64/include/asm/kvm_hyp.h
+++ b/arch/arm64/include/asm/kvm_hyp.h
@@ -89,6 +89,12 @@ int __vgic_v3_perform_cpuif_access(struct kvm_vcpu *vcpu);
 
 /* GICv5 */
 void __vgic_v5_detect_ppis(u64 *ppi);
+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);
+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);
 
 #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 3d1d0807be6db..e640776ca83ba 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -596,6 +596,34 @@ static void handle___vgic_v5_detect_ppis(struct kvm_cpu_context *host_ctxt)
 	cpu_reg(host_ctxt, 2) = ppi[1];
 }
 
+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);
+
+	__vgic_v5_save_apr(kern_hyp_va(cpu_if));
+}
+
+static void handle___vgic_v5_restore_vmcr_apr(struct kvm_cpu_context *host_ctxt)
+{
+	DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
+
+	__vgic_v5_restore_vmcr_apr(kern_hyp_va(cpu_if));
+}
+
+static void handle___vgic_v5_save_ppi_state(struct kvm_cpu_context *host_ctxt)
+{
+	DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
+
+	__vgic_v5_save_ppi_state(kern_hyp_va(cpu_if));
+}
+
+static void handle___vgic_v5_restore_ppi_state(struct kvm_cpu_context *host_ctxt)
+{
+	DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
+
+	__vgic_v5_restore_ppi_state(kern_hyp_va(cpu_if));
+}
+
 typedef void (*hcall_t)(struct kvm_cpu_context *);
 
 #define HANDLE_FUNC(x)	[__KVM_HOST_SMCCC_FUNC_##x] = (hcall_t)handle_##x
@@ -638,6 +666,10 @@ static const hcall_t host_hcall[] = {
 	HANDLE_FUNC(__pkvm_vcpu_put),
 	HANDLE_FUNC(__pkvm_tlb_flush_vmid),
 	HANDLE_FUNC(__vgic_v5_detect_ppis),
+	HANDLE_FUNC(__vgic_v5_save_apr),
+	HANDLE_FUNC(__vgic_v5_restore_vmcr_apr),
+	HANDLE_FUNC(__vgic_v5_save_ppi_state),
+	HANDLE_FUNC(__vgic_v5_restore_ppi_state),
 };
 
 static void handle_host_hcall(struct kvm_cpu_context *host_ctxt)
diff --git a/arch/arm64/kvm/hyp/vgic-v5-sr.c b/arch/arm64/kvm/hyp/vgic-v5-sr.c
index 4b088588757ea..310e49a2e6f45 100644
--- a/arch/arm64/kvm/hyp/vgic-v5-sr.c
+++ b/arch/arm64/kvm/hyp/vgic-v5-sr.c
@@ -25,3 +25,122 @@ void __vgic_v5_detect_ppis(u64 *impl_ppi_mask)
 	write_sysreg_s(0, SYS_ICH_PPI_ENABLER0_EL2);
 	write_sysreg_s(0, SYS_ICH_PPI_ENABLER1_EL2);
 }
+
+void __vgic_v5_save_apr(struct vgic_v5_cpu_if *cpu_if)
+{
+	cpu_if->vgic_apr = read_sysreg_s(SYS_ICH_APR_EL2);
+}
+
+static void  __vgic_v5_compat_mode_disable(void)
+{
+	sysreg_clear_set_s(SYS_ICH_VCTLR_EL2, ICH_VCTLR_EL2_V3, 0);
+	isb();
+}
+
+void __vgic_v5_restore_vmcr_apr(struct vgic_v5_cpu_if *cpu_if)
+{
+	__vgic_v5_compat_mode_disable();
+
+	write_sysreg_s(cpu_if->vgic_vmcr, SYS_ICH_VMCR_EL2);
+	write_sysreg_s(cpu_if->vgic_apr, SYS_ICH_APR_EL2);
+}
+
+void __vgic_v5_save_ppi_state(struct vgic_v5_cpu_if *cpu_if)
+{
+	cpu_if->vgic_ppi_activer_exit[0] = read_sysreg_s(SYS_ICH_PPI_ACTIVER0_EL2);
+	cpu_if->vgic_ppi_activer_exit[1] = read_sysreg_s(SYS_ICH_PPI_ACTIVER1_EL2);
+
+	cpu_if->vgic_ich_ppi_enabler_exit[0] = read_sysreg_s(SYS_ICH_PPI_ENABLER0_EL2);
+	cpu_if->vgic_ich_ppi_enabler_exit[1] = read_sysreg_s(SYS_ICH_PPI_ENABLER1_EL2);
+
+	cpu_if->vgic_ppi_pendr_exit[0] = read_sysreg_s(SYS_ICH_PPI_PENDR0_EL2);
+	cpu_if->vgic_ppi_pendr_exit[1] = read_sysreg_s(SYS_ICH_PPI_PENDR1_EL2);
+
+	cpu_if->vgic_ppi_priorityr[0] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR0_EL2);
+	cpu_if->vgic_ppi_priorityr[1] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR1_EL2);
+	cpu_if->vgic_ppi_priorityr[2] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR2_EL2);
+	cpu_if->vgic_ppi_priorityr[3] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR3_EL2);
+	cpu_if->vgic_ppi_priorityr[4] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR4_EL2);
+	cpu_if->vgic_ppi_priorityr[5] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR5_EL2);
+	cpu_if->vgic_ppi_priorityr[6] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR6_EL2);
+	cpu_if->vgic_ppi_priorityr[7] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR7_EL2);
+	cpu_if->vgic_ppi_priorityr[8] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR8_EL2);
+	cpu_if->vgic_ppi_priorityr[9] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR9_EL2);
+	cpu_if->vgic_ppi_priorityr[10] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR10_EL2);
+	cpu_if->vgic_ppi_priorityr[11] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR11_EL2);
+	cpu_if->vgic_ppi_priorityr[12] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR12_EL2);
+	cpu_if->vgic_ppi_priorityr[13] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR13_EL2);
+	cpu_if->vgic_ppi_priorityr[14] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR14_EL2);
+	cpu_if->vgic_ppi_priorityr[15] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR15_EL2);
+
+	/* Now that we are done, disable DVI */
+	write_sysreg_s(0, SYS_ICH_PPI_DVIR0_EL2);
+	write_sysreg_s(0, SYS_ICH_PPI_DVIR1_EL2);
+}
+
+void __vgic_v5_restore_ppi_state(struct vgic_v5_cpu_if *cpu_if)
+{
+	 /* Now enable DVI so that the guest's interrupt config takes over */
+	 write_sysreg_s(cpu_if->vgic_ppi_dvir[0], SYS_ICH_PPI_DVIR0_EL2);
+	 write_sysreg_s(cpu_if->vgic_ppi_dvir[1], SYS_ICH_PPI_DVIR1_EL2);
+
+	 write_sysreg_s(cpu_if->vgic_ppi_activer_entry[0],
+			SYS_ICH_PPI_ACTIVER0_EL2);
+	 write_sysreg_s(cpu_if->vgic_ppi_activer_entry[1],
+			SYS_ICH_PPI_ACTIVER1_EL2);
+
+	 write_sysreg_s(cpu_if->vgic_ich_ppi_enabler_entry[0],
+			SYS_ICH_PPI_ENABLER0_EL2);
+	 write_sysreg_s(cpu_if->vgic_ich_ppi_enabler_entry[1],
+			SYS_ICH_PPI_ENABLER1_EL2);
+
+	 /* Update the pending state of the NON-DVI'd PPIs, only */
+	 write_sysreg_s(cpu_if->vgic_ppi_pendr_entry[0] & ~cpu_if->vgic_ppi_dvir[0],
+			SYS_ICH_PPI_PENDR0_EL2);
+	 write_sysreg_s(cpu_if->vgic_ppi_pendr_entry[1] & ~cpu_if->vgic_ppi_dvir[1],
+			SYS_ICH_PPI_PENDR1_EL2);
+
+	 write_sysreg_s(cpu_if->vgic_ppi_priorityr[0],
+			SYS_ICH_PPI_PRIORITYR0_EL2);
+	 write_sysreg_s(cpu_if->vgic_ppi_priorityr[1],
+			SYS_ICH_PPI_PRIORITYR1_EL2);
+	 write_sysreg_s(cpu_if->vgic_ppi_priorityr[2],
+			SYS_ICH_PPI_PRIORITYR2_EL2);
+	 write_sysreg_s(cpu_if->vgic_ppi_priorityr[3],
+			SYS_ICH_PPI_PRIORITYR3_EL2);
+	 write_sysreg_s(cpu_if->vgic_ppi_priorityr[4],
+			SYS_ICH_PPI_PRIORITYR4_EL2);
+	 write_sysreg_s(cpu_if->vgic_ppi_priorityr[5],
+			SYS_ICH_PPI_PRIORITYR5_EL2);
+	 write_sysreg_s(cpu_if->vgic_ppi_priorityr[6],
+			SYS_ICH_PPI_PRIORITYR6_EL2);
+	 write_sysreg_s(cpu_if->vgic_ppi_priorityr[7],
+			SYS_ICH_PPI_PRIORITYR7_EL2);
+	 write_sysreg_s(cpu_if->vgic_ppi_priorityr[8],
+			SYS_ICH_PPI_PRIORITYR8_EL2);
+	 write_sysreg_s(cpu_if->vgic_ppi_priorityr[9],
+			SYS_ICH_PPI_PRIORITYR9_EL2);
+	 write_sysreg_s(cpu_if->vgic_ppi_priorityr[10],
+			SYS_ICH_PPI_PRIORITYR10_EL2);
+	 write_sysreg_s(cpu_if->vgic_ppi_priorityr[11],
+			SYS_ICH_PPI_PRIORITYR11_EL2);
+	 write_sysreg_s(cpu_if->vgic_ppi_priorityr[12],
+			SYS_ICH_PPI_PRIORITYR12_EL2);
+	 write_sysreg_s(cpu_if->vgic_ppi_priorityr[13],
+			SYS_ICH_PPI_PRIORITYR13_EL2);
+	 write_sysreg_s(cpu_if->vgic_ppi_priorityr[14],
+			SYS_ICH_PPI_PRIORITYR14_EL2);
+	 write_sysreg_s(cpu_if->vgic_ppi_priorityr[15],
+			SYS_ICH_PPI_PRIORITYR15_EL2);
+}
+
+void __vgic_v5_save_state(struct vgic_v5_cpu_if *cpu_if)
+{
+	cpu_if->vgic_vmcr = read_sysreg_s(SYS_ICH_VMCR_EL2);
+	cpu_if->vgic_icsr = read_sysreg_s(SYS_ICC_ICSR_EL1);
+}
+
+void __vgic_v5_restore_state(struct vgic_v5_cpu_if *cpu_if)
+{
+	write_sysreg_s(cpu_if->vgic_icsr, SYS_ICC_ICSR_EL1);
+}
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index c7786a2607ecd..e3e3518b1f099 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -414,6 +414,38 @@ struct vgic_v3_cpu_if {
 	unsigned int used_lrs;
 };
 
+struct vgic_v5_cpu_if {
+	u64	vgic_apr;
+	u64	vgic_vmcr;
+
+	/* PPI register state */
+	u64	vgic_ppi_hmr[2];
+	u64	vgic_ppi_dvir[2];
+	u64	vgic_ppi_priorityr[16];
+
+	/* The pending state of the guest. This is merged with the exit state */
+	u64	vgic_ppi_pendr[2];
+
+	/* The state flushed to the regs when entering the guest */
+	u64	vgic_ppi_activer_entry[2];
+	u64	vgic_ich_ppi_enabler_entry[2];
+	u64	vgic_ppi_pendr_entry[2];
+
+	/* The saved state of the regs when leaving the guest */
+	u64	vgic_ppi_activer_exit[2];
+	u64	vgic_ich_ppi_enabler_exit[2];
+	u64	vgic_ppi_pendr_exit[2];
+
+	/*
+	 * The ICSR is re-used across host and guest, and hence it needs to be
+	 * saved/restored. Only one copy is required as the host should block
+	 * preemption between executing GIC CDRCFG and acccessing the
+	 * ICC_ICSR_EL1. A guest, of course, can never guarantee this, and hence
+	 * it is the hyp's responsibility to keep the state constistent.
+	 */
+	u64	vgic_icsr;
+};
+
 /* What PPI capabilities does a GICv5 host have */
 struct vgic_v5_ppi_caps {
 	u64	impl_ppi_mask[2];
@@ -424,6 +456,7 @@ struct vgic_cpu {
 	union {
 		struct vgic_v2_cpu_if	vgic_v2;
 		struct vgic_v3_cpu_if	vgic_v3;
+		struct vgic_v5_cpu_if	vgic_v5;
 	};
 
 	struct vgic_irq *private_irqs;
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 17/36] KVM: arm64: gic: Introduce irq_queue and set_pending_state to irq_ops
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (14 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 16/36] KVM: arm64: gic-v5: Implement direct injection of PPIs Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 12:22   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 15/36] KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore Sascha Bischoff
                   ` (20 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

There are times when the default behaviour of vgic_queue_irq_unlock is
undesirable. This is because some GICs, such a GICv5 which is the main
driver for this change, handle the majority of the interrupt lifecycle
in hardware. In this case, there is no need for a per-VCPU AP list as
the interrupt can be made pending directly. This is done either via
the ICH_PPI_x_EL2 registers for PPIs, or with the VDPEND system
instruction for SPIs and LPIs.

The queue_irq_unlock function is made overridable using a new function
pointer in struct irq_ops. In kvm_vgic_inject_irq,
vgic_queue_irq_unlock is overridden if the function pointer is
non-null.

Additionally, a new function is added via a function pointer -
set_pending_state. The intent is for this to be used to directly set
the pending state in hardware.

Both of these new irq_ops are unused in this change - it is purely
providing the infrastructure itself. The subsequent PPI injection
changes provide a demonstration of their usage.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/kvm/vgic/vgic.c | 11 +++++++++++
 include/kvm/arm_vgic.h     | 15 +++++++++++++++
 2 files changed, 26 insertions(+)

diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index d88570bb2f9f0..ac8cb0270e1e4 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -404,6 +404,13 @@ bool vgic_queue_irq_unlock(struct kvm *kvm, struct vgic_irq *irq,
 
 	lockdep_assert_held(&irq->irq_lock);
 
+	/*
+	 * If we have the queue_irq_unlock irq_op, we want to override
+	 * the default behaviour. Call that, and return early.
+	 */
+	if (irq->ops && irq->ops->queue_irq_unlock)
+		return irq->ops->queue_irq_unlock(kvm, irq, flags);
+
 retry:
 	vcpu = vgic_target_oracle(irq);
 	if (irq->vcpu || !vcpu) {
@@ -547,7 +554,11 @@ 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);
 
 	return 0;
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 17cd0295b135f..500709bd62c8d 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -171,6 +171,8 @@ enum vgic_irq_config {
 	VGIC_CONFIG_LEVEL
 };
 
+struct vgic_irq;
+
 /*
  * Per-irq ops overriding some common behavious.
  *
@@ -189,6 +191,19 @@ struct irq_ops {
 	 * peaking into the physical GIC.
 	 */
 	bool (*get_input_level)(int vintid);
+
+	/*
+	 * Function pointer to directly set the pending state for interrupts
+	 * that don't need to be enqueued on AP lists (for example, GICv5 PPIs).
+	 */
+	bool (*set_pending_state)(struct kvm_vcpu *vcpu, struct vgic_irq *irq);
+
+	/*
+	 * Function pointer to override the queuing of an IRQ.
+	 */
+	bool (*queue_irq_unlock)(struct kvm *kvm, struct vgic_irq *irq,
+				unsigned long flags) __releases(&irq->irq_lock);
+
 };
 
 struct vgic_irq {
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 16/36] KVM: arm64: gic-v5: Implement direct injection of PPIs
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (13 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 14/36] KVM: arm64: gic-v5: Add vgic-v5 save/restore hyp interface Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 12:16   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 17/36] KVM: arm64: gic: Introduce irq_queue and set_pending_state to irq_ops Sascha Bischoff
                   ` (21 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

GICv5 is able to directly inject PPI pending state into a guest using
a mechanism called DVI whereby the pending bit for a paticular PPI is
driven directly by the physically-connected hardware. This mechanism
itself doesn't allow for any ID translation, so the host interrupt is
directly mapped into a guest with the same interrupt ID.

When mapping a virtual interrupt to a physical interrupt via
kvm_vgic_map_irq for a GICv5 guest, check if the interrupt itself is a
PPI or not. If it is, and the host's interrupt ID matches that used
for the guest DVI is enabled, and the interrupt itself is marked as
directly_injected.

When the interrupt is unmapped again, this process is reversed, and
DVI is disabled for the interrupt again.

Note: the expectation is that a directly injected PPI is disabled on
the host while the guest state is loaded. The reason is that although
DVI is enabled to drive the guest's pending state directly, the host
pending state also remains driven. In order to avoid the same PPI
firing on both the host and the guest, the host's interrupt must be
disabled (masked). This is left up to the code that owns the device
generating the PPI as this needs to be handled on a per-VM basis. One
VM might use DVI, while another might not, in which case the physical
PPI should be enabled for the latter.

Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/kvm/vgic/vgic-v5.c | 22 ++++++++++++++++++++++
 arch/arm64/kvm/vgic/vgic.c    | 13 +++++++++++++
 arch/arm64/kvm/vgic/vgic.h    |  1 +
 include/kvm/arm_vgic.h        |  1 +
 4 files changed, 37 insertions(+)

diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 168447ee3fbed..46c70dfc7bb08 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -83,6 +83,28 @@ void vgic_v5_get_implemented_ppis(void)
 	}
 }
 
+/*
+ * Sets/clears the corresponding bit in the ICH_PPI_DVIR register.
+ */
+int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi)
+{
+	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+	u32 ppi = FIELD_GET(GICV5_HWIRQ_ID, irq);
+
+	if (ppi >= 128)
+		return -EINVAL;
+
+	if (dvi) {
+		/* Set the bit */
+		cpu_if->vgic_ppi_dvir[ppi / 64] |= 1UL << (ppi % 64);
+	} else {
+		/* Clear the bit */
+		cpu_if->vgic_ppi_dvir[ppi / 64] &= ~(1UL << (ppi % 64));
+	}
+
+	return 0;
+}
+
 void vgic_v5_load(struct kvm_vcpu *vcpu)
 {
 	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index 1005ff5f36235..d88570bb2f9f0 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -577,12 +577,25 @@ static int kvm_vgic_map_irq(struct kvm_vcpu *vcpu, struct vgic_irq *irq,
 	irq->host_irq = host_irq;
 	irq->hwintid = data->hwirq;
 	irq->ops = ops;
+
+	if (!vgic_is_v5(vcpu->kvm) ||
+	    !__irq_is_ppi(KVM_DEV_TYPE_ARM_VGIC_V5, irq->intid))
+		return 0;
+
+	if (FIELD_GET(GICV5_HWIRQ_ID, irq->intid) == irq->hwintid)
+		irq->directly_injected = !vgic_v5_set_ppi_dvi(vcpu, irq->hwintid,
+							      true);
+
 	return 0;
 }
 
 /* @irq->irq_lock must be held */
 static inline void kvm_vgic_unmap_irq(struct vgic_irq *irq)
 {
+	if (irq->directly_injected && vgic_is_v5(irq->target_vcpu->kvm))
+		WARN_ON(vgic_v5_set_ppi_dvi(irq->target_vcpu, irq->hwintid, false));
+
+	irq->directly_injected = false;
 	irq->hw = false;
 	irq->hwintid = 0;
 	irq->ops = NULL;
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 9905317c9d49d..d5d9264f0a1e9 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -364,6 +364,7 @@ void vgic_debug_destroy(struct kvm *kvm);
 
 int vgic_v5_probe(const struct gic_kvm_info *info);
 void vgic_v5_get_implemented_ppis(void);
+int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
 void vgic_v5_load(struct kvm_vcpu *vcpu);
 void vgic_v5_put(struct kvm_vcpu *vcpu);
 void vgic_v5_set_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcr);
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index b9d96a8ea53fd..17cd0295b135f 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -219,6 +219,7 @@ struct vgic_irq {
 	bool enabled:1;
 	bool active:1;
 	bool hw:1;			/* Tied to HW IRQ */
+	bool directly_injected:1;	/* A directly injected HW IRQ */
 	bool on_lr:1;			/* Present in a CPU LR */
 	refcount_t refcount;		/* Used for LPIs */
 	u32 hwintid;			/* HW INTID number */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 15/36] KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (15 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 17/36] KVM: arm64: gic: Introduce irq_queue and set_pending_state to irq_ops Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 12:28   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 19/36] KVM: arm64: gic-v5: Check for pending PPIs Sascha Bischoff
                   ` (19 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

This change introduces GICv5 load/put. Additionally, it plumbs in
save/restore for:

* PPIs (ICH_PPI_x_EL2 regs)
* ICH_VMCR_EL2
* ICH_APR_EL2
* ICC_ICSR_EL1

A GICv5-specific enable bit is added to struct vgic_vmcr as this
differs from previous GICs. On GICv5-native systems, the VMCR only
contains the enable bit (driven by the guest via ICC_CR0_EL1.EN) and
the priority mask (PCR).

A struct gicv5_vpe is also introduced. This currently only contains a
single field - bool resident - which is used to track if a VPE is
currently running or not, and is used to avoid a case of double load
or double put on the WFI path for a vCPU. This struct will be extended
as additional GICv5 support is merged, specifically for VPE doorbells.

Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/kvm/hyp/nvhe/switch.c   | 12 +++++
 arch/arm64/kvm/vgic/vgic-mmio.c    | 28 +++++++----
 arch/arm64/kvm/vgic/vgic-v5.c      | 77 ++++++++++++++++++++++++++++++
 arch/arm64/kvm/vgic/vgic.c         | 32 ++++++++-----
 arch/arm64/kvm/vgic/vgic.h         |  7 +++
 include/kvm/arm_vgic.h             |  2 +
 include/linux/irqchip/arm-gic-v5.h |  5 ++
 7 files changed, 144 insertions(+), 19 deletions(-)

diff --git a/arch/arm64/kvm/hyp/nvhe/switch.c b/arch/arm64/kvm/hyp/nvhe/switch.c
index c23e22ffac080..bc446a5d94d68 100644
--- a/arch/arm64/kvm/hyp/nvhe/switch.c
+++ b/arch/arm64/kvm/hyp/nvhe/switch.c
@@ -113,6 +113,12 @@ static void __deactivate_traps(struct kvm_vcpu *vcpu)
 /* Save VGICv3 state on non-VHE systems */
 static void __hyp_vgic_save_state(struct kvm_vcpu *vcpu)
 {
+	if (kern_hyp_va(vcpu->kvm)->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) {
+		__vgic_v5_save_state(&vcpu->arch.vgic_cpu.vgic_v5);
+		__vgic_v5_save_ppi_state(&vcpu->arch.vgic_cpu.vgic_v5);
+		return;
+	}
+
 	if (static_branch_unlikely(&kvm_vgic_global_state.gicv3_cpuif)) {
 		__vgic_v3_save_state(&vcpu->arch.vgic_cpu.vgic_v3);
 		__vgic_v3_deactivate_traps(&vcpu->arch.vgic_cpu.vgic_v3);
@@ -122,6 +128,12 @@ static void __hyp_vgic_save_state(struct kvm_vcpu *vcpu)
 /* Restore VGICv3 state on non-VHE systems */
 static void __hyp_vgic_restore_state(struct kvm_vcpu *vcpu)
 {
+	if (kern_hyp_va(vcpu->kvm)->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) {
+		__vgic_v5_restore_state(&vcpu->arch.vgic_cpu.vgic_v5);
+		__vgic_v5_restore_ppi_state(&vcpu->arch.vgic_cpu.vgic_v5);
+		return;
+	}
+
 	if (static_branch_unlikely(&kvm_vgic_global_state.gicv3_cpuif)) {
 		__vgic_v3_activate_traps(&vcpu->arch.vgic_cpu.vgic_v3);
 		__vgic_v3_restore_state(&vcpu->arch.vgic_cpu.vgic_v3);
diff --git a/arch/arm64/kvm/vgic/vgic-mmio.c b/arch/arm64/kvm/vgic/vgic-mmio.c
index a573b1f0c6cbe..675c2844f5e5c 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio.c
+++ b/arch/arm64/kvm/vgic/vgic-mmio.c
@@ -842,18 +842,30 @@ vgic_find_mmio_region(const struct vgic_register_region *regions,
 
 void vgic_set_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcr)
 {
-	if (kvm_vgic_global_state.type == VGIC_V2)
-		vgic_v2_set_vmcr(vcpu, vmcr);
-	else
-		vgic_v3_set_vmcr(vcpu, vmcr);
+	const struct vgic_dist *dist = &vcpu->kvm->arch.vgic;
+
+	if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) {
+		vgic_v5_set_vmcr(vcpu, vmcr);
+	} else {
+		if (kvm_vgic_global_state.type == VGIC_V2)
+			vgic_v2_set_vmcr(vcpu, vmcr);
+		else
+			vgic_v3_set_vmcr(vcpu, vmcr);
+	}
 }
 
 void vgic_get_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcr)
 {
-	if (kvm_vgic_global_state.type == VGIC_V2)
-		vgic_v2_get_vmcr(vcpu, vmcr);
-	else
-		vgic_v3_get_vmcr(vcpu, vmcr);
+	const struct vgic_dist *dist = &vcpu->kvm->arch.vgic;
+
+	if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) {
+		vgic_v5_get_vmcr(vcpu, vmcr);
+	} else {
+		if (kvm_vgic_global_state.type == VGIC_V2)
+			vgic_v2_get_vmcr(vcpu, vmcr);
+		else
+			vgic_v3_get_vmcr(vcpu, vmcr);
+	}
 }
 
 /*
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 1fe1790f1f874..168447ee3fbed 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -1,4 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Arm Ltd.
+ */
 
 #include <kvm/arm_vgic.h>
 #include <linux/irqchip/arm-vgic-info.h>
@@ -79,3 +82,77 @@ void vgic_v5_get_implemented_ppis(void)
 		__vgic_v5_detect_ppis(ppi_caps->impl_ppi_mask);
 	}
 }
+
+void vgic_v5_load(struct kvm_vcpu *vcpu)
+{
+	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+
+	/*
+	 * On the WFI path, vgic_load is called a second time. The first is when
+	 * scheduling in the vcpu thread again, and the second is when leaving
+	 * WFI. Skip the second instance as it serves no purpose and just
+	 * restores the same state again.
+	 */
+	if (READ_ONCE(cpu_if->gicv5_vpe.resident))
+		return;
+
+	kvm_call_hyp(__vgic_v5_restore_vmcr_apr, cpu_if);
+
+	WRITE_ONCE(cpu_if->gicv5_vpe.resident, true);
+}
+
+void vgic_v5_put(struct kvm_vcpu *vcpu)
+{
+	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+
+	/*
+	 * Do nothing if we're not resident. This can happen in the WFI path
+	 * where we do a vgic_put in the WFI path and again later when
+	 * descheduling the thread. We risk losing VMCR state if we sync it
+	 * twice, so instead return early in this case.
+	 */
+	if (!READ_ONCE(cpu_if->gicv5_vpe.resident))
+		return;
+
+	kvm_call_hyp(__vgic_v5_save_apr, cpu_if);
+
+	WRITE_ONCE(cpu_if->gicv5_vpe.resident, false);
+}
+
+void vgic_v5_get_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcrp)
+{
+	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+	u64 vmcr = cpu_if->vgic_vmcr;
+
+	vmcrp->en = FIELD_GET(FEAT_GCIE_ICH_VMCR_EL2_EN, vmcr);
+	vmcrp->pmr = FIELD_GET(FEAT_GCIE_ICH_VMCR_EL2_VPMR, vmcr);
+}
+
+void vgic_v5_set_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcrp)
+{
+	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+	u64 vmcr;
+
+	vmcr = FIELD_PREP(FEAT_GCIE_ICH_VMCR_EL2_VPMR, vmcrp->pmr) |
+	       FIELD_PREP(FEAT_GCIE_ICH_VMCR_EL2_EN, vmcrp->en);
+
+	cpu_if->vgic_vmcr = vmcr;
+}
+
+void vgic_v5_restore_state(struct kvm_vcpu *vcpu)
+{
+	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+
+	__vgic_v5_restore_state(cpu_if);
+	kvm_call_hyp(__vgic_v5_restore_ppi_state, cpu_if);
+	dsb(sy);
+}
+
+void vgic_v5_save_state(struct kvm_vcpu *vcpu)
+{
+	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+
+	__vgic_v5_save_state(cpu_if);
+	kvm_call_hyp(__vgic_v5_save_ppi_state, cpu_if);
+	dsb(sy);
+}
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index 2c0e8803342e2..1005ff5f36235 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -996,7 +996,9 @@ static inline bool can_access_vgic_from_kernel(void)
 
 static inline void vgic_save_state(struct kvm_vcpu *vcpu)
 {
-	if (!static_branch_unlikely(&kvm_vgic_global_state.gicv3_cpuif))
+	if (vgic_is_v5(vcpu->kvm))
+		vgic_v5_save_state(vcpu);
+	else if (!static_branch_unlikely(&kvm_vgic_global_state.gicv3_cpuif))
 		vgic_v2_save_state(vcpu);
 	else
 		__vgic_v3_save_state(&vcpu->arch.vgic_cpu.vgic_v3);
@@ -1005,14 +1007,16 @@ static inline void vgic_save_state(struct kvm_vcpu *vcpu)
 /* Sync back the hardware VGIC state into our emulation after a guest's run. */
 void kvm_vgic_sync_hwstate(struct kvm_vcpu *vcpu)
 {
-	/* If nesting, emulate the HW effect from L0 to L1 */
-	if (vgic_state_is_nested(vcpu)) {
-		vgic_v3_sync_nested(vcpu);
-		return;
-	}
+	if (!vgic_is_v5(vcpu->kvm)) {
+		/* If nesting, emulate the HW effect from L0 to L1 */
+		if (vgic_state_is_nested(vcpu)) {
+			vgic_v3_sync_nested(vcpu);
+			return;
+		}
 
-	if (vcpu_has_nv(vcpu))
-		vgic_v3_nested_update_mi(vcpu);
+		if (vcpu_has_nv(vcpu))
+			vgic_v3_nested_update_mi(vcpu);
+	}
 
 	if (can_access_vgic_from_kernel())
 		vgic_save_state(vcpu);
@@ -1034,7 +1038,9 @@ void kvm_vgic_process_async_update(struct kvm_vcpu *vcpu)
 
 static inline void vgic_restore_state(struct kvm_vcpu *vcpu)
 {
-	if (!static_branch_unlikely(&kvm_vgic_global_state.gicv3_cpuif))
+	if (vgic_is_v5(vcpu->kvm))
+		vgic_v5_restore_state(vcpu);
+	else if (!static_branch_unlikely(&kvm_vgic_global_state.gicv3_cpuif))
 		vgic_v2_restore_state(vcpu);
 	else
 		__vgic_v3_restore_state(&vcpu->arch.vgic_cpu.vgic_v3);
@@ -1094,7 +1100,9 @@ void kvm_vgic_load(struct kvm_vcpu *vcpu)
 		return;
 	}
 
-	if (!static_branch_unlikely(&kvm_vgic_global_state.gicv3_cpuif))
+	if (vgic_is_v5(vcpu->kvm))
+		vgic_v5_load(vcpu);
+	else if (!static_branch_unlikely(&kvm_vgic_global_state.gicv3_cpuif))
 		vgic_v2_load(vcpu);
 	else
 		vgic_v3_load(vcpu);
@@ -1108,7 +1116,9 @@ void kvm_vgic_put(struct kvm_vcpu *vcpu)
 		return;
 	}
 
-	if (!static_branch_unlikely(&kvm_vgic_global_state.gicv3_cpuif))
+	if (vgic_is_v5(vcpu->kvm))
+		vgic_v5_put(vcpu);
+	else if (!static_branch_unlikely(&kvm_vgic_global_state.gicv3_cpuif))
 		vgic_v2_put(vcpu);
 	else
 		vgic_v3_put(vcpu);
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index eb16184c14cc5..9905317c9d49d 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -187,6 +187,7 @@ static inline u64 vgic_ich_hcr_trap_bits(void)
  * registers regardless of the hardware backed GIC used.
  */
 struct vgic_vmcr {
+	u32	en; /* GICv5-specific */
 	u32	grpen0;
 	u32	grpen1;
 
@@ -363,6 +364,12 @@ void vgic_debug_destroy(struct kvm *kvm);
 
 int vgic_v5_probe(const struct gic_kvm_info *info);
 void vgic_v5_get_implemented_ppis(void);
+void vgic_v5_load(struct kvm_vcpu *vcpu);
+void vgic_v5_put(struct kvm_vcpu *vcpu);
+void vgic_v5_set_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcr);
+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);
 
 static inline int vgic_v3_max_apr_idx(struct kvm_vcpu *vcpu)
 {
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index e3e3518b1f099..b9d96a8ea53fd 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -444,6 +444,8 @@ struct vgic_v5_cpu_if {
 	 * it is the hyp's responsibility to keep the state constistent.
 	 */
 	u64	vgic_icsr;
+
+	struct gicv5_vpe gicv5_vpe;
 };
 
 /* What PPI capabilities does a GICv5 host have */
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index 68ddcdb1cec5a..d2780fc99c344 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -354,6 +354,11 @@ int gicv5_spi_irq_set_type(struct irq_data *d, unsigned int type);
 int gicv5_irs_iste_alloc(u32 lpi);
 void gicv5_irs_syncr(void);
 
+/* Embedded in kvm.arch */
+struct gicv5_vpe {
+	bool			resident;
+};
+
 struct gicv5_its_devtab_cfg {
 	union {
 		struct {
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 19/36] KVM: arm64: gic-v5: Check for pending PPIs
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (16 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 15/36] KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 15:00   ` Jonathan Cameron
  2026-01-08 16:10   ` Joey Gouly
  2025-12-19 15:52 ` [PATCH v2 20/36] KVM: arm64: gic-v5: Init Private IRQs (PPIs) for GICv5 Sascha Bischoff
                   ` (18 subsequent siblings)
  36 siblings, 2 replies; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

This change allows KVM to check for pending PPI interrupts. This has
two main components:

First of all, the effective priority mask is calculated.  This is a
combination of the priority mask in the VPEs ICC_PCR_EL1.PRIORITY and
the currently running priority as determined from the VPE's
ICH_APR_EL1. If an interrupt's prioirity is greater than or equal to
the effective priority mask, it can be signalled. Otherwise, it
cannot.

Secondly, any Enabled and Pending PPIs must be checked against this
compound priority mask. The reqires the PPI priorities to by synced
back to the KVM shadow state - this is skipped in general operation as
it isn't required and is rather expensive. If any Enabled and Pending
PPIs are of sufficient priority to be signalled, then there are
pending PPIs. Else, there are not.  This ensures that a VPE is not
woken when it cannot actually process the pending interrupts.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/kvm/vgic/vgic-v5.c | 121 ++++++++++++++++++++++++++++++++++
 arch/arm64/kvm/vgic/vgic.c    |   5 +-
 arch/arm64/kvm/vgic/vgic.h    |   1 +
 3 files changed, 126 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index cb3dd872d16a6..c7ecc4f40b1e5 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -56,6 +56,31 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
 	return 0;
 }
 
+static u32 vgic_v5_get_effective_priority_mask(struct kvm_vcpu *vcpu)
+{
+	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+	u32 highest_ap, priority_mask;
+
+	/*
+	 * Counting the number of trailing zeros gives the current
+	 * active priority. Explicitly use the 32-bit version here as
+	 * we have 32 priorities. 0x20 then means that there are no
+	 * active priorities.
+	 */
+	highest_ap = cpu_if->vgic_apr ? __builtin_ctz(cpu_if->vgic_apr) : 32;
+
+	/*
+	 * An interrupt is of sufficient priority if it is equal to or
+	 * greater than the priority mask. Add 1 to the priority mask
+	 * (i.e., lower priority) to match the APR logic before taking
+	 * the min. This gives us the lowest priority that is masked.
+	 */
+	priority_mask = FIELD_GET(FEAT_GCIE_ICH_VMCR_EL2_VPMR, cpu_if->vgic_vmcr);
+	priority_mask = min(highest_ap, priority_mask + 1);
+
+	return priority_mask;
+}
+
 static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
 					  struct vgic_irq *irq)
 {
@@ -131,6 +156,102 @@ void vgic_v5_set_ppi_ops(struct vgic_irq *irq)
 	}
 }
 
+
+/*
+ * Sync back the PPI priorities to the vgic_irq shadow state
+ */
+static void vgic_v5_sync_ppi_priorities(struct kvm_vcpu *vcpu)
+{
+	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+	int i, reg;
+
+	/* We have 16 PPI Priority regs */
+	for (reg = 0; reg < 16; reg++) {
+		const unsigned long priorityr = cpu_if->vgic_ppi_priorityr[reg];
+
+		for (i = 0; i < 8; ++i) {
+			struct vgic_irq *irq;
+			u32 intid;
+			u8 priority;
+
+			priority = (priorityr >> (i * 8)) & 0x1f;
+
+			intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
+			intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg * 8 + i);
+
+			irq = vgic_get_vcpu_irq(vcpu, intid);
+
+			scoped_guard(raw_spinlock, &irq->irq_lock)
+				irq->priority = priority;
+
+			vgic_put_irq(vcpu->kvm, irq);
+		}
+	}
+}
+
+bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu)
+{
+	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+	int i, reg;
+	unsigned int priority_mask;
+
+	/* If no pending bits are set, exit early */
+	if (likely(!cpu_if->vgic_ppi_pendr[0] && !cpu_if->vgic_ppi_pendr[1]))
+		return false;
+
+	priority_mask = vgic_v5_get_effective_priority_mask(vcpu);
+
+	/* If the combined priority mask is 0, nothing can be signalled! */
+	if (!priority_mask)
+		return false;
+
+	/* The shadow priority is only updated on demand, sync it across first */
+	vgic_v5_sync_ppi_priorities(vcpu);
+
+	for (reg = 0; reg < 2; reg++) {
+		unsigned long possible_bits;
+		const unsigned long enabler = cpu_if->vgic_ich_ppi_enabler_exit[reg];
+		const unsigned long pendr = cpu_if->vgic_ppi_pendr_exit[reg];
+		bool has_pending = false;
+
+		/* Check all interrupts that are enabled and pending */
+		possible_bits = enabler & pendr;
+
+		/*
+		 * Optimisation: pending and enabled with no active priorities
+		 */
+		if (possible_bits && priority_mask > 0x1f)
+			return true;
+
+		for_each_set_bit(i, &possible_bits, 64) {
+			struct vgic_irq *irq;
+			u32 intid;
+
+			intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
+			intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg * 64 + i);
+
+			irq = vgic_get_vcpu_irq(vcpu, intid);
+
+			scoped_guard(raw_spinlock, &irq->irq_lock) {
+				/*
+				 * We know that the interrupt is
+				 * enabled and pending, so only check
+				 * the priority.
+				 */
+				if (irq->priority <= priority_mask)
+					has_pending = true;
+			}
+
+			vgic_put_irq(vcpu->kvm, irq);
+
+			if (has_pending)
+				return true;
+		}
+	}
+
+	return false;
+}
+
 /*
  * Detect any PPIs state changes, and propagate the state with KVM's
  * shadow structures.
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index cb5d43b34462b..dfec6ed7936ed 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -1180,9 +1180,12 @@ int kvm_vgic_vcpu_pending_irq(struct kvm_vcpu *vcpu)
 	unsigned long flags;
 	struct vgic_vmcr vmcr;
 
-	if (!vcpu->kvm->arch.vgic.enabled)
+	if (!vcpu->kvm->arch.vgic.enabled && !vgic_is_v5(vcpu->kvm))
 		return false;
 
+	if (vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
+		return vgic_v5_has_pending_ppi(vcpu);
+
 	if (vcpu->arch.vgic_cpu.vgic_v3.its_vpe.pending_last)
 		return true;
 
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 978d7f8426361..65c031da83e78 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -388,6 +388,7 @@ int vgic_v5_probe(const struct gic_kvm_info *info);
 void vgic_v5_get_implemented_ppis(void);
 void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
 int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
+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);
 void vgic_v5_load(struct kvm_vcpu *vcpu);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 18/36] KVM: arm64: gic-v5: Implement PPI interrupt injection
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (18 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 20/36] KVM: arm64: gic-v5: Init Private IRQs (PPIs) for GICv5 Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-06 16:06   ` Joey Gouly
  2026-01-07 12:50   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 21/36] KVM: arm64: gic-v5: Finalize GICv5 PPIs and generate mask Sascha Bischoff
                   ` (16 subsequent siblings)
  36 siblings, 2 replies; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

This change introduces interrupt injection for PPIs for GICv5-based
guests.

The lifecycle of PPIs is largely managed by the hardware for a GICv5
system. The hypervisor injects pending state into the guest by using
the ICH_PPI_PENDRx_EL2 registers. These are used by the hardware to
pick a Highest Priority Pending Interrupt (HPPI) for the guest based
on the enable state of each individual interrupt. The enable state and
priority for each interrupt are provided by the guest itself (through
writes to the PPI registers).

When Direct Virtual Interrupt (DVI) is set for a particular PPI, the
hypervisor is even able to skip the injection of the pending state
altogether - it all happens in hardware.

The result of the above is that no AP lists are required for GICv5,
unlike for older GICs. Instead, for PPIs the ICH_PPI_* registers
fulfil the same purpose for all 128 PPIs. Hence, as long as the
ICH_PPI_* registers are populated prior to guest entry, and merged
back into the KVM shadow state on exit, the PPI state is preserved,
and interrupts can be injected.

When injecting the state of a PPI the state is merged into the KVM's
shadow state using the set_pending_state irq_op. The directly sets the
relevant bit in the shadow ICH_PPI_PENDRx_EL2, which is presented to
the guest (and GICv5 hardware) on next guest entry. The
queue_irq_unlock irq_op is required to kick the vCPU to ensure that it
seems the new state. The result is that no AP lists are used for
private interrupts on GICv5.

Prior to entering the guest, vgic_v5_flush_ppi_state is called from
kvm_vgic_flush_hwstate. The effectively snapshots the shadow PPI
pending state (twice - an entry and an exit copy) in order to track
any changes. These changes can come from a guest consuming an
interrupt or from a guest making an Edge-triggered interrupt pending.

When returning from running a guest, the guest's PPI state is merged
back into KVM's shadow state in vgic_v5_merge_ppi_state from
kvm_vgic_sync_hwstate. The Enable and Active state is synced back for
all PPIs, and the pending state is synced back for Edge PPIs (Level is
driven directly by the devices generating said levels). The incoming
pending state from the guest is merged with KVM's shadow state to
avoid losing any incoming interrupts.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/kvm/vgic/vgic-v5.c | 159 ++++++++++++++++++++++++++++++++++
 arch/arm64/kvm/vgic/vgic.c    |  46 +++++++---
 arch/arm64/kvm/vgic/vgic.h    |  47 ++++++++--
 include/kvm/arm_vgic.h        |   3 +
 4 files changed, 235 insertions(+), 20 deletions(-)

diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 46c70dfc7bb08..cb3dd872d16a6 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -56,6 +56,165 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
 	return 0;
 }
 
+static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
+					  struct vgic_irq *irq)
+{
+	struct vgic_v5_cpu_if *cpu_if;
+	const u64 id_bit = BIT_ULL(irq->intid % 64);
+	const u32 reg = FIELD_GET(GICV5_HWIRQ_ID, irq->intid) / 64;
+
+	if (!vcpu || !irq)
+		return false;
+
+	/* Skip injecting the state altogether */
+	if (irq->directly_injected)
+		return true;
+
+	cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+
+	if (irq_is_pending(irq))
+		cpu_if->vgic_ppi_pendr[reg] |= id_bit;
+	else
+		cpu_if->vgic_ppi_pendr[reg] &= ~id_bit;
+
+	return true;
+}
+
+/*
+ * For GICv5, the PPIs are mostly directly managed by the hardware. We
+ * (the hypervisor) handle the pending, active, enable state
+ * save/restore, but don't need the PPIs to be queued on a per-VCPU AP
+ * list. Therefore, sanity check the state, unlock, and return.
+ */
+static bool vgic_v5_ppi_queue_irq_unlock(struct kvm *kvm, struct vgic_irq *irq,
+					 unsigned long flags)
+	__releases(&irq->irq_lock)
+{
+	struct kvm_vcpu *vcpu;
+
+	lockdep_assert_held(&irq->irq_lock);
+
+	if (WARN_ON_ONCE(!__irq_is_ppi(KVM_DEV_TYPE_ARM_VGIC_V5, irq->intid)))
+		goto out_unlock_fail;
+
+	vcpu = irq->target_vcpu;
+	if (WARN_ON_ONCE(!vcpu))
+		goto out_unlock_fail;
+
+	raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
+
+	/* Directly kick the target VCPU to make sure it sees the IRQ */
+	kvm_make_request(KVM_REQ_IRQ_PENDING, vcpu);
+	kvm_vcpu_kick(vcpu);
+
+	return true;
+
+out_unlock_fail:
+	raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
+
+	return false;
+}
+
+static struct irq_ops vgic_v5_ppi_irq_ops = {
+	.set_pending_state = vgic_v5_ppi_set_pending_state,
+	.queue_irq_unlock = vgic_v5_ppi_queue_irq_unlock,
+};
+
+void vgic_v5_set_ppi_ops(struct vgic_irq *irq)
+{
+	if (WARN_ON(!irq))
+		return;
+
+	scoped_guard(raw_spinlock, &irq->irq_lock) {
+		if (!WARN_ON(irq->ops))
+			irq->ops = &vgic_v5_ppi_irq_ops;
+	}
+}
+
+/*
+ * Detect any PPIs state changes, and propagate the state with KVM's
+ * shadow structures.
+ */
+void vgic_v5_fold_ppi_state(struct kvm_vcpu *vcpu)
+{
+	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+	int i, reg;
+
+	for (reg = 0; reg < 2; reg++) {
+		unsigned long changed_bits;
+		const unsigned long enabler = cpu_if->vgic_ich_ppi_enabler_exit[reg];
+		const unsigned long activer = cpu_if->vgic_ppi_activer_exit[reg];
+		const unsigned long pendr = cpu_if->vgic_ppi_pendr_exit[reg];
+
+		/*
+		 * Track what changed across enabler, activer, pendr, but mask
+		 * with ~DVI.
+		 */
+		changed_bits = cpu_if->vgic_ich_ppi_enabler_entry[reg] ^ enabler;
+		changed_bits |= cpu_if->vgic_ppi_activer_entry[reg] ^ activer;
+		changed_bits |= cpu_if->vgic_ppi_pendr_entry[reg] ^ pendr;
+		changed_bits &= ~cpu_if->vgic_ppi_dvir[reg];
+
+		for_each_set_bit(i, &changed_bits, 64) {
+			struct vgic_irq *irq;
+			u32 intid;
+
+			intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
+			intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg * 64 + i);
+
+			irq = vgic_get_vcpu_irq(vcpu, intid);
+
+			scoped_guard(raw_spinlock, &irq->irq_lock) {
+				irq->enabled = !!(enabler & BIT(i));
+				irq->active = !!(activer & BIT(i));
+
+				/* This is an OR to avoid losing incoming edges! */
+				if (irq->config == VGIC_CONFIG_EDGE)
+					irq->pending_latch |= !!(pendr & BIT(i));
+			}
+
+			vgic_put_irq(vcpu->kvm, irq);
+		}
+
+		/* Re-inject the exit state as entry state next time! */
+		cpu_if->vgic_ich_ppi_enabler_entry[reg] = enabler;
+		cpu_if->vgic_ppi_activer_entry[reg] = activer;
+
+		/*
+		 * Pending state is a bit different. We only propagate back
+		 * pending state for Edge interrupts. Moreover, this is OR'd
+		 * with the incoming state to make sure we don't lose incoming
+		 * edges. Use the (inverse) HMR to mask off all Level bits, and
+		 * OR.
+		 */
+		cpu_if->vgic_ppi_pendr[reg] |= pendr & ~cpu_if->vgic_ppi_hmr[reg];
+	}
+}
+
+void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu)
+{
+	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+
+	/*
+	 * We're about to enter the guest. Copy the shadow state to the pending
+	 * reg that will be written to the ICH_PPI_PENDRx_EL2 regs. While the
+	 * guest is running we track any incoming changes to the pending state in
+	 * vgic_ppi_pendr. The incoming changes are merged with the outgoing
+	 * changes on the return path.
+	 */
+	cpu_if->vgic_ppi_pendr_entry[0] = cpu_if->vgic_ppi_pendr[0];
+	cpu_if->vgic_ppi_pendr_entry[1] = cpu_if->vgic_ppi_pendr[1];
+
+	/*
+	 * Make sure that we can correctly detect "edges" in the PPI
+	 * state. There's a path where we never actually enter the guest, and
+	 * failure to do this risks losing pending state
+	 */
+	cpu_if->vgic_ppi_pendr_exit[0] = cpu_if->vgic_ppi_pendr[0];
+	cpu_if->vgic_ppi_pendr_exit[1] = cpu_if->vgic_ppi_pendr[1];
+
+}
+
 /*
  * Not all PPIs are guaranteed to be implemented for
  * GICv5. Deterermine which ones are, and generate a mask. This is
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index ac8cb0270e1e4..cb5d43b34462b 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -105,6 +105,14 @@ struct vgic_irq *vgic_get_vcpu_irq(struct kvm_vcpu *vcpu, u32 intid)
 	if (WARN_ON(!vcpu))
 		return NULL;
 
+	if (vgic_is_v5(vcpu->kvm) &&
+	    __irq_is_ppi(KVM_DEV_TYPE_ARM_VGIC_V5, intid)) {
+		u32 int_num = FIELD_GET(GICV5_HWIRQ_ID, intid);
+
+		int_num = array_index_nospec(int_num, VGIC_V5_NR_PRIVATE_IRQS);
+		return &vcpu->arch.vgic_cpu.private_irqs[int_num];
+	}
+
 	/* SGIs and PPIs */
 	if (intid < VGIC_NR_PRIVATE_IRQS) {
 		intid = array_index_nospec(intid, VGIC_NR_PRIVATE_IRQS);
@@ -258,10 +266,12 @@ struct kvm_vcpu *vgic_target_oracle(struct vgic_irq *irq)
 	 * If the distributor is disabled, pending interrupts shouldn't be
 	 * forwarded.
 	 */
-	if (irq->enabled && irq_is_pending(irq)) {
-		if (unlikely(irq->target_vcpu &&
-			     !irq->target_vcpu->kvm->arch.vgic.enabled))
-			return NULL;
+	if (irq_is_enabled(irq) && irq_is_pending(irq)) {
+		if (irq->target_vcpu) {
+			if (!vgic_is_v5(irq->target_vcpu->kvm) &&
+			    unlikely(!irq->target_vcpu->kvm->arch.vgic.enabled))
+				return NULL;
+		}
 
 		return irq->target_vcpu;
 	}
@@ -836,9 +846,11 @@ static void vgic_prune_ap_list(struct kvm_vcpu *vcpu)
 		vgic_release_deleted_lpis(vcpu->kvm);
 }
 
-static inline void vgic_fold_lr_state(struct kvm_vcpu *vcpu)
+static void vgic_fold_state(struct kvm_vcpu *vcpu)
 {
-	if (kvm_vgic_global_state.type == VGIC_V2)
+	if (vgic_is_v5(vcpu->kvm))
+		vgic_v5_fold_ppi_state(vcpu);
+	else if (kvm_vgic_global_state.type == VGIC_V2)
 		vgic_v2_fold_lr_state(vcpu);
 	else
 		vgic_v3_fold_lr_state(vcpu);
@@ -1045,8 +1057,10 @@ void kvm_vgic_sync_hwstate(struct kvm_vcpu *vcpu)
 	if (can_access_vgic_from_kernel())
 		vgic_save_state(vcpu);
 
-	vgic_fold_lr_state(vcpu);
-	vgic_prune_ap_list(vcpu);
+	vgic_fold_state(vcpu);
+
+	if (!vgic_is_v5(vcpu->kvm))
+		vgic_prune_ap_list(vcpu);
 }
 
 /* Sync interrupts that were deactivated through a DIR trap */
@@ -1070,6 +1084,17 @@ static inline void vgic_restore_state(struct kvm_vcpu *vcpu)
 		__vgic_v3_restore_state(&vcpu->arch.vgic_cpu.vgic_v3);
 }
 
+static void vgic_flush_state(struct kvm_vcpu *vcpu)
+{
+	if (vgic_is_v5(vcpu->kvm)) {
+		vgic_v5_flush_ppi_state(vcpu);
+		return;
+	}
+
+	scoped_guard(raw_spinlock, &vcpu->arch.vgic_cpu.ap_list_lock)
+		vgic_flush_lr_state(vcpu);
+}
+
 /* Flush our emulation state into the GIC hardware before entering the guest. */
 void kvm_vgic_flush_hwstate(struct kvm_vcpu *vcpu)
 {
@@ -1106,13 +1131,12 @@ void kvm_vgic_flush_hwstate(struct kvm_vcpu *vcpu)
 
 	DEBUG_SPINLOCK_BUG_ON(!irqs_disabled());
 
-	scoped_guard(raw_spinlock, &vcpu->arch.vgic_cpu.ap_list_lock)
-		vgic_flush_lr_state(vcpu);
+	vgic_flush_state(vcpu);
 
 	if (can_access_vgic_from_kernel())
 		vgic_restore_state(vcpu);
 
-	if (vgic_supports_direct_irqs(vcpu->kvm))
+	if (vgic_supports_direct_irqs(vcpu->kvm) && !vgic_is_v5(vcpu->kvm))
 		vgic_v4_commit(vcpu);
 }
 
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index d5d9264f0a1e9..978d7f8426361 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -132,6 +132,28 @@ static inline bool irq_is_pending(struct vgic_irq *irq)
 		return irq->pending_latch || irq->line_level;
 }
 
+/* Requires the irq_lock to be held by the caller. */
+static inline bool irq_is_enabled(struct vgic_irq *irq)
+{
+	if (irq->enabled)
+		return true;
+
+	/*
+	 * We always consider GICv5 interrupts as enabled as we can
+	 * always inject them. The state is handled by the hardware,
+	 * and the hardware will only signal the interrupt to the
+	 * guest once the guest enables it.
+	 */
+	if (irq->target_vcpu) {
+		u32 vgic_model = irq->target_vcpu->kvm->arch.vgic.vgic_model;
+
+		if (vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
+			return true;
+	}
+
+	return false;
+}
+
 static inline bool vgic_irq_is_mapped_level(struct vgic_irq *irq)
 {
 	return irq->config == VGIC_CONFIG_LEVEL && irq->hw;
@@ -364,7 +386,10 @@ void vgic_debug_destroy(struct kvm *kvm);
 
 int vgic_v5_probe(const struct gic_kvm_info *info);
 void vgic_v5_get_implemented_ppis(void);
+void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
 int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
+void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu);
+void vgic_v5_fold_ppi_state(struct kvm_vcpu *vcpu);
 void vgic_v5_load(struct kvm_vcpu *vcpu);
 void vgic_v5_put(struct kvm_vcpu *vcpu);
 void vgic_v5_set_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcr);
@@ -433,15 +458,6 @@ void vgic_its_invalidate_all_caches(struct kvm *kvm);
 int vgic_its_inv_lpi(struct kvm *kvm, struct vgic_irq *irq);
 int vgic_its_invall(struct kvm_vcpu *vcpu);
 
-bool system_supports_direct_sgis(void);
-bool vgic_supports_direct_msis(struct kvm *kvm);
-bool vgic_supports_direct_sgis(struct kvm *kvm);
-
-static inline bool vgic_supports_direct_irqs(struct kvm *kvm)
-{
-	return vgic_supports_direct_msis(kvm) || vgic_supports_direct_sgis(kvm);
-}
-
 int vgic_v4_init(struct kvm *kvm);
 void vgic_v4_teardown(struct kvm *kvm);
 void vgic_v4_configure_vsgis(struct kvm *kvm);
@@ -481,6 +497,19 @@ static inline bool vgic_is_v3(struct kvm *kvm)
 	return kvm_vgic_global_state.type == VGIC_V3 || vgic_is_v3_compat(kvm);
 }
 
+bool system_supports_direct_sgis(void);
+bool vgic_supports_direct_msis(struct kvm *kvm);
+bool vgic_supports_direct_sgis(struct kvm *kvm);
+
+static inline bool vgic_supports_direct_irqs(struct kvm *kvm)
+{
+	/* GICv5 always supports direct IRQs */
+	if (vgic_is_v5(kvm))
+		return true;
+
+	return vgic_supports_direct_msis(kvm) || vgic_supports_direct_sgis(kvm);
+}
+
 int vgic_its_debug_init(struct kvm_device *dev);
 void vgic_its_debug_destroy(struct kvm_device *dev);
 
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 500709bd62c8d..b5180edbd1165 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -32,6 +32,9 @@
 #define VGIC_MIN_LPI		8192
 #define KVM_IRQCHIP_NUM_PINS	(1020 - 32)
 
+/* GICv5 constants */
+#define VGIC_V5_NR_PRIVATE_IRQS	128
+
 #define is_v5_type(t, i)	(FIELD_GET(GICV5_HWIRQ_TYPE, (i)) == (t))
 
 #define __irq_is_sgi(t, i)						\
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 20/36] KVM: arm64: gic-v5: Init Private IRQs (PPIs) for GICv5
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (17 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 19/36] KVM: arm64: gic-v5: Check for pending PPIs Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 15:04   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 18/36] KVM: arm64: gic-v5: Implement PPI interrupt injection Sascha Bischoff
                   ` (17 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

Initialise the private interrupts (PPIs, only) for GICv5. This means
that a GICv5-style intid is generated (which encodes the PPI type in
the top bits) instead of the 0-based index that is used for older
GICs.

Additionally, set all of the GICv5 PPIs to use Level for the handling
mode, with the exception of the SW_PPI which uses Edge. This matches
the architecturally-defined set in the GICv5 specification (the CTIIRQ
handling mode is IMPDEF, so pick Level has been picked for that).

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/kvm/vgic/vgic-init.c    | 41 +++++++++++++++++++++++-------
 include/linux/irqchip/arm-gic-v5.h |  2 ++
 2 files changed, 34 insertions(+), 9 deletions(-)

diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index bcc2c79f7833c..03f45816464b0 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -263,13 +263,19 @@ static int vgic_allocate_private_irqs_locked(struct kvm_vcpu *vcpu, u32 type)
 {
 	struct vgic_cpu *vgic_cpu = &vcpu->arch.vgic_cpu;
 	int i;
+	u32 num_private_irqs;
+
+	if (vgic_is_v5(vcpu->kvm))
+		num_private_irqs = VGIC_V5_NR_PRIVATE_IRQS;
+	else
+		num_private_irqs = VGIC_NR_PRIVATE_IRQS;
 
 	lockdep_assert_held(&vcpu->kvm->arch.config_lock);
 
 	if (vgic_cpu->private_irqs)
 		return 0;
 
-	vgic_cpu->private_irqs = kcalloc(VGIC_NR_PRIVATE_IRQS,
+	vgic_cpu->private_irqs = kcalloc(num_private_irqs,
 					 sizeof(struct vgic_irq),
 					 GFP_KERNEL_ACCOUNT);
 
@@ -280,22 +286,39 @@ static int vgic_allocate_private_irqs_locked(struct kvm_vcpu *vcpu, u32 type)
 	 * Enable and configure all SGIs to be edge-triggered and
 	 * configure all PPIs as level-triggered.
 	 */
-	for (i = 0; i < VGIC_NR_PRIVATE_IRQS; i++) {
+	for (i = 0; i < num_private_irqs; i++) {
 		struct vgic_irq *irq = &vgic_cpu->private_irqs[i];
 
 		INIT_LIST_HEAD(&irq->ap_list);
 		raw_spin_lock_init(&irq->irq_lock);
-		irq->intid = i;
 		irq->vcpu = NULL;
 		irq->target_vcpu = vcpu;
 		refcount_set(&irq->refcount, 0);
-		if (vgic_irq_is_sgi(i)) {
-			/* SGIs */
-			irq->enabled = 1;
-			irq->config = VGIC_CONFIG_EDGE;
+		if (!vgic_is_v5(vcpu->kvm)) {
+			irq->intid = i;
+			if (vgic_irq_is_sgi(i)) {
+				/* SGIs */
+				irq->enabled = 1;
+				irq->config = VGIC_CONFIG_EDGE;
+			} else {
+				/* PPIs */
+				irq->config = VGIC_CONFIG_LEVEL;
+			}
 		} else {
-			/* PPIs */
-			irq->config = VGIC_CONFIG_LEVEL;
+			irq->intid = i | FIELD_PREP(GICV5_HWIRQ_TYPE,
+						    GICV5_HWIRQ_TYPE_PPI);
+
+			/*
+			 * The only architected PPI that is Edge is
+			 * the SW PPI.
+			 */
+			if (irq->intid == GICV5_SW_PPI)
+				irq->config = VGIC_CONFIG_EDGE;
+			else
+				irq->config = VGIC_CONFIG_LEVEL;
+
+			/* Register the GICv5-specific PPI ops */
+			vgic_v5_set_ppi_ops(irq);
 		}
 
 		switch (type) {
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index d2780fc99c344..2ab7ec718aaea 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -13,6 +13,8 @@
 
 #define GICV5_IPIS_PER_CPU		MAX_IPI
 
+#define GICV5_SW_PPI			0x20000003
+
 /*
  * INTID handling
  */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 23/36] KVM: arm64: gic-v5: Support GICv5 interrupts with KVM_IRQ_LINE
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (20 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 21/36] KVM: arm64: gic-v5: Finalize GICv5 PPIs and generate mask Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 15:29   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 22/36] KVM: arm64: gic-v5: Trap and mask guest PPI register accesses Sascha Bischoff
                   ` (14 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

Interrupts under GICv5 look quite different to those from older Arm
GICs. Specifically, the type is encoded in the top bits of the
interrupt ID.

Extend KVM_IRQ_LINE to cope with GICv5 PPIs and SPIs. The requires
subtly changing the KVM_IRQ_LINE API for GICv5 guests. For older Arm
GICs, PPIs had to be in the range of 16-31, and SPIs had to be
32-1019, but this no longer holds true for GICv5. Instead, for a GICv5
guest support PPIs in the range of 0-127, and SPIs in the range
0-65535. The documentation is updated accordingly.

The SPI range doesn't cover the full SPI range that a GICv5 system can
potentially cope with (GICv5 provides up to 24-bits of SPI ID space,
and we only have 16 bits to work with in KVM_IRQ_LINE). However, 65k
SPIs is more than would be reasonably expected on systems for years to
come.

Note: As the GICv5 KVM implementation currently doesn't support
injecting SPIs attempts to do so will fail. This restruction will
lifted as the GICv5 KVM support evolves.

Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 Documentation/virt/kvm/api.rst |  6 ++++--
 arch/arm64/kvm/arm.c           | 21 ++++++++++++++++++---
 arch/arm64/kvm/vgic/vgic.c     |  4 ++++
 3 files changed, 26 insertions(+), 5 deletions(-)

diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index 01a3abef8abb9..460a5511ebcec 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -907,10 +907,12 @@ The irq_type field has the following values:
 - KVM_ARM_IRQ_TYPE_CPU:
 	       out-of-kernel GIC: irq_id 0 is IRQ, irq_id 1 is FIQ
 - KVM_ARM_IRQ_TYPE_SPI:
-	       in-kernel GIC: SPI, irq_id between 32 and 1019 (incl.)
+	       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.)
 - KVM_ARM_IRQ_TYPE_PPI:
-	       in-kernel GIC: PPI, irq_id between 16 and 31 (incl.)
+	       in-kernel GICv2/GICv3: PPI, irq_id between 16 and 31 (incl.)
+	       in-kernel GICv5: PPI, irq_id between 0 and 127 (incl.)
 
 (The irq_id field thus corresponds nicely to the IRQ ID in the ARM GIC specs)
 
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 94f8d13ab3b58..4448e8a5fc076 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -45,6 +45,8 @@
 #include <kvm/arm_pmu.h>
 #include <kvm/arm_psci.h>
 
+#include <linux/irqchip/arm-gic-v5.h>
+
 #include "sys_regs.h"
 
 static enum kvm_mode kvm_mode = KVM_MODE_DEFAULT;
@@ -1430,16 +1432,29 @@ int kvm_vm_ioctl_irq_line(struct kvm *kvm, struct kvm_irq_level *irq_level,
 		if (!vcpu)
 			return -EINVAL;
 
-		if (irq_num < VGIC_NR_SGIS || irq_num >= VGIC_NR_PRIVATE_IRQS)
+		if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) {
+			if (irq_num >= VGIC_V5_NR_PRIVATE_IRQS)
+				return -EINVAL;
+
+			/* Build a GICv5-style IntID here */
+			irq_num |= FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
+		} else if (irq_num < VGIC_NR_SGIS ||
+			   irq_num >= VGIC_NR_PRIVATE_IRQS) {
 			return -EINVAL;
+		}
 
 		return kvm_vgic_inject_irq(kvm, vcpu, irq_num, level, NULL);
 	case KVM_ARM_IRQ_TYPE_SPI:
 		if (!irqchip_in_kernel(kvm))
 			return -ENXIO;
 
-		if (irq_num < VGIC_NR_PRIVATE_IRQS)
-			return -EINVAL;
+		if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) {
+			/* Build a GICv5-style IntID here */
+			irq_num |= FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_SPI);
+		} else {
+			if (irq_num < VGIC_NR_PRIVATE_IRQS)
+				return -EINVAL;
+		}
 
 		return kvm_vgic_inject_irq(kvm, NULL, irq_num, level, NULL);
 	}
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index dfec6ed7936ed..d91f7039fed6c 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -86,6 +86,10 @@ 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;
+
 	/* SPIs */
 	if (intid >= VGIC_NR_PRIVATE_IRQS &&
 	    intid < (kvm->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS)) {
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 22/36] KVM: arm64: gic-v5: Trap and mask guest PPI register accesses
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (21 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 23/36] KVM: arm64: gic-v5: Support GICv5 interrupts with KVM_IRQ_LINE Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 15:17   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 24/36] KVM: arm64: gic-v5: Create, init vgic_v5 Sascha Bischoff
                   ` (13 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

A guest should not be able to detect if a PPI that is not exposed to
the guest is implemented or not. If the writes to the PPI registers
are not masked, it becomes possible for the guest to detect the
presence of all implemented PPIs on the host.

Guest writes to the following registers are masked:

ICC_CACTIVERx_EL1
ICC_SACTIVERx_EL1
ICC_CPENDRx_EL1
ICC_SPENDRx_EL1
ICC_ENABLERx_EL1
ICC_PRIORITYRx_EL1

When a guest writes these registers, the write is masked with the set
of PPIs actually exposed to the guest, and the state is written back
to KVM's shadow state..

Reads for the above registers are not masked. When the guest is
running and reads from the above registers, it is presented with what
KVM provides in the ICH_PPI_x_EL2 registers, which is the masked
version of what the guest last wrote.

The ICC_PPI_HMRx_EL1 register is used to determine which PPIs use
Level-sensitive semantics, and which use Edge. For a GICv5 guest, the
correct view of the virtual PPIs must be provided to the guest, and
hence this must also be trapped, but only for reads. The content of
the HMRs is calculated and masked when finalising the PPI state for
the guest.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/kvm/config.c   |  22 ++++++-
 arch/arm64/kvm/sys_regs.c | 133 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 153 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
index eb0c6f4d95b6d..f81bfdadd12fb 100644
--- a/arch/arm64/kvm/config.c
+++ b/arch/arm64/kvm/config.c
@@ -1586,8 +1586,26 @@ static void __compute_ich_hfgrtr(struct kvm_vcpu *vcpu)
 {
 	__compute_fgt(vcpu, ICH_HFGRTR_EL2);
 
-	/* ICC_IAFFIDR_EL1 *always* needs to be trapped when running a guest */
+	/*
+	 * ICC_IAFFIDR_EL1 and ICH_PPI_HMRx_EL1 *always* needs to be
+	 * trapped when running a guest.
+	 **/
 	*vcpu_fgt(vcpu, ICH_HFGRTR_EL2) &= ~ICH_HFGRTR_EL2_ICC_IAFFIDR_EL1;
+	*vcpu_fgt(vcpu, ICH_HFGRTR_EL2) &= ~ICH_HFGRTR_EL2_ICC_PPI_HMRn_EL1;
+}
+
+static void __compute_ich_hfgwtr(struct kvm_vcpu *vcpu)
+{
+	__compute_fgt(vcpu, ICH_HFGWTR_EL2);
+
+	/*
+	 * We present a different subset of PPIs the guest from what
+	 * exist in real hardware. We only trap writes, not reads.
+	 */
+	*vcpu_fgt(vcpu, ICH_HFGWTR_EL2) &= ~(ICH_HFGWTR_EL2_ICC_PPI_ENABLERn_EL1 |
+					     ICH_HFGWTR_EL2_ICC_PPI_PENDRn_EL1 |
+					     ICH_HFGWTR_EL2_ICC_PPI_PRIORITYRn_EL1 |
+					     ICH_HFGWTR_EL2_ICC_PPI_ACTIVERn_EL1);
 }
 
 void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
@@ -1616,6 +1634,6 @@ void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
 		return;
 
 	__compute_ich_hfgrtr(vcpu);
-	__compute_fgt(vcpu, ICH_HFGWTR_EL2);
+	__compute_ich_hfgwtr(vcpu);
 	__compute_fgt(vcpu, ICH_HFGITR_EL2);
 }
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index 383ada0d75922..cef13bf6bb3a1 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -696,6 +696,111 @@ static bool access_gicv5_iaffid(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
 	return true;
 }
 
+static bool access_gicv5_ppi_hmr(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
+				 const struct sys_reg_desc *r)
+{
+	if (p->is_write)
+		return ignore_write(vcpu, p);
+
+	if (p->Op2 == 0) {	/* ICC_PPI_HMR0_EL1 */
+		p->regval = vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[0];
+	} else {		/* ICC_PPI_HMR1_EL1 */
+		p->regval = vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[1];
+	}
+
+	return true;
+}
+
+static bool access_gicv5_ppi_enabler(struct kvm_vcpu *vcpu,
+				     struct sys_reg_params *p,
+				     const struct sys_reg_desc *r)
+{
+	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+	u64 masked_write;
+
+	/* We never expect to get here with a read! */
+	if (WARN_ON_ONCE(!p->is_write))
+		return undef_access(vcpu, p, r);
+
+	masked_write = p->regval & cpu_if->vgic_ppi_mask[p->Op2 % 2];
+	cpu_if->vgic_ich_ppi_enabler_entry[p->Op2 % 2] = masked_write;
+
+	return true;
+}
+
+static bool access_gicv5_ppi_pendr(struct kvm_vcpu *vcpu,
+				   struct sys_reg_params *p,
+				   const struct sys_reg_desc *r)
+{
+	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+	u64 masked_write;
+
+	/* We never expect to get here with a read! */
+	if (WARN_ON_ONCE(!p->is_write))
+		return undef_access(vcpu, p, r);
+
+	masked_write = p->regval & cpu_if->vgic_ppi_mask[p->Op2 % 2];
+
+	if (p->Op2 & 0x2) {	/* SPENDRx */
+		cpu_if->vgic_ppi_pendr_entry[p->Op2 % 2] |= masked_write;
+	} else {		/* CPENDRx */
+		cpu_if->vgic_ppi_pendr_entry[p->Op2 % 2] &= ~masked_write;
+	}
+
+	return true;
+}
+
+static bool access_gicv5_ppi_activer(struct kvm_vcpu *vcpu,
+				     struct sys_reg_params *p,
+				     const struct sys_reg_desc *r)
+{
+	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+	u64 masked_write;
+
+	/* We never expect to get here with a read! */
+	if (WARN_ON_ONCE(!p->is_write))
+		return undef_access(vcpu, p, r);
+
+	masked_write = p->regval & cpu_if->vgic_ppi_mask[p->Op2 % 2];
+
+	if (p->Op2 & 0x2) {	/* SACTIVERx */
+		cpu_if->vgic_ppi_activer_entry[p->Op2 % 2] |= masked_write;
+	} else {		/* CACTIVERx */
+		cpu_if->vgic_ppi_activer_entry[p->Op2 % 2] &= ~masked_write;
+	}
+
+	return true;
+}
+
+static bool access_gicv5_ppi_priorityr(struct kvm_vcpu *vcpu,
+				     struct sys_reg_params *p,
+				     const struct sys_reg_desc *r)
+{
+	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+	u64 mask, masked_write;
+	unsigned long mask_slice;
+	int i;
+	int reg_idx = ((p->CRm & 0x1) << 3) | p->Op2;
+	int mask_idx = reg_idx >= 8;
+
+	/* We never expect to get here with a read! */
+	if (WARN_ON_ONCE(!p->is_write))
+		return undef_access(vcpu, p, r);
+
+	/* Get the 8 bits of the mask that we care about */
+	mask_slice = (cpu_if->vgic_ppi_mask[mask_idx] >> (reg_idx % 8) * 8) & 0xff;
+
+	/* Generate our mask for the PRIORITYR */
+	mask = 0;
+	for_each_set_bit(i, &mask_slice, 8)
+		mask |= 0x1f << i * 8;
+
+	masked_write = p->regval & mask;
+	vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_priorityr[reg_idx] = masked_write;
+
+	return true;
+}
+
 static bool trap_raz_wi(struct kvm_vcpu *vcpu,
 			struct sys_reg_params *p,
 			const struct sys_reg_desc *r)
@@ -3426,7 +3531,11 @@ static const struct sys_reg_desc sys_reg_descs[] = {
 	{ SYS_DESC(SYS_ICC_AP1R1_EL1), undef_access },
 	{ SYS_DESC(SYS_ICC_AP1R2_EL1), undef_access },
 	{ SYS_DESC(SYS_ICC_AP1R3_EL1), undef_access },
+	{ SYS_DESC(SYS_ICC_PPI_HMR0_EL1), access_gicv5_ppi_hmr },
+	{ SYS_DESC(SYS_ICC_PPI_HMR1_EL1), access_gicv5_ppi_hmr },
 	{ SYS_DESC(SYS_ICC_IAFFIDR_EL1), access_gicv5_iaffid },
+	{ SYS_DESC(SYS_ICC_PPI_ENABLER0_EL1), access_gicv5_ppi_enabler },
+	{ SYS_DESC(SYS_ICC_PPI_ENABLER1_EL1), access_gicv5_ppi_enabler },
 	{ SYS_DESC(SYS_ICC_DIR_EL1), access_gic_dir },
 	{ SYS_DESC(SYS_ICC_RPR_EL1), undef_access },
 	{ SYS_DESC(SYS_ICC_SGI1R_EL1), access_gic_sgi },
@@ -3440,6 +3549,30 @@ static const struct sys_reg_desc sys_reg_descs[] = {
 	{ SYS_DESC(SYS_ICC_SRE_EL1), access_gic_sre },
 	{ SYS_DESC(SYS_ICC_IGRPEN0_EL1), undef_access },
 	{ SYS_DESC(SYS_ICC_IGRPEN1_EL1), undef_access },
+	{ SYS_DESC(SYS_ICC_PPI_CACTIVER0_EL1), access_gicv5_ppi_activer },
+	{ SYS_DESC(SYS_ICC_PPI_CACTIVER1_EL1), access_gicv5_ppi_activer },
+	{ SYS_DESC(SYS_ICC_PPI_SACTIVER0_EL1), access_gicv5_ppi_activer },
+	{ SYS_DESC(SYS_ICC_PPI_SACTIVER1_EL1), access_gicv5_ppi_activer },
+	{ SYS_DESC(SYS_ICC_PPI_CPENDR0_EL1), access_gicv5_ppi_pendr },
+	{ SYS_DESC(SYS_ICC_PPI_CPENDR1_EL1), access_gicv5_ppi_pendr },
+	{ SYS_DESC(SYS_ICC_PPI_SPENDR0_EL1), access_gicv5_ppi_pendr },
+	{ SYS_DESC(SYS_ICC_PPI_SPENDR1_EL1), access_gicv5_ppi_pendr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR0_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR1_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR2_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR3_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR4_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR5_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR6_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR7_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR8_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR9_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR10_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR11_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR12_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR13_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR14_EL1), access_gicv5_ppi_priorityr },
+	{ SYS_DESC(SYS_ICC_PPI_PRIORITYR15_EL1), access_gicv5_ppi_priorityr },
 
 	{ SYS_DESC(SYS_CONTEXTIDR_EL1), access_vm_reg, reset_val, CONTEXTIDR_EL1, 0 },
 	{ SYS_DESC(SYS_TPIDR_EL1), NULL, reset_unknown, TPIDR_EL1 },
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 21/36] KVM: arm64: gic-v5: Finalize GICv5 PPIs and generate mask
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (19 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 18/36] KVM: arm64: gic-v5: Implement PPI interrupt injection Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 15:08   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 23/36] KVM: arm64: gic-v5: Support GICv5 interrupts with KVM_IRQ_LINE Sascha Bischoff
                   ` (15 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

We only want to expose a subset of the PPIs to a guest. If a PPI does
not have an owner, it is not being actively driven by a device. The
SW_PPI is a special case, as it is likely for userspace to wish to
inject that.

Therefore, just prior to running the guest for the first time, we need
to finalize the PPIs. A mask is generated which, when combined with
trapping a guest's PPI accesses, allows for the guest's view of the
PPI to be filtered.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/kvm/arm.c          |  4 +++
 arch/arm64/kvm/vgic/vgic-v5.c | 60 +++++++++++++++++++++++++++++++++++
 include/kvm/arm_vgic.h        |  9 ++++++
 3 files changed, 73 insertions(+)

diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index b7cf9d86aabb7..94f8d13ab3b58 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -888,6 +888,10 @@ int kvm_arch_vcpu_run_pid_change(struct kvm_vcpu *vcpu)
 			return ret;
 	}
 
+	ret = vgic_v5_finalize_ppi_state(kvm);
+	if (ret)
+		return ret;
+
 	if (is_protected_kvm_enabled()) {
 		ret = pkvm_create_hyp_vm(kvm);
 		if (ret)
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index c7ecc4f40b1e5..f1fa63e67c1f6 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -81,6 +81,66 @@ static u32 vgic_v5_get_effective_priority_mask(struct kvm_vcpu *vcpu)
 	return priority_mask;
 }
 
+static int vgic_v5_finalize_state(struct kvm_vcpu *vcpu)
+{
+	if (!ppi_caps)
+		return -ENXIO;
+
+	vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_mask[0] = 0;
+	vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_mask[1] = 0;
+	vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[0] = 0;
+	vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[1] = 0;
+	for (int i = 0; i < VGIC_V5_NR_PRIVATE_IRQS; ++i) {
+		int reg = i / 64;
+		u64 bit = BIT_ULL(i % 64);
+		struct vgic_irq *irq = &vcpu->arch.vgic_cpu.private_irqs[i];
+
+		raw_spin_lock(&irq->irq_lock);
+
+		/*
+		 * We only expose PPIs with an owner or thw SW_PPI to
+		 * the guest.
+		 */
+		if (!irq->owner && irq->intid == GICV5_SW_PPI)
+			goto unlock;
+
+		/*
+		 * If the PPI isn't implemented, we can't pass it
+		 * through to a guest anyhow.
+		 */
+		if (!(ppi_caps->impl_ppi_mask[reg] & bit))
+			goto unlock;
+
+		vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_mask[reg] |= bit;
+
+		if (irq->config == VGIC_CONFIG_LEVEL)
+			vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[reg] |= bit;
+
+unlock:
+		raw_spin_unlock(&irq->irq_lock);
+	}
+
+	return 0;
+}
+
+int vgic_v5_finalize_ppi_state(struct kvm *kvm)
+{
+	struct kvm_vcpu *vcpu;
+	unsigned long c;
+	int ret;
+
+	if (!vgic_is_v5(kvm))
+		return 0;
+
+	kvm_for_each_vcpu(c, vcpu, kvm) {
+		ret = vgic_v5_finalize_state(vcpu);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
 static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
 					  struct vgic_irq *irq)
 {
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index b5180edbd1165..dc7bac0226b3c 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -455,6 +455,13 @@ struct vgic_v5_cpu_if {
 	u64	vgic_ich_ppi_enabler_exit[2];
 	u64	vgic_ppi_pendr_exit[2];
 
+	/*
+	 * 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 actually choose to expose.
+	 */
+	u64	vgic_ppi_mask[2];
+
 	/*
 	 * The ICSR is re-used across host and guest, and hence it needs to be
 	 * saved/restored. Only one copy is required as the host should block
@@ -592,6 +599,8 @@ int vgic_v4_load(struct kvm_vcpu *vcpu);
 void vgic_v4_commit(struct kvm_vcpu *vcpu);
 int vgic_v4_put(struct kvm_vcpu *vcpu);
 
+int vgic_v5_finalize_ppi_state(struct kvm *kvm);
+
 bool vgic_state_is_nested(struct kvm_vcpu *vcpu);
 
 /* CPU HP callbacks */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 25/36] KVM: arm64: gic-v5: Reset vcpu state
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (23 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 24/36] KVM: arm64: gic-v5: Create, init vgic_v5 Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 15:51   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 27/36] KVM: arm64: gic-v5: Mandate architected PPI for PMU emulation on GICv5 Sascha Bischoff
                   ` (11 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

Limit the number of ID and priority bits supported based on the
hardware capabilities when resetting the vcpu state.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/kvm/vgic/vgic-init.c |  6 +++++-
 arch/arm64/kvm/vgic/vgic-v5.c   | 30 ++++++++++++++++++++++++++++++
 arch/arm64/kvm/vgic/vgic.h      |  1 +
 3 files changed, 36 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index afb5888cd8219..56cd5c05742df 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -396,7 +396,11 @@ int kvm_vgic_vcpu_init(struct kvm_vcpu *vcpu)
 
 static void kvm_vgic_vcpu_reset(struct kvm_vcpu *vcpu)
 {
-	if (kvm_vgic_global_state.type == VGIC_V2)
+	const struct vgic_dist *dist = &vcpu->kvm->arch.vgic;
+
+	if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
+		vgic_v5_reset(vcpu);
+	else if (kvm_vgic_global_state.type == VGIC_V2)
 		vgic_v2_reset(vcpu);
 	else
 		vgic_v3_reset(vcpu);
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 17001b06af600..feba175a5047d 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -56,6 +56,36 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
 	return 0;
 }
 
+void vgic_v5_reset(struct kvm_vcpu *vcpu)
+{
+	u64 idr0;
+
+	idr0 = read_sysreg_s(SYS_ICC_IDR0_EL1);
+	switch (FIELD_GET(ICC_IDR0_EL1_ID_BITS, idr0)) {
+	case ICC_IDR0_EL1_ID_BITS_16BITS:
+		vcpu->arch.vgic_cpu.num_id_bits = 16;
+		break;
+	case ICC_IDR0_EL1_ID_BITS_24BITS:
+		vcpu->arch.vgic_cpu.num_id_bits = 24;
+		break;
+	default:
+		pr_warn("unknown value for id_bits");
+		vcpu->arch.vgic_cpu.num_id_bits = 16;
+	}
+
+	switch (FIELD_GET(ICC_IDR0_EL1_PRI_BITS, idr0)) {
+	case ICC_IDR0_EL1_PRI_BITS_4BITS:
+		vcpu->arch.vgic_cpu.num_pri_bits = 4;
+		break;
+	case ICC_IDR0_EL1_PRI_BITS_5BITS:
+		vcpu->arch.vgic_cpu.num_pri_bits = 5;
+		break;
+	default:
+		pr_warn("unknown value for priority_bits");
+		vcpu->arch.vgic_cpu.num_pri_bits = 4;
+	}
+}
+
 int vgic_v5_init(struct kvm *kvm)
 {
 	struct kvm_vcpu *vcpu;
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index f974b55fb8058..c8ff545e777c2 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -386,6 +386,7 @@ void vgic_debug_destroy(struct kvm *kvm);
 
 int vgic_v5_probe(const struct gic_kvm_info *info);
 void vgic_v5_get_implemented_ppis(void);
+void vgic_v5_reset(struct kvm_vcpu *vcpu);
 int vgic_v5_init(struct kvm *kvm);
 int vgic_v5_map_resources(struct kvm *kvm);
 void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 24/36] KVM: arm64: gic-v5: Create, init vgic_v5
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (22 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 22/36] KVM: arm64: gic-v5: Trap and mask guest PPI register accesses Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 15:49   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 25/36] KVM: arm64: gic-v5: Reset vcpu state Sascha Bischoff
                   ` (12 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

Update kvm_vgic_create to create a vgic_v5 device. When creating a
vgic, FEAT_GCIE in the ID_AA64PFR2 is only exposed to vgic_v5-based
guests, and is hidden otherwise. GIC in ~ID_AA64PFR0_EL1 is never
exposed for a vgic_v5 guest.

When initialising a vgic_v5, skip kvm_vgic_dist_init as GICv5 doesn't
support one. The current vgic_v5 implementation only supports PPIs, so
no SPIs are initialised either.

The current vgic_v5 support doesn't extend to nested
guests. Therefore, the init of vgic_v5 for a nested guest is failed in
vgic_v5_init.

As the current vgic_v5 doesn't require any resources to be mapped,
vgic_v5_map_resources is simply used to check that the vgic has indeed
been initialised. Again, this will change as more GICv5 support is
merged in.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/kvm/vgic/vgic-init.c | 51 ++++++++++++++++++++++-----------
 arch/arm64/kvm/vgic/vgic-v5.c   | 26 +++++++++++++++++
 arch/arm64/kvm/vgic/vgic.h      |  2 ++
 include/kvm/arm_vgic.h          |  1 +
 4 files changed, 63 insertions(+), 17 deletions(-)

diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index 03f45816464b0..afb5888cd8219 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -66,12 +66,12 @@ static int vgic_allocate_private_irqs_locked(struct kvm_vcpu *vcpu, u32 type);
  * or through the generic KVM_CREATE_DEVICE API ioctl.
  * irqchip_in_kernel() tells you if this function succeeded or not.
  * @kvm: kvm struct pointer
- * @type: KVM_DEV_TYPE_ARM_VGIC_V[23]
+ * @type: KVM_DEV_TYPE_ARM_VGIC_V[235]
  */
 int kvm_vgic_create(struct kvm *kvm, u32 type)
 {
 	struct kvm_vcpu *vcpu;
-	u64 aa64pfr0, pfr1;
+	u64 aa64pfr0, aa64pfr2, pfr1;
 	unsigned long i;
 	int ret;
 
@@ -132,8 +132,11 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
 
 	if (type == KVM_DEV_TYPE_ARM_VGIC_V2)
 		kvm->max_vcpus = VGIC_V2_MAX_CPUS;
-	else
+	else if (type == 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);
 
 	if (atomic_read(&kvm->online_vcpus) > kvm->max_vcpus) {
 		ret = -E2BIG;
@@ -163,17 +166,21 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
 	}
 
 	aa64pfr0 = kvm_read_vm_id_reg(kvm, SYS_ID_AA64PFR0_EL1) & ~ID_AA64PFR0_EL1_GIC;
+	aa64pfr2 = kvm_read_vm_id_reg(kvm, SYS_ID_AA64PFR2_EL1) & ~ID_AA64PFR2_EL1_GCIE;
 	pfr1 = kvm_read_vm_id_reg(kvm, SYS_ID_PFR1_EL1) & ~ID_PFR1_EL1_GIC;
 
 	if (type == KVM_DEV_TYPE_ARM_VGIC_V2) {
 		kvm->arch.vgic.vgic_cpu_base = VGIC_ADDR_UNDEF;
-	} else {
+	} else if (type == KVM_DEV_TYPE_ARM_VGIC_V3) {
 		INIT_LIST_HEAD(&kvm->arch.vgic.rd_regions);
 		aa64pfr0 |= SYS_FIELD_PREP_ENUM(ID_AA64PFR0_EL1, GIC, IMP);
 		pfr1 |= SYS_FIELD_PREP_ENUM(ID_PFR1_EL1, GIC, GICv3);
+	} else {
+		aa64pfr2 |= SYS_FIELD_PREP_ENUM(ID_AA64PFR2_EL1, GCIE, IMP);
 	}
 
 	kvm_set_vm_id_reg(kvm, SYS_ID_AA64PFR0_EL1, aa64pfr0);
+	kvm_set_vm_id_reg(kvm, SYS_ID_AA64PFR2_EL1, aa64pfr2);
 	kvm_set_vm_id_reg(kvm, SYS_ID_PFR1_EL1, pfr1);
 
 	if (type == KVM_DEV_TYPE_ARM_VGIC_V3)
@@ -420,20 +427,26 @@ int vgic_init(struct kvm *kvm)
 	if (kvm->created_vcpus != atomic_read(&kvm->online_vcpus))
 		return -EBUSY;
 
-	/* freeze the number of spis */
-	if (!dist->nr_spis)
-		dist->nr_spis = VGIC_NR_IRQS_LEGACY - VGIC_NR_PRIVATE_IRQS;
+	if (!vgic_is_v5(kvm)) {
+		/* freeze the number of spis */
+		if (!dist->nr_spis)
+			dist->nr_spis = VGIC_NR_IRQS_LEGACY - VGIC_NR_PRIVATE_IRQS;
 
-	ret = kvm_vgic_dist_init(kvm, dist->nr_spis);
-	if (ret)
-		goto out;
+		ret = kvm_vgic_dist_init(kvm, dist->nr_spis);
+		if (ret)
+			goto out;
 
-	/*
-	 * Ensure vPEs are allocated if direct IRQ injection (e.g. vSGIs,
-	 * vLPIs) is supported.
-	 */
-	if (vgic_supports_direct_irqs(kvm)) {
-		ret = vgic_v4_init(kvm);
+		/*
+		 * Ensure vPEs are allocated if direct IRQ injection (e.g. vSGIs,
+		 * vLPIs) is supported.
+		 */
+		if (vgic_supports_direct_irqs(kvm)) {
+			ret = vgic_v4_init(kvm);
+			if (ret)
+				goto out;
+		}
+	} else {
+		ret = vgic_v5_init(kvm);
 		if (ret)
 			goto out;
 	}
@@ -610,9 +623,13 @@ int kvm_vgic_map_resources(struct kvm *kvm)
 	if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V2) {
 		ret = vgic_v2_map_resources(kvm);
 		type = VGIC_V2;
-	} else {
+	} else if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3) {
 		ret = vgic_v3_map_resources(kvm);
 		type = VGIC_V3;
+	} else {
+		ret = vgic_v5_map_resources(kvm);
+		type = VGIC_V5;
+		goto out;
 	}
 
 	if (ret)
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index f1fa63e67c1f6..17001b06af600 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -56,6 +56,32 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
 	return 0;
 }
 
+int vgic_v5_init(struct kvm *kvm)
+{
+	struct kvm_vcpu *vcpu;
+	unsigned long idx;
+
+	if (vgic_initialized(kvm))
+		return 0;
+
+	kvm_for_each_vcpu(idx, vcpu, kvm) {
+		if (vcpu_has_nv(vcpu)) {
+			kvm_err("Nested GICv5 VMs are currently unsupported\n");
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+int vgic_v5_map_resources(struct kvm *kvm)
+{
+	if (!vgic_initialized(kvm))
+		return -EBUSY;
+
+	return 0;
+}
+
 static u32 vgic_v5_get_effective_priority_mask(struct kvm_vcpu *vcpu)
 {
 	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 65c031da83e78..f974b55fb8058 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -386,6 +386,8 @@ void vgic_debug_destroy(struct kvm *kvm);
 
 int vgic_v5_probe(const struct gic_kvm_info *info);
 void vgic_v5_get_implemented_ppis(void);
+int vgic_v5_init(struct kvm *kvm);
+int vgic_v5_map_resources(struct kvm *kvm);
 void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
 int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
 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 dc7bac0226b3c..696e2316f1ea9 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -21,6 +21,7 @@
 #include <linux/irqchip/arm-gic-v4.h>
 #include <linux/irqchip/arm-gic-v5.h>
 
+#define VGIC_V5_MAX_CPUS	512
 #define VGIC_V3_MAX_CPUS	512
 #define VGIC_V2_MAX_CPUS	8
 #define VGIC_NR_IRQS_LEGACY     256
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 27/36] KVM: arm64: gic-v5: Mandate architected PPI for PMU emulation on GICv5
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (24 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 25/36] KVM: arm64: gic-v5: Reset vcpu state Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-06 15:06   ` Joey Gouly
  2026-01-07 16:11   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 28/36] KVM: arm64: gic: Hide GICv5 for protected guests Sascha Bischoff
                   ` (10 subsequent siblings)
  36 siblings, 2 replies; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

Make it mandatory to use the architected PPI when running a GICv5
guest. Attempts to set anything other than the architected PPI (23)
are rejected.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/kvm/pmu-emul.c | 14 ++++++++++++--
 include/kvm/arm_pmu.h     |  5 ++++-
 2 files changed, 16 insertions(+), 3 deletions(-)

diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
index afc838ea2503e..2d3b50dec6c5d 100644
--- a/arch/arm64/kvm/pmu-emul.c
+++ b/arch/arm64/kvm/pmu-emul.c
@@ -962,8 +962,13 @@ static int kvm_arm_pmu_v3_init(struct kvm_vcpu *vcpu)
 		if (!vgic_initialized(vcpu->kvm))
 			return -ENODEV;
 
-		if (!kvm_arm_pmu_irq_initialized(vcpu))
-			return -ENXIO;
+		if (!kvm_arm_pmu_irq_initialized(vcpu)) {
+			/* Use the architected irq number for GICv5. */
+			if (vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
+				vcpu->arch.pmu.irq_num = KVM_ARMV8_PMU_GICV5_IRQ;
+			else
+				return -ENXIO;
+		}
 
 		ret = kvm_vgic_set_owner(vcpu, vcpu->arch.pmu.irq_num,
 					 &vcpu->arch.pmu);
@@ -988,6 +993,11 @@ static bool pmu_irq_is_valid(struct kvm *kvm, int irq)
 	unsigned long i;
 	struct kvm_vcpu *vcpu;
 
+	/* On GICv5, the PMUIRQ is architecturally mandated to be PPI 23 */
+	if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5 &&
+	    irq != KVM_ARMV8_PMU_GICV5_IRQ)
+		return false;
+
 	kvm_for_each_vcpu(i, vcpu, kvm) {
 		if (!kvm_arm_pmu_irq_initialized(vcpu))
 			continue;
diff --git a/include/kvm/arm_pmu.h b/include/kvm/arm_pmu.h
index 96754b51b4116..0a36a3d5c8944 100644
--- a/include/kvm/arm_pmu.h
+++ b/include/kvm/arm_pmu.h
@@ -12,6 +12,9 @@
 
 #define KVM_ARMV8_PMU_MAX_COUNTERS	32
 
+/* PPI #23 - architecturally specified for GICv5 */
+#define KVM_ARMV8_PMU_GICV5_IRQ		0x20000017
+
 #if IS_ENABLED(CONFIG_HW_PERF_EVENTS) && IS_ENABLED(CONFIG_KVM)
 struct kvm_pmc {
 	u8 idx;	/* index into the pmu->pmc array */
@@ -38,7 +41,7 @@ struct arm_pmu_entry {
 };
 
 bool kvm_supports_guest_pmuv3(void);
-#define kvm_arm_pmu_irq_initialized(v)	((v)->arch.pmu.irq_num >= VGIC_NR_SGIS)
+#define kvm_arm_pmu_irq_initialized(v)	((v)->arch.pmu.irq_num != 0)
 u64 kvm_pmu_get_counter_value(struct kvm_vcpu *vcpu, u64 select_idx);
 void kvm_pmu_set_counter_value(struct kvm_vcpu *vcpu, u64 select_idx, u64 val);
 void kvm_pmu_set_counter_value_user(struct kvm_vcpu *vcpu, u64 select_idx, u64 val);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 28/36] KVM: arm64: gic: Hide GICv5 for protected guests
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (25 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 27/36] KVM: arm64: gic-v5: Mandate architected PPI for PMU emulation on GICv5 Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 16:12   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 26/36] KVM: arm64: gic-v5: Bump arch timer for GICv5 Sascha Bischoff
                   ` (9 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

We don't support running protected guest with GICv5 at the
moment. Therefore, be sure that we don't expose it to the guest at all
by actively hiding it when running a protected guest.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/include/asm/kvm_hyp.h   | 1 +
 arch/arm64/kvm/arm.c               | 1 +
 arch/arm64/kvm/hyp/nvhe/sys_regs.c | 8 ++++++++
 3 files changed, 10 insertions(+)

diff --git a/arch/arm64/include/asm/kvm_hyp.h b/arch/arm64/include/asm/kvm_hyp.h
index c965f4e178cee..7322ea3faded7 100644
--- a/arch/arm64/include/asm/kvm_hyp.h
+++ b/arch/arm64/include/asm/kvm_hyp.h
@@ -145,6 +145,7 @@ void __noreturn __host_enter(struct kvm_cpu_context *host_ctxt);
 
 extern u64 kvm_nvhe_sym(id_aa64pfr0_el1_sys_val);
 extern u64 kvm_nvhe_sym(id_aa64pfr1_el1_sys_val);
+extern u64 kvm_nvhe_sym(id_aa64pfr2_el1_sys_val);
 extern u64 kvm_nvhe_sym(id_aa64isar0_el1_sys_val);
 extern u64 kvm_nvhe_sym(id_aa64isar1_el1_sys_val);
 extern u64 kvm_nvhe_sym(id_aa64isar2_el1_sys_val);
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 4448e8a5fc076..1d3f2f713769f 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -2471,6 +2471,7 @@ static void kvm_hyp_init_symbols(void)
 {
 	kvm_nvhe_sym(id_aa64pfr0_el1_sys_val) = get_hyp_id_aa64pfr0_el1();
 	kvm_nvhe_sym(id_aa64pfr1_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64PFR1_EL1);
+	kvm_nvhe_sym(id_aa64pfr2_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64PFR2_EL1);
 	kvm_nvhe_sym(id_aa64isar0_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ISAR0_EL1);
 	kvm_nvhe_sym(id_aa64isar1_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ISAR1_EL1);
 	kvm_nvhe_sym(id_aa64isar2_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ISAR2_EL1);
diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
index 3108b5185c204..9652935a6ebdd 100644
--- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c
+++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
@@ -20,6 +20,7 @@
  */
 u64 id_aa64pfr0_el1_sys_val;
 u64 id_aa64pfr1_el1_sys_val;
+u64 id_aa64pfr2_el1_sys_val;
 u64 id_aa64isar0_el1_sys_val;
 u64 id_aa64isar1_el1_sys_val;
 u64 id_aa64isar2_el1_sys_val;
@@ -108,6 +109,11 @@ static const struct pvm_ftr_bits pvmid_aa64pfr1[] = {
 	FEAT_END
 };
 
+static const struct pvm_ftr_bits pvmid_aa64pfr2[] = {
+	MAX_FEAT(ID_AA64PFR2_EL1, GCIE, NI),
+	FEAT_END
+};
+
 static const struct pvm_ftr_bits pvmid_aa64mmfr0[] = {
 	MAX_FEAT_ENUM(ID_AA64MMFR0_EL1, PARANGE, 40),
 	MAX_FEAT_ENUM(ID_AA64MMFR0_EL1, ASIDBITS, 16),
@@ -221,6 +227,8 @@ static u64 pvm_calc_id_reg(const struct kvm_vcpu *vcpu, u32 id)
 		return get_restricted_features(vcpu, id_aa64pfr0_el1_sys_val, pvmid_aa64pfr0);
 	case SYS_ID_AA64PFR1_EL1:
 		return get_restricted_features(vcpu, id_aa64pfr1_el1_sys_val, pvmid_aa64pfr1);
+	case SYS_ID_AA64PFR2_EL1:
+		return get_restricted_features(vcpu, id_aa64pfr2_el1_sys_val, pvmid_aa64pfr2);
 	case SYS_ID_AA64ISAR0_EL1:
 		return id_aa64isar0_el1_sys_val;
 	case SYS_ID_AA64ISAR1_EL1:
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 26/36] KVM: arm64: gic-v5: Bump arch timer for GICv5
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (26 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 28/36] KVM: arm64: gic: Hide GICv5 for protected guests Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 16:08   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 31/36] KVM: arm64: gic-v5: Set ICH_VCTLR_EL2.En on boot Sascha Bischoff
                   ` (8 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

Now that GICv5 has arrived, the arch timer requires some TLC to
address some of the key differences introduced with GICv5.

For PPIs on GICv5, the set_pending_state and queue_irq_unlock irq_ops
are used as AP lists are not required at all for GICv5. The arch timer
also introduces an irq_op - get_input_level. Extend the
arch-timer-provided irq_ops to include the two PPI ops for vgic_v5
guests.

When possible, DVI (Direct Virtual Interrupt) is set for PPIs when
using a vgic_v5, which directly inject the pending state in to the
guest. This means that the host never sees the interrupt for the guest
for these interrupts. This has two impacts.

* First of all, the kvm_cpu_has_pending_timer check is updated to
  explicitly check if the timers are expected to fire.

* Secondly, for mapped timers (which use DVI) they must be masked on
  the host prior to entering a GICv5 guest, and unmasked on the return
  path. This is handled in set_timer_irq_phys_masked.

The final, but rather important, change is that the architected PPIs
for the timers are made mandatory for a GICv5 guest. Attempts to set
them to anything else are actively rejected. Once a vgic_v5 is
initialised, the arch timer PPIs are also explicitly reinitialised to
ensure the correct GICv5-compatible PPIs are used - this also adds in
the GICv5 PPI type to the intid.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/kvm/arch_timer.c     | 110 ++++++++++++++++++++++++++------
 arch/arm64/kvm/vgic/vgic-init.c |   9 +++
 arch/arm64/kvm/vgic/vgic-v5.c   |   8 +--
 include/kvm/arm_arch_timer.h    |   7 +-
 include/kvm/arm_vgic.h          |   4 ++
 5 files changed, 115 insertions(+), 23 deletions(-)

diff --git a/arch/arm64/kvm/arch_timer.c b/arch/arm64/kvm/arch_timer.c
index 6f033f6644219..78d66a67b34ac 100644
--- a/arch/arm64/kvm/arch_timer.c
+++ b/arch/arm64/kvm/arch_timer.c
@@ -56,6 +56,12 @@ static struct irq_ops arch_timer_irq_ops = {
 	.get_input_level = kvm_arch_timer_get_input_level,
 };
 
+static struct irq_ops arch_timer_irq_ops_vgic_v5 = {
+	.get_input_level = kvm_arch_timer_get_input_level,
+	.set_pending_state = vgic_v5_ppi_set_pending_state,
+	.queue_irq_unlock = vgic_v5_ppi_queue_irq_unlock,
+};
+
 static int nr_timers(struct kvm_vcpu *vcpu)
 {
 	if (!vcpu_has_nv(vcpu))
@@ -396,7 +402,11 @@ static bool kvm_timer_should_fire(struct arch_timer_context *timer_ctx)
 
 int kvm_cpu_has_pending_timer(struct kvm_vcpu *vcpu)
 {
-	return vcpu_has_wfit_active(vcpu) && wfit_delay_ns(vcpu) == 0;
+	struct arch_timer_context *vtimer = vcpu_vtimer(vcpu);
+	struct arch_timer_context *ptimer = vcpu_ptimer(vcpu);
+
+	return kvm_timer_should_fire(vtimer) || kvm_timer_should_fire(ptimer) ||
+	       (vcpu_has_wfit_active(vcpu) && wfit_delay_ns(vcpu) == 0);
 }
 
 /*
@@ -657,6 +667,24 @@ static inline void set_timer_irq_phys_active(struct arch_timer_context *ctx, boo
 	WARN_ON(r);
 }
 
+/*
+ * On GICv5 we use DVI for the arch timer PPIs. This is restored later
+ * on as part of vgic_load. Therefore, in order to avoid the guest's
+ * interrupt making it to the host we mask it before entering the
+ * guest and unmask it again when we return.
+ */
+static inline void set_timer_irq_phys_masked(struct arch_timer_context *ctx, bool masked)
+{
+	if (masked) {
+		disable_percpu_irq(ctx->host_timer_irq);
+	} else {
+		if (ctx->host_timer_irq == host_vtimer_irq)
+			enable_percpu_irq(ctx->host_timer_irq, host_vtimer_irq_flags);
+		else
+			enable_percpu_irq(ctx->host_timer_irq, host_ptimer_irq_flags);
+	}
+}
+
 static void kvm_timer_vcpu_load_gic(struct arch_timer_context *ctx)
 {
 	struct kvm_vcpu *vcpu = timer_context_to_vcpu(ctx);
@@ -675,7 +703,10 @@ static void kvm_timer_vcpu_load_gic(struct arch_timer_context *ctx)
 
 	phys_active |= ctx->irq.level;
 
-	set_timer_irq_phys_active(ctx, phys_active);
+	if (!vgic_is_v5(vcpu->kvm))
+		set_timer_irq_phys_active(ctx, phys_active);
+	else
+		set_timer_irq_phys_masked(ctx, true);
 }
 
 static void kvm_timer_vcpu_load_nogic(struct kvm_vcpu *vcpu)
@@ -719,10 +750,14 @@ static void kvm_timer_vcpu_load_nested_switch(struct kvm_vcpu *vcpu,
 					      struct timer_map *map)
 {
 	int hw, ret;
+	struct irq_ops *ops;
 
 	if (!irqchip_in_kernel(vcpu->kvm))
 		return;
 
+	ops = vgic_is_v5(vcpu->kvm) ? &arch_timer_irq_ops_vgic_v5 :
+				      &arch_timer_irq_ops;
+
 	/*
 	 * We only ever unmap the vtimer irq on a VHE system that runs nested
 	 * virtualization, in which case we have both a valid emul_vtimer,
@@ -741,12 +776,12 @@ static void kvm_timer_vcpu_load_nested_switch(struct kvm_vcpu *vcpu,
 		ret = kvm_vgic_map_phys_irq(vcpu,
 					    map->direct_vtimer->host_timer_irq,
 					    timer_irq(map->direct_vtimer),
-					    &arch_timer_irq_ops);
+					    ops);
 		WARN_ON_ONCE(ret);
 		ret = kvm_vgic_map_phys_irq(vcpu,
 					    map->direct_ptimer->host_timer_irq,
 					    timer_irq(map->direct_ptimer),
-					    &arch_timer_irq_ops);
+					    ops);
 		WARN_ON_ONCE(ret);
 	}
 }
@@ -864,7 +899,8 @@ void kvm_timer_vcpu_load(struct kvm_vcpu *vcpu)
 	get_timer_map(vcpu, &map);
 
 	if (static_branch_likely(&has_gic_active_state)) {
-		if (vcpu_has_nv(vcpu))
+		/* We don't do NV on GICv5, yet */
+		if (vcpu_has_nv(vcpu) && !vgic_is_v5(vcpu->kvm))
 			kvm_timer_vcpu_load_nested_switch(vcpu, &map);
 
 		kvm_timer_vcpu_load_gic(map.direct_vtimer);
@@ -934,6 +970,15 @@ void kvm_timer_vcpu_put(struct kvm_vcpu *vcpu)
 
 	if (kvm_vcpu_is_blocking(vcpu))
 		kvm_timer_blocking(vcpu);
+
+	/* Unmask again on GICV5 */
+	if (vgic_is_v5(vcpu->kvm)) {
+		set_timer_irq_phys_masked(map.direct_vtimer, false);
+
+		if (map.direct_ptimer)
+			set_timer_irq_phys_masked(map.direct_ptimer, false);
+
+	}
 }
 
 void kvm_timer_sync_nested(struct kvm_vcpu *vcpu)
@@ -1034,12 +1079,15 @@ void kvm_timer_vcpu_reset(struct kvm_vcpu *vcpu)
 	if (timer->enabled) {
 		for (int i = 0; i < nr_timers(vcpu); i++)
 			kvm_timer_update_irq(vcpu, false,
-					     vcpu_get_timer(vcpu, i));
+					vcpu_get_timer(vcpu, i));
 
 		if (irqchip_in_kernel(vcpu->kvm)) {
-			kvm_vgic_reset_mapped_irq(vcpu, timer_irq(map.direct_vtimer));
+			kvm_vgic_reset_mapped_irq(
+				vcpu, timer_irq(map.direct_vtimer));
 			if (map.direct_ptimer)
-				kvm_vgic_reset_mapped_irq(vcpu, timer_irq(map.direct_ptimer));
+				kvm_vgic_reset_mapped_irq(
+					vcpu,
+					timer_irq(map.direct_ptimer));
 		}
 	}
 
@@ -1092,10 +1140,19 @@ void kvm_timer_vcpu_init(struct kvm_vcpu *vcpu)
 		      HRTIMER_MODE_ABS_HARD);
 }
 
+/*
+ * This is always called during kvm_arch_init_vm, but will also be
+ * called from kvm_vgic_create if we have a vGICv5.
+ */
 void kvm_timer_init_vm(struct kvm *kvm)
 {
+	/*
+	 * Set up the default PPIs - note that we adjust them based on
+	 * the model of the GIC as GICv5 uses a different way to
+	 * describing interrupts.
+	 */
 	for (int i = 0; i < NR_KVM_TIMERS; i++)
-		kvm->arch.timer_data.ppi[i] = default_ppi[i];
+		kvm->arch.timer_data.ppi[i] = get_vgic_ppi(kvm, default_ppi[i]);
 }
 
 void kvm_timer_cpu_up(void)
@@ -1347,6 +1404,7 @@ static int kvm_irq_init(struct arch_timer_kvm_info *info)
 		}
 
 		arch_timer_irq_ops.flags |= VGIC_IRQ_SW_RESAMPLE;
+		arch_timer_irq_ops_vgic_v5.flags |= VGIC_IRQ_SW_RESAMPLE;
 		WARN_ON(irq_domain_push_irq(domain, host_vtimer_irq,
 					    (void *)TIMER_VTIMER));
 	}
@@ -1497,10 +1555,13 @@ static bool timer_irqs_are_valid(struct kvm_vcpu *vcpu)
 			break;
 
 		/*
-		 * We know by construction that we only have PPIs, so
-		 * all values are less than 32.
+		 * We know by construction that we only have PPIs, so all values
+		 * are less than 32 for non-GICv5 vgics. On GICv5, they are
+		 * architecturally defined to be under 32 too. However, we mask
+		 * off most of the bits as we might be presented with a GICv5
+		 * style PPI where the type is encoded in the top-bits.
 		 */
-		ppis |= BIT(irq);
+		ppis |= BIT(irq & 0x1f);
 	}
 
 	valid = hweight32(ppis) == nr_timers(vcpu);
@@ -1538,7 +1599,9 @@ int kvm_timer_enable(struct kvm_vcpu *vcpu)
 {
 	struct arch_timer_cpu *timer = vcpu_timer(vcpu);
 	struct timer_map map;
+	struct irq_ops *ops;
 	int ret;
+	int irq;
 
 	if (timer->enabled)
 		return 0;
@@ -1556,20 +1619,22 @@ int kvm_timer_enable(struct kvm_vcpu *vcpu)
 		return -EINVAL;
 	}
 
+	ops = vgic_is_v5(vcpu->kvm) ? &arch_timer_irq_ops_vgic_v5 :
+				      &arch_timer_irq_ops;
+
 	get_timer_map(vcpu, &map);
 
-	ret = kvm_vgic_map_phys_irq(vcpu,
-				    map.direct_vtimer->host_timer_irq,
-				    timer_irq(map.direct_vtimer),
-				    &arch_timer_irq_ops);
+	irq = timer_irq(map.direct_vtimer);
+	ret = kvm_vgic_map_phys_irq(vcpu, map.direct_vtimer->host_timer_irq,
+				    irq, ops);
 	if (ret)
 		return ret;
 
 	if (map.direct_ptimer) {
+		irq = timer_irq(map.direct_ptimer);
 		ret = kvm_vgic_map_phys_irq(vcpu,
 					    map.direct_ptimer->host_timer_irq,
-					    timer_irq(map.direct_ptimer),
-					    &arch_timer_irq_ops);
+					    irq, ops);
 	}
 
 	if (ret)
@@ -1627,6 +1692,15 @@ int kvm_arm_timer_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
 		goto out;
 	}
 
+	/*
+	 * The PPIs for the Arch Timers arch architecturally defined for
+	 * GICv5. Reject anything that changes them from the specified value.
+	 */
+	if (vgic_is_v5(vcpu->kvm) && vcpu->kvm->arch.timer_data.ppi[idx] != irq) {
+		ret = -EINVAL;
+		goto out;
+	}
+
 	/*
 	 * We cannot validate the IRQ unicity before we run, so take it at
 	 * face value. The verdict will be given on first vcpu run, for each
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index 56cd5c05742df..cca0b5fb5a465 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -177,6 +177,15 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
 		pfr1 |= SYS_FIELD_PREP_ENUM(ID_PFR1_EL1, GIC, GICv3);
 	} else {
 		aa64pfr2 |= SYS_FIELD_PREP_ENUM(ID_AA64PFR2_EL1, GCIE, IMP);
+
+		/*
+		 * 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);
 	}
 
 	kvm_set_vm_id_reg(kvm, SYS_ID_AA64PFR0_EL1, aa64pfr0);
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index feba175a5047d..97d67c1d16541 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -197,8 +197,8 @@ int vgic_v5_finalize_ppi_state(struct kvm *kvm)
 	return 0;
 }
 
-static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
-					  struct vgic_irq *irq)
+bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
+				   struct vgic_irq *irq)
 {
 	struct vgic_v5_cpu_if *cpu_if;
 	const u64 id_bit = BIT_ULL(irq->intid % 64);
@@ -227,8 +227,8 @@ static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
  * save/restore, but don't need the PPIs to be queued on a per-VCPU AP
  * list. Therefore, sanity check the state, unlock, and return.
  */
-static bool vgic_v5_ppi_queue_irq_unlock(struct kvm *kvm, struct vgic_irq *irq,
-					 unsigned long flags)
+bool vgic_v5_ppi_queue_irq_unlock(struct kvm *kvm, struct vgic_irq *irq,
+				  unsigned long flags)
 	__releases(&irq->irq_lock)
 {
 	struct kvm_vcpu *vcpu;
diff --git a/include/kvm/arm_arch_timer.h b/include/kvm/arm_arch_timer.h
index 7310841f45121..6cb9c20f9db65 100644
--- a/include/kvm/arm_arch_timer.h
+++ b/include/kvm/arm_arch_timer.h
@@ -10,6 +10,8 @@
 #include <linux/clocksource.h>
 #include <linux/hrtimer.h>
 
+#include <linux/irqchip/arm-gic-v5.h>
+
 enum kvm_arch_timers {
 	TIMER_PTIMER,
 	TIMER_VTIMER,
@@ -47,7 +49,7 @@ struct arch_timer_vm_data {
 	u64	poffset;
 
 	/* The PPI for each timer, global to the VM */
-	u8	ppi[NR_KVM_TIMERS];
+	u32	ppi[NR_KVM_TIMERS];
 };
 
 struct arch_timer_context {
@@ -130,6 +132,9 @@ void kvm_timer_init_vhe(void);
 #define timer_vm_data(ctx)		(&(timer_context_to_vcpu(ctx)->kvm->arch.timer_data))
 #define timer_irq(ctx)			(timer_vm_data(ctx)->ppi[arch_timer_ctx_index(ctx)])
 
+#define get_vgic_ppi(k, i) (((k)->arch.vgic.vgic_model != KVM_DEV_TYPE_ARM_VGIC_V5) ? \
+				(i) : ((i) | FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI)))
+
 u64 kvm_arm_timer_read_sysreg(struct kvm_vcpu *vcpu,
 			      enum kvm_arch_timers tmr,
 			      enum kvm_arch_timer_regs treg);
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 696e2316f1ea9..22f979b561054 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -601,6 +601,10 @@ void vgic_v4_commit(struct kvm_vcpu *vcpu);
 int vgic_v4_put(struct kvm_vcpu *vcpu);
 
 int vgic_v5_finalize_ppi_state(struct kvm *kvm);
+bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
+				   struct vgic_irq *irq);
+bool vgic_v5_ppi_queue_irq_unlock(struct kvm *kvm, struct vgic_irq *irq,
+				  unsigned long flags);
 
 bool vgic_state_is_nested(struct kvm_vcpu *vcpu);
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 29/36] KVM: arm64: gic-v5: Hide FEAT_GCIE from NV GICv5 guests
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (28 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 31/36] KVM: arm64: gic-v5: Set ICH_VCTLR_EL2.En on boot Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 16:13   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 30/36] KVM: arm64: gic-v5: Introduce kvm_arm_vgic_v5_ops and register them Sascha Bischoff
                   ` (6 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

Currently, NV guests are not supported with GICv5. Therefore, make
sure that FEAT_GCIE is always hidden from such guests.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/kvm/nested.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c
index cdeeb8f09e722..66404d48405e7 100644
--- a/arch/arm64/kvm/nested.c
+++ b/arch/arm64/kvm/nested.c
@@ -1547,6 +1547,11 @@ u64 limit_nv_id_reg(struct kvm *kvm, u32 reg, u64 val)
 			 ID_AA64PFR1_EL1_MTE);
 		break;
 
+	case SYS_ID_AA64PFR2_EL1:
+		/* GICv5 is not yet supported for NV */
+		val &= ~ID_AA64PFR2_EL1_GCIE;
+		break;
+
 	case SYS_ID_AA64MMFR0_EL1:
 		/* Hide ExS, Secure Memory */
 		val &= ~(ID_AA64MMFR0_EL1_EXS		|
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 30/36] KVM: arm64: gic-v5: Introduce kvm_arm_vgic_v5_ops and register them
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (29 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 29/36] KVM: arm64: gic-v5: Hide FEAT_GCIE from NV GICv5 guests Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 16:19   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 33/36] KVM: arm64: gic-v5: Probe for GICv5 device Sascha Bischoff
                   ` (5 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

Only the KVM_DEV_ARM_VGIC_GRP_CTRL->KVM_DEV_ARM_VGIC_CTRL_INIT op is
currently supported. All other ops are stubbed out.

Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/kvm/vgic/vgic-kvm-device.c | 72 +++++++++++++++++++++++++++
 include/linux/kvm_host.h              |  1 +
 2 files changed, 73 insertions(+)

diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c
index b12ba99a423e5..78903182bba08 100644
--- a/arch/arm64/kvm/vgic/vgic-kvm-device.c
+++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c
@@ -336,6 +336,9 @@ int kvm_register_vgic_device(unsigned long type)
 			break;
 		ret = kvm_vgic_register_its_device();
 		break;
+	case KVM_DEV_TYPE_ARM_VGIC_V5:
+		ret = kvm_register_device_ops(&kvm_arm_vgic_v5_ops,
+					      KVM_DEV_TYPE_ARM_VGIC_V5);
 	}
 
 	return ret;
@@ -715,3 +718,72 @@ struct kvm_device_ops kvm_arm_vgic_v3_ops = {
 	.get_attr = vgic_v3_get_attr,
 	.has_attr = vgic_v3_has_attr,
 };
+
+static int vgic_v5_set_attr(struct kvm_device *dev,
+			    struct kvm_device_attr *attr)
+{
+	switch (attr->group) {
+	case KVM_DEV_ARM_VGIC_GRP_ADDR:
+	case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
+	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);
+		default:
+			break;
+		}
+	}
+
+	return -ENXIO;
+}
+
+static int vgic_v5_get_attr(struct kvm_device *dev,
+			    struct kvm_device_attr *attr)
+{
+	switch (attr->group) {
+	case KVM_DEV_ARM_VGIC_GRP_ADDR:
+	case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
+	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);
+		default:
+			break;
+		}
+	}
+
+	return -ENXIO;
+}
+
+static int vgic_v5_has_attr(struct kvm_device *dev,
+			    struct kvm_device_attr *attr)
+{
+	switch (attr->group) {
+	case KVM_DEV_ARM_VGIC_GRP_ADDR:
+	case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
+	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 0;
+		default:
+			break;
+		}
+	}
+
+	return -ENXIO;
+}
+
+struct kvm_device_ops kvm_arm_vgic_v5_ops = {
+	.name = "kvm-arm-vgic-v5",
+	.create = vgic_create,
+	.destroy = vgic_destroy,
+	.set_attr = vgic_v5_set_attr,
+	.get_attr = vgic_v5_get_attr,
+	.has_attr = vgic_v5_has_attr,
+};
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index d93f75b05ae22..d6082f06ccae3 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -2368,6 +2368,7 @@ void kvm_unregister_device_ops(u32 type);
 extern struct kvm_device_ops kvm_mpic_ops;
 extern struct kvm_device_ops kvm_arm_vgic_v2_ops;
 extern struct kvm_device_ops kvm_arm_vgic_v3_ops;
+extern struct kvm_device_ops kvm_arm_vgic_v5_ops;
 
 #ifdef CONFIG_HAVE_KVM_CPU_RELAX_INTERCEPT
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 31/36] KVM: arm64: gic-v5: Set ICH_VCTLR_EL2.En on boot
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (27 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 26/36] KVM: arm64: gic-v5: Bump arch timer for GICv5 Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2025-12-19 15:52 ` [PATCH v2 29/36] KVM: arm64: gic-v5: Hide FEAT_GCIE from NV GICv5 guests Sascha Bischoff
                   ` (7 subsequent siblings)
  36 siblings, 0 replies; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

This control enables virtual HPPI selection, i.e., selection and
delivery of interrupts for a guest (assuming that the guest itself has
opted to receive interrupts). This is set to enabled on boot as there
is no reason for disabling it in normal operation as virtual interrupt
signalling itself is still controlled via the HCR_EL2.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/include/asm/el2_setup.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/arch/arm64/include/asm/el2_setup.h b/arch/arm64/include/asm/el2_setup.h
index 07c12f4a69b41..e7e39117c79e5 100644
--- a/arch/arm64/include/asm/el2_setup.h
+++ b/arch/arm64/include/asm/el2_setup.h
@@ -238,6 +238,8 @@
 		     ICH_HFGWTR_EL2_ICC_CR0_EL1			| \
 		     ICH_HFGWTR_EL2_ICC_APR_EL1)
 	msr_s	SYS_ICH_HFGWTR_EL2, x0		// Disable reg write traps
+	mov	x0, #(ICH_VCTLR_EL2_En)
+	msr_s	SYS_ICH_VCTLR_EL2, x0		// Enable vHPPI selection
 .Lskip_gicv5_\@:
 .endm
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 32/36] irqchip/gic-v5: Check if impl is virt capable
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (32 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 34/36] Documentation: KVM: Introduce documentation for VGICv5 Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 16:21   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 36/36] KVM: arm64: gic-v5: Communicate userspace-drivable PPIs via a UAPI Sascha Bischoff
                   ` (2 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

Now that there is support for creating a GICv5-based guest with KVM,
check that the hardware itself supports virtualisation, skipping the
setting of struct gic_kvm_info if not.

Note: If native GICv5 virt is not supported, then nor is
FEAT_GCIE_LEGACY, so we are able to skip altogether.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Reviewed-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
---
 drivers/irqchip/irq-gic-v5-irs.c   |  4 ++++
 drivers/irqchip/irq-gic-v5.c       | 10 ++++++++++
 include/linux/irqchip/arm-gic-v5.h |  4 ++++
 3 files changed, 18 insertions(+)

diff --git a/drivers/irqchip/irq-gic-v5-irs.c b/drivers/irqchip/irq-gic-v5-irs.c
index ce2732d649a3e..eebf9f219ac8c 100644
--- a/drivers/irqchip/irq-gic-v5-irs.c
+++ b/drivers/irqchip/irq-gic-v5-irs.c
@@ -744,6 +744,10 @@ static int __init gicv5_irs_init(struct device_node *node)
 	 */
 	if (list_empty(&irs_nodes)) {
 
+		idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR0);
+		gicv5_global_data.virt_capable =
+			!!FIELD_GET(GICV5_IRS_IDR0_VIRT, idr);
+
 		idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR1);
 		irs_setup_pri_bits(idr);
 
diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
index 41ef286c4d781..3c86bbc057615 100644
--- a/drivers/irqchip/irq-gic-v5.c
+++ b/drivers/irqchip/irq-gic-v5.c
@@ -1064,6 +1064,16 @@ static struct gic_kvm_info gic_v5_kvm_info __initdata;
 
 static void __init gic_of_setup_kvm_info(struct device_node *node)
 {
+	/*
+	 * If we don't have native GICv5 virtualisation support, then
+	 * we also don't have FEAT_GCIE_LEGACY - the architecture
+	 * forbids this combination.
+	 */
+	if (!gicv5_global_data.virt_capable) {
+		pr_info("GIC implementation is not virtualization capable\n");
+		return;
+	}
+
 	gic_v5_kvm_info.type = GIC_V5;
 
 	/* GIC Virtual CPU interface maintenance interrupt */
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index 2ab7ec718aaea..06ebb2a1cfb1d 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -45,6 +45,7 @@
 /*
  * IRS registers and tables structures
  */
+#define GICV5_IRS_IDR0			0x0000
 #define GICV5_IRS_IDR1			0x0004
 #define GICV5_IRS_IDR2			0x0008
 #define GICV5_IRS_IDR5			0x0014
@@ -65,6 +66,8 @@
 #define GICV5_IRS_IST_STATUSR		0x0194
 #define GICV5_IRS_MAP_L2_ISTR		0x01c0
 
+#define GICV5_IRS_IDR0_VIRT		BIT(6)
+
 #define GICV5_IRS_IDR1_PRIORITY_BITS	GENMASK(22, 20)
 #define GICV5_IRS_IDR1_IAFFID_BITS	GENMASK(19, 16)
 
@@ -280,6 +283,7 @@ struct gicv5_chip_data {
 	u8			cpuif_pri_bits;
 	u8			cpuif_id_bits;
 	u8			irs_pri_bits;
+	bool			virt_capable;
 	struct {
 		__le64 *l1ist_addr;
 		u32 l2_size;
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 34/36] Documentation: KVM: Introduce documentation for VGICv5
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (31 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 33/36] KVM: arm64: gic-v5: Probe for GICv5 device Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 16:27   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 32/36] irqchip/gic-v5: Check if impl is virt capable Sascha Bischoff
                   ` (3 subsequent siblings)
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

Now that it is possible to create a VGICv5 device, provide initial
documentation for it. At this stage, there is little to document.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 .../virt/kvm/devices/arm-vgic-v5.rst          | 37 +++++++++++++++++++
 Documentation/virt/kvm/devices/index.rst      |  1 +
 2 files changed, 38 insertions(+)
 create mode 100644 Documentation/virt/kvm/devices/arm-vgic-v5.rst

diff --git a/Documentation/virt/kvm/devices/arm-vgic-v5.rst b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
new file mode 100644
index 0000000000000..9904cb888277d
--- /dev/null
+++ b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
@@ -0,0 +1,37 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+====================================================
+ARM Virtual Generic Interrupt Controller v5 (VGICv5)
+====================================================
+
+
+Device types supported:
+  - KVM_DEV_TYPE_ARM_VGIC_V5     ARM Generic Interrupt Controller v5.0
+
+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 host 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.
+
+Groups:
+  KVM_DEV_ARM_VGIC_GRP_CTRL
+   Attributes:
+
+    KVM_DEV_ARM_VGIC_CTRL_INIT
+      request the initialization of the VGIC, no additional parameter in
+      kvm_device_attr.addr. Must be called after all VCPUs have been created.
+
+  Errors:
+
+    =======  ========================================================
+    -ENXIO   VGIC not properly configured as required prior to calling
+             this attribute
+    -ENODEV  no online VCPU
+    -ENOMEM  memory shortage when allocating vgic internal data
+    -EFAULT  Invalid guest ram access
+    -EBUSY   One or more VCPUS are running
+    =======  ========================================================
diff --git a/Documentation/virt/kvm/devices/index.rst b/Documentation/virt/kvm/devices/index.rst
index 192cda7405c84..70845aba38f45 100644
--- a/Documentation/virt/kvm/devices/index.rst
+++ b/Documentation/virt/kvm/devices/index.rst
@@ -10,6 +10,7 @@ Devices
    arm-vgic-its
    arm-vgic
    arm-vgic-v3
+   arm-vgic-v5
    mpic
    s390_flic
    vcpu
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 33/36] KVM: arm64: gic-v5: Probe for GICv5 device
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (30 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 30/36] KVM: arm64: gic-v5: Introduce kvm_arm_vgic_v5_ops and register them Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 16:25   ` Jonathan Cameron
  2026-01-09 15:00   ` Joey Gouly
  2025-12-19 15:52 ` [PATCH v2 34/36] Documentation: KVM: Introduce documentation for VGICv5 Sascha Bischoff
                   ` (4 subsequent siblings)
  36 siblings, 2 replies; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

The basic GICv5 PPI support is now complete. Allow probing for a
native GICv5 rather than just the legacy support.

The implementation doesn't support protected VMs with GICv5 at this
time. Therefore, if KVM has protected mode enabled the native GICv5
init is skipped, but legacy VMs are allowed if the hardware supports
it.

At this stage the GICv5 KVM implementation only supports PPIs, and
doesn't interact with the host IRS at all. This means that there is no
need to check how many concurrent VMs or vCPUs per VM are supported by
the IRS - the PPI support only requires the CPUIF. The support is
artificially limited to VGIC_V5_MAX_CPUS, i.e. 512, vCPUs per VM.

With this change it becomes possible to run basic GICv5-based VMs,
provided that they only use PPIs.

Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 arch/arm64/kvm/vgic/vgic-v5.c | 39 +++++++++++++++++++++++++++--------
 1 file changed, 30 insertions(+), 9 deletions(-)

diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 97d67c1d16541..bf72982d6a2e8 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -12,22 +12,13 @@ static struct vgic_v5_ppi_caps *ppi_caps;
 
 /*
  * Probe for a vGICv5 compatible interrupt controller, returning 0 on success.
- * Currently only supports GICv3-based VMs on a GICv5 host, and hence only
- * registers a VGIC_V3 device.
  */
 int vgic_v5_probe(const struct gic_kvm_info *info)
 {
 	u64 ich_vtr_el2;
 	int ret;
 
-	if (!cpus_have_final_cap(ARM64_HAS_GICV5_LEGACY))
-		return -ENODEV;
-
 	kvm_vgic_global_state.type = VGIC_V5;
-	kvm_vgic_global_state.has_gcie_v3_compat = true;
-
-	/* We only support v3 compat mode - use vGICv3 limits */
-	kvm_vgic_global_state.max_gic_vcpus = VGIC_V3_MAX_CPUS;
 
 	kvm_vgic_global_state.vcpu_base = 0;
 	kvm_vgic_global_state.vctrl_base = NULL;
@@ -35,6 +26,32 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
 	kvm_vgic_global_state.has_gicv4 = false;
 	kvm_vgic_global_state.has_gicv4_1 = false;
 
+	/*
+	 * GICv5 is currently not supported in Protected mode. Skip the
+	 * registration of GICv5 completely to make sure no guests can create a
+	 * GICv5-based guest.
+	 */
+	if (is_protected_kvm_enabled()) {
+		kvm_info("GICv5-based guests are not supported with pKVM\n");
+		goto skip_v5;
+	}
+
+	kvm_vgic_global_state.max_gic_vcpus = 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");
+		goto skip_v5;
+	}
+
+	kvm_info("GCIE system register CPU interface\n");
+
+skip_v5:
+	/* If we don't support the GICv3 compat mode we're done. */
+	if (!cpus_have_final_cap(ARM64_HAS_GICV5_LEGACY))
+		return 0;
+
+	kvm_vgic_global_state.has_gcie_v3_compat = true;
 	ich_vtr_el2 =  kvm_call_hyp_ret(__vgic_v3_get_gic_config);
 	kvm_vgic_global_state.ich_vtr_el2 = (u32)ich_vtr_el2;
 
@@ -50,6 +67,10 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
 		return 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);
+
 	static_branch_enable(&kvm_vgic_global_state.gicv3_cpuif);
 	kvm_info("GCIE legacy system register CPU interface\n");
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 35/36] KVM: arm64: selftests: Introduce a minimal GICv5 PPI selftest
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (34 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 36/36] KVM: arm64: gic-v5: Communicate userspace-drivable PPIs via a UAPI Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 16:38   ` Jonathan Cameron
  2025-12-19 16:17 ` [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

This basic selftest creates a vgic_v5 device (if supported), and tests
that one of the PPI interrupts works as expected with a basic
single-vCPU guest.

Upon starting, the guest enables interrupts. That means that it is
initialising all PPIs to have reasonable priorities, but marking them
as disabled. Then the priority mask in the ICC_PCR_EL1 is set, and
interrupts are enable in ICC_CR0_EL1. At this stage the guest is able
to recieve interrupts. The first IMPDEF PPI (64) is enabled and
kvm_irq_line is used to inject the state into the guest.

The guest's interrupt handler has an explicit WFI in order to ensure
that the guest skips WFI when there are pending and enabled PPI
interrupts.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 tools/testing/selftests/kvm/arm64/vgic_v5.c   | 248 ++++++++++++++++++
 .../selftests/kvm/include/arm64/gic_v5.h      | 148 +++++++++++
 3 files changed, 397 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/arm64/vgic_v5.c
 create mode 100644 tools/testing/selftests/kvm/include/arm64/gic_v5.h

diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index ba5c2b643efaa..5c325b8a0766d 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -173,6 +173,7 @@ TEST_GEN_PROGS_arm64 += arm64/vcpu_width_config
 TEST_GEN_PROGS_arm64 += arm64/vgic_init
 TEST_GEN_PROGS_arm64 += arm64/vgic_irq
 TEST_GEN_PROGS_arm64 += arm64/vgic_lpi_stress
+TEST_GEN_PROGS_arm64 += arm64/vgic_v5
 TEST_GEN_PROGS_arm64 += arm64/vpmu_counter_access
 TEST_GEN_PROGS_arm64 += arm64/no-vgic-v3
 TEST_GEN_PROGS_arm64 += arm64/kvm-uuid
diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
new file mode 100644
index 0000000000000..5879fbd71042d
--- /dev/null
+++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
@@ -0,0 +1,248 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/kernel.h>
+#include <sys/syscall.h>
+#include <asm/kvm.h>
+#include <asm/kvm_para.h>
+
+#include <arm64/gic_v5.h>
+
+#include "test_util.h"
+#include "kvm_util.h"
+#include "processor.h"
+#include "vgic.h"
+
+#define NR_VCPUS		1
+
+struct vm_gic {
+	struct kvm_vm *vm;
+	int gic_fd;
+	uint32_t gic_dev_type;
+};
+
+static uint64_t max_phys_size;
+
+#define GUEST_CMD_IRQ_CDIA	10
+#define GUEST_CMD_IRQ_DIEOI	11
+#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_GIC_CDIA_INTID, ia);
+
+	gic_insn(hwirq, CDDI);
+	gic_insn(0, CDEOI);
+
+	++count;
+	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 PPI 64 */
+	write_sysreg_s(1UL, SYS_ICC_PPI_ENABLER1_EL1);
+
+	/* Ask for the first interrupt to be injected */
+	GUEST_SYNC(GUEST_CMD_IS_READY);
+
+	/* Loop forever waiting for interrupts */
+	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(uint32_t gic_dev_type)
+{
+	struct ucall uc;
+	struct kvm_vcpu *vcpus[NR_VCPUS];
+	struct vm_gic v;
+	int ret, i;
+
+	v.gic_dev_type = gic_dev_type;
+	v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
+
+	v.gic_fd = kvm_create_device(v.vm, gic_dev_type);
+
+	for (i = 0; i < NR_VCPUS; ++i)
+		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);
+
+	for (i = 0; i < NR_VCPUS; i++)
+		vcpu_init_descriptor_tables(vcpus[i]);
+
+	kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+			    KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+	while (1) {
+		ret = run_vcpu(vcpus[0]);
+
+		switch (get_ucall(vcpus[0], &uc)) {
+		case UCALL_SYNC:
+			/*
+			 * The guest is ready for the next level
+			 * change. Set high if ready, and lower if it
+			 * has been consumed.
+			 */
+			if (uc.args[1] == GUEST_CMD_IS_READY ||
+			    uc.args[1] == GUEST_CMD_IRQ_DIEOI) {
+				u64 irq = 64;
+				bool level = uc.args[1] == GUEST_CMD_IRQ_DIEOI ? 0 : 1;
+
+				irq &= KVM_ARM_IRQ_NUM_MASK;
+				irq |= KVM_ARM_IRQ_TYPE_PPI << KVM_ARM_IRQ_TYPE_SHIFT;
+
+				_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");
+			} else if (uc.args[1] == GUEST_CMD_IRQ_CDIA) {
+				pr_info("Guest acknowledged IRQ\n");
+			}
+
+			continue;
+		case UCALL_ABORT:
+			REPORT_GUEST_ASSERT(uc);
+			break;
+		case UCALL_DONE:
+			goto done;
+		default:
+			TEST_FAIL("Unknown ucall %lu", uc.cmd);
+		}
+	}
+
+done:
+	TEST_ASSERT(ret == 0, "Failed to test GICv5 PPIs");
+
+	vm_gic_destroy(&v);
+}
+
+/*
+ * Returns 0 if it's possible to create GIC device of a given type (V2 or V3).
+ */
+int test_kvm_device(uint32_t gic_dev_type)
+{
+	struct kvm_vcpu *vcpus[NR_VCPUS];
+	struct vm_gic v;
+	uint32_t other;
+	int ret;
+
+	v.vm = vm_create_with_vcpus(NR_VCPUS, guest_code, vcpus);
+
+	/* try to create a non existing KVM device */
+	ret = __kvm_test_create_device(v.vm, 0);
+	TEST_ASSERT(ret && errno == ENODEV, "unsupported device");
+
+	/* trial mode */
+	ret = __kvm_test_create_device(v.vm, gic_dev_type);
+	if (ret)
+		return ret;
+	v.gic_fd = kvm_create_device(v.vm, gic_dev_type);
+
+	ret = __kvm_create_device(v.vm, gic_dev_type);
+	TEST_ASSERT(ret < 0 && errno == EEXIST, "create GIC device twice");
+
+	/* try to create the other gic_dev_types */
+	other = KVM_DEV_TYPE_ARM_VGIC_V2;
+	if (!__kvm_test_create_device(v.vm, other)) {
+		ret = __kvm_create_device(v.vm, other);
+		TEST_ASSERT(ret < 0 && (errno == EINVAL || errno == EEXIST),
+				"create GIC device while other version exists");
+	}
+
+	other = KVM_DEV_TYPE_ARM_VGIC_V3;
+	if (!__kvm_test_create_device(v.vm, other)) {
+		ret = __kvm_create_device(v.vm, other);
+		TEST_ASSERT(ret < 0 && (errno == EINVAL || errno == EEXIST),
+				"create GIC device while other version exists");
+	}
+
+	other = KVM_DEV_TYPE_ARM_VGIC_V5;
+	if (!__kvm_test_create_device(v.vm, other)) {
+		ret = __kvm_create_device(v.vm, other);
+		TEST_ASSERT(ret < 0 && (errno == EINVAL || errno == EEXIST),
+				"create GIC device while other version exists");
+	}
+
+	vm_gic_destroy(&v);
+
+	return 0;
+}
+
+void run_tests(uint32_t gic_dev_type)
+{
+	pr_info("Test VGICv5 PPIs\n");
+	test_vgic_v5_ppis(gic_dev_type);
+}
+
+int main(int ac, char **av)
+{
+	int ret;
+	int pa_bits;
+	int cnt_impl = 0;
+
+	test_disable_default_vgic();
+
+	pa_bits = vm_guest_mode_params[VM_MODE_DEFAULT].pa_bits;
+	max_phys_size = 1ULL << pa_bits;
+
+	ret = test_kvm_device(KVM_DEV_TYPE_ARM_VGIC_V5);
+	if (!ret) {
+		pr_info("Running VGIC_V5 tests.\n");
+		run_tests(KVM_DEV_TYPE_ARM_VGIC_V5);
+		cnt_impl++;
+	} else {
+		pr_info("No GICv5 support; Not running GIC_v5 tests.\n");
+		exit(KSFT_SKIP);
+	}
+
+	return 0;
+}
+
+
diff --git a/tools/testing/selftests/kvm/include/arm64/gic_v5.h b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
new file mode 100644
index 0000000000000..5daaa84318bb1
--- /dev/null
+++ b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
@@ -0,0 +1,148 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef __SELFTESTS_GIC_V5_H
+#define __SELFTESTS_GIC_V5_H
+
+#include <asm/barrier.h>
+#include <asm/sysreg.h>
+
+#include <linux/bitfield.h>
+
+#include "processor.h"
+
+/*
+ * 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)
+#define GICV5_OP_GIC_CDDIS		sys_insn(1, 0, 12, 1, 0)
+#define GICV5_OP_GIC_CDHM		sys_insn(1, 0, 12, 2, 1)
+#define GICV5_OP_GIC_CDEN		sys_insn(1, 0, 12, 1, 1)
+#define GICV5_OP_GIC_CDEOI		sys_insn(1, 0, 12, 1, 7)
+#define GICV5_OP_GIC_CDPEND		sys_insn(1, 0, 12, 1, 4)
+#define GICV5_OP_GIC_CDPRI		sys_insn(1, 0, 12, 1, 2)
+#define GICV5_OP_GIC_CDRCFG		sys_insn(1, 0, 12, 1, 5)
+#define GICV5_OP_GICR_CDIA		sys_insn(1, 0, 12, 3, 0)
+#define GICV5_OP_GICR_CDNMIA		sys_insn(1, 0, 12, 3, 1)
+
+/* Definitions for GIC CDAFF */
+#define GICV5_GIC_CDAFF_IAFFID_MASK	GENMASK_ULL(47, 32)
+#define GICV5_GIC_CDAFF_TYPE_MASK	GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDAFF_IRM_MASK	BIT_ULL(28)
+#define GICV5_GIC_CDAFF_ID_MASK		GENMASK_ULL(23, 0)
+
+/* Definitions for GIC CDDI */
+#define GICV5_GIC_CDDI_TYPE_MASK	GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDDI_ID_MASK		GENMASK_ULL(23, 0)
+
+/* Definitions for GIC CDDIS */
+#define GICV5_GIC_CDDIS_TYPE_MASK	GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDDIS_TYPE(r)		FIELD_GET(GICV5_GIC_CDDIS_TYPE_MASK, r)
+#define GICV5_GIC_CDDIS_ID_MASK		GENMASK_ULL(23, 0)
+#define GICV5_GIC_CDDIS_ID(r)		FIELD_GET(GICV5_GIC_CDDIS_ID_MASK, r)
+
+/* Definitions for GIC CDEN */
+#define GICV5_GIC_CDEN_TYPE_MASK	GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDEN_ID_MASK		GENMASK_ULL(23, 0)
+
+/* Definitions for GIC CDHM */
+#define GICV5_GIC_CDHM_HM_MASK		BIT_ULL(32)
+#define GICV5_GIC_CDHM_TYPE_MASK	GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDHM_ID_MASK		GENMASK_ULL(23, 0)
+
+/* Definitions for GIC CDPEND */
+#define GICV5_GIC_CDPEND_PENDING_MASK	BIT_ULL(32)
+#define GICV5_GIC_CDPEND_TYPE_MASK	GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDPEND_ID_MASK	GENMASK_ULL(23, 0)
+
+/* Definitions for GIC CDPRI */
+#define GICV5_GIC_CDPRI_PRIORITY_MASK	GENMASK_ULL(39, 35)
+#define GICV5_GIC_CDPRI_TYPE_MASK	GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDPRI_ID_MASK		GENMASK_ULL(23, 0)
+
+/* Definitions for GIC CDRCFG */
+#define GICV5_GIC_CDRCFG_TYPE_MASK	GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDRCFG_ID_MASK	GENMASK_ULL(23, 0)
+
+/* Definitions for GICR CDIA */
+#define GICV5_GIC_CDIA_VALID_MASK	BIT_ULL(32)
+#define GICV5_GICR_CDIA_VALID(r)	FIELD_GET(GICV5_GIC_CDIA_VALID_MASK, r)
+#define GICV5_GIC_CDIA_TYPE_MASK	GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDIA_ID_MASK		GENMASK_ULL(23, 0)
+#define GICV5_GIC_CDIA_INTID		GENMASK_ULL(31, 0)
+
+/* Definitions for GICR CDNMIA */
+#define GICV5_GIC_CDNMIA_VALID_MASK	BIT_ULL(32)
+#define GICV5_GICR_CDNMIA_VALID(r)	FIELD_GET(GICV5_GIC_CDNMIA_VALID_MASK, r)
+#define GICV5_GIC_CDNMIA_TYPE_MASK	GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDNMIA_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)
+
+#define __GIC_BARRIER_INSN(op0, op1, CRn, CRm, op2, Rt)			\
+	__emit_inst(0xd5000000					|	\
+		    sys_insn((op0), (op1), (CRn), (CRm), (op2))	|	\
+		    ((Rt) & 0x1f))
+
+#define GSB_SYS_BARRIER_INSN		__GIC_BARRIER_INSN(1, 0, 12, 0, 0, 31)
+#define GSB_ACK_BARRIER_INSN		__GIC_BARRIER_INSN(1, 0, 12, 0, 1, 31)
+
+#define gsb_ack()	asm volatile(GSB_ACK_BARRIER_INSN : : : "memory")
+#define gsb_sys()	asm volatile(GSB_SYS_BARRIER_INSN : : : "memory")
+
+#define REPEAT_BYTE(x)	((~0ul / 0xff) * (x))
+
+#define GICV5_IRQ_DEFAULT_PRI 0b10000
+
+void gicv5_ppi_priority_init(void)
+{
+	write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR0_EL1);
+	write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR1_EL1);
+	write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR2_EL1);
+	write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR3_EL1);
+	write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR4_EL1);
+	write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR5_EL1);
+	write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR6_EL1);
+	write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR7_EL1);
+	write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR8_EL1);
+	write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR9_EL1);
+	write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR10_EL1);
+	write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR11_EL1);
+	write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR12_EL1);
+	write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR13_EL1);
+	write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR14_EL1);
+	write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR15_EL1);
+
+	/*
+	 * Context syncronization required to make sure system register writes
+	 * effects are synchronised.
+	 */
+	isb();
+}
+
+void gicv5_cpu_disable_interrupts(void)
+{
+	u64 cr0;
+
+	cr0 = FIELD_PREP(ICC_CR0_EL1_EN, 0);
+	write_sysreg_s(cr0, SYS_ICC_CR0_EL1);
+}
+
+void gicv5_cpu_enable_interrupts(void)
+{
+	u64 cr0, pcr;
+
+	write_sysreg_s(0, SYS_ICC_PPI_ENABLER0_EL1);
+	write_sysreg_s(0, SYS_ICC_PPI_ENABLER1_EL1);
+
+	gicv5_ppi_priority_init();
+
+	pcr = FIELD_PREP(ICC_PCR_EL1_PRIORITY, GICV5_IRQ_DEFAULT_PRI);
+	write_sysreg_s(pcr, SYS_ICC_PCR_EL1);
+
+	cr0 = FIELD_PREP(ICC_CR0_EL1_EN, 1);
+	write_sysreg_s(cr0, SYS_ICC_CR0_EL1);
+}
+
+#endif
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* [PATCH v2 36/36] KVM: arm64: gic-v5: Communicate userspace-drivable PPIs via a UAPI
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (33 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 32/36] irqchip/gic-v5: Check if impl is virt capable Sascha Bischoff
@ 2025-12-19 15:52 ` Sascha Bischoff
  2026-01-07 16:51   ` Jonathan Cameron
  2025-12-19 15:52 ` [PATCH v2 35/36] KVM: arm64: selftests: Introduce a minimal GICv5 PPI selftest Sascha Bischoff
  2025-12-19 16:17 ` [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
  36 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 15:52 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

GICv5 systems will likely not support the full set of PPIs. The
presence of any virtual PPI is tied to the presence of the physical
PPI. Therefore, the available PPIs will be limited by the physical
host. Userspace cannot drive any PPIs that are not implemented.

Moreover, it is not desirable to expose all PPIs to the guest in the
first place, even if they are supported in hardware. Some devices,
such as the arch timer, are implemented in KVM, and hence those PPIs
shouldn't be driven by userspace, either.

Provided a new UAPI:
  KVM_DEV_ARM_VGIC_GRP_CTRL => KVM_DEV_ARM_VGIC_USERPSPACE_PPIs

This allows userspace to query which PPIs it is able to drive via
KVM_IRQ_LINE.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
 .../virt/kvm/devices/arm-vgic-v5.rst          | 13 ++++++++++
 arch/arm64/include/uapi/asm/kvm.h             |  1 +
 arch/arm64/kvm/vgic/vgic-kvm-device.c         | 25 +++++++++++++++++++
 arch/arm64/kvm/vgic/vgic-v5.c                 |  8 ++++++
 include/kvm/arm_vgic.h                        |  5 ++++
 include/linux/irqchip/arm-gic-v5.h            |  4 +++
 tools/arch/arm64/include/uapi/asm/kvm.h       |  1 +
 7 files changed, 57 insertions(+)

diff --git a/Documentation/virt/kvm/devices/arm-vgic-v5.rst b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
index 9904cb888277d..d9f2917b609c5 100644
--- a/Documentation/virt/kvm/devices/arm-vgic-v5.rst
+++ b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
@@ -25,6 +25,19 @@ Groups:
       request the initialization of the VGIC, no additional parameter in
       kvm_device_attr.addr. Must be called after all VCPUs have been created.
 
+   KVM_DEV_ARM_VGIC_USERPSPACE_PPIs
+      request the mask of userspace-drivable PPIs. Only a subset of the PPIs can
+      be directly driven from userspace with GICv5, and the returned mask
+      informs userspace of which it is allowed to drive via KVM_IRQ_LINE.
+
+      Userspace must allocate and point to __u64[2] with of data in
+      kvm_device_attr.addr. When this call returns, the provided memory will be
+      populated with the userspace PPI mask. The lower __u64 contains the mask
+      for the lower 64 PPIS, with the remaining 64 being in the second __u64.
+
+      This is a read-only attribute, and cannot be set. Attempts to set it are
+      rejected.
+
   Errors:
 
     =======  ========================================================
diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
index a792a599b9d68..1c13bfa2d38aa 100644
--- a/arch/arm64/include/uapi/asm/kvm.h
+++ b/arch/arm64/include/uapi/asm/kvm.h
@@ -428,6 +428,7 @@ enum {
 #define   KVM_DEV_ARM_ITS_RESTORE_TABLES        2
 #define   KVM_DEV_ARM_VGIC_SAVE_PENDING_TABLES	3
 #define   KVM_DEV_ARM_ITS_CTRL_RESET		4
+#define   KVM_DEV_ARM_VGIC_USERSPACE_PPIS	5
 
 /* Device Control API on vcpu fd */
 #define KVM_ARM_VCPU_PMU_V3_CTRL	0
diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c
index 78903182bba08..360c78ed4f104 100644
--- a/arch/arm64/kvm/vgic/vgic-kvm-device.c
+++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c
@@ -719,6 +719,25 @@ struct kvm_device_ops kvm_arm_vgic_v3_ops = {
 	.has_attr = vgic_v3_has_attr,
 };
 
+static int vgic_v5_get_userspace_ppis(struct kvm_device *dev,
+				      struct kvm_device_attr *attr)
+{
+	u64 __user *uaddr = (u64 __user *)(long)attr->addr;
+	struct gicv5_vm *gicv5_vm = &dev->kvm->arch.vgic.gicv5_vm;
+	int i, ret;
+
+	guard(mutex)(&dev->kvm->arch.config_lock);
+
+	for (i = 0; i < 2; ++i) {
+		ret = put_user(gicv5_vm->userspace_ppis[i], uaddr);
+		if (ret)
+			return ret;
+		uaddr++;
+	}
+
+	return 0;
+}
+
 static int vgic_v5_set_attr(struct kvm_device *dev,
 			    struct kvm_device_attr *attr)
 {
@@ -731,6 +750,8 @@ static int vgic_v5_set_attr(struct kvm_device *dev,
 		switch (attr->attr) {
 		case KVM_DEV_ARM_VGIC_CTRL_INIT:
 			return  vgic_set_common_attr(dev, attr);
+		case KVM_DEV_ARM_VGIC_USERSPACE_PPIS:
+			break;
 		default:
 			break;
 		}
@@ -751,6 +772,8 @@ static int vgic_v5_get_attr(struct kvm_device *dev,
 		switch (attr->attr) {
 		case KVM_DEV_ARM_VGIC_CTRL_INIT:
 			return vgic_get_common_attr(dev, attr);
+		case KVM_DEV_ARM_VGIC_USERSPACE_PPIS:
+			return vgic_v5_get_userspace_ppis(dev, attr);
 		default:
 			break;
 		}
@@ -771,6 +794,8 @@ static int vgic_v5_has_attr(struct kvm_device *dev,
 		switch (attr->attr) {
 		case KVM_DEV_ARM_VGIC_CTRL_INIT:
 			return 0;
+		case KVM_DEV_ARM_VGIC_USERSPACE_PPIS:
+			return 0;
 		default:
 			break;
 		}
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index bf72982d6a2e8..04300926683b6 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -122,6 +122,14 @@ int vgic_v5_init(struct kvm *kvm)
 		}
 	}
 
+	/*
+	 * We only allow userspace to drive the SW_PPI, if it is
+	 * implemented.
+	 */
+	kvm->arch.vgic.gicv5_vm.userspace_ppis[0] = GICV5_SW_PPI & GICV5_HWIRQ_ID;
+	kvm->arch.vgic.gicv5_vm.userspace_ppis[0] &= ppi_caps->impl_ppi_mask[0];
+	kvm->arch.vgic.gicv5_vm.userspace_ppis[1] = 0;
+
 	return 0;
 }
 
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 22f979b561054..5a7de3b74a99d 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -404,6 +404,11 @@ struct vgic_dist {
 	 * else.
 	 */
 	struct its_vm		its_vm;
+
+	/*
+	 * GICv5 per-VM data.
+	 */
+	struct gicv5_vm		gicv5_vm;
 };
 
 struct vgic_v2_cpu_if {
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index 06ebb2a1cfb1d..123c6cfc344c5 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -365,6 +365,10 @@ struct gicv5_vpe {
 	bool			resident;
 };
 
+struct gicv5_vm {
+	u64			userspace_ppis[2];
+};
+
 struct gicv5_its_devtab_cfg {
 	union {
 		struct {
diff --git a/tools/arch/arm64/include/uapi/asm/kvm.h b/tools/arch/arm64/include/uapi/asm/kvm.h
index a792a599b9d68..1c13bfa2d38aa 100644
--- a/tools/arch/arm64/include/uapi/asm/kvm.h
+++ b/tools/arch/arm64/include/uapi/asm/kvm.h
@@ -428,6 +428,7 @@ enum {
 #define   KVM_DEV_ARM_ITS_RESTORE_TABLES        2
 #define   KVM_DEV_ARM_VGIC_SAVE_PENDING_TABLES	3
 #define   KVM_DEV_ARM_ITS_CTRL_RESET		4
+#define   KVM_DEV_ARM_VGIC_USERSPACE_PPIS	5
 
 /* Device Control API on vcpu fd */
 #define KVM_ARM_VCPU_PMU_V3_CTRL	0
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support
  2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
                   ` (35 preceding siblings ...)
  2025-12-19 15:52 ` [PATCH v2 35/36] KVM: arm64: selftests: Introduce a minimal GICv5 PPI selftest Sascha Bischoff
@ 2025-12-19 16:17 ` Sascha Bischoff
  36 siblings, 0 replies; 100+ messages in thread
From: Sascha Bischoff @ 2025-12-19 16:17 UTC (permalink / raw)
  To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
	kvm@vger.kernel.org
  Cc: yuzenghui@huawei.com, Timothy Hayes, Suzuki Poulose,
	oliver.upton@linux.dev, peter.maydell@linaro.org, nd,
	maz@kernel.org, Joey Gouly, lpieralisi@kernel.org

On Fri, 2025-12-19 at 15:51 +0000, Sascha Bischoff wrote:
> This is the second version of the patch series to add the virtual
> GICv5 [1] device (vgic_v5). Only PPIs are supported by this initial
> series, and the vgic_v5 implementation is restricted to the CPU
> interface, only. Further patch series are to follow in due course,
> and
> will add support for SPIs, LPIs, the GICv5 IRS, and the GICv5 ITS.
> 
> The first version of this series can be found at [2].
> 
> The noteworthy changes since V1 of this series are:
> 
> 1. Added detection of implemented PPIs on a GICv5 host at boot time.
> 2. Added masking for PPIs that are presented to guests. Only PPIs
> with
>    owners and the SW_PPI (if present) are exposed.
> 3. Added trapping and masking for all guest writes to the writable
>    ICC_PPI_x_EL1 registers. The writes are masked with the subset of
>    PPIs exposed to the guest. This ensures that the guest cannot
>    discover PPIs that are not intentionally exposed to it.
> 4. Added an new UAPI to allow userspace to query which PPIs can be
>    driven via KVM_IRQ_LINE. For the time being, only the SW_ PPI is
>    exposed for guest control.
> 5. Interrupt type checks are now re-worked to be more readable and
>    scalable. Thanks, Marc.
> 
> I have addressed some, but alas not all (see below), review comments
> against v1 of the series. Thanks a lot Marc, Joey, and Lorenzo!
> 
> I'm posting V2 even though I've yet to address all review comments as
> I shall be out of office for the next 2 weeks. Therefore, I wanted to
> make sure that the latest version was available for anyone to take a
> look. Any outstanding and new comments will be addressed on my
> return.
> 
> The main outstanding changes are:
> 
> 1. Rework the PPI save/restore mechanisms to remove the _entry/_exit
>    from the vcpu, and instead use per-cpu data structures.
> 2. PPI injection needs clean up around shadow state tracking an
>    manipulation.
> 3. PPI state tracking needs to be heaviliy optimised to reduce the
>    number of locks taken and PPIs iterated over. This is now possible
>    with the introduction of the masks, but remains to be implemented.
> 4. Allow for sparse PPI state storage. Given that most of the 128
>    potential PPIs will never be used with a guest, it is extremely
>    wasteful to allocate storage for them.
> 
> These changes are based on v6.19-rc1. As before, the first commit has
> been cherry-picked from Marc's VTCR sanitisation series [3].
> 
> For those that are interested in the overall direction of the GICv5
> KVM support, Marc Zyngier has very kindly agreed to host the full
> *WIP* set of GICv5 KVM patches which can be found at [4]. These are
> not intended for review, and require some serious clean up, but
> should
> give a rough idea of what is still to come.
> 
> Thanks all for the feedback so far and any more you have,
> Sascha
> 
> [1] https://developer.arm.com/documentation/aes0070/latest
> [2]
> https://lore.kernel.org/all/20251212152215.675767-1-sascha.bischoff@arm.com/
> [3]
> https://lore.kernel.org/all/20251210173024.561160-1-maz@kernel.org/
> [4]
> https://git.kernel.org/pub/scm/linux/kernel/git/maz/arm-platforms.git/log/?h=kvm-arm64/gicv5-full
> 

As an FYI, I've just posted the GICv5 kvmtool support for review here:

https://lore.kernel.org/all/20251219161240.1385034-1-sascha.bischoff@arm.com

Thanks,
Sascha

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 07/36] KVM: arm64: gic: Introduce interrupt type helpers
  2025-12-19 15:52 ` [PATCH v2 07/36] KVM: arm64: gic: Introduce interrupt type helpers Sascha Bischoff
@ 2026-01-06 14:51   ` Joey Gouly
  2026-01-06 18:43   ` Jonathan Cameron
  1 sibling, 0 replies; 100+ messages in thread
From: Joey Gouly @ 2026-01-06 14:51 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,
	Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
	lpieralisi@kernel.org, Timothy Hayes

Hello from 2026!

On Fri, Dec 19, 2025 at 03:52:38PM +0000, Sascha Bischoff wrote:
> GICv5 has moved from using interrupt ranges for different interrupt
> types to using some of the upper bits of the interrupt ID to denote
> the interrupt type. This is not compatible with older GICs (which rely
> on ranges of interrupts to determine the type), and hence a set of
> helpers is introduced. These helpers take a struct kvm*, and use the
> vgic model to determine how to interpret the interrupt ID.
> 
> Helpers are introduced for PPIs, SPIs, and LPIs. Additionally, a
> helper is introduced to determine if an interrupt is private - SGIs
> and PPIs for older GICs, and PPIs only for GICv5.
> 
> The helpers are plumbed into the core vgic code, as well as the Arch
> Timer and PMU code.
> 
> There should be no functional changes as part of this change.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> ---
>  arch/arm64/kvm/arch_timer.c           |  2 +-
>  arch/arm64/kvm/pmu-emul.c             |  7 ++-
>  arch/arm64/kvm/vgic/vgic-kvm-device.c |  2 +-
>  arch/arm64/kvm/vgic/vgic.c            | 14 ++---
>  include/kvm/arm_vgic.h                | 82 +++++++++++++++++++++++++--
>  5 files changed, 91 insertions(+), 16 deletions(-)
> 
> diff --git a/arch/arm64/kvm/arch_timer.c b/arch/arm64/kvm/arch_timer.c
> index 99a07972068d1..6f033f6644219 100644
> --- a/arch/arm64/kvm/arch_timer.c
> +++ b/arch/arm64/kvm/arch_timer.c
> @@ -1598,7 +1598,7 @@ int kvm_arm_timer_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
>  	if (get_user(irq, uaddr))
>  		return -EFAULT;
>  
> -	if (!(irq_is_ppi(irq)))
> +	if (!(irq_is_ppi(vcpu->kvm, irq)))
>  		return -EINVAL;
>  
>  	mutex_lock(&vcpu->kvm->arch.config_lock);
> diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
> index b03dbda7f1ab9..afc838ea2503e 100644
> --- a/arch/arm64/kvm/pmu-emul.c
> +++ b/arch/arm64/kvm/pmu-emul.c
> @@ -939,7 +939,8 @@ int kvm_arm_pmu_v3_enable(struct kvm_vcpu *vcpu)
>  		 * number against the dimensions of the vgic and make sure
>  		 * it's valid.
>  		 */
> -		if (!irq_is_ppi(irq) && !vgic_valid_spi(vcpu->kvm, irq))
> +		if (!irq_is_ppi(vcpu->kvm, irq) &&
> +		    !vgic_valid_spi(vcpu->kvm, irq))
>  			return -EINVAL;
>  	} else if (kvm_arm_pmu_irq_initialized(vcpu)) {
>  		   return -EINVAL;
> @@ -991,7 +992,7 @@ static bool pmu_irq_is_valid(struct kvm *kvm, int irq)
>  		if (!kvm_arm_pmu_irq_initialized(vcpu))
>  			continue;
>  
> -		if (irq_is_ppi(irq)) {
> +		if (irq_is_ppi(vcpu->kvm, irq)) {
>  			if (vcpu->arch.pmu.irq_num != irq)
>  				return false;
>  		} else {
> @@ -1142,7 +1143,7 @@ int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
>  			return -EFAULT;
>  
>  		/* The PMU overflow interrupt can be a PPI or a valid SPI. */
> -		if (!(irq_is_ppi(irq) || irq_is_spi(irq)))
> +		if (!(irq_is_ppi(vcpu->kvm, irq) || irq_is_spi(vcpu->kvm, irq)))
>  			return -EINVAL;
>  
>  		if (!pmu_irq_is_valid(kvm, irq))
> diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c
> index 3d1a776b716d7..b12ba99a423e5 100644
> --- a/arch/arm64/kvm/vgic/vgic-kvm-device.c
> +++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c
> @@ -639,7 +639,7 @@ static int vgic_v3_set_attr(struct kvm_device *dev,
>  		if (vgic_initialized(dev->kvm))
>  			return -EBUSY;
>  
> -		if (!irq_is_ppi(val))
> +		if (!irq_is_ppi(dev->kvm, val))
>  			return -EINVAL;
>  
>  		dev->kvm->arch.vgic.mi_intid = val;
> diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
> index 430aa98888fda..2c0e8803342e2 100644
> --- a/arch/arm64/kvm/vgic/vgic.c
> +++ b/arch/arm64/kvm/vgic/vgic.c
> @@ -94,7 +94,7 @@ struct vgic_irq *vgic_get_irq(struct kvm *kvm, u32 intid)
>  	}
>  
>  	/* LPIs */
> -	if (intid >= VGIC_MIN_LPI)
> +	if (irq_is_lpi(kvm, intid))
>  		return vgic_get_lpi(kvm, intid);
>  
>  	return NULL;
> @@ -123,7 +123,7 @@ static void vgic_release_lpi_locked(struct vgic_dist *dist, struct vgic_irq *irq
>  
>  static __must_check bool __vgic_put_irq(struct kvm *kvm, struct vgic_irq *irq)
>  {
> -	if (irq->intid < VGIC_MIN_LPI)
> +	if (!irq_is_lpi(kvm, irq->intid))
>  		return false;
>  
>  	return refcount_dec_and_test(&irq->refcount);
> @@ -148,7 +148,7 @@ void vgic_put_irq(struct kvm *kvm, struct vgic_irq *irq)
>  	 * Acquire/release it early on lockdep kernels to make locking issues
>  	 * in rare release paths a bit more obvious.
>  	 */
> -	if (IS_ENABLED(CONFIG_LOCKDEP) && irq->intid >= VGIC_MIN_LPI) {
> +	if (IS_ENABLED(CONFIG_LOCKDEP) && irq_is_lpi(kvm, irq->intid)) {
>  		guard(spinlock_irqsave)(&dist->lpi_xa.xa_lock);
>  	}
>  
> @@ -186,7 +186,7 @@ void vgic_flush_pending_lpis(struct kvm_vcpu *vcpu)
>  	raw_spin_lock_irqsave(&vgic_cpu->ap_list_lock, flags);
>  
>  	list_for_each_entry_safe(irq, tmp, &vgic_cpu->ap_list_head, ap_list) {
> -		if (irq->intid >= VGIC_MIN_LPI) {
> +		if (irq_is_lpi(vcpu->kvm, irq->intid)) {
>  			raw_spin_lock(&irq->irq_lock);
>  			list_del(&irq->ap_list);
>  			irq->vcpu = NULL;
> @@ -521,12 +521,12 @@ int kvm_vgic_inject_irq(struct kvm *kvm, struct kvm_vcpu *vcpu,
>  	if (ret)
>  		return ret;
>  
> -	if (!vcpu && intid < VGIC_NR_PRIVATE_IRQS)
> +	if (!vcpu && irq_is_private(kvm, intid))
>  		return -EINVAL;
>  
>  	trace_vgic_update_irq_pending(vcpu ? vcpu->vcpu_idx : 0, intid, level);
>  
> -	if (intid < VGIC_NR_PRIVATE_IRQS)
> +	if (irq_is_private(kvm, intid))
>  		irq = vgic_get_vcpu_irq(vcpu, intid);
>  	else
>  		irq = vgic_get_irq(kvm, intid);
> @@ -685,7 +685,7 @@ int kvm_vgic_set_owner(struct kvm_vcpu *vcpu, unsigned int intid, void *owner)
>  		return -EAGAIN;
>  
>  	/* SGIs and LPIs cannot be wired up to any device */
> -	if (!irq_is_ppi(intid) && !vgic_valid_spi(vcpu->kvm, intid))
> +	if (!irq_is_ppi(vcpu->kvm, intid) && !vgic_valid_spi(vcpu->kvm, intid))
>  		return -EINVAL;
>  
>  	irq = vgic_get_vcpu_irq(vcpu, intid);
> diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
> index b261fb3968d03..6778f676eaf08 100644
> --- a/include/kvm/arm_vgic.h
> +++ b/include/kvm/arm_vgic.h
> @@ -19,6 +19,7 @@
>  #include <linux/jump_label.h>
>  
>  #include <linux/irqchip/arm-gic-v4.h>
> +#include <linux/irqchip/arm-gic-v5.h>
>  
>  #define VGIC_V3_MAX_CPUS	512
>  #define VGIC_V2_MAX_CPUS	8
> @@ -31,9 +32,78 @@
>  #define VGIC_MIN_LPI		8192
>  #define KVM_IRQCHIP_NUM_PINS	(1020 - 32)
>  
> -#define irq_is_ppi(irq) ((irq) >= VGIC_NR_SGIS && (irq) < VGIC_NR_PRIVATE_IRQS)
> -#define irq_is_spi(irq) ((irq) >= VGIC_NR_PRIVATE_IRQS && \
> -			 (irq) <= VGIC_MAX_SPI)
> +#define is_v5_type(t, i)	(FIELD_GET(GICV5_HWIRQ_TYPE, (i)) == (t))
> +
> +#define __irq_is_sgi(t, i)						\
> +	({								\
> +		bool __ret;						\
> +									\
> +		switch (t) {						\
> +		case KVM_DEV_TYPE_ARM_VGIC_V5:				\
> +			__ret = false;					\
> +			break;						\
> +		default:						\
> +			__ret  = (i) < VGIC_NR_SGIS;			\
> +		}							\
> +									\
> +		__ret;							\
> +	})
> +
> +#define __irq_is_ppi(t, i)						\
> +	({								\
> +		bool __ret;						\
> +									\
> +		switch (t) {						\
> +		case KVM_DEV_TYPE_ARM_VGIC_V5:				\
> +			__ret = is_v5_type(GICV5_HWIRQ_TYPE_PPI, (i));	\
> +			break;						\
> +		default:						\
> +			__ret  = (i) >= VGIC_NR_SGIS;			\
> +			__ret &= (i) < VGIC_NR_PRIVATE_IRQS;		\
> +		}							\
> +									\
> +		__ret;							\
> +	})
> +
> +#define __irq_is_spi(t, i)						\
> +	({								\
> +		bool __ret;						\
> +									\
> +		switch (t) {						\
> +		case KVM_DEV_TYPE_ARM_VGIC_V5:				\
> +			__ret = is_v5_type(GICV5_HWIRQ_TYPE_SPI, (i));	\
> +			break;						\
> +		default:						\
> +			__ret  = (i) <= VGIC_MAX_SPI;			\
> +			__ret &= (i) >= VGIC_NR_PRIVATE_IRQS;		\
> +		}							\
> +									\
> +		__ret;							\
> +	})
> +
> +#define __irq_is_lpi(t, i)						\
> +	({								\
> +		bool __ret;						\
> +									\
> +		switch (t) {						\
> +		case KVM_DEV_TYPE_ARM_VGIC_V5:				\
> +			__ret = is_v5_type(GICV5_HWIRQ_TYPE_LPI, (i));	\
> +			break;						\
> +		default:						\
> +			__ret  = (i) >= 8192;				\
> +		}							\
> +									\
> +		__ret;							\
> +	})
> +
> +#define irq_is_sgi(k, i) __irq_is_sgi((k)->arch.vgic.vgic_model, i)
> +#define irq_is_ppi(k, i) __irq_is_ppi((k)->arch.vgic.vgic_model, i)
> +#define irq_is_spi(k, i) __irq_is_spi((k)->arch.vgic.vgic_model, i)
> +#define irq_is_lpi(k, i) __irq_is_lpi((k)->arch.vgic.vgic_model, i)
> +
> +#define irq_is_private(k, i) (irq_is_ppi(k, i) || irq_is_sgi(k, i))
> +
> +#define vgic_is_v5(k) ((k)->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
>  
>  enum vgic_type {
>  	VGIC_V2,		/* Good ol' GICv2 */
> @@ -418,8 +488,12 @@ u64 vgic_v3_get_misr(struct kvm_vcpu *vcpu);
>  
>  #define irqchip_in_kernel(k)	(!!((k)->arch.vgic.in_kernel))
>  #define vgic_initialized(k)	((k)->arch.vgic.initialized)
> -#define vgic_valid_spi(k, i)	(((i) >= VGIC_NR_PRIVATE_IRQS) && \
> +#define vgic_valid_nv5_spi(k, i)	(((i) >= VGIC_NR_PRIVATE_IRQS) && \
>  			((i) < (k)->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS))
> +#define vgic_valid_v5_spi(k, i)	(irq_is_spi(k, i) && \
> +				 (FIELD_GET(GICV5_HWIRQ_ID, i) < (k)->arch.vgic.nr_spis))
> +#define vgic_valid_spi(k, i) (vgic_is_v5(k) ?				\
> +			      vgic_valid_v5_spi(k, i) : vgic_valid_nv5_spi(k, i))
>  
>  bool kvm_vcpu_has_pending_irqs(struct kvm_vcpu *vcpu);
>  void kvm_vgic_sync_hwstate(struct kvm_vcpu *vcpu);

Reviewed-by: Joey Gouly <joey.gouly@arm.com>

Thanks,
Joey


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 27/36] KVM: arm64: gic-v5: Mandate architected PPI for PMU emulation on GICv5
  2025-12-19 15:52 ` [PATCH v2 27/36] KVM: arm64: gic-v5: Mandate architected PPI for PMU emulation on GICv5 Sascha Bischoff
@ 2026-01-06 15:06   ` Joey Gouly
  2026-01-07  9:48     ` Sascha Bischoff
  2026-01-07 16:11   ` Jonathan Cameron
  1 sibling, 1 reply; 100+ messages in thread
From: Joey Gouly @ 2026-01-06 15:06 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,
	Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
	lpieralisi@kernel.org, Timothy Hayes

Question,

On Fri, Dec 19, 2025 at 03:52:45PM +0000, Sascha Bischoff wrote:
> Make it mandatory to use the architected PPI when running a GICv5
> guest. Attempts to set anything other than the architected PPI (23)
> are rejected.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> ---
>  arch/arm64/kvm/pmu-emul.c | 14 ++++++++++++--
>  include/kvm/arm_pmu.h     |  5 ++++-
>  2 files changed, 16 insertions(+), 3 deletions(-)
> 
> diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
> index afc838ea2503e..2d3b50dec6c5d 100644
> --- a/arch/arm64/kvm/pmu-emul.c
> +++ b/arch/arm64/kvm/pmu-emul.c
> @@ -962,8 +962,13 @@ static int kvm_arm_pmu_v3_init(struct kvm_vcpu *vcpu)
>  		if (!vgic_initialized(vcpu->kvm))
>  			return -ENODEV;
>  
> -		if (!kvm_arm_pmu_irq_initialized(vcpu))
> -			return -ENXIO;
> +		if (!kvm_arm_pmu_irq_initialized(vcpu)) {
> +			/* Use the architected irq number for GICv5. */
> +			if (vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
> +				vcpu->arch.pmu.irq_num = KVM_ARMV8_PMU_GICV5_IRQ;
> +			else
> +				return -ENXIO;
> +		}

This relaxes the KVM_ARM_VCPU_PMU_V3_INIT uAPI to not need
KVM_ARM_VCPU_PMU_V3_IRQ to be called first for gic-v5, right?

Maybe that's intentional, but it should be mentioned in the commit and maybe
even in the docs (Documentation/virt/kvm/devices/vcpu.rst)?

Thanks,
Joey

>  
>  		ret = kvm_vgic_set_owner(vcpu, vcpu->arch.pmu.irq_num,
>  					 &vcpu->arch.pmu);
> @@ -988,6 +993,11 @@ static bool pmu_irq_is_valid(struct kvm *kvm, int irq)
>  	unsigned long i;
>  	struct kvm_vcpu *vcpu;
>  
> +	/* On GICv5, the PMUIRQ is architecturally mandated to be PPI 23 */
> +	if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5 &&
> +	    irq != KVM_ARMV8_PMU_GICV5_IRQ)
> +		return false;
> +
>  	kvm_for_each_vcpu(i, vcpu, kvm) {
>  		if (!kvm_arm_pmu_irq_initialized(vcpu))
>  			continue;
> diff --git a/include/kvm/arm_pmu.h b/include/kvm/arm_pmu.h
> index 96754b51b4116..0a36a3d5c8944 100644
> --- a/include/kvm/arm_pmu.h
> +++ b/include/kvm/arm_pmu.h
> @@ -12,6 +12,9 @@
>  
>  #define KVM_ARMV8_PMU_MAX_COUNTERS	32
>  
> +/* PPI #23 - architecturally specified for GICv5 */
> +#define KVM_ARMV8_PMU_GICV5_IRQ		0x20000017
> +
>  #if IS_ENABLED(CONFIG_HW_PERF_EVENTS) && IS_ENABLED(CONFIG_KVM)
>  struct kvm_pmc {
>  	u8 idx;	/* index into the pmu->pmc array */
> @@ -38,7 +41,7 @@ struct arm_pmu_entry {
>  };
>  
>  bool kvm_supports_guest_pmuv3(void);
> -#define kvm_arm_pmu_irq_initialized(v)	((v)->arch.pmu.irq_num >= VGIC_NR_SGIS)
> +#define kvm_arm_pmu_irq_initialized(v)	((v)->arch.pmu.irq_num != 0)
>  u64 kvm_pmu_get_counter_value(struct kvm_vcpu *vcpu, u64 select_idx);
>  void kvm_pmu_set_counter_value(struct kvm_vcpu *vcpu, u64 select_idx, u64 val);
>  void kvm_pmu_set_counter_value_user(struct kvm_vcpu *vcpu, u64 select_idx, u64 val);
> -- 
> 2.34.1


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 18/36] KVM: arm64: gic-v5: Implement PPI interrupt injection
  2025-12-19 15:52 ` [PATCH v2 18/36] KVM: arm64: gic-v5: Implement PPI interrupt injection Sascha Bischoff
@ 2026-01-06 16:06   ` Joey Gouly
  2026-01-06 18:04     ` Sascha Bischoff
  2026-01-07 12:50   ` Jonathan Cameron
  1 sibling, 1 reply; 100+ messages in thread
From: Joey Gouly @ 2026-01-06 16:06 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,
	Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
	lpieralisi@kernel.org, Timothy Hayes

On Fri, Dec 19, 2025 at 03:52:42PM +0000, Sascha Bischoff wrote:
> This change introduces interrupt injection for PPIs for GICv5-based
> guests.
> 
> The lifecycle of PPIs is largely managed by the hardware for a GICv5
> system. The hypervisor injects pending state into the guest by using
> the ICH_PPI_PENDRx_EL2 registers. These are used by the hardware to
> pick a Highest Priority Pending Interrupt (HPPI) for the guest based
> on the enable state of each individual interrupt. The enable state and
> priority for each interrupt are provided by the guest itself (through
> writes to the PPI registers).
> 
> When Direct Virtual Interrupt (DVI) is set for a particular PPI, the
> hypervisor is even able to skip the injection of the pending state
> altogether - it all happens in hardware.
> 
> The result of the above is that no AP lists are required for GICv5,
> unlike for older GICs. Instead, for PPIs the ICH_PPI_* registers
> fulfil the same purpose for all 128 PPIs. Hence, as long as the
> ICH_PPI_* registers are populated prior to guest entry, and merged
> back into the KVM shadow state on exit, the PPI state is preserved,
> and interrupts can be injected.
> 
> When injecting the state of a PPI the state is merged into the KVM's
> shadow state using the set_pending_state irq_op. The directly sets the
> relevant bit in the shadow ICH_PPI_PENDRx_EL2, which is presented to
> the guest (and GICv5 hardware) on next guest entry. The
> queue_irq_unlock irq_op is required to kick the vCPU to ensure that it
> seems the new state. The result is that no AP lists are used for
> private interrupts on GICv5.
> 
> Prior to entering the guest, vgic_v5_flush_ppi_state is called from
> kvm_vgic_flush_hwstate. The effectively snapshots the shadow PPI
> pending state (twice - an entry and an exit copy) in order to track
> any changes. These changes can come from a guest consuming an
> interrupt or from a guest making an Edge-triggered interrupt pending.
> 
> When returning from running a guest, the guest's PPI state is merged
> back into KVM's shadow state in vgic_v5_merge_ppi_state from
> kvm_vgic_sync_hwstate. The Enable and Active state is synced back for
> all PPIs, and the pending state is synced back for Edge PPIs (Level is
> driven directly by the devices generating said levels). The incoming
> pending state from the guest is merged with KVM's shadow state to
> avoid losing any incoming interrupts.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> ---
>  arch/arm64/kvm/vgic/vgic-v5.c | 159 ++++++++++++++++++++++++++++++++++
>  arch/arm64/kvm/vgic/vgic.c    |  46 +++++++---
>  arch/arm64/kvm/vgic/vgic.h    |  47 ++++++++--
>  include/kvm/arm_vgic.h        |   3 +
>  4 files changed, 235 insertions(+), 20 deletions(-)
> 
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index 46c70dfc7bb08..cb3dd872d16a6 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> @@ -56,6 +56,165 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
>  	return 0;
>  }
>  
> +static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
> +					  struct vgic_irq *irq)
> +{
> +	struct vgic_v5_cpu_if *cpu_if;
> +	const u64 id_bit = BIT_ULL(irq->intid % 64);
> +	const u32 reg = FIELD_GET(GICV5_HWIRQ_ID, irq->intid) / 64;
> +
> +	if (!vcpu || !irq)
> +		return false;
> +
> +	/* Skip injecting the state altogether */
> +	if (irq->directly_injected)
> +		return true;
> +
> +	cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> +
> +	if (irq_is_pending(irq))
> +		cpu_if->vgic_ppi_pendr[reg] |= id_bit;
> +	else
> +		cpu_if->vgic_ppi_pendr[reg] &= ~id_bit;
> +
> +	return true;
> +}
> +
> +/*
> + * For GICv5, the PPIs are mostly directly managed by the hardware. We
> + * (the hypervisor) handle the pending, active, enable state
> + * save/restore, but don't need the PPIs to be queued on a per-VCPU AP
> + * list. Therefore, sanity check the state, unlock, and return.
> + */
> +static bool vgic_v5_ppi_queue_irq_unlock(struct kvm *kvm, struct vgic_irq *irq,
> +					 unsigned long flags)
> +	__releases(&irq->irq_lock)
> +{
> +	struct kvm_vcpu *vcpu;
> +
> +	lockdep_assert_held(&irq->irq_lock);
> +
> +	if (WARN_ON_ONCE(!__irq_is_ppi(KVM_DEV_TYPE_ARM_VGIC_V5, irq->intid)))
> +		goto out_unlock_fail;
> +
> +	vcpu = irq->target_vcpu;
> +	if (WARN_ON_ONCE(!vcpu))
> +		goto out_unlock_fail;
> +
> +	raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
> +
> +	/* Directly kick the target VCPU to make sure it sees the IRQ */
> +	kvm_make_request(KVM_REQ_IRQ_PENDING, vcpu);
> +	kvm_vcpu_kick(vcpu);
> +
> +	return true;
> +
> +out_unlock_fail:
> +	raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
> +
> +	return false;
> +}
> +
> +static struct irq_ops vgic_v5_ppi_irq_ops = {
> +	.set_pending_state = vgic_v5_ppi_set_pending_state,
> +	.queue_irq_unlock = vgic_v5_ppi_queue_irq_unlock,
> +};
> +
> +void vgic_v5_set_ppi_ops(struct vgic_irq *irq)
> +{
> +	if (WARN_ON(!irq))
> +		return;
> +
> +	scoped_guard(raw_spinlock, &irq->irq_lock) {
> +		if (!WARN_ON(irq->ops))
> +			irq->ops = &vgic_v5_ppi_irq_ops;
> +	}
> +}
> +
> +/*
> + * Detect any PPIs state changes, and propagate the state with KVM's
> + * shadow structures.
> + */
> +void vgic_v5_fold_ppi_state(struct kvm_vcpu *vcpu)
> +{
> +	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> +	int i, reg;
> +
> +	for (reg = 0; reg < 2; reg++) {
> +		unsigned long changed_bits;
> +		const unsigned long enabler = cpu_if->vgic_ich_ppi_enabler_exit[reg];
> +		const unsigned long activer = cpu_if->vgic_ppi_activer_exit[reg];
> +		const unsigned long pendr = cpu_if->vgic_ppi_pendr_exit[reg];
> +
> +		/*
> +		 * Track what changed across enabler, activer, pendr, but mask
> +		 * with ~DVI.
> +		 */
> +		changed_bits = cpu_if->vgic_ich_ppi_enabler_entry[reg] ^ enabler;
> +		changed_bits |= cpu_if->vgic_ppi_activer_entry[reg] ^ activer;
> +		changed_bits |= cpu_if->vgic_ppi_pendr_entry[reg] ^ pendr;
> +		changed_bits &= ~cpu_if->vgic_ppi_dvir[reg];
> +
> +		for_each_set_bit(i, &changed_bits, 64) {
> +			struct vgic_irq *irq;
> +			u32 intid;
> +
> +			intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
> +			intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg * 64 + i);
> +
> +			irq = vgic_get_vcpu_irq(vcpu, intid);
> +
> +			scoped_guard(raw_spinlock, &irq->irq_lock) {
> +				irq->enabled = !!(enabler & BIT(i));
> +				irq->active = !!(activer & BIT(i));
> +
> +				/* This is an OR to avoid losing incoming edges! */
> +				if (irq->config == VGIC_CONFIG_EDGE)
> +					irq->pending_latch |= !!(pendr & BIT(i));
> +			}
> +
> +			vgic_put_irq(vcpu->kvm, irq);
> +		}
> +
> +		/* Re-inject the exit state as entry state next time! */
> +		cpu_if->vgic_ich_ppi_enabler_entry[reg] = enabler;
> +		cpu_if->vgic_ppi_activer_entry[reg] = activer;
> +
> +		/*
> +		 * Pending state is a bit different. We only propagate back
> +		 * pending state for Edge interrupts. Moreover, this is OR'd
> +		 * with the incoming state to make sure we don't lose incoming
> +		 * edges. Use the (inverse) HMR to mask off all Level bits, and
> +		 * OR.
> +		 */
> +		cpu_if->vgic_ppi_pendr[reg] |= pendr & ~cpu_if->vgic_ppi_hmr[reg];
> +	}
> +}
> +
> +void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu)
> +{
> +	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> +
> +	/*
> +	 * We're about to enter the guest. Copy the shadow state to the pending
> +	 * reg that will be written to the ICH_PPI_PENDRx_EL2 regs. While the
> +	 * guest is running we track any incoming changes to the pending state in
> +	 * vgic_ppi_pendr. The incoming changes are merged with the outgoing
> +	 * changes on the return path.
> +	 */
> +	cpu_if->vgic_ppi_pendr_entry[0] = cpu_if->vgic_ppi_pendr[0];
> +	cpu_if->vgic_ppi_pendr_entry[1] = cpu_if->vgic_ppi_pendr[1];
> +
> +	/*
> +	 * Make sure that we can correctly detect "edges" in the PPI
> +	 * state. There's a path where we never actually enter the guest, and
> +	 * failure to do this risks losing pending state
> +	 */
> +	cpu_if->vgic_ppi_pendr_exit[0] = cpu_if->vgic_ppi_pendr[0];
> +	cpu_if->vgic_ppi_pendr_exit[1] = cpu_if->vgic_ppi_pendr[1];
> +
> +}
> +
>  /*
>   * Not all PPIs are guaranteed to be implemented for
>   * GICv5. Deterermine which ones are, and generate a mask. This is
> diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
> index ac8cb0270e1e4..cb5d43b34462b 100644
> --- a/arch/arm64/kvm/vgic/vgic.c
> +++ b/arch/arm64/kvm/vgic/vgic.c
> @@ -105,6 +105,14 @@ struct vgic_irq *vgic_get_vcpu_irq(struct kvm_vcpu *vcpu, u32 intid)
>  	if (WARN_ON(!vcpu))
>  		return NULL;
>  
> +	if (vgic_is_v5(vcpu->kvm) &&
> +	    __irq_is_ppi(KVM_DEV_TYPE_ARM_VGIC_V5, intid)) {
> +		u32 int_num = FIELD_GET(GICV5_HWIRQ_ID, intid);
> +
> +		int_num = array_index_nospec(int_num, VGIC_V5_NR_PRIVATE_IRQS);
> +		return &vcpu->arch.vgic_cpu.private_irqs[int_num];
> +	}
> +
>  	/* SGIs and PPIs */
>  	if (intid < VGIC_NR_PRIVATE_IRQS) {
>  		intid = array_index_nospec(intid, VGIC_NR_PRIVATE_IRQS);
> @@ -258,10 +266,12 @@ struct kvm_vcpu *vgic_target_oracle(struct vgic_irq *irq)
>  	 * If the distributor is disabled, pending interrupts shouldn't be
>  	 * forwarded.
>  	 */
> -	if (irq->enabled && irq_is_pending(irq)) {
> -		if (unlikely(irq->target_vcpu &&
> -			     !irq->target_vcpu->kvm->arch.vgic.enabled))
> -			return NULL;
> +	if (irq_is_enabled(irq) && irq_is_pending(irq)) {
> +		if (irq->target_vcpu) {
> +			if (!vgic_is_v5(irq->target_vcpu->kvm) &&
> +			    unlikely(!irq->target_vcpu->kvm->arch.vgic.enabled))
> +				return NULL;
> +		}

Don't understand this, can you explain?

Thanks,
Joey

>  
>  		return irq->target_vcpu;
>  	}
> @@ -836,9 +846,11 @@ static void vgic_prune_ap_list(struct kvm_vcpu *vcpu)
>  		vgic_release_deleted_lpis(vcpu->kvm);
>  }
>  
> -static inline void vgic_fold_lr_state(struct kvm_vcpu *vcpu)
> +static void vgic_fold_state(struct kvm_vcpu *vcpu)
>  {
> -	if (kvm_vgic_global_state.type == VGIC_V2)
> +	if (vgic_is_v5(vcpu->kvm))
> +		vgic_v5_fold_ppi_state(vcpu);
> +	else if (kvm_vgic_global_state.type == VGIC_V2)
>  		vgic_v2_fold_lr_state(vcpu);
>  	else
>  		vgic_v3_fold_lr_state(vcpu);
> @@ -1045,8 +1057,10 @@ void kvm_vgic_sync_hwstate(struct kvm_vcpu *vcpu)
>  	if (can_access_vgic_from_kernel())
>  		vgic_save_state(vcpu);
>  
> -	vgic_fold_lr_state(vcpu);
> -	vgic_prune_ap_list(vcpu);
> +	vgic_fold_state(vcpu);
> +
> +	if (!vgic_is_v5(vcpu->kvm))
> +		vgic_prune_ap_list(vcpu);
>  }
>  
>  /* Sync interrupts that were deactivated through a DIR trap */
> @@ -1070,6 +1084,17 @@ static inline void vgic_restore_state(struct kvm_vcpu *vcpu)
>  		__vgic_v3_restore_state(&vcpu->arch.vgic_cpu.vgic_v3);
>  }
>  
> +static void vgic_flush_state(struct kvm_vcpu *vcpu)
> +{
> +	if (vgic_is_v5(vcpu->kvm)) {
> +		vgic_v5_flush_ppi_state(vcpu);
> +		return;
> +	}
> +
> +	scoped_guard(raw_spinlock, &vcpu->arch.vgic_cpu.ap_list_lock)
> +		vgic_flush_lr_state(vcpu);
> +}
> +
>  /* Flush our emulation state into the GIC hardware before entering the guest. */
>  void kvm_vgic_flush_hwstate(struct kvm_vcpu *vcpu)
>  {
> @@ -1106,13 +1131,12 @@ void kvm_vgic_flush_hwstate(struct kvm_vcpu *vcpu)
>  
>  	DEBUG_SPINLOCK_BUG_ON(!irqs_disabled());
>  
> -	scoped_guard(raw_spinlock, &vcpu->arch.vgic_cpu.ap_list_lock)
> -		vgic_flush_lr_state(vcpu);
> +	vgic_flush_state(vcpu);
>  
>  	if (can_access_vgic_from_kernel())
>  		vgic_restore_state(vcpu);
>  
> -	if (vgic_supports_direct_irqs(vcpu->kvm))
> +	if (vgic_supports_direct_irqs(vcpu->kvm) && !vgic_is_v5(vcpu->kvm))
>  		vgic_v4_commit(vcpu);
>  }
>  
> diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
> index d5d9264f0a1e9..978d7f8426361 100644
> --- a/arch/arm64/kvm/vgic/vgic.h
> +++ b/arch/arm64/kvm/vgic/vgic.h
> @@ -132,6 +132,28 @@ static inline bool irq_is_pending(struct vgic_irq *irq)
>  		return irq->pending_latch || irq->line_level;
>  }
>  
> +/* Requires the irq_lock to be held by the caller. */
> +static inline bool irq_is_enabled(struct vgic_irq *irq)
> +{
> +	if (irq->enabled)
> +		return true;
> +
> +	/*
> +	 * We always consider GICv5 interrupts as enabled as we can
> +	 * always inject them. The state is handled by the hardware,
> +	 * and the hardware will only signal the interrupt to the
> +	 * guest once the guest enables it.
> +	 */
> +	if (irq->target_vcpu) {
> +		u32 vgic_model = irq->target_vcpu->kvm->arch.vgic.vgic_model;
> +
> +		if (vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
>  static inline bool vgic_irq_is_mapped_level(struct vgic_irq *irq)
>  {
>  	return irq->config == VGIC_CONFIG_LEVEL && irq->hw;
> @@ -364,7 +386,10 @@ void vgic_debug_destroy(struct kvm *kvm);
>  
>  int vgic_v5_probe(const struct gic_kvm_info *info);
>  void vgic_v5_get_implemented_ppis(void);
> +void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
>  int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
> +void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu);
> +void vgic_v5_fold_ppi_state(struct kvm_vcpu *vcpu);
>  void vgic_v5_load(struct kvm_vcpu *vcpu);
>  void vgic_v5_put(struct kvm_vcpu *vcpu);
>  void vgic_v5_set_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcr);
> @@ -433,15 +458,6 @@ void vgic_its_invalidate_all_caches(struct kvm *kvm);
>  int vgic_its_inv_lpi(struct kvm *kvm, struct vgic_irq *irq);
>  int vgic_its_invall(struct kvm_vcpu *vcpu);
>  
> -bool system_supports_direct_sgis(void);
> -bool vgic_supports_direct_msis(struct kvm *kvm);
> -bool vgic_supports_direct_sgis(struct kvm *kvm);
> -
> -static inline bool vgic_supports_direct_irqs(struct kvm *kvm)
> -{
> -	return vgic_supports_direct_msis(kvm) || vgic_supports_direct_sgis(kvm);
> -}
> -
>  int vgic_v4_init(struct kvm *kvm);
>  void vgic_v4_teardown(struct kvm *kvm);
>  void vgic_v4_configure_vsgis(struct kvm *kvm);
> @@ -481,6 +497,19 @@ static inline bool vgic_is_v3(struct kvm *kvm)
>  	return kvm_vgic_global_state.type == VGIC_V3 || vgic_is_v3_compat(kvm);
>  }
>  
> +bool system_supports_direct_sgis(void);
> +bool vgic_supports_direct_msis(struct kvm *kvm);
> +bool vgic_supports_direct_sgis(struct kvm *kvm);
> +
> +static inline bool vgic_supports_direct_irqs(struct kvm *kvm)
> +{
> +	/* GICv5 always supports direct IRQs */
> +	if (vgic_is_v5(kvm))
> +		return true;
> +
> +	return vgic_supports_direct_msis(kvm) || vgic_supports_direct_sgis(kvm);
> +}
> +
>  int vgic_its_debug_init(struct kvm_device *dev);
>  void vgic_its_debug_destroy(struct kvm_device *dev);
>  
> diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
> index 500709bd62c8d..b5180edbd1165 100644
> --- a/include/kvm/arm_vgic.h
> +++ b/include/kvm/arm_vgic.h
> @@ -32,6 +32,9 @@
>  #define VGIC_MIN_LPI		8192
>  #define KVM_IRQCHIP_NUM_PINS	(1020 - 32)
>  
> +/* GICv5 constants */
> +#define VGIC_V5_NR_PRIVATE_IRQS	128
> +
>  #define is_v5_type(t, i)	(FIELD_GET(GICV5_HWIRQ_TYPE, (i)) == (t))
>  
>  #define __irq_is_sgi(t, i)						\
> -- 
> 2.34.1


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 01/36] KVM: arm64: Account for RES1 bits in DECLARE_FEAT_MAP() and co
  2025-12-19 15:52 ` [PATCH v2 01/36] KVM: arm64: Account for RES1 bits in DECLARE_FEAT_MAP() and co Sascha Bischoff
@ 2026-01-06 17:23   ` Jonathan Cameron
  2026-01-08 16:52     ` Sascha Bischoff
  0 siblings, 1 reply; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-06 17:23 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:36 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> From: Marc Zyngier <maz@kernel.org>
> 
> None of the registers we manage in the feature dependency infrastructure
> so far has any RES1 bit. This is about to change, as VTCR_EL2 has
> its bit 31 being RES1.

Oh goody.

> 
> In order to not fail the consistency checks by not describing a bit,
> add RES1 bits to the set of immutable bits. This requires some extra
> surgery for the FGT handling, as we now need to track RES1 bits there
> as well.
> 
> There are no RES1 FGT bits *yet*. Watch this space.
> 
> Signed-off-by: Marc Zyngier <maz@kernel.org>
FWIW it seems correct.  The only thing I wondered about is
the assumption that if there is an error best thing to do is
to assume it was res0 that was wrong and paper over it.
I guess we can't do anything better if that does happen.

Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>

Process thing though: before anyone can merge this, Sasha
please take a look at submitting patches documentation.

When you 'post' a patch that was written by someone else you have
handled the patch and for the Developer Certificate of origin stuff
to work you have to add your Signed-off-by after theirs.

Jonathan


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 02/36] KVM: arm64: gic-v3: Switch vGIC-v3 to use generated ICH_VMCR_EL2
  2025-12-19 15:52 ` [PATCH v2 02/36] KVM: arm64: gic-v3: Switch vGIC-v3 to use generated ICH_VMCR_EL2 Sascha Bischoff
@ 2026-01-06 18:00   ` Jonathan Cameron
  2026-01-07 10:55     ` Sascha Bischoff
  0 siblings, 1 reply; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-06 18:00 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:36 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> From: Sascha Bischoff <Sascha.Bischoff@arm.com>
> 
> The VGIC-v3 code relied on hand-written definitions for the
> ICH_VMCR_EL2 register. This register, and the associated fields, is
> now generated as part of the sysreg framework. Move to using the
> generated definitions instead of the hand-written ones.
> 
> There are no functional changes as part of this change.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Hi Sascha

Happy new year.  There is a bit in here that isn't obviously going
to result in no functional change. I'm too lazy to chase where the value
goes to check it it's a real bug or not.

Otherwise this is inconsistent on whether the _MASK or define without
it from the sysreg generated header is used in FIELD_GET() / FIELD_PREP()

I'd always use the _MASK version.

> ---
>  arch/arm64/include/asm/sysreg.h      | 21 ---------
>  arch/arm64/kvm/hyp/vgic-v3-sr.c      | 64 ++++++++++++----------------
>  arch/arm64/kvm/vgic/vgic-v3-nested.c |  8 ++--
>  arch/arm64/kvm/vgic/vgic-v3.c        | 48 ++++++++++-----------
>  4 files changed, 54 insertions(+), 87 deletions(-)
> 
> diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
> index 9df51accbb025..b3b8b8cd7bf1e 100644
> --- a/arch/arm64/include/asm/sysreg.h
> +++ b/arch/arm64/include/asm/sysreg.h


> @@ -865,12 +865,12 @@ static void __vgic_v3_write_eoir(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
>  
>  static void __vgic_v3_read_igrpen0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
>  {
> -	vcpu_set_reg(vcpu, rt, !!(vmcr & ICH_VMCR_ENG0_MASK));
> +	vcpu_set_reg(vcpu, rt, vmcr & ICH_VMCR_EL2_VENG0_MASK);
>  }
>  
>  static void __vgic_v3_read_igrpen1(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
>  {
> -	vcpu_set_reg(vcpu, rt, !!(vmcr & ICH_VMCR_ENG1_MASK));
> +	vcpu_set_reg(vcpu, rt, vmcr & ICH_VMCR_EL2_VENG1_MASK);

It's more than possible it doesn't matter, but this isn't functionally
equivalent.
The original set passed 1 as the val parameter to vcpu_set_reg(), and now it passes 2.

Given these don't take a bool I'd use FIELD_GET() for both this and the veng0 one above.
Or put back the horrible !!

>  }

> @@ -916,10 +916,8 @@ static void __vgic_v3_write_bpr0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
>  	if (val < bpr_min)
>  		val = bpr_min;
>  
> -	val <<= ICH_VMCR_BPR0_SHIFT;
> -	val &= ICH_VMCR_BPR0_MASK;
> -	vmcr &= ~ICH_VMCR_BPR0_MASK;
> -	vmcr |= val;
> +	vmcr &= ~ICH_VMCR_EL2_VBPR0_MASK;
> +	vmcr |= FIELD_PREP(ICH_VMCR_EL2_VBPR0, val);

You could uses FIELD_MODIFY() though that would mean using the _MASK
define for both places.  Not sure why the sysreg script generates both
(always have same actual value). I guess the idea is it is a little
shorter if you don't want to be explicit that the intent is to use it
as a mask.

I'd just use the _MASK defines throughout rather than trying for another
consistent scheme. 




>  
>  	__vgic_v3_write_vmcr(vmcr);
>  }
> @@ -929,17 +927,15 @@ static void __vgic_v3_write_bpr1(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
>  	u64 val = vcpu_get_reg(vcpu, rt);
>  	u8 bpr_min = __vgic_v3_bpr_min();
>  
> -	if (vmcr & ICH_VMCR_CBPR_MASK)
> +	if (FIELD_GET(ICH_VMCR_EL2_VCBPR_MASK, val))
>  		return;
>  
>  	/* Enforce BPR limiting */
>  	if (val < bpr_min)
>  		val = bpr_min;
>  
> -	val <<= ICH_VMCR_BPR1_SHIFT;
> -	val &= ICH_VMCR_BPR1_MASK;
> -	vmcr &= ~ICH_VMCR_BPR1_MASK;
> -	vmcr |= val;
> +	vmcr &= ~ICH_VMCR_EL2_VBPR1_MASK;
> +	vmcr |= FIELD_PREP(ICH_VMCR_EL2_VBPR1, val);

As above, FIELD_MODIFY() makes this a one liner.

>  
>  	__vgic_v3_write_vmcr(vmcr);
>  }
> @@ -1029,19 +1025,15 @@ static void __vgic_v3_read_hppir(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
>  
>  static void __vgic_v3_read_pmr(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
>  {
> -	vmcr &= ICH_VMCR_PMR_MASK;
> -	vmcr >>= ICH_VMCR_PMR_SHIFT;
> -	vcpu_set_reg(vcpu, rt, vmcr);
> +	vcpu_set_reg(vcpu, rt, FIELD_GET(ICH_VMCR_EL2_VPMR, vmcr));
>  }
>  
>  static void __vgic_v3_write_pmr(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
>  {
>  	u32 val = vcpu_get_reg(vcpu, rt);
>  
> -	val <<= ICH_VMCR_PMR_SHIFT;
> -	val &= ICH_VMCR_PMR_MASK;
> -	vmcr &= ~ICH_VMCR_PMR_MASK;
> -	vmcr |= val;
> +	vmcr &= ~ICH_VMCR_EL2_VPMR_MASK;
> +	vmcr |= FIELD_PREP(ICH_VMCR_EL2_VPMR, val);

FIELD_MODIFY() should be fine here I think.

>  
>  	write_gicreg(vmcr, ICH_VMCR_EL2);
>  }
> @@ -1064,9 +1056,9 @@ static void __vgic_v3_read_ctlr(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
>  	/* A3V */
>  	val |= ((vtr >> 21) & 1) << ICC_CTLR_EL1_A3V_SHIFT;
>  	/* EOImode */
> -	val |= ((vmcr & ICH_VMCR_EOIM_MASK) >> ICH_VMCR_EOIM_SHIFT) << ICC_CTLR_EL1_EOImode_SHIFT;
> +	val |= FIELD_GET(ICH_VMCR_EL2_VEOIM, vmcr) << ICC_CTLR_EL1_EOImode_SHIFT;

Bit ugly to mix and match styles.
ICC_CTRL_EL1_EOImode_MASK is defined so you could do a FIELD_PREP()

>  	/* CBPR */
> -	val |= (vmcr & ICH_VMCR_CBPR_MASK) >> ICH_VMCR_CBPR_SHIFT;
> +	val |= FIELD_GET(ICH_VMCR_EL2_VCBPR, vmcr);
>  
>  	vcpu_set_reg(vcpu, rt, val);
>  }
> @@ -1076,14 +1068,14 @@ static void __vgic_v3_write_ctlr(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
>  	u32 val = vcpu_get_reg(vcpu, rt);
>  
>  	if (val & ICC_CTLR_EL1_CBPR_MASK)
> -		vmcr |= ICH_VMCR_CBPR_MASK;
> +		vmcr |= ICH_VMCR_EL2_VCBPR_MASK;
>  	else
> -		vmcr &= ~ICH_VMCR_CBPR_MASK;
> +		vmcr &= ~ICH_VMCR_EL2_VCBPR_MASK;
These could be something like

	FIELD_MODIFY(ICH_VMCR_EL2_VCBPR_MASK, &vmcr,
		     FIELD_GET(ICC_CTRL_EL1_CBPR_MASK, val));

More compact. Whether more readable is a little bit more debatable.

>  
>  	if (val & ICC_CTLR_EL1_EOImode_MASK)
> -		vmcr |= ICH_VMCR_EOIM_MASK;
> +		vmcr |= ICH_VMCR_EL2_VEOIM_MASK;
>  	else
> -		vmcr &= ~ICH_VMCR_EOIM_MASK;
> +		vmcr &= ~ICH_VMCR_EL2_VEOIM_MASK;
>  
>  	write_gicreg(vmcr, ICH_VMCR_EL2);
>  }

Thanks,

Jonathan



^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 18/36] KVM: arm64: gic-v5: Implement PPI interrupt injection
  2026-01-06 16:06   ` Joey Gouly
@ 2026-01-06 18:04     ` Sascha Bischoff
  0 siblings, 0 replies; 100+ messages in thread
From: Sascha Bischoff @ 2026-01-06 18:04 UTC (permalink / raw)
  To: Joey Gouly
  Cc: yuzenghui@huawei.com, Timothy Hayes, Suzuki Poulose, nd,
	peter.maydell@linaro.org, kvmarm@lists.linux.dev,
	linux-arm-kernel@lists.infradead.org, kvm@vger.kernel.org,
	lpieralisi@kernel.org, maz@kernel.org, oliver.upton@linux.dev

On Tue, 2026-01-06 at 16:06 +0000, Joey Gouly wrote:
> On Fri, Dec 19, 2025 at 03:52:42PM +0000, Sascha Bischoff wrote:
> > This change introduces interrupt injection for PPIs for GICv5-based
> > guests.
> > 
> > The lifecycle of PPIs is largely managed by the hardware for a
> > GICv5
> > system. The hypervisor injects pending state into the guest by
> > using
> > the ICH_PPI_PENDRx_EL2 registers. These are used by the hardware to
> > pick a Highest Priority Pending Interrupt (HPPI) for the guest
> > based
> > on the enable state of each individual interrupt. The enable state
> > and
> > priority for each interrupt are provided by the guest itself
> > (through
> > writes to the PPI registers).
> > 
> > When Direct Virtual Interrupt (DVI) is set for a particular PPI,
> > the
> > hypervisor is even able to skip the injection of the pending state
> > altogether - it all happens in hardware.
> > 
> > The result of the above is that no AP lists are required for GICv5,
> > unlike for older GICs. Instead, for PPIs the ICH_PPI_* registers
> > fulfil the same purpose for all 128 PPIs. Hence, as long as the
> > ICH_PPI_* registers are populated prior to guest entry, and merged
> > back into the KVM shadow state on exit, the PPI state is preserved,
> > and interrupts can be injected.
> > 
> > When injecting the state of a PPI the state is merged into the
> > KVM's
> > shadow state using the set_pending_state irq_op. The directly sets
> > the
> > relevant bit in the shadow ICH_PPI_PENDRx_EL2, which is presented
> > to
> > the guest (and GICv5 hardware) on next guest entry. The
> > queue_irq_unlock irq_op is required to kick the vCPU to ensure that
> > it
> > seems the new state. The result is that no AP lists are used for
> > private interrupts on GICv5.
> > 
> > Prior to entering the guest, vgic_v5_flush_ppi_state is called from
> > kvm_vgic_flush_hwstate. The effectively snapshots the shadow PPI
> > pending state (twice - an entry and an exit copy) in order to track
> > any changes. These changes can come from a guest consuming an
> > interrupt or from a guest making an Edge-triggered interrupt
> > pending.
> > 
> > When returning from running a guest, the guest's PPI state is
> > merged
> > back into KVM's shadow state in vgic_v5_merge_ppi_state from
> > kvm_vgic_sync_hwstate. The Enable and Active state is synced back
> > for
> > all PPIs, and the pending state is synced back for Edge PPIs (Level
> > is
> > driven directly by the devices generating said levels). The
> > incoming
> > pending state from the guest is merged with KVM's shadow state to
> > avoid losing any incoming interrupts.
> > 
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > ---
> >  arch/arm64/kvm/vgic/vgic-v5.c | 159
> > ++++++++++++++++++++++++++++++++++
> >  arch/arm64/kvm/vgic/vgic.c    |  46 +++++++---
> >  arch/arm64/kvm/vgic/vgic.h    |  47 ++++++++--
> >  include/kvm/arm_vgic.h        |   3 +
> >  4 files changed, 235 insertions(+), 20 deletions(-)
> > 
> > diff --git a/arch/arm64/kvm/vgic/vgic-v5.c
> > b/arch/arm64/kvm/vgic/vgic-v5.c
> > index 46c70dfc7bb08..cb3dd872d16a6 100644
> > --- a/arch/arm64/kvm/vgic/vgic-v5.c
> > +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> > @@ -56,6 +56,165 @@ int vgic_v5_probe(const struct gic_kvm_info
> > *info)
> >  	return 0;
> >  }
> >  
> > +static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
> > +					  struct vgic_irq *irq)
> > +{
> > +	struct vgic_v5_cpu_if *cpu_if;
> > +	const u64 id_bit = BIT_ULL(irq->intid % 64);
> > +	const u32 reg = FIELD_GET(GICV5_HWIRQ_ID, irq->intid) /
> > 64;
> > +
> > +	if (!vcpu || !irq)
> > +		return false;
> > +
> > +	/* Skip injecting the state altogether */
> > +	if (irq->directly_injected)
> > +		return true;
> > +
> > +	cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> > +
> > +	if (irq_is_pending(irq))
> > +		cpu_if->vgic_ppi_pendr[reg] |= id_bit;
> > +	else
> > +		cpu_if->vgic_ppi_pendr[reg] &= ~id_bit;
> > +
> > +	return true;
> > +}
> > +
> > +/*
> > + * For GICv5, the PPIs are mostly directly managed by the
> > hardware. We
> > + * (the hypervisor) handle the pending, active, enable state
> > + * save/restore, but don't need the PPIs to be queued on a per-
> > VCPU AP
> > + * list. Therefore, sanity check the state, unlock, and return.
> > + */
> > +static bool vgic_v5_ppi_queue_irq_unlock(struct kvm *kvm, struct
> > vgic_irq *irq,
> > +					 unsigned long flags)
> > +	__releases(&irq->irq_lock)
> > +{
> > +	struct kvm_vcpu *vcpu;
> > +
> > +	lockdep_assert_held(&irq->irq_lock);
> > +
> > +	if (WARN_ON_ONCE(!__irq_is_ppi(KVM_DEV_TYPE_ARM_VGIC_V5,
> > irq->intid)))
> > +		goto out_unlock_fail;
> > +
> > +	vcpu = irq->target_vcpu;
> > +	if (WARN_ON_ONCE(!vcpu))
> > +		goto out_unlock_fail;
> > +
> > +	raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
> > +
> > +	/* Directly kick the target VCPU to make sure it sees the
> > IRQ */
> > +	kvm_make_request(KVM_REQ_IRQ_PENDING, vcpu);
> > +	kvm_vcpu_kick(vcpu);
> > +
> > +	return true;
> > +
> > +out_unlock_fail:
> > +	raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
> > +
> > +	return false;
> > +}
> > +
> > +static struct irq_ops vgic_v5_ppi_irq_ops = {
> > +	.set_pending_state = vgic_v5_ppi_set_pending_state,
> > +	.queue_irq_unlock = vgic_v5_ppi_queue_irq_unlock,
> > +};
> > +
> > +void vgic_v5_set_ppi_ops(struct vgic_irq *irq)
> > +{
> > +	if (WARN_ON(!irq))
> > +		return;
> > +
> > +	scoped_guard(raw_spinlock, &irq->irq_lock) {
> > +		if (!WARN_ON(irq->ops))
> > +			irq->ops = &vgic_v5_ppi_irq_ops;
> > +	}
> > +}
> > +
> > +/*
> > + * Detect any PPIs state changes, and propagate the state with
> > KVM's
> > + * shadow structures.
> > + */
> > +void vgic_v5_fold_ppi_state(struct kvm_vcpu *vcpu)
> > +{
> > +	struct vgic_v5_cpu_if *cpu_if = &vcpu-
> > >arch.vgic_cpu.vgic_v5;
> > +	int i, reg;
> > +
> > +	for (reg = 0; reg < 2; reg++) {
> > +		unsigned long changed_bits;
> > +		const unsigned long enabler = cpu_if-
> > >vgic_ich_ppi_enabler_exit[reg];
> > +		const unsigned long activer = cpu_if-
> > >vgic_ppi_activer_exit[reg];
> > +		const unsigned long pendr = cpu_if-
> > >vgic_ppi_pendr_exit[reg];
> > +
> > +		/*
> > +		 * Track what changed across enabler, activer,
> > pendr, but mask
> > +		 * with ~DVI.
> > +		 */
> > +		changed_bits = cpu_if-
> > >vgic_ich_ppi_enabler_entry[reg] ^ enabler;
> > +		changed_bits |= cpu_if-
> > >vgic_ppi_activer_entry[reg] ^ activer;
> > +		changed_bits |= cpu_if->vgic_ppi_pendr_entry[reg]
> > ^ pendr;
> > +		changed_bits &= ~cpu_if->vgic_ppi_dvir[reg];
> > +
> > +		for_each_set_bit(i, &changed_bits, 64) {
> > +			struct vgic_irq *irq;
> > +			u32 intid;
> > +
> > +			intid = FIELD_PREP(GICV5_HWIRQ_TYPE,
> > GICV5_HWIRQ_TYPE_PPI);
> > +			intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg *
> > 64 + i);
> > +
> > +			irq = vgic_get_vcpu_irq(vcpu, intid);
> > +
> > +			scoped_guard(raw_spinlock, &irq->irq_lock)
> > {
> > +				irq->enabled = !!(enabler &
> > BIT(i));
> > +				irq->active = !!(activer &
> > BIT(i));
> > +
> > +				/* This is an OR to avoid losing
> > incoming edges! */
> > +				if (irq->config ==
> > VGIC_CONFIG_EDGE)
> > +					irq->pending_latch |=
> > !!(pendr & BIT(i));
> > +			}
> > +
> > +			vgic_put_irq(vcpu->kvm, irq);
> > +		}
> > +
> > +		/* Re-inject the exit state as entry state next
> > time! */
> > +		cpu_if->vgic_ich_ppi_enabler_entry[reg] = enabler;
> > +		cpu_if->vgic_ppi_activer_entry[reg] = activer;
> > +
> > +		/*
> > +		 * Pending state is a bit different. We only
> > propagate back
> > +		 * pending state for Edge interrupts. Moreover,
> > this is OR'd
> > +		 * with the incoming state to make sure we don't
> > lose incoming
> > +		 * edges. Use the (inverse) HMR to mask off all
> > Level bits, and
> > +		 * OR.
> > +		 */
> > +		cpu_if->vgic_ppi_pendr[reg] |= pendr & ~cpu_if-
> > >vgic_ppi_hmr[reg];
> > +	}
> > +}
> > +
> > +void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu)
> > +{
> > +	struct vgic_v5_cpu_if *cpu_if = &vcpu-
> > >arch.vgic_cpu.vgic_v5;
> > +
> > +	/*
> > +	 * We're about to enter the guest. Copy the shadow state
> > to the pending
> > +	 * reg that will be written to the ICH_PPI_PENDRx_EL2
> > regs. While the
> > +	 * guest is running we track any incoming changes to the
> > pending state in
> > +	 * vgic_ppi_pendr. The incoming changes are merged with
> > the outgoing
> > +	 * changes on the return path.
> > +	 */
> > +	cpu_if->vgic_ppi_pendr_entry[0] = cpu_if-
> > >vgic_ppi_pendr[0];
> > +	cpu_if->vgic_ppi_pendr_entry[1] = cpu_if-
> > >vgic_ppi_pendr[1];
> > +
> > +	/*
> > +	 * Make sure that we can correctly detect "edges" in the
> > PPI
> > +	 * state. There's a path where we never actually enter the
> > guest, and
> > +	 * failure to do this risks losing pending state
> > +	 */
> > +	cpu_if->vgic_ppi_pendr_exit[0] = cpu_if-
> > >vgic_ppi_pendr[0];
> > +	cpu_if->vgic_ppi_pendr_exit[1] = cpu_if-
> > >vgic_ppi_pendr[1];
> > +
> > +}
> > +
> >  /*
> >   * Not all PPIs are guaranteed to be implemented for
> >   * GICv5. Deterermine which ones are, and generate a mask. This is
> > diff --git a/arch/arm64/kvm/vgic/vgic.c
> > b/arch/arm64/kvm/vgic/vgic.c
> > index ac8cb0270e1e4..cb5d43b34462b 100644
> > --- a/arch/arm64/kvm/vgic/vgic.c
> > +++ b/arch/arm64/kvm/vgic/vgic.c
> > @@ -105,6 +105,14 @@ struct vgic_irq *vgic_get_vcpu_irq(struct
> > kvm_vcpu *vcpu, u32 intid)
> >  	if (WARN_ON(!vcpu))
> >  		return NULL;
> >  
> > +	if (vgic_is_v5(vcpu->kvm) &&
> > +	    __irq_is_ppi(KVM_DEV_TYPE_ARM_VGIC_V5, intid)) {
> > +		u32 int_num = FIELD_GET(GICV5_HWIRQ_ID, intid);
> > +
> > +		int_num = array_index_nospec(int_num,
> > VGIC_V5_NR_PRIVATE_IRQS);
> > +		return &vcpu->arch.vgic_cpu.private_irqs[int_num];
> > +	}
> > +
> >  	/* SGIs and PPIs */
> >  	if (intid < VGIC_NR_PRIVATE_IRQS) {
> >  		intid = array_index_nospec(intid,
> > VGIC_NR_PRIVATE_IRQS);
> > @@ -258,10 +266,12 @@ struct kvm_vcpu *vgic_target_oracle(struct
> > vgic_irq *irq)
> >  	 * If the distributor is disabled, pending interrupts
> > shouldn't be
> >  	 * forwarded.
> >  	 */
> > -	if (irq->enabled && irq_is_pending(irq)) {
> > -		if (unlikely(irq->target_vcpu &&
> > -			     !irq->target_vcpu->kvm-
> > >arch.vgic.enabled))
> > -			return NULL;
> > +	if (irq_is_enabled(irq) && irq_is_pending(irq)) {
> > +		if (irq->target_vcpu) {
> > +			if (!vgic_is_v5(irq->target_vcpu->kvm) &&
> > +			    unlikely(!irq->target_vcpu->kvm-
> > >arch.vgic.enabled))
> > +				return NULL;
> > +		}
> 
> Don't understand this, can you explain?
> 
> Thanks,
> Joey

We want to preserve the existing behaviour for non-GICv5 guests in that
the oracle returns NULL if the distributor (i.e., vgic) is disabled.

For GICv5, this check doesn't really make sense - we can always inject
interrupt state, and the hardware itself will decide if and when it can
be presented to the guest. GICv5 doesn't have a distributor to begin
with.

PPI state is managed via the ICH_PPI_x registers and can always be
presented to the guest. The hardware will present an enabled and
pending PPI of sufficient priority if the CPU has elected to receive
interrupts, only.

(For SPIs and LPIs, there is a control in the ICH_CONTEXTR_EL2 -
IRICHPPIDIS - which, when set, disables the HPPI selection for SPIs and
LPIs. This effectively acts as the virtual IRS enable (albeit
inverted), and does the same job as the kvm->arch.vgic.enabled here by
blocking the delivery of interrupts that the guest doesn't expect to
see.)

The issue is that we need to know if we are running a GICv5 guest or
not at this point, which requires a struct kvm*, which we only have if
we have a target CPU.

I think I should re-work it to become:

	if (unlikely(irq->target_vcpu &&
		     !vgic_is_v5(irq->target_vcpu->kvm) &&
     		     !irq->target_vcpu->kvm->arch.vgic.enabled))
		return NULL;

I think this at least makes it a little more readable, and better
preserves the underlying logic.

All of that said, I think that this might be moot now. This PPI
injection has gone through numerous iterations, and I don't believe
that vgic_target_oracle() is actually reachable anymore for a GICv5-
based guest. I think that holds true for SPIs and LPIs too, in which
case I think these specific changes can be dropped. I'll look into this
and update the series accordingly.

Thanks, and I hope this clears things up a little!

Sascha

> 
> >  
> >  		return irq->target_vcpu;
> >  	}
> > @@ -836,9 +846,11 @@ static void vgic_prune_ap_list(struct kvm_vcpu
> > *vcpu)
> >  		vgic_release_deleted_lpis(vcpu->kvm);
> >  }
> >  
> > -static inline void vgic_fold_lr_state(struct kvm_vcpu *vcpu)
> > +static void vgic_fold_state(struct kvm_vcpu *vcpu)
> >  {
> > -	if (kvm_vgic_global_state.type == VGIC_V2)
> > +	if (vgic_is_v5(vcpu->kvm))
> > +		vgic_v5_fold_ppi_state(vcpu);
> > +	else if (kvm_vgic_global_state.type == VGIC_V2)
> >  		vgic_v2_fold_lr_state(vcpu);
> >  	else
> >  		vgic_v3_fold_lr_state(vcpu);
> > @@ -1045,8 +1057,10 @@ void kvm_vgic_sync_hwstate(struct kvm_vcpu
> > *vcpu)
> >  	if (can_access_vgic_from_kernel())
> >  		vgic_save_state(vcpu);
> >  
> > -	vgic_fold_lr_state(vcpu);
> > -	vgic_prune_ap_list(vcpu);
> > +	vgic_fold_state(vcpu);
> > +
> > +	if (!vgic_is_v5(vcpu->kvm))
> > +		vgic_prune_ap_list(vcpu);
> >  }
> >  
> >  /* Sync interrupts that were deactivated through a DIR trap */
> > @@ -1070,6 +1084,17 @@ static inline void vgic_restore_state(struct
> > kvm_vcpu *vcpu)
> >  		__vgic_v3_restore_state(&vcpu-
> > >arch.vgic_cpu.vgic_v3);
> >  }
> >  
> > +static void vgic_flush_state(struct kvm_vcpu *vcpu)
> > +{
> > +	if (vgic_is_v5(vcpu->kvm)) {
> > +		vgic_v5_flush_ppi_state(vcpu);
> > +		return;
> > +	}
> > +
> > +	scoped_guard(raw_spinlock, &vcpu-
> > >arch.vgic_cpu.ap_list_lock)
> > +		vgic_flush_lr_state(vcpu);
> > +}
> > +
> >  /* Flush our emulation state into the GIC hardware before entering
> > the guest. */
> >  void kvm_vgic_flush_hwstate(struct kvm_vcpu *vcpu)
> >  {
> > @@ -1106,13 +1131,12 @@ void kvm_vgic_flush_hwstate(struct kvm_vcpu
> > *vcpu)
> >  
> >  	DEBUG_SPINLOCK_BUG_ON(!irqs_disabled());
> >  
> > -	scoped_guard(raw_spinlock, &vcpu-
> > >arch.vgic_cpu.ap_list_lock)
> > -		vgic_flush_lr_state(vcpu);
> > +	vgic_flush_state(vcpu);
> >  
> >  	if (can_access_vgic_from_kernel())
> >  		vgic_restore_state(vcpu);
> >  
> > -	if (vgic_supports_direct_irqs(vcpu->kvm))
> > +	if (vgic_supports_direct_irqs(vcpu->kvm) &&
> > !vgic_is_v5(vcpu->kvm))
> >  		vgic_v4_commit(vcpu);
> >  }
> >  
> > diff --git a/arch/arm64/kvm/vgic/vgic.h
> > b/arch/arm64/kvm/vgic/vgic.h
> > index d5d9264f0a1e9..978d7f8426361 100644
> > --- a/arch/arm64/kvm/vgic/vgic.h
> > +++ b/arch/arm64/kvm/vgic/vgic.h
> > @@ -132,6 +132,28 @@ static inline bool irq_is_pending(struct
> > vgic_irq *irq)
> >  		return irq->pending_latch || irq->line_level;
> >  }
> >  
> > +/* Requires the irq_lock to be held by the caller. */
> > +static inline bool irq_is_enabled(struct vgic_irq *irq)
> > +{
> > +	if (irq->enabled)
> > +		return true;
> > +
> > +	/*
> > +	 * We always consider GICv5 interrupts as enabled as we
> > can
> > +	 * always inject them. The state is handled by the
> > hardware,
> > +	 * and the hardware will only signal the interrupt to the
> > +	 * guest once the guest enables it.
> > +	 */
> > +	if (irq->target_vcpu) {
> > +		u32 vgic_model = irq->target_vcpu->kvm-
> > >arch.vgic.vgic_model;
> > +
> > +		if (vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
> > +			return true;
> > +	}
> > +
> > +	return false;
> > +}
> > +
> >  static inline bool vgic_irq_is_mapped_level(struct vgic_irq *irq)
> >  {
> >  	return irq->config == VGIC_CONFIG_LEVEL && irq->hw;
> > @@ -364,7 +386,10 @@ void vgic_debug_destroy(struct kvm *kvm);
> >  
> >  int vgic_v5_probe(const struct gic_kvm_info *info);
> >  void vgic_v5_get_implemented_ppis(void);
> > +void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
> >  int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
> > +void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu);
> > +void vgic_v5_fold_ppi_state(struct kvm_vcpu *vcpu);
> >  void vgic_v5_load(struct kvm_vcpu *vcpu);
> >  void vgic_v5_put(struct kvm_vcpu *vcpu);
> >  void vgic_v5_set_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr
> > *vmcr);
> > @@ -433,15 +458,6 @@ void vgic_its_invalidate_all_caches(struct kvm
> > *kvm);
> >  int vgic_its_inv_lpi(struct kvm *kvm, struct vgic_irq *irq);
> >  int vgic_its_invall(struct kvm_vcpu *vcpu);
> >  
> > -bool system_supports_direct_sgis(void);
> > -bool vgic_supports_direct_msis(struct kvm *kvm);
> > -bool vgic_supports_direct_sgis(struct kvm *kvm);
> > -
> > -static inline bool vgic_supports_direct_irqs(struct kvm *kvm)
> > -{
> > -	return vgic_supports_direct_msis(kvm) ||
> > vgic_supports_direct_sgis(kvm);
> > -}
> > -
> >  int vgic_v4_init(struct kvm *kvm);
> >  void vgic_v4_teardown(struct kvm *kvm);
> >  void vgic_v4_configure_vsgis(struct kvm *kvm);
> > @@ -481,6 +497,19 @@ static inline bool vgic_is_v3(struct kvm *kvm)
> >  	return kvm_vgic_global_state.type == VGIC_V3 ||
> > vgic_is_v3_compat(kvm);
> >  }
> >  
> > +bool system_supports_direct_sgis(void);
> > +bool vgic_supports_direct_msis(struct kvm *kvm);
> > +bool vgic_supports_direct_sgis(struct kvm *kvm);
> > +
> > +static inline bool vgic_supports_direct_irqs(struct kvm *kvm)
> > +{
> > +	/* GICv5 always supports direct IRQs */
> > +	if (vgic_is_v5(kvm))
> > +		return true;
> > +
> > +	return vgic_supports_direct_msis(kvm) ||
> > vgic_supports_direct_sgis(kvm);
> > +}
> > +
> >  int vgic_its_debug_init(struct kvm_device *dev);
> >  void vgic_its_debug_destroy(struct kvm_device *dev);
> >  
> > diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
> > index 500709bd62c8d..b5180edbd1165 100644
> > --- a/include/kvm/arm_vgic.h
> > +++ b/include/kvm/arm_vgic.h
> > @@ -32,6 +32,9 @@
> >  #define VGIC_MIN_LPI		8192
> >  #define KVM_IRQCHIP_NUM_PINS	(1020 - 32)
> >  
> > +/* GICv5 constants */
> > +#define VGIC_V5_NR_PRIVATE_IRQS	128
> > +
> >  #define is_v5_type(t, i)	(FIELD_GET(GICV5_HWIRQ_TYPE, (i))
> > == (t))
> >  
> >  #define __irq_is_sgi(t,
> > i)						\
> > -- 
> > 2.34.1


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 05/36] arm64/sysreg: Add GICR CDNMIA encoding
  2025-12-19 15:52 ` [PATCH v2 05/36] arm64/sysreg: Add GICR CDNMIA encoding Sascha Bischoff
@ 2026-01-06 18:08   ` Jonathan Cameron
  2026-01-07  8:39     ` Sascha Bischoff
  0 siblings, 1 reply; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-06 18:08 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:37 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> The encoding for the GICR CDNMIA system instruction is thus far unused
> (and shall remain unused for the time being). However, in order to
> plumb the FGTs into KVM correctly, KVM needs to be made aware of the
> encoding of this system instruction.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> ---
>  arch/arm64/include/asm/sysreg.h | 7 +++++++
>  1 file changed, 7 insertions(+)
> 
> diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
> index b3b8b8cd7bf1e..e99acb6dbd5d8 100644
> --- a/arch/arm64/include/asm/sysreg.h
> +++ b/arch/arm64/include/asm/sysreg.h
> @@ -1059,6 +1059,7 @@
>  #define GICV5_OP_GIC_CDPRI		sys_insn(1, 0, 12, 1, 2)
>  #define GICV5_OP_GIC_CDRCFG		sys_insn(1, 0, 12, 1, 5)
>  #define GICV5_OP_GICR_CDIA		sys_insn(1, 0, 12, 3, 0)
> +#define GICV5_OP_GICR_CDNMIA		sys_insn(1, 0, 12, 3, 1)
>  
>  /* Definitions for GIC CDAFF */
>  #define GICV5_GIC_CDAFF_IAFFID_MASK	GENMASK_ULL(47, 32)
> @@ -1105,6 +1106,12 @@
>  #define GICV5_GIC_CDIA_TYPE_MASK	GENMASK_ULL(31, 29)
>  #define GICV5_GIC_CDIA_ID_MASK		GENMASK_ULL(23, 0)
>  
> +/* Definitions for GICR CDNMIA */
> +#define GICV5_GIC_CDNMIA_VALID_MASK	BIT_ULL(32)
> +#define GICV5_GICR_CDNMIA_VALID(r)	FIELD_GET(GICV5_GIC_CDNMIA_VALID_MASK, r)

Why the R for just this one?

There is precedence with GICV5_GICR_CDIA_VALID() but I've no idea
why that one got the R (and not the field definitions next to it)
either!

Lorenzo, guessing that was in your main gicv5 series?

Given it's GICR CDIA (and here GICR CDNMIA) in the spec, I think
all the definitions should have the R but maybe I'm missing something.

Jonathan


> +#define GICV5_GIC_CDNMIA_TYPE_MASK	GENMASK_ULL(31, 29)
> +#define GICV5_GIC_CDNMIA_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)
>  



^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 04/36] arm64/sysreg: Add remaining GICv5 ICC_ & ICH_ sysregs for KVM support
  2025-12-19 15:52 ` [PATCH v2 04/36] arm64/sysreg: Add remaining GICv5 ICC_ & ICH_ sysregs for KVM support Sascha Bischoff
@ 2026-01-06 18:28   ` Jonathan Cameron
  0 siblings, 0 replies; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-06 18:28 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:37 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> Add the GICv5 system registers required to support native GICv5 guests
> with KVM. Many of the GICv5 sysregs have already been added as part of
> the host GICv5 driver, keeping this set relatively small. The
> registers added in this change complete the set by adding those
> required by KVM either directly (ICH_) or indirectly (FGTs for the
> ICC_ sysregs).
> 
> The following system registers and their fields are added:
> 
> 	ICC_APR_EL1
> 	ICC_HPPIR_EL1
> 	ICC_IAFFIDR_EL1
> 	ICH_APR_EL2
> 	ICH_CONTEXTR_EL2
> 	ICH_PPI_ACTIVER<n>_EL2
> 	ICH_PPI_DVI<n>_EL2
> 	ICH_PPI_ENABLER<n>_EL2
> 	ICH_PPI_PENDR<n>_EL2
> 	ICH_PPI_PRIORITYR<n>_EL2
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>

Matches spec as far as I can spot.

Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 09/36] KVM: arm64: gic-v5: Detect implemented PPIs on boot
  2025-12-19 15:52 ` [PATCH v2 09/36] KVM: arm64: gic-v5: Detect implemented PPIs on boot Sascha Bischoff
@ 2026-01-06 18:34   ` Jonathan Cameron
  0 siblings, 0 replies; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-06 18:34 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:38 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> It is expected that most GICv5 implementations will only implement a
> subset of the PPIs. GICv5 supports up to 128 PPIs in total, where the
> first 64 are architecturally defined, and the second 64 are
> implementation defined. This limitation applies to both physical and
> virtual PPIs as the same set is implemented in each case, and
> therefore KVM needs to determine a mask of implemented PPIs during
> early KVM init.
> 
> The check involves writing all of the ICH_PPI_ENABLERx_EL2 bits and
> reading those back again to determine which are stateful. If the bits
> are stateful, the PPIs are implemented. Else, they are not.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>



^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 07/36] KVM: arm64: gic: Introduce interrupt type helpers
  2025-12-19 15:52 ` [PATCH v2 07/36] KVM: arm64: gic: Introduce interrupt type helpers Sascha Bischoff
  2026-01-06 14:51   ` Joey Gouly
@ 2026-01-06 18:43   ` Jonathan Cameron
  2026-01-08  9:33     ` Sascha Bischoff
  1 sibling, 1 reply; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-06 18:43 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:38 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> GICv5 has moved from using interrupt ranges for different interrupt
> types to using some of the upper bits of the interrupt ID to denote
> the interrupt type. This is not compatible with older GICs (which rely
> on ranges of interrupts to determine the type), and hence a set of
> helpers is introduced. These helpers take a struct kvm*, and use the
> vgic model to determine how to interpret the interrupt ID.
> 
> Helpers are introduced for PPIs, SPIs, and LPIs. Additionally, a
> helper is introduced to determine if an interrupt is private - SGIs
> and PPIs for older GICs, and PPIs only for GICv5.
> 
> The helpers are plumbed into the core vgic code, as well as the Arch
> Timer and PMU code.
> 
> There should be no functional changes as part of this change.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Hi Sascha,

A bit of bikeshedding / 'valuable' naming feedback to end the day.
Otherwise LGTM.

> diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
> index b261fb3968d03..6778f676eaf08 100644
> --- a/include/kvm/arm_vgic.h
> +++ b/include/kvm/arm_vgic.h
...

>  enum vgic_type {
>  	VGIC_V2,		/* Good ol' GICv2 */
> @@ -418,8 +488,12 @@ u64 vgic_v3_get_misr(struct kvm_vcpu *vcpu);
>  
>  #define irqchip_in_kernel(k)	(!!((k)->arch.vgic.in_kernel))
>  #define vgic_initialized(k)	((k)->arch.vgic.initialized)
> -#define vgic_valid_spi(k, i)	(((i) >= VGIC_NR_PRIVATE_IRQS) && \
> +#define vgic_valid_nv5_spi(k, i)	(((i) >= VGIC_NR_PRIVATE_IRQS) && \
>  			((i) < (k)->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS))
> +#define vgic_valid_v5_spi(k, i)	(irq_is_spi(k, i) && \
> +				 (FIELD_GET(GICV5_HWIRQ_ID, i) < (k)->arch.vgic.nr_spis))
> +#define vgic_valid_spi(k, i) (vgic_is_v5(k) ?				\
> +			      vgic_valid_v5_spi(k, i) : vgic_valid_nv5_spi(k, i))

nv is a little awkward as a name as immediately makes me thinking
nested virtualization instead of not v5 (which I guess is the thinking behind that?)

Probably just me and naming it v23 will break if we get to GIC version 23 :)
nv5 breaks when we get GICv6 ;)


>  
>  bool kvm_vcpu_has_pending_irqs(struct kvm_vcpu *vcpu);
>  void kvm_vgic_sync_hwstate(struct kvm_vcpu *vcpu);



^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 05/36] arm64/sysreg: Add GICR CDNMIA encoding
  2026-01-06 18:08   ` Jonathan Cameron
@ 2026-01-07  8:39     ` Sascha Bischoff
  0 siblings, 0 replies; 100+ messages in thread
From: Sascha Bischoff @ 2026-01-07  8:39 UTC (permalink / raw)
  To: jonathan.cameron@huawei.com, lpieralisi@kernel.org
  Cc: yuzenghui@huawei.com, Timothy Hayes, Suzuki Poulose, nd,
	peter.maydell@linaro.org, kvmarm@lists.linux.dev,
	linux-arm-kernel@lists.infradead.org, kvm@vger.kernel.org,
	Joey Gouly, maz@kernel.org, oliver.upton@linux.dev

On Tue, 2026-01-06 at 18:08 +0000, Jonathan Cameron wrote:
> On Fri, 19 Dec 2025 15:52:37 +0000
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> 
> > The encoding for the GICR CDNMIA system instruction is thus far
> > unused
> > (and shall remain unused for the time being). However, in order to
> > plumb the FGTs into KVM correctly, KVM needs to be made aware of
> > the
> > encoding of this system instruction.
> > 
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > ---
> >  arch/arm64/include/asm/sysreg.h | 7 +++++++
> >  1 file changed, 7 insertions(+)
> > 
> > diff --git a/arch/arm64/include/asm/sysreg.h
> > b/arch/arm64/include/asm/sysreg.h
> > index b3b8b8cd7bf1e..e99acb6dbd5d8 100644
> > --- a/arch/arm64/include/asm/sysreg.h
> > +++ b/arch/arm64/include/asm/sysreg.h
> > @@ -1059,6 +1059,7 @@
> >  #define GICV5_OP_GIC_CDPRI		sys_insn(1, 0, 12, 1, 2)
> >  #define GICV5_OP_GIC_CDRCFG		sys_insn(1, 0, 12, 1, 5)
> >  #define GICV5_OP_GICR_CDIA		sys_insn(1, 0, 12, 3, 0)
> > +#define GICV5_OP_GICR_CDNMIA		sys_insn(1, 0, 12, 3, 1)
> >  
> >  /* Definitions for GIC CDAFF */
> >  #define GICV5_GIC_CDAFF_IAFFID_MASK	GENMASK_ULL(47, 32)
> > @@ -1105,6 +1106,12 @@
> >  #define GICV5_GIC_CDIA_TYPE_MASK	GENMASK_ULL(31, 29)
> >  #define GICV5_GIC_CDIA_ID_MASK		GENMASK_ULL(23, 0)
> >  
> > +/* Definitions for GICR CDNMIA */
> > +#define GICV5_GIC_CDNMIA_VALID_MASK	BIT_ULL(32)
> > +#define
> > GICV5_GICR_CDNMIA_VALID(r)	FIELD_GET(GICV5_GIC_CDNMIA_VALID_MASK, r)
> 
> Why the R for just this one?
> 
> There is precedence with GICV5_GICR_CDIA_VALID() but I've no idea
> why that one got the R (and not the field definitions next to it)
> either!
> 
> Lorenzo, guessing that was in your main gicv5 series?
> 
> Given it's GICR CDIA (and here GICR CDNMIA) in the spec, I think
> all the definitions should have the R but maybe I'm missing
> something.
> 
> Jonathan

Hi Jonathan,

Happy new year!

Thanks for spotting this. I'm inclined to agree with you - all of the
GICR CD(NM)IA definitions should really have the R present. I'll add it
to the CDNMIA defs.

Lorenzo, assuming that you agree, how would you like to handle CDIA? I
can add the R to the CDIA definitions as part of this change (only
GICV5_GICR_CDIA_VALID() is used so there are no changes to other
files), or one of us can post a separate clean up for that. Let me know
your preference.

Thanks,
Sascha

> 
> 
> > +#define GICV5_GIC_CDNMIA_TYPE_MASK	GENMASK_ULL(31, 29)
> > +#define GICV5_GIC_CDNMIA_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)
> >  
> 


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 27/36] KVM: arm64: gic-v5: Mandate architected PPI for PMU emulation on GICv5
  2026-01-06 15:06   ` Joey Gouly
@ 2026-01-07  9:48     ` Sascha Bischoff
  0 siblings, 0 replies; 100+ messages in thread
From: Sascha Bischoff @ 2026-01-07  9:48 UTC (permalink / raw)
  To: Joey Gouly
  Cc: yuzenghui@huawei.com, Timothy Hayes, Suzuki Poulose, nd,
	peter.maydell@linaro.org, kvmarm@lists.linux.dev,
	linux-arm-kernel@lists.infradead.org, kvm@vger.kernel.org,
	lpieralisi@kernel.org, maz@kernel.org, oliver.upton@linux.dev

On Tue, 2026-01-06 at 15:06 +0000, Joey Gouly wrote:
> Question,
> 
> On Fri, Dec 19, 2025 at 03:52:45PM +0000, Sascha Bischoff wrote:
> > Make it mandatory to use the architected PPI when running a GICv5
> > guest. Attempts to set anything other than the architected PPI (23)
> > are rejected.
> > 
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > ---
> >  arch/arm64/kvm/pmu-emul.c | 14 ++++++++++++--
> >  include/kvm/arm_pmu.h     |  5 ++++-
> >  2 files changed, 16 insertions(+), 3 deletions(-)
> > 
> > diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
> > index afc838ea2503e..2d3b50dec6c5d 100644
> > --- a/arch/arm64/kvm/pmu-emul.c
> > +++ b/arch/arm64/kvm/pmu-emul.c
> > @@ -962,8 +962,13 @@ static int kvm_arm_pmu_v3_init(struct kvm_vcpu
> > *vcpu)
> >  		if (!vgic_initialized(vcpu->kvm))
> >  			return -ENODEV;
> >  
> > -		if (!kvm_arm_pmu_irq_initialized(vcpu))
> > -			return -ENXIO;
> > +		if (!kvm_arm_pmu_irq_initialized(vcpu)) {
> > +			/* Use the architected irq number for
> > GICv5. */
> > +			if (vcpu->kvm->arch.vgic.vgic_model ==
> > KVM_DEV_TYPE_ARM_VGIC_V5)
> > +				vcpu->arch.pmu.irq_num =
> > KVM_ARMV8_PMU_GICV5_IRQ;
> > +			else
> > +				return -ENXIO;
> > +		}
> 
> This relaxes the KVM_ARM_VCPU_PMU_V3_INIT uAPI to not need
> KVM_ARM_VCPU_PMU_V3_IRQ to be called first for gic-v5, right?
> 
> Maybe that's intentional, but it should be mentioned in the commit
> and maybe
> even in the docs (Documentation/virt/kvm/devices/vcpu.rst)?
> 
> Thanks,
> Joey

Hi Joey,

That's a good point - it does relax that.

I'll go and bump that documentation. In addition, it should be
documented that GICv5 requires the architected PPI to be used
irrespective of making the IRQ init optional, so will call that out
too.

Thanks,
Sascha

> 
> >  
> >  		ret = kvm_vgic_set_owner(vcpu, vcpu-
> > >arch.pmu.irq_num,
> >  					 &vcpu->arch.pmu);
> > @@ -988,6 +993,11 @@ static bool pmu_irq_is_valid(struct kvm *kvm,
> > int irq)
> >  	unsigned long i;
> >  	struct kvm_vcpu *vcpu;
> >  
> > +	/* On GICv5, the PMUIRQ is architecturally mandated to be
> > PPI 23 */
> > +	if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5
> > &&
> > +	    irq != KVM_ARMV8_PMU_GICV5_IRQ)
> > +		return false;
> > +
> >  	kvm_for_each_vcpu(i, vcpu, kvm) {
> >  		if (!kvm_arm_pmu_irq_initialized(vcpu))
> >  			continue;
> > diff --git a/include/kvm/arm_pmu.h b/include/kvm/arm_pmu.h
> > index 96754b51b4116..0a36a3d5c8944 100644
> > --- a/include/kvm/arm_pmu.h
> > +++ b/include/kvm/arm_pmu.h
> > @@ -12,6 +12,9 @@
> >  
> >  #define KVM_ARMV8_PMU_MAX_COUNTERS	32
> >  
> > +/* PPI #23 - architecturally specified for GICv5 */
> > +#define KVM_ARMV8_PMU_GICV5_IRQ		0x20000017
> > +
> >  #if IS_ENABLED(CONFIG_HW_PERF_EVENTS) && IS_ENABLED(CONFIG_KVM)
> >  struct kvm_pmc {
> >  	u8 idx;	/* index into the pmu->pmc array */
> > @@ -38,7 +41,7 @@ struct arm_pmu_entry {
> >  };
> >  
> >  bool kvm_supports_guest_pmuv3(void);
> > -#define kvm_arm_pmu_irq_initialized(v)	((v)->arch.pmu.irq_num >=
> > VGIC_NR_SGIS)
> > +#define kvm_arm_pmu_irq_initialized(v)	((v)->arch.pmu.irq_num !=
> > 0)
> >  u64 kvm_pmu_get_counter_value(struct kvm_vcpu *vcpu, u64
> > select_idx);
> >  void kvm_pmu_set_counter_value(struct kvm_vcpu *vcpu, u64
> > select_idx, u64 val);
> >  void kvm_pmu_set_counter_value_user(struct kvm_vcpu *vcpu, u64
> > select_idx, u64 val);
> > -- 
> > 2.34.1


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 08/36] KVM: arm64: Introduce kvm_call_hyp_nvhe_res()
  2025-12-19 15:52 ` [PATCH v2 08/36] KVM: arm64: Introduce kvm_call_hyp_nvhe_res() Sascha Bischoff
@ 2026-01-07 10:30   ` Jonathan Cameron
  2026-01-08  9:48     ` Sascha Bischoff
  0 siblings, 1 reply; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 10:30 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:38 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> There are times when it is desirable to have more than one return
> value for a hyp call. Split out kvm_call_hyp_nvhe_res from
> kvm_call_hyp_nvhe such that it is possible to directly provide struct
> arm_smccc_res from the calling code. Thereby, additional return values
> can be stored in res.a2, etc.
> 
> Suggested-by: Marc Zyngier <maz@kernel.org>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
One question inline, mostly because I'm curious rather than a suggestion
to change anything

Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>

> ---
>  arch/arm64/include/asm/kvm_host.h | 15 ++++++++++-----
>  1 file changed, 10 insertions(+), 5 deletions(-)
> 
> diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
> index b552a1e03848c..971b153b0a3fa 100644
> --- a/arch/arm64/include/asm/kvm_host.h
> +++ b/arch/arm64/include/asm/kvm_host.h
> @@ -1208,14 +1208,19 @@ void kvm_arm_resume_guest(struct kvm *kvm);
>  #define vcpu_has_run_once(vcpu)	(!!READ_ONCE((vcpu)->pid))
>  
>  #ifndef __KVM_NVHE_HYPERVISOR__
> -#define kvm_call_hyp_nvhe(f, ...)						\
> +#define kvm_call_hyp_nvhe_res(res, f, ...)				\
>  	({								\
> -		struct arm_smccc_res res;				\
> -									\
> +		struct arm_smccc_res *__res = res;			\

What's the purpose of the local variable? Type sanity check?
Feels like typecheck() would make the intent clearer.
Meh. Not used anywhere in arch/arm64 so maybe not.



>  		arm_smccc_1_1_hvc(KVM_HOST_SMCCC_FUNC(f),		\
> -				  ##__VA_ARGS__, &res);			\
> -		WARN_ON(res.a0 != SMCCC_RET_SUCCESS);			\
> +				  ##__VA_ARGS__, __res);		\
> +		WARN_ON(__res->a0 != SMCCC_RET_SUCCESS);		\
> +	})
> +
> +#define kvm_call_hyp_nvhe(f, ...)					\
> +	({								\
> +		struct arm_smccc_res res;				\
>  									\
> +		kvm_call_hyp_nvhe_res(&res, f, ##__VA_ARGS__);		\
>  		res.a1;							\
>  	})
>  



^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 02/36] KVM: arm64: gic-v3: Switch vGIC-v3 to use generated ICH_VMCR_EL2
  2026-01-06 18:00   ` Jonathan Cameron
@ 2026-01-07 10:55     ` Sascha Bischoff
  2026-01-09 16:57       ` Sascha Bischoff
  0 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2026-01-07 10:55 UTC (permalink / raw)
  To: jonathan.cameron@huawei.com
  Cc: yuzenghui@huawei.com, lpieralisi@kernel.org, Timothy Hayes,
	Suzuki Poulose, nd, peter.maydell@linaro.org,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, Joey Gouly, maz@kernel.org,
	oliver.upton@linux.dev

On Tue, 2026-01-06 at 18:00 +0000, Jonathan Cameron wrote:
> On Fri, 19 Dec 2025 15:52:36 +0000
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> 
> > From: Sascha Bischoff <Sascha.Bischoff@arm.com>
> > 
> > The VGIC-v3 code relied on hand-written definitions for the
> > ICH_VMCR_EL2 register. This register, and the associated fields, is
> > now generated as part of the sysreg framework. Move to using the
> > generated definitions instead of the hand-written ones.
> > 
> > There are no functional changes as part of this change.
> > 
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> Hi Sascha
> 
> Happy new year.  There is a bit in here that isn't obviously going
> to result in no functional change. I'm too lazy to chase where the
> value
> goes to check it it's a real bug or not.
> 
> Otherwise this is inconsistent on whether the _MASK or define without
> it from the sysreg generated header is used in FIELD_GET() /
> FIELD_PREP()
> 
> I'd always use the _MASK version.

Hi Jonathan,

I've updated the code to use the _MASK version.

> 
> > ---
> >  arch/arm64/include/asm/sysreg.h      | 21 ---------
> >  arch/arm64/kvm/hyp/vgic-v3-sr.c      | 64 ++++++++++++------------
> > ----
> >  arch/arm64/kvm/vgic/vgic-v3-nested.c |  8 ++--
> >  arch/arm64/kvm/vgic/vgic-v3.c        | 48 ++++++++++-----------
> >  4 files changed, 54 insertions(+), 87 deletions(-)
> > 
> > diff --git a/arch/arm64/include/asm/sysreg.h
> > b/arch/arm64/include/asm/sysreg.h
> > index 9df51accbb025..b3b8b8cd7bf1e 100644
> > --- a/arch/arm64/include/asm/sysreg.h
> > +++ b/arch/arm64/include/asm/sysreg.h
> 
> 
> > @@ -865,12 +865,12 @@ static void __vgic_v3_write_eoir(struct
> > kvm_vcpu *vcpu, u32 vmcr, int rt)
> >  
> >  static void __vgic_v3_read_igrpen0(struct kvm_vcpu *vcpu, u32
> > vmcr, int rt)
> >  {
> > -	vcpu_set_reg(vcpu, rt, !!(vmcr & ICH_VMCR_ENG0_MASK));
> > +	vcpu_set_reg(vcpu, rt, vmcr & ICH_VMCR_EL2_VENG0_MASK);
> >  }
> >  
> >  static void __vgic_v3_read_igrpen1(struct kvm_vcpu *vcpu, u32
> > vmcr, int rt)
> >  {
> > -	vcpu_set_reg(vcpu, rt, !!(vmcr & ICH_VMCR_ENG1_MASK));
> > +	vcpu_set_reg(vcpu, rt, vmcr & ICH_VMCR_EL2_VENG1_MASK);
> 
> It's more than possible it doesn't matter, but this isn't
> functionally
> equivalent.
> The original set passed 1 as the val parameter to vcpu_set_reg(), and
> now it passes 2.
> 
> Given these don't take a bool I'd use FIELD_GET() for both this and
> the veng0 one above.
> Or put back the horrible !!

Ah, that's a good catch, and might well be an unintended functional
change looking into it. I've switched to using FIELD_GET() for both.

> 
> >  }
> 
> > @@ -916,10 +916,8 @@ static void __vgic_v3_write_bpr0(struct
> > kvm_vcpu *vcpu, u32 vmcr, int rt)
> >  	if (val < bpr_min)
> >  		val = bpr_min;
> >  
> > -	val <<= ICH_VMCR_BPR0_SHIFT;
> > -	val &= ICH_VMCR_BPR0_MASK;
> > -	vmcr &= ~ICH_VMCR_BPR0_MASK;
> > -	vmcr |= val;
> > +	vmcr &= ~ICH_VMCR_EL2_VBPR0_MASK;
> > +	vmcr |= FIELD_PREP(ICH_VMCR_EL2_VBPR0, val);
> 
> You could uses FIELD_MODIFY() though that would mean using the _MASK
> define for both places.  Not sure why the sysreg script generates
> both
> (always have same actual value). I guess the idea is it is a little
> shorter if you don't want to be explicit that the intent is to use it
> as a mask.
> 
> I'd just use the _MASK defines throughout rather than trying for
> another
> consistent scheme. 

FIELD_MODIFY() is a great shout here. Done & thanks.

Yeah, I'd tried to use _MASK when explicitly using it as a mask, and
without in FIELD_x() (and still managed to be inconsistent with that).
I've now used _MASK everywhere.

> 
> 
> 
> 
> >  
> >  	__vgic_v3_write_vmcr(vmcr);
> >  }
> > @@ -929,17 +927,15 @@ static void __vgic_v3_write_bpr1(struct
> > kvm_vcpu *vcpu, u32 vmcr, int rt)
> >  	u64 val = vcpu_get_reg(vcpu, rt);
> >  	u8 bpr_min = __vgic_v3_bpr_min();
> >  
> > -	if (vmcr & ICH_VMCR_CBPR_MASK)
> > +	if (FIELD_GET(ICH_VMCR_EL2_VCBPR_MASK, val))
> >  		return;
> >  
> >  	/* Enforce BPR limiting */
> >  	if (val < bpr_min)
> >  		val = bpr_min;
> >  
> > -	val <<= ICH_VMCR_BPR1_SHIFT;
> > -	val &= ICH_VMCR_BPR1_MASK;
> > -	vmcr &= ~ICH_VMCR_BPR1_MASK;
> > -	vmcr |= val;
> > +	vmcr &= ~ICH_VMCR_EL2_VBPR1_MASK;
> > +	vmcr |= FIELD_PREP(ICH_VMCR_EL2_VBPR1, val);
> 
> As above, FIELD_MODIFY() makes this a one liner.
> 

Done.

> >  
> >  	__vgic_v3_write_vmcr(vmcr);
> >  }
> > @@ -1029,19 +1025,15 @@ static void __vgic_v3_read_hppir(struct
> > kvm_vcpu *vcpu, u32 vmcr, int rt)
> >  
> >  static void __vgic_v3_read_pmr(struct kvm_vcpu *vcpu, u32 vmcr,
> > int rt)
> >  {
> > -	vmcr &= ICH_VMCR_PMR_MASK;
> > -	vmcr >>= ICH_VMCR_PMR_SHIFT;
> > -	vcpu_set_reg(vcpu, rt, vmcr);
> > +	vcpu_set_reg(vcpu, rt, FIELD_GET(ICH_VMCR_EL2_VPMR,
> > vmcr));
> >  }
> >  
> >  static void __vgic_v3_write_pmr(struct kvm_vcpu *vcpu, u32 vmcr,
> > int rt)
> >  {
> >  	u32 val = vcpu_get_reg(vcpu, rt);
> >  
> > -	val <<= ICH_VMCR_PMR_SHIFT;
> > -	val &= ICH_VMCR_PMR_MASK;
> > -	vmcr &= ~ICH_VMCR_PMR_MASK;
> > -	vmcr |= val;
> > +	vmcr &= ~ICH_VMCR_EL2_VPMR_MASK;
> > +	vmcr |= FIELD_PREP(ICH_VMCR_EL2_VPMR, val);
> 
> FIELD_MODIFY() should be fine here I think.
> 

Done.

> >  
> >  	write_gicreg(vmcr, ICH_VMCR_EL2);
> >  }
> > @@ -1064,9 +1056,9 @@ static void __vgic_v3_read_ctlr(struct
> > kvm_vcpu *vcpu, u32 vmcr, int rt)
> >  	/* A3V */
> >  	val |= ((vtr >> 21) & 1) << ICC_CTLR_EL1_A3V_SHIFT;
> >  	/* EOImode */
> > -	val |= ((vmcr & ICH_VMCR_EOIM_MASK) >>
> > ICH_VMCR_EOIM_SHIFT) << ICC_CTLR_EL1_EOImode_SHIFT;
> > +	val |= FIELD_GET(ICH_VMCR_EL2_VEOIM, vmcr) <<
> > ICC_CTLR_EL1_EOImode_SHIFT;
> 
> Bit ugly to mix and match styles.
> ICC_CTRL_EL1_EOImode_MASK is defined so you could do a FIELD_PREP()

Done.

> 
> >  	/* CBPR */
> > -	val |= (vmcr & ICH_VMCR_CBPR_MASK) >> ICH_VMCR_CBPR_SHIFT;
> > +	val |= FIELD_GET(ICH_VMCR_EL2_VCBPR, vmcr);
> >  
> >  	vcpu_set_reg(vcpu, rt, val);
> >  }
> > @@ -1076,14 +1068,14 @@ static void __vgic_v3_write_ctlr(struct
> > kvm_vcpu *vcpu, u32 vmcr, int rt)
> >  	u32 val = vcpu_get_reg(vcpu, rt);
> >  
> >  	if (val & ICC_CTLR_EL1_CBPR_MASK)
> > -		vmcr |= ICH_VMCR_CBPR_MASK;
> > +		vmcr |= ICH_VMCR_EL2_VCBPR_MASK;
> >  	else
> > -		vmcr &= ~ICH_VMCR_CBPR_MASK;
> > +		vmcr &= ~ICH_VMCR_EL2_VCBPR_MASK;
> These could be something like
> 
> 	FIELD_MODIFY(ICH_VMCR_EL2_VCBPR_MASK, &vmcr,
> 		     FIELD_GET(ICC_CTRL_EL1_CBPR_MASK, val));
> 
> More compact. Whether more readable is a little bit more debatable.

I've gone with this for now. I think it is sufficiently readable.

> 
> >  
> >  	if (val & ICC_CTLR_EL1_EOImode_MASK)
> > -		vmcr |= ICH_VMCR_EOIM_MASK;
> > +		vmcr |= ICH_VMCR_EL2_VEOIM_MASK;
> >  	else
> > -		vmcr &= ~ICH_VMCR_EOIM_MASK;
> > +		vmcr &= ~ICH_VMCR_EL2_VEOIM_MASK;
> >  
> >  	write_gicreg(vmcr, ICH_VMCR_EL2);
> >  }
> 
> Thanks,
> 
> Jonathan
> 

Thanks a lot!
Sascha

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 10/36] KVM: arm64: gic-v5: Sanitize ID_AA64PFR2_EL1.GCIE
  2025-12-19 15:52 ` [PATCH v2 10/36] KVM: arm64: gic-v5: Sanitize ID_AA64PFR2_EL1.GCIE Sascha Bischoff
@ 2026-01-07 10:58   ` Jonathan Cameron
  2026-01-08  9:54     ` Sascha Bischoff
  0 siblings, 1 reply; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 10:58 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:39 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> Set the guest's view of the GCIE field to IMP when running a GICv5 VM,
> NI otherwise. Reject any writes to the register that try to do
> anything but set GCIE to IMP when running a GICv5 VM.
> 
> As part of this change, we also introduce vgic_is_v5(kvm), in order to
> check if the guest is a GICv5-native VM. We're also required to extend
> vgic_is_v3_compat to check for the actual vgic_model. This has one
> potential issue - if any of the vgic_is_v* checks are used prior to
> setting the vgic_model (that is, before kvm_vgic_create) then
> vgic_model will be set to 0, which can result in a false-positive.
> 
> Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Hi Sascha, Timothy

The masking of val has me a little confused in the sanitize function.
Probably needs a slightly rewrite.

Jonathan

> ---
>  arch/arm64/kvm/sys_regs.c  | 39 ++++++++++++++++++++++++++++++--------
>  arch/arm64/kvm/vgic/vgic.h | 10 +++++++++-
>  2 files changed, 40 insertions(+), 9 deletions(-)
> 
> diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
> index c8fd7c6a12a13..a065f8939bc8f 100644
> --- a/arch/arm64/kvm/sys_regs.c
> +++ b/arch/arm64/kvm/sys_regs.c
> @@ -1758,6 +1758,7 @@ static u8 pmuver_to_perfmon(u8 pmuver)
>  
>  static u64 sanitise_id_aa64pfr0_el1(const struct kvm_vcpu *vcpu, u64 val);
>  static u64 sanitise_id_aa64pfr1_el1(const struct kvm_vcpu *vcpu, u64 val);
> +static u64 sanitise_id_aa64pfr2_el1(const struct kvm_vcpu *vcpu, u64 val);
>  static u64 sanitise_id_aa64dfr0_el1(const struct kvm_vcpu *vcpu, u64 val);
>  
>  /* Read a sanitised cpufeature ID register by sys_reg_desc */
> @@ -1783,10 +1784,7 @@ static u64 __kvm_read_sanitised_id_reg(const struct kvm_vcpu *vcpu,
>  		val = sanitise_id_aa64pfr1_el1(vcpu, val);
>  		break;
>  	case SYS_ID_AA64PFR2_EL1:
> -		val &= ID_AA64PFR2_EL1_FPMR |
> -			(kvm_has_mte(vcpu->kvm) ?
> -			 ID_AA64PFR2_EL1_MTEFAR | ID_AA64PFR2_EL1_MTESTOREONLY :
> -			 0);
> +		val = sanitise_id_aa64pfr2_el1(vcpu, val);
>  		break;
>  	case SYS_ID_AA64ISAR1_EL1:
>  		if (!vcpu_has_ptrauth(vcpu))
> @@ -2024,6 +2022,20 @@ static u64 sanitise_id_aa64pfr1_el1(const struct kvm_vcpu *vcpu, u64 val)
>  	return val;
>  }
>  
> +static u64 sanitise_id_aa64pfr2_el1(const struct kvm_vcpu *vcpu, u64 val)
> +{

The code flow in here seems confusing, so maybe needs a rethink even if it
works.  Feels like we need a mask first of everything the kernel understands,
then specific masking out / setting of parts for each feature.
I'm not sure if the initial mask is handled by the caller (didn't check but
it's in the register array structure).
Also I love crossing specs where the gicv5 spec says all the other fields are
reserved and they aren't any more.  Would have been better if that had
just said see arm arm for the other parts of this register.

> +	val &= ID_AA64PFR2_EL1_FPMR |
> +		(kvm_has_mte(vcpu->kvm) ?
> +			ID_AA64PFR2_EL1_MTEFAR | ID_AA64PFR2_EL1_MTESTOREONLY : 0);

So this either masks out everything other than FPRM or masks out everything other
than EL1_MTEFAR, EL1_MTESTORE_ONLY and FPMR.

Hence...

> +
> +	if (vgic_is_v5(vcpu->kvm)) {
> +		val &= ~ID_AA64PFR2_EL1_GCIE_MASK;

This is doing nothing as that field isn't set anyway in either of the earlier
possible maskings of val.

> +		val |= SYS_FIELD_PREP_ENUM(ID_AA64PFR2_EL1, GCIE, IMP);
> +	}
> +
> +	return val;
> +}




^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 12/36] KVM: arm64: gic-v5: Add emulation for ICC_IAFFIDR_EL1 accesses
  2025-12-19 15:52 ` [PATCH v2 12/36] KVM: arm64: gic-v5: Add emulation for ICC_IAFFIDR_EL1 accesses Sascha Bischoff
@ 2026-01-07 11:10   ` Jonathan Cameron
  0 siblings, 0 replies; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 11:10 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:39 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> GICv5 doesn't provide an ICV_IAFFIDR_EL1 or ICH_IAFFIDR_EL2 for
> providing the IAFFID to the guest. A guest access to the
> ICC_IAFFIDR_EL1 must therefore be trapped and emulated to avoid the
> guest accessing the host's ICC_IAFFIDR_EL1.
> 
> The virtual IAFFID is provided to the guest when it reads
> ICC_IAFFIDR_EL1 (which always traps back to the hypervisor). Writes are
> rightly ignored. KVM treats the GICv5 VPEID, the virtual IAFFID, and
> the vcpu_id as the same, and so the vcpu_id is returned.
> 
> The trapping for the ICC_IAFFIDR_EL1 is always enabled when in a guest
> context.
> 
> Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Way out of my normal domain of expertise, so comments that follow might
be completely invalid for some reason that is obvious to KVM folk.

> ---
>  arch/arm64/kvm/config.c   | 10 +++++++++-
>  arch/arm64/kvm/sys_regs.c | 16 ++++++++++++++++
>  2 files changed, 25 insertions(+), 1 deletion(-)
> 
> diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
> index 5f57dc07cc482..eb0c6f4d95b6d 100644
> --- a/arch/arm64/kvm/config.c
> +++ b/arch/arm64/kvm/config.c
> @@ -1582,6 +1582,14 @@ static void __compute_hdfgwtr(struct kvm_vcpu *vcpu)
>  		*vcpu_fgt(vcpu, HDFGWTR_EL2) |= HDFGWTR_EL2_MDSCR_EL1;
>  }
>  
> +static void __compute_ich_hfgrtr(struct kvm_vcpu *vcpu)
> +{
> +	__compute_fgt(vcpu, ICH_HFGRTR_EL2);

The other cases where there is a __compute_xxxxx that adjusts output
of __compute_fgt are seem to be about things that are optional.

I wonder a bit if a more generic solution (in __compute_fgt()) makes sense
for any thing that must always be trapped?

> +
> +	/* ICC_IAFFIDR_EL1 *always* needs to be trapped when running a guest */
> +	*vcpu_fgt(vcpu, ICH_HFGRTR_EL2) &= ~ICH_HFGRTR_EL2_ICC_IAFFIDR_EL1;
> +}
> +
>  void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
>  {
>  	if (!cpus_have_final_cap(ARM64_HAS_FGT))
> @@ -1607,7 +1615,7 @@ void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
>  	if (!cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF))
>  		return;
>  
> -	__compute_fgt(vcpu, ICH_HFGRTR_EL2);
> +	__compute_ich_hfgrtr(vcpu);
>  	__compute_fgt(vcpu, ICH_HFGWTR_EL2);
>  	__compute_fgt(vcpu, ICH_HFGITR_EL2);
>  }
> diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
> index fbbd7b6ff6507..383ada0d75922 100644
> --- a/arch/arm64/kvm/sys_regs.c
> +++ b/arch/arm64/kvm/sys_regs.c
> @@ -681,6 +681,21 @@ static bool access_gic_dir(struct kvm_vcpu *vcpu,
>  	return true;
>  }
>  
> +static bool access_gicv5_iaffid(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
> +				const struct sys_reg_desc *r)
> +{
> +	if (p->is_write)
> +		return ignore_write(vcpu, p);
> +
> +	/*
> +	 * For GICv5 VMs, the IAFFID value is the same as the VPE ID. The VPE ID
> +	 * is the same as the VCPU's ID.
> +	 */
> +	p->regval = FIELD_PREP(ICC_IAFFIDR_EL1_IAFFID, vcpu->vcpu_id);
> +
> +	return true;
> +}
> +
>  static bool trap_raz_wi(struct kvm_vcpu *vcpu,
>  			struct sys_reg_params *p,
>  			const struct sys_reg_desc *r)
> @@ -3411,6 +3426,7 @@ static const struct sys_reg_desc sys_reg_descs[] = {
>  	{ SYS_DESC(SYS_ICC_AP1R1_EL1), undef_access },
>  	{ SYS_DESC(SYS_ICC_AP1R2_EL1), undef_access },
>  	{ SYS_DESC(SYS_ICC_AP1R3_EL1), undef_access },
> +	{ SYS_DESC(SYS_ICC_IAFFIDR_EL1), access_gicv5_iaffid },
>  	{ SYS_DESC(SYS_ICC_DIR_EL1), access_gic_dir },
>  	{ SYS_DESC(SYS_ICC_RPR_EL1), undef_access },
>  	{ SYS_DESC(SYS_ICC_SGI1R_EL1), access_gic_sgi },



^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 11/36] KVM: arm64: gic-v5: Support GICv5 FGTs & FGUs
  2025-12-19 15:52 ` [PATCH v2 11/36] KVM: arm64: gic-v5: Support GICv5 FGTs & FGUs Sascha Bischoff
@ 2026-01-07 11:19   ` Jonathan Cameron
  2026-01-08 10:36     ` Sascha Bischoff
  0 siblings, 1 reply; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 11:19 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:39 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> Extend the existing FGT/FGU infrastructure to include the GICv5 trap
> registers (ICH_HFGRTR_EL2, ICH_HFGWTR_EL2, ICH_HFGITR_EL2). This
> involves mapping the trap registers and their bits to the
> corresponding feature that introduces them (FEAT_GCIE for all, in this
> case), and mapping each trap bit to the system register/instruction
> controlled by it.
> 
> As of this change, none of the GICv5 instructions or register accesses
> are being trapped.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Hi Sascha,

Superficial stuff only on code flow to make it easier to extend next
time.

Jonathan


> diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
> index 3845b188551b6..5f57dc07cc482 100644
> --- a/arch/arm64/kvm/config.c
> +++ b/arch/arm64/kvm/config.c

> @@ -1511,11 +1595,19 @@ void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
>  	__compute_fgt(vcpu, HAFGRTR_EL2);
>  
>  	if (!cpus_have_final_cap(ARM64_HAS_FGT2))
> -		return;
> +		goto skip_fgt2;

Nicer to avoid more complex code flow and just make the next
block an if.

	if (cpus_have_final_cap(ARM64_HAS_FGT2)) {
		__compute_fgt(vcpu, HFGRTR2_EL2);
		__compute_fgt(vcpu, HFGWTR2_EL2);
		__compute_fgt(vcpu, HFGITR2_EL2);
		__compute_fgt(vcpu, HDFGRTR2_EL2);
		__compute_fgt(vcpu, HDFGWTR2_EL2);
	}
>  
>  	__compute_fgt(vcpu, HFGRTR2_EL2);
>  	__compute_fgt(vcpu, HFGWTR2_EL2);
>  	__compute_fgt(vcpu, HFGITR2_EL2);
>  	__compute_fgt(vcpu, HDFGRTR2_EL2);
>  	__compute_fgt(vcpu, HDFGWTR2_EL2);
> +
> +skip_fgt2:
> +	if (!cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF))
Given the above shows this code sometimes gets extended I'd
be tempted to just go with
	if (cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF)) {
		__compute_fgt(vcpu, ICH_HFGRTR_EL2);
		__compute_fgt(vcpu, ICH_HFGWTR_EL2);
		__compute_fgt(vcpu, ICH_HFGITR_EL2);
	}

From the start and avoid future churn or goto nasties.

>	
> +		return;
> +
> +	__compute_fgt(vcpu, ICH_HFGRTR_EL2);
> +	__compute_fgt(vcpu, ICH_HFGWTR_EL2);
> +	__compute_fgt(vcpu, ICH_HFGITR_EL2);
>  }





^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 13/36] KVM: arm64: gic: Set vgic_model before initing private IRQs
  2025-12-19 15:52 ` [PATCH v2 13/36] KVM: arm64: gic: Set vgic_model before initing private IRQs Sascha Bischoff
@ 2026-01-07 11:24   ` Jonathan Cameron
  2026-01-08 13:39     ` Sascha Bischoff
  0 siblings, 1 reply; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 11:24 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:40 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> Different GIC types require the private IRQs to be initialised
> differently. GICv5 is the culprit as it supports both a different
> number of private IRQs, and all of these are PPIs (there are no
> SGIs). Moreover, as GICv5 uses the top bits of the interrupt ID to
> encode the type, the intid also needs to computed differently.
> 
> Up until now, the GIC model has been set after initialising the
> private IRQs for a VCPU. Move this earlier to ensure that the GIC
> model is available when configuring the private IRQs.
Hi Sascha,

Good to mention you are moving a bit more than just the type
initialization. One question on whether it makes sense to move
vgic_dist_base inline.

> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> ---
>  arch/arm64/kvm/vgic/vgic-init.c | 12 ++++++------
>  1 file changed, 6 insertions(+), 6 deletions(-)
> 
> diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
> index c602f24bab1bb..bcc2c79f7833c 100644
> --- a/arch/arm64/kvm/vgic/vgic-init.c
> +++ b/arch/arm64/kvm/vgic/vgic-init.c
> @@ -140,6 +140,12 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
>  		goto out_unlock;
>  	}
>  
> +	kvm->arch.vgic.in_kernel = true;
> +	kvm->arch.vgic.vgic_model = type;
> +	kvm->arch.vgic.implementation_rev = KVM_VGIC_IMP_REV_LATEST;

Moving these looks fine.

> +
> +	kvm->arch.vgic.vgic_dist_base = VGIC_ADDR_UNDEF;

Does this need to move?  The rest of the *base =
stuff is still where this code originally came from.
Might well be necessary but I'd expect a little in the patch description on why.

> +
>  	kvm_for_each_vcpu(i, vcpu, kvm) {
>  		ret = vgic_allocate_private_irqs_locked(vcpu, type);
>  		if (ret)
> @@ -156,12 +162,6 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
>  		goto out_unlock;
>  	}
>  
> -	kvm->arch.vgic.in_kernel = true;
> -	kvm->arch.vgic.vgic_model = type;
> -	kvm->arch.vgic.implementation_rev = KVM_VGIC_IMP_REV_LATEST;
> -
> -	kvm->arch.vgic.vgic_dist_base = VGIC_ADDR_UNDEF;
> -
>  	aa64pfr0 = kvm_read_vm_id_reg(kvm, SYS_ID_AA64PFR0_EL1) & ~ID_AA64PFR0_EL1_GIC;
>  	pfr1 = kvm_read_vm_id_reg(kvm, SYS_ID_PFR1_EL1) & ~ID_PFR1_EL1_GIC;
>  



^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 16/36] KVM: arm64: gic-v5: Implement direct injection of PPIs
  2025-12-19 15:52 ` [PATCH v2 16/36] KVM: arm64: gic-v5: Implement direct injection of PPIs Sascha Bischoff
@ 2026-01-07 12:16   ` Jonathan Cameron
  0 siblings, 0 replies; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 12:16 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:41 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> GICv5 is able to directly inject PPI pending state into a guest using
> a mechanism called DVI whereby the pending bit for a paticular PPI is
> driven directly by the physically-connected hardware. This mechanism
> itself doesn't allow for any ID translation, so the host interrupt is
> directly mapped into a guest with the same interrupt ID.
> 
> When mapping a virtual interrupt to a physical interrupt via
> kvm_vgic_map_irq for a GICv5 guest, check if the interrupt itself is a
> PPI or not. If it is, and the host's interrupt ID matches that used
> for the guest DVI is enabled, and the interrupt itself is marked as
> directly_injected.
> 
> When the interrupt is unmapped again, this process is reversed, and
> DVI is disabled for the interrupt again.
> 
> Note: the expectation is that a directly injected PPI is disabled on
> the host while the guest state is loaded. The reason is that although
> DVI is enabled to drive the guest's pending state directly, the host
> pending state also remains driven. In order to avoid the same PPI
> firing on both the host and the guest, the host's interrupt must be
> disabled (masked). This is left up to the code that owns the device
> generating the PPI as this needs to be handled on a per-VM basis. One
> VM might use DVI, while another might not, in which case the physical
> PPI should be enabled for the latter.
> 
> Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>

Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>




^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 17/36] KVM: arm64: gic: Introduce irq_queue and set_pending_state to irq_ops
  2025-12-19 15:52 ` [PATCH v2 17/36] KVM: arm64: gic: Introduce irq_queue and set_pending_state to irq_ops Sascha Bischoff
@ 2026-01-07 12:22   ` Jonathan Cameron
  0 siblings, 0 replies; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 12:22 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:41 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> There are times when the default behaviour of vgic_queue_irq_unlock is

Nice to make function names explicit in a patch description by using
vgic_queue_irq_unlock() style. Some subsystems insist we do that in commit
descriptions. No idea if kvm does though.

> undesirable. This is because some GICs, such a GICv5 which is the main
> driver for this change, handle the majority of the interrupt lifecycle
> in hardware. In this case, there is no need for a per-VCPU AP list as
> the interrupt can be made pending directly. This is done either via
> the ICH_PPI_x_EL2 registers for PPIs, or with the VDPEND system
> instruction for SPIs and LPIs.
> 
> The queue_irq_unlock function is made overridable using a new function
> pointer in struct irq_ops. In kvm_vgic_inject_irq,
> vgic_queue_irq_unlock is overridden if the function pointer is
> non-null.
> 
> Additionally, a new function is added via a function pointer -
> set_pending_state. The intent is for this to be used to directly set
> the pending state in hardware.
> 
> Both of these new irq_ops are unused in this change - it is purely
> providing the infrastructure itself. The subsequent PPI injection
> changes provide a demonstration of their usage.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>

Trivial stuff only.

Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>

> ---
>  arch/arm64/kvm/vgic/vgic.c | 11 +++++++++++
>  include/kvm/arm_vgic.h     | 15 +++++++++++++++
>  2 files changed, 26 insertions(+)
> 
> diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
> index d88570bb2f9f0..ac8cb0270e1e4 100644
> --- a/arch/arm64/kvm/vgic/vgic.c
> +++ b/arch/arm64/kvm/vgic/vgic.c
> @@ -404,6 +404,13 @@ bool vgic_queue_irq_unlock(struct kvm *kvm, struct vgic_irq *irq,
>  
>  	lockdep_assert_held(&irq->irq_lock);
>  
> +	/*
> +	 * If we have the queue_irq_unlock irq_op, we want to override
> +	 * the default behaviour. Call that, and return early.

I'd say this comment is really just saying what the code clearly does
so provides not benefit. So drop it.

> +	 */
> +	if (irq->ops && irq->ops->queue_irq_unlock)
> +		return irq->ops->queue_irq_unlock(kvm, irq, flags);
> +
>  retry:
>  	vcpu = vgic_target_oracle(irq);
>  	if (irq->vcpu || !vcpu) {
> @@ -547,7 +554,11 @@ 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);
> +
Reasonable change, for readability but not relevant to this patch, so don't
do it here.

>  	vgic_put_irq(kvm, irq);
>  
>  	return 0;
> diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h




^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 15/36] KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore
  2025-12-19 15:52 ` [PATCH v2 15/36] KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore Sascha Bischoff
@ 2026-01-07 12:28   ` Jonathan Cameron
  2026-01-08 13:40     ` Sascha Bischoff
  0 siblings, 1 reply; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 12:28 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:41 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> This change introduces GICv5 load/put. Additionally, it plumbs in
> save/restore for:
> 
> * PPIs (ICH_PPI_x_EL2 regs)
> * ICH_VMCR_EL2
> * ICH_APR_EL2
> * ICC_ICSR_EL1
> 
> A GICv5-specific enable bit is added to struct vgic_vmcr as this
> differs from previous GICs. On GICv5-native systems, the VMCR only
> contains the enable bit (driven by the guest via ICC_CR0_EL1.EN) and
> the priority mask (PCR).
> 
> A struct gicv5_vpe is also introduced. This currently only contains a
> single field - bool resident - which is used to track if a VPE is
> currently running or not, and is used to avoid a case of double load
> or double put on the WFI path for a vCPU. This struct will be extended
> as additional GICv5 support is merged, specifically for VPE doorbells.
> 
> Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>


> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index 1fe1790f1f874..168447ee3fbed 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> @@ -1,4 +1,7 @@
>  // SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Arm Ltd.
> + */

Why in this patch?  It's trivial enough that maybe it doesn't need to be
on it's own, but the first patch touching this file seems like a more
logical place to find it.







^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 18/36] KVM: arm64: gic-v5: Implement PPI interrupt injection
  2025-12-19 15:52 ` [PATCH v2 18/36] KVM: arm64: gic-v5: Implement PPI interrupt injection Sascha Bischoff
  2026-01-06 16:06   ` Joey Gouly
@ 2026-01-07 12:50   ` Jonathan Cameron
  2026-01-08 14:43     ` Sascha Bischoff
  1 sibling, 1 reply; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 12:50 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:42 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> This change introduces interrupt injection for PPIs for GICv5-based
> guests.
> 
> The lifecycle of PPIs is largely managed by the hardware for a GICv5
> system. The hypervisor injects pending state into the guest by using
> the ICH_PPI_PENDRx_EL2 registers. These are used by the hardware to
> pick a Highest Priority Pending Interrupt (HPPI) for the guest based
> on the enable state of each individual interrupt. The enable state and
> priority for each interrupt are provided by the guest itself (through
> writes to the PPI registers).
> 
> When Direct Virtual Interrupt (DVI) is set for a particular PPI, the
> hypervisor is even able to skip the injection of the pending state
> altogether - it all happens in hardware.
> 
> The result of the above is that no AP lists are required for GICv5,
> unlike for older GICs. Instead, for PPIs the ICH_PPI_* registers
> fulfil the same purpose for all 128 PPIs. Hence, as long as the
> ICH_PPI_* registers are populated prior to guest entry, and merged
> back into the KVM shadow state on exit, the PPI state is preserved,
> and interrupts can be injected.
> 
> When injecting the state of a PPI the state is merged into the KVM's
> shadow state using the set_pending_state irq_op. The directly sets the
> relevant bit in the shadow ICH_PPI_PENDRx_EL2, which is presented to
> the guest (and GICv5 hardware) on next guest entry. The
> queue_irq_unlock irq_op is required to kick the vCPU to ensure that it
> seems the new state. The result is that no AP lists are used for
> private interrupts on GICv5.
> 
> Prior to entering the guest, vgic_v5_flush_ppi_state is called from
> kvm_vgic_flush_hwstate. The effectively snapshots the shadow PPI
> pending state (twice - an entry and an exit copy) in order to track
> any changes. These changes can come from a guest consuming an
> interrupt or from a guest making an Edge-triggered interrupt pending.
> 
> When returning from running a guest, the guest's PPI state is merged
> back into KVM's shadow state in vgic_v5_merge_ppi_state from
> kvm_vgic_sync_hwstate. The Enable and Active state is synced back for
> all PPIs, and the pending state is synced back for Edge PPIs (Level is
> driven directly by the devices generating said levels). The incoming
> pending state from the guest is merged with KVM's shadow state to
> avoid losing any incoming interrupts.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Minor things inline

> ---
>  arch/arm64/kvm/vgic/vgic-v5.c | 159 ++++++++++++++++++++++++++++++++++
>  arch/arm64/kvm/vgic/vgic.c    |  46 +++++++---
>  arch/arm64/kvm/vgic/vgic.h    |  47 ++++++++--
>  include/kvm/arm_vgic.h        |   3 +
>  4 files changed, 235 insertions(+), 20 deletions(-)
> 
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index 46c70dfc7bb08..cb3dd872d16a6 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> @@ -56,6 +56,165 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
>  	return 0;
>  }
>  
> +static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
> +					  struct vgic_irq *irq)
> +{
> +	struct vgic_v5_cpu_if *cpu_if;
> +	const u64 id_bit = BIT_ULL(irq->intid % 64);

Obviously the % 64 means other bits of irq->intid above the HWIRQ_ID
field don't matter, but this still seems a little odd.  I'd extract
the field first, then use that for the reg and id_bit or just
do those inline where they are used.

	const u32 hwirq_id = FIELD_GET(GICV5_HWIRQ_ID, irq->intid);

	if (irq_is_pending(irq))
		cpu_if->vgic_ppi_pendr[hwirq_id / 64] |= hwirq_id % 64;
..

Which matches style you used for similar cases in earlier patches.

> +	const u32 reg = FIELD_GET(GICV5_HWIRQ_ID, irq->intid) / 64;
> +
> +	if (!vcpu || !irq)
> +		return false;
> +
> +	/* Skip injecting the state altogether */
> +	if (irq->directly_injected)
> +		return true;
> +
> +	cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> +
> +	if (irq_is_pending(irq))
> +		cpu_if->vgic_ppi_pendr[reg] |= id_bit;
> +	else
> +		cpu_if->vgic_ppi_pendr[reg] &= ~id_bit;
> +
> +	return true;
> +}


> +void vgic_v5_set_ppi_ops(struct vgic_irq *irq)
> +{
> +	if (WARN_ON(!irq))
> +		return;
> +
> +	scoped_guard(raw_spinlock, &irq->irq_lock) {
Not checked on for whether code ends up outside this lock. If not
use a guard(raw_spinlock)(&irq->irq_lock);

> +		if (!WARN_ON(irq->ops))
> +			irq->ops = &vgic_v5_ppi_irq_ops;
> +	}
> +}
> +
> +/*
> + * Detect any PPIs state changes, and propagate the state with KVM's
> + * shadow structures.
> + */
> +void vgic_v5_fold_ppi_state(struct kvm_vcpu *vcpu)
> +{
> +	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> +	int i, reg;
> +
> +	for (reg = 0; reg < 2; reg++) {
It's now considered fine to declare loop variables in the loop and always
nice to limit their scope.

	for (int reg = 0; reg < 2...

> +		unsigned long changed_bits;
> +		const unsigned long enabler = cpu_if->vgic_ich_ppi_enabler_exit[reg];
> +		const unsigned long activer = cpu_if->vgic_ppi_activer_exit[reg];
> +		const unsigned long pendr = cpu_if->vgic_ppi_pendr_exit[reg];

...

> +
> +void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu)
> +{
> +	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> +
> +	/*
> +	 * We're about to enter the guest. Copy the shadow state to the pending
> +	 * reg that will be written to the ICH_PPI_PENDRx_EL2 regs. While the
> +	 * guest is running we track any incoming changes to the pending state in
> +	 * vgic_ppi_pendr. The incoming changes are merged with the outgoing
> +	 * changes on the return path.
> +	 */
> +	cpu_if->vgic_ppi_pendr_entry[0] = cpu_if->vgic_ppi_pendr[0];
> +	cpu_if->vgic_ppi_pendr_entry[1] = cpu_if->vgic_ppi_pendr[1];
> +
> +	/*
> +	 * Make sure that we can correctly detect "edges" in the PPI
> +	 * state. There's a path where we never actually enter the guest, and
> +	 * failure to do this risks losing pending state
> +	 */
> +	cpu_if->vgic_ppi_pendr_exit[0] = cpu_if->vgic_ppi_pendr[0];
> +	cpu_if->vgic_ppi_pendr_exit[1] = cpu_if->vgic_ppi_pendr[1];
> +
Drop this blank line.

> +}

> diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
> index ac8cb0270e1e4..cb5d43b34462b 100644
> --- a/arch/arm64/kvm/vgic/vgic.c
> +++ b/arch/arm64/kvm/vgic/vgic.c

> @@ -258,10 +266,12 @@ struct kvm_vcpu *vgic_target_oracle(struct vgic_irq *irq)
>  	 * If the distributor is disabled, pending interrupts shouldn't be
>  	 * forwarded.
>  	 */
> -	if (irq->enabled && irq_is_pending(irq)) {
> -		if (unlikely(irq->target_vcpu &&
> -			     !irq->target_vcpu->kvm->arch.vgic.enabled))
> -			return NULL;
> +	if (irq_is_enabled(irq) && irq_is_pending(irq)) {
> +		if (irq->target_vcpu) {

Just from a readability point of view, maybe clearer to get rid of
the 'else# path for this one first.

		if (!irq->target_vcpu)
			return NULL;

		if (!vgic_is_v5(irq->target_vcpu->kvm) &&
		    unlikely(!irq->target_vcpu->kvm->arch.vgic.enabled))
			return NULL;

		return irq->target_vcpu;

Though I see this code might go away anyway...

> +			if (!vgic_is_v5(irq->target_vcpu->kvm) &&
> +			    unlikely(!irq->target_vcpu->kvm->arch.vgic.enabled))
> +				return NULL;
> +		}
>  
>  		return irq->target_vcpu;
>  	}



>  /* Flush our emulation state into the GIC hardware before entering the guest. */
>  void kvm_vgic_flush_hwstate(struct kvm_vcpu *vcpu)
>  {
> @@ -1106,13 +1131,12 @@ void kvm_vgic_flush_hwstate(struct kvm_vcpu *vcpu)
>  
>  	DEBUG_SPINLOCK_BUG_ON(!irqs_disabled());
>  
> -	scoped_guard(raw_spinlock, &vcpu->arch.vgic_cpu.ap_list_lock)
> -		vgic_flush_lr_state(vcpu);
> +	vgic_flush_state(vcpu);
>  
>  	if (can_access_vgic_from_kernel())
>  		vgic_restore_state(vcpu);
>  
> -	if (vgic_supports_direct_irqs(vcpu->kvm))
> +	if (vgic_supports_direct_irqs(vcpu->kvm) && !vgic_is_v5(vcpu->kvm))

This feels like a somewhat backwards check.
No function to check it vgic_is_v4? Similar cases elsewhere.

>  		vgic_v4_commit(vcpu);
>  }
>  
> diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
> index d5d9264f0a1e9..978d7f8426361 100644
> --- a/arch/arm64/kvm/vgic/vgic.h
> +++ b/arch/arm64/kvm/vgic/vgic.h
> @@ -132,6 +132,28 @@ static inline bool irq_is_pending(struct vgic_irq *irq)
>  		return irq->pending_latch || irq->line_level;
>  }
>  
> +/* Requires the irq_lock to be held by the caller. */

Can you use a lockdep notation to make that explicit?

> +static inline bool irq_is_enabled(struct vgic_irq *irq)
> +{
> +	if (irq->enabled)
> +		return true;
> +
> +	/*
> +	 * We always consider GICv5 interrupts as enabled as we can
> +	 * always inject them. The state is handled by the hardware,
> +	 * and the hardware will only signal the interrupt to the
> +	 * guest once the guest enables it.

With my fussy reviewer hat on, that's wrapped a bit early.  Go up
to 80 chars for comments.

> +	 */
> +	if (irq->target_vcpu) {
> +		u32 vgic_model = irq->target_vcpu->kvm->arch.vgic.vgic_model;
> +
> +		if (vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
> +			return true;
> +	}
> +
> +	return false;
> +}

> diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
> index 500709bd62c8d..b5180edbd1165 100644
> --- a/include/kvm/arm_vgic.h
> +++ b/include/kvm/arm_vgic.h
> @@ -32,6 +32,9 @@
>  #define VGIC_MIN_LPI		8192
>  #define KVM_IRQCHIP_NUM_PINS	(1020 - 32)
>  
> +/* GICv5 constants */
> +#define VGIC_V5_NR_PRIVATE_IRQS	128

You have earlier checks against this value (there was one around PPI DVI setup 
a few patches back).  So probably better to pull the define earlier and
use it there as well?

> +
>  #define is_v5_type(t, i)	(FIELD_GET(GICV5_HWIRQ_TYPE, (i)) == (t))
>  
>  #define __irq_is_sgi(t, i)						\



^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 19/36] KVM: arm64: gic-v5: Check for pending PPIs
  2025-12-19 15:52 ` [PATCH v2 19/36] KVM: arm64: gic-v5: Check for pending PPIs Sascha Bischoff
@ 2026-01-07 15:00   ` Jonathan Cameron
  2026-01-08 16:23     ` Sascha Bischoff
  2026-01-08 16:10   ` Joey Gouly
  1 sibling, 1 reply; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 15:00 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:42 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> This change allows KVM to check for pending PPI interrupts. This has
> two main components:
> 
> First of all, the effective priority mask is calculated.  This is a
> combination of the priority mask in the VPEs ICC_PCR_EL1.PRIORITY and
> the currently running priority as determined from the VPE's
> ICH_APR_EL1. If an interrupt's prioirity is greater than or equal to

priority

> the effective priority mask, it can be signalled. Otherwise, it
> cannot.
> 
> Secondly, any Enabled and Pending PPIs must be checked against this
> compound priority mask. The reqires the PPI priorities to by synced
> back to the KVM shadow state - this is skipped in general operation as
> it isn't required and is rather expensive. If any Enabled and Pending
> PPIs are of sufficient priority to be signalled, then there are
> pending PPIs. Else, there are not.  This ensures that a VPE is not
> woken when it cannot actually process the pending interrupts.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Hi Sascha,

One thing I notice in here is the use of unsigned long vs u64 is a bit
inconsistent.  When it's a register or something we just read from a register
I'd always use u64.

A few other things inline.
> ---
>  arch/arm64/kvm/vgic/vgic-v5.c | 121 ++++++++++++++++++++++++++++++++++
>  arch/arm64/kvm/vgic/vgic.c    |   5 +-
>  arch/arm64/kvm/vgic/vgic.h    |   1 +
>  3 files changed, 126 insertions(+), 1 deletion(-)
> 
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index cb3dd872d16a6..c7ecc4f40b1e5 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> @@ -56,6 +56,31 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
>  	return 0;
>  }
>  
> +static u32 vgic_v5_get_effective_priority_mask(struct kvm_vcpu *vcpu)
> +{
> +	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> +	u32 highest_ap, priority_mask;
> +
> +	/*
> +	 * Counting the number of trailing zeros gives the current
> +	 * active priority. Explicitly use the 32-bit version here as

Short wrap.  I'll stop commenting on these and assume you'll check throughout
(or ignore throughout if you disagree ;) Everyone should use an email
client with rulers!

> +	 * we have 32 priorities. 0x20 then means that there are no
> +	 * active priorities.
> +	 */
> +	highest_ap = cpu_if->vgic_apr ? __builtin_ctz(cpu_if->vgic_apr) : 32;

If the comment is going to say 0x20 means no active, then use hex in the code
as well. Or just use 32 in the comment.

> +
> +	/*
> +	 * An interrupt is of sufficient priority if it is equal to or
> +	 * greater than the priority mask. Add 1 to the priority mask
> +	 * (i.e., lower priority) to match the APR logic before taking
> +	 * the min. This gives us the lowest priority that is masked.
> +	 */
> +	priority_mask = FIELD_GET(FEAT_GCIE_ICH_VMCR_EL2_VPMR, cpu_if->vgic_vmcr);
> +	priority_mask = min(highest_ap, priority_mask + 1);
> +
> +	return priority_mask;

Unless you are going to do more with that in later patches
	return min(highest_ap, priority_mask + 1);
Doesn't lose any significant readability to my eyes.

> +}
> +
>  static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
>  					  struct vgic_irq *irq)
>  {
> @@ -131,6 +156,102 @@ void vgic_v5_set_ppi_ops(struct vgic_irq *irq)
>  	}
>  }
>  
> +
> +/*
> + * Sync back the PPI priorities to the vgic_irq shadow state
> + */
> +static void vgic_v5_sync_ppi_priorities(struct kvm_vcpu *vcpu)
> +{
> +	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> +	int i, reg;
> +
> +	/* We have 16 PPI Priority regs */
> +	for (reg = 0; reg < 16; reg++) {

I'd drag the declaration in as
	for (int ret = 0;

> +		const unsigned long priorityr = cpu_if->vgic_ppi_priorityr[reg];
> +
> +		for (i = 0; i < 8; ++i) {
similar for int i = 0 here

Kernel style is getting more accepting of these 'modern' style things ;)
Up to you though if you prefer old school.

> +			struct vgic_irq *irq;
> +			u32 intid;
> +			u8 priority;
> +
> +			priority = (priorityr >> (i * 8)) & 0x1f;

GENMASK(4, 0); maybe.  It's short enough (I can count to 1 f easily enough!)
that I don't really mind which style you use for this.

> +
> +			intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
> +			intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg * 8 + i);
> +
> +			irq = vgic_get_vcpu_irq(vcpu, intid);
> +
> +			scoped_guard(raw_spinlock, &irq->irq_lock)
> +				irq->priority = priority;
> +
> +			vgic_put_irq(vcpu->kvm, irq);
> +		}
> +	}
> +}
> +
> +bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu)
> +{
> +	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> +	int i, reg;
> +	unsigned int priority_mask;
> +
> +	/* If no pending bits are set, exit early */
> +	if (likely(!cpu_if->vgic_ppi_pendr[0] && !cpu_if->vgic_ppi_pendr[1]))

That likely seems a little bit dubious. I'd be tempted to not mark this
unless you have stats on running systems where the predictors get it wrong
enough that the mark is useful.

> +		return false;
> +
> +	priority_mask = vgic_v5_get_effective_priority_mask(vcpu);
> +
> +	/* If the combined priority mask is 0, nothing can be signalled! */
> +	if (!priority_mask)
> +		return false;
> +
> +	/* The shadow priority is only updated on demand, sync it across first */
> +	vgic_v5_sync_ppi_priorities(vcpu);
> +
> +	for (reg = 0; reg < 2; reg++) {
> +		unsigned long possible_bits;
> +		const unsigned long enabler = cpu_if->vgic_ich_ppi_enabler_exit[reg];
Given storage of vgic_ich_ppi_enabler_exit[reg] is a u64 and you are going to
use that length explicitly (the 64 in the bitmap walk below) I'd make these
u64s.  I've not really been keeping an eye open for this in other patches, so
maybe look for other cases where an explicit length is clearer.
u64 shorter as well!

> +		const unsigned long pendr = cpu_if->vgic_ppi_pendr_exit[reg];
> +		bool has_pending = false;
> +
> +		/* Check all interrupts that are enabled and pending */
> +		possible_bits = enabler & pendr;
> +
> +		/*
> +		 * Optimisation: pending and enabled with no active priorities
> +		 */
> +		if (possible_bits && priority_mask > 0x1f)

I 'think' priority_mask > 0x1f is always 0x20?  I'd match that explicitly so the
relationship to the magic value comment above is obvious

> +			return true;
> +
> +		for_each_set_bit(i, &possible_bits, 64) {
> +			struct vgic_irq *irq;
> +			u32 intid;
> +
> +			intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
> +			intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg * 64 + i);
> +
> +			irq = vgic_get_vcpu_irq(vcpu, intid);
> +
> +			scoped_guard(raw_spinlock, &irq->irq_lock) {
> +				/*
> +				 * We know that the interrupt is
> +				 * enabled and pending, so only check
> +				 * the priority.
> +				 */
> +				if (irq->priority <= priority_mask)
> +					has_pending = true;
> +			}
> +
> +			vgic_put_irq(vcpu->kvm, irq);
> +
> +			if (has_pending)
> +				return true;
> +		}
> +	}
> +
> +	return false;
> +}
> +
>  /*
>   * Detect any PPIs state changes, and propagate the state with KVM's
>   * shadow structures.
> diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
> index cb5d43b34462b..dfec6ed7936ed 100644
> --- a/arch/arm64/kvm/vgic/vgic.c
> +++ b/arch/arm64/kvm/vgic/vgic.c
> @@ -1180,9 +1180,12 @@ int kvm_vgic_vcpu_pending_irq(struct kvm_vcpu *vcpu)
>  	unsigned long flags;
>  	struct vgic_vmcr vmcr;
>  
> -	if (!vcpu->kvm->arch.vgic.enabled)
> +	if (!vcpu->kvm->arch.vgic.enabled && !vgic_is_v5(vcpu->kvm))
>  		return false;
>  
> +	if (vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
> +		return vgic_v5_has_pending_ppi(vcpu);
> +
>  	if (vcpu->arch.vgic_cpu.vgic_v3.its_vpe.pending_last)
>  		return true;
>  
> diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
> index 978d7f8426361..65c031da83e78 100644
> --- a/arch/arm64/kvm/vgic/vgic.h
> +++ b/arch/arm64/kvm/vgic/vgic.h
> @@ -388,6 +388,7 @@ int vgic_v5_probe(const struct gic_kvm_info *info);
>  void vgic_v5_get_implemented_ppis(void);
>  void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
>  int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
> +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);
>  void vgic_v5_load(struct kvm_vcpu *vcpu);



^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 20/36] KVM: arm64: gic-v5: Init Private IRQs (PPIs) for GICv5
  2025-12-19 15:52 ` [PATCH v2 20/36] KVM: arm64: gic-v5: Init Private IRQs (PPIs) for GICv5 Sascha Bischoff
@ 2026-01-07 15:04   ` Jonathan Cameron
  0 siblings, 0 replies; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 15:04 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:42 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> Initialise the private interrupts (PPIs, only) for GICv5. This means
> that a GICv5-style intid is generated (which encodes the PPI type in
> the top bits) instead of the 0-based index that is used for older
> GICs.
> 
> Additionally, set all of the GICv5 PPIs to use Level for the handling
> mode, with the exception of the SW_PPI which uses Edge. This matches
> the architecturally-defined set in the GICv5 specification (the CTIIRQ
> handling mode is IMPDEF, so pick Level has been picked for that).

so Level has been

> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>

Trivial comment inline, which you can feel free to ignore if you like

Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
> ---
>  arch/arm64/kvm/vgic/vgic-init.c    | 41 +++++++++++++++++++++++-------
>  include/linux/irqchip/arm-gic-v5.h |  2 ++
>  2 files changed, 34 insertions(+), 9 deletions(-)
> 
> diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
> index bcc2c79f7833c..03f45816464b0 100644
> --- a/arch/arm64/kvm/vgic/vgic-init.c
> +++ b/arch/arm64/kvm/vgic/vgic-init.c

> @@ -280,22 +286,39 @@ static int vgic_allocate_private_irqs_locked(struct kvm_vcpu *vcpu, u32 type)
>  	 * Enable and configure all SGIs to be edge-triggered and
>  	 * configure all PPIs as level-triggered.
>  	 */
> -	for (i = 0; i < VGIC_NR_PRIVATE_IRQS; i++) {
> +	for (i = 0; i < num_private_irqs; i++) {
>  		struct vgic_irq *irq = &vgic_cpu->private_irqs[i];
>  
>  		INIT_LIST_HEAD(&irq->ap_list);
>  		raw_spin_lock_init(&irq->irq_lock);
> -		irq->intid = i;
>  		irq->vcpu = NULL;
>  		irq->target_vcpu = vcpu;
>  		refcount_set(&irq->refcount, 0);
> -		if (vgic_irq_is_sgi(i)) {
> -			/* SGIs */
> -			irq->enabled = 1;
> -			irq->config = VGIC_CONFIG_EDGE;
> +		if (!vgic_is_v5(vcpu->kvm)) {
> +			irq->intid = i;
> +			if (vgic_irq_is_sgi(i)) {
> +				/* SGIs */
> +				irq->enabled = 1;
> +				irq->config = VGIC_CONFIG_EDGE;
> +			} else {
> +				/* PPIs */
> +				irq->config = VGIC_CONFIG_LEVEL;
> +			}
>  		} else {
> -			/* PPIs */
> -			irq->config = VGIC_CONFIG_LEVEL;
> +			irq->intid = i | FIELD_PREP(GICV5_HWIRQ_TYPE,
> +						    GICV5_HWIRQ_TYPE_PPI);
Trivial:
I'd use FIELD_PREP() even for the ID part.  Makes no difference other than
making it explicit that it's a field that doesn't overlap with the type
one.



^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 21/36] KVM: arm64: gic-v5: Finalize GICv5 PPIs and generate mask
  2025-12-19 15:52 ` [PATCH v2 21/36] KVM: arm64: gic-v5: Finalize GICv5 PPIs and generate mask Sascha Bischoff
@ 2026-01-07 15:08   ` Jonathan Cameron
  2026-01-08 16:51     ` Sascha Bischoff
  0 siblings, 1 reply; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 15:08 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:43 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> We only want to expose a subset of the PPIs to a guest. If a PPI does
> not have an owner, it is not being actively driven by a device. The
> SW_PPI is a special case, as it is likely for userspace to wish to
> inject that.
> 
> Therefore, just prior to running the guest for the first time, we need
> to finalize the PPIs. A mask is generated which, when combined with
> trapping a guest's PPI accesses, allows for the guest's view of the
> PPI to be filtered.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>

Minor suggestion inline. Either way

Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>

> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index c7ecc4f40b1e5..f1fa63e67c1f6 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> @@ -81,6 +81,66 @@ static u32 vgic_v5_get_effective_priority_mask(struct kvm_vcpu *vcpu)
>  	return priority_mask;
>  }
>  
> +static int vgic_v5_finalize_state(struct kvm_vcpu *vcpu)
> +{
> +	if (!ppi_caps)
> +		return -ENXIO;
> +
> +	vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_mask[0] = 0;
> +	vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_mask[1] = 0;
> +	vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[0] = 0;
> +	vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[1] = 0;
> +	for (int i = 0; i < VGIC_V5_NR_PRIVATE_IRQS; ++i) {
> +		int reg = i / 64;
> +		u64 bit = BIT_ULL(i % 64);
> +		struct vgic_irq *irq = &vcpu->arch.vgic_cpu.private_irqs[i];
> +
> +		raw_spin_lock(&irq->irq_lock);
> +
A little nicer perhaps with:
		guard(raw_spin_lock(&irq->irq_lock);
> +		/*
> +		 * We only expose PPIs with an owner or thw SW_PPI to
> +		 * the guest.
> +		 */
> +		if (!irq->owner && irq->intid == GICV5_SW_PPI)
> +			goto unlock;
and
			continue;
> +
> +		/*
> +		 * If the PPI isn't implemented, we can't pass it
> +		 * through to a guest anyhow.
> +		 */
> +		if (!(ppi_caps->impl_ppi_mask[reg] & bit))
> +			goto unlock;
and
			continue;
> +
> +		vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_mask[reg] |= bit;
> +
> +		if (irq->config == VGIC_CONFIG_LEVEL)
> +			vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[reg] |= bit;
> +
> +unlock:
> +		raw_spin_unlock(&irq->irq_lock);
Then the label and unlock can go away.

> +	}
> +
> +	return 0;
> +}



^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 22/36] KVM: arm64: gic-v5: Trap and mask guest PPI register accesses
  2025-12-19 15:52 ` [PATCH v2 22/36] KVM: arm64: gic-v5: Trap and mask guest PPI register accesses Sascha Bischoff
@ 2026-01-07 15:17   ` Jonathan Cameron
  2026-01-09 16:59     ` Sascha Bischoff
  0 siblings, 1 reply; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 15:17 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:43 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> A guest should not be able to detect if a PPI that is not exposed to
> the guest is implemented or not. If the writes to the PPI registers
> are not masked, it becomes possible for the guest to detect the
> presence of all implemented PPIs on the host.
> 
> Guest writes to the following registers are masked:
> 
> ICC_CACTIVERx_EL1
> ICC_SACTIVERx_EL1
> ICC_CPENDRx_EL1
> ICC_SPENDRx_EL1
> ICC_ENABLERx_EL1
> ICC_PRIORITYRx_EL1
> 
> When a guest writes these registers, the write is masked with the set
> of PPIs actually exposed to the guest, and the state is written back
> to KVM's shadow state..

One . seems enough.

> 
> Reads for the above registers are not masked. When the guest is
> running and reads from the above registers, it is presented with what
> KVM provides in the ICH_PPI_x_EL2 registers, which is the masked
> version of what the guest last wrote.
> 
> The ICC_PPI_HMRx_EL1 register is used to determine which PPIs use
> Level-sensitive semantics, and which use Edge. For a GICv5 guest, the
> correct view of the virtual PPIs must be provided to the guest, and
> hence this must also be trapped, but only for reads. The content of
> the HMRs is calculated and masked when finalising the PPI state for
> the guest.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
A few bits inline but nothing significant so I'll assume you tidy those up
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>

> ---
>  arch/arm64/kvm/config.c   |  22 ++++++-
>  arch/arm64/kvm/sys_regs.c | 133 ++++++++++++++++++++++++++++++++++++++
>  2 files changed, 153 insertions(+), 2 deletions(-)
> 
> diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
> index eb0c6f4d95b6d..f81bfdadd12fb 100644
> --- a/arch/arm64/kvm/config.c
> +++ b/arch/arm64/kvm/config.c
> @@ -1586,8 +1586,26 @@ static void __compute_ich_hfgrtr(struct kvm_vcpu *vcpu)
>  {
>  	__compute_fgt(vcpu, ICH_HFGRTR_EL2);
>  
> -	/* ICC_IAFFIDR_EL1 *always* needs to be trapped when running a guest */
> +	/*
> +	 * ICC_IAFFIDR_EL1 and ICH_PPI_HMRx_EL1 *always* needs to be

need to be

> +	 * trapped when running a guest.
> +	 **/

*/

>  	*vcpu_fgt(vcpu, ICH_HFGRTR_EL2) &= ~ICH_HFGRTR_EL2_ICC_IAFFIDR_EL1;
> +	*vcpu_fgt(vcpu, ICH_HFGRTR_EL2) &= ~ICH_HFGRTR_EL2_ICC_PPI_HMRn_EL1;
> +}

> diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
> index 383ada0d75922..cef13bf6bb3a1 100644
> --- a/arch/arm64/kvm/sys_regs.c
> +++ b/arch/arm64/kvm/sys_regs.c
> @@ -696,6 +696,111 @@ static bool access_gicv5_iaffid(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
>  	return true;
>  }
>  
> +static bool access_gicv5_ppi_hmr(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
> +				 const struct sys_reg_desc *r)
> +{
> +	if (p->is_write)
> +		return ignore_write(vcpu, p);
> +
> +	if (p->Op2 == 0) {	/* ICC_PPI_HMR0_EL1 */
> +		p->regval = vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[0];
> +	} else {		/* ICC_PPI_HMR1_EL1 */
> +		p->regval = vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[1];
> +	}

No {} as single line statements in all legs.

However, I'd be tempted to use a local variable for the index like you've
done in many other cases
	
	unsigned int index;

...

	index = p->Op2 == 0 ? 0 : 1;
	p->regval = vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hrm[index];

Or use the p->Op2 % 2 as you do in ppi_enabler.


> +
> +	return true;
> +}
> +
> +static bool access_gicv5_ppi_enabler(struct kvm_vcpu *vcpu,
> +				     struct sys_reg_params *p,
> +				     const struct sys_reg_desc *r)
> +{
> +	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> +	u64 masked_write;
> +
> +	/* We never expect to get here with a read! */
> +	if (WARN_ON_ONCE(!p->is_write))
> +		return undef_access(vcpu, p, r);
> +
> +	masked_write = p->regval & cpu_if->vgic_ppi_mask[p->Op2 % 2];
> +	cpu_if->vgic_ich_ppi_enabler_entry[p->Op2 % 2] = masked_write;
> +
> +	return true;
> +}
> +
> +static bool access_gicv5_ppi_pendr(struct kvm_vcpu *vcpu,
> +				   struct sys_reg_params *p,
> +				   const struct sys_reg_desc *r)
> +{
> +	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> +	u64 masked_write;
> +
> +	/* We never expect to get here with a read! */
> +	if (WARN_ON_ONCE(!p->is_write))
> +		return undef_access(vcpu, p, r);
> +
> +	masked_write = p->regval & cpu_if->vgic_ppi_mask[p->Op2 % 2];
> +
> +	if (p->Op2 & 0x2) {	/* SPENDRx */
> +		cpu_if->vgic_ppi_pendr_entry[p->Op2 % 2] |= masked_write;
> +	} else {		/* CPENDRx */
> +		cpu_if->vgic_ppi_pendr_entry[p->Op2 % 2] &= ~masked_write;
> +	}

No {} wanted in kernel style when all legs are single line statements.
Same applies in a few other cases that follow.

> +
> +	return true;
> +}
> +



^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 23/36] KVM: arm64: gic-v5: Support GICv5 interrupts with KVM_IRQ_LINE
  2025-12-19 15:52 ` [PATCH v2 23/36] KVM: arm64: gic-v5: Support GICv5 interrupts with KVM_IRQ_LINE Sascha Bischoff
@ 2026-01-07 15:29   ` Jonathan Cameron
  2026-01-08 16:53     ` Sascha Bischoff
  0 siblings, 1 reply; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 15:29 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:43 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> Interrupts under GICv5 look quite different to those from older Arm
> GICs. Specifically, the type is encoded in the top bits of the
> interrupt ID.
> 
> Extend KVM_IRQ_LINE to cope with GICv5 PPIs and SPIs. The requires
> subtly changing the KVM_IRQ_LINE API for GICv5 guests. For older Arm
> GICs, PPIs had to be in the range of 16-31, and SPIs had to be
> 32-1019, but this no longer holds true for GICv5. Instead, for a GICv5
> guest support PPIs in the range of 0-127, and SPIs in the range
> 0-65535. The documentation is updated accordingly.
> 
> The SPI range doesn't cover the full SPI range that a GICv5 system can
> potentially cope with (GICv5 provides up to 24-bits of SPI ID space,
> and we only have 16 bits to work with in KVM_IRQ_LINE). However, 65k
> SPIs is more than would be reasonably expected on systems for years to
> come.
> 
> Note: As the GICv5 KVM implementation currently doesn't support
> injecting SPIs attempts to do so will fail. This restruction will

restriction

In general,  worth spell checking the lot. (something I always
forget to do for my own series!)

> lifted as the GICv5 KVM support evolves.
> 
> Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
One passing comment inline. Perhaps there isn't a suitable place to put
vgic_is_v5() though. I haven't checked.

Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>

> ---
>  Documentation/virt/kvm/api.rst |  6 ++++--
>  arch/arm64/kvm/arm.c           | 21 ++++++++++++++++++---
>  arch/arm64/kvm/vgic/vgic.c     |  4 ++++
>  3 files changed, 26 insertions(+), 5 deletions(-)
> 

> diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
> index 94f8d13ab3b58..4448e8a5fc076 100644
> --- a/arch/arm64/kvm/arm.c
> +++ b/arch/arm64/kvm/arm.c
> @@ -45,6 +45,8 @@
>  #include <kvm/arm_pmu.h>
>  #include <kvm/arm_psci.h>
>  
> +#include <linux/irqchip/arm-gic-v5.h>
> +
>  #include "sys_regs.h"
>  
>  static enum kvm_mode kvm_mode = KVM_MODE_DEFAULT;
> @@ -1430,16 +1432,29 @@ int kvm_vm_ioctl_irq_line(struct kvm *kvm, struct kvm_irq_level *irq_level,
>  		if (!vcpu)
>  			return -EINVAL;
>  
> -		if (irq_num < VGIC_NR_SGIS || irq_num >= VGIC_NR_PRIVATE_IRQS)
> +		if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) {

Maybe it's worth moving the vgic_is_v5() helper to somewhere that makes it useable
here?


> +			if (irq_num >= VGIC_V5_NR_PRIVATE_IRQS)
> +				return -EINVAL;
> +
> +			/* Build a GICv5-style IntID here */
> +			irq_num |= FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
> +		} else if (irq_num < VGIC_NR_SGIS ||
> +			   irq_num >= VGIC_NR_PRIVATE_IRQS) {
>  			return -EINVAL;
> +		}
>  
>  		return kvm_vgic_inject_irq(kvm, vcpu, irq_num, level, NULL);



^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 24/36] KVM: arm64: gic-v5: Create, init vgic_v5
  2025-12-19 15:52 ` [PATCH v2 24/36] KVM: arm64: gic-v5: Create, init vgic_v5 Sascha Bischoff
@ 2026-01-07 15:49   ` Jonathan Cameron
  2026-01-08 16:55     ` Sascha Bischoff
  0 siblings, 1 reply; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 15:49 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:44 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> Update kvm_vgic_create to create a vgic_v5 device. When creating a
> vgic, FEAT_GCIE in the ID_AA64PFR2 is only exposed to vgic_v5-based
> guests, and is hidden otherwise. GIC in ~ID_AA64PFR0_EL1 is never
> exposed for a vgic_v5 guest.
> 
> When initialising a vgic_v5, skip kvm_vgic_dist_init as GICv5 doesn't
> support one. The current vgic_v5 implementation only supports PPIs, so
> no SPIs are initialised either.
> 
> The current vgic_v5 support doesn't extend to nested

Odd early wrapping of message.

> guests. Therefore, the init of vgic_v5 for a nested guest is failed in
> vgic_v5_init.
> 
> As the current vgic_v5 doesn't require any resources to be mapped,
> vgic_v5_map_resources is simply used to check that the vgic has indeed
> been initialised. Again, this will change as more GICv5 support is
> merged in.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Comments mostly on existing code, so
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>

> ---
>  arch/arm64/kvm/vgic/vgic-init.c | 51 ++++++++++++++++++++++-----------
>  arch/arm64/kvm/vgic/vgic-v5.c   | 26 +++++++++++++++++
>  arch/arm64/kvm/vgic/vgic.h      |  2 ++
>  include/kvm/arm_vgic.h          |  1 +
>  4 files changed, 63 insertions(+), 17 deletions(-)
> 
> diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
> index 03f45816464b0..afb5888cd8219 100644
> --- a/arch/arm64/kvm/vgic/vgic-init.c
> +++ b/arch/arm64/kvm/vgic/vgic-init.c

>  	if (type == KVM_DEV_TYPE_ARM_VGIC_V3)
> @@ -420,20 +427,26 @@ int vgic_init(struct kvm *kvm)
>  	if (kvm->created_vcpus != atomic_read(&kvm->online_vcpus))
>  		return -EBUSY;
>  
> -	/* freeze the number of spis */
> -	if (!dist->nr_spis)
> -		dist->nr_spis = VGIC_NR_IRQS_LEGACY - VGIC_NR_PRIVATE_IRQS;
> +	if (!vgic_is_v5(kvm)) {
> +		/* freeze the number of spis */
> +		if (!dist->nr_spis)
> +			dist->nr_spis = VGIC_NR_IRQS_LEGACY - VGIC_NR_PRIVATE_IRQS;
>  
> -	ret = kvm_vgic_dist_init(kvm, dist->nr_spis);
> -	if (ret)
> -		goto out;
> +		ret = kvm_vgic_dist_init(kvm, dist->nr_spis);
> +		if (ret)
> +			goto out;

Not really related to this patch, but I have no idea why this function
doesn't just do early returns on error in all paths (rather than just some of them).
It might be worth changing that to improve readability.


>  
> -	/*
> -	 * Ensure vPEs are allocated if direct IRQ injection (e.g. vSGIs,
> -	 * vLPIs) is supported.
> -	 */
> -	if (vgic_supports_direct_irqs(kvm)) {
> -		ret = vgic_v4_init(kvm);
> +		/*
> +		 * Ensure vPEs are allocated if direct IRQ injection (e.g. vSGIs,
> +		 * vLPIs) is supported.
> +		 */
> +		if (vgic_supports_direct_irqs(kvm)) {
> +			ret = vgic_v4_init(kvm);
> +			if (ret)
> +				goto out;
> +		}
> +	} else {
> +		ret = vgic_v5_init(kvm);
>  		if (ret)
>  			goto out;
>  	}
> @@ -610,9 +623,13 @@ int kvm_vgic_map_resources(struct kvm *kvm)
>  	if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V2) {
>  		ret = vgic_v2_map_resources(kvm);
>  		type = VGIC_V2;
> -	} else {
> +	} else if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3) {
>  		ret = vgic_v3_map_resources(kvm);
>  		type = VGIC_V3;
> +	} else {
> +		ret = vgic_v5_map_resources(kvm);
> +		type = VGIC_V5;
> +		goto out;
This skips over the checking of ret which is fine (given it's just goto out)
but I'd add a comment to say why the next bit is skipped or a more complex
flow (maybe a flag to say dist is relevant that gates the next bit.

>  	}
>  
>  	if (ret)




^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 25/36] KVM: arm64: gic-v5: Reset vcpu state
  2025-12-19 15:52 ` [PATCH v2 25/36] KVM: arm64: gic-v5: Reset vcpu state Sascha Bischoff
@ 2026-01-07 15:51   ` Jonathan Cameron
  0 siblings, 0 replies; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 15:51 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:44 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> Limit the number of ID and priority bits supported based on the
> hardware capabilities when resetting the vcpu state.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 26/36] KVM: arm64: gic-v5: Bump arch timer for GICv5
  2025-12-19 15:52 ` [PATCH v2 26/36] KVM: arm64: gic-v5: Bump arch timer for GICv5 Sascha Bischoff
@ 2026-01-07 16:08   ` Jonathan Cameron
  2026-01-09 16:56     ` Sascha Bischoff
  0 siblings, 1 reply; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 16:08 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:45 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> Now that GICv5 has arrived, the arch timer requires some TLC to
> address some of the key differences introduced with GICv5.
> 
> For PPIs on GICv5, the set_pending_state and queue_irq_unlock irq_ops
> are used as AP lists are not required at all for GICv5. The arch timer
> also introduces an irq_op - get_input_level. Extend the
> arch-timer-provided irq_ops to include the two PPI ops for vgic_v5
> guests.
> 
> When possible, DVI (Direct Virtual Interrupt) is set for PPIs when
> using a vgic_v5, which directly inject the pending state in to the

into ?

> guest. This means that the host never sees the interrupt for the guest
> for these interrupts. This has two impacts.
> 
> * First of all, the kvm_cpu_has_pending_timer check is updated to
>   explicitly check if the timers are expected to fire.
> 
> * Secondly, for mapped timers (which use DVI) they must be masked on
>   the host prior to entering a GICv5 guest, and unmasked on the return
>   path. This is handled in set_timer_irq_phys_masked.
> 
> The final, but rather important, change is that the architected PPIs
> for the timers are made mandatory for a GICv5 guest. Attempts to set
> them to anything else are actively rejected. Once a vgic_v5 is
> initialised, the arch timer PPIs are also explicitly reinitialised to
> ensure the correct GICv5-compatible PPIs are used - this also adds in
> the GICv5 PPI type to the intid.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Various comments inline. 

J
> ---
>  arch/arm64/kvm/arch_timer.c     | 110 ++++++++++++++++++++++++++------
>  arch/arm64/kvm/vgic/vgic-init.c |   9 +++
>  arch/arm64/kvm/vgic/vgic-v5.c   |   8 +--
>  include/kvm/arm_arch_timer.h    |   7 +-
>  include/kvm/arm_vgic.h          |   4 ++
>  5 files changed, 115 insertions(+), 23 deletions(-)
> 
> diff --git a/arch/arm64/kvm/arch_timer.c b/arch/arm64/kvm/arch_timer.c
> index 6f033f6644219..78d66a67b34ac 100644
> --- a/arch/arm64/kvm/arch_timer.c
> +++ b/arch/arm64/kvm/arch_timer.c


>  void kvm_timer_sync_nested(struct kvm_vcpu *vcpu)
> @@ -1034,12 +1079,15 @@ void kvm_timer_vcpu_reset(struct kvm_vcpu *vcpu)
>  	if (timer->enabled) {
>  		for (int i = 0; i < nr_timers(vcpu); i++)
>  			kvm_timer_update_irq(vcpu, false,
> -					     vcpu_get_timer(vcpu, i));
> +					vcpu_get_timer(vcpu, i));

Unrelated change, and a bad one at that!


>  
>  		if (irqchip_in_kernel(vcpu->kvm)) {
> -			kvm_vgic_reset_mapped_irq(vcpu, timer_irq(map.direct_vtimer));
> +			kvm_vgic_reset_mapped_irq(
> +				vcpu, timer_irq(map.direct_vtimer));

Also unrelated and not a good change.

>  			if (map.direct_ptimer)
> -				kvm_vgic_reset_mapped_irq(vcpu, timer_irq(map.direct_ptimer));
> +				kvm_vgic_reset_mapped_irq(
> +					vcpu,
> +					timer_irq(map.direct_ptimer));

Leave all these alone.

>  		}
>  	}
>  
> @@ -1092,10 +1140,19 @@ void kvm_timer_vcpu_init(struct kvm_vcpu *vcpu)
>  		      HRTIMER_MODE_ABS_HARD);
>  }
>  
> +/*
> + * This is always called during kvm_arch_init_vm, but will also be
> + * called from kvm_vgic_create if we have a vGICv5.
> + */
>  void kvm_timer_init_vm(struct kvm *kvm)
>  {
> +	/*
> +	 * Set up the default PPIs - note that we adjust them based on
> +	 * the model of the GIC as GICv5 uses a different way to
> +	 * describing interrupts.
> +	 */
>  	for (int i = 0; i < NR_KVM_TIMERS; i++)
> -		kvm->arch.timer_data.ppi[i] = default_ppi[i];
> +		kvm->arch.timer_data.ppi[i] = get_vgic_ppi(kvm, default_ppi[i]);
>  }
>  
>  void kvm_timer_cpu_up(void)
> @@ -1347,6 +1404,7 @@ static int kvm_irq_init(struct arch_timer_kvm_info *info)
>  		}
>  
>  		arch_timer_irq_ops.flags |= VGIC_IRQ_SW_RESAMPLE;
> +		arch_timer_irq_ops_vgic_v5.flags |= VGIC_IRQ_SW_RESAMPLE;
>  		WARN_ON(irq_domain_push_irq(domain, host_vtimer_irq,
>  					    (void *)TIMER_VTIMER));
>  	}
> @@ -1497,10 +1555,13 @@ static bool timer_irqs_are_valid(struct kvm_vcpu *vcpu)
>  			break;
>  
>  		/*
> -		 * We know by construction that we only have PPIs, so
> -		 * all values are less than 32.
> +		 * We know by construction that we only have PPIs, so all values
> +		 * are less than 32 for non-GICv5 vgics. On GICv5, they are

VGICs maybe?  It's not consistent in existing comments in this file though.

> +		 * architecturally defined to be under 32 too. However, we mask
> +		 * off most of the bits as we might be presented with a GICv5
> +		 * style PPI where the type is encoded in the top-bits.
>  		 */
> -		ppis |= BIT(irq);
> +		ppis |= BIT(irq & 0x1f);
>  	}
>  
>  	valid = hweight32(ppis) == nr_timers(vcpu);
> @@ -1538,7 +1599,9 @@ int kvm_timer_enable(struct kvm_vcpu *vcpu)
>  {
>  	struct arch_timer_cpu *timer = vcpu_timer(vcpu);
>  	struct timer_map map;
> +	struct irq_ops *ops;
>  	int ret;
> +	int irq;
Might as well put irq on same line as ret

>  
>  	if (timer->enabled)
>  		return 0;
> @@ -1556,20 +1619,22 @@ int kvm_timer_enable(struct kvm_vcpu *vcpu)
>  		return -EINVAL;
>  	}
>  
> +	ops = vgic_is_v5(vcpu->kvm) ? &arch_timer_irq_ops_vgic_v5 :
> +				      &arch_timer_irq_ops;
> +
>  	get_timer_map(vcpu, &map);
>  
> -	ret = kvm_vgic_map_phys_irq(vcpu,
> -				    map.direct_vtimer->host_timer_irq,
> -				    timer_irq(map.direct_vtimer),
> -				    &arch_timer_irq_ops);
> +	irq = timer_irq(map.direct_vtimer);
> +	ret = kvm_vgic_map_phys_irq(vcpu, map.direct_vtimer->host_timer_irq,
> +				    irq, ops);

As irq is only used with this value in here, I'd avoid having the local variable
that changes meaning.

	ret = kvm_vgic_map_phys_irq(vcpu, map.direct_vtimer->host_timer_irq,
				    timer_irq(map.direct_vtimer), ops);
>  	if (ret)
>  		return ret;
>  
>  	if (map.direct_ptimer) {
> +		irq = timer_irq(map.direct_ptimer);
>  		ret = kvm_vgic_map_phys_irq(vcpu,
>  					    map.direct_ptimer->host_timer_irq,
> -					    timer_irq(map.direct_ptimer),
> -					    &arch_timer_irq_ops);
> +					    irq, ops);
As above
					    timer_irq(map.direct_ptimer), ops);

Doesn't make it much harder to read and avoids the local variable
being needed.
>  	}
>  
>  	if (ret)
> @@ -1627,6 +1692,15 @@ int kvm_arm_timer_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
>  		goto out;
>  	}
>  
> +	/*
> +	 * The PPIs for the Arch Timers arch architecturally defined for
> +	 * GICv5. Reject anything that changes them from the specified value.
> +	 */
> +	if (vgic_is_v5(vcpu->kvm) && vcpu->kvm->arch.timer_data.ppi[idx] != irq) {
> +		ret = -EINVAL;
> +		goto out;

Whilst you are here, maybe throw some guard() magic dust at this and do a direct return?
Or leave it for someone else who has more spare time ;)

> +	}
> +
>  	/*
>  	 * We cannot validate the IRQ unicity before we run, so take it at
>  	 * face value. The verdict will be given on first vcpu run, for each

> diff --git a/include/kvm/arm_arch_timer.h b/include/kvm/arm_arch_timer.h
> index 7310841f45121..6cb9c20f9db65 100644
> --- a/include/kvm/arm_arch_timer.h
> +++ b/include/kvm/arm_arch_timer.h

>  
>  struct arch_timer_context {
> @@ -130,6 +132,9 @@ void kvm_timer_init_vhe(void);
>  #define timer_vm_data(ctx)		(&(timer_context_to_vcpu(ctx)->kvm->arch.timer_data))
>  #define timer_irq(ctx)			(timer_vm_data(ctx)->ppi[arch_timer_ctx_index(ctx)])
>  
> +#define get_vgic_ppi(k, i) (((k)->arch.vgic.vgic_model != KVM_DEV_TYPE_ARM_VGIC_V5) ? \
> +				(i) : ((i) | FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI)))

Similar to earlier comment I'd use FIELD_PREP() for i as well but not that important
I'm just lazy about remembering where the numbers go.



>  



^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 27/36] KVM: arm64: gic-v5: Mandate architected PPI for PMU emulation on GICv5
  2025-12-19 15:52 ` [PATCH v2 27/36] KVM: arm64: gic-v5: Mandate architected PPI for PMU emulation on GICv5 Sascha Bischoff
  2026-01-06 15:06   ` Joey Gouly
@ 2026-01-07 16:11   ` Jonathan Cameron
  1 sibling, 0 replies; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 16:11 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:45 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> Make it mandatory to use the architected PPI when running a GICv5
> guest. Attempts to set anything other than the architected PPI (23)
> are rejected.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> ---
>  arch/arm64/kvm/pmu-emul.c | 14 ++++++++++++--
>  include/kvm/arm_pmu.h     |  5 ++++-
>  2 files changed, 16 insertions(+), 3 deletions(-)
> 
> diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
> index afc838ea2503e..2d3b50dec6c5d 100644
> --- a/arch/arm64/kvm/pmu-emul.c
> +++ b/arch/arm64/kvm/pmu-emul.c
> @@ -962,8 +962,13 @@ static int kvm_arm_pmu_v3_init(struct kvm_vcpu *vcpu)
>  		if (!vgic_initialized(vcpu->kvm))
>  			return -ENODEV;
>  
> -		if (!kvm_arm_pmu_irq_initialized(vcpu))
> -			return -ENXIO;
> +		if (!kvm_arm_pmu_irq_initialized(vcpu)) {
> +			/* Use the architected irq number for GICv5. */
> +			if (vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
> +				vcpu->arch.pmu.irq_num = KVM_ARMV8_PMU_GICV5_IRQ;
> +			else
> +				return -ENXIO;
Might as well flip logic and exit quickly on error.

			if (vcpu->kvm->arch.vgic.vgic_model != KVM_DEV_TYPE_ARM_VGIC_V5)
As before it might be nice to make the helper for this visible.

				return -ENXIO;

			vcpu->arch.pmu.irq_num = KVM_ARMV8_PMU_GICV5_IRQl;
		}
> +		}



^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 28/36] KVM: arm64: gic: Hide GICv5 for protected guests
  2025-12-19 15:52 ` [PATCH v2 28/36] KVM: arm64: gic: Hide GICv5 for protected guests Sascha Bischoff
@ 2026-01-07 16:12   ` Jonathan Cameron
  0 siblings, 0 replies; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 16:12 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:45 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> We don't support running protected guest with GICv5 at the
> moment. Therefore, be sure that we don't expose it to the guest at all

Tidy up line wrap to 75 ish chars.

> by actively hiding it when running a protected guest.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Seems fine to me, but I know almost nothing about protected guests, so no tag.



^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 29/36] KVM: arm64: gic-v5: Hide FEAT_GCIE from NV GICv5 guests
  2025-12-19 15:52 ` [PATCH v2 29/36] KVM: arm64: gic-v5: Hide FEAT_GCIE from NV GICv5 guests Sascha Bischoff
@ 2026-01-07 16:13   ` Jonathan Cameron
  0 siblings, 0 replies; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 16:13 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:46 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> Currently, NV guests are not supported with GICv5. Therefore, make
> sure that FEAT_GCIE is always hidden from such guests.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 30/36] KVM: arm64: gic-v5: Introduce kvm_arm_vgic_v5_ops and register them
  2025-12-19 15:52 ` [PATCH v2 30/36] KVM: arm64: gic-v5: Introduce kvm_arm_vgic_v5_ops and register them Sascha Bischoff
@ 2026-01-07 16:19   ` Jonathan Cameron
  0 siblings, 0 replies; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 16:19 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:46 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> Only the KVM_DEV_ARM_VGIC_GRP_CTRL->KVM_DEV_ARM_VGIC_CTRL_INIT op is
> currently supported. All other ops are stubbed out.
> 
> Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Trivial stuff only from me on this one.


> ---
>  arch/arm64/kvm/vgic/vgic-kvm-device.c | 72 +++++++++++++++++++++++++++
>  include/linux/kvm_host.h              |  1 +
>  2 files changed, 73 insertions(+)
> 
> diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c
> index b12ba99a423e5..78903182bba08 100644
> --- a/arch/arm64/kvm/vgic/vgic-kvm-device.c
> +++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c
> @@ -336,6 +336,9 @@ int kvm_register_vgic_device(unsigned long type)
>  			break;
>  		ret = kvm_vgic_register_its_device();
>  		break;
> +	case KVM_DEV_TYPE_ARM_VGIC_V5:
> +		ret = kvm_register_device_ops(&kvm_arm_vgic_v5_ops,
> +					      KVM_DEV_TYPE_ARM_VGIC_V5);

I'd stick to existing style and have a break for last case as well.

>  	}
>  
>  	return ret;
> @@ -715,3 +718,72 @@ struct kvm_device_ops kvm_arm_vgic_v3_ops = {
>  	.get_attr = vgic_v3_get_attr,
>  	.has_attr = vgic_v3_has_attr,
>  };
> +
> +static int vgic_v5_set_attr(struct kvm_device *dev,
> +			    struct kvm_device_attr *attr)
> +{
> +	switch (attr->group) {
> +	case KVM_DEV_ARM_VGIC_GRP_ADDR:
> +	case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
> +	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);
bonus space before vgic

> +		default:
> +			break;
> +		}
> +	}
> +
> +	return -ENXIO;

I'd do this where you have breaks above. Makes it easier to follow
code flow. (a tiny bit anyway!)
Similar for other cases. Style in the file is pretty random wrt to
errors in switch statements so up to you on which one to pick.

> +}

> diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
> index d93f75b05ae22..d6082f06ccae3 100644
> --- a/include/linux/kvm_host.h
> +++ b/include/linux/kvm_host.h
> @@ -2368,6 +2368,7 @@ void kvm_unregister_device_ops(u32 type);
>  extern struct kvm_device_ops kvm_mpic_ops;
>  extern struct kvm_device_ops kvm_arm_vgic_v2_ops;
>  extern struct kvm_device_ops kvm_arm_vgic_v3_ops;
> +extern struct kvm_device_ops kvm_arm_vgic_v5_ops;
>  
>  #ifdef CONFIG_HAVE_KVM_CPU_RELAX_INTERCEPT
>  



^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 32/36] irqchip/gic-v5: Check if impl is virt capable
  2025-12-19 15:52 ` [PATCH v2 32/36] irqchip/gic-v5: Check if impl is virt capable Sascha Bischoff
@ 2026-01-07 16:21   ` Jonathan Cameron
  0 siblings, 0 replies; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 16:21 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:47 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> Now that there is support for creating a GICv5-based guest with KVM,
> check that the hardware itself supports virtualisation, skipping the
> setting of struct gic_kvm_info if not.
> 
> Note: If native GICv5 virt is not supported, then nor is
> FEAT_GCIE_LEGACY, so we are able to skip altogether.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> Reviewed-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 33/36] KVM: arm64: gic-v5: Probe for GICv5 device
  2025-12-19 15:52 ` [PATCH v2 33/36] KVM: arm64: gic-v5: Probe for GICv5 device Sascha Bischoff
@ 2026-01-07 16:25   ` Jonathan Cameron
  2026-01-09 15:00   ` Joey Gouly
  1 sibling, 0 replies; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 16:25 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:47 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> The basic GICv5 PPI support is now complete. Allow probing for a
> native GICv5 rather than just the legacy support.
> 
> The implementation doesn't support protected VMs with GICv5 at this
> time. Therefore, if KVM has protected mode enabled the native GICv5
> init is skipped, but legacy VMs are allowed if the hardware supports
> it.
> 
> At this stage the GICv5 KVM implementation only supports PPIs, and
> doesn't interact with the host IRS at all. This means that there is no
> need to check how many concurrent VMs or vCPUs per VM are supported by
> the IRS - the PPI support only requires the CPUIF. The support is
> artificially limited to VGIC_V5_MAX_CPUS, i.e. 512, vCPUs per VM.
> 
> With this change it becomes possible to run basic GICv5-based VMs,
> provided that they only use PPIs.
> 
> Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 34/36] Documentation: KVM: Introduce documentation for VGICv5
  2025-12-19 15:52 ` [PATCH v2 34/36] Documentation: KVM: Introduce documentation for VGICv5 Sascha Bischoff
@ 2026-01-07 16:27   ` Jonathan Cameron
  0 siblings, 0 replies; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 16:27 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:47 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> Now that it is possible to create a VGICv5 device, provide initial
> documentation for it. At this stage, there is little to document.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Seems fine to me
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 35/36] KVM: arm64: selftests: Introduce a minimal GICv5 PPI selftest
  2025-12-19 15:52 ` [PATCH v2 35/36] KVM: arm64: selftests: Introduce a minimal GICv5 PPI selftest Sascha Bischoff
@ 2026-01-07 16:38   ` Jonathan Cameron
  0 siblings, 0 replies; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 16:38 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:48 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> This basic selftest creates a vgic_v5 device (if supported), and tests
> that one of the PPI interrupts works as expected with a basic
> single-vCPU guest.
> 
> Upon starting, the guest enables interrupts. That means that it is
> initialising all PPIs to have reasonable priorities, but marking them
> as disabled. Then the priority mask in the ICC_PCR_EL1 is set, and
> interrupts are enable in ICC_CR0_EL1. At this stage the guest is able
> to recieve interrupts. The first IMPDEF PPI (64) is enabled and
> kvm_irq_line is used to inject the state into the guest.
> 
> The guest's interrupt handler has an explicit WFI in order to ensure
> that the guest skips WFI when there are pending and enabled PPI
> interrupts.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Hi Sascha,

A few comments inline.

> diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
> new file mode 100644
> index 0000000000000..5879fbd71042d
> --- /dev/null
> +++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
> @@ -0,0 +1,248 @@

> +static void test_vgic_v5_ppis(uint32_t gic_dev_type)
> +{
> +	struct ucall uc;
> +	struct kvm_vcpu *vcpus[NR_VCPUS];
> +	struct vm_gic v;
> +	int ret, i;
> +
> +	v.gic_dev_type = gic_dev_type;
> +	v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
> +
> +	v.gic_fd = kvm_create_device(v.vm, gic_dev_type);
> +
> +	for (i = 0; i < NR_VCPUS; ++i)
> +		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);
> +
> +	for (i = 0; i < NR_VCPUS; i++)
> +		vcpu_init_descriptor_tables(vcpus[i]);
> +
> +	kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
> +			    KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
> +
> +	while (1) {
> +		ret = run_vcpu(vcpus[0]);
> +
> +		switch (get_ucall(vcpus[0], &uc)) {
> +		case UCALL_SYNC:
> +			/*
> +			 * The guest is ready for the next level
> +			 * change. Set high if ready, and lower if it

Odd line wrap. Go to 80 chars.

> +			 * has been consumed.
> +			 */
> +			if (uc.args[1] == GUEST_CMD_IS_READY ||
> +			    uc.args[1] == GUEST_CMD_IRQ_DIEOI) {
> +				u64 irq = 64;
> +				bool level = uc.args[1] == GUEST_CMD_IRQ_DIEOI ? 0 : 1;
> +
> +				irq &= KVM_ARM_IRQ_NUM_MASK;
Can use FIELD_PREP in tools. Seems likely to be useful here.

> +				irq |= KVM_ARM_IRQ_TYPE_PPI << KVM_ARM_IRQ_TYPE_SHIFT;
> +
> +				_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");
> +			} else if (uc.args[1] == GUEST_CMD_IRQ_CDIA) {
> +				pr_info("Guest acknowledged IRQ\n");
> +			}
> +
> +			continue;
> +		case UCALL_ABORT:
> +			REPORT_GUEST_ASSERT(uc);
> +			break;
> +		case UCALL_DONE:
> +			goto done;
> +		default:
> +			TEST_FAIL("Unknown ucall %lu", uc.cmd);
> +		}
> +	}
> +
> +done:
> +	TEST_ASSERT(ret == 0, "Failed to test GICv5 PPIs");
> +
> +	vm_gic_destroy(&v);
> +}
> +
> +/*
> + * Returns 0 if it's possible to create GIC device of a given type (V2 or V3).

Comment needs an update given you pass in v5

Maybe worth pulling this out as a library function for both sets of tests.
If not, rip out the v2, v3 code from here and the type parameter as that is
all code that will bit rot.

> + */
> +int test_kvm_device(uint32_t gic_dev_type)
> +{
> +	struct kvm_vcpu *vcpus[NR_VCPUS];
> +	struct vm_gic v;
> +	uint32_t other;
> +	int ret;
> +
> +	v.vm = vm_create_with_vcpus(NR_VCPUS, guest_code, vcpus);
> +
> +	/* try to create a non existing KVM device */
> +	ret = __kvm_test_create_device(v.vm, 0);
> +	TEST_ASSERT(ret && errno == ENODEV, "unsupported device");
> +
> +	/* trial mode */
> +	ret = __kvm_test_create_device(v.vm, gic_dev_type);
> +	if (ret)
> +		return ret;
> +	v.gic_fd = kvm_create_device(v.vm, gic_dev_type);
> +
> +	ret = __kvm_create_device(v.vm, gic_dev_type);
> +	TEST_ASSERT(ret < 0 && errno == EEXIST, "create GIC device twice");
> +
> +	/* try to create the other gic_dev_types */
> +	other = KVM_DEV_TYPE_ARM_VGIC_V2;
> +	if (!__kvm_test_create_device(v.vm, other)) {
> +		ret = __kvm_create_device(v.vm, other);
> +		TEST_ASSERT(ret < 0 && (errno == EINVAL || errno == EEXIST),
> +				"create GIC device while other version exists");
> +	}
> +
> +	other = KVM_DEV_TYPE_ARM_VGIC_V3;
> +	if (!__kvm_test_create_device(v.vm, other)) {
> +		ret = __kvm_create_device(v.vm, other);
> +		TEST_ASSERT(ret < 0 && (errno == EINVAL || errno == EEXIST),
> +				"create GIC device while other version exists");
> +	}
> +
> +	other = KVM_DEV_TYPE_ARM_VGIC_V5;
> +	if (!__kvm_test_create_device(v.vm, other)) {
> +		ret = __kvm_create_device(v.vm, other);
> +		TEST_ASSERT(ret < 0 && (errno == EINVAL || errno == EEXIST),
> +				"create GIC device while other version exists");
> +	}
> +
> +	vm_gic_destroy(&v);
> +
> +	return 0;
> +}

> +
> +int main(int ac, char **av)
> +{
> +	int ret;
> +	int pa_bits;
> +	int cnt_impl = 0;
> +
> +	test_disable_default_vgic();
> +
> +	pa_bits = vm_guest_mode_params[VM_MODE_DEFAULT].pa_bits;
> +	max_phys_size = 1ULL << pa_bits;
> +
> +	ret = test_kvm_device(KVM_DEV_TYPE_ARM_VGIC_V5);
> +	if (!ret) {
> +		pr_info("Running VGIC_V5 tests.\n");
> +		run_tests(KVM_DEV_TYPE_ARM_VGIC_V5);
> +		cnt_impl++;
> +	} else {
> +		pr_info("No GICv5 support; Not running GIC_v5 tests.\n");
> +		exit(KSFT_SKIP);
> +	}

Flip to exit early on no device.

	if (ret) {
		pr_info("..);
		exit(KSFT_SKIP);
	}

	pr_info(...);
	run_tests(...
..

	return 0;

> +
> +	return 0;
> +}
> +
> +

Bonus blank line at end of file. One is fine.

> diff --git a/tools/testing/selftests/kvm/include/arm64/gic_v5.h b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
> new file mode 100644
> index 0000000000000..5daaa84318bb1
> --- /dev/null
> +++ b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
> @@ -0,0 +1,148 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +
> +#ifndef __SELFTESTS_GIC_V5_H
> +#define __SELFTESTS_GIC_V5_H
> +
> +#include <asm/barrier.h>
> +#include <asm/sysreg.h>
> +
> +#include <linux/bitfield.h>
> +
> +#include "processor.h"

> +
> +/* Definitions for GICR CDIA */
> +#define GICV5_GIC_CDIA_VALID_MASK	BIT_ULL(32)
> +#define GICV5_GICR_CDIA_VALID(r)	FIELD_GET(GICV5_GIC_CDIA_VALID_MASK, r)
> +#define GICV5_GIC_CDIA_TYPE_MASK	GENMASK_ULL(31, 29)
> +#define GICV5_GIC_CDIA_ID_MASK		GENMASK_ULL(23, 0)
> +#define GICV5_GIC_CDIA_INTID		GENMASK_ULL(31, 0)
> +
> +/* Definitions for GICR CDNMIA */
> +#define GICV5_GIC_CDNMIA_VALID_MASK	BIT_ULL(32)
> +#define GICV5_GICR_CDNMIA_VALID(r)	FIELD_GET(GICV5_GIC_CDNMIA_VALID_MASK, r)
> +#define GICV5_GIC_CDNMIA_TYPE_MASK	GENMASK_ULL(31, 29)
> +#define GICV5_GIC_CDNMIA_ID_MASK	GENMASK_ULL(23, 0)

If we are updating the sysreg.h ones, remember to add R here as well.





^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 36/36] KVM: arm64: gic-v5: Communicate userspace-drivable PPIs via a UAPI
  2025-12-19 15:52 ` [PATCH v2 36/36] KVM: arm64: gic-v5: Communicate userspace-drivable PPIs via a UAPI Sascha Bischoff
@ 2026-01-07 16:51   ` Jonathan Cameron
  2026-01-09 17:00     ` Sascha Bischoff
  0 siblings, 1 reply; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-07 16:51 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, lpieralisi@kernel.org, Timothy Hayes

On Fri, 19 Dec 2025 15:52:48 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> GICv5 systems will likely not support the full set of PPIs. The
> presence of any virtual PPI is tied to the presence of the physical
> PPI. Therefore, the available PPIs will be limited by the physical
> host. Userspace cannot drive any PPIs that are not implemented.
> 
> Moreover, it is not desirable to expose all PPIs to the guest in the
> first place, even if they are supported in hardware. Some devices,
> such as the arch timer, are implemented in KVM, and hence those PPIs
> shouldn't be driven by userspace, either.
> 
> Provided a new UAPI:
>   KVM_DEV_ARM_VGIC_GRP_CTRL => KVM_DEV_ARM_VGIC_USERPSPACE_PPIs
> 
> This allows userspace to query which PPIs it is able to drive via
> KVM_IRQ_LINE.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>

A couple of trivial comments on this patch.

Overall, to me as a definite non expert in kvm GIC emulation this
series looks to be in a pretty good state.

Thanks,

Jonathan
> ---
>  .../virt/kvm/devices/arm-vgic-v5.rst          | 13 ++++++++++
>  arch/arm64/include/uapi/asm/kvm.h             |  1 +
>  arch/arm64/kvm/vgic/vgic-kvm-device.c         | 25 +++++++++++++++++++
>  arch/arm64/kvm/vgic/vgic-v5.c                 |  8 ++++++
>  include/kvm/arm_vgic.h                        |  5 ++++
>  include/linux/irqchip/arm-gic-v5.h            |  4 +++
>  tools/arch/arm64/include/uapi/asm/kvm.h       |  1 +
>  7 files changed, 57 insertions(+)
> 
> diff --git a/Documentation/virt/kvm/devices/arm-vgic-v5.rst b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
> index 9904cb888277d..d9f2917b609c5 100644
> --- a/Documentation/virt/kvm/devices/arm-vgic-v5.rst
> +++ b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
> @@ -25,6 +25,19 @@ Groups:
>        request the initialization of the VGIC, no additional parameter in
>        kvm_device_attr.addr. Must be called after all VCPUs have been created.
>  
> +   KVM_DEV_ARM_VGIC_USERPSPACE_PPIs
> +      request the mask of userspace-drivable PPIs. Only a subset of the PPIs can
> +      be directly driven from userspace with GICv5, and the returned mask
> +      informs userspace of which it is allowed to drive via KVM_IRQ_LINE.
> +
> +      Userspace must allocate and point to __u64[2] with of data in

with of?

> +      kvm_device_attr.addr. When this call returns, the provided memory will be
> +      populated with the userspace PPI mask. The lower __u64 contains the mask
> +      for the lower 64 PPIS, with the remaining 64 being in the second __u64.
> +
> +      This is a read-only attribute, and cannot be set. Attempts to set it are
> +      rejected.
> +
>    Errors:
>  
>      =======  ========================================================

> diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c
> index 78903182bba08..360c78ed4f104 100644
> --- a/arch/arm64/kvm/vgic/vgic-kvm-device.c
> +++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c
> @@ -719,6 +719,25 @@ struct kvm_device_ops kvm_arm_vgic_v3_ops = {
>  	.has_attr = vgic_v3_has_attr,
>  };
>  
> +static int vgic_v5_get_userspace_ppis(struct kvm_device *dev,
> +				      struct kvm_device_attr *attr)
> +{
> +	u64 __user *uaddr = (u64 __user *)(long)attr->addr;
> +	struct gicv5_vm *gicv5_vm = &dev->kvm->arch.vgic.gicv5_vm;
> +	int i, ret;
> +
> +	guard(mutex)(&dev->kvm->arch.config_lock);
> +
> +	for (i = 0; i < 2; ++i) {

Can drag declaration of i into loop init.
Also I just noticed the series is rather random wrt to pre or post increment
in cases where it doesn't matter. I'd go with post increment for for loops.
I took a quick look at a random file in this directory and that's what is used there.


> +		ret = put_user(gicv5_vm->userspace_ppis[i], uaddr);
> +		if (ret)
> +			return ret;
> +		uaddr++;
> +	}
> +
> +	return 0;
> +}

> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index bf72982d6a2e8..04300926683b6 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> @@ -122,6 +122,14 @@ int vgic_v5_init(struct kvm *kvm)
>  		}
>  	}
>  
> +	/*
> +	 * We only allow userspace to drive the SW_PPI, if it is
> +	 * implemented.
> +	 */

	/* We only allow userspace to drive the SW_PPI, if it is implemented. */

Is under 80 chars (just) so go with that.


> +	kvm->arch.vgic.gicv5_vm.userspace_ppis[0] = GICV5_SW_PPI & GICV5_HWIRQ_ID;
> +	kvm->arch.vgic.gicv5_vm.userspace_ppis[0] &= ppi_caps->impl_ppi_mask[0];
> +	kvm->arch.vgic.gicv5_vm.userspace_ppis[1] = 0;
> +
>  	return 0;
>  }




^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 07/36] KVM: arm64: gic: Introduce interrupt type helpers
  2026-01-06 18:43   ` Jonathan Cameron
@ 2026-01-08  9:33     ` Sascha Bischoff
  2026-01-08 10:25       ` Jonathan Cameron
  0 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2026-01-08  9:33 UTC (permalink / raw)
  To: jonathan.cameron@huawei.com
  Cc: yuzenghui@huawei.com, lpieralisi@kernel.org, Timothy Hayes,
	Suzuki Poulose, nd, peter.maydell@linaro.org,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, Joey Gouly, maz@kernel.org,
	oliver.upton@linux.dev

On Tue, 2026-01-06 at 18:43 +0000, Jonathan Cameron wrote:
> On Fri, 19 Dec 2025 15:52:38 +0000
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> 
> > GICv5 has moved from using interrupt ranges for different interrupt
> > types to using some of the upper bits of the interrupt ID to denote
> > the interrupt type. This is not compatible with older GICs (which
> > rely
> > on ranges of interrupts to determine the type), and hence a set of
> > helpers is introduced. These helpers take a struct kvm*, and use
> > the
> > vgic model to determine how to interpret the interrupt ID.
> > 
> > Helpers are introduced for PPIs, SPIs, and LPIs. Additionally, a
> > helper is introduced to determine if an interrupt is private - SGIs
> > and PPIs for older GICs, and PPIs only for GICv5.
> > 
> > The helpers are plumbed into the core vgic code, as well as the
> > Arch
> > Timer and PMU code.
> > 
> > There should be no functional changes as part of this change.
> > 
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> Hi Sascha,
> 
> A bit of bikeshedding / 'valuable' naming feedback to end the day.
> Otherwise LGTM.
> 
> > diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
> > index b261fb3968d03..6778f676eaf08 100644
> > --- a/include/kvm/arm_vgic.h
> > +++ b/include/kvm/arm_vgic.h
> ...
> 
> >  enum vgic_type {
> >  	VGIC_V2,		/* Good ol' GICv2 */
> > @@ -418,8 +488,12 @@ u64 vgic_v3_get_misr(struct kvm_vcpu *vcpu);
> >  
> >  #define irqchip_in_kernel(k)	(!!((k)->arch.vgic.in_kernel))
> >  #define vgic_initialized(k)	((k)->arch.vgic.initialized)
> > -#define vgic_valid_spi(k, i)	(((i) >= VGIC_NR_PRIVATE_IRQS) &&
> > \
> > +#define vgic_valid_nv5_spi(k, i)	(((i) >=
> > VGIC_NR_PRIVATE_IRQS) && \
> >  			((i) < (k)->arch.vgic.nr_spis +
> > VGIC_NR_PRIVATE_IRQS))
> > +#define vgic_valid_v5_spi(k, i)	(irq_is_spi(k, i) && \
> > +				 (FIELD_GET(GICV5_HWIRQ_ID, i) <
> > (k)->arch.vgic.nr_spis))
> > +#define vgic_valid_spi(k, i) (vgic_is_v5(k)
> > ?				\
> > +			      vgic_valid_v5_spi(k, i) :
> > vgic_valid_nv5_spi(k, i))
> 
> nv is a little awkward as a name as immediately makes me thinking
> nested virtualization instead of not v5 (which I guess is the
> thinking behind that?)
> 
> Probably just me and naming it v23 will break if we get to GIC
> version 23 :)
> nv5 breaks when we get GICv6 ;)

Absolutely agreed here. The v5 and nv5 macros were not used anywhere,
so I've re-worked this a bit to be more in the style of those added
earlier:

-#define vgic_valid_nv5_spi(k, i)       (((i) >= VGIC_NR_PRIVATE_IRQS) && \
-                       ((i) < (k)->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS))
-#define vgic_valid_v5_spi(k, i)        (irq_is_spi(k, i) && \
-                                (FIELD_GET(GICV5_HWIRQ_ID, i) < (k)->arch.vgic.nr_spis))
-#define vgic_valid_spi(k, i) (vgic_is_v5(k) ?                          \
-                             vgic_valid_v5_spi(k, i) : vgic_valid_nv5_spi(k, i))
+#define vgic_valid_spi(k, i)                                           \
+       ({                                                              \
+               bool __ret = irq_is_spi(k, i);                          \
+                                                                       \
+               switch ((k)->arch.vgic.vgic_model) {                    \
+               case KVM_DEV_TYPE_ARM_VGIC_V5:                          \
+                       __ret &= FIELD_GET(GICV5_HWIRQ_ID, i) < (k)->arch.vgic.nr_spis; \
+                       break;                                          \
+               default:                                                \
+                       __ret &= (i) < ((k)->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS); \
+               }                                                       \
+                                                                       \
+               __ret;                                                  \
+       })

More verbose (with annoying line lengths), but certainly more scalable
and removes the naming issue altogether. Personally, I prefer it
because it is more aligned with the related macros above.

Is this preferable/acceptable?

Thanks,
Sascha

> 
> 
> >  
> >  bool kvm_vcpu_has_pending_irqs(struct kvm_vcpu *vcpu);
> >  void kvm_vgic_sync_hwstate(struct kvm_vcpu *vcpu);
> 


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 08/36] KVM: arm64: Introduce kvm_call_hyp_nvhe_res()
  2026-01-07 10:30   ` Jonathan Cameron
@ 2026-01-08  9:48     ` Sascha Bischoff
  2026-01-08 10:26       ` Jonathan Cameron
  0 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2026-01-08  9:48 UTC (permalink / raw)
  To: jonathan.cameron@huawei.com
  Cc: yuzenghui@huawei.com, lpieralisi@kernel.org, Timothy Hayes,
	Suzuki Poulose, nd, peter.maydell@linaro.org,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, Joey Gouly, maz@kernel.org,
	oliver.upton@linux.dev

On Wed, 2026-01-07 at 10:30 +0000, Jonathan Cameron wrote:
> On Fri, 19 Dec 2025 15:52:38 +0000
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> 
> > There are times when it is desirable to have more than one return
> > value for a hyp call. Split out kvm_call_hyp_nvhe_res from
> > kvm_call_hyp_nvhe such that it is possible to directly provide
> > struct
> > arm_smccc_res from the calling code. Thereby, additional return
> > values
> > can be stored in res.a2, etc.
> > 
> > Suggested-by: Marc Zyngier <maz@kernel.org>
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> One question inline, mostly because I'm curious rather than a
> suggestion
> to change anything
> 
> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
> 
> > ---
> >  arch/arm64/include/asm/kvm_host.h | 15 ++++++++++-----
> >  1 file changed, 10 insertions(+), 5 deletions(-)
> > 
> > diff --git a/arch/arm64/include/asm/kvm_host.h
> > b/arch/arm64/include/asm/kvm_host.h
> > index b552a1e03848c..971b153b0a3fa 100644
> > --- a/arch/arm64/include/asm/kvm_host.h
> > +++ b/arch/arm64/include/asm/kvm_host.h
> > @@ -1208,14 +1208,19 @@ void kvm_arm_resume_guest(struct kvm *kvm);
> >  #define vcpu_has_run_once(vcpu)	(!!READ_ONCE((vcpu)->pid))
> >  
> >  #ifndef __KVM_NVHE_HYPERVISOR__
> > -#define kvm_call_hyp_nvhe(f,
> > ...)						\
> > +#define kvm_call_hyp_nvhe_res(res, f,
> > ...)				\
> >  	({							
> > 	\
> > -		struct arm_smccc_res
> > res;				\
> > -
> > 									\
> > +		struct arm_smccc_res *__res =
> > res;			\
> 
> What's the purpose of the local variable? Type sanity check?
> Feels like typecheck() would make the intent clearer.
> Meh. Not used anywhere in arch/arm64 so maybe not.

Far less exciting, I'm afraid.

It is because of the text substitution that happens. The res here is
replaced with &res from kvm_call_hyp_nvhe() and we end up with &res-
>a0. Given that -> binds more tightly than &, we end up with the wrong
thing, and the compiler barfs.

An alternative would be to do:

	WARN_ON((res)->a0 != SMCCC_RET_SUCCESS);

This removes the need for the local variable. I've got no preference
for which version we go with.

Sascha

> 
> 
> 
> >  		arm_smccc_1_1_hvc(KVM_HOST_SMCCC_FUNC(f),	
> > 	\
> > -				  ##__VA_ARGS__,
> > &res);			\
> > -		WARN_ON(res.a0 !=
> > SMCCC_RET_SUCCESS);			\
> > +				  ##__VA_ARGS__,
> > __res);		\
> > +		WARN_ON(__res->a0 !=
> > SMCCC_RET_SUCCESS);		\
> > +	})
> > +
> > +#define kvm_call_hyp_nvhe(f,
> > ...)					\
> > +	({							
> > 	\
> > +		struct arm_smccc_res
> > res;				\
> >  								
> > 	\
> > +		kvm_call_hyp_nvhe_res(&res, f,
> > ##__VA_ARGS__);		\
> >  		res.a1;					
> > 		\
> >  	})
> >  
> 


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 10/36] KVM: arm64: gic-v5: Sanitize ID_AA64PFR2_EL1.GCIE
  2026-01-07 10:58   ` Jonathan Cameron
@ 2026-01-08  9:54     ` Sascha Bischoff
  0 siblings, 0 replies; 100+ messages in thread
From: Sascha Bischoff @ 2026-01-08  9:54 UTC (permalink / raw)
  To: jonathan.cameron@huawei.com
  Cc: yuzenghui@huawei.com, lpieralisi@kernel.org, Timothy Hayes,
	Suzuki Poulose, nd, peter.maydell@linaro.org,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, Joey Gouly, maz@kernel.org,
	oliver.upton@linux.dev

On Wed, 2026-01-07 at 10:58 +0000, Jonathan Cameron wrote:
> On Fri, 19 Dec 2025 15:52:39 +0000
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> 
> > Set the guest's view of the GCIE field to IMP when running a GICv5
> > VM,
> > NI otherwise. Reject any writes to the register that try to do
> > anything but set GCIE to IMP when running a GICv5 VM.
> > 
> > As part of this change, we also introduce vgic_is_v5(kvm), in order
> > to
> > check if the guest is a GICv5-native VM. We're also required to
> > extend
> > vgic_is_v3_compat to check for the actual vgic_model. This has one
> > potential issue - if any of the vgic_is_v* checks are used prior to
> > setting the vgic_model (that is, before kvm_vgic_create) then
> > vgic_model will be set to 0, which can result in a false-positive.
> > 
> > Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
> > Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> Hi Sascha, Timothy
> 
> The masking of val has me a little confused in the sanitize function.
> Probably needs a slightly rewrite.

Yeah, agreed.

> 
> Jonathan
> 
> > ---
> >  arch/arm64/kvm/sys_regs.c  | 39 ++++++++++++++++++++++++++++++----
> > ----
> >  arch/arm64/kvm/vgic/vgic.h | 10 +++++++++-
> >  2 files changed, 40 insertions(+), 9 deletions(-)
> > 
> > diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
> > index c8fd7c6a12a13..a065f8939bc8f 100644
> > --- a/arch/arm64/kvm/sys_regs.c
> > +++ b/arch/arm64/kvm/sys_regs.c
> > @@ -1758,6 +1758,7 @@ static u8 pmuver_to_perfmon(u8 pmuver)
> >  
> >  static u64 sanitise_id_aa64pfr0_el1(const struct kvm_vcpu *vcpu,
> > u64 val);
> >  static u64 sanitise_id_aa64pfr1_el1(const struct kvm_vcpu *vcpu,
> > u64 val);
> > +static u64 sanitise_id_aa64pfr2_el1(const struct kvm_vcpu *vcpu,
> > u64 val);
> >  static u64 sanitise_id_aa64dfr0_el1(const struct kvm_vcpu *vcpu,
> > u64 val);
> >  
> >  /* Read a sanitised cpufeature ID register by sys_reg_desc */
> > @@ -1783,10 +1784,7 @@ static u64 __kvm_read_sanitised_id_reg(const
> > struct kvm_vcpu *vcpu,
> >  		val = sanitise_id_aa64pfr1_el1(vcpu, val);
> >  		break;
> >  	case SYS_ID_AA64PFR2_EL1:
> > -		val &= ID_AA64PFR2_EL1_FPMR |
> > -			(kvm_has_mte(vcpu->kvm) ?
> > -			 ID_AA64PFR2_EL1_MTEFAR |
> > ID_AA64PFR2_EL1_MTESTOREONLY :
> > -			 0);
> > +		val = sanitise_id_aa64pfr2_el1(vcpu, val);
> >  		break;
> >  	case SYS_ID_AA64ISAR1_EL1:
> >  		if (!vcpu_has_ptrauth(vcpu))
> > @@ -2024,6 +2022,20 @@ static u64 sanitise_id_aa64pfr1_el1(const
> > struct kvm_vcpu *vcpu, u64 val)
> >  	return val;
> >  }
> >  
> > +static u64 sanitise_id_aa64pfr2_el1(const struct kvm_vcpu *vcpu,
> > u64 val)
> > +{
> 
> The code flow in here seems confusing, so maybe needs a rethink even
> if it
> works.  Feels like we need a mask first of everything the kernel
> understands,
> then specific masking out / setting of parts for each feature.
> I'm not sure if the initial mask is handled by the caller (didn't
> check but
> it's in the register array structure).
> Also I love crossing specs where the gicv5 spec says all the other
> fields are
> reserved and they aren't any more.  Would have been better if that
> had
> just said see arm arm for the other parts of this register.
> 
> > +	val &= ID_AA64PFR2_EL1_FPMR |
> > +		(kvm_has_mte(vcpu->kvm) ?
> > +			ID_AA64PFR2_EL1_MTEFAR |
> > ID_AA64PFR2_EL1_MTESTOREONLY : 0);
> 
> So this either masks out everything other than FPRM or masks out
> everything other
> than EL1_MTEFAR, EL1_MTESTORE_ONLY and FPMR.
> 
> Hence...
> 
> > +
> > +	if (vgic_is_v5(vcpu->kvm)) {
> > +		val &= ~ID_AA64PFR2_EL1_GCIE_MASK;
> 
> This is doing nothing as that field isn't set anyway in either of the
> earlier
> possible maskings of val.
> 
> > +		val |= SYS_FIELD_PREP_ENUM(ID_AA64PFR2_EL1, GCIE,
> > IMP);
> > +	}
> > +
> > +	return val;
> > +}
> 
> 

I've taken your advice here, and the flow is now much clearer. Thanks.

We now first allow through all FPMR and MTE* fields, then filter out
the MTE fields if MTE isn't supported. Finally, the GCIE field is set
for GICv5-based guests.

Sascha

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 07/36] KVM: arm64: gic: Introduce interrupt type helpers
  2026-01-08  9:33     ` Sascha Bischoff
@ 2026-01-08 10:25       ` Jonathan Cameron
  0 siblings, 0 replies; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-08 10:25 UTC (permalink / raw)
  To: Sascha Bischoff
  Cc: yuzenghui@huawei.com, lpieralisi@kernel.org, Timothy Hayes,
	Suzuki Poulose, nd, peter.maydell@linaro.org,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, Joey Gouly, maz@kernel.org,
	oliver.upton@linux.dev

On Thu, 8 Jan 2026 09:33:30 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> On Tue, 2026-01-06 at 18:43 +0000, Jonathan Cameron wrote:
> > On Fri, 19 Dec 2025 15:52:38 +0000
> > Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> >   
> > > GICv5 has moved from using interrupt ranges for different interrupt
> > > types to using some of the upper bits of the interrupt ID to denote
> > > the interrupt type. This is not compatible with older GICs (which
> > > rely
> > > on ranges of interrupts to determine the type), and hence a set of
> > > helpers is introduced. These helpers take a struct kvm*, and use
> > > the
> > > vgic model to determine how to interpret the interrupt ID.
> > > 
> > > Helpers are introduced for PPIs, SPIs, and LPIs. Additionally, a
> > > helper is introduced to determine if an interrupt is private - SGIs
> > > and PPIs for older GICs, and PPIs only for GICv5.
> > > 
> > > The helpers are plumbed into the core vgic code, as well as the
> > > Arch
> > > Timer and PMU code.
> > > 
> > > There should be no functional changes as part of this change.
> > > 
> > > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>  
> > Hi Sascha,
> > 
> > A bit of bikeshedding / 'valuable' naming feedback to end the day.
> > Otherwise LGTM.
> >   
> > > diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
> > > index b261fb3968d03..6778f676eaf08 100644
> > > --- a/include/kvm/arm_vgic.h
> > > +++ b/include/kvm/arm_vgic.h  
> > ...
> >   
> > >  enum vgic_type {
> > >  	VGIC_V2,		/* Good ol' GICv2 */
> > > @@ -418,8 +488,12 @@ u64 vgic_v3_get_misr(struct kvm_vcpu *vcpu);
> > >  
> > >  #define irqchip_in_kernel(k)	(!!((k)->arch.vgic.in_kernel))
> > >  #define vgic_initialized(k)	((k)->arch.vgic.initialized)
> > > -#define vgic_valid_spi(k, i)	(((i) >= VGIC_NR_PRIVATE_IRQS) &&
> > > \
> > > +#define vgic_valid_nv5_spi(k, i)	(((i) >=
> > > VGIC_NR_PRIVATE_IRQS) && \
> > >  			((i) < (k)->arch.vgic.nr_spis +
> > > VGIC_NR_PRIVATE_IRQS))
> > > +#define vgic_valid_v5_spi(k, i)	(irq_is_spi(k, i) && \
> > > +				 (FIELD_GET(GICV5_HWIRQ_ID, i) <
> > > (k)->arch.vgic.nr_spis))
> > > +#define vgic_valid_spi(k, i) (vgic_is_v5(k)
> > > ?				\
> > > +			      vgic_valid_v5_spi(k, i) :
> > > vgic_valid_nv5_spi(k, i))  
> > 
> > nv is a little awkward as a name as immediately makes me thinking
> > nested virtualization instead of not v5 (which I guess is the
> > thinking behind that?)
> > 
> > Probably just me and naming it v23 will break if we get to GIC
> > version 23 :)
> > nv5 breaks when we get GICv6 ;)  
> 
> Absolutely agreed here. The v5 and nv5 macros were not used anywhere,
> so I've re-worked this a bit to be more in the style of those added
> earlier:
> 
> -#define vgic_valid_nv5_spi(k, i)       (((i) >= VGIC_NR_PRIVATE_IRQS) && \
> -                       ((i) < (k)->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS))
> -#define vgic_valid_v5_spi(k, i)        (irq_is_spi(k, i) && \
> -                                (FIELD_GET(GICV5_HWIRQ_ID, i) < (k)->arch.vgic.nr_spis))
> -#define vgic_valid_spi(k, i) (vgic_is_v5(k) ?                          \
> -                             vgic_valid_v5_spi(k, i) : vgic_valid_nv5_spi(k, i))
> +#define vgic_valid_spi(k, i)                                           \
> +       ({                                                              \
> +               bool __ret = irq_is_spi(k, i);                          \
> +                                                                       \
> +               switch ((k)->arch.vgic.vgic_model) {                    \
> +               case KVM_DEV_TYPE_ARM_VGIC_V5:                          \
> +                       __ret &= FIELD_GET(GICV5_HWIRQ_ID, i) < (k)->arch.vgic.nr_spis; \
> +                       break;                                          \
> +               default:                                                \
> +                       __ret &= (i) < ((k)->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS); \
> +               }                                                       \
> +                                                                       \
> +               __ret;                                                  \
> +       })
> 
> More verbose (with annoying line lengths), but certainly more scalable
> and removes the naming issue altogether. Personally, I prefer it
> because it is more aligned with the related macros above.
> 
> Is this preferable/acceptable?

Looks good to me, though it's getting complex enough that it might be better
as a static inline function. That will also reduce line lengths
a little.

Jonathan

> 
> Thanks,
> Sascha
> 
> > 
> >   
> > >  
> > >  bool kvm_vcpu_has_pending_irqs(struct kvm_vcpu *vcpu);
> > >  void kvm_vgic_sync_hwstate(struct kvm_vcpu *vcpu);  
> >   
> 



^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 08/36] KVM: arm64: Introduce kvm_call_hyp_nvhe_res()
  2026-01-08  9:48     ` Sascha Bischoff
@ 2026-01-08 10:26       ` Jonathan Cameron
  0 siblings, 0 replies; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-08 10:26 UTC (permalink / raw)
  To: Sascha Bischoff
  Cc: yuzenghui@huawei.com, lpieralisi@kernel.org, Timothy Hayes,
	Suzuki Poulose, nd, peter.maydell@linaro.org,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, Joey Gouly, maz@kernel.org,
	oliver.upton@linux.dev

On Thu, 8 Jan 2026 09:48:39 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> On Wed, 2026-01-07 at 10:30 +0000, Jonathan Cameron wrote:
> > On Fri, 19 Dec 2025 15:52:38 +0000
> > Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> >   
> > > There are times when it is desirable to have more than one return
> > > value for a hyp call. Split out kvm_call_hyp_nvhe_res from
> > > kvm_call_hyp_nvhe such that it is possible to directly provide
> > > struct
> > > arm_smccc_res from the calling code. Thereby, additional return
> > > values
> > > can be stored in res.a2, etc.
> > > 
> > > Suggested-by: Marc Zyngier <maz@kernel.org>
> > > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>  
> > One question inline, mostly because I'm curious rather than a
> > suggestion
> > to change anything
> > 
> > Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
> >   
> > > ---
> > >  arch/arm64/include/asm/kvm_host.h | 15 ++++++++++-----
> > >  1 file changed, 10 insertions(+), 5 deletions(-)
> > > 
> > > diff --git a/arch/arm64/include/asm/kvm_host.h
> > > b/arch/arm64/include/asm/kvm_host.h
> > > index b552a1e03848c..971b153b0a3fa 100644
> > > --- a/arch/arm64/include/asm/kvm_host.h
> > > +++ b/arch/arm64/include/asm/kvm_host.h
> > > @@ -1208,14 +1208,19 @@ void kvm_arm_resume_guest(struct kvm *kvm);
> > >  #define vcpu_has_run_once(vcpu)	(!!READ_ONCE((vcpu)->pid))
> > >  
> > >  #ifndef __KVM_NVHE_HYPERVISOR__
> > > -#define kvm_call_hyp_nvhe(f,
> > > ...)						\
> > > +#define kvm_call_hyp_nvhe_res(res, f,
> > > ...)				\
> > >  	({							
> > > 	\
> > > -		struct arm_smccc_res
> > > res;				\
> > > -
> > > 									\
> > > +		struct arm_smccc_res *__res =
> > > res;			\  
> > 
> > What's the purpose of the local variable? Type sanity check?
> > Feels like typecheck() would make the intent clearer.
> > Meh. Not used anywhere in arch/arm64 so maybe not.  
> 
> Far less exciting, I'm afraid.
> 
> It is because of the text substitution that happens. The res here is
> replaced with &res from kvm_call_hyp_nvhe() and we end up with &res-
> >a0. Given that -> binds more tightly than &, we end up with the wrong  
> thing, and the compiler barfs.
> 
> An alternative would be to do:
> 
> 	WARN_ON((res)->a0 != SMCCC_RET_SUCCESS);
> 
> This removes the need for the local variable. I've got no preference
> for which version we go with.

Ah fair enough.  Just leave it as it is.

Jonathan

> 
> Sascha
> 
> > 
> > 
> >   
> > >  		arm_smccc_1_1_hvc(KVM_HOST_SMCCC_FUNC(f),	
> > > 	\
> > > -				  ##__VA_ARGS__,
> > > &res);			\
> > > -		WARN_ON(res.a0 !=
> > > SMCCC_RET_SUCCESS);			\
> > > +				  ##__VA_ARGS__,
> > > __res);		\
> > > +		WARN_ON(__res->a0 !=
> > > SMCCC_RET_SUCCESS);		\
> > > +	})
> > > +
> > > +#define kvm_call_hyp_nvhe(f,
> > > ...)					\
> > > +	({							
> > > 	\
> > > +		struct arm_smccc_res
> > > res;				\
> > >  								
> > > 	\
> > > +		kvm_call_hyp_nvhe_res(&res, f,
> > > ##__VA_ARGS__);		\
> > >  		res.a1;					
> > > 		\
> > >  	})
> > >    
> >   
> 



^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 11/36] KVM: arm64: gic-v5: Support GICv5 FGTs & FGUs
  2026-01-07 11:19   ` Jonathan Cameron
@ 2026-01-08 10:36     ` Sascha Bischoff
  0 siblings, 0 replies; 100+ messages in thread
From: Sascha Bischoff @ 2026-01-08 10:36 UTC (permalink / raw)
  To: jonathan.cameron@huawei.com
  Cc: yuzenghui@huawei.com, lpieralisi@kernel.org, Timothy Hayes,
	Suzuki Poulose, nd, peter.maydell@linaro.org,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, Joey Gouly, maz@kernel.org,
	oliver.upton@linux.dev

On Wed, 2026-01-07 at 11:19 +0000, Jonathan Cameron wrote:
> On Fri, 19 Dec 2025 15:52:39 +0000
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> 
> > Extend the existing FGT/FGU infrastructure to include the GICv5
> > trap
> > registers (ICH_HFGRTR_EL2, ICH_HFGWTR_EL2, ICH_HFGITR_EL2). This
> > involves mapping the trap registers and their bits to the
> > corresponding feature that introduces them (FEAT_GCIE for all, in
> > this
> > case), and mapping each trap bit to the system register/instruction
> > controlled by it.
> > 
> > As of this change, none of the GICv5 instructions or register
> > accesses
> > are being trapped.
> > 
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> Hi Sascha,
> 
> Superficial stuff only on code flow to make it easier to extend next
> time.
> 
> Jonathan
> 
> 
> > diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
> > index 3845b188551b6..5f57dc07cc482 100644
> > --- a/arch/arm64/kvm/config.c
> > +++ b/arch/arm64/kvm/config.c
> 
> > @@ -1511,11 +1595,19 @@ void kvm_vcpu_load_fgt(struct kvm_vcpu
> > *vcpu)
> >  	__compute_fgt(vcpu, HAFGRTR_EL2);
> >  
> >  	if (!cpus_have_final_cap(ARM64_HAS_FGT2))
> > -		return;
> > +		goto skip_fgt2;
> 
> Nicer to avoid more complex code flow and just make the next
> block an if.
> 
> 	if (cpus_have_final_cap(ARM64_HAS_FGT2)) {
> 		__compute_fgt(vcpu, HFGRTR2_EL2);
> 		__compute_fgt(vcpu, HFGWTR2_EL2);
> 		__compute_fgt(vcpu, HFGITR2_EL2);
> 		__compute_fgt(vcpu, HDFGRTR2_EL2);
> 		__compute_fgt(vcpu, HDFGWTR2_EL2);
> 	}
> >  
> >  	__compute_fgt(vcpu, HFGRTR2_EL2);
> >  	__compute_fgt(vcpu, HFGWTR2_EL2);
> >  	__compute_fgt(vcpu, HFGITR2_EL2);
> >  	__compute_fgt(vcpu, HDFGRTR2_EL2);
> >  	__compute_fgt(vcpu, HDFGWTR2_EL2);
> > +
> > +skip_fgt2:
> > +	if (!cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF))
> Given the above shows this code sometimes gets extended I'd
> be tempted to just go with
> 	if (cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF)) {
> 		__compute_fgt(vcpu, ICH_HFGRTR_EL2);
> 		__compute_fgt(vcpu, ICH_HFGWTR_EL2);
> 		__compute_fgt(vcpu, ICH_HFGITR_EL2);
> 	}
> 
> From the start and avoid future churn or goto nasties.

Yeah, you're probably right. Done.

Thanks,
Sascha

> 
> > 	
> > +		return;
> > +
> > +	__compute_fgt(vcpu, ICH_HFGRTR_EL2);
> > +	__compute_fgt(vcpu, ICH_HFGWTR_EL2);
> > +	__compute_fgt(vcpu, ICH_HFGITR_EL2);
> >  }
> 
> 
> 


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 13/36] KVM: arm64: gic: Set vgic_model before initing private IRQs
  2026-01-07 11:24   ` Jonathan Cameron
@ 2026-01-08 13:39     ` Sascha Bischoff
  0 siblings, 0 replies; 100+ messages in thread
From: Sascha Bischoff @ 2026-01-08 13:39 UTC (permalink / raw)
  To: jonathan.cameron@huawei.com
  Cc: yuzenghui@huawei.com, lpieralisi@kernel.org, Timothy Hayes,
	Suzuki Poulose, nd, peter.maydell@linaro.org,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, Joey Gouly, maz@kernel.org,
	oliver.upton@linux.dev

On Wed, 2026-01-07 at 11:24 +0000, Jonathan Cameron wrote:
> On Fri, 19 Dec 2025 15:52:40 +0000
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> 
> > Different GIC types require the private IRQs to be initialised
> > differently. GICv5 is the culprit as it supports both a different
> > number of private IRQs, and all of these are PPIs (there are no
> > SGIs). Moreover, as GICv5 uses the top bits of the interrupt ID to
> > encode the type, the intid also needs to computed differently.
> > 
> > Up until now, the GIC model has been set after initialising the
> > private IRQs for a VCPU. Move this earlier to ensure that the GIC
> > model is available when configuring the private IRQs.
> Hi Sascha,
> 
> Good to mention you are moving a bit more than just the type
> initialization.

Done.

> One question on whether it makes sense to move
> vgic_dist_base inline.

It doesn't. It is an artifact left over from prising out the PPI
support from the rest of the GICv5 support (coming soon), and actually
I'm sure that it is not needed there anymore so have dropped it.

> 
> > 
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > ---
> >  arch/arm64/kvm/vgic/vgic-init.c | 12 ++++++------
> >  1 file changed, 6 insertions(+), 6 deletions(-)
> > 
> > diff --git a/arch/arm64/kvm/vgic/vgic-init.c
> > b/arch/arm64/kvm/vgic/vgic-init.c
> > index c602f24bab1bb..bcc2c79f7833c 100644
> > --- a/arch/arm64/kvm/vgic/vgic-init.c
> > +++ b/arch/arm64/kvm/vgic/vgic-init.c
> > @@ -140,6 +140,12 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
> >  		goto out_unlock;
> >  	}
> >  
> > +	kvm->arch.vgic.in_kernel = true;
> > +	kvm->arch.vgic.vgic_model = type;
> > +	kvm->arch.vgic.implementation_rev =
> > KVM_VGIC_IMP_REV_LATEST;
> 
> Moving these looks fine.
> 
> > +
> > +	kvm->arch.vgic.vgic_dist_base = VGIC_ADDR_UNDEF;
> 
> Does this need to move? 
> 

I've reverted this line of the change.

Thanks,
Sascha

> > The rest of the *base =
> stuff is still where this code originally came from.
> Might well be necessary but I'd expect a little in the patch
> description on why.
> 
> > +
> >  	kvm_for_each_vcpu(i, vcpu, kvm) {
> >  		ret = vgic_allocate_private_irqs_locked(vcpu,
> > type);
> >  		if (ret)
> > @@ -156,12 +162,6 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
> >  		goto out_unlock;
> >  	}
> >  
> > -	kvm->arch.vgic.in_kernel = true;
> > -	kvm->arch.vgic.vgic_model = type;
> > -	kvm->arch.vgic.implementation_rev =
> > KVM_VGIC_IMP_REV_LATEST;
> > -
> > -	kvm->arch.vgic.vgic_dist_base = VGIC_ADDR_UNDEF;
> > -
> >  	aa64pfr0 = kvm_read_vm_id_reg(kvm, SYS_ID_AA64PFR0_EL1) &
> > ~ID_AA64PFR0_EL1_GIC;
> >  	pfr1 = kvm_read_vm_id_reg(kvm, SYS_ID_PFR1_EL1) &
> > ~ID_PFR1_EL1_GIC;
> >  
> 


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 15/36] KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore
  2026-01-07 12:28   ` Jonathan Cameron
@ 2026-01-08 13:40     ` Sascha Bischoff
  2026-01-08 16:52       ` Jonathan Cameron
  0 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2026-01-08 13:40 UTC (permalink / raw)
  To: jonathan.cameron@huawei.com
  Cc: yuzenghui@huawei.com, lpieralisi@kernel.org, Timothy Hayes,
	Suzuki Poulose, nd, peter.maydell@linaro.org,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, Joey Gouly, maz@kernel.org,
	oliver.upton@linux.dev

On Wed, 2026-01-07 at 12:28 +0000, Jonathan Cameron wrote:
> On Fri, 19 Dec 2025 15:52:41 +0000
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> 
> > This change introduces GICv5 load/put. Additionally, it plumbs in
> > save/restore for:
> > 
> > * PPIs (ICH_PPI_x_EL2 regs)
> > * ICH_VMCR_EL2
> > * ICH_APR_EL2
> > * ICC_ICSR_EL1
> > 
> > A GICv5-specific enable bit is added to struct vgic_vmcr as this
> > differs from previous GICs. On GICv5-native systems, the VMCR only
> > contains the enable bit (driven by the guest via ICC_CR0_EL1.EN)
> > and
> > the priority mask (PCR).
> > 
> > A struct gicv5_vpe is also introduced. This currently only contains
> > a
> > single field - bool resident - which is used to track if a VPE is
> > currently running or not, and is used to avoid a case of double
> > load
> > or double put on the WFI path for a vCPU. This struct will be
> > extended
> > as additional GICv5 support is merged, specifically for VPE
> > doorbells.
> > 
> > Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
> > Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> 
> 
> > diff --git a/arch/arm64/kvm/vgic/vgic-v5.c
> > b/arch/arm64/kvm/vgic/vgic-v5.c
> > index 1fe1790f1f874..168447ee3fbed 100644
> > --- a/arch/arm64/kvm/vgic/vgic-v5.c
> > +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> > @@ -1,4 +1,7 @@
> >  // SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * Copyright (C) 2025 Arm Ltd.
> > + */
> 
> Why in this patch?  It's trivial enough that maybe it doesn't need to
> be
> on it's own, but the first patch touching this file seems like a more
> logical place to find it.

I wholeheartedly agree, but it was unintentionally omitted when the
GICv5 compat mode changes were introduced. It was originally in the
first commit in this series to touch the file, but then things got re-
worked so it became the second. I'll make sure that it lives in the
first commit of this series to touch the file.

Sascha


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 18/36] KVM: arm64: gic-v5: Implement PPI interrupt injection
  2026-01-07 12:50   ` Jonathan Cameron
@ 2026-01-08 14:43     ` Sascha Bischoff
  0 siblings, 0 replies; 100+ messages in thread
From: Sascha Bischoff @ 2026-01-08 14:43 UTC (permalink / raw)
  To: jonathan.cameron@huawei.com
  Cc: yuzenghui@huawei.com, lpieralisi@kernel.org, Timothy Hayes,
	Suzuki Poulose, nd, peter.maydell@linaro.org,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, Joey Gouly, maz@kernel.org,
	oliver.upton@linux.dev

On Wed, 2026-01-07 at 12:50 +0000, Jonathan Cameron wrote:
> On Fri, 19 Dec 2025 15:52:42 +0000
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> 
> > This change introduces interrupt injection for PPIs for GICv5-based
> > guests.
> > 
> > The lifecycle of PPIs is largely managed by the hardware for a
> > GICv5
> > system. The hypervisor injects pending state into the guest by
> > using
> > the ICH_PPI_PENDRx_EL2 registers. These are used by the hardware to
> > pick a Highest Priority Pending Interrupt (HPPI) for the guest
> > based
> > on the enable state of each individual interrupt. The enable state
> > and
> > priority for each interrupt are provided by the guest itself
> > (through
> > writes to the PPI registers).
> > 
> > When Direct Virtual Interrupt (DVI) is set for a particular PPI,
> > the
> > hypervisor is even able to skip the injection of the pending state
> > altogether - it all happens in hardware.
> > 
> > The result of the above is that no AP lists are required for GICv5,
> > unlike for older GICs. Instead, for PPIs the ICH_PPI_* registers
> > fulfil the same purpose for all 128 PPIs. Hence, as long as the
> > ICH_PPI_* registers are populated prior to guest entry, and merged
> > back into the KVM shadow state on exit, the PPI state is preserved,
> > and interrupts can be injected.
> > 
> > When injecting the state of a PPI the state is merged into the
> > KVM's
> > shadow state using the set_pending_state irq_op. The directly sets
> > the
> > relevant bit in the shadow ICH_PPI_PENDRx_EL2, which is presented
> > to
> > the guest (and GICv5 hardware) on next guest entry. The
> > queue_irq_unlock irq_op is required to kick the vCPU to ensure that
> > it
> > seems the new state. The result is that no AP lists are used for
> > private interrupts on GICv5.
> > 
> > Prior to entering the guest, vgic_v5_flush_ppi_state is called from
> > kvm_vgic_flush_hwstate. The effectively snapshots the shadow PPI
> > pending state (twice - an entry and an exit copy) in order to track
> > any changes. These changes can come from a guest consuming an
> > interrupt or from a guest making an Edge-triggered interrupt
> > pending.
> > 
> > When returning from running a guest, the guest's PPI state is
> > merged
> > back into KVM's shadow state in vgic_v5_merge_ppi_state from
> > kvm_vgic_sync_hwstate. The Enable and Active state is synced back
> > for
> > all PPIs, and the pending state is synced back for Edge PPIs (Level
> > is
> > driven directly by the devices generating said levels). The
> > incoming
> > pending state from the guest is merged with KVM's shadow state to
> > avoid losing any incoming interrupts.
> > 
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> Minor things inline
> 
> > ---
> >  arch/arm64/kvm/vgic/vgic-v5.c | 159
> > ++++++++++++++++++++++++++++++++++
> >  arch/arm64/kvm/vgic/vgic.c    |  46 +++++++---
> >  arch/arm64/kvm/vgic/vgic.h    |  47 ++++++++--
> >  include/kvm/arm_vgic.h        |   3 +
> >  4 files changed, 235 insertions(+), 20 deletions(-)
> > 
> > diff --git a/arch/arm64/kvm/vgic/vgic-v5.c
> > b/arch/arm64/kvm/vgic/vgic-v5.c
> > index 46c70dfc7bb08..cb3dd872d16a6 100644
> > --- a/arch/arm64/kvm/vgic/vgic-v5.c
> > +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> > @@ -56,6 +56,165 @@ int vgic_v5_probe(const struct gic_kvm_info
> > *info)
> >  	return 0;
> >  }
> >  
> > +static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
> > +					  struct vgic_irq *irq)
> > +{
> > +	struct vgic_v5_cpu_if *cpu_if;
> > +	const u64 id_bit = BIT_ULL(irq->intid % 64);
> 
> Obviously the % 64 means other bits of irq->intid above the HWIRQ_ID
> field don't matter, but this still seems a little odd.  I'd extract
> the field first, then use that for the reg and id_bit or just
> do those inline where they are used.
> 
> 	const u32 hwirq_id = FIELD_GET(GICV5_HWIRQ_ID, irq->intid);
> 
> 	if (irq_is_pending(irq))
> 		cpu_if->vgic_ppi_pendr[hwirq_id / 64] |= hwirq_id %
> 64;
> ..
> 
> Which matches style you used for similar cases in earlier patches.

Done.

> 
> > +	const u32 reg = FIELD_GET(GICV5_HWIRQ_ID, irq->intid) /
> > 64;
> > +
> > +	if (!vcpu || !irq)
> > +		return false;
> > +
> > +	/* Skip injecting the state altogether */
> > +	if (irq->directly_injected)
> > +		return true;
> > +
> > +	cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> > +
> > +	if (irq_is_pending(irq))
> > +		cpu_if->vgic_ppi_pendr[reg] |= id_bit;
> > +	else
> > +		cpu_if->vgic_ppi_pendr[reg] &= ~id_bit;
> > +
> > +	return true;
> > +}
> 
> 
> > +void vgic_v5_set_ppi_ops(struct vgic_irq *irq)
> > +{
> > +	if (WARN_ON(!irq))
> > +		return;
> > +
> > +	scoped_guard(raw_spinlock, &irq->irq_lock) {
> Not checked on for whether code ends up outside this lock. If not
> use a guard(raw_spinlock)(&irq->irq_lock);

Have used guard() instead.

> 
> > +		if (!WARN_ON(irq->ops))
> > +			irq->ops = &vgic_v5_ppi_irq_ops;
> > +	}
> > +}
> > +
> > +/*
> > + * Detect any PPIs state changes, and propagate the state with
> > KVM's
> > + * shadow structures.
> > + */
> > +void vgic_v5_fold_ppi_state(struct kvm_vcpu *vcpu)
> > +{
> > +	struct vgic_v5_cpu_if *cpu_if = &vcpu-
> > >arch.vgic_cpu.vgic_v5;
> > +	int i, reg;
> > +
> > +	for (reg = 0; reg < 2; reg++) {
> It's now considered fine to declare loop variables in the loop and
> always
> nice to limit their scope.
> 
> 	for (int reg = 0; reg < 2...

Done.

> 
> > +		unsigned long changed_bits;
> > +		const unsigned long enabler = cpu_if-
> > >vgic_ich_ppi_enabler_exit[reg];
> > +		const unsigned long activer = cpu_if-
> > >vgic_ppi_activer_exit[reg];
> > +		const unsigned long pendr = cpu_if-
> > >vgic_ppi_pendr_exit[reg];
> 
> ...
> 
> > +
> > +void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu)
> > +{
> > +	struct vgic_v5_cpu_if *cpu_if = &vcpu-
> > >arch.vgic_cpu.vgic_v5;
> > +
> > +	/*
> > +	 * We're about to enter the guest. Copy the shadow state
> > to the pending
> > +	 * reg that will be written to the ICH_PPI_PENDRx_EL2
> > regs. While the
> > +	 * guest is running we track any incoming changes to the
> > pending state in
> > +	 * vgic_ppi_pendr. The incoming changes are merged with
> > the outgoing
> > +	 * changes on the return path.
> > +	 */
> > +	cpu_if->vgic_ppi_pendr_entry[0] = cpu_if-
> > >vgic_ppi_pendr[0];
> > +	cpu_if->vgic_ppi_pendr_entry[1] = cpu_if-
> > >vgic_ppi_pendr[1];
> > +
> > +	/*
> > +	 * Make sure that we can correctly detect "edges" in the
> > PPI
> > +	 * state. There's a path where we never actually enter the
> > guest, and
> > +	 * failure to do this risks losing pending state
> > +	 */
> > +	cpu_if->vgic_ppi_pendr_exit[0] = cpu_if-
> > >vgic_ppi_pendr[0];
> > +	cpu_if->vgic_ppi_pendr_exit[1] = cpu_if-
> > >vgic_ppi_pendr[1];
> > +
> Drop this blank line.

Done.

> 
> > +}
> 
> > diff --git a/arch/arm64/kvm/vgic/vgic.c
> > b/arch/arm64/kvm/vgic/vgic.c
> > index ac8cb0270e1e4..cb5d43b34462b 100644
> > --- a/arch/arm64/kvm/vgic/vgic.c
> > +++ b/arch/arm64/kvm/vgic/vgic.c
> 
> > @@ -258,10 +266,12 @@ struct kvm_vcpu *vgic_target_oracle(struct
> > vgic_irq *irq)
> >  	 * If the distributor is disabled, pending interrupts
> > shouldn't be
> >  	 * forwarded.
> >  	 */
> > -	if (irq->enabled && irq_is_pending(irq)) {
> > -		if (unlikely(irq->target_vcpu &&
> > -			     !irq->target_vcpu->kvm-
> > >arch.vgic.enabled))
> > -			return NULL;
> > +	if (irq_is_enabled(irq) && irq_is_pending(irq)) {
> > +		if (irq->target_vcpu) {
> 
> Just from a readability point of view, maybe clearer to get rid of
> the 'else# path for this one first.
> 
> 		if (!irq->target_vcpu)
> 			return NULL;
> 
> 		if (!vgic_is_v5(irq->target_vcpu->kvm) &&
> 		    unlikely(!irq->target_vcpu->kvm-
> >arch.vgic.enabled))
> 			return NULL;
> 
> 		return irq->target_vcpu;
> 
> Though I see this code might go away anyway...

Yeah, this has now gone away.

> 
> > +			if (!vgic_is_v5(irq->target_vcpu->kvm) &&
> > +			    unlikely(!irq->target_vcpu->kvm-
> > >arch.vgic.enabled))
> > +				return NULL;
> > +		}
> >  
> >  		return irq->target_vcpu;
> >  	}
> 
> 
> 
> >  /* Flush our emulation state into the GIC hardware before entering
> > the guest. */
> >  void kvm_vgic_flush_hwstate(struct kvm_vcpu *vcpu)
> >  {
> > @@ -1106,13 +1131,12 @@ void kvm_vgic_flush_hwstate(struct kvm_vcpu
> > *vcpu)
> >  
> >  	DEBUG_SPINLOCK_BUG_ON(!irqs_disabled());
> >  
> > -	scoped_guard(raw_spinlock, &vcpu-
> > >arch.vgic_cpu.ap_list_lock)
> > -		vgic_flush_lr_state(vcpu);
> > +	vgic_flush_state(vcpu);
> >  
> >  	if (can_access_vgic_from_kernel())
> >  		vgic_restore_state(vcpu);
> >  
> > -	if (vgic_supports_direct_irqs(vcpu->kvm))
> > +	if (vgic_supports_direct_irqs(vcpu->kvm) &&
> > !vgic_is_v5(vcpu->kvm))
> 
> This feels like a somewhat backwards check.
> No function to check it vgic_is_v4? Similar cases elsewhere.

There is not, because KVM doesn't support a vGICv4. One can create a
vGICv3-based guest, and if the underlying hardware is GICv4.x, then the
additional GICv4 features are used - such as the direct injection of
IRQs.

Instead of checking for !v5 I think it is cleaner to instead check
kvm_vgic_global_state.has_gicv4, which tells us that the host GIC is
v4. I'll try and update the relevant locations.

> 
> >  		vgic_v4_commit(vcpu);
> >  }
> >  
> > diff --git a/arch/arm64/kvm/vgic/vgic.h
> > b/arch/arm64/kvm/vgic/vgic.h
> > index d5d9264f0a1e9..978d7f8426361 100644
> > --- a/arch/arm64/kvm/vgic/vgic.h
> > +++ b/arch/arm64/kvm/vgic/vgic.h
> > @@ -132,6 +132,28 @@ static inline bool irq_is_pending(struct
> > vgic_irq *irq)
> >  		return irq->pending_latch || irq->line_level;
> >  }
> >  
> > +/* Requires the irq_lock to be held by the caller. */
> 
> Can you use a lockdep notation to make that explicit?

I have actually dropped this as it didn't add anything useful once the
oracle code was cleaned up.

> 
> > +static inline bool irq_is_enabled(struct vgic_irq *irq)
> > +{
> > +	if (irq->enabled)
> > +		return true;
> > +
> > +	/*
> > +	 * We always consider GICv5 interrupts as enabled as we
> > can
> > +	 * always inject them. The state is handled by the
> > hardware,
> > +	 * and the hardware will only signal the interrupt to the
> > +	 * guest once the guest enables it.
> 
> With my fussy reviewer hat on, that's wrapped a bit early.  Go up
> to 80 chars for comments.
> 
> > +	 */
> > +	if (irq->target_vcpu) {
> > +		u32 vgic_model = irq->target_vcpu->kvm-
> > >arch.vgic.vgic_model;
> > +
> > +		if (vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
> > +			return true;
> > +	}
> > +
> > +	return false;
> > +}
> 
> > diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
> > index 500709bd62c8d..b5180edbd1165 100644
> > --- a/include/kvm/arm_vgic.h
> > +++ b/include/kvm/arm_vgic.h
> > @@ -32,6 +32,9 @@
> >  #define VGIC_MIN_LPI		8192
> >  #define KVM_IRQCHIP_NUM_PINS	(1020 - 32)
> >  
> > +/* GICv5 constants */
> > +#define VGIC_V5_NR_PRIVATE_IRQS	128
> 
> You have earlier checks against this value (there was one around PPI
> DVI setup 
> a few patches back).  So probably better to pull the define earlier
> and
> use it there as well?

Good shout. I've actually re-worked the DVI one a little, but will
check if it makes sense to move this to one of the earlier commits or
not.

> 
> > +
> >  #define is_v5_type(t, i)	(FIELD_GET(GICV5_HWIRQ_TYPE, (i))
> > == (t))
> >  
> >  #define __irq_is_sgi(t,
> > i)						\
> 

Thanks as always,
Sascha


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 19/36] KVM: arm64: gic-v5: Check for pending PPIs
  2025-12-19 15:52 ` [PATCH v2 19/36] KVM: arm64: gic-v5: Check for pending PPIs Sascha Bischoff
  2026-01-07 15:00   ` Jonathan Cameron
@ 2026-01-08 16:10   ` Joey Gouly
  2026-01-08 16:21     ` Sascha Bischoff
  1 sibling, 1 reply; 100+ messages in thread
From: Joey Gouly @ 2026-01-08 16:10 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,
	Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
	lpieralisi@kernel.org, Timothy Hayes

Small nit,

On Fri, Dec 19, 2025 at 03:52:42PM +0000, Sascha Bischoff wrote:
> This change allows KVM to check for pending PPI interrupts. This has
> two main components:
> 
> First of all, the effective priority mask is calculated.  This is a
> combination of the priority mask in the VPEs ICC_PCR_EL1.PRIORITY and
> the currently running priority as determined from the VPE's
> ICH_APR_EL1. If an interrupt's prioirity is greater than or equal to
> the effective priority mask, it can be signalled. Otherwise, it
> cannot.
> 
> Secondly, any Enabled and Pending PPIs must be checked against this
> compound priority mask. The reqires the PPI priorities to by synced
> back to the KVM shadow state - this is skipped in general operation as
> it isn't required and is rather expensive. If any Enabled and Pending
> PPIs are of sufficient priority to be signalled, then there are
> pending PPIs. Else, there are not.  This ensures that a VPE is not
> woken when it cannot actually process the pending interrupts.
> 
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> ---
>  arch/arm64/kvm/vgic/vgic-v5.c | 121 ++++++++++++++++++++++++++++++++++
>  arch/arm64/kvm/vgic/vgic.c    |   5 +-
>  arch/arm64/kvm/vgic/vgic.h    |   1 +
>  3 files changed, 126 insertions(+), 1 deletion(-)
> 
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index cb3dd872d16a6..c7ecc4f40b1e5 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> @@ -56,6 +56,31 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
>  	return 0;
>  }
>  
> +static u32 vgic_v5_get_effective_priority_mask(struct kvm_vcpu *vcpu)
> +{
> +	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> +	u32 highest_ap, priority_mask;
> +
> +	/*
> +	 * Counting the number of trailing zeros gives the current
> +	 * active priority. Explicitly use the 32-bit version here as
> +	 * we have 32 priorities. 0x20 then means that there are no
> +	 * active priorities.
> +	 */
> +	highest_ap = cpu_if->vgic_apr ? __builtin_ctz(cpu_if->vgic_apr) : 32;
> +
> +	/*
> +	 * An interrupt is of sufficient priority if it is equal to or
> +	 * greater than the priority mask. Add 1 to the priority mask
> +	 * (i.e., lower priority) to match the APR logic before taking
> +	 * the min. This gives us the lowest priority that is masked.
> +	 */
> +	priority_mask = FIELD_GET(FEAT_GCIE_ICH_VMCR_EL2_VPMR, cpu_if->vgic_vmcr);
> +	priority_mask = min(highest_ap, priority_mask + 1);
> +
> +	return priority_mask;
> +}
> +
>  static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
>  					  struct vgic_irq *irq)
>  {
> @@ -131,6 +156,102 @@ void vgic_v5_set_ppi_ops(struct vgic_irq *irq)
>  	}
>  }
>  
> +
> +/*
> + * Sync back the PPI priorities to the vgic_irq shadow state
> + */
> +static void vgic_v5_sync_ppi_priorities(struct kvm_vcpu *vcpu)
> +{
> +	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> +	int i, reg;
> +
> +	/* We have 16 PPI Priority regs */
> +	for (reg = 0; reg < 16; reg++) {
> +		const unsigned long priorityr = cpu_if->vgic_ppi_priorityr[reg];
> +
> +		for (i = 0; i < 8; ++i) {
> +			struct vgic_irq *irq;
> +			u32 intid;
> +			u8 priority;
> +
> +			priority = (priorityr >> (i * 8)) & 0x1f;
> +
> +			intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
> +			intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg * 8 + i);
> +
> +			irq = vgic_get_vcpu_irq(vcpu, intid);
> +
> +			scoped_guard(raw_spinlock, &irq->irq_lock)
> +				irq->priority = priority;
> +
> +			vgic_put_irq(vcpu->kvm, irq);
> +		}
> +	}
> +}
> +
> +bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu)
> +{
> +	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> +	int i, reg;
> +	unsigned int priority_mask;
> +
> +	/* If no pending bits are set, exit early */
> +	if (likely(!cpu_if->vgic_ppi_pendr[0] && !cpu_if->vgic_ppi_pendr[1]))
> +		return false;
> +
> +	priority_mask = vgic_v5_get_effective_priority_mask(vcpu);
> +
> +	/* If the combined priority mask is 0, nothing can be signalled! */
> +	if (!priority_mask)
> +		return false;
> +
> +	/* The shadow priority is only updated on demand, sync it across first */
> +	vgic_v5_sync_ppi_priorities(vcpu);
> +
> +	for (reg = 0; reg < 2; reg++) {
> +		unsigned long possible_bits;
> +		const unsigned long enabler = cpu_if->vgic_ich_ppi_enabler_exit[reg];
> +		const unsigned long pendr = cpu_if->vgic_ppi_pendr_exit[reg];
> +		bool has_pending = false;
> +
> +		/* Check all interrupts that are enabled and pending */
> +		possible_bits = enabler & pendr;
> +
> +		/*
> +		 * Optimisation: pending and enabled with no active priorities
> +		 */
> +		if (possible_bits && priority_mask > 0x1f)
> +			return true;
> +
> +		for_each_set_bit(i, &possible_bits, 64) {
> +			struct vgic_irq *irq;
> +			u32 intid;
> +
> +			intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
> +			intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg * 64 + i);
> +
> +			irq = vgic_get_vcpu_irq(vcpu, intid);
> +
> +			scoped_guard(raw_spinlock, &irq->irq_lock) {
> +				/*
> +				 * We know that the interrupt is
> +				 * enabled and pending, so only check
> +				 * the priority.
> +				 */
> +				if (irq->priority <= priority_mask)
> +					has_pending = true;
> +			}
> +
> +			vgic_put_irq(vcpu->kvm, irq);
> +
> +			if (has_pending)
> +				return true;
> +		}
> +	}
> +
> +	return false;
> +}
> +
>  /*
>   * Detect any PPIs state changes, and propagate the state with KVM's
>   * shadow structures.
> diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
> index cb5d43b34462b..dfec6ed7936ed 100644
> --- a/arch/arm64/kvm/vgic/vgic.c
> +++ b/arch/arm64/kvm/vgic/vgic.c
> @@ -1180,9 +1180,12 @@ int kvm_vgic_vcpu_pending_irq(struct kvm_vcpu *vcpu)
>  	unsigned long flags;
>  	struct vgic_vmcr vmcr;
>  
> -	if (!vcpu->kvm->arch.vgic.enabled)
> +	if (!vcpu->kvm->arch.vgic.enabled && !vgic_is_v5(vcpu->kvm))
>  		return false;
>  
> +	if (vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)

        if (vgic_is_v5(vcpu->kvm))

Otherwise:

Reviewed-by: Joey Gouly <joey.gouly@arm.com>

> +		return vgic_v5_has_pending_ppi(vcpu);
> +
>  	if (vcpu->arch.vgic_cpu.vgic_v3.its_vpe.pending_last)
>  		return true;
>  
> diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
> index 978d7f8426361..65c031da83e78 100644
> --- a/arch/arm64/kvm/vgic/vgic.h
> +++ b/arch/arm64/kvm/vgic/vgic.h
> @@ -388,6 +388,7 @@ int vgic_v5_probe(const struct gic_kvm_info *info);
>  void vgic_v5_get_implemented_ppis(void);
>  void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
>  int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
> +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);
>  void vgic_v5_load(struct kvm_vcpu *vcpu);
> -- 
> 2.34.1


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 19/36] KVM: arm64: gic-v5: Check for pending PPIs
  2026-01-08 16:10   ` Joey Gouly
@ 2026-01-08 16:21     ` Sascha Bischoff
  0 siblings, 0 replies; 100+ messages in thread
From: Sascha Bischoff @ 2026-01-08 16:21 UTC (permalink / raw)
  To: Joey Gouly
  Cc: yuzenghui@huawei.com, Timothy Hayes, Suzuki Poulose, nd,
	peter.maydell@linaro.org, kvmarm@lists.linux.dev,
	linux-arm-kernel@lists.infradead.org, kvm@vger.kernel.org,
	lpieralisi@kernel.org, maz@kernel.org, oliver.upton@linux.dev

On Thu, 2026-01-08 at 16:10 +0000, Joey Gouly wrote:
> Small nit,
> 
> On Fri, Dec 19, 2025 at 03:52:42PM +0000, Sascha Bischoff wrote:
> > This change allows KVM to check for pending PPI interrupts. This
> > has
> > two main components:
> > 
> > First of all, the effective priority mask is calculated.  This is a
> > combination of the priority mask in the VPEs ICC_PCR_EL1.PRIORITY
> > and
> > the currently running priority as determined from the VPE's
> > ICH_APR_EL1. If an interrupt's prioirity is greater than or equal
> > to
> > the effective priority mask, it can be signalled. Otherwise, it
> > cannot.
> > 
> > Secondly, any Enabled and Pending PPIs must be checked against this
> > compound priority mask. The reqires the PPI priorities to by synced
> > back to the KVM shadow state - this is skipped in general operation
> > as
> > it isn't required and is rather expensive. If any Enabled and
> > Pending
> > PPIs are of sufficient priority to be signalled, then there are
> > pending PPIs. Else, there are not.  This ensures that a VPE is not
> > woken when it cannot actually process the pending interrupts.
> > 
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > ---
> >  arch/arm64/kvm/vgic/vgic-v5.c | 121
> > ++++++++++++++++++++++++++++++++++
> >  arch/arm64/kvm/vgic/vgic.c    |   5 +-
> >  arch/arm64/kvm/vgic/vgic.h    |   1 +
> >  3 files changed, 126 insertions(+), 1 deletion(-)
> > 
> > diff --git a/arch/arm64/kvm/vgic/vgic-v5.c
> > b/arch/arm64/kvm/vgic/vgic-v5.c
> > index cb3dd872d16a6..c7ecc4f40b1e5 100644
> > --- a/arch/arm64/kvm/vgic/vgic-v5.c
> > +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> > @@ -56,6 +56,31 @@ int vgic_v5_probe(const struct gic_kvm_info
> > *info)
> >  	return 0;
> >  }
> >  
> > +static u32 vgic_v5_get_effective_priority_mask(struct kvm_vcpu
> > *vcpu)
> > +{
> > +	struct vgic_v5_cpu_if *cpu_if = &vcpu-
> > >arch.vgic_cpu.vgic_v5;
> > +	u32 highest_ap, priority_mask;
> > +
> > +	/*
> > +	 * Counting the number of trailing zeros gives the current
> > +	 * active priority. Explicitly use the 32-bit version here
> > as
> > +	 * we have 32 priorities. 0x20 then means that there are
> > no
> > +	 * active priorities.
> > +	 */
> > +	highest_ap = cpu_if->vgic_apr ? __builtin_ctz(cpu_if-
> > >vgic_apr) : 32;
> > +
> > +	/*
> > +	 * An interrupt is of sufficient priority if it is equal
> > to or
> > +	 * greater than the priority mask. Add 1 to the priority
> > mask
> > +	 * (i.e., lower priority) to match the APR logic before
> > taking
> > +	 * the min. This gives us the lowest priority that is
> > masked.
> > +	 */
> > +	priority_mask = FIELD_GET(FEAT_GCIE_ICH_VMCR_EL2_VPMR,
> > cpu_if->vgic_vmcr);
> > +	priority_mask = min(highest_ap, priority_mask + 1);
> > +
> > +	return priority_mask;
> > +}
> > +
> >  static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
> >  					  struct vgic_irq *irq)
> >  {
> > @@ -131,6 +156,102 @@ void vgic_v5_set_ppi_ops(struct vgic_irq
> > *irq)
> >  	}
> >  }
> >  
> > +
> > +/*
> > + * Sync back the PPI priorities to the vgic_irq shadow state
> > + */
> > +static void vgic_v5_sync_ppi_priorities(struct kvm_vcpu *vcpu)
> > +{
> > +	struct vgic_v5_cpu_if *cpu_if = &vcpu-
> > >arch.vgic_cpu.vgic_v5;
> > +	int i, reg;
> > +
> > +	/* We have 16 PPI Priority regs */
> > +	for (reg = 0; reg < 16; reg++) {
> > +		const unsigned long priorityr = cpu_if-
> > >vgic_ppi_priorityr[reg];
> > +
> > +		for (i = 0; i < 8; ++i) {
> > +			struct vgic_irq *irq;
> > +			u32 intid;
> > +			u8 priority;
> > +
> > +			priority = (priorityr >> (i * 8)) & 0x1f;
> > +
> > +			intid = FIELD_PREP(GICV5_HWIRQ_TYPE,
> > GICV5_HWIRQ_TYPE_PPI);
> > +			intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg *
> > 8 + i);
> > +
> > +			irq = vgic_get_vcpu_irq(vcpu, intid);
> > +
> > +			scoped_guard(raw_spinlock, &irq->irq_lock)
> > +				irq->priority = priority;
> > +
> > +			vgic_put_irq(vcpu->kvm, irq);
> > +		}
> > +	}
> > +}
> > +
> > +bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu)
> > +{
> > +	struct vgic_v5_cpu_if *cpu_if = &vcpu-
> > >arch.vgic_cpu.vgic_v5;
> > +	int i, reg;
> > +	unsigned int priority_mask;
> > +
> > +	/* If no pending bits are set, exit early */
> > +	if (likely(!cpu_if->vgic_ppi_pendr[0] && !cpu_if-
> > >vgic_ppi_pendr[1]))
> > +		return false;
> > +
> > +	priority_mask = vgic_v5_get_effective_priority_mask(vcpu);
> > +
> > +	/* If the combined priority mask is 0, nothing can be
> > signalled! */
> > +	if (!priority_mask)
> > +		return false;
> > +
> > +	/* The shadow priority is only updated on demand, sync it
> > across first */
> > +	vgic_v5_sync_ppi_priorities(vcpu);
> > +
> > +	for (reg = 0; reg < 2; reg++) {
> > +		unsigned long possible_bits;
> > +		const unsigned long enabler = cpu_if-
> > >vgic_ich_ppi_enabler_exit[reg];
> > +		const unsigned long pendr = cpu_if-
> > >vgic_ppi_pendr_exit[reg];
> > +		bool has_pending = false;
> > +
> > +		/* Check all interrupts that are enabled and
> > pending */
> > +		possible_bits = enabler & pendr;
> > +
> > +		/*
> > +		 * Optimisation: pending and enabled with no
> > active priorities
> > +		 */
> > +		if (possible_bits && priority_mask > 0x1f)
> > +			return true;
> > +
> > +		for_each_set_bit(i, &possible_bits, 64) {
> > +			struct vgic_irq *irq;
> > +			u32 intid;
> > +
> > +			intid = FIELD_PREP(GICV5_HWIRQ_TYPE,
> > GICV5_HWIRQ_TYPE_PPI);
> > +			intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg *
> > 64 + i);
> > +
> > +			irq = vgic_get_vcpu_irq(vcpu, intid);
> > +
> > +			scoped_guard(raw_spinlock, &irq->irq_lock)
> > {
> > +				/*
> > +				 * We know that the interrupt is
> > +				 * enabled and pending, so only
> > check
> > +				 * the priority.
> > +				 */
> > +				if (irq->priority <=
> > priority_mask)
> > +					has_pending = true;
> > +			}
> > +
> > +			vgic_put_irq(vcpu->kvm, irq);
> > +
> > +			if (has_pending)
> > +				return true;
> > +		}
> > +	}
> > +
> > +	return false;
> > +}
> > +
> >  /*
> >   * Detect any PPIs state changes, and propagate the state with
> > KVM's
> >   * shadow structures.
> > diff --git a/arch/arm64/kvm/vgic/vgic.c
> > b/arch/arm64/kvm/vgic/vgic.c
> > index cb5d43b34462b..dfec6ed7936ed 100644
> > --- a/arch/arm64/kvm/vgic/vgic.c
> > +++ b/arch/arm64/kvm/vgic/vgic.c
> > @@ -1180,9 +1180,12 @@ int kvm_vgic_vcpu_pending_irq(struct
> > kvm_vcpu *vcpu)
> >  	unsigned long flags;
> >  	struct vgic_vmcr vmcr;
> >  
> > -	if (!vcpu->kvm->arch.vgic.enabled)
> > +	if (!vcpu->kvm->arch.vgic.enabled && !vgic_is_v5(vcpu-
> > >kvm))
> >  		return false;
> >  
> > +	if (vcpu->kvm->arch.vgic.vgic_model ==
> > KVM_DEV_TYPE_ARM_VGIC_V5)
> 
>         if (vgic_is_v5(vcpu->kvm))
> 

Ah, yes. Cheers!

> Otherwise:
> 
> Reviewed-by: Joey Gouly <joey.gouly@arm.com>

Thanks,
Sascha

> 
> > +		return vgic_v5_has_pending_ppi(vcpu);
> > +
> >  	if (vcpu->arch.vgic_cpu.vgic_v3.its_vpe.pending_last)
> >  		return true;
> >  
> > diff --git a/arch/arm64/kvm/vgic/vgic.h
> > b/arch/arm64/kvm/vgic/vgic.h
> > index 978d7f8426361..65c031da83e78 100644
> > --- a/arch/arm64/kvm/vgic/vgic.h
> > +++ b/arch/arm64/kvm/vgic/vgic.h
> > @@ -388,6 +388,7 @@ int vgic_v5_probe(const struct gic_kvm_info
> > *info);
> >  void vgic_v5_get_implemented_ppis(void);
> >  void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
> >  int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
> > +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);
> >  void vgic_v5_load(struct kvm_vcpu *vcpu);
> > -- 
> > 2.34.1


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 19/36] KVM: arm64: gic-v5: Check for pending PPIs
  2026-01-07 15:00   ` Jonathan Cameron
@ 2026-01-08 16:23     ` Sascha Bischoff
  2026-01-08 16:57       ` Jonathan Cameron
  0 siblings, 1 reply; 100+ messages in thread
From: Sascha Bischoff @ 2026-01-08 16:23 UTC (permalink / raw)
  To: jonathan.cameron@huawei.com
  Cc: yuzenghui@huawei.com, lpieralisi@kernel.org, Timothy Hayes,
	Suzuki Poulose, nd, peter.maydell@linaro.org,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, Joey Gouly, maz@kernel.org,
	oliver.upton@linux.dev

On Wed, 2026-01-07 at 15:00 +0000, Jonathan Cameron wrote:
> On Fri, 19 Dec 2025 15:52:42 +0000
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> 
> > This change allows KVM to check for pending PPI interrupts. This
> > has
> > two main components:
> > 
> > First of all, the effective priority mask is calculated.  This is a
> > combination of the priority mask in the VPEs ICC_PCR_EL1.PRIORITY
> > and
> > the currently running priority as determined from the VPE's
> > ICH_APR_EL1. If an interrupt's prioirity is greater than or equal
> > to
> 
> priority
> 
> > the effective priority mask, it can be signalled. Otherwise, it
> > cannot.
> > 
> > Secondly, any Enabled and Pending PPIs must be checked against this
> > compound priority mask. The reqires the PPI priorities to by synced
> > back to the KVM shadow state - this is skipped in general operation
> > as
> > it isn't required and is rather expensive. If any Enabled and
> > Pending
> > PPIs are of sufficient priority to be signalled, then there are
> > pending PPIs. Else, there are not.  This ensures that a VPE is not
> > woken when it cannot actually process the pending interrupts.
> > 
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> Hi Sascha,
> 
> One thing I notice in here is the use of unsigned long vs u64 is a
> bit
> inconsistent.  When it's a register or something we just read from a
> register
> I'd always use u64.

Yeah, I'd like to do the same. The issue is that the for_each_set_bit()
loop construct only works with unsigned long, and not u64. I'll rework
the code to use u64 wherever possible.

> 
> A few other things inline.
> > ---
> >  arch/arm64/kvm/vgic/vgic-v5.c | 121
> > ++++++++++++++++++++++++++++++++++
> >  arch/arm64/kvm/vgic/vgic.c    |   5 +-
> >  arch/arm64/kvm/vgic/vgic.h    |   1 +
> >  3 files changed, 126 insertions(+), 1 deletion(-)
> > 
> > diff --git a/arch/arm64/kvm/vgic/vgic-v5.c
> > b/arch/arm64/kvm/vgic/vgic-v5.c
> > index cb3dd872d16a6..c7ecc4f40b1e5 100644
> > --- a/arch/arm64/kvm/vgic/vgic-v5.c
> > +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> > @@ -56,6 +56,31 @@ int vgic_v5_probe(const struct gic_kvm_info
> > *info)
> >  	return 0;
> >  }
> >  
> > +static u32 vgic_v5_get_effective_priority_mask(struct kvm_vcpu
> > *vcpu)
> > +{
> > +	struct vgic_v5_cpu_if *cpu_if = &vcpu-
> > >arch.vgic_cpu.vgic_v5;
> > +	u32 highest_ap, priority_mask;
> > +
> > +	/*
> > +	 * Counting the number of trailing zeros gives the current
> > +	 * active priority. Explicitly use the 32-bit version here
> > as
> 
> Short wrap.  I'll stop commenting on these and assume you'll check
> throughout
> (or ignore throughout if you disagree ;) Everyone should use an email
> client with rulers!

Yeah, I'll address these wherever I spot them.

> 
> > +	 * we have 32 priorities. 0x20 then means that there are
> > no
> > +	 * active priorities.
> > +	 */
> > +	highest_ap = cpu_if->vgic_apr ? __builtin_ctz(cpu_if-
> > >vgic_apr) : 32;
> 
> If the comment is going to say 0x20 means no active, then use hex in
> the code
> as well. Or just use 32 in the comment.

Done.

> 
> > +
> > +	/*
> > +	 * An interrupt is of sufficient priority if it is equal
> > to or
> > +	 * greater than the priority mask. Add 1 to the priority
> > mask
> > +	 * (i.e., lower priority) to match the APR logic before
> > taking
> > +	 * the min. This gives us the lowest priority that is
> > masked.
> > +	 */
> > +	priority_mask = FIELD_GET(FEAT_GCIE_ICH_VMCR_EL2_VPMR,
> > cpu_if->vgic_vmcr);
> > +	priority_mask = min(highest_ap, priority_mask + 1);
> > +
> > +	return priority_mask;
> 
> Unless you are going to do more with that in later patches
> 	return min(highest_ap, priority_mask + 1);
> Doesn't lose any significant readability to my eyes.

Agreed, done.

> 
> > +}
> > +
> >  static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
> >  					  struct vgic_irq *irq)
> >  {
> > @@ -131,6 +156,102 @@ void vgic_v5_set_ppi_ops(struct vgic_irq
> > *irq)
> >  	}
> >  }
> >  
> > +
> > +/*
> > + * Sync back the PPI priorities to the vgic_irq shadow state
> > + */
> > +static void vgic_v5_sync_ppi_priorities(struct kvm_vcpu *vcpu)
> > +{
> > +	struct vgic_v5_cpu_if *cpu_if = &vcpu-
> > >arch.vgic_cpu.vgic_v5;
> > +	int i, reg;
> > +
> > +	/* We have 16 PPI Priority regs */
> > +	for (reg = 0; reg < 16; reg++) {
> 
> I'd drag the declaration in as
> 	for (int ret = 0;
> 

Done.

> > +		const unsigned long priorityr = cpu_if-
> > >vgic_ppi_priorityr[reg];
> > +
> > +		for (i = 0; i < 8; ++i) {
> similar for int i = 0 here
> 
> Kernel style is getting more accepting of these 'modern' style things
> ;)
> Up to you though if you prefer old school.
> 
> > +			struct vgic_irq *irq;
> > +			u32 intid;
> > +			u8 priority;
> > +
> > +			priority = (priorityr >> (i * 8)) & 0x1f;
> 
> GENMASK(4, 0); maybe.  It's short enough (I can count to 1 f easily
> enough!)
> that I don't really mind which style you use for this.

Frankly, that makes the intent clearer to me so that's better.

> 
> > +
> > +			intid = FIELD_PREP(GICV5_HWIRQ_TYPE,
> > GICV5_HWIRQ_TYPE_PPI);
> > +			intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg *
> > 8 + i);
> > +
> > +			irq = vgic_get_vcpu_irq(vcpu, intid);
> > +
> > +			scoped_guard(raw_spinlock, &irq->irq_lock)
> > +				irq->priority = priority;
> > +
> > +			vgic_put_irq(vcpu->kvm, irq);
> > +		}
> > +	}
> > +}
> > +
> > +bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu)
> > +{
> > +	struct vgic_v5_cpu_if *cpu_if = &vcpu-
> > >arch.vgic_cpu.vgic_v5;
> > +	int i, reg;
> > +	unsigned int priority_mask;
> > +
> > +	/* If no pending bits are set, exit early */
> > +	if (likely(!cpu_if->vgic_ppi_pendr[0] && !cpu_if-
> > >vgic_ppi_pendr[1]))
> 
> That likely seems a little bit dubious. I'd be tempted to not mark
> this
> unless you have stats on running systems where the predictors get it
> wrong
> enough that the mark is useful.

OK, I've dropped that. Something to revisit once we have some hardware.

> 
> > +		return false;
> > +
> > +	priority_mask = vgic_v5_get_effective_priority_mask(vcpu);
> > +
> > +	/* If the combined priority mask is 0, nothing can be
> > signalled! */
> > +	if (!priority_mask)
> > +		return false;
> > +
> > +	/* The shadow priority is only updated on demand, sync it
> > across first */
> > +	vgic_v5_sync_ppi_priorities(vcpu);
> > +
> > +	for (reg = 0; reg < 2; reg++) {
> > +		unsigned long possible_bits;
> > +		const unsigned long enabler = cpu_if-
> > >vgic_ich_ppi_enabler_exit[reg];
> Given storage of vgic_ich_ppi_enabler_exit[reg] is a u64 and you are
> going to
> use that length explicitly (the 64 in the bitmap walk below) I'd make
> these
> u64s.  I've not really been keeping an eye open for this in other
> patches, so
> maybe look for other cases where an explicit length is clearer.
> u64 shorter as well!

Yeah, I'm making sure to use u64 wherever possible, with he exception
being for the bit-based loops.

> 
> > +		const unsigned long pendr = cpu_if-
> > >vgic_ppi_pendr_exit[reg];
> > +		bool has_pending = false;
> > +
> > +		/* Check all interrupts that are enabled and
> > pending */
> > +		possible_bits = enabler & pendr;
> > +
> > +		/*
> > +		 * Optimisation: pending and enabled with no
> > active priorities
> > +		 */
> > +		if (possible_bits && priority_mask > 0x1f)
> 
> I 'think' priority_mask > 0x1f is always 0x20?  I'd match that
> explicitly so the
> relationship to the magic value comment above is obvious

Yeah, I've just changed that to explicitly check for 32 (matching what
I did in the other patch that introduces the effective priority
calculation).

Thanks,
Sascha

> 
> > +			return true;
> > +
> > +		for_each_set_bit(i, &possible_bits, 64) {
> > +			struct vgic_irq *irq;
> > +			u32 intid;
> > +
> > +			intid = FIELD_PREP(GICV5_HWIRQ_TYPE,
> > GICV5_HWIRQ_TYPE_PPI);
> > +			intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg *
> > 64 + i);
> > +
> > +			irq = vgic_get_vcpu_irq(vcpu, intid);
> > +
> > +			scoped_guard(raw_spinlock, &irq->irq_lock)
> > {
> > +				/*
> > +				 * We know that the interrupt is
> > +				 * enabled and pending, so only
> > check
> > +				 * the priority.
> > +				 */
> > +				if (irq->priority <=
> > priority_mask)
> > +					has_pending = true;
> > +			}
> > +
> > +			vgic_put_irq(vcpu->kvm, irq);
> > +
> > +			if (has_pending)
> > +				return true;
> > +		}
> > +	}
> > +
> > +	return false;
> > +}
> > +
> >  /*
> >   * Detect any PPIs state changes, and propagate the state with
> > KVM's
> >   * shadow structures.
> > diff --git a/arch/arm64/kvm/vgic/vgic.c
> > b/arch/arm64/kvm/vgic/vgic.c
> > index cb5d43b34462b..dfec6ed7936ed 100644
> > --- a/arch/arm64/kvm/vgic/vgic.c
> > +++ b/arch/arm64/kvm/vgic/vgic.c
> > @@ -1180,9 +1180,12 @@ int kvm_vgic_vcpu_pending_irq(struct
> > kvm_vcpu *vcpu)
> >  	unsigned long flags;
> >  	struct vgic_vmcr vmcr;
> >  
> > -	if (!vcpu->kvm->arch.vgic.enabled)
> > +	if (!vcpu->kvm->arch.vgic.enabled && !vgic_is_v5(vcpu-
> > >kvm))
> >  		return false;
> >  
> > +	if (vcpu->kvm->arch.vgic.vgic_model ==
> > KVM_DEV_TYPE_ARM_VGIC_V5)
> > +		return vgic_v5_has_pending_ppi(vcpu);
> > +
> >  	if (vcpu->arch.vgic_cpu.vgic_v3.its_vpe.pending_last)
> >  		return true;
> >  
> > diff --git a/arch/arm64/kvm/vgic/vgic.h
> > b/arch/arm64/kvm/vgic/vgic.h
> > index 978d7f8426361..65c031da83e78 100644
> > --- a/arch/arm64/kvm/vgic/vgic.h
> > +++ b/arch/arm64/kvm/vgic/vgic.h
> > @@ -388,6 +388,7 @@ int vgic_v5_probe(const struct gic_kvm_info
> > *info);
> >  void vgic_v5_get_implemented_ppis(void);
> >  void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
> >  int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
> > +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);
> >  void vgic_v5_load(struct kvm_vcpu *vcpu);
> 


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 21/36] KVM: arm64: gic-v5: Finalize GICv5 PPIs and generate mask
  2026-01-07 15:08   ` Jonathan Cameron
@ 2026-01-08 16:51     ` Sascha Bischoff
  0 siblings, 0 replies; 100+ messages in thread
From: Sascha Bischoff @ 2026-01-08 16:51 UTC (permalink / raw)
  To: jonathan.cameron@huawei.com
  Cc: yuzenghui@huawei.com, lpieralisi@kernel.org, Timothy Hayes,
	Suzuki Poulose, nd, peter.maydell@linaro.org,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, Joey Gouly, maz@kernel.org,
	oliver.upton@linux.dev

On Wed, 2026-01-07 at 15:08 +0000, Jonathan Cameron wrote:
> On Fri, 19 Dec 2025 15:52:43 +0000
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> 
> > We only want to expose a subset of the PPIs to a guest. If a PPI
> > does
> > not have an owner, it is not being actively driven by a device. The
> > SW_PPI is a special case, as it is likely for userspace to wish to
> > inject that.
> > 
> > Therefore, just prior to running the guest for the first time, we
> > need
> > to finalize the PPIs. A mask is generated which, when combined with
> > trapping a guest's PPI accesses, allows for the guest's view of the
> > PPI to be filtered.
> > 
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> 
> Minor suggestion inline. Either way
> 
> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
> 
> > diff --git a/arch/arm64/kvm/vgic/vgic-v5.c
> > b/arch/arm64/kvm/vgic/vgic-v5.c
> > index c7ecc4f40b1e5..f1fa63e67c1f6 100644
> > --- a/arch/arm64/kvm/vgic/vgic-v5.c
> > +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> > @@ -81,6 +81,66 @@ static u32
> > vgic_v5_get_effective_priority_mask(struct kvm_vcpu *vcpu)
> >  	return priority_mask;
> >  }
> >  
> > +static int vgic_v5_finalize_state(struct kvm_vcpu *vcpu)
> > +{
> > +	if (!ppi_caps)
> > +		return -ENXIO;
> > +
> > +	vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_mask[0] = 0;
> > +	vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_mask[1] = 0;
> > +	vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[0] = 0;
> > +	vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[1] = 0;
> > +	for (int i = 0; i < VGIC_V5_NR_PRIVATE_IRQS; ++i) {
> > +		int reg = i / 64;
> > +		u64 bit = BIT_ULL(i % 64);
> > +		struct vgic_irq *irq = &vcpu-
> > >arch.vgic_cpu.private_irqs[i];
> > +
> > +		raw_spin_lock(&irq->irq_lock);
> > +
> A little nicer perhaps with:
> 		guard(raw_spin_lock(&irq->irq_lock);

Harder to break in the future too. Done.

Thanks,
Sascha

> > +		/*
> > +		 * We only expose PPIs with an owner or thw SW_PPI
> > to
> > +		 * the guest.
> > +		 */
> > +		if (!irq->owner && irq->intid == GICV5_SW_PPI)
> > +			goto unlock;
> and
> 			continue;
> > +
> > +		/*
> > +		 * If the PPI isn't implemented, we can't pass it
> > +		 * through to a guest anyhow.
> > +		 */
> > +		if (!(ppi_caps->impl_ppi_mask[reg] & bit))
> > +			goto unlock;
> and
> 			continue;
> > +
> > +		vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_mask[reg] |=
> > bit;
> > +
> > +		if (irq->config == VGIC_CONFIG_LEVEL)
> > +			vcpu-
> > >arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[reg] |= bit;
> > +
> > +unlock:
> > +		raw_spin_unlock(&irq->irq_lock);
> Then the label and unlock can go away.
> 
> > +	}
> > +
> > +	return 0;
> > +}
> 


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 15/36] KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore
  2026-01-08 13:40     ` Sascha Bischoff
@ 2026-01-08 16:52       ` Jonathan Cameron
  0 siblings, 0 replies; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-08 16:52 UTC (permalink / raw)
  To: Sascha Bischoff
  Cc: yuzenghui@huawei.com, lpieralisi@kernel.org, Timothy Hayes,
	Suzuki Poulose, nd, peter.maydell@linaro.org,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, Joey Gouly, maz@kernel.org,
	oliver.upton@linux.dev

On Thu, 8 Jan 2026 13:40:48 +0000
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:

> On Wed, 2026-01-07 at 12:28 +0000, Jonathan Cameron wrote:
> > On Fri, 19 Dec 2025 15:52:41 +0000
> > Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> >   
> > > This change introduces GICv5 load/put. Additionally, it plumbs in
> > > save/restore for:
> > > 
> > > * PPIs (ICH_PPI_x_EL2 regs)
> > > * ICH_VMCR_EL2
> > > * ICH_APR_EL2
> > > * ICC_ICSR_EL1
> > > 
> > > A GICv5-specific enable bit is added to struct vgic_vmcr as this
> > > differs from previous GICs. On GICv5-native systems, the VMCR only
> > > contains the enable bit (driven by the guest via ICC_CR0_EL1.EN)
> > > and
> > > the priority mask (PCR).
> > > 
> > > A struct gicv5_vpe is also introduced. This currently only contains
> > > a
> > > single field - bool resident - which is used to track if a VPE is
> > > currently running or not, and is used to avoid a case of double
> > > load
> > > or double put on the WFI path for a vCPU. This struct will be
> > > extended
> > > as additional GICv5 support is merged, specifically for VPE
> > > doorbells.
> > > 
> > > Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
> > > Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> > > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>  
> > 
> >   
> > > diff --git a/arch/arm64/kvm/vgic/vgic-v5.c
> > > b/arch/arm64/kvm/vgic/vgic-v5.c
> > > index 1fe1790f1f874..168447ee3fbed 100644
> > > --- a/arch/arm64/kvm/vgic/vgic-v5.c
> > > +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> > > @@ -1,4 +1,7 @@
> > >  // SPDX-License-Identifier: GPL-2.0-only
> > > +/*
> > > + * Copyright (C) 2025 Arm Ltd.
> > > + */  
> > 
> > Why in this patch?  It's trivial enough that maybe it doesn't need to
> > be
> > on it's own, but the first patch touching this file seems like a more
> > logical place to find it.  
> 
> I wholeheartedly agree, but it was unintentionally omitted when the
> GICv5 compat mode changes were introduced. It was originally in the
> first commit in this series to touch the file, but then things got re-
> worked so it became the second. I'll make sure that it lives in the
> first commit of this series to touch the file.
I'd just throw in a trivial commit that only does this.
Then any reorders that occur before this merges don't move it.

J
> 
> Sascha
> 



^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 01/36] KVM: arm64: Account for RES1 bits in DECLARE_FEAT_MAP() and co
  2026-01-06 17:23   ` Jonathan Cameron
@ 2026-01-08 16:52     ` Sascha Bischoff
  0 siblings, 0 replies; 100+ messages in thread
From: Sascha Bischoff @ 2026-01-08 16:52 UTC (permalink / raw)
  To: jonathan.cameron@huawei.com
  Cc: yuzenghui@huawei.com, lpieralisi@kernel.org, Timothy Hayes,
	Suzuki Poulose, nd, peter.maydell@linaro.org,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, Joey Gouly, maz@kernel.org,
	oliver.upton@linux.dev

On Tue, 2026-01-06 at 17:23 +0000, Jonathan Cameron wrote:
> On Fri, 19 Dec 2025 15:52:36 +0000
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> 
> > From: Marc Zyngier <maz@kernel.org>
> > 
> > None of the registers we manage in the feature dependency
> > infrastructure
> > so far has any RES1 bit. This is about to change, as VTCR_EL2 has
> > its bit 31 being RES1.
> 
> Oh goody.
> 
> > 
> > In order to not fail the consistency checks by not describing a
> > bit,
> > add RES1 bits to the set of immutable bits. This requires some
> > extra
> > surgery for the FGT handling, as we now need to track RES1 bits
> > there
> > as well.
> > 
> > There are no RES1 FGT bits *yet*. Watch this space.
> > 
> > Signed-off-by: Marc Zyngier <maz@kernel.org>
> FWIW it seems correct.  The only thing I wondered about is
> the assumption that if there is an error best thing to do is
> to assume it was res0 that was wrong and paper over it.
> I guess we can't do anything better if that does happen.
> 
> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
> 
> Process thing though: before anyone can merge this, Sasha
> please take a look at submitting patches documentation.
> 
> When you 'post' a patch that was written by someone else you have
> handled the patch and for the Developer Certificate of origin stuff
> to work you have to add your Signed-off-by after theirs.

Thanks for pointing that out. As Marc has posted this for review
separately I'd wrongly (!) assumed that that I shouldn't have signed it
off here. Have added that now, thanks.

Sascha

> 
> Jonathan


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 23/36] KVM: arm64: gic-v5: Support GICv5 interrupts with KVM_IRQ_LINE
  2026-01-07 15:29   ` Jonathan Cameron
@ 2026-01-08 16:53     ` Sascha Bischoff
  0 siblings, 0 replies; 100+ messages in thread
From: Sascha Bischoff @ 2026-01-08 16:53 UTC (permalink / raw)
  To: jonathan.cameron@huawei.com
  Cc: yuzenghui@huawei.com, lpieralisi@kernel.org, Timothy Hayes,
	Suzuki Poulose, nd, peter.maydell@linaro.org,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, Joey Gouly, maz@kernel.org,
	oliver.upton@linux.dev

On Wed, 2026-01-07 at 15:29 +0000, Jonathan Cameron wrote:
> On Fri, 19 Dec 2025 15:52:43 +0000
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> 
> > Interrupts under GICv5 look quite different to those from older Arm
> > GICs. Specifically, the type is encoded in the top bits of the
> > interrupt ID.
> > 
> > Extend KVM_IRQ_LINE to cope with GICv5 PPIs and SPIs. The requires
> > subtly changing the KVM_IRQ_LINE API for GICv5 guests. For older
> > Arm
> > GICs, PPIs had to be in the range of 16-31, and SPIs had to be
> > 32-1019, but this no longer holds true for GICv5. Instead, for a
> > GICv5
> > guest support PPIs in the range of 0-127, and SPIs in the range
> > 0-65535. The documentation is updated accordingly.
> > 
> > The SPI range doesn't cover the full SPI range that a GICv5 system
> > can
> > potentially cope with (GICv5 provides up to 24-bits of SPI ID
> > space,
> > and we only have 16 bits to work with in KVM_IRQ_LINE). However,
> > 65k
> > SPIs is more than would be reasonably expected on systems for years
> > to
> > come.
> > 
> > Note: As the GICv5 KVM implementation currently doesn't support
> > injecting SPIs attempts to do so will fail. This restruction will
> 
> restriction
> 
> In general,  worth spell checking the lot. (something I always
> forget to do for my own series!)
> 
> > lifted as the GICv5 KVM support evolves.
> > 
> > Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
> > Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> One passing comment inline. Perhaps there isn't a suitable place to
> put
> vgic_is_v5() though. I haven't checked.
> 
> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
> 
> > ---
> >  Documentation/virt/kvm/api.rst |  6 ++++--
> >  arch/arm64/kvm/arm.c           | 21 ++++++++++++++++++---
> >  arch/arm64/kvm/vgic/vgic.c     |  4 ++++
> >  3 files changed, 26 insertions(+), 5 deletions(-)
> > 
> 
> > diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
> > index 94f8d13ab3b58..4448e8a5fc076 100644
> > --- a/arch/arm64/kvm/arm.c
> > +++ b/arch/arm64/kvm/arm.c
> > @@ -45,6 +45,8 @@
> >  #include <kvm/arm_pmu.h>
> >  #include <kvm/arm_psci.h>
> >  
> > +#include <linux/irqchip/arm-gic-v5.h>
> > +
> >  #include "sys_regs.h"
> >  
> >  static enum kvm_mode kvm_mode = KVM_MODE_DEFAULT;
> > @@ -1430,16 +1432,29 @@ int kvm_vm_ioctl_irq_line(struct kvm *kvm,
> > struct kvm_irq_level *irq_level,
> >  		if (!vcpu)
> >  			return -EINVAL;
> >  
> > -		if (irq_num < VGIC_NR_SGIS || irq_num >=
> > VGIC_NR_PRIVATE_IRQS)
> > +		if (kvm->arch.vgic.vgic_model ==
> > KVM_DEV_TYPE_ARM_VGIC_V5) {
> 
> Maybe it's worth moving the vgic_is_v5() helper to somewhere that
> makes it useable
> here?

Considering that this is handling code for interrupts on Arm, I've
added the kvm/arm_vgic.h header, and changed to using that helper.

Sascha

> 
> 
> > +			if (irq_num >= VGIC_V5_NR_PRIVATE_IRQS)
> > +				return -EINVAL;
> > +
> > +			/* Build a GICv5-style IntID here */
> > +			irq_num |= FIELD_PREP(GICV5_HWIRQ_TYPE,
> > GICV5_HWIRQ_TYPE_PPI);
> > +		} else if (irq_num < VGIC_NR_SGIS ||
> > +			   irq_num >= VGIC_NR_PRIVATE_IRQS) {
> >  			return -EINVAL;
> > +		}
> >  
> >  		return kvm_vgic_inject_irq(kvm, vcpu, irq_num,
> > level, NULL);
> 


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 24/36] KVM: arm64: gic-v5: Create, init vgic_v5
  2026-01-07 15:49   ` Jonathan Cameron
@ 2026-01-08 16:55     ` Sascha Bischoff
  0 siblings, 0 replies; 100+ messages in thread
From: Sascha Bischoff @ 2026-01-08 16:55 UTC (permalink / raw)
  To: jonathan.cameron@huawei.com
  Cc: yuzenghui@huawei.com, lpieralisi@kernel.org, Timothy Hayes,
	Suzuki Poulose, nd, peter.maydell@linaro.org,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, Joey Gouly, maz@kernel.org,
	oliver.upton@linux.dev

On Wed, 2026-01-07 at 15:49 +0000, Jonathan Cameron wrote:
> On Fri, 19 Dec 2025 15:52:44 +0000
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> 
> > Update kvm_vgic_create to create a vgic_v5 device. When creating a
> > vgic, FEAT_GCIE in the ID_AA64PFR2 is only exposed to vgic_v5-based
> > guests, and is hidden otherwise. GIC in ~ID_AA64PFR0_EL1 is never
> > exposed for a vgic_v5 guest.
> > 
> > When initialising a vgic_v5, skip kvm_vgic_dist_init as GICv5
> > doesn't
> > support one. The current vgic_v5 implementation only supports PPIs,
> > so
> > no SPIs are initialised either.
> > 
> > The current vgic_v5 support doesn't extend to nested
> 
> Odd early wrapping of message.

Fixed. Interestingly, emacs is very insistent that this is the correct
way to wrap here.

> 
> > guests. Therefore, the init of vgic_v5 for a nested guest is failed
> > in
> > vgic_v5_init.
> > 
> > As the current vgic_v5 doesn't require any resources to be mapped,
> > vgic_v5_map_resources is simply used to check that the vgic has
> > indeed
> > been initialised. Again, this will change as more GICv5 support is
> > merged in.
> > 
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> Comments mostly on existing code, so
> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
> 
> > ---
> >  arch/arm64/kvm/vgic/vgic-init.c | 51 ++++++++++++++++++++++-------
> > ----
> >  arch/arm64/kvm/vgic/vgic-v5.c   | 26 +++++++++++++++++
> >  arch/arm64/kvm/vgic/vgic.h      |  2 ++
> >  include/kvm/arm_vgic.h          |  1 +
> >  4 files changed, 63 insertions(+), 17 deletions(-)
> > 
> > diff --git a/arch/arm64/kvm/vgic/vgic-init.c
> > b/arch/arm64/kvm/vgic/vgic-init.c
> > index 03f45816464b0..afb5888cd8219 100644
> > --- a/arch/arm64/kvm/vgic/vgic-init.c
> > +++ b/arch/arm64/kvm/vgic/vgic-init.c
> 
> >  	if (type == KVM_DEV_TYPE_ARM_VGIC_V3)
> > @@ -420,20 +427,26 @@ int vgic_init(struct kvm *kvm)
> >  	if (kvm->created_vcpus != atomic_read(&kvm->online_vcpus))
> >  		return -EBUSY;
> >  
> > -	/* freeze the number of spis */
> > -	if (!dist->nr_spis)
> > -		dist->nr_spis = VGIC_NR_IRQS_LEGACY -
> > VGIC_NR_PRIVATE_IRQS;
> > +	if (!vgic_is_v5(kvm)) {
> > +		/* freeze the number of spis */
> > +		if (!dist->nr_spis)
> > +			dist->nr_spis = VGIC_NR_IRQS_LEGACY -
> > VGIC_NR_PRIVATE_IRQS;
> >  
> > -	ret = kvm_vgic_dist_init(kvm, dist->nr_spis);
> > -	if (ret)
> > -		goto out;
> > +		ret = kvm_vgic_dist_init(kvm, dist->nr_spis);
> > +		if (ret)
> > +			goto out;
> 
> Not really related to this patch, but I have no idea why this
> function
> doesn't just do early returns on error in all paths (rather than just
> some of them).
> It might be worth changing that to improve readability.

Yeah, I've done that.

> 
> 
> >  
> > -	/*
> > -	 * Ensure vPEs are allocated if direct IRQ injection (e.g.
> > vSGIs,
> > -	 * vLPIs) is supported.
> > -	 */
> > -	if (vgic_supports_direct_irqs(kvm)) {
> > -		ret = vgic_v4_init(kvm);
> > +		/*
> > +		 * Ensure vPEs are allocated if direct IRQ
> > injection (e.g. vSGIs,
> > +		 * vLPIs) is supported.
> > +		 */
> > +		if (vgic_supports_direct_irqs(kvm)) {
> > +			ret = vgic_v4_init(kvm);
> > +			if (ret)
> > +				goto out;
> > +		}
> > +	} else {
> > +		ret = vgic_v5_init(kvm);
> >  		if (ret)
> >  			goto out;
> >  	}
> > @@ -610,9 +623,13 @@ int kvm_vgic_map_resources(struct kvm *kvm)
> >  	if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V2) {
> >  		ret = vgic_v2_map_resources(kvm);
> >  		type = VGIC_V2;
> > -	} else {
> > +	} else if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3) {
> >  		ret = vgic_v3_map_resources(kvm);
> >  		type = VGIC_V3;
> > +	} else {
> > +		ret = vgic_v5_map_resources(kvm);
> > +		type = VGIC_V5;
> > +		goto out;
> This skips over the checking of ret which is fine (given it's just
> goto out)
> but I'd add a comment to say why the next bit is skipped or a more
> complex
> flow (maybe a flag to say dist is relevant that gates the next bit.

I've moved to using a flag as it makes things more readable.

Sascha

> 
> >  	}
> >  
> >  	if (ret)
> 
> 


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 19/36] KVM: arm64: gic-v5: Check for pending PPIs
  2026-01-08 16:23     ` Sascha Bischoff
@ 2026-01-08 16:57       ` Jonathan Cameron
  0 siblings, 0 replies; 100+ messages in thread
From: Jonathan Cameron @ 2026-01-08 16:57 UTC (permalink / raw)
  To: Sascha Bischoff
  Cc: yuzenghui@huawei.com, lpieralisi@kernel.org, Timothy Hayes,
	Suzuki Poulose, nd, peter.maydell@linaro.org,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, Joey Gouly, maz@kernel.org,
	oliver.upton@linux.dev


> > > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>  
> > Hi Sascha,
> > 
> > One thing I notice in here is the use of unsigned long vs u64 is a
> > bit
> > inconsistent.  When it's a register or something we just read from a
> > register
> > I'd always use u64.  
> 
> Yeah, I'd like to do the same. The issue is that the for_each_set_bit()
> loop construct only works with unsigned long, and not u64. I'll rework
> the code to use u64 wherever possible.

Whilst is a bit silly with a single u64, there is bitmap_from_arr64()
The compiler should be able to see enough to flatten that to an assignment
so it's pretty cheap and ends up documenting why types are different.


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 33/36] KVM: arm64: gic-v5: Probe for GICv5 device
  2025-12-19 15:52 ` [PATCH v2 33/36] KVM: arm64: gic-v5: Probe for GICv5 device Sascha Bischoff
  2026-01-07 16:25   ` Jonathan Cameron
@ 2026-01-09 15:00   ` Joey Gouly
  1 sibling, 0 replies; 100+ messages in thread
From: Joey Gouly @ 2026-01-09 15:00 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,
	Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
	lpieralisi@kernel.org, Timothy Hayes

On Fri, Dec 19, 2025 at 03:52:47PM +0000, Sascha Bischoff wrote:
> The basic GICv5 PPI support is now complete. Allow probing for a
> native GICv5 rather than just the legacy support.
> 
> The implementation doesn't support protected VMs with GICv5 at this
> time. Therefore, if KVM has protected mode enabled the native GICv5
> init is skipped, but legacy VMs are allowed if the hardware supports
> it.
> 
> At this stage the GICv5 KVM implementation only supports PPIs, and
> doesn't interact with the host IRS at all. This means that there is no
> need to check how many concurrent VMs or vCPUs per VM are supported by
> the IRS - the PPI support only requires the CPUIF. The support is
> artificially limited to VGIC_V5_MAX_CPUS, i.e. 512, vCPUs per VM.
> 
> With this change it becomes possible to run basic GICv5-based VMs,
> provided that they only use PPIs.
> 
> Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> ---

Reviewed-by: Joey Gouly <joey.gouly@arm.com>


>  arch/arm64/kvm/vgic/vgic-v5.c | 39 +++++++++++++++++++++++++++--------
>  1 file changed, 30 insertions(+), 9 deletions(-)
> 
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index 97d67c1d16541..bf72982d6a2e8 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> @@ -12,22 +12,13 @@ static struct vgic_v5_ppi_caps *ppi_caps;
>  
>  /*
>   * Probe for a vGICv5 compatible interrupt controller, returning 0 on success.
> - * Currently only supports GICv3-based VMs on a GICv5 host, and hence only
> - * registers a VGIC_V3 device.
>   */
>  int vgic_v5_probe(const struct gic_kvm_info *info)
>  {
>  	u64 ich_vtr_el2;
>  	int ret;
>  
> -	if (!cpus_have_final_cap(ARM64_HAS_GICV5_LEGACY))
> -		return -ENODEV;
> -
>  	kvm_vgic_global_state.type = VGIC_V5;
> -	kvm_vgic_global_state.has_gcie_v3_compat = true;
> -
> -	/* We only support v3 compat mode - use vGICv3 limits */
> -	kvm_vgic_global_state.max_gic_vcpus = VGIC_V3_MAX_CPUS;
>  
>  	kvm_vgic_global_state.vcpu_base = 0;
>  	kvm_vgic_global_state.vctrl_base = NULL;
> @@ -35,6 +26,32 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
>  	kvm_vgic_global_state.has_gicv4 = false;
>  	kvm_vgic_global_state.has_gicv4_1 = false;
>  
> +	/*
> +	 * GICv5 is currently not supported in Protected mode. Skip the
> +	 * registration of GICv5 completely to make sure no guests can create a
> +	 * GICv5-based guest.
> +	 */
> +	if (is_protected_kvm_enabled()) {
> +		kvm_info("GICv5-based guests are not supported with pKVM\n");
> +		goto skip_v5;
> +	}
> +
> +	kvm_vgic_global_state.max_gic_vcpus = 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");
> +		goto skip_v5;
> +	}
> +
> +	kvm_info("GCIE system register CPU interface\n");
> +
> +skip_v5:
> +	/* If we don't support the GICv3 compat mode we're done. */
> +	if (!cpus_have_final_cap(ARM64_HAS_GICV5_LEGACY))
> +		return 0;
> +
> +	kvm_vgic_global_state.has_gcie_v3_compat = true;
>  	ich_vtr_el2 =  kvm_call_hyp_ret(__vgic_v3_get_gic_config);
>  	kvm_vgic_global_state.ich_vtr_el2 = (u32)ich_vtr_el2;
>  
> @@ -50,6 +67,10 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
>  		return 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);
> +
>  	static_branch_enable(&kvm_vgic_global_state.gicv3_cpuif);
>  	kvm_info("GCIE legacy system register CPU interface\n");
>  
> -- 
> 2.34.1


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 26/36] KVM: arm64: gic-v5: Bump arch timer for GICv5
  2026-01-07 16:08   ` Jonathan Cameron
@ 2026-01-09 16:56     ` Sascha Bischoff
  0 siblings, 0 replies; 100+ messages in thread
From: Sascha Bischoff @ 2026-01-09 16:56 UTC (permalink / raw)
  To: jonathan.cameron@huawei.com
  Cc: yuzenghui@huawei.com, lpieralisi@kernel.org, Timothy Hayes,
	Suzuki Poulose, nd, peter.maydell@linaro.org,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, Joey Gouly, maz@kernel.org,
	oliver.upton@linux.dev

On Wed, 2026-01-07 at 16:08 +0000, Jonathan Cameron wrote:
> On Fri, 19 Dec 2025 15:52:45 +0000
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> 
> > Now that GICv5 has arrived, the arch timer requires some TLC to
> > address some of the key differences introduced with GICv5.
> > 
> > For PPIs on GICv5, the set_pending_state and queue_irq_unlock
> > irq_ops
> > are used as AP lists are not required at all for GICv5. The arch
> > timer
> > also introduces an irq_op - get_input_level. Extend the
> > arch-timer-provided irq_ops to include the two PPI ops for vgic_v5
> > guests.
> > 
> > When possible, DVI (Direct Virtual Interrupt) is set for PPIs when
> > using a vgic_v5, which directly inject the pending state in to the
> 
> into ?
> 
> > guest. This means that the host never sees the interrupt for the
> > guest
> > for these interrupts. This has two impacts.
> > 
> > * First of all, the kvm_cpu_has_pending_timer check is updated to
> >   explicitly check if the timers are expected to fire.
> > 
> > * Secondly, for mapped timers (which use DVI) they must be masked
> > on
> >   the host prior to entering a GICv5 guest, and unmasked on the
> > return
> >   path. This is handled in set_timer_irq_phys_masked.
> > 
> > The final, but rather important, change is that the architected
> > PPIs
> > for the timers are made mandatory for a GICv5 guest. Attempts to
> > set
> > them to anything else are actively rejected. Once a vgic_v5 is
> > initialised, the arch timer PPIs are also explicitly reinitialised
> > to
> > ensure the correct GICv5-compatible PPIs are used - this also adds
> > in
> > the GICv5 PPI type to the intid.
> > 
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> Various comments inline. 
> 
> J
> > ---
> >  arch/arm64/kvm/arch_timer.c     | 110 ++++++++++++++++++++++++++--
> > ----
> >  arch/arm64/kvm/vgic/vgic-init.c |   9 +++
> >  arch/arm64/kvm/vgic/vgic-v5.c   |   8 +--
> >  include/kvm/arm_arch_timer.h    |   7 +-
> >  include/kvm/arm_vgic.h          |   4 ++
> >  5 files changed, 115 insertions(+), 23 deletions(-)
> > 
> > diff --git a/arch/arm64/kvm/arch_timer.c
> > b/arch/arm64/kvm/arch_timer.c
> > index 6f033f6644219..78d66a67b34ac 100644
> > --- a/arch/arm64/kvm/arch_timer.c
> > +++ b/arch/arm64/kvm/arch_timer.c
> 
> 
> >  void kvm_timer_sync_nested(struct kvm_vcpu *vcpu)
> > @@ -1034,12 +1079,15 @@ void kvm_timer_vcpu_reset(struct kvm_vcpu
> > *vcpu)
> >  	if (timer->enabled) {
> >  		for (int i = 0; i < nr_timers(vcpu); i++)
> >  			kvm_timer_update_irq(vcpu, false,
> > -					     vcpu_get_timer(vcpu,
> > i));
> > +					vcpu_get_timer(vcpu, i));
> 
> Unrelated change, and a bad one at that!
> 
> 
> >  
> >  		if (irqchip_in_kernel(vcpu->kvm)) {
> > -			kvm_vgic_reset_mapped_irq(vcpu,
> > timer_irq(map.direct_vtimer));
> > +			kvm_vgic_reset_mapped_irq(
> > +				vcpu,
> > timer_irq(map.direct_vtimer));
> 
> Also unrelated and not a good change.
> 
> >  			if (map.direct_ptimer)
> > -				kvm_vgic_reset_mapped_irq(vcpu,
> > timer_irq(map.direct_ptimer));
> > +				kvm_vgic_reset_mapped_irq(
> > +					vcpu,
> > +					timer_irq(map.direct_ptime
> > r));
> 
> Leave all these alone.

Yeah, have done. Bad revert of a change here on my part.

> 
> >  		}
> >  	}
> >  
> > @@ -1092,10 +1140,19 @@ void kvm_timer_vcpu_init(struct kvm_vcpu
> > *vcpu)
> >  		      HRTIMER_MODE_ABS_HARD);
> >  }
> >  
> > +/*
> > + * This is always called during kvm_arch_init_vm, but will also be
> > + * called from kvm_vgic_create if we have a vGICv5.
> > + */
> >  void kvm_timer_init_vm(struct kvm *kvm)
> >  {
> > +	/*
> > +	 * Set up the default PPIs - note that we adjust them
> > based on
> > +	 * the model of the GIC as GICv5 uses a different way to
> > +	 * describing interrupts.
> > +	 */
> >  	for (int i = 0; i < NR_KVM_TIMERS; i++)
> > -		kvm->arch.timer_data.ppi[i] = default_ppi[i];
> > +		kvm->arch.timer_data.ppi[i] = get_vgic_ppi(kvm,
> > default_ppi[i]);
> >  }
> >  
> >  void kvm_timer_cpu_up(void)
> > @@ -1347,6 +1404,7 @@ static int kvm_irq_init(struct
> > arch_timer_kvm_info *info)
> >  		}
> >  
> >  		arch_timer_irq_ops.flags |= VGIC_IRQ_SW_RESAMPLE;
> > +		arch_timer_irq_ops_vgic_v5.flags |=
> > VGIC_IRQ_SW_RESAMPLE;
> >  		WARN_ON(irq_domain_push_irq(domain,
> > host_vtimer_irq,
> >  					    (void
> > *)TIMER_VTIMER));
> >  	}
> > @@ -1497,10 +1555,13 @@ static bool timer_irqs_are_valid(struct
> > kvm_vcpu *vcpu)
> >  			break;
> >  
> >  		/*
> > -		 * We know by construction that we only have PPIs,
> > so
> > -		 * all values are less than 32.
> > +		 * We know by construction that we only have PPIs,
> > so all values
> > +		 * are less than 32 for non-GICv5 vgics. On GICv5,
> > they are
> 
> VGICs maybe?  It's not consistent in existing comments in this file
> though.
> 
> > +		 * architecturally defined to be under 32 too.
> > However, we mask
> > +		 * off most of the bits as we might be presented
> > with a GICv5
> > +		 * style PPI where the type is encoded in the top-
> > bits.
> >  		 */
> > -		ppis |= BIT(irq);
> > +		ppis |= BIT(irq & 0x1f);
> >  	}
> >  
> >  	valid = hweight32(ppis) == nr_timers(vcpu);
> > @@ -1538,7 +1599,9 @@ int kvm_timer_enable(struct kvm_vcpu *vcpu)
> >  {
> >  	struct arch_timer_cpu *timer = vcpu_timer(vcpu);
> >  	struct timer_map map;
> > +	struct irq_ops *ops;
> >  	int ret;
> > +	int irq;
> Might as well put irq on same line as ret
> 

Actually, this should really be a u32! However, I've dropped the local
var as you suggest below.

> >  
> >  	if (timer->enabled)
> >  		return 0;
> > @@ -1556,20 +1619,22 @@ int kvm_timer_enable(struct kvm_vcpu *vcpu)
> >  		return -EINVAL;
> >  	}
> >  
> > +	ops = vgic_is_v5(vcpu->kvm) ? &arch_timer_irq_ops_vgic_v5
> > :
> > +				      &arch_timer_irq_ops;
> > +
> >  	get_timer_map(vcpu, &map);
> >  
> > -	ret = kvm_vgic_map_phys_irq(vcpu,
> > -				    map.direct_vtimer-
> > >host_timer_irq,
> > -				    timer_irq(map.direct_vtimer),
> > -				    &arch_timer_irq_ops);
> > +	irq = timer_irq(map.direct_vtimer);
> > +	ret = kvm_vgic_map_phys_irq(vcpu, map.direct_vtimer-
> > >host_timer_irq,
> > +				    irq, ops);
> 
> As irq is only used with this value in here, I'd avoid having the
> local variable
> that changes meaning.

Agreed.

> 
> 	ret = kvm_vgic_map_phys_irq(vcpu, map.direct_vtimer-
> >host_timer_irq,
> 				    timer_irq(map.direct_vtimer),
> ops);
> >  	if (ret)
> >  		return ret;
> >  
> >  	if (map.direct_ptimer) {
> > +		irq = timer_irq(map.direct_ptimer);
> >  		ret = kvm_vgic_map_phys_irq(vcpu,
> >  					    map.direct_ptimer-
> > >host_timer_irq,
> > -					   
> > timer_irq(map.direct_ptimer),
> > -					    &arch_timer_irq_ops);
> > +					    irq, ops);
> As above
> 					   
> timer_irq(map.direct_ptimer), ops);
> 
> Doesn't make it much harder to read and avoids the local variable
> being needed.
> >  	}
> >  
> >  	if (ret)
> > @@ -1627,6 +1692,15 @@ int kvm_arm_timer_set_attr(struct kvm_vcpu
> > *vcpu, struct kvm_device_attr *attr)
> >  		goto out;
> >  	}
> >  
> > +	/*
> > +	 * The PPIs for the Arch Timers arch architecturally
> > defined for
> > +	 * GICv5. Reject anything that changes them from the
> > specified value.
> > +	 */
> > +	if (vgic_is_v5(vcpu->kvm) && vcpu->kvm-
> > >arch.timer_data.ppi[idx] != irq) {
> > +		ret = -EINVAL;
> > +		goto out;
> 
> Whilst you are here, maybe throw some guard() magic dust at this and
> do a direct return?
> Or leave it for someone else who has more spare time ;)

I might as well as it makes the code cleaner.

> 
> > +	}
> > +
> >  	/*
> >  	 * We cannot validate the IRQ unicity before we run, so
> > take it at
> >  	 * face value. The verdict will be given on first vcpu
> > run, for each
> 
> > diff --git a/include/kvm/arm_arch_timer.h
> > b/include/kvm/arm_arch_timer.h
> > index 7310841f45121..6cb9c20f9db65 100644
> > --- a/include/kvm/arm_arch_timer.h
> > +++ b/include/kvm/arm_arch_timer.h
> 
> >  
> >  struct arch_timer_context {
> > @@ -130,6 +132,9 @@ void kvm_timer_init_vhe(void);
> >  #define
> > timer_vm_data(ctx)		(&(timer_context_to_vcpu(ctx)->kvm->arch.timer_data))
> >  #define timer_irq(ctx)			(timer_vm_data(ctx)-
> > >ppi[arch_timer_ctx_index(ctx)])
> >  
> > +#define get_vgic_ppi(k, i) (((k)->arch.vgic.vgic_model !=
> > KVM_DEV_TYPE_ARM_VGIC_V5) ? \
> > +				(i) : ((i) |
> > FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI)))
> 
> Similar to earlier comment I'd use FIELD_PREP() for i as well but not
> that important
> I'm just lazy about remembering where the numbers go.

Done, thanks.
Sascha

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 02/36] KVM: arm64: gic-v3: Switch vGIC-v3 to use generated ICH_VMCR_EL2
  2026-01-07 10:55     ` Sascha Bischoff
@ 2026-01-09 16:57       ` Sascha Bischoff
  0 siblings, 0 replies; 100+ messages in thread
From: Sascha Bischoff @ 2026-01-09 16:57 UTC (permalink / raw)
  To: jonathan.cameron@huawei.com
  Cc: yuzenghui@huawei.com, lpieralisi@kernel.org, Timothy Hayes,
	Suzuki Poulose, nd, peter.maydell@linaro.org,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, Joey Gouly, maz@kernel.org,
	oliver.upton@linux.dev

On Wed, 2026-01-07 at 10:55 +0000, Sascha Bischoff wrote:
> On Tue, 2026-01-06 at 18:00 +0000, Jonathan Cameron wrote:
> > On Fri, 19 Dec 2025 15:52:36 +0000
> > Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> > 
> > > From: Sascha Bischoff <Sascha.Bischoff@arm.com>
> > > 
> > > The VGIC-v3 code relied on hand-written definitions for the
> > > ICH_VMCR_EL2 register. This register, and the associated fields,
> > > is
> > > now generated as part of the sysreg framework. Move to using the
> > > generated definitions instead of the hand-written ones.
> > > 
> > > There are no functional changes as part of this change.
> > > 
> > > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > Hi Sascha
> > 
> > Happy new year.  There is a bit in here that isn't obviously going
> > to result in no functional change. I'm too lazy to chase where the
> > value
> > goes to check it it's a real bug or not.
> > 
> > Otherwise this is inconsistent on whether the _MASK or define
> > without
> > it from the sysreg generated header is used in FIELD_GET() /
> > FIELD_PREP()
> > 
> > I'd always use the _MASK version.
> 
> Hi Jonathan,
> 
> I've updated the code to use the _MASK version.
> 
Hi Jonathan,

I've actually had a change of heart (sorry!). I think it is clearer to
use the _MASK version when explicitly using the value as a mask, but to
drop that for the FIELD_x() ops as in that case we're naming a field.

I've gone through and made the usage of those consistent.

Sascha

> 

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 22/36] KVM: arm64: gic-v5: Trap and mask guest PPI register accesses
  2026-01-07 15:17   ` Jonathan Cameron
@ 2026-01-09 16:59     ` Sascha Bischoff
  0 siblings, 0 replies; 100+ messages in thread
From: Sascha Bischoff @ 2026-01-09 16:59 UTC (permalink / raw)
  To: jonathan.cameron@huawei.com
  Cc: yuzenghui@huawei.com, lpieralisi@kernel.org, Timothy Hayes,
	Suzuki Poulose, nd, peter.maydell@linaro.org,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, Joey Gouly, maz@kernel.org,
	oliver.upton@linux.dev

On Wed, 2026-01-07 at 15:17 +0000, Jonathan Cameron wrote:
> On Fri, 19 Dec 2025 15:52:43 +0000
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> 
> > A guest should not be able to detect if a PPI that is not exposed
> > to
> > the guest is implemented or not. If the writes to the PPI registers
> > are not masked, it becomes possible for the guest to detect the
> > presence of all implemented PPIs on the host.
> > 
> > Guest writes to the following registers are masked:
> > 
> > ICC_CACTIVERx_EL1
> > ICC_SACTIVERx_EL1
> > ICC_CPENDRx_EL1
> > ICC_SPENDRx_EL1
> > ICC_ENABLERx_EL1
> > ICC_PRIORITYRx_EL1
> > 
> > When a guest writes these registers, the write is masked with the
> > set
> > of PPIs actually exposed to the guest, and the state is written
> > back
> > to KVM's shadow state..
> 
> One . seems enough.
> 
> > 
> > Reads for the above registers are not masked. When the guest is
> > running and reads from the above registers, it is presented with
> > what
> > KVM provides in the ICH_PPI_x_EL2 registers, which is the masked
> > version of what the guest last wrote.
> > 
> > The ICC_PPI_HMRx_EL1 register is used to determine which PPIs use
> > Level-sensitive semantics, and which use Edge. For a GICv5 guest,
> > the
> > correct view of the virtual PPIs must be provided to the guest, and
> > hence this must also be trapped, but only for reads. The content of
> > the HMRs is calculated and masked when finalising the PPI state for
> > the guest.
> > 
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> A few bits inline but nothing significant so I'll assume you tidy
> those up
> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>

I've left this tag off for now as this code has gone a bit of a rework
(mostly reducing the scope) and I don't think it really applies
anymore.

Instead of trapping many of the writes to the PPI registers, the set
has been reduced to just the ICH_PPI_ENABLERx_EL1. By trapping and
masking writes to the PPI enable, we stop the guest from ever enabling
any interrupt that is not exposed to it, and are able to forgo trapping
the rest of the writes.

Thanks,
Sascha

> 
> > ---
> >  arch/arm64/kvm/config.c   |  22 ++++++-
> >  arch/arm64/kvm/sys_regs.c | 133
> > ++++++++++++++++++++++++++++++++++++++
> >  2 files changed, 153 insertions(+), 2 deletions(-)
> > 
> > diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
> > index eb0c6f4d95b6d..f81bfdadd12fb 100644
> > --- a/arch/arm64/kvm/config.c
> > +++ b/arch/arm64/kvm/config.c
> > @@ -1586,8 +1586,26 @@ static void __compute_ich_hfgrtr(struct
> > kvm_vcpu *vcpu)
> >  {
> >  	__compute_fgt(vcpu, ICH_HFGRTR_EL2);
> >  
> > -	/* ICC_IAFFIDR_EL1 *always* needs to be trapped when
> > running a guest */
> > +	/*
> > +	 * ICC_IAFFIDR_EL1 and ICH_PPI_HMRx_EL1 *always* needs to
> > be
> 
> need to be
> 
> > +	 * trapped when running a guest.
> > +	 **/
> 
> */
> 
> >  	*vcpu_fgt(vcpu, ICH_HFGRTR_EL2) &=
> > ~ICH_HFGRTR_EL2_ICC_IAFFIDR_EL1;
> > +	*vcpu_fgt(vcpu, ICH_HFGRTR_EL2) &=
> > ~ICH_HFGRTR_EL2_ICC_PPI_HMRn_EL1;
> > +}
> 
> > diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
> > index 383ada0d75922..cef13bf6bb3a1 100644
> > --- a/arch/arm64/kvm/sys_regs.c
> > +++ b/arch/arm64/kvm/sys_regs.c
> > @@ -696,6 +696,111 @@ static bool access_gicv5_iaffid(struct
> > kvm_vcpu *vcpu, struct sys_reg_params *p,
> >  	return true;
> >  }
> >  
> > +static bool access_gicv5_ppi_hmr(struct kvm_vcpu *vcpu, struct
> > sys_reg_params *p,
> > +				 const struct sys_reg_desc *r)
> > +{
> > +	if (p->is_write)
> > +		return ignore_write(vcpu, p);
> > +
> > +	if (p->Op2 == 0) {	/* ICC_PPI_HMR0_EL1 */
> > +		p->regval = vcpu-
> > >arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[0];
> > +	} else {		/* ICC_PPI_HMR1_EL1 */
> > +		p->regval = vcpu-
> > >arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[1];
> > +	}
> 
> No {} as single line statements in all legs.
> 
> However, I'd be tempted to use a local variable for the index like
> you've
> done in many other cases
> 	
> 	unsigned int index;
> 
> ...
> 
> 	index = p->Op2 == 0 ? 0 : 1;
> 	p->regval = vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hrm[index];
> 
> Or use the p->Op2 % 2 as you do in ppi_enabler.
> 
> 
> > +
> > +	return true;
> > +}
> > +
> > +static bool access_gicv5_ppi_enabler(struct kvm_vcpu *vcpu,
> > +				     struct sys_reg_params *p,
> > +				     const struct sys_reg_desc *r)
> > +{
> > +	struct vgic_v5_cpu_if *cpu_if = &vcpu-
> > >arch.vgic_cpu.vgic_v5;
> > +	u64 masked_write;
> > +
> > +	/* We never expect to get here with a read! */
> > +	if (WARN_ON_ONCE(!p->is_write))
> > +		return undef_access(vcpu, p, r);
> > +
> > +	masked_write = p->regval & cpu_if->vgic_ppi_mask[p->Op2 %
> > 2];
> > +	cpu_if->vgic_ich_ppi_enabler_entry[p->Op2 % 2] =
> > masked_write;
> > +
> > +	return true;
> > +}
> > +
> > +static bool access_gicv5_ppi_pendr(struct kvm_vcpu *vcpu,
> > +				   struct sys_reg_params *p,
> > +				   const struct sys_reg_desc *r)
> > +{
> > +	struct vgic_v5_cpu_if *cpu_if = &vcpu-
> > >arch.vgic_cpu.vgic_v5;
> > +	u64 masked_write;
> > +
> > +	/* We never expect to get here with a read! */
> > +	if (WARN_ON_ONCE(!p->is_write))
> > +		return undef_access(vcpu, p, r);
> > +
> > +	masked_write = p->regval & cpu_if->vgic_ppi_mask[p->Op2 %
> > 2];
> > +
> > +	if (p->Op2 & 0x2) {	/* SPENDRx */
> > +		cpu_if->vgic_ppi_pendr_entry[p->Op2 % 2] |=
> > masked_write;
> > +	} else {		/* CPENDRx */
> > +		cpu_if->vgic_ppi_pendr_entry[p->Op2 % 2] &=
> > ~masked_write;
> > +	}
> 
> No {} wanted in kernel style when all legs are single line
> statements.
> Same applies in a few other cases that follow.
> 
> > +
> > +	return true;
> > +}
> > +
> 


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 36/36] KVM: arm64: gic-v5: Communicate userspace-drivable PPIs via a UAPI
  2026-01-07 16:51   ` Jonathan Cameron
@ 2026-01-09 17:00     ` Sascha Bischoff
  0 siblings, 0 replies; 100+ messages in thread
From: Sascha Bischoff @ 2026-01-09 17:00 UTC (permalink / raw)
  To: jonathan.cameron@huawei.com
  Cc: yuzenghui@huawei.com, lpieralisi@kernel.org, Timothy Hayes,
	Suzuki Poulose, nd, peter.maydell@linaro.org,
	kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
	kvm@vger.kernel.org, Joey Gouly, maz@kernel.org,
	oliver.upton@linux.dev

On Wed, 2026-01-07 at 16:51 +0000, Jonathan Cameron wrote:
> On Fri, 19 Dec 2025 15:52:48 +0000
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> 
> > GICv5 systems will likely not support the full set of PPIs. The
> > presence of any virtual PPI is tied to the presence of the physical
> > PPI. Therefore, the available PPIs will be limited by the physical
> > host. Userspace cannot drive any PPIs that are not implemented.
> > 
> > Moreover, it is not desirable to expose all PPIs to the guest in
> > the
> > first place, even if they are supported in hardware. Some devices,
> > such as the arch timer, are implemented in KVM, and hence those
> > PPIs
> > shouldn't be driven by userspace, either.
> > 
> > Provided a new UAPI:
> >   KVM_DEV_ARM_VGIC_GRP_CTRL => KVM_DEV_ARM_VGIC_USERPSPACE_PPIs
> > 
> > This allows userspace to query which PPIs it is able to drive via
> > KVM_IRQ_LINE.
> > 
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> 
> A couple of trivial comments on this patch.
> 
> Overall, to me as a definite non expert in kvm GIC emulation this
> series looks to be in a pretty good state.
> 
> Thanks,
> 
> Jonathan

This seems like as good a place as any to thank you for all of the
reviews. They have been really helpful in getting is series to a better
state. Thanks a lot for taking the time!

(+ some responses inline)

Sascha

> > ---
> >  .../virt/kvm/devices/arm-vgic-v5.rst          | 13 ++++++++++
> >  arch/arm64/include/uapi/asm/kvm.h             |  1 +
> >  arch/arm64/kvm/vgic/vgic-kvm-device.c         | 25
> > +++++++++++++++++++
> >  arch/arm64/kvm/vgic/vgic-v5.c                 |  8 ++++++
> >  include/kvm/arm_vgic.h                        |  5 ++++
> >  include/linux/irqchip/arm-gic-v5.h            |  4 +++
> >  tools/arch/arm64/include/uapi/asm/kvm.h       |  1 +
> >  7 files changed, 57 insertions(+)
> > 
> > diff --git a/Documentation/virt/kvm/devices/arm-vgic-v5.rst
> > b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
> > index 9904cb888277d..d9f2917b609c5 100644
> > --- a/Documentation/virt/kvm/devices/arm-vgic-v5.rst
> > +++ b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
> > @@ -25,6 +25,19 @@ Groups:
> >        request the initialization of the VGIC, no additional
> > parameter in
> >        kvm_device_attr.addr. Must be called after all VCPUs have
> > been created.
> >  
> > +   KVM_DEV_ARM_VGIC_USERPSPACE_PPIs
> > +      request the mask of userspace-drivable PPIs. Only a subset
> > of the PPIs can
> > +      be directly driven from userspace with GICv5, and the
> > returned mask
> > +      informs userspace of which it is allowed to drive via
> > KVM_IRQ_LINE.
> > +
> > +      Userspace must allocate and point to __u64[2] with of data
> > in
> 
> with of?

Fixed ("with" was meant to be "worth", but I just dropped that and went
with "of").

> 
> > +      kvm_device_attr.addr. When this call returns, the provided
> > memory will be
> > +      populated with the userspace PPI mask. The lower __u64
> > contains the mask
> > +      for the lower 64 PPIS, with the remaining 64 being in the
> > second __u64.
> > +
> > +      This is a read-only attribute, and cannot be set. Attempts
> > to set it are
> > +      rejected.
> > +
> >    Errors:
> >  
> >      ======= 
> > ========================================================
> 
> > diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c
> > b/arch/arm64/kvm/vgic/vgic-kvm-device.c
> > index 78903182bba08..360c78ed4f104 100644
> > --- a/arch/arm64/kvm/vgic/vgic-kvm-device.c
> > +++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c
> > @@ -719,6 +719,25 @@ struct kvm_device_ops kvm_arm_vgic_v3_ops = {
> >  	.has_attr = vgic_v3_has_attr,
> >  };
> >  
> > +static int vgic_v5_get_userspace_ppis(struct kvm_device *dev,
> > +				      struct kvm_device_attr
> > *attr)
> > +{
> > +	u64 __user *uaddr = (u64 __user *)(long)attr->addr;
> > +	struct gicv5_vm *gicv5_vm = &dev->kvm->arch.vgic.gicv5_vm;
> > +	int i, ret;
> > +
> > +	guard(mutex)(&dev->kvm->arch.config_lock);
> > +
> > +	for (i = 0; i < 2; ++i) {
> 
> Can drag declaration of i into loop init.

Done.

> Also I just noticed the series is rather random wrt to pre or post
> increment
> in cases where it doesn't matter. I'd go with post increment for for
> loops.
> I took a quick look at a random file in this directory and that's
> what is used there.

I've gone through and made them consistent.

> 
> 
> > +		ret = put_user(gicv5_vm->userspace_ppis[i],
> > uaddr);
> > +		if (ret)
> > +			return ret;
> > +		uaddr++;
> > +	}
> > +
> > +	return 0;
> > +}
> 
> > diff --git a/arch/arm64/kvm/vgic/vgic-v5.c
> > b/arch/arm64/kvm/vgic/vgic-v5.c
> > index bf72982d6a2e8..04300926683b6 100644
> > --- a/arch/arm64/kvm/vgic/vgic-v5.c
> > +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> > @@ -122,6 +122,14 @@ int vgic_v5_init(struct kvm *kvm)
> >  		}
> >  	}
> >  
> > +	/*
> > +	 * We only allow userspace to drive the SW_PPI, if it is
> > +	 * implemented.
> > +	 */
> 
> 	/* We only allow userspace to drive the SW_PPI, if it is
> implemented. */
> 
> Is under 80 chars (just) so go with that.

Done.

> 
> 
> > +	kvm->arch.vgic.gicv5_vm.userspace_ppis[0] = GICV5_SW_PPI &
> > GICV5_HWIRQ_ID;
> > +	kvm->arch.vgic.gicv5_vm.userspace_ppis[0] &= ppi_caps-
> > >impl_ppi_mask[0];
> > +	kvm->arch.vgic.gicv5_vm.userspace_ppis[1] = 0;
> > +
> >  	return 0;
> >  }
> 
> 


^ permalink raw reply	[flat|nested] 100+ messages in thread

end of thread, other threads:[~2026-01-09 17:02 UTC | newest]

Thread overview: 100+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-12-19 15:52 [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
2025-12-19 15:52 ` [PATCH v2 03/36] arm64/sysreg: Drop ICH_HFGRTR_EL2.ICC_HAPR_EL1 and make RES1 Sascha Bischoff
2025-12-19 15:52 ` [PATCH v2 01/36] KVM: arm64: Account for RES1 bits in DECLARE_FEAT_MAP() and co Sascha Bischoff
2026-01-06 17:23   ` Jonathan Cameron
2026-01-08 16:52     ` Sascha Bischoff
2025-12-19 15:52 ` [PATCH v2 02/36] KVM: arm64: gic-v3: Switch vGIC-v3 to use generated ICH_VMCR_EL2 Sascha Bischoff
2026-01-06 18:00   ` Jonathan Cameron
2026-01-07 10:55     ` Sascha Bischoff
2026-01-09 16:57       ` Sascha Bischoff
2025-12-19 15:52 ` [PATCH v2 04/36] arm64/sysreg: Add remaining GICv5 ICC_ & ICH_ sysregs for KVM support Sascha Bischoff
2026-01-06 18:28   ` Jonathan Cameron
2025-12-19 15:52 ` [PATCH v2 06/36] KVM: arm64: gic-v5: Add ARM_VGIC_V5 device to KVM headers Sascha Bischoff
2025-12-19 15:52 ` [PATCH v2 05/36] arm64/sysreg: Add GICR CDNMIA encoding Sascha Bischoff
2026-01-06 18:08   ` Jonathan Cameron
2026-01-07  8:39     ` Sascha Bischoff
2025-12-19 15:52 ` [PATCH v2 09/36] KVM: arm64: gic-v5: Detect implemented PPIs on boot Sascha Bischoff
2026-01-06 18:34   ` Jonathan Cameron
2025-12-19 15:52 ` [PATCH v2 08/36] KVM: arm64: Introduce kvm_call_hyp_nvhe_res() Sascha Bischoff
2026-01-07 10:30   ` Jonathan Cameron
2026-01-08  9:48     ` Sascha Bischoff
2026-01-08 10:26       ` Jonathan Cameron
2025-12-19 15:52 ` [PATCH v2 07/36] KVM: arm64: gic: Introduce interrupt type helpers Sascha Bischoff
2026-01-06 14:51   ` Joey Gouly
2026-01-06 18:43   ` Jonathan Cameron
2026-01-08  9:33     ` Sascha Bischoff
2026-01-08 10:25       ` Jonathan Cameron
2025-12-19 15:52 ` [PATCH v2 12/36] KVM: arm64: gic-v5: Add emulation for ICC_IAFFIDR_EL1 accesses Sascha Bischoff
2026-01-07 11:10   ` Jonathan Cameron
2025-12-19 15:52 ` [PATCH v2 11/36] KVM: arm64: gic-v5: Support GICv5 FGTs & FGUs Sascha Bischoff
2026-01-07 11:19   ` Jonathan Cameron
2026-01-08 10:36     ` Sascha Bischoff
2025-12-19 15:52 ` [PATCH v2 10/36] KVM: arm64: gic-v5: Sanitize ID_AA64PFR2_EL1.GCIE Sascha Bischoff
2026-01-07 10:58   ` Jonathan Cameron
2026-01-08  9:54     ` Sascha Bischoff
2025-12-19 15:52 ` [PATCH v2 13/36] KVM: arm64: gic: Set vgic_model before initing private IRQs Sascha Bischoff
2026-01-07 11:24   ` Jonathan Cameron
2026-01-08 13:39     ` Sascha Bischoff
2025-12-19 15:52 ` [PATCH v2 14/36] KVM: arm64: gic-v5: Add vgic-v5 save/restore hyp interface Sascha Bischoff
2025-12-19 15:52 ` [PATCH v2 16/36] KVM: arm64: gic-v5: Implement direct injection of PPIs Sascha Bischoff
2026-01-07 12:16   ` Jonathan Cameron
2025-12-19 15:52 ` [PATCH v2 17/36] KVM: arm64: gic: Introduce irq_queue and set_pending_state to irq_ops Sascha Bischoff
2026-01-07 12:22   ` Jonathan Cameron
2025-12-19 15:52 ` [PATCH v2 15/36] KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore Sascha Bischoff
2026-01-07 12:28   ` Jonathan Cameron
2026-01-08 13:40     ` Sascha Bischoff
2026-01-08 16:52       ` Jonathan Cameron
2025-12-19 15:52 ` [PATCH v2 19/36] KVM: arm64: gic-v5: Check for pending PPIs Sascha Bischoff
2026-01-07 15:00   ` Jonathan Cameron
2026-01-08 16:23     ` Sascha Bischoff
2026-01-08 16:57       ` Jonathan Cameron
2026-01-08 16:10   ` Joey Gouly
2026-01-08 16:21     ` Sascha Bischoff
2025-12-19 15:52 ` [PATCH v2 20/36] KVM: arm64: gic-v5: Init Private IRQs (PPIs) for GICv5 Sascha Bischoff
2026-01-07 15:04   ` Jonathan Cameron
2025-12-19 15:52 ` [PATCH v2 18/36] KVM: arm64: gic-v5: Implement PPI interrupt injection Sascha Bischoff
2026-01-06 16:06   ` Joey Gouly
2026-01-06 18:04     ` Sascha Bischoff
2026-01-07 12:50   ` Jonathan Cameron
2026-01-08 14:43     ` Sascha Bischoff
2025-12-19 15:52 ` [PATCH v2 21/36] KVM: arm64: gic-v5: Finalize GICv5 PPIs and generate mask Sascha Bischoff
2026-01-07 15:08   ` Jonathan Cameron
2026-01-08 16:51     ` Sascha Bischoff
2025-12-19 15:52 ` [PATCH v2 23/36] KVM: arm64: gic-v5: Support GICv5 interrupts with KVM_IRQ_LINE Sascha Bischoff
2026-01-07 15:29   ` Jonathan Cameron
2026-01-08 16:53     ` Sascha Bischoff
2025-12-19 15:52 ` [PATCH v2 22/36] KVM: arm64: gic-v5: Trap and mask guest PPI register accesses Sascha Bischoff
2026-01-07 15:17   ` Jonathan Cameron
2026-01-09 16:59     ` Sascha Bischoff
2025-12-19 15:52 ` [PATCH v2 24/36] KVM: arm64: gic-v5: Create, init vgic_v5 Sascha Bischoff
2026-01-07 15:49   ` Jonathan Cameron
2026-01-08 16:55     ` Sascha Bischoff
2025-12-19 15:52 ` [PATCH v2 25/36] KVM: arm64: gic-v5: Reset vcpu state Sascha Bischoff
2026-01-07 15:51   ` Jonathan Cameron
2025-12-19 15:52 ` [PATCH v2 27/36] KVM: arm64: gic-v5: Mandate architected PPI for PMU emulation on GICv5 Sascha Bischoff
2026-01-06 15:06   ` Joey Gouly
2026-01-07  9:48     ` Sascha Bischoff
2026-01-07 16:11   ` Jonathan Cameron
2025-12-19 15:52 ` [PATCH v2 28/36] KVM: arm64: gic: Hide GICv5 for protected guests Sascha Bischoff
2026-01-07 16:12   ` Jonathan Cameron
2025-12-19 15:52 ` [PATCH v2 26/36] KVM: arm64: gic-v5: Bump arch timer for GICv5 Sascha Bischoff
2026-01-07 16:08   ` Jonathan Cameron
2026-01-09 16:56     ` Sascha Bischoff
2025-12-19 15:52 ` [PATCH v2 31/36] KVM: arm64: gic-v5: Set ICH_VCTLR_EL2.En on boot Sascha Bischoff
2025-12-19 15:52 ` [PATCH v2 29/36] KVM: arm64: gic-v5: Hide FEAT_GCIE from NV GICv5 guests Sascha Bischoff
2026-01-07 16:13   ` Jonathan Cameron
2025-12-19 15:52 ` [PATCH v2 30/36] KVM: arm64: gic-v5: Introduce kvm_arm_vgic_v5_ops and register them Sascha Bischoff
2026-01-07 16:19   ` Jonathan Cameron
2025-12-19 15:52 ` [PATCH v2 33/36] KVM: arm64: gic-v5: Probe for GICv5 device Sascha Bischoff
2026-01-07 16:25   ` Jonathan Cameron
2026-01-09 15:00   ` Joey Gouly
2025-12-19 15:52 ` [PATCH v2 34/36] Documentation: KVM: Introduce documentation for VGICv5 Sascha Bischoff
2026-01-07 16:27   ` Jonathan Cameron
2025-12-19 15:52 ` [PATCH v2 32/36] irqchip/gic-v5: Check if impl is virt capable Sascha Bischoff
2026-01-07 16:21   ` Jonathan Cameron
2025-12-19 15:52 ` [PATCH v2 36/36] KVM: arm64: gic-v5: Communicate userspace-drivable PPIs via a UAPI Sascha Bischoff
2026-01-07 16:51   ` Jonathan Cameron
2026-01-09 17:00     ` Sascha Bischoff
2025-12-19 15:52 ` [PATCH v2 35/36] KVM: arm64: selftests: Introduce a minimal GICv5 PPI selftest Sascha Bischoff
2026-01-07 16:38   ` Jonathan Cameron
2025-12-19 16:17 ` [PATCH v2 00/36] KVM: arm64: Introduce vGIC-v5 with PPI support 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).