summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/arch/arm64/include/uapi/asm/kvm.h15
-rw-r--r--tools/testing/selftests/kvm/Makefile.kvm2
-rw-r--r--tools/testing/selftests/kvm/arm64/arch_timer_edge_cases.c25
-rw-r--r--tools/testing/selftests/kvm/arm64/debug-exceptions.c2
-rw-r--r--tools/testing/selftests/kvm/arm64/external_aborts.c23
-rw-r--r--tools/testing/selftests/kvm/arm64/hypercalls.c31
-rw-r--r--tools/testing/selftests/kvm/arm64/nv_pre_fault_memory_test.c158
-rw-r--r--tools/testing/selftests/kvm/arm64/sea_to_user.c75
-rw-r--r--tools/testing/selftests/kvm/arm64/set_id_regs.c106
-rw-r--r--tools/testing/selftests/kvm/arm64/vgic_init.c116
-rw-r--r--tools/testing/selftests/kvm/arm64/vgic_v5.c1849
-rw-r--r--tools/testing/selftests/kvm/include/arm64/gic_v5.h105
-rw-r--r--tools/testing/selftests/kvm/include/test_util.h1
-rw-r--r--tools/testing/selftests/kvm/lib/test_util.c15
-rw-r--r--tools/testing/selftests/kvm/pre_fault_memory_test.c152
15 files changed, 2612 insertions, 63 deletions
diff --git a/tools/arch/arm64/include/uapi/asm/kvm.h b/tools/arch/arm64/include/uapi/asm/kvm.h
index 1c13bfa2d38a..9998252ea8ec 100644
--- a/tools/arch/arm64/include/uapi/asm/kvm.h
+++ b/tools/arch/arm64/include/uapi/asm/kvm.h
@@ -97,6 +97,11 @@ struct kvm_regs {
#define KVM_VGIC_V3_REDIST_SIZE (2 * SZ_64K)
#define KVM_VGIC_V3_ITS_SIZE (2 * SZ_64K)
+/* Supported VGICv5 address types */
+#define KVM_VGIC_V5_ADDR_TYPE_IRS 6
+
+#define KVM_VGIC_V5_IRS_SIZE (2 * SZ_64K)
+
#define KVM_ARM_VCPU_POWER_OFF 0 /* CPU is started in OFF state */
#define KVM_ARM_VCPU_EL1_32BIT 1 /* CPU running a 32bit VM */
#define KVM_ARM_VCPU_PSCI_0_2 2 /* CPU uses PSCI v0.2 */
@@ -417,6 +422,16 @@ enum {
#define KVM_DEV_ARM_VGIC_GRP_LEVEL_INFO 7
#define KVM_DEV_ARM_VGIC_GRP_ITS_REGS 8
#define KVM_DEV_ARM_VGIC_GRP_MAINT_IRQ 9
+#define KVM_DEV_ARM_VGIC_GRP_IRS_REGS 10
+#define KVM_DEV_ARM_VGIC_GRP_IST 11
+
+struct kvm_vgic_v5_ist {
+ __u64 spi_ist_addr;
+ __u64 spi_ist_size;
+ __u64 lpi_ist_addr;
+ __u64 lpi_ist_size;
+};
+
#define KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_SHIFT 10
#define KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_MASK \
(0x3fffffULL << KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_SHIFT)
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 51b98f47044f..fa10eb9e8fa7 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -178,6 +178,7 @@ TEST_GEN_PROGS_arm64 += arm64/debug-exceptions
TEST_GEN_PROGS_arm64 += arm64/hello_el2
TEST_GEN_PROGS_arm64 += arm64/host_sve
TEST_GEN_PROGS_arm64 += arm64/hypercalls
+TEST_GEN_PROGS_arm64 += arm64/nv_pre_fault_memory_test
TEST_GEN_PROGS_arm64 += arm64/external_aborts
TEST_GEN_PROGS_arm64 += arm64/mmio_sign_ext
TEST_GEN_PROGS_arm64 += arm64/page_fault_test
@@ -206,6 +207,7 @@ TEST_GEN_PROGS_arm64 += guest_memfd_test
TEST_GEN_PROGS_arm64 += mmu_stress_test
TEST_GEN_PROGS_arm64 += rseq_test
TEST_GEN_PROGS_arm64 += steal_time
+TEST_GEN_PROGS_arm64 += pre_fault_memory_test
TEST_GEN_PROGS_s390 = $(TEST_GEN_PROGS_COMMON)
TEST_GEN_PROGS_s390 += s390/memop
diff --git a/tools/testing/selftests/kvm/arm64/arch_timer_edge_cases.c b/tools/testing/selftests/kvm/arm64/arch_timer_edge_cases.c
index d9c9377a6325..d46799ff91dc 100644
--- a/tools/testing/selftests/kvm/arm64/arch_timer_edge_cases.c
+++ b/tools/testing/selftests/kvm/arm64/arch_timer_edge_cases.c
@@ -787,6 +787,30 @@ static void test_timers_in_the_past(enum arch_timer timer)
}
}
+/*
+ * A cval within long_wait_ms of the counter's wrap while the counter sits at
+ * wait_ms: a deadline further away than the counter can reach. Unmasked, so
+ * an early ISTATUS shows up as an IRQ.
+ */
+static void test_timers_past_counter_wrap(enum arch_timer timer)
+{
+ u64 cval = -msec_to_cycles(test_args.long_wait_ms);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(sleep_method); i++) {
+ sleep_method_t sm = sleep_method[i];
+
+ local_irq_disable();
+ set_counter(timer, msec_to_cycles(test_args.wait_ms));
+ set_cval_irq(timer, cval, CTL_ENABLE);
+ sm(timer, msecs_to_usecs(test_args.wait_ms) + TIMEOUT_NO_IRQ_US);
+ local_irq_enable();
+ isb();
+ assert_irqs_handled(0);
+ timer_set_ctl(timer, CTL_IMASK);
+ }
+}
+
static void test_long_timer_delays(enum arch_timer timer)
{
s32 tval = (s32)msec_to_cycles(test_args.long_wait_ms);
@@ -806,6 +830,7 @@ static void guest_run_iteration(enum arch_timer timer)
test_basic_functionality(timer);
test_timers_sanity_checks(timer);
+ test_timers_past_counter_wrap(timer);
test_timers_above_tval_max(timer);
test_timers_in_the_past(timer);
diff --git a/tools/testing/selftests/kvm/arm64/debug-exceptions.c b/tools/testing/selftests/kvm/arm64/debug-exceptions.c
index 7dc5f0b4f6ad..9da6dc5bcfe6 100644
--- a/tools/testing/selftests/kvm/arm64/debug-exceptions.c
+++ b/tools/testing/selftests/kvm/arm64/debug-exceptions.c
@@ -387,7 +387,7 @@ static void guest_code_ss(int test_cnt)
* Enable Single Step execution. Note! This _must_ be a bare
* ucall as the ucall() path uses atomic operations to manage
* the ucall structures, and the built-in "atomics" are usually
- * implemented via exclusive access instructions. The exlusive
+ * implemented via exclusive access instructions. The exclusive
* monitor is cleared on ERET, and so taking debug exceptions
* during a LDREX=>STREX sequence will prevent forward progress
* and hang the guest/test.
diff --git a/tools/testing/selftests/kvm/arm64/external_aborts.c b/tools/testing/selftests/kvm/arm64/external_aborts.c
index d8fe17a6cc59..1a858d0d8296 100644
--- a/tools/testing/selftests/kvm/arm64/external_aborts.c
+++ b/tools/testing/selftests/kvm/arm64/external_aborts.c
@@ -75,6 +75,17 @@ static void vcpu_inject_serror(struct kvm_vcpu *vcpu)
vcpu_events_set(vcpu, &events);
}
+static void vcpu_assert_serror_pending(struct kvm_vcpu *vcpu, bool pending)
+{
+ struct kvm_vcpu_events events;
+
+ vcpu_events_get(vcpu, &events);
+ TEST_ASSERT_EQ(events.exception.serror_pending, pending);
+
+ if (pending && vcpu_has_ras(vcpu))
+ TEST_ASSERT_EQ(events.exception.serror_esr, EXPECTED_SERROR_ISS);
+}
+
static void __vcpu_run_expect(struct kvm_vcpu *vcpu, unsigned int cmd)
{
struct ucall uc;
@@ -134,6 +145,7 @@ static void test_mmio_abort(void)
vcpu_inject_sea(vcpu);
vcpu_run_expect_done(vcpu);
+ vcpu_assert_serror_pending(vcpu, false);
kvm_vm_free(vm);
}
@@ -162,6 +174,7 @@ static void test_mmio_nisv(void)
TEST_ASSERT(_vcpu_run(vcpu), "Expected nonzero return code from KVM_RUN");
TEST_ASSERT_EQ(errno, ENOSYS);
+ vcpu_assert_serror_pending(vcpu, false);
kvm_vm_free(vm);
}
@@ -185,6 +198,7 @@ static void test_mmio_nisv_abort(void)
vcpu_inject_sea(vcpu);
vcpu_run_expect_done(vcpu);
+ vcpu_assert_serror_pending(vcpu, false);
kvm_vm_free(vm);
}
@@ -212,6 +226,7 @@ static void test_serror_masked(void)
vcpu_inject_serror(vcpu);
vcpu_run_expect_done(vcpu);
+ vcpu_assert_serror_pending(vcpu, true);
kvm_vm_free(vm);
}
@@ -247,6 +262,7 @@ static void test_serror(void)
vcpu_inject_serror(vcpu);
vcpu_run_expect_done(vcpu);
+ vcpu_assert_serror_pending(vcpu, false);
kvm_vm_free(vm);
}
@@ -288,6 +304,7 @@ static void test_s1ptw_abort(void)
*ptep |= bad_pa;
vcpu_run_expect_done(vcpu);
+ vcpu_assert_serror_pending(vcpu, false);
kvm_vm_free(vm);
}
@@ -313,6 +330,7 @@ static void test_serror_emulated(void)
vcpu_run_expect_sync(vcpu);
vcpu_inject_serror(vcpu);
vcpu_run_expect_done(vcpu);
+ vcpu_assert_serror_pending(vcpu, false);
kvm_vm_free(vm);
}
@@ -339,7 +357,7 @@ static void test_mmio_ease(void)
pfr1 = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64PFR1_EL1));
if (!SYS_FIELD_GET(ID_AA64PFR1_EL1, DF2, pfr1)) {
pr_debug("Skipping %s\n", __func__);
- return;
+ goto done;
}
/*
@@ -356,6 +374,8 @@ static void test_mmio_ease(void)
vcpu_inject_sea(vcpu);
vcpu_run_expect_done(vcpu);
+ vcpu_assert_serror_pending(vcpu, false);
+done:
kvm_vm_free(vm);
}
@@ -394,6 +414,7 @@ static void test_serror_amo(void)
vcpu_run_expect_sync(vcpu);
vcpu_inject_serror(vcpu);
vcpu_run_expect_done(vcpu);
+ vcpu_assert_serror_pending(vcpu, false);
kvm_vm_free(vm);
}
diff --git a/tools/testing/selftests/kvm/arm64/hypercalls.c b/tools/testing/selftests/kvm/arm64/hypercalls.c
index 5d96cdf382c4..391e2ba58b0b 100644
--- a/tools/testing/selftests/kvm/arm64/hypercalls.c
+++ b/tools/testing/selftests/kvm/arm64/hypercalls.c
@@ -1,12 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-only
-/* hypercalls: Check the ARM64's psuedo-firmware bitmap register interface.
+/* hypercalls: Check the ARM64's pseudo-firmware bitmap register interface.
*
* The test validates the basic hypercall functionalities that are exposed
- * via the psuedo-firmware bitmap register. This includes the registers'
+ * via the pseudo-firmware bitmap register. This includes the registers'
* read/write behavior before and after the VM has started, and if the
* hypercalls are properly masked or unmasked to the guest when disabled or
- * enabled from the KVM userspace, respectively.
+ * enabled from the KVM userspace, respectively. It also checks the two
+ * SMCCC_ARCH_FEATURES queries whose answer SMCCC pins to SUCCESS.
*/
#include <errno.h>
#include <linux/arm-smccc.h>
@@ -97,6 +98,12 @@ static const struct test_hvc_info false_hvc_info[] = {
TEST_HVC_INFO(ARM_SMCCC_HV_PV_TIME_FEATURES, ARM_SMCCC_TRNG_RND64),
};
+/* SMCCC pins the answer to SUCCESS, whatever the bitmaps hold. */
+static const struct test_hvc_info always_hvc_info[] = {
+ TEST_HVC_INFO(ARM_SMCCC_ARCH_FEATURES_FUNC_ID, ARM_SMCCC_VERSION_FUNC_ID),
+ TEST_HVC_INFO(ARM_SMCCC_ARCH_FEATURES_FUNC_ID, ARM_SMCCC_ARCH_FEATURES_FUNC_ID),
+};
+
static void guest_test_hvc(const struct test_hvc_info *hc_info)
{
unsigned int i;
@@ -128,6 +135,23 @@ static void guest_test_hvc(const struct test_hvc_info *hc_info)
}
}
+static void guest_test_always_hvc(void)
+{
+ struct arm_smccc_res res;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(always_hvc_info); i++) {
+ memset(&res, 0, sizeof(res));
+ do_smccc(always_hvc_info[i].func_id, always_hvc_info[i].arg1,
+ 0, 0, 0, 0, 0, 0, &res);
+
+ __GUEST_ASSERT(res.a0 == SMCCC_RET_SUCCESS,
+ "a0 = 0x%lx, func_id = 0x%x, arg1 = 0x%lx, stage = %u",
+ res.a0, always_hvc_info[i].func_id,
+ always_hvc_info[i].arg1, stage);
+ }
+}
+
static void guest_code(void)
{
while (stage != TEST_STAGE_END) {
@@ -137,6 +161,7 @@ static void guest_code(void)
case TEST_STAGE_HVC_IFACE_FEAT_DISABLED:
case TEST_STAGE_HVC_IFACE_FEAT_ENABLED:
guest_test_hvc(hvc_info);
+ guest_test_always_hvc();
break;
case TEST_STAGE_HVC_IFACE_FALSE_INFO:
guest_test_hvc(false_hvc_info);
diff --git a/tools/testing/selftests/kvm/arm64/nv_pre_fault_memory_test.c b/tools/testing/selftests/kvm/arm64/nv_pre_fault_memory_test.c
new file mode 100644
index 000000000000..09c1db303856
--- /dev/null
+++ b/tools/testing/selftests/kvm/arm64/nv_pre_fault_memory_test.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * nv_pre_fault_memory_test - Test KVM_PRE_FAULT_MEMORY on a vCPU whose
+ * last-run context is nested.
+ *
+ * The guest enters vEL2, sets up its EL2 translation configuration into the
+ * real EL1 registers then ERETs to vEL1 and exits to userspace so its vCPU
+ * last-run context is nested backed by a shadow stage 2 MMU.
+ *
+ * Assert that pre-faulting ignores that and targets the canonical stage-2
+ * page tables only.
+ */
+#include "kvm_util.h"
+#include "processor.h"
+#include "test_util.h"
+#include "ucall.h"
+
+#include <asm/sysreg.h>
+#include <linux/sizes.h>
+
+#define TEST_MEM_SLOT 10
+#define TEST_MEM_SIZE SZ_2M
+#define TEST_MEM_GPA SZ_1G
+
+static void guest_el1_code(void)
+{
+ u64 offset;
+
+ GUEST_ASSERT_EQ(get_current_el(), 1);
+
+ /* Exit to userspace with the vEL1 (nested) context live. */
+ GUEST_SYNC(1);
+
+ /*
+ * Touch the prefaulted range. vstage-2 is disabled, so the shadow
+ * stage-2 is a 1:1 view of the canonical IPA space.
+ */
+ for (offset = 0; offset < TEST_MEM_SIZE; offset += SZ_4K)
+ READ_ONCE(*(u64 *)(TEST_MEM_GPA + offset));
+
+ GUEST_DONE();
+}
+
+static void guest_code(void)
+{
+ u64 sp;
+
+ GUEST_ASSERT_EQ(get_current_el(), 2);
+
+ /*
+ * Mirror the EL2 translation regime into the real EL1 registers so
+ * that vEL1 runs on the test's stage-1 page tables. With E2H=1, the
+ * _EL1 accessors read the EL2 registers, and the _EL12 accessors
+ * write the real EL1 registers.
+ */
+ write_sysreg_s(read_sysreg(sctlr_el1), SYS_SCTLR_EL12);
+ write_sysreg_s(read_sysreg(tcr_el1), SYS_TCR_EL12);
+ write_sysreg_s(read_sysreg(ttbr0_el1), SYS_TTBR0_EL12);
+ write_sysreg_s(read_sysreg(mair_el1), SYS_MAIR_EL12);
+ write_sysreg_s(read_sysreg(cpacr_el1), SYS_CPACR_EL12);
+
+ /* Run vEL1 on the same stack. */
+ asm volatile("mov %0, sp" : "=r"(sp));
+ write_sysreg(sp, sp_el1);
+
+ /*
+ * Drop TGE so that vEL1 is a nested context rather than host EL0.
+ * KVM backs it with a shadow stage-2 MMU even though vstage-2 is
+ * disabled (HCR_EL2.VM=0).
+ */
+ write_sysreg(read_sysreg(hcr_el2) & ~HCR_EL2_TGE, hcr_el2);
+ isb();
+
+ write_sysreg(PSR_MODE_EL1h | PSR_F_BIT | PSR_I_BIT | PSR_A_BIT |
+ PSR_D_BIT, spsr_el2);
+ write_sysreg((u64)guest_el1_code, elr_el2);
+ asm volatile("eret");
+
+ GUEST_ASSERT(false);
+}
+
+static void pre_fault(struct kvm_vcpu *vcpu, u64 gpa, u64 size)
+{
+ struct kvm_pre_fault_memory range = {
+ .gpa = gpa,
+ .size = size,
+ };
+ int ret;
+
+ do {
+ ret = __vcpu_ioctl(vcpu, KVM_PRE_FAULT_MEMORY, &range);
+ } while ((!ret && range.size) ||
+ (ret < 0 && (errno == EINTR || errno == EAGAIN)));
+
+ TEST_ASSERT(!ret, "KVM_PRE_FAULT_MEMORY failed, ret: %d errno: %d",
+ ret, errno);
+ TEST_ASSERT_EQ(range.size, 0);
+}
+
+int main(void)
+{
+ struct kvm_vcpu_init init;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ struct ucall uc;
+ u64 npages;
+
+ TEST_REQUIRE(test_supports_el2());
+ TEST_REQUIRE(kvm_check_cap(KVM_CAP_PRE_FAULT_MEMORY));
+
+ vm = vm_create(1);
+
+ kvm_get_default_vcpu_target(vm, &init);
+ init.features[0] |= BIT(KVM_ARM_VCPU_HAS_EL2);
+ vcpu = aarch64_vcpu_add(vm, 0, &init, guest_code);
+ kvm_arch_vm_finalize_vcpus(vm);
+
+ npages = TEST_MEM_SIZE / vm->page_size;
+ vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, TEST_MEM_GPA,
+ TEST_MEM_SLOT, npages, 0);
+ virt_map(vm, TEST_MEM_GPA, TEST_MEM_GPA, npages);
+
+ /* Run the guest until it has ERET'd from vEL2 to vEL1. */
+ vcpu_run(vcpu);
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_SYNC:
+ TEST_ASSERT_EQ(uc.args[1], 1);
+ break;
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ default:
+ TEST_FAIL("Unhandled ucall: %ld", uc.cmd);
+ }
+
+ /*
+ * The vCPU's last-run context is vEL1, so its hw_mmu is a shadow
+ * stage-2 MMU.
+ *
+ * Pre-faulting must ignore that and populate the canonical stage-2.
+ */
+ pre_fault(vcpu, TEST_MEM_GPA, TEST_MEM_SIZE);
+
+ /* Resume at vEL1 and touch the prefaulted range. */
+ vcpu_run(vcpu);
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_DONE:
+ break;
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ default:
+ TEST_FAIL("Unhandled ucall: %ld", uc.cmd);
+ }
+
+ kvm_vm_free(vm);
+ return 0;
+}
diff --git a/tools/testing/selftests/kvm/arm64/sea_to_user.c b/tools/testing/selftests/kvm/arm64/sea_to_user.c
index e96d8982c28b..d4af6e0eed28 100644
--- a/tools/testing/selftests/kvm/arm64/sea_to_user.c
+++ b/tools/testing/selftests/kvm/arm64/sea_to_user.c
@@ -81,25 +81,46 @@ static u64 translate_hva_to_hpa(unsigned long hva)
static void write_einj_entry(const char *einj_path, u64 val)
{
- char cmd[256] = {0};
- FILE *cmdfile = NULL;
+ char buf[32];
+ int fd, len, ret;
- sprintf(cmd, "echo %#lx > %s", val, einj_path);
- cmdfile = popen(cmd, "r");
+ fd = open(einj_path, O_WRONLY);
+ if (fd < 0)
+ ksft_exit_fail_perror(einj_path);
- if (pclose(cmdfile) == 0)
- ksft_print_msg("echo %#lx > %s - done\n", val, einj_path);
- else
- ksft_exit_fail_perror("Failed to write EINJ entry");
+ len = snprintf(buf, sizeof(buf), "%#lx\n", val);
+ ret = write(fd, buf, len);
+ if (ret != len)
+ ksft_exit_fail_perror(einj_path);
+
+ close(fd);
+ ksft_print_msg("%#lx > %s - done\n", val, einj_path);
}
static void inject_uer(u64 hpa)
{
- if (access("/sys/firmware/acpi/tables/EINJ", R_OK) == -1)
- ksft_test_result_skip("EINJ table no available in firmware");
+ int fd;
- if (access(EINJ_ETYPE, R_OK | W_OK) == -1)
- ksft_test_result_skip("EINJ module probably not loaded?");
+ /*
+ * EINJ is exposed only through debugfs, and opening it tells us why it
+ * is unusable far more reliably than access()-ing the ACPI EINJ table:
+ * reading that table needs CAP_SYS_ADMIN, so access() gives a false
+ * negative for unprivileged runs. Open the injection interface and let
+ * errno say whether the test cannot run (skip) or genuinely failed.
+ */
+ fd = open(EINJ_ETYPE, O_WRONLY);
+ if (fd < 0) {
+ switch (errno) {
+ case ENOENT:
+ ksft_exit_skip("need CONFIG_ACPI_APEI_EINJ and firmware EINJ support\n");
+ case EACCES:
+ case EPERM:
+ ksft_exit_skip("EINJ requires running as root\n");
+ default:
+ ksft_exit_fail_perror(EINJ_ETYPE);
+ }
+ }
+ close(fd);
write_einj_entry(EINJ_ETYPE, ERROR_TYPE_MEMORY_UER);
write_einj_entry(EINJ_FLAGS, MASK_MEMORY_UER);
@@ -194,13 +215,33 @@ static void run_vm(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
ksft_print_msg("Dump kvm_run info about KVM_EXIT_%s\n",
exit_reason_str(run->exit_reason));
+
+ /*
+ * The guest's read of the injected location is expected to trap to KVM
+ * as an SEA. If it does not, the injected error was never placed as
+ * consumable poison: some firmware honours EINJ's notrigger request by
+ * arming the poison only as part of the (now skipped) trigger step, so
+ * nothing is left in memory for the guest to consume. The guest then
+ * reads back the sentinel value and reports it via GUEST_FAIL, which
+ * arm64 delivers as a ucall over MMIO (hence a KVM_EXIT_MMIO here).
+ * Treat that as "this platform cannot host the test" and skip, matching
+ * the requirement documented at the top of this file, rather than
+ * failing on a hardware/firmware limitation the test cannot control.
+ */
+ if (run->exit_reason != KVM_EXIT_ARM_SEA &&
+ get_ucall(vcpu, &uc) == UCALL_ABORT) {
+ ksft_print_msg("Guest consumed no SEA: %s", uc.buffer);
+ ksft_exit_skip("EINJ notrigger placed no consumable poison on this platform\n");
+ }
+
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_ARM_SEA);
+
+ /* arm_sea holds valid data only for a KVM_EXIT_ARM_SEA exit. */
ksft_print_msg("kvm_run.arm_sea: esr=%#llx, flags=%#llx\n",
run->arm_sea.esr, run->arm_sea.flags);
ksft_print_msg("kvm_run.arm_sea: gva=%#llx, gpa=%#llx\n",
run->arm_sea.gva, run->arm_sea.gpa);
- TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_ARM_SEA);
-
esr = run->arm_sea.esr;
TEST_ASSERT_EQ(ESR_ELx_EC(esr), ESR_ELx_EC_DABT_LOW);
TEST_ASSERT_EQ(esr & ESR_ELx_FSC_TYPE, ESR_ELx_FSC_EXTABT);
@@ -260,6 +301,12 @@ static struct kvm_vm *vm_create_with_sea_handler(struct kvm_vcpu **vcpu)
alignment = max(backing_page_size, guest_page_size);
num_guest_pages = VM_MEM_SIZE / guest_page_size;
+ /*
+ * The region is backed by 1GB hugepages; skip gracefully rather than
+ * failing with mmap() -ENOMEM if the host has none reserved.
+ */
+ TEST_REQUIRE(get_free_hugepages(backing_page_size) >= VM_MEM_SIZE);
+
vm = __vm_create_with_one_vcpu(vcpu, num_guest_pages, guest_code);
vm_init_descriptor_tables(vm);
vcpu_init_descriptor_tables(*vcpu);
diff --git a/tools/testing/selftests/kvm/arm64/set_id_regs.c b/tools/testing/selftests/kvm/arm64/set_id_regs.c
index 7429a1055df5..10849d21c0dd 100644
--- a/tools/testing/selftests/kvm/arm64/set_id_regs.c
+++ b/tools/testing/selftests/kvm/arm64/set_id_regs.c
@@ -13,6 +13,7 @@
#include "kvm_util.h"
#include "processor.h"
#include "test_util.h"
+#include "vgic.h"
#include <linux/bitfield.h>
enum ftr_type {
@@ -803,6 +804,107 @@ static void test_reset_preserves_id_regs(struct kvm_vcpu *vcpu)
ksft_test_result_pass("%s\n", __func__);
}
+/*
+ * ID registers must stay immutable even when a vCPU's first KVM_RUN fails
+ * after finalization but before KVM_ARCH_FLAG_HAS_RAN_ONCE is set.
+ */
+static void test_idreg_frozen_after_failed_run(void)
+{
+ static const u32 imp_id_regs[] = {
+ SYS_MIDR_EL1,
+ SYS_REVIDR_EL1,
+ SYS_AIDR_EL1,
+ };
+ struct kvm_vcpu_init init;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ int r;
+
+ if (!kvm_has_cap(KVM_CAP_ARM_PMU_V3)) {
+ ksft_print_msg("PMUv3 unsupported, cannot fail the first run\n");
+ ksft_test_result_skip("%s\n", __func__);
+ return;
+ }
+
+ /* Skip the default vGIC so the KVM_CREATE_DEVICE gate is reachable. */
+ test_disable_default_vgic();
+
+ vm = vm_create(1);
+ vm_enable_cap(vm, KVM_CAP_ARM_WRITABLE_IMP_ID_REGS, 0);
+ kvm_get_default_vcpu_target(vm, &init);
+ init.features[0] |= (1 << KVM_ARM_VCPU_PMU_V3);
+ vcpu = aarch64_vcpu_add(vm, 0, &init, guest_code);
+ kvm_arch_vm_finalize_vcpus(vm);
+
+ /*
+ * A PMUv3 vCPU left without PMU init is rejected by
+ * kvm_arm_pmu_v3_enable(), which runs after sysreg finalization.
+ */
+ r = _vcpu_run(vcpu);
+ TEST_ASSERT(r < 0 && errno == EINVAL,
+ "first KVM_RUN should fail post-finalization: r=%d errno=%d",
+ r, errno);
+
+ /*
+ * Feature ID registers: use values that would have been accepted before
+ * finalization, so that a rejection means the registers are final
+ * rather than the value being invalid.
+ */
+ for (int i = 0; i < ARRAY_SIZE(test_regs); i++) {
+ const struct reg_ftr_bits *ftr_bits = test_regs[i].ftr_bits;
+ u64 reg = KVM_ARM64_SYS_REG(test_regs[i].reg);
+ u64 val = vcpu_get_reg(vcpu, reg);
+
+ for (int j = 0; ftr_bits[j].type != FTR_END; j++) {
+ u64 ftr = (val & ftr_bits[j].mask) >> ftr_bits[j].shift;
+ u64 safe = get_safe_value(&ftr_bits[j], ftr);
+ u64 new_val;
+
+ if (safe == ftr)
+ continue;
+
+ new_val = (val & ~ftr_bits[j].mask) |
+ (safe << ftr_bits[j].shift);
+
+ r = __vcpu_set_reg(vcpu, reg, new_val);
+ TEST_ASSERT(r < 0 && errno == EBUSY,
+ "%s write after failed first run: r=%d errno=%d",
+ ftr_bits[j].name, r, errno);
+ TEST_ASSERT_EQ(vcpu_get_reg(vcpu, reg), val);
+ }
+
+ /* A write matching the finalized value is still accepted. */
+ vcpu_set_reg(vcpu, reg, val);
+ }
+
+ /*
+ * The VM-wide implementation ID registers are gated separately. Bit 0
+ * is within the writable mask of all three, so flipping it is a change
+ * KVM would otherwise accept.
+ */
+ for (int i = 0; i < ARRAY_SIZE(imp_id_regs); i++) {
+ u64 reg = KVM_ARM64_SYS_REG(imp_id_regs[i]);
+ u64 val = vcpu_get_reg(vcpu, reg);
+
+ r = __vcpu_set_reg(vcpu, reg, val ^ 1);
+ TEST_ASSERT(r < 0 && errno == EBUSY,
+ "implementation ID reg write after failed first run: r=%d errno=%d",
+ r, errno);
+ TEST_ASSERT_EQ(vcpu_get_reg(vcpu, reg), val);
+ }
+
+ /* Creating an in-kernel irqchip would change the ID registers too. */
+ if (kvm_supports_vgic_v3()) {
+ r = __kvm_create_device(vm, KVM_DEV_TYPE_ARM_VGIC_V3);
+ TEST_ASSERT(r < 0 && errno == EBUSY,
+ "vGIC creation after failed first run: r=%d errno=%d",
+ r, errno);
+ }
+
+ kvm_vm_free(vm);
+ ksft_test_result_pass("%s\n", __func__);
+}
+
int main(void)
{
struct kvm_vcpu *vcpu;
@@ -828,7 +930,7 @@ int main(void)
ksft_print_header();
- test_cnt = 3 + MPAM_IDREG_TEST + MTE_IDREG_TEST;
+ test_cnt = 4 + MPAM_IDREG_TEST + MTE_IDREG_TEST;
for (i = 0; i < ARRAY_SIZE(test_regs); i++)
for (j = 0; test_regs[i].ftr_bits[j].type != FTR_END; j++)
test_cnt++;
@@ -847,5 +949,7 @@ int main(void)
kvm_vm_free(vm);
+ test_idreg_frozen_after_failed_run();
+
ksft_finished();
}
diff --git a/tools/testing/selftests/kvm/arm64/vgic_init.c b/tools/testing/selftests/kvm/arm64/vgic_init.c
index 47e34b43afb2..5a30f3cb039b 100644
--- a/tools/testing/selftests/kvm/arm64/vgic_init.c
+++ b/tools/testing/selftests/kvm/arm64/vgic_init.c
@@ -5,6 +5,7 @@
* Copyright (C) 2020, Red Hat, Inc.
*/
#include <linux/kernel.h>
+#include <linux/sizes.h>
#include <sys/syscall.h>
#include <asm/kvm.h>
#include <asm/kvm_para.h>
@@ -13,12 +14,21 @@
#include "test_util.h"
#include "kvm_util.h"
+#include "gic.h"
#include "processor.h"
#include "vgic.h"
#include "gic_v3.h"
#define NR_VCPUS 4
+#define REDIST_RETRY_REGION0_BASE GICR_BASE_GPA
+#define REDIST_RETRY_REGION1_BASE \
+ (REDIST_RETRY_REGION0_BASE + 2 * KVM_VGIC_V3_REDIST_SIZE)
+#define REDIST_RETRY_DIST_BASE \
+ (REDIST_RETRY_REGION1_BASE + KVM_VGIC_V3_REDIST_SIZE)
+#define REDIST_RETRY_REGION2_BASE \
+ (REDIST_RETRY_DIST_BASE + KVM_VGIC_V3_DIST_SIZE)
+
#define REG_OFFSET(vcpu, offset) (((u64)vcpu << 32) | offset)
#define VGIC_DEV_IS_V2(_d) ((_d) == KVM_DEV_TYPE_ARM_VGIC_V2)
@@ -65,6 +75,23 @@ static void guest_code(void)
GUEST_DONE();
}
+static void guest_check_redist_retry(void)
+{
+ unsigned int i;
+
+ /* The first three redistributors span adjacent regions 0 and 1. */
+ for (i = 0; i < NR_VCPUS; i++) {
+ u64 base = i < 3 ? REDIST_RETRY_REGION0_BASE +
+ i * KVM_VGIC_V3_REDIST_SIZE :
+ REDIST_RETRY_REGION2_BASE;
+ u64 typer = readq((void *)(unsigned long)(base + GICR_TYPER));
+
+ GUEST_ASSERT_EQ(GICR_TYPER_CPU_NUMBER(typer), i);
+ }
+
+ GUEST_DONE();
+}
+
/* we don't want to assert on run execution, hence that helper */
static int run_vcpu(struct kvm_vcpu *vcpu)
{
@@ -73,6 +100,7 @@ static int run_vcpu(struct kvm_vcpu *vcpu)
static struct vm_gic vm_gic_create_with_vcpus(u32 gic_dev_type,
u32 nr_vcpus,
+ void *guest_code,
struct kvm_vcpu *vcpus[])
{
struct vm_gic v;
@@ -338,7 +366,7 @@ static void test_vgic_then_vcpus(u32 gic_dev_type)
struct vm_gic v;
int ret, i;
- v = vm_gic_create_with_vcpus(gic_dev_type, 1, vcpus);
+ v = vm_gic_create_with_vcpus(gic_dev_type, 1, guest_code, vcpus);
subtest_dist_rdist(&v);
@@ -359,7 +387,8 @@ static void test_vcpus_then_vgic(u32 gic_dev_type)
struct vm_gic v;
int ret;
- v = vm_gic_create_with_vcpus(gic_dev_type, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(gic_dev_type, NR_VCPUS, guest_code,
+ vcpus);
subtest_dist_rdist(&v);
@@ -411,7 +440,8 @@ static void test_v3_new_redist_regions(void)
u64 addr;
int ret;
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
subtest_v3_redist_regions(&v);
kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
@@ -422,7 +452,8 @@ static void test_v3_new_redist_regions(void)
/* step2 */
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
subtest_v3_redist_regions(&v);
addr = REDIST_REGION_ATTR_ADDR(1, 0x280000, 0, 2);
@@ -436,7 +467,8 @@ static void test_v3_new_redist_regions(void)
/* step 3 */
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
subtest_v3_redist_regions(&v);
ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
@@ -457,6 +489,70 @@ static void test_v3_new_redist_regions(void)
vm_gic_destroy(&v);
}
+static void test_v3_redist_region_retry(void)
+{
+ struct kvm_vcpu *vcpus[NR_VCPUS];
+ struct vm_gic v;
+ struct ucall uc;
+ u64 addr;
+ int ret;
+
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_check_redist_retry, vcpus);
+
+ addr = REDIST_REGION_ATTR_ADDR(2, REDIST_RETRY_REGION0_BASE, 0, 0);
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION, &addr);
+
+ addr = REDIST_REGION_ATTR_ADDR(1, REDIST_RETRY_REGION1_BASE, 0, 1);
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION, &addr);
+
+ addr = REDIST_RETRY_DIST_BASE;
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_DIST, &addr);
+
+ addr = REDIST_REGION_ATTR_ADDR(1, REDIST_RETRY_DIST_BASE, 0, 2);
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION,
+ &addr);
+ TEST_ASSERT(ret && errno == EINVAL,
+ "register redist region colliding with dist");
+
+ addr = REDIST_REGION_ATTR_ADDR(1, REDIST_RETRY_REGION2_BASE, 0, 2);
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION, &addr);
+
+ virt_map(v.vm, REDIST_RETRY_REGION0_BASE, REDIST_RETRY_REGION0_BASE,
+ vm_calc_num_guest_pages(v.vm->mode,
+ 3 * KVM_VGIC_V3_REDIST_SIZE));
+ virt_map(v.vm, REDIST_RETRY_REGION2_BASE, REDIST_RETRY_REGION2_BASE,
+ vm_calc_num_guest_pages(v.vm->mode,
+ KVM_VGIC_V3_REDIST_SIZE));
+
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ vcpu_run(vcpus[0]);
+ switch (get_ucall(vcpus[0], &uc)) {
+ case UCALL_DONE:
+ break;
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ case UCALL_NONE:
+ if (vcpus[0]->run->exit_reason == KVM_EXIT_MMIO)
+ TEST_FAIL("Unexpected MMIO exit at 0x%llx",
+ vcpus[0]->run->mmio.phys_addr);
+ fallthrough;
+ default:
+ TEST_FAIL("Unexpected ucall %lu, exit_reason %u",
+ uc.cmd, vcpus[0]->run->exit_reason);
+ }
+
+ vm_gic_destroy(&v);
+}
+
static void test_v3_typer_accesses(void)
{
struct vm_gic v;
@@ -608,7 +704,8 @@ static void test_v3_redist_ipa_range_check_at_vcpu_run(void)
int ret, i;
u64 addr;
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, 1, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, 1, guest_code,
+ vcpus);
/* Set space for 3 redists, we have 1 vcpu, so this succeeds. */
addr = max_phys_size - (3 * 2 * 0x10000);
@@ -641,7 +738,8 @@ static void test_v3_its_region(void)
u64 addr;
int its_fd, ret;
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
its_fd = kvm_create_device(v.vm, KVM_DEV_TYPE_ARM_VGIC_ITS);
addr = 0x401000;
@@ -684,7 +782,8 @@ static void test_v3_nassgicap(void)
u32 typer2;
int ret;
- vm = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ vm = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
kvm_device_attr_get(vm.gic_fd, KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
GICD_TYPER2, &typer2);
has_nassgicap = typer2 & GICD_TYPER2_nASSGIcap;
@@ -978,6 +1077,7 @@ void run_tests(u32 gic_dev_type)
if (VGIC_DEV_IS_V3(gic_dev_type)) {
test_v3_new_redist_regions();
+ test_v3_redist_region_retry();
test_v3_typer_accesses();
test_v3_last_bit_redist_regions();
test_v3_last_bit_single_rdist();
diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
index 96cfd6bb32f6..d5df742b2300 100644
--- a/tools/testing/selftests/kvm/arm64/vgic_v5.c
+++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
@@ -13,6 +13,32 @@
#include "vgic.h"
#define NR_VCPUS 1
+#define VGIC_V5_LPI_NR_VCPUS 2
+#define VGIC_V5_SPARSE_NR_VCPUS 2
+#define VGIC_V5_SPARSE_VCPU0_ID 17
+#define VGIC_V5_SPARSE_VCPU1_ID 3
+#define VGIC_V5_LPI_MEMSLOT 1
+#define VGIC_V5_NR_PRIVATE_IRQS 64
+#define VGIC_V5_DEFAULT_NR_SPIS 32
+#define VGIC_V5_MAX_NR_SPIS BIT(10)
+#define VGIC_V5_IRS_SIZE 0x20000
+#define VGIC_V5_IRS_WAIT_RETRIES 1000000
+#define VGIC_V5_LPI_IST_BASE_GPA (GICV5_IRS_CONFIG_BASE_GPA + VGIC_V5_IRS_SIZE)
+#define VGIC_V5_LPI_IST_SIZE 0x10000
+#define LPI_TEST_TO_VPE1 0
+#define LPI_TEST_TO_VPE0 1
+#define RESTORE_TEST_LPI_PENDING 0
+#define RESTORE_TEST_LPI_ENABLED 1
+#define RESTORE_TEST_SPI_PRIO_HIGH 0
+#define RESTORE_TEST_SPI_PRIO_LOW 1
+#define RESTORE_TEST_SPI_PENDING_DISABLED 2
+#define RESTORE_TEST_SPI_ENABLED 3
+#define VGIC_V5_IST_PRIO_HIGH 0x08
+#define VGIC_V5_IST_PRIO_LOW 0x10
+
+static u64 max_phys_size;
+
+#define VGIC_V5_TEST_IST_BASER_GPA 0x10000000ULL
struct vm_gic {
struct kvm_vm *vm;
@@ -24,8 +50,200 @@ struct vm_gic {
#define GUEST_CMD_IRQ_DIEOI 11
#define GUEST_CMD_IS_AWAKE 12
#define GUEST_CMD_IS_READY 13
+#define GUEST_CMD_LPI_SENT 14
+#define GUEST_CMD_LPI_REPLIED 15
+#define GUEST_CMD_IST_SOURCE_READY 16
+#define GUEST_CMD_IST_SPI_INJECT_READY 17
+#define GUEST_CMD_IST_LPI_INJECT_READY 18
+
+static struct kvm_vgic_v5_ist vgic_v5_ist_attr(void *spi_ist, size_t spi_size,
+ void *lpi_ist, size_t lpi_size)
+{
+ return (struct kvm_vgic_v5_ist) {
+ .spi_ist_addr = (uintptr_t)spi_ist,
+ .spi_ist_size = spi_size,
+ .lpi_ist_addr = (uintptr_t)lpi_ist,
+ .lpi_ist_size = lpi_size,
+ };
+}
+
+static u32 spi_line_expected;
+static bool spi_line_level_sensitive;
+static bool lpi_ist_ready;
+static u32 ist_restore_irq_count[VGIC_V5_LPI_NR_VCPUS];
+
+static u64 gicv5_hwirq(u32 type, u32 intid)
+{
+ return FIELD_PREP(GICV5_HWIRQ_TYPE, type) |
+ FIELD_PREP(GICV5_HWIRQ_ID, intid);
+}
+
+static u64 gicv5_lpi_hwirq(u32 lpi)
+{
+ return gicv5_hwirq(GICV5_HWIRQ_TYPE_LPI, lpi);
+}
+
+static u64 gicv5_spi_hwirq(u32 spi)
+{
+ return gicv5_hwirq(GICV5_HWIRQ_TYPE_SPI, spi);
+}
+
+static void gicv5_setup_and_enable_hwirq(u64 hwirq, u32 target_vpe)
+{
+ u64 val;
+
+ val = hwirq | FIELD_PREP(GICV5_GIC_CDPRI_PRIORITY_MASK,
+ GICV5_IRQ_DEFAULT_PRI);
+ gic_insn(val, CDPRI);
+
+ val = hwirq | FIELD_PREP(GICV5_GIC_CDAFF_IAFFID_MASK, target_vpe);
+ gic_insn(val, CDAFF);
+
+ gic_insn(hwirq, CDEN);
+}
+
+static void gicv5_set_hwirq_priority(u64 hwirq, u32 priority)
+{
+ u64 val;
+
+ val = hwirq | FIELD_PREP(GICV5_GIC_CDPRI_PRIORITY_MASK, priority);
+ gic_insn(val, CDPRI);
+}
+
+static void gicv5_set_hwirq_affinity(u64 hwirq, u32 target_vpe)
+{
+ u64 val;
+
+ val = hwirq | FIELD_PREP(GICV5_GIC_CDAFF_IAFFID_MASK, target_vpe);
+ gic_insn(val, CDAFF);
+}
+
+static void gicv5_enable_hwirq(u64 hwirq)
+{
+ gic_insn(hwirq, CDEN);
+}
+
+static void gicv5_enable_spi(u32 spi, u32 target_vpe)
+{
+ gicv5_setup_and_enable_hwirq(gicv5_spi_hwirq(spi), target_vpe);
+}
+
+static void gicv5_enable_lpi(u32 lpi, u32 target_vpe)
+{
+ gicv5_setup_and_enable_hwirq(gicv5_lpi_hwirq(lpi), target_vpe);
+}
+
+static void gicv5_send_lpi(u32 lpi)
+{
+ u64 hwirq = gicv5_lpi_hwirq(lpi);
+
+ gic_insn(hwirq | GICV5_GIC_CDPEND_PENDING_MASK, CDPEND);
+}
+
+static void gicv5_wait_for_irs_idle(u32 reg, u32 idle)
+{
+ int i;
+
+ for (i = 0; i < VGIC_V5_IRS_WAIT_RETRIES; i++) {
+ if (readl(GICV5_IRS_CONFIG_BASE_GVA + reg) & idle)
+ return;
+
+ cpu_relax();
+ }
+
+ GUEST_FAIL("IRS operation did not become idle");
+}
+
+static void gicv5_configure_spi(u32 spi, bool level)
+{
+ u32 val;
+
+ val = FIELD_PREP(GICV5_IRS_SPI_SELR_ID, spi);
+ writel(val, GICV5_IRS_CONFIG_BASE_GVA + GICV5_IRS_SPI_SELR);
+ gicv5_wait_for_irs_idle(GICV5_IRS_SPI_STATUSR,
+ GICV5_IRS_SPI_STATUSR_IDLE);
+
+ val = level ? GICV5_IRS_SPI_CFGR_TM : 0;
+ writel(val, GICV5_IRS_CONFIG_BASE_GVA + GICV5_IRS_SPI_CFGR);
+ gicv5_wait_for_irs_idle(GICV5_IRS_SPI_STATUSR,
+ GICV5_IRS_SPI_STATUSR_IDLE);
+}
+
+static u32 gicv5_lpi_istsz(u32 idr2, u32 lpi_id_bits)
+{
+ if (!(idr2 & GICV5_IRS_IDR2_ISTMD))
+ return GICV5_IRS_IST_CFGR_ISTSZ_4;
+
+ if (lpi_id_bits >= FIELD_GET(GICV5_IRS_IDR2_ISTMD_SZ, idr2))
+ return GICV5_IRS_IST_CFGR_ISTSZ_16;
+
+ return GICV5_IRS_IST_CFGR_ISTSZ_8;
+}
+
+static void gicv5_configure_lpi_ist(void)
+{
+ u32 idr2, min_lpi_id_bits, lpi_id_bits, id_bits, istsz;
+ u64 val;
+
+ idr2 = readl(GICV5_IRS_CONFIG_BASE_GVA + GICV5_IRS_IDR2);
+ GUEST_ASSERT(idr2 & GICV5_IRS_IDR2_LPI);
+
+ min_lpi_id_bits = FIELD_GET(GICV5_IRS_IDR2_MIN_LPI_ID_BITS, idr2);
+ id_bits = FIELD_GET(GICV5_IRS_IDR2_ID_BITS, idr2);
+ /* Default to 32 LPIs, unless the min requirement is higher */
+ lpi_id_bits = max(5U, min_lpi_id_bits);
+ GUEST_ASSERT(lpi_id_bits <= id_bits);
+
+ istsz = gicv5_lpi_istsz(idr2, lpi_id_bits);
+ /*
+ * We allocate a fixed-size IST buffer on the host to avoid dynamic
+ * allocation from the guest. Make sure it fits the linear IST described
+ * by IDR2 before programming IRS_IST_BASER.
+ */
+ GUEST_ASSERT((BIT_ULL(lpi_id_bits) << (istsz + 2)) <= VGIC_V5_LPI_IST_SIZE);
+
+ val = FIELD_PREP(GICV5_IRS_IST_CFGR_LPI_ID_BITS, lpi_id_bits) |
+ FIELD_PREP(GICV5_IRS_IST_CFGR_ISTSZ, istsz);
+ writel(val, GICV5_IRS_CONFIG_BASE_GVA + GICV5_IRS_IST_CFGR);
+
+ val = FIELD_PREP(GICV5_IRS_IST_BASER_ADDR_MASK,
+ VGIC_V5_LPI_IST_BASE_GPA >> GICV5_IRS_IST_BASER_ADDR_SHIFT);
+ val |= GICV5_IRS_IST_BASER_VALID;
+ writeq(val, GICV5_IRS_CONFIG_BASE_GVA + GICV5_IRS_IST_BASER);
+
+ GUEST_ASSERT_EQ(readl(GICV5_IRS_CONFIG_BASE_GVA + GICV5_IRS_IST_STATUSR),
+ GICV5_IRS_IST_STATUSR_IDLE);
+
+ /* The address is immutable while the BASER is valid. */
+ writeq(val ^ FIELD_PREP(GICV5_IRS_IST_BASER_ADDR_MASK, 1),
+ GICV5_IRS_CONFIG_BASE_GVA + GICV5_IRS_IST_BASER);
+ GUEST_ASSERT_EQ(readq(GICV5_IRS_CONFIG_BASE_GVA +
+ GICV5_IRS_IST_BASER), val);
+}
+
+static void gicv5_enable_irs(void)
+{
+ writel(GICV5_IRS_CR0_IRSEN,
+ GICV5_IRS_CONFIG_BASE_GVA + GICV5_IRS_CR0);
+ gicv5_wait_for_irs_idle(GICV5_IRS_CR0, GICV5_IRS_CR0_IDLE);
+}
+
+static void gicv5_configure_test_lpis(void)
+{
+ gicv5_enable_lpi(LPI_TEST_TO_VPE1, 1);
+ gicv5_enable_lpi(LPI_TEST_TO_VPE0, 0);
+}
+
+static void gicv5_set_pcr(u32 priority)
+{
+ u64 pcr;
-static void guest_irq_handler(struct ex_regs *regs)
+ pcr = FIELD_PREP(ICC_PCR_EL1_PRIORITY, priority);
+ write_sysreg_s(pcr, SYS_ICC_PCR_EL1);
+ isb();
+}
+
+static void guest_ppi_irq_handler(struct ex_regs *regs)
{
bool valid;
u32 hwirq;
@@ -78,9 +296,284 @@ static void guest_code(void)
GUEST_SYNC(GUEST_CMD_IS_READY);
/* Loop forever waiting for interrupts */
- while (1);
+ for (;;)
+ cpu_relax();
+}
+
+static void guest_iaffid_code(void)
+{
+ GUEST_SYNC(FIELD_GET(ICC_IAFFIDR_EL1_IAFFID,
+ read_sysreg_s(SYS_ICC_IAFFIDR_EL1)));
+ GUEST_DONE();
+}
+
+static void guest_spi_irq_handler(struct ex_regs *regs)
+{
+ bool valid;
+ u32 hwirq;
+ u64 ia;
+
+ ia = gicr_insn(CDIA);
+ valid = GICV5_GICR_CDIA_VALID(ia);
+
+ if (!valid)
+ return;
+
+ gsb_ack();
+ isb();
+
+ hwirq = FIELD_GET(GICV5_GICR_CDIA_INTID, ia);
+
+ GUEST_ASSERT_EQ(hwirq, gicv5_spi_hwirq(READ_ONCE(spi_line_expected)));
+
+ gic_insn(hwirq, CDDI);
+ gic_insn(0, CDEOI);
+
+ GUEST_DONE();
+}
+
+static void guest_spi_line_code(void)
+{
+ local_irq_disable();
+
+ gicv5_enable_irs();
+ gicv5_cpu_enable_interrupts();
+ gicv5_configure_spi(READ_ONCE(spi_line_expected),
+ READ_ONCE(spi_line_level_sensitive));
+ gicv5_enable_spi(READ_ONCE(spi_line_expected), 0);
+
+ local_irq_enable();
+
+ GUEST_SYNC(GUEST_CMD_IS_READY);
+
+ while (1)
+ wfi();
+}
+
+static void guest_lpi_irq_handler(struct ex_regs *regs)
+{
+ u32 vcpu_id = guest_get_vcpuid();
+ u32 expected_lpi = vcpu_id ? LPI_TEST_TO_VPE1 : LPI_TEST_TO_VPE0;
+ u64 expected_hwirq = gicv5_lpi_hwirq(expected_lpi);
+ u32 hwirq;
+ u64 ia;
+
+ ia = gicr_insn(CDIA);
+ if (!GICV5_GICR_CDIA_VALID(ia))
+ return;
+
+ gsb_ack();
+ isb();
+
+ hwirq = FIELD_GET(GICV5_GICR_CDIA_INTID, ia);
+ GUEST_ASSERT_EQ(hwirq, expected_hwirq);
+
+ gic_insn(hwirq, CDDI);
+ gic_insn(0, CDEOI);
+
+ if (vcpu_id) {
+ gicv5_send_lpi(LPI_TEST_TO_VPE0);
+ GUEST_SYNC(GUEST_CMD_LPI_REPLIED);
+ while (1)
+ wfi();
+ }
+
+ GUEST_DONE();
+}
+
+static void guest_unexpected_irq_handler(struct ex_regs *regs)
+{
+ GUEST_FAIL("Unexpected IRQ");
+}
+
+static void guest_lpi_code(void)
+{
+ u32 vcpu_id = guest_get_vcpuid();
+
+ local_irq_disable();
+
+ if (!vcpu_id) {
+ gicv5_enable_irs();
+ gicv5_configure_lpi_ist();
+ gicv5_configure_test_lpis();
+ WRITE_ONCE(lpi_ist_ready, true);
+ }
+
+ /* Go bang if we run VCPU1 before VCPU0 */
+ GUEST_ASSERT(READ_ONCE(lpi_ist_ready));
+
+ gicv5_cpu_enable_interrupts();
+ local_irq_enable();
+
+ GUEST_SYNC(GUEST_CMD_IS_READY);
+
+ if (!vcpu_id) {
+ gicv5_send_lpi(LPI_TEST_TO_VPE1);
+ GUEST_SYNC(GUEST_CMD_LPI_SENT);
+ }
+
+ while (1)
+ wfi();
}
+static void restore_test_setup_hwirq(u64 hwirq, u32 target_vpe,
+ u32 priority, bool enable)
+{
+ gicv5_set_hwirq_priority(hwirq, priority);
+ gicv5_set_hwirq_affinity(hwirq, target_vpe);
+
+ if (enable)
+ gicv5_enable_hwirq(hwirq);
+}
+
+/*
+ * The source VM configures SPI and LPI state for the IST save/restore test.
+ * It leaves interrupts disabled, allowing userspace to save the state before
+ * the restore VM enables interrupts and checks delivery.
+ */
+static void guest_ist_source_code(void)
+{
+ local_irq_disable();
+
+ gicv5_enable_irs();
+ gicv5_configure_lpi_ist();
+
+ restore_test_setup_hwirq(gicv5_lpi_hwirq(RESTORE_TEST_LPI_PENDING),
+ 1, VGIC_V5_IST_PRIO_HIGH, true);
+ restore_test_setup_hwirq(gicv5_lpi_hwirq(RESTORE_TEST_LPI_ENABLED),
+ 1, VGIC_V5_IST_PRIO_HIGH, true);
+ restore_test_setup_hwirq(gicv5_spi_hwirq(RESTORE_TEST_SPI_PRIO_HIGH),
+ 0, VGIC_V5_IST_PRIO_HIGH, true);
+ restore_test_setup_hwirq(gicv5_spi_hwirq(RESTORE_TEST_SPI_PRIO_LOW),
+ 0, VGIC_V5_IST_PRIO_LOW, true);
+ restore_test_setup_hwirq(gicv5_spi_hwirq(RESTORE_TEST_SPI_ENABLED),
+ 0, VGIC_V5_IST_PRIO_HIGH, true);
+ restore_test_setup_hwirq(gicv5_spi_hwirq(RESTORE_TEST_SPI_PENDING_DISABLED),
+ 0, VGIC_V5_IST_PRIO_HIGH, false);
+
+ gicv5_send_lpi(RESTORE_TEST_LPI_PENDING);
+
+ GUEST_SYNC(GUEST_CMD_IST_SOURCE_READY);
+
+ while (1)
+ wfi();
+}
+
+/*
+ * This is the restore VM for the IST save/restore test. The IRS regs and ISTs
+ * have been restored before this code runs. Based on which VPE is running, a
+ * different set of interrupts is expected. We check that we get what we
+ * expected before terminating the test.
+ *
+ * The expected interrupts for VPE0 are:
+ * - High-priority SPI, pending and enabled before save.
+ * - Low-priority SPI, pending and enabled before save. This is only delivered
+ * after the guest lowers the masked priority via ICC_PCR_EL1.
+ * - SPI that was pending but disabled before save. This is only delivered
+ * after the guest enables it after restore.
+ * - SPI that was enabled but not pending before save. Userspace injects this
+ * after restore to check that the enable state and affinity were restored.
+ *
+ * The expected interrupts for VPE1 are:
+ * - LPI that was pending and enabled before save.
+ * - LPI that was enabled but not pending before save. The guest injects this
+ * after restore to check that the enable state and affinity were restored.
+ */
+static void guest_ist_restore_irq_handler(struct ex_regs *regs)
+{
+ u32 vcpu_id = guest_get_vcpuid();
+ u32 count = ist_restore_irq_count[vcpu_id];
+ u64 hwirq, expected_hwirq = 0;
+ u64 ia;
+
+ ia = gicr_insn(CDIA);
+ if (!GICV5_GICR_CDIA_VALID(ia))
+ return;
+
+ gsb_ack();
+ isb();
+
+ hwirq = FIELD_GET(GICV5_GICR_CDIA_INTID, ia);
+
+ if (!vcpu_id) {
+ switch (count) {
+ case 0:
+ expected_hwirq = gicv5_spi_hwirq(RESTORE_TEST_SPI_PRIO_HIGH);
+ break;
+ case 1:
+ expected_hwirq = gicv5_spi_hwirq(RESTORE_TEST_SPI_PRIO_LOW);
+ break;
+ case 2:
+ expected_hwirq = gicv5_spi_hwirq(RESTORE_TEST_SPI_PENDING_DISABLED);
+ break;
+ case 3:
+ expected_hwirq = gicv5_spi_hwirq(RESTORE_TEST_SPI_ENABLED);
+ break;
+ default:
+ GUEST_FAIL("Unexpected VPE0 IST restore interrupt");
+ }
+ } else {
+ switch (count) {
+ case 0:
+ expected_hwirq = gicv5_lpi_hwirq(RESTORE_TEST_LPI_PENDING);
+ break;
+ case 1:
+ expected_hwirq = gicv5_lpi_hwirq(RESTORE_TEST_LPI_ENABLED);
+ break;
+ default:
+ GUEST_FAIL("Unexpected VPE1 IST restore interrupt");
+ }
+ }
+
+ GUEST_ASSERT_EQ(hwirq, expected_hwirq);
+
+ gic_insn(hwirq, CDDI);
+ gic_insn(0, CDEOI);
+ ist_restore_irq_count[vcpu_id]++;
+
+ if (!vcpu_id) {
+ switch (count) {
+ case 0:
+ gicv5_set_pcr(VGIC_V5_IST_PRIO_LOW);
+ break;
+ case 1:
+ gicv5_enable_hwirq(
+ gicv5_spi_hwirq(RESTORE_TEST_SPI_PENDING_DISABLED));
+ break;
+ case 2:
+ GUEST_SYNC(GUEST_CMD_IST_SPI_INJECT_READY);
+ break;
+ case 3:
+ GUEST_DONE();
+ }
+ } else {
+ switch (count) {
+ case 0:
+ gicv5_send_lpi(RESTORE_TEST_LPI_ENABLED);
+ GUEST_SYNC(GUEST_CMD_IST_LPI_INJECT_READY);
+ break;
+ case 1:
+ GUEST_DONE();
+ }
+ }
+}
+
+static void guest_ist_restore_code(void)
+{
+ local_irq_disable();
+
+ gicv5_cpu_enable_interrupts();
+
+ if (!guest_get_vcpuid())
+ gicv5_set_pcr(VGIC_V5_IST_PRIO_HIGH);
+
+ GUEST_SYNC(GUEST_CMD_IS_READY);
+
+ local_irq_enable();
+
+ while (1)
+ wfi();
+}
/* we don't want to assert on run execution, hence that helper */
static int run_vcpu(struct kvm_vcpu *vcpu)
@@ -94,12 +587,1005 @@ static void vm_gic_destroy(struct vm_gic *v)
kvm_vm_free(v->vm);
}
+static void vgic_v5_map_irs(struct kvm_vm *vm)
+{
+ unsigned int nr_irs_pages;
+
+ nr_irs_pages = vm_calc_num_guest_pages(vm->mode, VGIC_V5_IRS_SIZE);
+
+ /* Map the IRS at VA == IPA so guest MMIO writes hit the IRS IODEV. */
+ virt_map(vm, GICV5_IRS_CONFIG_BASE_GPA,
+ GICV5_IRS_CONFIG_BASE_GPA, nr_irs_pages);
+}
+
+static u32 vgic_v5_irq_line_payload(u32 type, u32 num)
+{
+ return (type << KVM_ARM_IRQ_TYPE_SHIFT) |
+ FIELD_PREP(KVM_ARM_IRQ_NUM_MASK, num);
+}
+
+static int __vgic_v5_irq_line(struct kvm_vm *vm, u32 type, u32 num, int level)
+{
+ return _kvm_irq_line(vm, vgic_v5_irq_line_payload(type, num), level);
+}
+
+static void vgic_v5_spi_line(struct kvm_vm *vm, u32 spi, int level)
+{
+ int ret = __vgic_v5_irq_line(vm, KVM_ARM_IRQ_TYPE_SPI, spi, level);
+
+ TEST_ASSERT(!ret, "KVM_IRQ_LINE failed for SPI %u level %d",
+ spi, level);
+}
+
+static void vgic_v5_spi_line_vm_create(struct vm_gic *v,
+ struct kvm_vcpu **vcpu,
+ u32 nr_spis)
+{
+ u64 attr;
+
+ v->gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ v->vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
+ v->gic_fd = kvm_create_device(v->vm, v->gic_dev_type);
+ *vcpu = vm_vcpu_add(v->vm, 0, guest_spi_line_code);
+ TEST_ASSERT(*vcpu, "Failed to create vCPU");
+
+ vm_init_descriptor_tables(v->vm);
+ vm_install_exception_handler(v->vm, VECTOR_IRQ_CURRENT,
+ guest_spi_irq_handler);
+ vcpu_init_descriptor_tables(*vcpu);
+
+ kvm_device_attr_set(v->gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS, 0,
+ &nr_spis);
+
+ attr = GICV5_IRS_CONFIG_BASE_GPA;
+ kvm_device_attr_set(v->gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &attr);
+ vgic_v5_map_irs(v->vm);
+ kvm_device_attr_set(v->gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+}
+
+static void vgic_v5_lpi_ist_vm_create(struct vm_gic *v,
+ struct kvm_vcpu *vcpus[VGIC_V5_LPI_NR_VCPUS],
+ void (*guest_code)(void),
+ void (*irq_handler)(struct ex_regs *))
+{
+ unsigned int nr_lpi_ist_pages;
+ u64 attr;
+ int i;
+
+ v->gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ v->vm = __vm_create(VM_SHAPE_DEFAULT, VGIC_V5_LPI_NR_VCPUS, 0);
+ v->gic_fd = kvm_create_device(v->vm, v->gic_dev_type);
+
+ for (i = 0; i < VGIC_V5_LPI_NR_VCPUS; i++) {
+ vcpus[i] = vm_vcpu_add(v->vm, i, guest_code);
+ TEST_ASSERT(vcpus[i], "Failed to create vCPU");
+ }
+
+ vm_init_descriptor_tables(v->vm);
+ vm_install_exception_handler(v->vm, VECTOR_IRQ_CURRENT,
+ irq_handler);
+
+ for (i = 0; i < VGIC_V5_LPI_NR_VCPUS; i++)
+ vcpu_init_descriptor_tables(vcpus[i]);
+
+ attr = GICV5_IRS_CONFIG_BASE_GPA;
+ kvm_device_attr_set(v->gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &attr);
+ vgic_v5_map_irs(v->vm);
+
+ nr_lpi_ist_pages = vm_calc_num_guest_pages(v->vm->mode,
+ VGIC_V5_LPI_IST_SIZE);
+ vm_userspace_mem_region_add(v->vm, VM_MEM_SRC_ANONYMOUS,
+ VGIC_V5_LPI_IST_BASE_GPA,
+ VGIC_V5_LPI_MEMSLOT,
+ nr_lpi_ist_pages, 0);
+ /*
+ * Map the IST at VA == IPA so the guest can program the same BASER
+ * address.
+ */
+ virt_map(v->vm, VGIC_V5_LPI_IST_BASE_GPA,
+ VGIC_V5_LPI_IST_BASE_GPA, nr_lpi_ist_pages);
+
+ kvm_device_attr_set(v->gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+}
+
+static void vgic_v5_lpi_vm_create(struct vm_gic *v,
+ struct kvm_vcpu *vcpus[VGIC_V5_LPI_NR_VCPUS])
+{
+ vgic_v5_lpi_ist_vm_create(v, vcpus, guest_lpi_code,
+ guest_lpi_irq_handler);
+}
+
+static void vgic_v5_ist_restore_vm_create(struct vm_gic *v,
+ struct kvm_vcpu *vcpus[VGIC_V5_LPI_NR_VCPUS],
+ void (*guest_code)(void),
+ void (*irq_handler)(struct ex_regs *))
+{
+ vgic_v5_lpi_ist_vm_create(v, vcpus, guest_code, irq_handler);
+}
+
+static void vgic_v5_run_spi_line_test(u32 nr_spis, u32 expected_spi,
+ bool level_sensitive,
+ bool lower_before_run)
+{
+ struct kvm_vcpu *vcpu;
+ struct vm_gic v;
+ struct ucall uc;
+ int ret;
+
+ spi_line_expected = expected_spi;
+ spi_line_level_sensitive = level_sensitive;
+
+ vgic_v5_spi_line_vm_create(&v, &vcpu, nr_spis);
+
+ sync_global_to_guest(v.vm, spi_line_expected);
+ sync_global_to_guest(v.vm, spi_line_level_sensitive);
+
+ ret = run_vcpu(vcpu);
+ TEST_ASSERT(!ret, "Failed to run GICv5 IRQ_LINE VM");
+ TEST_ASSERT(get_ucall(vcpu, &uc) == UCALL_SYNC &&
+ uc.args[1] == GUEST_CMD_IS_READY,
+ "GICv5 IRQ_LINE guest did not become ready");
+
+ vgic_v5_spi_line(v.vm, expected_spi, 1);
+ /*
+ * For edge-triggered SPIs, a following low transition must be ignored:
+ * once the edge has made the SPI pending, it cannot be recalled. This
+ * allows us to test that.
+ */
+ if (lower_before_run)
+ vgic_v5_spi_line(v.vm, expected_spi, 0);
+
+ while (1) {
+ ret = run_vcpu(vcpu);
+ TEST_ASSERT(!ret, "Failed to run GICv5 IRQ_LINE VM");
+
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ case UCALL_DONE:
+ goto done;
+ default:
+ TEST_FAIL("Unknown ucall %lu", uc.cmd);
+ }
+ }
+
+done:
+ vm_gic_destroy(&v);
+}
+
+static void vgic_v5_expect_sync(struct kvm_vcpu *vcpu, u64 cmd,
+ const char *msg)
+{
+ struct ucall uc;
+
+ TEST_ASSERT(get_ucall(vcpu, &uc) == UCALL_SYNC && uc.args[1] == cmd,
+ "%s", msg);
+}
+
+struct vgic_region_attr {
+ u64 attr;
+ u64 size;
+ u64 alignment;
+};
+
+static const struct vgic_region_attr gic_v5_irs_region = {
+ .attr = KVM_VGIC_V5_ADDR_TYPE_IRS,
+ .size = GICV5_IRS_SIZE,
+ .alignment = GICV5_IRS_ALIGN,
+};
+
+struct vgic_sysreg_attr {
+ const char *name;
+ u32 encoding;
+};
+
+struct vgic_irs_reg_attr {
+ const char *name;
+ u64 attr;
+};
+
+#define PACK_SR(r) \
+ ((sys_reg_Op0(r) << 14) | \
+ (sys_reg_Op1(r) << 11) | \
+ (sys_reg_CRn(r) << 7) | \
+ (sys_reg_CRm(r) << 3) | \
+ (sys_reg_Op2(r)))
+
+#define SR(r) \
+ { \
+ .name = #r, \
+ .encoding = r, \
+ }
+
+#define IRS_REG(r) \
+ { \
+ .name = #r, \
+ .attr = r, \
+ }
+
+static const struct vgic_irs_reg_attr gic_v5_irs_regs[] = {
+ IRS_REG(GICV5_IRS_IDR0),
+ IRS_REG(GICV5_IRS_IDR1),
+ IRS_REG(GICV5_IRS_IDR2),
+ IRS_REG(GICV5_IRS_IDR3),
+ IRS_REG(GICV5_IRS_IDR4),
+ IRS_REG(GICV5_IRS_IDR5),
+ IRS_REG(GICV5_IRS_IDR6),
+ IRS_REG(GICV5_IRS_IDR7),
+ IRS_REG(GICV5_IRS_IIDR),
+ IRS_REG(GICV5_IRS_AIDR),
+ IRS_REG(GICV5_IRS_CR0),
+ IRS_REG(GICV5_IRS_CR1),
+ IRS_REG(GICV5_IRS_SYNCR),
+ IRS_REG(GICV5_IRS_SYNC_STATUSR),
+ IRS_REG(GICV5_IRS_SPI_VMR),
+ IRS_REG(GICV5_IRS_SPI_SELR),
+ IRS_REG(GICV5_IRS_SPI_DOMAINR),
+ IRS_REG(GICV5_IRS_SPI_RESAMPLER),
+ IRS_REG(GICV5_IRS_SPI_CFGR),
+ IRS_REG(GICV5_IRS_SPI_STATUSR),
+ IRS_REG(GICV5_IRS_PE_SELR),
+ IRS_REG(GICV5_IRS_PE_STATUSR),
+ IRS_REG(GICV5_IRS_PE_CR0),
+ IRS_REG(GICV5_IRS_IST_BASER),
+ IRS_REG(GICV5_IRS_IST_CFGR),
+ IRS_REG(GICV5_IRS_IST_STATUSR),
+ IRS_REG(GICV5_IRS_MAP_L2_ISTR),
+ IRS_REG(GICV5_IRS_VMT_BASER),
+ IRS_REG(GICV5_IRS_VMT_CFGR),
+ IRS_REG(GICV5_IRS_VMT_STATUSR),
+ IRS_REG(GICV5_IRS_VPE_SELR),
+ IRS_REG(GICV5_IRS_VPE_DBR),
+ IRS_REG(GICV5_IRS_VPE_HPPIR),
+ IRS_REG(GICV5_IRS_VPE_CR0),
+ IRS_REG(GICV5_IRS_VPE_STATUSR),
+ IRS_REG(GICV5_IRS_VM_DBR),
+ IRS_REG(GICV5_IRS_VM_SELR),
+ IRS_REG(GICV5_IRS_VM_STATUSR),
+ IRS_REG(GICV5_IRS_VMAP_L2_VMTR),
+ IRS_REG(GICV5_IRS_VMAP_VMR),
+ IRS_REG(GICV5_IRS_VMAP_VISTR),
+ IRS_REG(GICV5_IRS_VMAP_L2_VISTR),
+ IRS_REG(GICV5_IRS_VMAP_VPER),
+ IRS_REG(GICV5_IRS_SAVE_VMR),
+ IRS_REG(GICV5_IRS_SAVE_VM_STATUSR),
+ IRS_REG(GICV5_IRS_MEC_IDR),
+ IRS_REG(GICV5_IRS_MEC_MECID_R),
+ IRS_REG(GICV5_IRS_MPAM_IDR),
+ IRS_REG(GICV5_IRS_MPAM_PARTID_R),
+ IRS_REG(GICV5_IRS_SWERR_STATUSR),
+ IRS_REG(GICV5_IRS_SWERR_SYNDROMER0),
+ IRS_REG(GICV5_IRS_SWERR_SYNDROMER1),
+};
+
+static const struct vgic_sysreg_attr gic_v5_cpu_sysregs[] = {
+ SR(SYS_ICC_ICSR_EL1),
+ SR(SYS_ICC_PPI_ENABLER0_EL1),
+ SR(SYS_ICC_PPI_ENABLER1_EL1),
+ SR(SYS_ICC_PPI_SACTIVER0_EL1),
+ SR(SYS_ICC_PPI_SACTIVER1_EL1),
+ SR(SYS_ICC_PPI_SPENDR0_EL1),
+ SR(SYS_ICC_PPI_SPENDR1_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR0_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR1_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR2_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR3_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR4_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR5_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR6_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR7_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR8_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR9_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR10_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR11_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR12_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR13_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR14_EL1),
+ SR(SYS_ICC_PPI_PRIORITYR15_EL1),
+ SR(SYS_ICC_APR_EL1),
+ SR(SYS_ICC_CR0_EL1),
+ SR(SYS_ICC_PCR_EL1),
+};
+
+static void vgic_v5_save_irs_regs(struct vm_gic *v, u64 *regs)
+{
+ int ret, i;
+
+ for (i = 0; i < ARRAY_SIZE(gic_v5_irs_regs); i++) {
+ ret = __kvm_device_attr_get(v->gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ gic_v5_irs_regs[i].attr,
+ &regs[i]);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS save failed for %s",
+ gic_v5_irs_regs[i].name);
+ }
+}
+
+static void vgic_v5_restore_irs_regs(struct vm_gic *v, u64 *regs)
+{
+ int ret, i;
+
+ for (i = 0; i < ARRAY_SIZE(gic_v5_irs_regs); i++) {
+ ret = __kvm_device_attr_set(v->gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ gic_v5_irs_regs[i].attr,
+ &regs[i]);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS restore failed for %s",
+ gic_v5_irs_regs[i].name);
+ }
+}
+
+static u64 vgic_v5_saved_irs_reg(u64 *regs, u64 attr)
+{
+ for (int i = 0; i < ARRAY_SIZE(gic_v5_irs_regs); i++) {
+ if (gic_v5_irs_regs[i].attr == attr)
+ return regs[i];
+ }
+
+ TEST_FAIL("GICv5 IRS register 0x%lx was not saved", attr);
+}
+
+static size_t vgic_v5_lpi_ist_state_size(u64 *irs_regs)
+{
+ u64 cfgr = vgic_v5_saved_irs_reg(irs_regs, GICV5_IRS_IST_CFGR);
+ u32 lpi_id_bits = FIELD_GET(GICV5_IRS_IST_CFGR_LPI_ID_BITS, cfgr);
+
+ return BIT(lpi_id_bits) * sizeof(__u32);
+}
+
+static void test_vgic_v5_addr_attrs(void)
+{
+ struct kvm_vcpu *vcpu;
+ struct vm_gic v;
+ u64 addr;
+ int ret;
+
+ v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
+ v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
+
+ /* Check existing group/attributes */
+ kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR, gic_v5_irs_region.attr);
+
+ /* check non existing attribute */
+ ret = __kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR, -1);
+ TEST_ASSERT(ret && errno == ENXIO, "attribute not supported");
+
+ /* get IRS base address before setting*/
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &addr);
+ TEST_ASSERT(!ret && addr == (-1ULL), "GICv5 IRS returns VGIC_ADDR_UNDEF");
+
+ /* misaligned IRS address settings */
+ addr = gic_v5_irs_region.alignment / 0x10;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &addr);
+ TEST_ASSERT(ret && errno == EINVAL, "GIC IRS base not aligned");
+
+ /* out of range address */
+ addr = max_phys_size;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &addr);
+ TEST_ASSERT(ret && errno == E2BIG, "IRS address beyond IPA limit");
+
+ /* Space for half an IRS (an IRS is: 2 * irs.alignment). */
+ addr = max_phys_size - gic_v5_irs_region.alignment;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &addr);
+ TEST_ASSERT(ret && errno == E2BIG,
+ "half of the IRS is beyond IPA limit");
+
+ /* set IRS base address @0x0*/
+ addr = 0x00000;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &addr);
+ TEST_ASSERT(!ret, "GICv5 IRS base correctly set");
+
+ /* get IRS base address */
+ addr = 0xbad;
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &addr);
+ TEST_ASSERT(!ret && addr == 0, "GICv5 IRS base correctly set");
+
+ /* Attempt to create a second IRS region */
+ addr = 0xE0000;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &addr);
+ TEST_ASSERT(ret && errno == EEXIST, "GICv5 IRS base set again");
+
+ vm_gic_destroy(&v);
+
+ /* Try running a VM without ever setting the IRS base addr */
+ v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
+ v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
+ vcpu = vm_vcpu_add(v.vm, 0, NULL);
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+ ret = run_vcpu(vcpu);
+ TEST_ASSERT(ret && errno == ENXIO, "GICv5 IRS base not set");
+
+ vm_gic_destroy(&v);
+}
+
+static void test_vgic_v5_nr_irqs_attrs(void)
+{
+ struct kvm_vcpu *vcpu;
+ struct vm_gic v;
+ u32 nr_irqs;
+ int ret;
+
+ v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
+ v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
+
+ /* Check existing group/attribute */
+ kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS, 0);
+
+ /* Before userspace sets NR_IRQS, no SPI count has been selected. */
+ nr_irqs = 0xbad;
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
+ 0, &nr_irqs);
+ TEST_ASSERT(!ret && nr_irqs == 0, "GICv5 NR_IRQS defaults to 0 before init");
+
+ /* Too few SPIs */
+ nr_irqs = VGIC_V5_DEFAULT_NR_SPIS - 1;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
+ 0, &nr_irqs);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 NR_IRQS below minimum");
+
+ /* Not a multiple of 32 */
+ nr_irqs = VGIC_V5_DEFAULT_NR_SPIS + 1;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
+ 0, &nr_irqs);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 NR_IRQS not 32-aligned");
+
+ /* Larger than KVM's supported VGICv5 SPI count */
+ nr_irqs = VGIC_V5_MAX_NR_SPIS + 32;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
+ 0, &nr_irqs);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 NR_IRQS above maximum");
+
+ /* Valid custom SPI count */
+ nr_irqs = VGIC_V5_MAX_NR_SPIS;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
+ 0, &nr_irqs);
+ TEST_ASSERT(!ret, "GICv5 NR_IRQS accepts valid custom SPI count");
+
+ nr_irqs = 0xbad;
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
+ 0, &nr_irqs);
+ TEST_ASSERT(!ret && nr_irqs == VGIC_V5_MAX_NR_SPIS,
+ "GICv5 NR_IRQS returns SPI count only");
+
+ /* A second successful configuration attempt must be rejected. */
+ nr_irqs = VGIC_V5_DEFAULT_NR_SPIS;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
+ 0, &nr_irqs);
+ TEST_ASSERT(ret && errno == EBUSY, "GICv5 NR_IRQS set twice");
+
+ /* The maximum supported count must also initialize successfully. */
+ vcpu = vm_vcpu_add(v.vm, 0, NULL);
+ TEST_ASSERT(vcpu, "Failed to create vCPU");
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ vm_gic_destroy(&v);
+
+ /* If userspace does not set NR_IRQS, init selects the default. */
+ v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
+ v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
+ vcpu = vm_vcpu_add(v.vm, 0, NULL);
+ TEST_ASSERT(vcpu, "Failed to create vCPU");
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ nr_irqs = 0xbad;
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
+ 0, &nr_irqs);
+ TEST_ASSERT(!ret && nr_irqs == VGIC_V5_DEFAULT_NR_SPIS,
+ "GICv5 NR_IRQS defaults to 32 SPIs after init");
+
+ nr_irqs = VGIC_V5_DEFAULT_NR_SPIS * 2;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
+ 0, &nr_irqs);
+ TEST_ASSERT(ret && errno == EBUSY, "GICv5 NR_IRQS set after init");
+
+ vm_gic_destroy(&v);
+
+}
+
+static void test_vgic_v5_irs_regs_attrs(void)
+{
+ struct kvm_vcpu *vcpu;
+ struct vm_gic v;
+ u64 attr, val;
+ int ret, i;
+
+ v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
+ v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
+ vcpu = vm_vcpu_add(v.vm, 0, NULL);
+ TEST_ASSERT(vcpu, "Failed to create vCPU");
+
+ /* IRS_REGS attributes can be probed before the IRS base is set. */
+ kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR0);
+
+ attr = GICV5_IRS_CONFIG_BASE_GPA;
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &attr);
+
+ /* Check existing group/attribute */
+ kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR0);
+
+ /* IRS_REGS are not accessible before the VGIC is initialized. */
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR0, &val);
+ TEST_ASSERT(ret && errno == EBUSY, "GICv5 IRS_REGS get before init");
+
+ val = 0;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR0, &val);
+ TEST_ASSERT(ret && errno == EBUSY, "GICv5 IRS_REGS set before init");
+
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ /* Read all supported IRS regs and write the value back. */
+ for (i = 0; i < ARRAY_SIZE(gic_v5_irs_regs); i++) {
+ attr = gic_v5_irs_regs[i].attr;
+ ret = __kvm_has_device_attr(v.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ attr);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS missing %s",
+ gic_v5_irs_regs[i].name);
+
+ val = 0xbad;
+ ret = __kvm_device_attr_get(v.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ attr, &val);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS get failed for %s",
+ gic_v5_irs_regs[i].name);
+
+ ret = __kvm_device_attr_set(v.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ attr, &val);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS set failed for %s",
+ gic_v5_irs_regs[i].name);
+ }
+
+ /* Check bad offsets */
+ attr = 0x10000;
+ ret = __kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ attr);
+ TEST_ASSERT(ret && errno == ENXIO, "GICv5 IRS_REGS accepted bad offset");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ attr, &val);
+ TEST_ASSERT(ret && errno == ENXIO, "GICv5 IRS_REGS get bad offset");
+
+ /* Check alignment for 32-bit and 64-bit IRS regs. */
+ attr = GICV5_IRS_IDR0 + 2;
+ ret = __kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IRS_REGS accepted unaligned 32-bit attr");
+
+ attr = GICV5_IRS_IST_BASER + 4;
+ ret = __kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IRS_REGS accepted unaligned 64-bit attr");
+
+ /* Check bad user pointers */
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR0, NULL);
+ TEST_ASSERT(ret && errno == EFAULT, "GICv5 IRS_REGS get with bad pointer");
+
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR0, NULL);
+ TEST_ASSERT(ret && errno == EFAULT, "GICv5 IRS_REGS set with bad pointer");
+
+ /* ID restore validation rejects unsupported values. */
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR0, &val);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS get IDR0 failed");
+ val &= ~GICV5_IRS_IDR0_INT_DOM;
+ val |= FIELD_PREP(GICV5_IRS_IDR0_INT_DOM,
+ GICV5_IRS_IDR0_INT_DOM_SECURE);
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR0, &val);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IRS_REGS accepted bad IDR0 domain");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR0, &val);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS get IDR0 failed");
+ val |= GICV5_IRS_IDR0_SETLPI;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR0, &val);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IRS_REGS accepted unsupported IDR0");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR1, &val);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS get IDR1 failed");
+ val |= FIELD_PREP(GICV5_IRS_IDR1_PRIORITY_BITS, 0x7);
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR1, &val);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IRS_REGS accepted bad IDR1");
+
+ val = 0;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR2, &val);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IRS_REGS accepted bad IDR2");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR5, &val);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS get IDR5 failed");
+ val++;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR5, &val);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IRS_REGS accepted bad IDR5");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR6, &val);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS get IDR6 failed");
+ val++;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR6, &val);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IRS_REGS accepted bad IDR6");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR7, &val);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS get IDR7 failed");
+ val++;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IDR7, &val);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IRS_REGS accepted bad IDR7");
+
+ /* Status registers read as idle through userspace accessors. */
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_SYNC_STATUSR, &val);
+ TEST_ASSERT(!ret && val == GICV5_IRS_SYNC_STATUSR_IDLE,
+ "GICv5 IRS_REGS SYNC_STATUSR is not idle");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_SPI_STATUSR, &val);
+ TEST_ASSERT(!ret && val == GICV5_IRS_SPI_STATUSR_IDLE,
+ "GICv5 IRS_REGS SPI_STATUSR is not idle");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_PE_STATUSR, &val);
+ TEST_ASSERT(!ret && val == GICV5_IRS_PE_STATUSR_IDLE,
+ "GICv5 IRS_REGS PE_STATUSR is not idle");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_STATUSR, &val);
+ TEST_ASSERT(!ret && val == GICV5_IRS_IST_STATUSR_IDLE,
+ "GICv5 IRS_REGS IST_STATUSR is not idle");
+
+ /*
+ * Userspace restores IST_BASER without allocating or accessing an IST.
+ * Supply a synthetic, aligned guest address only to verify the register
+ * round trip.
+ */
+ val = FIELD_PREP(GICV5_IRS_IST_BASER_ADDR_MASK,
+ VGIC_V5_TEST_IST_BASER_GPA >> GICV5_IRS_IST_BASER_ADDR_SHIFT);
+ val |= GICV5_IRS_IST_BASER_VALID;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_BASER, &val);
+ TEST_ASSERT(!ret, "GICv5 IRS_REGS failed to restore IST_BASER");
+
+ attr = 0xbad;
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_BASER, &attr);
+ TEST_ASSERT(!ret && attr == val, "GICv5 IRS_REGS IST_BASER restore mismatch");
+
+ vm_gic_destroy(&v);
+}
+
+static void test_vgic_v5_ist_attrs(void)
+{
+ struct kvm_vgic_v5_ist ist_attr;
+ struct kvm_vcpu *vcpu;
+ struct vm_gic v;
+ u32 spi_ist[VGIC_V5_DEFAULT_NR_SPIS];
+ u64 attr;
+ int ret;
+
+ v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
+ v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
+ vcpu = vm_vcpu_add(v.vm, 0, NULL);
+ TEST_ASSERT(vcpu, "Failed to create vCPU");
+
+ /* Check existing group/attribute */
+ kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST, 0);
+
+ /* Check non-existing attribute */
+ ret = __kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST, 1);
+ TEST_ASSERT(ret && errno == ENXIO, "GICv5 IST accepted bad attr");
+
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist), NULL, 0);
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 1, &ist_attr);
+ TEST_ASSERT(ret && errno == ENXIO, "GICv5 IST get accepted bad attr");
+
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 1, &ist_attr);
+ TEST_ASSERT(ret && errno == ENXIO, "GICv5 IST set accepted bad attr");
+
+ attr = GICV5_IRS_CONFIG_BASE_GPA;
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &attr);
+
+ /* IST save/restore is not accessible before the VGIC is initialized. */
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EBUSY, "GICv5 IST get before init");
+
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EBUSY, "GICv5 IST set before init");
+
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ /* A VM with SPIs must provide a userspace IST descriptor. */
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, NULL);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST get accepted NULL descriptor");
+
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, NULL);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST set accepted NULL descriptor");
+
+ /* Check bad userspace IST descriptors. */
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, (void *)1);
+ TEST_ASSERT(ret && errno == EFAULT, "GICv5 IST get with bad descriptor");
+
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, (void *)1);
+ TEST_ASSERT(ret && errno == EFAULT, "GICv5 IST set with bad descriptor");
+
+ /* Check missing and incorrectly sized SPI IST buffers. */
+ ist_attr = vgic_v5_ist_attr(NULL, 0, NULL, 0);
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST get accepted missing SPI buffer");
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST set accepted missing SPI buffer");
+
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist) - sizeof(__u32),
+ NULL, 0);
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST set accepted bad SPI size");
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST get accepted bad SPI size");
+
+ /* LPI storage must be absent when no LPI IST is configured. */
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist), spi_ist,
+ sizeof(__u32));
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST get accepted unexpected LPI buffer");
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST set accepted unexpected LPI buffer");
+
+ vm_gic_destroy(&v);
+
+ /* IST restore is rejected after the VM has run. */
+ v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
+ v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
+ vcpu = vm_vcpu_add(v.vm, 0, guest_code);
+
+ attr = GICV5_IRS_CONFIG_BASE_GPA;
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &attr);
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ ret = run_vcpu(vcpu);
+ TEST_ASSERT(!ret, "Failed to run GICv5 VM before IST restore test");
+
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist), NULL, 0);
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EBUSY, "GICv5 IST restore after run");
+
+ vm_gic_destroy(&v);
+}
+
+static void test_vgic_v5_userspace_ppis_attrs(void)
+{
+ struct kvm_vcpu *vcpu;
+ struct vm_gic v;
+ u64 user_ppis[2];
+ int ret;
+
+ v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
+ v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
+ vcpu = vm_vcpu_add(v.vm, 0, NULL);
+ TEST_ASSERT(vcpu, "Failed to create vCPU");
+
+ kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT);
+ kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_USERSPACE_PPIS);
+
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ user_ppis[0] = 0;
+ user_ppis[1] = 0xbad;
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_USERSPACE_PPIS, user_ppis);
+ TEST_ASSERT(!ret, "GICv5 USERSPACE_PPIS get failed");
+ TEST_ASSERT(user_ppis[0] & BIT(GICV5_ARCH_PPI_SW_PPI),
+ "GICv5 USERSPACE_PPIS does not expose SW_PPI");
+ TEST_ASSERT(!user_ppis[1], "GICv5 USERSPACE_PPIS upper word is not zero");
+
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_USERSPACE_PPIS, user_ppis);
+ TEST_ASSERT(ret && errno == ENXIO, "GICv5 USERSPACE_PPIS set accepted");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_USERSPACE_PPIS, NULL);
+ TEST_ASSERT(ret && errno == EFAULT, "GICv5 USERSPACE_PPIS get with bad pointer");
+
+ vm_gic_destroy(&v);
+}
+
+static void test_vgic_v5_cpu_sysreg_attrs(void)
+{
+ struct kvm_vcpu *vcpu;
+ struct vm_gic v;
+ u64 attr, val;
+ int ret, i;
+
+ v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
+ v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
+ vcpu = vm_vcpu_add(v.vm, 0, NULL);
+ TEST_ASSERT(vcpu, "Failed to create vCPU");
+
+ /* Check existing group/attribute */
+ attr = PACK_SR(SYS_ICC_CR0_EL1);
+ kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS, attr);
+
+ /* CPU sysregs are not accessible before the VGIC is initialized. */
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr, &val);
+ TEST_ASSERT(ret && errno == EBUSY, "GICv5 CPU_SYSREGS get before init");
+
+ val = 0;
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr, &val);
+ TEST_ASSERT(ret && errno == EBUSY, "GICv5 CPU_SYSREGS set before init");
+
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ /* Read all exposed CPU sysregs and write the value back. */
+ for (i = 0; i < ARRAY_SIZE(gic_v5_cpu_sysregs); i++) {
+ attr = PACK_SR(gic_v5_cpu_sysregs[i].encoding);
+ ret = __kvm_has_device_attr(v.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr);
+ TEST_ASSERT(!ret, "GICv5 CPU_SYSREGS missing %s",
+ gic_v5_cpu_sysregs[i].name);
+
+ val = 0xbad;
+ ret = __kvm_device_attr_get(v.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr, &val);
+ TEST_ASSERT(!ret, "GICv5 CPU_SYSREGS get failed for %s",
+ gic_v5_cpu_sysregs[i].name);
+
+ ret = __kvm_device_attr_set(v.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr, &val);
+ TEST_ASSERT(!ret, "GICv5 CPU_SYSREGS set failed for %s",
+ gic_v5_cpu_sysregs[i].name);
+ }
+
+ /* Check non existent GICv3 sysreg */
+ attr = PACK_SR(SYS_ICC_CTLR_EL1);
+ ret = __kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr);
+ TEST_ASSERT(ret && errno == ENXIO, "GICv5 CPU_SYSREGS accepted bad sysreg");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr, &val);
+ TEST_ASSERT(ret && errno == ENOENT, "GICv5 CPU_SYSREGS get bad sysreg");
+
+ /* Check non existing vCPU */
+ attr = PACK_SR(SYS_ICC_CR0_EL1) |
+ (1ULL << KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT);
+ ret = __kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 CPU_SYSREGS accepted bad MPIDR");
+
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr, &val);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 CPU_SYSREGS get bad MPIDR");
+
+ /* Check bad user pointers */
+ attr = PACK_SR(SYS_ICC_CR0_EL1);
+ ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr, NULL);
+ TEST_ASSERT(ret && errno == EFAULT, "GICv5 CPU_SYSREGS get with bad pointer");
+
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
+ attr, NULL);
+ TEST_ASSERT(ret && errno == EFAULT, "GICv5 CPU_SYSREGS set with bad pointer");
+
+ vm_gic_destroy(&v);
+}
+
+static void test_vgic_v5_sparse_vcpu_ids(void)
+{
+ struct kvm_vcpu *vcpus[VGIC_V5_SPARSE_NR_VCPUS];
+ const u32 vcpu_ids[VGIC_V5_SPARSE_NR_VCPUS] = {
+ VGIC_V5_SPARSE_VCPU0_ID,
+ VGIC_V5_SPARSE_VCPU1_ID,
+ };
+ int ret, i;
+ struct ucall uc;
+ struct vm_gic v;
+ u64 attr;
+
+ v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
+ v.vm = __vm_create(VM_SHAPE_DEFAULT, VGIC_V5_SPARSE_NR_VCPUS, 0);
+ v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
+
+ /* Create two vCPUs with legal, albeit sparse, IDs. */
+ for (i = 0; i < ARRAY_SIZE(vcpus); i++) {
+ vcpus[i] = vm_vcpu_add(v.vm, vcpu_ids[i], guest_iaffid_code);
+ TEST_ASSERT(vcpus[i], "Failed to create vCPU %u", vcpu_ids[i]);
+ }
+
+ attr = GICV5_IRS_CONFIG_BASE_GPA;
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &attr);
+ vgic_v5_map_irs(v.vm);
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ /* Check both are runnable, and read back their correct IAFFIDs */
+ for (i = 0; i < ARRAY_SIZE(vcpus); i++) {
+ /* IAFFID is the VPE ID and must match the userspace vCPU ID. */
+ ret = run_vcpu(vcpus[i]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 vCPU %u", vcpu_ids[i]);
+ TEST_ASSERT(get_ucall(vcpus[i], &uc) == UCALL_SYNC &&
+ uc.args[1] == vcpu_ids[i],
+ "GICv5 vCPU %u IAFFID mismatch", vcpu_ids[i]);
+
+ ret = run_vcpu(vcpus[i]);
+ TEST_ASSERT(!ret, "Failed to complete GICv5 vCPU %u", vcpu_ids[i]);
+ TEST_ASSERT(get_ucall(vcpus[i], NULL) == UCALL_DONE,
+ "GICv5 vCPU %u did not complete", vcpu_ids[i]);
+ }
+
+ vm_gic_destroy(&v);
+}
+
static void test_vgic_v5_ppis(u32 gic_dev_type)
{
struct kvm_vcpu *vcpus[NR_VCPUS];
struct ucall uc;
u64 user_ppis[2];
struct vm_gic v;
+ uint64_t attr;
int ret, i;
v.gic_dev_type = gic_dev_type;
@@ -111,11 +1597,17 @@ static void test_vgic_v5_ppis(u32 gic_dev_type)
vcpus[i] = vm_vcpu_add(v.vm, i, guest_code);
vm_init_descriptor_tables(v.vm);
- vm_install_exception_handler(v.vm, VECTOR_IRQ_CURRENT, guest_irq_handler);
+ vm_install_exception_handler(v.vm, VECTOR_IRQ_CURRENT,
+ guest_ppi_irq_handler);
for (i = 0; i < NR_VCPUS; i++)
vcpu_init_descriptor_tables(vcpus[i]);
+ /* Set the address of the IRS before initialising the GIC */
+ attr = GICV5_IRS_CONFIG_BASE_GPA;
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &attr);
+
kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
@@ -127,6 +1619,20 @@ static void test_vgic_v5_ppis(u32 gic_dev_type)
TEST_ASSERT(user_ppis[0] & BIT(GICV5_ARCH_PPI_SW_PPI),
"SW_PPI is not drivable by userspace");
+ /* PPIs not explicitly exposed to userspace must be rejected. */
+ for (i = 0; i < VGIC_V5_NR_PRIVATE_IRQS; i++) {
+ if (user_ppis[i / 64] & BIT_ULL(i % 64))
+ continue;
+
+ ret = __vgic_v5_irq_line(v.vm, KVM_ARM_IRQ_TYPE_PPI, i, 1);
+ TEST_ASSERT(ret && errno == EINVAL,
+ "GICv5 accepted non-userspace PPI %d", i);
+ }
+
+ ret = __vgic_v5_irq_line(v.vm, KVM_ARM_IRQ_TYPE_PPI,
+ VGIC_V5_NR_PRIVATE_IRQS, 1);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 accepted out-of-range PPI");
+
while (1) {
ret = run_vcpu(vcpus[0]);
if (ret)
@@ -148,9 +1654,9 @@ static void test_vgic_v5_ppis(u32 gic_dev_type)
kvm_irq_line(v.vm, irq, level);
} else if (uc.args[1] == GUEST_CMD_IS_AWAKE) {
- pr_info("Guest skipping WFI due to pending IRQ\n");
+ pr_debug("Guest skipping WFI due to pending IRQ\n");
} else if (uc.args[1] == GUEST_CMD_IRQ_CDIA) {
- pr_info("Guest acknowledged IRQ\n");
+ pr_debug("Guest acknowledged IRQ\n");
}
continue;
@@ -170,6 +1676,304 @@ done:
vm_gic_destroy(&v);
}
+static void test_vgic_v5_spis(void)
+{
+ struct kvm_vcpu *vcpu;
+ struct vm_gic v;
+ int ret;
+
+ /* Default NR_IRQS exposes 32 SPIs, numbered 0..31 in KVM_IRQ_LINE. */
+ vgic_v5_spi_line_vm_create(&v, &vcpu, VGIC_V5_DEFAULT_NR_SPIS);
+ ret = __vgic_v5_irq_line(v.vm, KVM_ARM_IRQ_TYPE_SPI,
+ VGIC_V5_DEFAULT_NR_SPIS, 1);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 accepted first invalid SPI");
+
+ vm_gic_destroy(&v);
+
+ /* Basic SPI injection through KVM_IRQ_LINE. */
+ vgic_v5_run_spi_line_test(VGIC_V5_DEFAULT_NR_SPIS, 0, false, false);
+
+ /* The last valid SPI in the maximum configured range is injectable. */
+ vgic_v5_run_spi_line_test(VGIC_V5_MAX_NR_SPIS,
+ VGIC_V5_MAX_NR_SPIS - 1, false, false);
+
+ vgic_v5_spi_line_vm_create(&v, &vcpu, VGIC_V5_MAX_NR_SPIS);
+ ret = __vgic_v5_irq_line(v.vm, KVM_ARM_IRQ_TYPE_SPI,
+ VGIC_V5_MAX_NR_SPIS, 1);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 accepted configured invalid SPI");
+ vm_gic_destroy(&v);
+
+ /*
+ * Edge SPIs remain pending after the line is lowered. The low
+ * transition is injected before the guest runs and must be ignored:
+ * once an edge interrupt is pending, lowering the line cannot recall it.
+ */
+ vgic_v5_run_spi_line_test(VGIC_V5_DEFAULT_NR_SPIS, 1, false, true);
+
+ /* Level SPIs can be raised and delivered through KVM_IRQ_LINE. */
+ vgic_v5_run_spi_line_test(VGIC_V5_DEFAULT_NR_SPIS, 2, true, false);
+}
+
+static void test_vgic_v5_lpis(void)
+{
+ struct kvm_vcpu *vcpus[VGIC_V5_LPI_NR_VCPUS];
+ struct ucall uc;
+ struct vm_gic v;
+ int ret;
+
+ lpi_ist_ready = false;
+ vgic_v5_lpi_vm_create(&v, vcpus);
+ sync_global_to_guest(v.vm, lpi_ist_ready);
+
+ /* VPE0 programs a linear LPI IST from the virtual IRS ID registers. */
+ ret = run_vcpu(vcpus[0]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 LPI vCPU0");
+ TEST_ASSERT(get_ucall(vcpus[0], &uc) == UCALL_SYNC &&
+ uc.args[1] == GUEST_CMD_IS_READY,
+ "GICv5 LPI vCPU0 did not become ready");
+
+ /* VPE1 waits for the shared LPI setup and enables its CPU interface. */
+ ret = run_vcpu(vcpus[1]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 LPI vCPU1");
+ TEST_ASSERT(get_ucall(vcpus[1], &uc) == UCALL_SYNC &&
+ uc.args[1] == GUEST_CMD_IS_READY,
+ "GICv5 LPI vCPU1 did not become ready");
+
+ /* VPE0 sends an LPI to VPE1. */
+ ret = run_vcpu(vcpus[0]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 LPI vCPU0 sender");
+ TEST_ASSERT(get_ucall(vcpus[0], &uc) == UCALL_SYNC &&
+ uc.args[1] == GUEST_CMD_LPI_SENT,
+ "GICv5 LPI vCPU0 did not send LPI");
+
+ /* VPE1 consumes the LPI and replies with another LPI. */
+ ret = run_vcpu(vcpus[1]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 LPI vCPU1 receiver");
+ TEST_ASSERT(get_ucall(vcpus[1], &uc) == UCALL_SYNC &&
+ uc.args[1] == GUEST_CMD_LPI_REPLIED,
+ "GICv5 LPI vCPU1 did not reply");
+
+ /* VPE0 consumes the reply LPI and ends the test. */
+ ret = run_vcpu(vcpus[0]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 LPI vCPU0 receiver");
+ TEST_ASSERT(get_ucall(vcpus[0], &uc) == UCALL_DONE,
+ "GICv5 LPI vCPU0 did not complete");
+
+ vm_gic_destroy(&v);
+}
+
+static void test_vgic_v5_ist_save_restore(void)
+{
+ static u32 lpi_ist[VGIC_V5_LPI_IST_SIZE / sizeof(u32)];
+ static u32 lpi_ist_resave[VGIC_V5_LPI_IST_SIZE / sizeof(u32)];
+ static u32 spi_ist[VGIC_V5_DEFAULT_NR_SPIS];
+ static u32 spi_ist_resave[VGIC_V5_DEFAULT_NR_SPIS];
+ static u64 irs_regs[ARRAY_SIZE(gic_v5_irs_regs)];
+ struct kvm_vcpu *vcpus[VGIC_V5_LPI_NR_VCPUS];
+ struct kvm_vgic_v5_ist ist_attr;
+ struct vm_gic src, dst;
+ u64 ist_baser, bad_ist_baser;
+ u64 ist_cfgr, bad_ist_cfgr;
+ size_t lpi_ist_size;
+ int ret;
+
+ memset(ist_restore_irq_count, 0, sizeof(ist_restore_irq_count));
+
+ vgic_v5_ist_restore_vm_create(&src, vcpus, guest_ist_source_code,
+ guest_unexpected_irq_handler);
+
+ ret = run_vcpu(vcpus[0]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 IST save source VM");
+ vgic_v5_expect_sync(vcpus[0], GUEST_CMD_IST_SOURCE_READY,
+ "GICv5 IST source guest did not become ready");
+
+ /*
+ * The guest configured enable/priority/affinity state. Make selected
+ * SPIs pending from userspace so the SPI IST save path sees them.
+ */
+ vgic_v5_spi_line(src.vm, RESTORE_TEST_SPI_PRIO_HIGH, 1);
+ vgic_v5_spi_line(src.vm, RESTORE_TEST_SPI_PRIO_LOW, 1);
+ vgic_v5_spi_line(src.vm, RESTORE_TEST_SPI_PENDING_DISABLED, 1);
+
+ vgic_v5_save_irs_regs(&src, irs_regs);
+ lpi_ist_size = vgic_v5_lpi_ist_state_size(irs_regs);
+
+ /*
+ * VCPU0 has configured the initial state. KVM must reject missing or
+ * incorrectly sized LPI storage before accepting the complete IST image.
+ */
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist), NULL, 0);
+ ret = __kvm_device_attr_get(src.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST save accepted missing LPI buffer");
+
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist), lpi_ist,
+ lpi_ist_size - sizeof(__u32));
+ ret = __kvm_device_attr_get(src.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST save accepted bad LPI size");
+
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist), lpi_ist,
+ lpi_ist_size);
+ ret = __kvm_device_attr_get(src.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(!ret, "GICv5 IST save failed");
+
+ vm_gic_destroy(&src);
+
+ /* Create the guest that we are restoring state into */
+ memset(ist_restore_irq_count, 0, sizeof(ist_restore_irq_count));
+ vgic_v5_ist_restore_vm_create(&dst, vcpus, guest_ist_restore_code,
+ guest_ist_restore_irq_handler);
+ sync_global_to_guest(dst.vm, ist_restore_irq_count);
+
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist), lpi_ist,
+ lpi_ist_size);
+ ret = __kvm_device_attr_set(dst.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST restore before IRS state");
+
+ vgic_v5_restore_irs_regs(&dst, irs_regs);
+
+ /* BASER remains mutable until the host LPI IST has been allocated. */
+ ist_baser = vgic_v5_saved_irs_reg(irs_regs, GICV5_IRS_IST_BASER);
+ bad_ist_baser = ist_baser ^
+ FIELD_PREP(GICV5_IRS_IST_BASER_ADDR_MASK, 1);
+ ret = __kvm_device_attr_set(dst.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_BASER, &bad_ist_baser);
+ TEST_ASSERT(!ret, "GICv5 rejected changed unallocated IST_BASER");
+
+ bad_ist_baser = ist_baser & ~GICV5_IRS_IST_BASER_VALID;
+ ret = __kvm_device_attr_set(dst.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_BASER, &bad_ist_baser);
+ TEST_ASSERT(!ret, "GICv5 rejected invalidated unallocated IST_BASER");
+
+ ret = __kvm_device_attr_set(dst.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_BASER, &ist_baser);
+ TEST_ASSERT(!ret, "GICv5 failed to restore IST_BASER");
+
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist), NULL, 0);
+ ret = __kvm_device_attr_set(dst.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST restore accepted missing LPI buffer");
+
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist), lpi_ist,
+ lpi_ist_size - sizeof(__u32));
+ ret = __kvm_device_attr_set(dst.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(ret && errno == EINVAL, "GICv5 IST restore accepted bad LPI size");
+
+ /* Restore the VMM-owned SPI and LPI IST images. */
+ ist_attr = vgic_v5_ist_attr(spi_ist, sizeof(spi_ist), lpi_ist,
+ lpi_ist_size);
+ ret = __kvm_device_attr_set(dst.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(!ret, "GICv5 IST restore failed");
+
+ /* A live IST makes BASER and the guest-visible configuration immutable. */
+ ret = __kvm_device_attr_set(dst.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_BASER, &ist_baser);
+ TEST_ASSERT(!ret, "GICv5 rejected unchanged live IST_BASER");
+
+ bad_ist_baser = ist_baser ^
+ FIELD_PREP(GICV5_IRS_IST_BASER_ADDR_MASK, 1);
+ ret = __kvm_device_attr_set(dst.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_BASER, &bad_ist_baser);
+ TEST_ASSERT(ret && errno == EINVAL,
+ "GICv5 accepted changed live IST_BASER");
+
+ bad_ist_baser = ist_baser & ~GICV5_IRS_IST_BASER_VALID;
+ ret = __kvm_device_attr_set(dst.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_BASER, &bad_ist_baser);
+ TEST_ASSERT(ret && errno == EINVAL,
+ "GICv5 accepted invalidated live IST_BASER");
+
+ ist_cfgr = vgic_v5_saved_irs_reg(irs_regs, GICV5_IRS_IST_CFGR);
+ ret = __kvm_device_attr_set(dst.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_CFGR, &ist_cfgr);
+ TEST_ASSERT(!ret, "GICv5 rejected unchanged live IST_CFGR");
+
+ bad_ist_cfgr = ist_cfgr ^
+ FIELD_PREP(GICV5_IRS_IST_CFGR_LPI_ID_BITS, 1);
+ ret = __kvm_device_attr_set(dst.gic_fd,
+ KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
+ GICV5_IRS_IST_CFGR, &bad_ist_cfgr);
+ TEST_ASSERT(ret && errno == EINVAL,
+ "GICv5 accepted changed live IST_CFGR");
+
+ /*
+ * Re-save the restored IST image before the destination VM runs, so a
+ * dropped serialized field fails independently of later interrupt
+ * delivery.
+ */
+ ist_attr = vgic_v5_ist_attr(spi_ist_resave, sizeof(spi_ist_resave),
+ lpi_ist_resave, lpi_ist_size);
+ ret = __kvm_device_attr_get(dst.gic_fd, KVM_DEV_ARM_VGIC_GRP_IST,
+ 0, &ist_attr);
+ TEST_ASSERT(!ret, "GICv5 IST re-save failed");
+ TEST_ASSERT(!memcmp(spi_ist, spi_ist_resave, sizeof(spi_ist)),
+ "GICv5 SPI IST state changed across restore");
+ TEST_ASSERT(!memcmp(lpi_ist, lpi_ist_resave, lpi_ist_size),
+ "GICv5 LPI IST state changed across restore");
+
+ /*
+ * At this stage, the guest's IST state should match what was saved. Run
+ * each vcpu once to let it enable interrupts, etc.
+ */
+ ret = run_vcpu(vcpus[0]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 IST restore vCPU0");
+ vgic_v5_expect_sync(vcpus[0], GUEST_CMD_IS_READY,
+ "GICv5 IST restore vCPU0 did not become ready");
+
+ ret = run_vcpu(vcpus[1]);
+ TEST_ASSERT(!ret, "Failed to run GICv5 IST restore vCPU1");
+ vgic_v5_expect_sync(vcpus[1], GUEST_CMD_IS_READY,
+ "GICv5 IST restore vCPU1 did not become ready");
+
+ /*
+ * VPE0 consumes two pending SPIs in restored priority order, then
+ * enables a restored pending-but-disabled SPI.
+ */
+ ret = run_vcpu(vcpus[0]);
+ TEST_ASSERT(!ret, "Failed to consume restored GICv5 SPIs");
+ vgic_v5_expect_sync(vcpus[0], GUEST_CMD_IST_SPI_INJECT_READY,
+ "GICv5 IST restore vCPU0 did not consume restored SPIs");
+
+ /*
+ * This SPI was enabled before save, but not pending. Inject it after
+ * restore to prove enable, priority and affinity were restored.
+ */
+ vgic_v5_spi_line(dst.vm, RESTORE_TEST_SPI_ENABLED, 1);
+
+ ret = run_vcpu(vcpus[0]);
+ TEST_ASSERT(!ret, "Failed to consume post-restore GICv5 SPI");
+ TEST_ASSERT(get_ucall(vcpus[0], NULL) == UCALL_DONE,
+ "GICv5 IST restore vCPU0 did not complete");
+
+ /*
+ * VPE1 consumes a restored pending LPI, then pends another LPI that
+ * was enabled before save but not pending.
+ */
+ ret = run_vcpu(vcpus[1]);
+ TEST_ASSERT(!ret, "Failed to consume restored GICv5 LPI");
+ vgic_v5_expect_sync(vcpus[1], GUEST_CMD_IST_LPI_INJECT_READY,
+ "GICv5 IST restore vCPU1 did not consume restored LPI");
+
+ ret = run_vcpu(vcpus[1]);
+ TEST_ASSERT(!ret, "Failed to consume post-restore GICv5 LPI");
+ TEST_ASSERT(get_ucall(vcpus[1], NULL) == UCALL_DONE,
+ "GICv5 IST restore vCPU1 did not complete");
+
+ vm_gic_destroy(&dst);
+}
+
/*
* Returns 0 if it's possible to create GIC device of a given type (V5).
*/
@@ -201,13 +2005,46 @@ int test_kvm_device(u32 gic_dev_type)
void run_tests(u32 gic_dev_type)
{
+ pr_info("Test VGICv5 address attrs\n");
+ test_vgic_v5_addr_attrs();
+
+ pr_info("Test VGICv5 NR_IRQS attrs\n");
+ test_vgic_v5_nr_irqs_attrs();
+
+ pr_info("Test VGICv5 IRS_REGS attrs\n");
+ test_vgic_v5_irs_regs_attrs();
+
+ pr_info("Test VGICv5 IST attrs\n");
+ test_vgic_v5_ist_attrs();
+
+ pr_info("Test VGICv5 userspace PPI attrs\n");
+ test_vgic_v5_userspace_ppis_attrs();
+
+ pr_info("Test VGICv5 CPU sysreg attrs\n");
+ test_vgic_v5_cpu_sysreg_attrs();
+
+ pr_info("Test VGICv5 sparse vCPU IDs\n");
+ test_vgic_v5_sparse_vcpu_ids();
+
pr_info("Test VGICv5 PPIs\n");
test_vgic_v5_ppis(gic_dev_type);
+
+ pr_info("Test VGICv5 SPIs\n");
+ test_vgic_v5_spis();
+
+ pr_info("Test VGICv5 LPIs\n");
+ test_vgic_v5_lpis();
+
+ pr_info("Test VGICv5 IST save/restore\n");
+ test_vgic_v5_ist_save_restore();
}
int main(int ac, char **av)
{
- int ret;
+ int pa_bits, ret;
+
+ pa_bits = vm_guest_mode_params[VM_MODE_DEFAULT].pa_bits;
+ max_phys_size = 1ULL << pa_bits;
test_disable_default_vgic();
diff --git a/tools/testing/selftests/kvm/include/arm64/gic_v5.h b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
index eb523d9277cf..2d455515563e 100644
--- a/tools/testing/selftests/kvm/include/arm64/gic_v5.h
+++ b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
@@ -10,6 +10,106 @@
#include "processor.h"
+/* GIC component base address is guest PA space */
+#define GICV5_IRS_CONFIG_BASE_GPA 0x8000000ULL
+#define GICV5_IRS_CONFIG_BASE_GVA ((volatile void *)GICV5_IRS_CONFIG_BASE_GPA)
+
+#define GICV5_IRS_SIZE 0x20000
+#define GICV5_IRS_ALIGN 0x10000
+
+/*
+ * IRS register offsets. Keep this selftest copy scoped to the offsets and
+ * fields exercised by the userspace API tests.
+ */
+#define GICV5_IRS_IDR0 0x0000
+#define GICV5_IRS_IDR1 0x0004
+#define GICV5_IRS_IDR2 0x0008
+#define GICV5_IRS_IDR3 0x000c
+#define GICV5_IRS_IDR4 0x0010
+#define GICV5_IRS_IDR5 0x0014
+#define GICV5_IRS_IDR6 0x0018
+#define GICV5_IRS_IDR7 0x001c
+#define GICV5_IRS_IIDR 0x0040
+#define GICV5_IRS_AIDR 0x0044
+#define GICV5_IRS_CR0 0x0080
+#define GICV5_IRS_CR1 0x0084
+#define GICV5_IRS_SYNCR 0x00c0
+#define GICV5_IRS_SYNC_STATUSR 0x00c4
+#define GICV5_IRS_SPI_VMR 0x0100
+#define GICV5_IRS_SPI_SELR 0x0108
+#define GICV5_IRS_SPI_DOMAINR 0x010c
+#define GICV5_IRS_SPI_RESAMPLER 0x0110
+#define GICV5_IRS_SPI_CFGR 0x0114
+#define GICV5_IRS_SPI_STATUSR 0x0118
+#define GICV5_IRS_PE_SELR 0x0140
+#define GICV5_IRS_PE_STATUSR 0x0144
+#define GICV5_IRS_PE_CR0 0x0148
+#define GICV5_IRS_IST_BASER 0x0180
+#define GICV5_IRS_IST_CFGR 0x0190
+#define GICV5_IRS_IST_STATUSR 0x0194
+#define GICV5_IRS_MAP_L2_ISTR 0x01c0
+#define GICV5_IRS_VMT_BASER 0x0200
+#define GICV5_IRS_VMT_CFGR 0x0210
+#define GICV5_IRS_VMT_STATUSR 0x0214
+#define GICV5_IRS_VPE_SELR 0x0240
+#define GICV5_IRS_VPE_DBR 0x0248
+#define GICV5_IRS_VPE_HPPIR 0x0250
+#define GICV5_IRS_VPE_CR0 0x0258
+#define GICV5_IRS_VPE_STATUSR 0x025c
+#define GICV5_IRS_VM_DBR 0x0280
+#define GICV5_IRS_VM_SELR 0x0288
+#define GICV5_IRS_VM_STATUSR 0x028c
+#define GICV5_IRS_VMAP_L2_VMTR 0x02c0
+#define GICV5_IRS_VMAP_VMR 0x02c8
+#define GICV5_IRS_VMAP_VISTR 0x02d0
+#define GICV5_IRS_VMAP_L2_VISTR 0x02d8
+#define GICV5_IRS_VMAP_VPER 0x02e0
+#define GICV5_IRS_SAVE_VMR 0x0300
+#define GICV5_IRS_SAVE_VM_STATUSR 0x0308
+#define GICV5_IRS_MEC_IDR 0x0340
+#define GICV5_IRS_MEC_MECID_R 0x0344
+#define GICV5_IRS_MPAM_IDR 0x0380
+#define GICV5_IRS_MPAM_PARTID_R 0x0384
+#define GICV5_IRS_SWERR_STATUSR 0x03c0
+#define GICV5_IRS_SWERR_SYNDROMER0 0x03c8
+#define GICV5_IRS_SWERR_SYNDROMER1 0x03d0
+
+#define GICV5_IRS_IDR0_SETLPI BIT(9)
+#define GICV5_IRS_IDR0_INT_DOM GENMASK(1, 0)
+#define GICV5_IRS_IDR0_INT_DOM_SECURE 0b00
+
+#define GICV5_IRS_IDR1_PRIORITY_BITS GENMASK(22, 20)
+
+#define GICV5_IRS_IDR2_ISTMD_SZ GENMASK(19, 15)
+#define GICV5_IRS_IDR2_ISTMD BIT(14)
+#define GICV5_IRS_IDR2_IST_L2SZ GENMASK(13, 11)
+#define GICV5_IRS_IDR2_IST_LEVELS BIT(10)
+#define GICV5_IRS_IDR2_MIN_LPI_ID_BITS GENMASK(9, 6)
+#define GICV5_IRS_IDR2_LPI BIT(5)
+#define GICV5_IRS_IDR2_ID_BITS GENMASK(4, 0)
+
+#define GICV5_IRS_CR0_IDLE BIT(1)
+#define GICV5_IRS_CR0_IRSEN BIT(0)
+
+#define GICV5_IRS_SPI_SELR_ID GENMASK(23, 0)
+#define GICV5_IRS_SPI_CFGR_TM BIT(0)
+
+#define GICV5_IRS_SYNC_STATUSR_IDLE BIT(0)
+#define GICV5_IRS_SPI_STATUSR_IDLE BIT(0)
+#define GICV5_IRS_PE_STATUSR_IDLE BIT(0)
+#define GICV5_IRS_IST_STATUSR_IDLE BIT(0)
+
+#define GICV5_IRS_IST_BASER_ADDR_MASK GENMASK_ULL(55, 6)
+#define GICV5_IRS_IST_BASER_VALID BIT_ULL(0)
+#define GICV5_IRS_IST_BASER_ADDR_SHIFT 6ULL
+#define GICV5_IRS_IST_CFGR_STRUCTURE BIT(16)
+#define GICV5_IRS_IST_CFGR_ISTSZ GENMASK(8, 7)
+#define GICV5_IRS_IST_CFGR_L2SZ GENMASK(6, 5)
+#define GICV5_IRS_IST_CFGR_LPI_ID_BITS GENMASK(4, 0)
+#define GICV5_IRS_IST_CFGR_ISTSZ_4 0b00
+#define GICV5_IRS_IST_CFGR_ISTSZ_8 0b01
+#define GICV5_IRS_IST_CFGR_ISTSZ_16 0b10
+
/*
* Definitions for GICv5 instructions for the Current Domain
*/
@@ -77,6 +177,11 @@
#define GICV5_GICR_CDNMIA_TYPE_MASK GENMASK_ULL(31, 29)
#define GICV5_GICR_CDNMIA_ID_MASK GENMASK_ULL(23, 0)
+#define GICV5_HWIRQ_TYPE GENMASK_ULL(31, 29)
+#define GICV5_HWIRQ_ID GENMASK_ULL(23, 0)
+#define GICV5_HWIRQ_TYPE_LPI 0x2
+#define GICV5_HWIRQ_TYPE_SPI 0x3
+
#define gicr_insn(insn) read_sysreg_s(GICV5_OP_GICR_##insn)
#define gic_insn(v, insn) write_sysreg_s(v, GICV5_OP_GIC_##insn)
diff --git a/tools/testing/selftests/kvm/include/test_util.h b/tools/testing/selftests/kvm/include/test_util.h
index a6a3e1657895..76bb11bb79f3 100644
--- a/tools/testing/selftests/kvm/include/test_util.h
+++ b/tools/testing/selftests/kvm/include/test_util.h
@@ -172,6 +172,7 @@ struct vm_mem_backing_src_alias {
bool thp_configured(void);
size_t get_trans_hugepagesz(void);
size_t get_def_hugetlb_pagesz(void);
+size_t get_free_hugepages(size_t page_size);
const struct vm_mem_backing_src_alias *vm_mem_backing_src_alias(u32 i);
size_t get_backing_src_pagesz(u32 i);
bool is_backing_src_hugetlb(u32 i);
diff --git a/tools/testing/selftests/kvm/lib/test_util.c b/tools/testing/selftests/kvm/lib/test_util.c
index 4dff72f6bd34..f9263a715704 100644
--- a/tools/testing/selftests/kvm/lib/test_util.c
+++ b/tools/testing/selftests/kvm/lib/test_util.c
@@ -255,6 +255,21 @@ size_t get_def_hugetlb_pagesz(void)
TEST_FAIL("Error in reading /proc/meminfo");
}
+size_t get_free_hugepages(size_t page_size)
+{
+ char path[128];
+ size_t free;
+
+ snprintf(path, sizeof(path),
+ "/sys/kernel/mm/hugepages/hugepages-%zukB/free_hugepages",
+ page_size >> 10);
+ if (!test_sysfs_path(path))
+ return 0;
+
+ free = get_sysfs_val(path);
+ return free * page_size;
+}
+
#define ANON_FLAGS (MAP_PRIVATE | MAP_ANONYMOUS)
#define ANON_HUGE_FLAGS (ANON_FLAGS | MAP_HUGETLB)
diff --git a/tools/testing/selftests/kvm/pre_fault_memory_test.c b/tools/testing/selftests/kvm/pre_fault_memory_test.c
index c57631aab3d3..3082fe09b95f 100644
--- a/tools/testing/selftests/kvm/pre_fault_memory_test.c
+++ b/tools/testing/selftests/kvm/pre_fault_memory_test.c
@@ -12,19 +12,29 @@
#include <processor.h>
#include <pthread.h>
#include <ucall_common.h>
+#include <guest_modes.h>
/* Arbitrarily chosen values */
-#define TEST_SIZE (SZ_2M + PAGE_SIZE)
-#define TEST_NPAGES (TEST_SIZE / PAGE_SIZE)
+#define TEST_BASE_SIZE SZ_2M
#define TEST_SLOT 10
+/* Storage of test info to share with guest code */
+struct test_config {
+ u64 page_size;
+ u64 test_size;
+ u64 test_num_pages;
+};
+
+static struct test_config test_config;
+
static void guest_code(u64 base_gva)
{
volatile u64 val __used;
+ struct test_config *config = &test_config;
int i;
- for (i = 0; i < TEST_NPAGES; i++) {
- u64 *src = (u64 *)(base_gva + i * PAGE_SIZE);
+ for (i = 0; i < config->test_num_pages; i++) {
+ u64 *src = (u64 *)(base_gva + i * config->page_size);
val = *src;
}
@@ -36,6 +46,7 @@ struct slot_worker_data {
struct kvm_vm *vm;
gpa_t gpa;
u32 flags;
+ enum vm_mem_backing_src_type mem_backing_src;
bool worker_ready;
bool prefault_ready;
bool recreate_slot;
@@ -56,14 +67,16 @@ static void *delete_slot_worker(void *__data)
while (!READ_ONCE(data->recreate_slot))
cpu_relax();
- vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, data->gpa,
- TEST_SLOT, TEST_NPAGES, data->flags);
+ vm_userspace_mem_region_add(vm, data->mem_backing_src, data->gpa,
+ TEST_SLOT, test_config.test_num_pages, data->flags);
return NULL;
}
static void pre_fault_memory(struct kvm_vcpu *vcpu, u64 base_gpa, u64 offset,
- u64 size, u64 expected_left, bool private)
+ u64 size, u64 expected_left,
+ enum vm_mem_backing_src_type mem_backing_src,
+ bool private)
{
struct kvm_pre_fault_memory range = {
.gpa = base_gpa + offset,
@@ -74,6 +87,7 @@ static void pre_fault_memory(struct kvm_vcpu *vcpu, u64 base_gpa, u64 offset,
.vm = vcpu->vm,
.gpa = base_gpa,
.flags = private ? KVM_MEM_GUEST_MEMFD : 0,
+ .mem_backing_src = mem_backing_src,
};
bool slot_recreated = false;
pthread_t slot_worker;
@@ -150,8 +164,8 @@ static void pre_fault_memory(struct kvm_vcpu *vcpu, u64 base_gpa, u64 offset,
/*
* Assert success if prefaulting the entire range should succeed, i.e.
* complete with no bytes remaining. Otherwise prefaulting should have
- * failed due to ENOENT (due to RET_PF_EMULATE for emulated MMIO when
- * no memslot exists).
+ * failed due to ENOENT (no memslot exists for the GPA; on x86 this
+ * surfaces via RET_PF_EMULATE).
*/
if (!expected_left)
TEST_ASSERT_VM_VCPU_IOCTL(!ret, KVM_PRE_FAULT_MEMORY, ret, vcpu->vm);
@@ -160,39 +174,82 @@ static void pre_fault_memory(struct kvm_vcpu *vcpu, u64 base_gpa, u64 offset,
KVM_PRE_FAULT_MEMORY, ret, vcpu->vm);
}
-static void __test_pre_fault_memory(unsigned long vm_type, bool private)
+struct test_params {
+ unsigned long vm_type;
+ bool private;
+ enum vm_mem_backing_src_type mem_backing_src;
+};
+
+static void __test_pre_fault_memory(enum vm_guest_mode guest_mode, void *arg)
{
- gpa_t gpa, gva, alignment, guest_page_size;
+ gpa_t gpa, gva, alignment, guest_page_size, host_page_size;
+ gpa_t backing_src_pagesz, mem_page_size;
+ struct test_params *p = arg;
const struct vm_shape shape = {
- .mode = VM_MODE_DEFAULT,
- .type = vm_type,
+ .mode = guest_mode,
+ .type = p->vm_type,
};
struct kvm_vcpu *vcpu;
+ struct kvm_run *run;
struct kvm_vm *vm;
struct ucall uc;
+ pr_info("Testing guest mode: %s\n", vm_guest_mode_string(guest_mode));
+ pr_info("Testing memory backing src type: %s\n",
+ vm_mem_backing_src_alias(p->mem_backing_src)->name);
+
vm = vm_create_shape_with_one_vcpu(shape, &vcpu, guest_code);
- alignment = guest_page_size = vm_guest_mode_params[VM_MODE_DEFAULT].page_size;
- gpa = (vm->max_gfn - TEST_NPAGES) * guest_page_size;
+ guest_page_size = vm_guest_mode_params[guest_mode].page_size;
+ host_page_size = getpagesize();
+ backing_src_pagesz = get_backing_src_pagesz(p->mem_backing_src);
+ mem_page_size = max(host_page_size, backing_src_pagesz);
+
+ test_config.page_size = guest_page_size;
+ test_config.test_size = align_up(TEST_BASE_SIZE + test_config.page_size,
+ mem_page_size);
+ test_config.test_num_pages = vm_calc_num_guest_pages(vm->mode, test_config.test_size);
+
+ gpa = (vm->max_gfn - test_config.test_num_pages) * test_config.page_size;
alignment = SZ_2M;
+ alignment = max(alignment, mem_page_size);
gpa = align_down(gpa, alignment);
gva = gpa & ((1ULL << (vm->va_bits - 1)) - 1);
- vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, gpa, TEST_SLOT,
- TEST_NPAGES, private ? KVM_MEM_GUEST_MEMFD : 0);
- virt_map(vm, gva, gpa, TEST_NPAGES);
+ vm_userspace_mem_region_add(vm, p->mem_backing_src,
+ gpa, TEST_SLOT, test_config.test_num_pages,
+ p->private ? KVM_MEM_GUEST_MEMFD : 0);
+ virt_map(vm, gva, gpa, test_config.test_num_pages);
+
+ if (p->private)
+ vm_mem_set_private(vm, gpa, test_config.test_size);
+
+ pre_fault_memory(vcpu, gpa, 0, test_config.test_size, 0,
+ p->mem_backing_src, p->private);
+ /* Retry the same range after the first prefault attempt. */
+ pre_fault_memory(vcpu, gpa, 0, test_config.test_size, 0,
+ p->mem_backing_src, p->private);
+ pre_fault_memory(vcpu, gpa,
+ test_config.test_size - host_page_size,
+ host_page_size * 2, host_page_size,
+ p->mem_backing_src, p->private);
+ pre_fault_memory(vcpu, gpa, test_config.test_size,
+ host_page_size, host_page_size,
+ p->mem_backing_src, p->private);
- if (private)
- vm_mem_set_private(vm, gpa, TEST_SIZE);
+ vcpu_args_set(vcpu, 1, gva);
- pre_fault_memory(vcpu, gpa, 0, SZ_2M, 0, private);
- pre_fault_memory(vcpu, gpa, SZ_2M, PAGE_SIZE * 2, PAGE_SIZE, private);
- pre_fault_memory(vcpu, gpa, TEST_SIZE, PAGE_SIZE, PAGE_SIZE, private);
+ /* Export the shared variables to the guest. */
+ sync_global_to_guest(vm, test_config);
- vcpu_args_set(vcpu, 1, gva);
vcpu_run(vcpu);
+ run = vcpu->run;
+ TEST_ASSERT(run->exit_reason == UCALL_EXIT_REASON,
+ "Wanted %s, got exit reason: %u (%s)",
+ exit_reason_str(UCALL_EXIT_REASON),
+ run->exit_reason, exit_reason_str(run->exit_reason));
+
switch (get_ucall(vcpu, &uc)) {
case UCALL_ABORT:
REPORT_GUEST_ASSERT(uc);
@@ -207,24 +264,61 @@ static void __test_pre_fault_memory(unsigned long vm_type, bool private)
kvm_vm_free(vm);
}
-static void test_pre_fault_memory(unsigned long vm_type, bool private)
+static void test_pre_fault_memory(unsigned long vm_type, enum vm_mem_backing_src_type backing_src,
+ bool private)
{
+ struct test_params p = {
+ .vm_type = vm_type,
+ .private = private,
+ .mem_backing_src = backing_src,
+ };
+
if (vm_type && !(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(vm_type))) {
pr_info("Skipping tests for vm_type 0x%lx\n", vm_type);
return;
}
- __test_pre_fault_memory(vm_type, private);
+ for_each_guest_mode(__test_pre_fault_memory, &p);
+}
+
+static void help(char *name)
+{
+ puts("");
+ printf("usage: %s [-h] [-m mode] [-s mem-type]\n", name);
+ puts("");
+ guest_modes_help();
+ backing_src_help("-s");
+ puts("");
}
int main(int argc, char *argv[])
{
+ enum vm_mem_backing_src_type backing = DEFAULT_VM_MEM_SRC;
+ int opt;
+
+ guest_modes_append_default();
+
+ while ((opt = getopt(argc, argv, "hm:s:")) != -1) {
+ switch (opt) {
+ case 'm':
+ guest_modes_cmdline(optarg);
+ break;
+ case 's':
+ backing = parse_backing_src_type(optarg);
+ break;
+ case 'h':
+ default:
+ help(argv[0]);
+ exit(0);
+ }
+ }
+
TEST_REQUIRE(kvm_check_cap(KVM_CAP_PRE_FAULT_MEMORY));
- test_pre_fault_memory(0, false);
+ test_pre_fault_memory(0, backing, false);
#ifdef __x86_64__
- test_pre_fault_memory(KVM_X86_SW_PROTECTED_VM, false);
- test_pre_fault_memory(KVM_X86_SW_PROTECTED_VM, true);
+ test_pre_fault_memory(KVM_X86_SW_PROTECTED_VM, backing, false);
+ test_pre_fault_memory(KVM_X86_SW_PROTECTED_VM, backing, true);
#endif
return 0;
}