summaryrefslogtreecommitdiff
path: root/tools/testing
diff options
context:
space:
mode:
authorMarc Zyngier <maz@kernel.org>2026-10-11 10:41:07 +0100
committerMarc Zyngier <maz@kernel.org>2026-10-11 10:41:07 +0100
commit827b245c96a031c5529ba075503e112c287c6ea8 (patch)
tree15ee77db92e5dcbf9012090e33f88c4ba97852e0 /tools/testing
parenta378f75269a483c44df3e435ae84fe74cec6f086 (diff)
parent49664cd0b1db434b6bbf9e37c84b3f448a545dc5 (diff)
downloadlinux-next-827b245c96a031c5529ba075503e112c287c6ea8.tar.gz
linux-next-827b245c96a031c5529ba075503e112c287c6ea8.zip
Merge branch kvm-arm64/selftests-7.4 into kvmarm-master/next
* kvm-arm64/selftests-7.4: : \ : Various fixes for selftests: : : - Render sea_to_user less braindead, though the utility of the test : itself is still debatable : : - Correctly skip testing FEAT_EASE when the feature isn't available : : - Check for consumption of a pended SError : / KVM: arm64: selftests: Check the pending SError state in external_aborts KVM: arm64: selftests: Skip sea_to_user when EINJ places no poison KVM: arm64: selftests: Skip sea_to_user without 1GB hugepages KVM: arm64: selftests: Fix EINJ handling in sea_to_user KVM: arm64: selftests: Fix VM leak in early return Signed-off-by: Marc Zyngier <maz@kernel.org>
Diffstat (limited to 'tools/testing')
-rw-r--r--tools/testing/selftests/kvm/arm64/external_aborts.c23
-rw-r--r--tools/testing/selftests/kvm/arm64/sea_to_user.c75
-rw-r--r--tools/testing/selftests/kvm/include/test_util.h1
-rw-r--r--tools/testing/selftests/kvm/lib/test_util.c15
4 files changed, 99 insertions, 15 deletions
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/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/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)