From: David Woodhouse The uaccess write handlers for GICD_IIDR in both GICv2 and GICv3 extract the revision field from 'reg' (the current IIDR value read back from the emulated distributor) instead of 'val' (the value userspace is trying to write). This means userspace can never actually change the implementation revision — the extracted value is always the current one. Fix the FIELD_GET to use 'val' so that userspace can select a different revision for migration compatibility. Fixes: 49a1a2c70a7f ("KVM: arm64: vgic-v3: Advertise GICR_CTLR.{IR, CES} as a new GICD_IIDR revision") Signed-off-by: David Woodhouse --- arch/arm64/kvm/vgic/vgic-mmio-v2.c | 2 +- arch/arm64/kvm/vgic/vgic-mmio-v3.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-mmio-v2.c b/arch/arm64/kvm/vgic/vgic-mmio-v2.c index 406845b3117c..0643e333db35 100644 --- a/arch/arm64/kvm/vgic/vgic-mmio-v2.c +++ b/arch/arm64/kvm/vgic/vgic-mmio-v2.c @@ -91,7 +91,7 @@ static int vgic_mmio_uaccess_write_v2_misc(struct kvm_vcpu *vcpu, * migration from old kernels to new kernels with legacy * userspace. */ - reg = FIELD_GET(GICD_IIDR_REVISION_MASK, reg); + reg = FIELD_GET(GICD_IIDR_REVISION_MASK, val); switch (reg) { case KVM_VGIC_IMP_REV_2: case KVM_VGIC_IMP_REV_3: diff --git a/arch/arm64/kvm/vgic/vgic-mmio-v3.c b/arch/arm64/kvm/vgic/vgic-mmio-v3.c index 89edb84d1ac6..5913a20d8301 100644 --- a/arch/arm64/kvm/vgic/vgic-mmio-v3.c +++ b/arch/arm64/kvm/vgic/vgic-mmio-v3.c @@ -194,7 +194,7 @@ static int vgic_mmio_uaccess_write_v3_misc(struct kvm_vcpu *vcpu, if ((reg ^ val) & ~GICD_IIDR_REVISION_MASK) return -EINVAL; - reg = FIELD_GET(GICD_IIDR_REVISION_MASK, reg); + reg = FIELD_GET(GICD_IIDR_REVISION_MASK, val); switch (reg) { case KVM_VGIC_IMP_REV_2: case KVM_VGIC_IMP_REV_3: -- 2.51.0 From: David Woodhouse Allow userspace to select GICD_IIDR revision 1, which restores the original pre-d53c2c29ae0d ("KVM: arm/arm64: vgic: Allow configuration of interrupt groups") behaviour 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. Note that d53c2c29ae0d wired guest IGROUPR writes directly to vgic_mmio_write_group() without any revision check, while only gating the userspace write path via v2_groups_user_writable. This meant a guest could modify interrupt groups even at revision 1, which was never intended. The write_group revision check fixes both the guest and GICv3 userspace paths. Fixes: d53c2c29ae0d ("KVM: arm/arm64: vgic: Allow configuration of interrupt groups") Signed-off-by: David Woodhouse --- 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 a573b1f0c6cb..4fbe0ad22adf 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 f2eafc65bbf4..90fb6cd3c91c 100644 --- a/include/kvm/arm_vgic.h +++ b/include/kvm/arm_vgic.h @@ -248,6 +248,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 From: David Woodhouse 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 --- 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 6471fa214a9f..df729a70124f 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -177,6 +177,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_group_iidr TEST_GEN_PROGS_arm64 += arm64/vpmu_counter_access TEST_GEN_PROGS_arm64 += arm64/no-vgic-v3 TEST_GEN_PROGS_arm64 += arm64/idreg-idst 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 + +#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 From: David Woodhouse 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 a GICv2 guest could write IGROUPR even when the IIDR had not been explicitly set by userspace. Fixes: d53c2c29ae0d ("KVM: arm/arm64: vgic: Allow configuration of interrupt groups") Signed-off-by: David Woodhouse --- 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 90fb6cd3c91c..cdfab2c20877 100644 --- a/include/kvm/arm_vgic.h +++ b/include/kvm/arm_vgic.h @@ -253,9 +253,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 From: David Woodhouse 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 --- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../selftests/kvm/arm64/vgic_group_v2.c | 168 ++++++++++++++++++ 2 files changed, 169 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 df729a70124f..878d7cb92555 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -178,6 +178,7 @@ 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_group_iidr +TEST_GEN_PROGS_arm64 += arm64/vgic_group_v2 TEST_GEN_PROGS_arm64 += arm64/vpmu_counter_access TEST_GEN_PROGS_arm64 += arm64/no-vgic-v3 TEST_GEN_PROGS_arm64 += arm64/idreg-idst 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..6d4bad44bae7 --- /dev/null +++ b/tools/testing/selftests/kvm/arm64/vgic_group_v2.c @@ -0,0 +1,168 @@ +// 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 + +#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); +} + +int main(int argc, char *argv[]) +{ + run_test(-1); /* default */ + run_test(1); /* rev 1 */ + run_test(2); /* rev 2 */ + return 0; +} -- 2.51.0