* [PATCH v3 0/4] KVM: arm64: vgic: Fix IGROUPR writability and IIDR revision control
@ 2026-05-11 11:30 David Woodhouse
2026-05-11 11:30 ` [PATCH v3 1/4] KVM: arm64: vgic: Allow userspace to set IIDR revision 1 David Woodhouse
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: David Woodhouse @ 2026-05-11 11:30 UTC (permalink / raw)
To: Paolo Bonzini
Cc: Jonathan Corbet, Shuah Khan, Marc Zyngier, Oliver Upton,
Joey Gouly, Suzuki K Poulose, Zenghui Yu, Catalin Marinas,
Will Deacon, Jonathan Cameron, Sascha Bischoff, Eric Auger,
Raghavendra Rao Ananta, Maxim Levitsky, David Woodhouse,
Kees Cook, Timothy Hayes, Arnd Bergmann, kvm, linux-doc,
linux-kernel, linux-arm-kernel, kvmarm, linux-kselftest,
Peter Maydell, qemu-arm, qemu-devel
Maintaining precise guest compatibility across host kernel upgrades —
and even downgrades, since rollback is sometimes necessary — is not
optional. That *shouldn't* need saying, but maybe it does:
https://lore.kernel.org/all/6856b269d2af706eae397e0cf9c1231f89d9a932.camel@infradead.org/
This series fixes the GICv2/v3 IGROUPR writability model to be
consistently controlled by the GICD_IIDR implementation revision,
replacing the ad-hoc v2_groups_user_writable flag.
Before commit d53c2c29ae0d ("KVM: arm/arm64: vgic: Allow configuration
of interrupt groups"), IGROUPR was read-only on both GICv2 and GICv3.
That commit made it unconditionally guest-writable, but provided no
mechanism for userspace to restore the previous behaviour for guests
that were launched on older kernels (which which might need to be
migrated back to older kernels).
The v2_groups_user_writable flag (added by 32f8777ed92d) attempted to
gate GICv2 userspace IGROUPR writes until userspace wrote the IIDR,
but the guest write path was never gated, creating an inconsistency
where the guest could modify groups that userspace couldn't save or
restore. QEMU never writes GICD_IIDR, so its GICv2 IGROUPR
save/restore (QEMU commit eb8b9530b0c) appears to be silently broken.
This series:
- Allows userspace to set IIDR revision 1, to restore the original
read-only IGROUPR behaviour for both GICv2 and GICv3 (patch 1)
- Removes v2_groups_user_writable and makes both guest and userspace
writability follow the IIDR revision directly (patch 3)
- Adds selftests covering IIDR revision semantics and a QEMU-style
save/restore scenario (patches 2, 4)
Tested on EC2 c7g.metal (GICv3 native) and under QEMU-TCG (GICv2).
David Woodhouse (4):
KVM: arm64: vgic: Allow userspace to set IIDR revision 1
KVM: arm64: selftests: Add vgic IIDR revision test
KVM: arm64: vgic: Remove v2_groups_user_writable and use IIDR revision directly
KVM: arm64: selftests: Add GICv2 IGROUPR writability test
arch/arm64/kvm/vgic/vgic-mmio-v2.c | 15 +-
arch/arm64/kvm/vgic/vgic-mmio-v3.c | 4 +
arch/arm64/kvm/vgic/vgic-mmio.c | 4 +
include/kvm/arm_vgic.h | 4 +-
tools/testing/selftests/kvm/Makefile.kvm | 2 +
.../testing/selftests/kvm/arm64/vgic_group_iidr.c | 118 +++++++++++
tools/testing/selftests/kvm/arm64/vgic_group_v2.c | 226 +++++++++++++++++++++
7 files changed, 361 insertions(+), 12 deletions(-)
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v3 1/4] KVM: arm64: vgic: Allow userspace to set IIDR revision 1
2026-05-11 11:30 [PATCH v3 0/4] KVM: arm64: vgic: Fix IGROUPR writability and IIDR revision control David Woodhouse
@ 2026-05-11 11:30 ` David Woodhouse
2026-05-11 11:30 ` [PATCH v3 2/4] KVM: arm64: selftests: Add vgic IIDR revision test David Woodhouse
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: David Woodhouse @ 2026-05-11 11:30 UTC (permalink / raw)
To: Paolo Bonzini
Cc: Jonathan Corbet, Shuah Khan, Marc Zyngier, Oliver Upton,
Joey Gouly, Suzuki K Poulose, Zenghui Yu, Catalin Marinas,
Will Deacon, Jonathan Cameron, Sascha Bischoff, Eric Auger,
Raghavendra Rao Ananta, Maxim Levitsky, David Woodhouse,
Kees Cook, Timothy Hayes, Arnd Bergmann, kvm, linux-doc,
linux-kernel, linux-arm-kernel, kvmarm, linux-kselftest,
Peter Maydell, qemu-arm, qemu-devel
From: David Woodhouse <dwmw@amazon.co.uk>
In order to preserve guest compatibility across kernel upgrades, allow
userspace to select GICD_IIDR revision 1. This allows compatibility with
the original behaviour from before commit d53c2c29ae0d ("KVM: arm/arm64:
vgic: Allow configuration of interrupt groups") where interrupt groups
are not guest-configurable.
When revision 1 is selected:
- GICv2: IGROUPR reads as zero (group 0), writes are ignored
- GICv3: IGROUPR reads as all-ones (group 1), writes are ignored
- v2_groups_user_writable is not set
This is implemented by checking the implementation revision in
vgic_mmio_write_group() and suppressing writes when the revision is
below 2. The read side needs no change since the per-IRQ group reset
values already match the expected behaviour.
For GICv2, commit 32f8777ed92d7 ("KVM: arm/arm64: vgic: Let userspace
opt-in to writable v2 IGROUPR") introduced a confusing model where
IGROUPR registers were not writable from userspace by default until the
IIDR was written — even if it was written to the *same* as its default
value (which, in fact, was the only thing that userspace *could* set it
to before commit a0e6ae45af17 fixed the IIDR write path). Furthermore,
even when the v2_groups_user_writable flag wasn't set, the *guest*
could still actually write to the registers... but userspace couldn't
save/restore them. That default behaviour for GICv2 remains unchanged;
it can be fixed in a future commit.
Fixes: d53c2c29ae0d ("KVM: arm/arm64: vgic: Allow configuration of interrupt groups")
Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
arch/arm64/kvm/vgic/vgic-mmio-v2.c | 3 +++
arch/arm64/kvm/vgic/vgic-mmio-v3.c | 4 ++++
arch/arm64/kvm/vgic/vgic-mmio.c | 4 ++++
include/kvm/arm_vgic.h | 1 +
4 files changed, 12 insertions(+)
diff --git a/arch/arm64/kvm/vgic/vgic-mmio-v2.c b/arch/arm64/kvm/vgic/vgic-mmio-v2.c
index 0643e333db35..e5714f7fd2ec 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio-v2.c
+++ b/arch/arm64/kvm/vgic/vgic-mmio-v2.c
@@ -20,6 +20,7 @@
* Revision 1: Report GICv2 interrupts as group 0 instead of group 1
* Revision 2: Interrupt groups are guest-configurable and signaled using
* their configured groups.
+ * Revision 3: GICv2 behaviour is unchanged from revision 2.
*/
static unsigned long vgic_mmio_read_v2_misc(struct kvm_vcpu *vcpu,
@@ -96,6 +97,8 @@ static int vgic_mmio_uaccess_write_v2_misc(struct kvm_vcpu *vcpu,
case KVM_VGIC_IMP_REV_2:
case KVM_VGIC_IMP_REV_3:
vcpu->kvm->arch.vgic.v2_groups_user_writable = true;
+ fallthrough;
+ case KVM_VGIC_IMP_REV_1:
dist->implementation_rev = reg;
return 0;
default:
diff --git a/arch/arm64/kvm/vgic/vgic-mmio-v3.c b/arch/arm64/kvm/vgic/vgic-mmio-v3.c
index 5913a20d8301..0130db71cfc9 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio-v3.c
+++ b/arch/arm64/kvm/vgic/vgic-mmio-v3.c
@@ -74,8 +74,11 @@ bool vgic_supports_direct_sgis(struct kvm *kvm)
/*
* The Revision field in the IIDR have the following meanings:
*
+ * Revision 1: Interrupt groups are not guest-configurable.
+ * IGROUPR reads as all-ones (group 1), writes ignored.
* Revision 2: Interrupt groups are guest-configurable and signaled using
* their configured groups.
+ * Revision 3: GICR_CTLR.{IR,CES} are advertised.
*/
static unsigned long vgic_mmio_read_v3_misc(struct kvm_vcpu *vcpu,
@@ -196,6 +199,7 @@ static int vgic_mmio_uaccess_write_v3_misc(struct kvm_vcpu *vcpu,
reg = FIELD_GET(GICD_IIDR_REVISION_MASK, val);
switch (reg) {
+ case KVM_VGIC_IMP_REV_1:
case KVM_VGIC_IMP_REV_2:
case KVM_VGIC_IMP_REV_3:
dist->implementation_rev = reg;
diff --git a/arch/arm64/kvm/vgic/vgic-mmio.c b/arch/arm64/kvm/vgic/vgic-mmio.c
index 74d76dec9730..1b662744ec5b 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio.c
+++ b/arch/arm64/kvm/vgic/vgic-mmio.c
@@ -73,6 +73,10 @@ void vgic_mmio_write_group(struct kvm_vcpu *vcpu, gpa_t addr,
int i;
unsigned long flags;
+ /* Revision 1 and below: groups are not guest-configurable. */
+ if (vgic_get_implementation_rev(vcpu) < KVM_VGIC_IMP_REV_2)
+ return;
+
for (i = 0; i < len * 8; i++) {
struct vgic_irq *irq = vgic_get_vcpu_irq(vcpu, intid + i);
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 1388dc6028a9..16811ec03d54 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -372,6 +372,7 @@ struct vgic_dist {
/* Implementation revision as reported in the GICD_IIDR */
u32 implementation_rev;
+#define KVM_VGIC_IMP_REV_1 1 /* GICv2 interrupts as group 0 */
#define KVM_VGIC_IMP_REV_2 2 /* GICv2 restorable groups */
#define KVM_VGIC_IMP_REV_3 3 /* GICv3 GICR_CTLR.{IW,CES,RWP} */
#define KVM_VGIC_IMP_REV_LATEST KVM_VGIC_IMP_REV_3
--
2.51.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v3 2/4] KVM: arm64: selftests: Add vgic IIDR revision test
2026-05-11 11:30 [PATCH v3 0/4] KVM: arm64: vgic: Fix IGROUPR writability and IIDR revision control David Woodhouse
2026-05-11 11:30 ` [PATCH v3 1/4] KVM: arm64: vgic: Allow userspace to set IIDR revision 1 David Woodhouse
@ 2026-05-11 11:30 ` David Woodhouse
2026-05-11 11:30 ` [PATCH v3 3/4] KVM: arm64: vgic: Remove v2_groups_user_writable and use IIDR revision directly David Woodhouse
2026-05-11 11:30 ` [PATCH v3 4/4] KVM: arm64: selftests: Add GICv2 IGROUPR writability test David Woodhouse
3 siblings, 0 replies; 5+ messages in thread
From: David Woodhouse @ 2026-05-11 11:30 UTC (permalink / raw)
To: Paolo Bonzini
Cc: Jonathan Corbet, Shuah Khan, Marc Zyngier, Oliver Upton,
Joey Gouly, Suzuki K Poulose, Zenghui Yu, Catalin Marinas,
Will Deacon, Jonathan Cameron, Sascha Bischoff, Eric Auger,
Raghavendra Rao Ananta, Maxim Levitsky, David Woodhouse,
Kees Cook, Timothy Hayes, Arnd Bergmann, kvm, linux-doc,
linux-kernel, linux-arm-kernel, kvmarm, linux-kselftest,
Peter Maydell, qemu-arm, qemu-devel
From: David Woodhouse <dwmw@amazon.co.uk>
Test that the GICD_IIDR implementation revision correctly controls
guest-visible behaviour for GICv3:
Revision 1: IGROUPR reads as all-ones (group 1), writes are ignored.
GICR_CTLR.{IR,CES} not advertised.
Revision 2: IGROUPR is guest-configurable (read/write).
GICR_CTLR.{IR,CES} not advertised.
Revision 3: IGROUPR is guest-configurable (read/write).
GICR_CTLR.{IR,CES} advertised.
For each revision, the test sets the IIDR via KVM_DEV_ARM_VGIC_GRP_DIST_REGS
before initializing the vGIC, then runs a guest that verifies the
expected IGROUPR and GICR_CTLR behaviour.
Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../selftests/kvm/arm64/vgic_group_iidr.c | 118 ++++++++++++++++++
2 files changed, 119 insertions(+)
create mode 100644 tools/testing/selftests/kvm/arm64/vgic_group_iidr.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 9118a5a51b89..8cadfed4d79a 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -177,6 +177,7 @@ TEST_GEN_PROGS_arm64 += arm64/sea_to_user
TEST_GEN_PROGS_arm64 += arm64/set_id_regs
TEST_GEN_PROGS_arm64 += arm64/smccc_filter
TEST_GEN_PROGS_arm64 += arm64/vcpu_width_config
+TEST_GEN_PROGS_arm64 += arm64/vgic_group_iidr
TEST_GEN_PROGS_arm64 += arm64/vgic_init
TEST_GEN_PROGS_arm64 += arm64/vgic_irq
TEST_GEN_PROGS_arm64 += arm64/vgic_lpi_stress
diff --git a/tools/testing/selftests/kvm/arm64/vgic_group_iidr.c b/tools/testing/selftests/kvm/arm64/vgic_group_iidr.c
new file mode 100644
index 000000000000..0073ccc19e92
--- /dev/null
+++ b/tools/testing/selftests/kvm/arm64/vgic_group_iidr.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * vgic_group_iidr.c - Test IGROUPR behaviour across IIDR revisions
+ *
+ * Validate that the GICD_IIDR implementation revision controls
+ * IGROUPR semantics for GICv3:
+ * Rev 1: IGROUPR reads as all-ones (group 1), writes ignored
+ * Rev 2+: IGROUPR is guest-configurable (read/write)
+ */
+#include <linux/sizes.h>
+
+#include "test_util.h"
+#include "kvm_util.h"
+#include "processor.h"
+#include "gic.h"
+#include "gic_v3.h"
+#include "vgic.h"
+
+#define NR_IRQS 128
+#define SPI_IGROUPR (GICD_IGROUPR + (32 / 32) * 4) /* intids 32-63 */
+
+static uint64_t shared_rev;
+
+static void guest_code(void)
+{
+ uint32_t val;
+
+ val = readl(GICD_BASE_GVA + SPI_IGROUPR);
+
+ if (shared_rev == 1) {
+ /* Rev 1: all group 1, guest writes must be ignored */
+ GUEST_ASSERT_EQ(val, 0xffffffff);
+ writel(0x0, GICD_BASE_GVA + SPI_IGROUPR);
+ val = readl(GICD_BASE_GVA + SPI_IGROUPR);
+ GUEST_ASSERT_EQ(val, 0xffffffff);
+ writel(0x55aa55aa, GICD_BASE_GVA + SPI_IGROUPR);
+ val = readl(GICD_BASE_GVA + SPI_IGROUPR);
+ GUEST_ASSERT_EQ(val, 0xffffffff);
+ } else {
+ /* Rev 2/3: guest-configurable */
+ writel(0xa5a5a5a5, GICD_BASE_GVA + SPI_IGROUPR);
+ val = readl(GICD_BASE_GVA + SPI_IGROUPR);
+ GUEST_ASSERT_EQ(val, 0xa5a5a5a5);
+ writel(0x0, GICD_BASE_GVA + SPI_IGROUPR);
+ val = readl(GICD_BASE_GVA + SPI_IGROUPR);
+ GUEST_ASSERT_EQ(val, 0x0);
+ }
+
+ /* Rev 3: GICR_CTLR advertises IR and CES. Rev 1/2: it does not. */
+ val = readl(GICR_BASE_GVA + GICR_CTLR);
+ if (shared_rev >= 3)
+ GUEST_ASSERT(val & (GICR_CTLR_IR | GICR_CTLR_CES));
+ else
+ GUEST_ASSERT(!(val & (GICR_CTLR_IR | GICR_CTLR_CES)));
+
+ GUEST_DONE();
+}
+
+static void run_test(int rev)
+{
+ struct kvm_vcpu *vcpus[1];
+ struct kvm_vm *vm;
+ struct ucall uc;
+ uint32_t iidr;
+ int gic_fd;
+
+ pr_info("Testing IIDR revision %d\n", rev);
+
+ test_disable_default_vgic();
+ vm = vm_create_with_vcpus(1, guest_code, vcpus);
+
+ gic_fd = __vgic_v3_setup(vm, 1, NR_IRQS);
+ TEST_ASSERT(gic_fd >= 0, "Failed to create vGICv3");
+
+ /* Set the requested IIDR revision before init. */
+ kvm_device_attr_get(gic_fd, KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
+ GICD_IIDR, &iidr);
+ iidr &= ~GICD_IIDR_REVISION_MASK;
+ iidr |= rev << GICD_IIDR_REVISION_SHIFT;
+ kvm_device_attr_set(gic_fd, KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
+ GICD_IIDR, &iidr);
+
+ __vgic_v3_init(gic_fd);
+
+ /* Verify the revision was applied. */
+ kvm_device_attr_get(gic_fd, KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
+ GICD_IIDR, &iidr);
+ TEST_ASSERT(((iidr & GICD_IIDR_REVISION_MASK) >> GICD_IIDR_REVISION_SHIFT) == rev,
+ "IIDR revision readback: expected %d, got %d",
+ rev, (iidr & GICD_IIDR_REVISION_MASK) >> GICD_IIDR_REVISION_SHIFT);
+
+ /* Tell the guest which revision we set. */
+ sync_global_to_guest(vm, shared_rev);
+ shared_rev = rev;
+ sync_global_to_guest(vm, shared_rev);
+
+ vcpu_run(vcpus[0]);
+ switch (get_ucall(vcpus[0], &uc)) {
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ case UCALL_DONE:
+ break;
+ default:
+ TEST_FAIL("Unexpected ucall %lu", uc.cmd);
+ }
+
+ close(gic_fd);
+ kvm_vm_free(vm);
+}
+
+int main(int argc, char *argv[])
+{
+ run_test(1);
+ run_test(2);
+ run_test(3);
+ return 0;
+}
--
2.51.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v3 3/4] KVM: arm64: vgic: Remove v2_groups_user_writable and use IIDR revision directly
2026-05-11 11:30 [PATCH v3 0/4] KVM: arm64: vgic: Fix IGROUPR writability and IIDR revision control David Woodhouse
2026-05-11 11:30 ` [PATCH v3 1/4] KVM: arm64: vgic: Allow userspace to set IIDR revision 1 David Woodhouse
2026-05-11 11:30 ` [PATCH v3 2/4] KVM: arm64: selftests: Add vgic IIDR revision test David Woodhouse
@ 2026-05-11 11:30 ` David Woodhouse
2026-05-11 11:30 ` [PATCH v3 4/4] KVM: arm64: selftests: Add GICv2 IGROUPR writability test David Woodhouse
3 siblings, 0 replies; 5+ messages in thread
From: David Woodhouse @ 2026-05-11 11:30 UTC (permalink / raw)
To: Paolo Bonzini
Cc: Jonathan Corbet, Shuah Khan, Marc Zyngier, Oliver Upton,
Joey Gouly, Suzuki K Poulose, Zenghui Yu, Catalin Marinas,
Will Deacon, Jonathan Cameron, Sascha Bischoff, Eric Auger,
Raghavendra Rao Ananta, Maxim Levitsky, David Woodhouse,
Kees Cook, Timothy Hayes, Arnd Bergmann, kvm, linux-doc,
linux-kernel, linux-arm-kernel, kvmarm, linux-kselftest,
Peter Maydell, qemu-arm, qemu-devel
From: David Woodhouse <dwmw@amazon.co.uk>
The v2_groups_user_writable flag was introduced to gate GICv2 userspace
IGROUPR writes until userspace explicitly wrote the IIDR, signalling
awareness of the group semantics. However, the guest write path through
vgic_mmio_write_group() was never gated by this flag, allowing a GICv2
guest to modify interrupt groups regardless of whether userspace had
opted in.
Rather than adding the same flag check to the guest path, remove the
flag entirely and make both guest and userspace IGROUPR writability
follow the IIDR implementation revision directly. Groups are writable
when the revision is >= 2, which is the case when userspace explicitly
sets the IIDR to revision 2 or 3. When userspace does not write the
IIDR, vgic_init() defaults to KVM_VGIC_IMP_REV_LATEST (currently 3),
so the behaviour is unchanged for userspace that doesn't set the IIDR.
This also fixes the inconsistency where GICv2 userspace could not write
IGROUPR at the default revision, even though the guest could.
As far as I can tell, QEMU commit eb8b9530b0c ("hw/intc/arm_gic_kvm.c:
Save and restore GICD_IGROUPRn state") made QEMU attempt to save/restore
the GICD_IGROUPR registers (which, again, are guest-writable but not
userspace-writable by default) without ever actually setting GICD_IIDR.
Fixes: 32f8777ed92d ("KVM: arm/arm64: vgic: Let userspace opt-in to writable v2 IGROUPR")
Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
arch/arm64/kvm/vgic/vgic-mmio-v2.c | 16 +++++-----------
include/kvm/arm_vgic.h | 3 ---
2 files changed, 5 insertions(+), 14 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-mmio-v2.c b/arch/arm64/kvm/vgic/vgic-mmio-v2.c
index e5714f7fd2ec..e5fc673a1ea9 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio-v2.c
+++ b/arch/arm64/kvm/vgic/vgic-mmio-v2.c
@@ -84,21 +84,15 @@ static int vgic_mmio_uaccess_write_v2_misc(struct kvm_vcpu *vcpu,
return -EINVAL;
/*
- * If we observe a write to GICD_IIDR we know that userspace
- * has been updated and has had a chance to cope with older
- * kernels (VGICv2 IIDR.Revision == 0) incorrectly reporting
- * interrupts as group 1, and therefore we now allow groups to
- * be user writable. Doing this by default would break
- * migration from old kernels to new kernels with legacy
- * userspace.
+ * Allow userspace to select the GICv2 IIDR revision.
+ * Group writability follows the revision directly:
+ * groups are guest/user writable for revision >= 2.
*/
reg = FIELD_GET(GICD_IIDR_REVISION_MASK, val);
switch (reg) {
+ case KVM_VGIC_IMP_REV_1:
case KVM_VGIC_IMP_REV_2:
case KVM_VGIC_IMP_REV_3:
- vcpu->kvm->arch.vgic.v2_groups_user_writable = true;
- fallthrough;
- case KVM_VGIC_IMP_REV_1:
dist->implementation_rev = reg;
return 0;
default:
@@ -114,7 +108,7 @@ static int vgic_mmio_uaccess_write_v2_group(struct kvm_vcpu *vcpu,
gpa_t addr, unsigned int len,
unsigned long val)
{
- if (vcpu->kvm->arch.vgic.v2_groups_user_writable)
+ if (vgic_get_implementation_rev(vcpu) >= KVM_VGIC_IMP_REV_2)
vgic_mmio_write_group(vcpu, addr, len, val);
return 0;
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 16811ec03d54..a9490e43d98d 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -377,9 +377,6 @@ struct vgic_dist {
#define KVM_VGIC_IMP_REV_3 3 /* GICv3 GICR_CTLR.{IW,CES,RWP} */
#define KVM_VGIC_IMP_REV_LATEST KVM_VGIC_IMP_REV_3
- /* Userspace can write to GICv2 IGROUPR */
- bool v2_groups_user_writable;
-
/* Do injected MSIs require an additional device ID? */
bool msis_require_devid;
--
2.51.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v3 4/4] KVM: arm64: selftests: Add GICv2 IGROUPR writability test
2026-05-11 11:30 [PATCH v3 0/4] KVM: arm64: vgic: Fix IGROUPR writability and IIDR revision control David Woodhouse
` (2 preceding siblings ...)
2026-05-11 11:30 ` [PATCH v3 3/4] KVM: arm64: vgic: Remove v2_groups_user_writable and use IIDR revision directly David Woodhouse
@ 2026-05-11 11:30 ` David Woodhouse
3 siblings, 0 replies; 5+ messages in thread
From: David Woodhouse @ 2026-05-11 11:30 UTC (permalink / raw)
To: Paolo Bonzini
Cc: Jonathan Corbet, Shuah Khan, Marc Zyngier, Oliver Upton,
Joey Gouly, Suzuki K Poulose, Zenghui Yu, Catalin Marinas,
Will Deacon, Jonathan Cameron, Sascha Bischoff, Eric Auger,
Raghavendra Rao Ananta, Maxim Levitsky, David Woodhouse,
Kees Cook, Timothy Hayes, Arnd Bergmann, kvm, linux-doc,
linux-kernel, linux-arm-kernel, kvmarm, linux-kselftest,
Peter Maydell, qemu-arm, qemu-devel
From: David Woodhouse <dwmw@amazon.co.uk>
Test that GICv2 IGROUPR writability is consistently gated by the IIDR
implementation revision for both guest and userspace paths:
Default (no IIDR write): implementation_rev defaults to 3, groups
writable from both guest and userspace.
Rev 1: IGROUPR reads as zero (group 0), writes ignored from both
guest and userspace.
Rev 2: IGROUPR is writable from both guest and userspace.
This test requires GICv2 emulation support (GICv3 with GICv2 compat
CPU interface) and will be skipped on hardware without it.
Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../selftests/kvm/arm64/vgic_group_v2.c | 226 ++++++++++++++++++
2 files changed, 227 insertions(+)
create mode 100644 tools/testing/selftests/kvm/arm64/vgic_group_v2.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 8cadfed4d79a..6bc295d0b776 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -178,6 +178,7 @@ TEST_GEN_PROGS_arm64 += arm64/set_id_regs
TEST_GEN_PROGS_arm64 += arm64/smccc_filter
TEST_GEN_PROGS_arm64 += arm64/vcpu_width_config
TEST_GEN_PROGS_arm64 += arm64/vgic_group_iidr
+TEST_GEN_PROGS_arm64 += arm64/vgic_group_v2
TEST_GEN_PROGS_arm64 += arm64/vgic_init
TEST_GEN_PROGS_arm64 += arm64/vgic_irq
TEST_GEN_PROGS_arm64 += arm64/vgic_lpi_stress
diff --git a/tools/testing/selftests/kvm/arm64/vgic_group_v2.c b/tools/testing/selftests/kvm/arm64/vgic_group_v2.c
new file mode 100644
index 000000000000..f2b384a816ba
--- /dev/null
+++ b/tools/testing/selftests/kvm/arm64/vgic_group_v2.c
@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * vgic_group_v2.c - Test GICv2 IGROUPR behaviour across IIDR revisions
+ *
+ * Validate that the GICD_IIDR implementation revision controls GICv2
+ * IGROUPR writability for both guest and userspace:
+ * Default (no IIDR write): groups writable (implementation_rev defaults to 3)
+ * Rev 1: IGROUPR reads as zero (group 0), writes ignored
+ * Rev 2: IGROUPR is guest and userspace configurable
+ */
+#include <linux/sizes.h>
+
+#include "test_util.h"
+#include "kvm_util.h"
+#include "processor.h"
+#include "gic.h"
+#include "gic_v3.h"
+#include "vgic.h"
+
+#define NR_IRQS 64
+
+#define V2_DIST_BASE 0x8000000ULL
+#define V2_CPU_BASE 0x8010000ULL
+#define V2_DIST_GVA ((volatile void *)V2_DIST_BASE)
+
+#define SPI_IGROUPR (GICD_IGROUPR + (32 / 32) * 4)
+
+static uint64_t shared_rev;
+static uint64_t guest_result;
+
+static void guest_code(void)
+{
+ uint32_t before, after;
+
+ before = readl(V2_DIST_GVA + SPI_IGROUPR);
+ writel(0x5a5a5a5a, V2_DIST_GVA + SPI_IGROUPR);
+ after = readl(V2_DIST_GVA + SPI_IGROUPR);
+
+ guest_result = ((uint64_t)before << 32) | after;
+ GUEST_DONE();
+}
+
+static int create_v2_gic(struct kvm_vm *vm)
+{
+ uint32_t nr_irqs = NR_IRQS;
+ uint64_t addr;
+ int gic_fd;
+
+ gic_fd = __kvm_create_device(vm, KVM_DEV_TYPE_ARM_VGIC_V2);
+ if (gic_fd < 0)
+ return gic_fd;
+
+ addr = V2_DIST_BASE;
+ kvm_device_attr_set(gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V2_ADDR_TYPE_DIST, &addr);
+ addr = V2_CPU_BASE;
+ kvm_device_attr_set(gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V2_ADDR_TYPE_CPU, &addr);
+
+ virt_map(vm, V2_DIST_BASE, V2_DIST_BASE,
+ vm_calc_num_guest_pages(vm->mode, SZ_64K));
+ virt_map(vm, V2_CPU_BASE, V2_CPU_BASE,
+ vm_calc_num_guest_pages(vm->mode, SZ_64K));
+
+ kvm_device_attr_set(gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
+ 0, &nr_irqs);
+ return gic_fd;
+}
+
+static void run_test(int set_iidr_rev)
+{
+ struct kvm_vcpu *vcpus[1];
+ struct kvm_vm *vm;
+ struct ucall uc;
+ uint32_t before, after, igroupr, iidr;
+ int gic_fd;
+ bool expect_writable;
+
+ if (set_iidr_rev >= 0)
+ pr_info("Testing GICv2 IIDR revision %d\n", set_iidr_rev);
+ else
+ pr_info("Testing GICv2 IIDR default (no write)\n");
+
+ test_disable_default_vgic();
+ vm = vm_create_with_vcpus(1, guest_code, vcpus);
+
+ gic_fd = create_v2_gic(vm);
+ TEST_REQUIRE(gic_fd >= 0);
+
+ if (set_iidr_rev >= 0) {
+ kvm_device_attr_get(gic_fd, KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
+ GICD_IIDR, &iidr);
+ iidr &= ~GICD_IIDR_REVISION_MASK;
+ iidr |= set_iidr_rev << GICD_IIDR_REVISION_SHIFT;
+ kvm_device_attr_set(gic_fd, KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
+ GICD_IIDR, &iidr);
+ }
+
+ kvm_device_attr_set(gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ /*
+ * Default (no IIDR write) gets implementation_rev=3 from vgic_init(),
+ * so groups should be writable. Rev 1 = not writable. Rev 2+ = writable.
+ */
+ expect_writable = (set_iidr_rev != 1);
+
+ /* Test userspace IGROUPR write */
+ igroupr = 0xa5a5a5a5;
+ kvm_device_attr_set(gic_fd, KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
+ SPI_IGROUPR, &igroupr);
+ igroupr = 0;
+ kvm_device_attr_get(gic_fd, KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
+ SPI_IGROUPR, &igroupr);
+
+ if (expect_writable)
+ TEST_ASSERT(igroupr == 0xa5a5a5a5,
+ "Userspace write should succeed: got 0x%08x", igroupr);
+ else
+ TEST_ASSERT(igroupr == 0x00000000,
+ "Userspace write should be ignored: got 0x%08x", igroupr);
+
+ /* Reset IGROUPR to 0 via userspace for rev 2+ before guest test */
+ if (expect_writable) {
+ igroupr = 0;
+ kvm_device_attr_set(gic_fd, KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
+ SPI_IGROUPR, &igroupr);
+ }
+
+ /* Test guest IGROUPR write */
+ sync_global_to_guest(vm, guest_result);
+ vcpu_run(vcpus[0]);
+
+ switch (get_ucall(vcpus[0], &uc)) {
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ case UCALL_DONE:
+ break;
+ default:
+ TEST_FAIL("Unexpected ucall %lu", uc.cmd);
+ }
+
+ sync_global_from_guest(vm, guest_result);
+ before = guest_result >> 32;
+ after = guest_result & 0xffffffff;
+
+ TEST_ASSERT(before == 0x00000000,
+ "Initial IGROUPR should be 0 (group 0): got 0x%08x", before);
+
+ if (expect_writable)
+ TEST_ASSERT(after == 0x5a5a5a5a,
+ "Guest write should succeed: got 0x%08x", after);
+ else
+ TEST_ASSERT(after == 0x00000000,
+ "Guest write should be ignored: got 0x%08x", after);
+
+ close(gic_fd);
+ kvm_vm_free(vm);
+}
+
+/*
+ * Test QEMU-style save/restore: the guest writes IGROUPR, then userspace
+ * reads it back (save) and writes it again (restore) — all without ever
+ * writing GICD_IIDR. This exercises the bug where v2_groups_user_writable
+ * gated userspace writes but not guest writes, so userspace could observe
+ * guest-modified groups but couldn't restore them.
+ */
+static void run_save_restore_test(void)
+{
+ struct kvm_vcpu *vcpus[1];
+ struct kvm_vm *vm;
+ struct ucall uc;
+ uint32_t igroupr;
+ int gic_fd;
+
+ pr_info("Testing GICv2 IGROUPR save/restore (no IIDR write)\n");
+
+ test_disable_default_vgic();
+ vm = vm_create_with_vcpus(1, guest_code, vcpus);
+
+ gic_fd = create_v2_gic(vm);
+ TEST_REQUIRE(gic_fd >= 0);
+
+ /* Do NOT write GICD_IIDR — mimicking QEMU */
+
+ kvm_device_attr_set(gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ /* Let the guest write 0x5a5a5a5a to IGROUPR */
+ sync_global_to_guest(vm, guest_result);
+ vcpu_run(vcpus[0]);
+ TEST_ASSERT(get_ucall(vcpus[0], &uc) == UCALL_DONE,
+ "Guest failed");
+
+ /* Save: userspace reads IGROUPR — should see guest's write */
+ igroupr = 0;
+ kvm_device_attr_get(gic_fd, KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
+ SPI_IGROUPR, &igroupr);
+ TEST_ASSERT(igroupr == 0x5a5a5a5a,
+ "Save: expected 0x5a5a5a5a, got 0x%08x", igroupr);
+
+ /* Restore: userspace writes a different value — should succeed */
+ igroupr = 0x12345678;
+ kvm_device_attr_set(gic_fd, KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
+ SPI_IGROUPR, &igroupr);
+
+ /* Verify: read back should reflect the restore */
+ igroupr = 0;
+ kvm_device_attr_get(gic_fd, KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
+ SPI_IGROUPR, &igroupr);
+ TEST_ASSERT(igroupr == 0x12345678,
+ "Restore: expected 0x12345678, got 0x%08x", igroupr);
+
+ close(gic_fd);
+ kvm_vm_free(vm);
+}
+
+int main(int argc, char *argv[])
+{
+ run_test(-1); /* default */
+ run_test(1); /* rev 1 */
+ run_test(2); /* rev 2 */
+ run_save_restore_test();
+ return 0;
+}
--
2.51.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-05-11 11:36 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-11 11:30 [PATCH v3 0/4] KVM: arm64: vgic: Fix IGROUPR writability and IIDR revision control David Woodhouse
2026-05-11 11:30 ` [PATCH v3 1/4] KVM: arm64: vgic: Allow userspace to set IIDR revision 1 David Woodhouse
2026-05-11 11:30 ` [PATCH v3 2/4] KVM: arm64: selftests: Add vgic IIDR revision test David Woodhouse
2026-05-11 11:30 ` [PATCH v3 3/4] KVM: arm64: vgic: Remove v2_groups_user_writable and use IIDR revision directly David Woodhouse
2026-05-11 11:30 ` [PATCH v3 4/4] KVM: arm64: selftests: Add GICv2 IGROUPR writability test David Woodhouse
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox