summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorPaolo Bonzini <pbonzini@redhat.com>2026-10-14 18:26:28 +0200
committerPaolo Bonzini <pbonzini@redhat.com>2026-10-14 18:26:28 +0200
commit439d8f9cb01cb04f9b31910efd0ae8dbdfa4ad97 (patch)
treeea4bdb8cc608ba5ba4b90bfdabff8d521060ec93 /tools
parente17f9dca0a6d29f72662ae0a501a5b236eea06a6 (diff)
parentdd0bc00e3e9201fd49b62ff9cc07890c89003afe (diff)
Merge tag 'kvmarm-7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/kvmarm/kvmarm into HEAD
KVM/arm64 updates for 7.4 * New features - pKVM state isolation: years after pKVM started being enabled upstream, we finally get the basics in the form of state isolation. The vcpu state is now off-limit for the host for protected VMs. - pKVM also grows a hypervisor-private memory allocator, allowing it to deal with sub-page allocation, much like everywhere else. - pKVM also lands a trace_printk() equivalent, which hopefully will make debugging a bit easier. - First shot at annotating portions of the KVM/arm64 code to be extracted and built on s390. The real fun is yet to come. - Allow pre-faulting of stage-2 canonical mappings from userspace, joining x86 and s390 in implementing this feature. - Add a reverse mapping infrastructure for shadow stage-2, significantly improving performance when MMU notifiers kick in, and avoiding full unmap of all shadow tables for this VM. - Next step of GICv5 enablement, adding support for the IRS block, and providing both SPI and LPI support. This is the first point where KVM/arm64 can support an SMP GICv5 guest. * Improvements - As a preliminary to eventually enabling CCA, add a bunch of infrastructure to KVM/arm64, splitting some of the basic operations into individual backends (normal VM, normal pKVM VM, protected pKVM VM). This allows CCA to later slot in without polluting the rest of KVM with its firmware-driven nonsense. - Make ID registers immutable from the point where a vcpu is able to run, even if it fails to run. This prevents KVM from sampling untrusted values. - Restore some type checking to the EL2 object. KVM/arm64 originally had some strong type checking between the kernel and the EL2 object, but lost it along the way when the private hypercall interface was (somewhat pointlessly) converted to SMCCC. Type checking is back, syscall style, and with added sparse goodies. - Expand the FFA support to FFA_NOTIFICATION_*, and add some sanitisation of the protocol that pKVM relays to the "trusted" runtime. * Fixes - Make GICv3 RD assignment more robust to userspace nonsense - Convert the remaining MMU fault handler backends to kvm_s2_fault_vma_info and co, fixing a number of bugs in the process. - Fix the SError/SEA selftests to be both more robust and less braindead. - Fix a smattering of documentation bugs, some of them as a result of our AI friends spitting out credible assertions based on non-existing symbols. - The usual collection of low impact bug fixes (SMCCC, timers, PMU).
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;
}