Test how KVM handles guest SEA when APEI is unable to claim it, and
KVM_CAP_ARM_SEA_TO_USER is enabled.
The behavior is triggered by consuming recoverable memory error (UER)
injected via EINJ. The test asserts two major things:
1. KVM returns to userspace with KVM_EXIT_ARM_SEA exit reason, and
has provided expected fault information, e.g. esr, flags, gva, gpa.
2. Userspace is able to handle KVM_EXIT_ARM_SEA by injecting SEA to
guest and KVM injects expected SEA into the VCPU.
Tested on a data center server running Siryn AmpereOne processor
that has RAS support.
Several things to notice before attempting to run this selftest:
- The test relies on EINJ support in both firmware and kernel to
inject UER. Otherwise the test will be skipped.
- The under-test platform's APEI should be unable to claim the SEA.
Otherwise the test will be skipped.
- Some platform doesn't support notrigger in EINJ, which may cause
APEI and GHES to offline the memory before guest can consume
injected UER, and making test unable to trigger SEA.
Signed-off-by: Jiaqi Yan <jiaqiyan@google.com>
---
tools/arch/arm64/include/asm/esr.h | 2 +
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../testing/selftests/kvm/arm64/sea_to_user.c | 331 ++++++++++++++++++
tools/testing/selftests/kvm/lib/kvm_util.c | 1 +
4 files changed, 335 insertions(+)
create mode 100644 tools/testing/selftests/kvm/arm64/sea_to_user.c
diff --git a/tools/arch/arm64/include/asm/esr.h b/tools/arch/arm64/include/asm/esr.h
index bd592ca815711..0fa17b3af1f78 100644
--- a/tools/arch/arm64/include/asm/esr.h
+++ b/tools/arch/arm64/include/asm/esr.h
@@ -141,6 +141,8 @@
#define ESR_ELx_SF (UL(1) << ESR_ELx_SF_SHIFT)
#define ESR_ELx_AR_SHIFT (14)
#define ESR_ELx_AR (UL(1) << ESR_ELx_AR_SHIFT)
+#define ESR_ELx_VNCR_SHIFT (13)
+#define ESR_ELx_VNCR (UL(1) << ESR_ELx_VNCR_SHIFT)
#define ESR_ELx_CM_SHIFT (8)
#define ESR_ELx_CM (UL(1) << ESR_ELx_CM_SHIFT)
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 148d427ff24be..02a7663c097b5 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -163,6 +163,7 @@ TEST_GEN_PROGS_arm64 += arm64/hypercalls
TEST_GEN_PROGS_arm64 += arm64/external_aborts
TEST_GEN_PROGS_arm64 += arm64/page_fault_test
TEST_GEN_PROGS_arm64 += arm64/psci_test
+TEST_GEN_PROGS_arm64 += arm64/sea_to_user
TEST_GEN_PROGS_arm64 += arm64/set_id_regs
TEST_GEN_PROGS_arm64 += arm64/smccc_filter
TEST_GEN_PROGS_arm64 += arm64/vcpu_width_config
diff --git a/tools/testing/selftests/kvm/arm64/sea_to_user.c b/tools/testing/selftests/kvm/arm64/sea_to_user.c
new file mode 100644
index 0000000000000..573dd790aeb8e
--- /dev/null
+++ b/tools/testing/selftests/kvm/arm64/sea_to_user.c
@@ -0,0 +1,331 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Test KVM returns to userspace with KVM_EXIT_ARM_SEA if host APEI fails
+ * to handle SEA and userspace has opt-ed in KVM_CAP_ARM_SEA_TO_USER.
+ *
+ * After reaching userspace with expected arm_sea info, also test userspace
+ * injecting a synchronous external data abort into the guest.
+ *
+ * This test utilizes EINJ to generate a REAL synchronous external data
+ * abort by consuming a recoverable uncorrectable memory error. Therefore
+ * the device under test must support EINJ in both firmware and host kernel,
+ * including the notrigger feature. Otherwise the test will be skipped.
+ * The under-test platform's APEI should be unable to claim SEA. Otherwise
+ * the test will also be skipped.
+ */
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "test_util.h"
+#include "kvm_util.h"
+#include "processor.h"
+#include "guest_modes.h"
+
+#define PAGE_PRESENT (1ULL << 63)
+#define PAGE_PHYSICAL 0x007fffffffffffffULL
+#define PAGE_ADDR_MASK (~(0xfffULL))
+
+/* Group ISV and ISS[23:14]. */
+#define ESR_ELx_INST_SYNDROME ((ESR_ELx_ISV) | (ESR_ELx_SAS) | \
+ (ESR_ELx_SSE) | (ESR_ELx_SRT_MASK) | \
+ (ESR_ELx_SF) | (ESR_ELx_AR))
+
+#define EINJ_ETYPE "/sys/kernel/debug/apei/einj/error_type"
+#define EINJ_ADDR "/sys/kernel/debug/apei/einj/param1"
+#define EINJ_MASK "/sys/kernel/debug/apei/einj/param2"
+#define EINJ_FLAGS "/sys/kernel/debug/apei/einj/flags"
+#define EINJ_NOTRIGGER "/sys/kernel/debug/apei/einj/notrigger"
+#define EINJ_DOIT "/sys/kernel/debug/apei/einj/error_inject"
+/* Memory Uncorrectable non-fatal. */
+#define ERROR_TYPE_MEMORY_UER 0x10
+/* Memory address and mask valid (param1 and param2). */
+#define MASK_MEMORY_UER 0b10
+
+/* Guest virtual address region = [2G, 3G). */
+#define START_GVA 0x80000000UL
+#define VM_MEM_SIZE 0x40000000UL
+/* Note: EINJ_OFFSET must < VM_MEM_SIZE. */
+#define EINJ_OFFSET 0x01234badUL
+#define EINJ_GVA ((START_GVA) + (EINJ_OFFSET))
+
+static vm_paddr_t einj_gpa;
+static void *einj_hva;
+static uint64_t einj_hpa;
+static bool far_invalid;
+
+static uint64_t translate_to_host_paddr(unsigned long vaddr)
+{
+ uint64_t pinfo;
+ int64_t offset = vaddr / getpagesize() * sizeof(pinfo);
+ int fd;
+ uint64_t page_addr;
+ uint64_t paddr;
+
+ fd = open("/proc/self/pagemap", O_RDONLY);
+ if (fd < 0)
+ ksft_exit_fail_perror("Failed to open /proc/self/pagemap");
+ if (pread(fd, &pinfo, sizeof(pinfo), offset) != sizeof(pinfo)) {
+ close(fd);
+ ksft_exit_fail_perror("Failed to read /proc/self/pagemap");
+ }
+
+ close(fd);
+
+ if ((pinfo & PAGE_PRESENT) == 0)
+ ksft_exit_fail_perror("Page not present");
+
+ page_addr = (pinfo & PAGE_PHYSICAL) << MIN_PAGE_SHIFT;
+ paddr = page_addr + (vaddr & (getpagesize() - 1));
+ return paddr;
+}
+
+static void write_einj_entry(const char *einj_path, uint64_t val)
+{
+ char cmd[256] = {0};
+ FILE *cmdfile = NULL;
+
+ sprintf(cmd, "echo %#lx > %s", val, einj_path);
+ cmdfile = popen(cmd, "r");
+
+ 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");
+}
+
+static void inject_uer(uint64_t paddr)
+{
+ if (access("/sys/firmware/acpi/tables/EINJ", R_OK) == -1)
+ ksft_test_result_skip("EINJ table no available in firmware");
+
+ if (access(EINJ_ETYPE, R_OK | W_OK) == -1)
+ ksft_test_result_skip("EINJ module probably not loaded?");
+
+ write_einj_entry(EINJ_ETYPE, ERROR_TYPE_MEMORY_UER);
+ write_einj_entry(EINJ_FLAGS, MASK_MEMORY_UER);
+ write_einj_entry(EINJ_ADDR, paddr);
+ write_einj_entry(EINJ_MASK, ~0x0UL);
+ write_einj_entry(EINJ_NOTRIGGER, 1);
+ write_einj_entry(EINJ_DOIT, 1);
+}
+
+/*
+ * When host APEI successfully claims the SEA caused by guest_code, kernel
+ * will send SIGBUS signal with BUS_MCEERR_AR to test thread.
+ *
+ * We set up this SIGBUS handler to skip the test for that case.
+ */
+static void sigbus_signal_handler(int sig, siginfo_t *si, void *v)
+{
+ ksft_print_msg("SIGBUS (%d) received, dumping siginfo...\n", sig);
+ ksft_print_msg("si_signo=%d, si_errno=%d, si_code=%d, si_addr=%p\n",
+ si->si_signo, si->si_errno, si->si_code, si->si_addr);
+ if (si->si_code == BUS_MCEERR_AR)
+ ksft_test_result_skip("SEA is claimed by host APEI\n");
+ else
+ ksft_test_result_fail("Exit with signal unhandled\n");
+
+ exit(0);
+}
+
+static void setup_sigbus_handler(void)
+{
+ struct sigaction act;
+
+ memset(&act, 0, sizeof(act));
+ sigemptyset(&act.sa_mask);
+ act.sa_sigaction = sigbus_signal_handler;
+ act.sa_flags = SA_SIGINFO;
+ TEST_ASSERT(sigaction(SIGBUS, &act, NULL) == 0,
+ "Failed to setup SIGBUS handler");
+}
+
+static void guest_code(void)
+{
+ uint64_t guest_data;
+
+ /* Consumes error will cause a SEA. */
+ guest_data = *(uint64_t *)EINJ_GVA;
+
+ GUEST_FAIL("Poison not protected by SEA: gva=%#lx, guest_data=%#lx\n",
+ EINJ_GVA, guest_data);
+}
+
+static void expect_sea_handler(struct ex_regs *regs)
+{
+ u64 esr = read_sysreg(esr_el1);
+ u64 far = read_sysreg(far_el1);
+ bool expect_far_invalid = far_invalid;
+
+ GUEST_PRINTF("Handling Guest SEA\n");
+ GUEST_PRINTF("ESR_EL1=%#lx, FAR_EL1=%#lx\n", esr, far);
+
+ GUEST_ASSERT_EQ(ESR_ELx_EC(esr), ESR_ELx_EC_DABT_CUR);
+ GUEST_ASSERT_EQ(esr & ESR_ELx_FSC_TYPE, ESR_ELx_FSC_EXTABT);
+
+ if (expect_far_invalid) {
+ GUEST_ASSERT_EQ(esr & ESR_ELx_FnV, ESR_ELx_FnV);
+ GUEST_PRINTF("Guest observed garbage value in FAR\n");
+ } else {
+ GUEST_ASSERT_EQ(esr & ESR_ELx_FnV, 0);
+ GUEST_ASSERT_EQ(far, EINJ_GVA);
+ }
+
+ GUEST_DONE();
+}
+
+static void vcpu_inject_sea(struct kvm_vcpu *vcpu)
+{
+ struct kvm_vcpu_events events = {};
+
+ events.exception.ext_dabt_pending = true;
+ vcpu_events_set(vcpu, &events);
+}
+
+static void run_vm(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
+{
+ struct ucall uc;
+ bool guest_done = false;
+ struct kvm_run *run = vcpu->run;
+ u64 esr;
+
+ /* Resume the vCPU after error injection to consume the error. */
+ vcpu_run(vcpu);
+
+ ksft_print_msg("Dump kvm_run info about KVM_EXIT_%s\n",
+ exit_reason_str(run->exit_reason));
+ 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);
+ TEST_ASSERT_EQ(ESR_ELx_ISS2(esr), 0);
+ TEST_ASSERT_EQ((esr & ESR_ELx_INST_SYNDROME), 0);
+ TEST_ASSERT_EQ(esr & ESR_ELx_VNCR, 0);
+
+ if (!(esr & ESR_ELx_FnV)) {
+ ksft_print_msg("Expect gva to match given FnV bit is 0\n");
+ TEST_ASSERT_EQ(run->arm_sea.gva, EINJ_GVA);
+ }
+
+ if (run->arm_sea.flags & KVM_EXIT_ARM_SEA_FLAG_GPA_VALID) {
+ ksft_print_msg("Expect gpa to match given KVM_EXIT_ARM_SEA_FLAG_GPA_VALID is set\n");
+ TEST_ASSERT_EQ(run->arm_sea.gpa, einj_gpa & PAGE_ADDR_MASK);
+ }
+
+ far_invalid = esr & ESR_ELx_FnV;
+
+ /* Inject a SEA into guest and expect handled in SEA handler. */
+ vcpu_inject_sea(vcpu);
+
+ /* Expect the guest to reach GUEST_DONE gracefully. */
+ do {
+ vcpu_run(vcpu);
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_PRINTF:
+ ksft_print_msg("From guest: %s", uc.buffer);
+ break;
+ case UCALL_DONE:
+ ksft_print_msg("Guest done gracefully!\n");
+ guest_done = 1;
+ break;
+ case UCALL_ABORT:
+ ksft_print_msg("Guest aborted!\n");
+ guest_done = 1;
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ default:
+ TEST_FAIL("Unexpected ucall: %lu\n", uc.cmd);
+ }
+ } while (!guest_done);
+}
+
+static struct kvm_vm *vm_create_with_sea_handler(struct kvm_vcpu **vcpu)
+{
+ size_t backing_page_size;
+ size_t guest_page_size;
+ size_t alignment;
+ uint64_t num_guest_pages;
+ vm_paddr_t start_gpa;
+ enum vm_mem_backing_src_type src_type = VM_MEM_SRC_ANONYMOUS_HUGETLB_1GB;
+ struct kvm_vm *vm;
+
+ backing_page_size = get_backing_src_pagesz(src_type);
+ guest_page_size = vm_guest_mode_params[VM_MODE_DEFAULT].page_size;
+ alignment = max(backing_page_size, guest_page_size);
+ num_guest_pages = VM_MEM_SIZE / guest_page_size;
+
+ vm = __vm_create_with_one_vcpu(vcpu, num_guest_pages, guest_code);
+ vm_init_descriptor_tables(vm);
+ vcpu_init_descriptor_tables(*vcpu);
+
+ vm_install_sync_handler(vm,
+ /*vector=*/VECTOR_SYNC_CURRENT,
+ /*ec=*/ESR_ELx_EC_DABT_CUR,
+ /*handler=*/expect_sea_handler);
+
+ start_gpa = (vm->max_gfn - num_guest_pages) * guest_page_size;
+ start_gpa = align_down(start_gpa, alignment);
+
+ vm_userspace_mem_region_add(
+ /*vm=*/vm,
+ /*src_type=*/src_type,
+ /*guest_paddr=*/start_gpa,
+ /*slot=*/1,
+ /*npages=*/num_guest_pages,
+ /*flags=*/0);
+
+ virt_map(vm, START_GVA, start_gpa, num_guest_pages);
+
+ ksft_print_msg("Mapped %#lx pages: gva=%#lx to gpa=%#lx\n",
+ num_guest_pages, START_GVA, start_gpa);
+ return vm;
+}
+
+static void vm_inject_memory_uer(struct kvm_vm *vm)
+{
+ uint64_t guest_data;
+
+ einj_gpa = addr_gva2gpa(vm, EINJ_GVA);
+ einj_hva = addr_gva2hva(vm, EINJ_GVA);
+
+ /* Populate certain data before injecting UER. */
+ *(uint64_t *)einj_hva = 0xBAADCAFE;
+ guest_data = *(uint64_t *)einj_hva;
+ ksft_print_msg("Before EINJect: data=%#lx\n",
+ guest_data);
+
+ einj_hpa = translate_to_host_paddr((unsigned long)einj_hva);
+
+ ksft_print_msg("EINJ_GVA=%#lx, einj_gpa=%#lx, einj_hva=%p, einj_hpa=%#lx\n",
+ EINJ_GVA, einj_gpa, einj_hva, einj_hpa);
+
+ inject_uer(einj_hpa);
+ ksft_print_msg("Memory UER EINJected\n");
+}
+
+int main(int argc, char *argv[])
+{
+ struct kvm_vm *vm;
+ struct kvm_vcpu *vcpu;
+
+ TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_SEA_TO_USER));
+
+ setup_sigbus_handler();
+
+ vm = vm_create_with_sea_handler(&vcpu);
+ vm_enable_cap(vm, KVM_CAP_ARM_SEA_TO_USER, 0);
+ vm_inject_memory_uer(vm);
+ run_vm(vm, vcpu);
+ kvm_vm_free(vm);
+
+ return 0;
+}
diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c
index 6743fbd9bd671..3e2f249e88271 100644
--- a/tools/testing/selftests/kvm/lib/kvm_util.c
+++ b/tools/testing/selftests/kvm/lib/kvm_util.c
@@ -2039,6 +2039,7 @@ static struct exit_reason {
KVM_EXIT_STRING(NOTIFY),
KVM_EXIT_STRING(LOONGARCH_IOCSR),
KVM_EXIT_STRING(MEMORY_FAULT),
+ KVM_EXIT_STRING(ARM_SEA),
};
/*
--
2.51.0.760.g7b8bcc2412-goog
Hi Jiaqi,
I had run into several problems when testing it on different servers. I
haven't figured them out yet but post it early for discussion.
On 2025/10/14 2:59, Jiaqi Yan wrote:
> Test how KVM handles guest SEA when APEI is unable to claim it, and
> KVM_CAP_ARM_SEA_TO_USER is enabled.
>
> The behavior is triggered by consuming recoverable memory error (UER)
> injected via EINJ. The test asserts two major things:
> 1. KVM returns to userspace with KVM_EXIT_ARM_SEA exit reason, and
> has provided expected fault information, e.g. esr, flags, gva, gpa.
> 2. Userspace is able to handle KVM_EXIT_ARM_SEA by injecting SEA to
> guest and KVM injects expected SEA into the VCPU.
>
> Tested on a data center server running Siryn AmpereOne processor
> that has RAS support.
>
> Several things to notice before attempting to run this selftest:
> - The test relies on EINJ support in both firmware and kernel to
> inject UER. Otherwise the test will be skipped.
> - The under-test platform's APEI should be unable to claim the SEA.
> Otherwise the test will be skipped.
> - Some platform doesn't support notrigger in EINJ, which may cause
> APEI and GHES to offline the memory before guest can consume
> injected UER, and making test unable to trigger SEA.
>
> Signed-off-by: Jiaqi Yan <jiaqiyan@google.com>
[...]
> +static void inject_uer(uint64_t paddr)
> +{
> + if (access("/sys/firmware/acpi/tables/EINJ", R_OK) == -1)
> + ksft_test_result_skip("EINJ table no available in firmware");
Missing '\n'.
We should return early (to actually skip the test) if the file can not
be accessed, right?
> +
> + if (access(EINJ_ETYPE, R_OK | W_OK) == -1)
> + ksft_test_result_skip("EINJ module probably not loaded?");
> +
> + write_einj_entry(EINJ_ETYPE, ERROR_TYPE_MEMORY_UER);
> + write_einj_entry(EINJ_FLAGS, MASK_MEMORY_UER);
> + write_einj_entry(EINJ_ADDR, paddr);
> + write_einj_entry(EINJ_MASK, ~0x0UL);
> + write_einj_entry(EINJ_NOTRIGGER, 1);
> + write_einj_entry(EINJ_DOIT, 1);
> +}
> +
> +/*
> + * When host APEI successfully claims the SEA caused by guest_code, kernel
> + * will send SIGBUS signal with BUS_MCEERR_AR to test thread.
> + *
> + * We set up this SIGBUS handler to skip the test for that case.
> + */
> +static void sigbus_signal_handler(int sig, siginfo_t *si, void *v)
> +{
> + ksft_print_msg("SIGBUS (%d) received, dumping siginfo...\n", sig);
> + ksft_print_msg("si_signo=%d, si_errno=%d, si_code=%d, si_addr=%p\n",
> + si->si_signo, si->si_errno, si->si_code, si->si_addr);
> + if (si->si_code == BUS_MCEERR_AR)
> + ksft_test_result_skip("SEA is claimed by host APEI\n");
> + else
> + ksft_test_result_fail("Exit with signal unhandled\n");
> +
> + exit(0);
> +}
> +
> +static void setup_sigbus_handler(void)
> +{
> + struct sigaction act;
> +
> + memset(&act, 0, sizeof(act));
> + sigemptyset(&act.sa_mask);
> + act.sa_sigaction = sigbus_signal_handler;
> + act.sa_flags = SA_SIGINFO;
> + TEST_ASSERT(sigaction(SIGBUS, &act, NULL) == 0,
> + "Failed to setup SIGBUS handler");
> +}
> +
> +static void guest_code(void)
> +{
> + uint64_t guest_data;
> +
> + /* Consumes error will cause a SEA. */
> + guest_data = *(uint64_t *)EINJ_GVA;
> +
> + GUEST_FAIL("Poison not protected by SEA: gva=%#lx, guest_data=%#lx\n",
> + EINJ_GVA, guest_data);
> +}
> +
> +static void expect_sea_handler(struct ex_regs *regs)
> +{
> + u64 esr = read_sysreg(esr_el1);
> + u64 far = read_sysreg(far_el1);
> + bool expect_far_invalid = far_invalid;
> +
> + GUEST_PRINTF("Handling Guest SEA\n");
> + GUEST_PRINTF("ESR_EL1=%#lx, FAR_EL1=%#lx\n", esr, far);
> +
> + GUEST_ASSERT_EQ(ESR_ELx_EC(esr), ESR_ELx_EC_DABT_CUR);
> + GUEST_ASSERT_EQ(esr & ESR_ELx_FSC_TYPE, ESR_ELx_FSC_EXTABT);
> +
> + if (expect_far_invalid) {
> + GUEST_ASSERT_EQ(esr & ESR_ELx_FnV, ESR_ELx_FnV);
I hit this ASSERT with:
# Mapped 0x40000 pages: gva=0x80000000 to gpa=0xff80000000
# Before EINJect: data=0xbaadcafe
# EINJ_GVA=0x81234bad, einj_gpa=0xff81234bad, einj_hva=0xffff41234bad,
einj_hpa=0x202841234bad
# echo 0x10 > /sys/kernel/debug/apei/einj/error_type - done
# echo 0x2 > /sys/kernel/debug/apei/einj/flags - done
# echo 0x202841234bad > /sys/kernel/debug/apei/einj/param1 - done
# echo 0xffffffffffffffff > /sys/kernel/debug/apei/einj/param2 - done
# echo 0x1 > /sys/kernel/debug/apei/einj/notrigger - done
# echo 0x1 > /sys/kernel/debug/apei/einj/error_inject - done
# Memory UER EINJected
# Dump kvm_run info about KVM_EXIT_ARM_SEA
# kvm_run.arm_sea: esr=0x92000610, flags=0
# kvm_run.arm_sea: gva=0, gpa=0
# From guest: Handling Guest SEA
# From guest: ESR_EL1=0x96000010, FAR_EL1=0xaaaadf254828
# Guest aborted!
==== Test Assertion Failure ====
arm64/sea_to_user.c:172: esr & ESR_ELx_FnV == ESR_ELx_FnV
pid=38112 tid=38112 errno=4 - Interrupted system call
1 0x0000000000402f9b: run_vm at sea_to_user.c:246
2 0x0000000000402467: main at sea_to_user.c:330
3 0x0000ffff8e22b03f: ?? ??:0
4 0x0000ffff8e22b117: ?? ??:0
5 0x00000000004026ef: _start at ??:?
0x0 != 0x400 (esr & ESR_ELx_FnV != ESR_ELx_FnV)
It seems that KVM doesn't emulate FnV when injecting an abort.
> + GUEST_PRINTF("Guest observed garbage value in FAR\n");
> + } else {
> + GUEST_ASSERT_EQ(esr & ESR_ELx_FnV, 0);
> + GUEST_ASSERT_EQ(far, EINJ_GVA);
> + }
> +
> + GUEST_DONE();
> +}
> +
> +static void vcpu_inject_sea(struct kvm_vcpu *vcpu)
> +{
> + struct kvm_vcpu_events events = {};
> +
> + events.exception.ext_dabt_pending = true;
> + vcpu_events_set(vcpu, &events);
> +}
> +
> +static void run_vm(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
> +{
> + struct ucall uc;
> + bool guest_done = false;
> + struct kvm_run *run = vcpu->run;
> + u64 esr;
> +
> + /* Resume the vCPU after error injection to consume the error. */
> + vcpu_run(vcpu);
> +
> + ksft_print_msg("Dump kvm_run info about KVM_EXIT_%s\n",
> + exit_reason_str(run->exit_reason));
> + 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);
I can also hit this ASSERT with:
Random seed: 0x6b8b4567
# Mapped 0x40000 pages: gva=0x80000000 to gpa=0xff80000000
# Before EINJect: data=0xbaadcafe
# EINJ_GVA=0x81234bad, einj_gpa=0xff81234bad, einj_hva=0xffff41234bad,
einj_hpa=0x2841234bad
# echo 0x10 > /sys/kernel/debug/apei/einj/error_type - done
# echo 0x2 > /sys/kernel/debug/apei/einj/flags - done
# echo 0x2841234bad > /sys/kernel/debug/apei/einj/param1 - done
# echo 0xffffffffffffffff > /sys/kernel/debug/apei/einj/param2 - done
# echo 0x1 > /sys/kernel/debug/apei/einj/notrigger - done
# echo 0x1 > /sys/kernel/debug/apei/einj/error_inject - done
# Memory UER EINJected
# Dump kvm_run info about KVM_EXIT_MMIO
# kvm_run.arm_sea: esr=0xffff90ba0040, flags=0x691000
# kvm_run.arm_sea: gva=0x100000008, gpa=0
==== Test Assertion Failure ====
arm64/sea_to_user.c:207: exit_reason == (41)
pid=38023 tid=38023 errno=4 - Interrupted system call
1 0x0000000000402d1b: run_vm at sea_to_user.c:207
2 0x0000000000402467: main at sea_to_user.c:330
3 0x0000ffff9122b03f: ?? ??:0
4 0x0000ffff9122b117: ?? ??:0
5 0x00000000004026ef: _start at ??:?
Wanted KVM exit reason: 41 (ARM_SEA), got: 6 (MMIO)
Not sure what's wrong it..
> +
> + 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);
> + TEST_ASSERT_EQ(ESR_ELx_ISS2(esr), 0);
> + TEST_ASSERT_EQ((esr & ESR_ELx_INST_SYNDROME), 0);
> + TEST_ASSERT_EQ(esr & ESR_ELx_VNCR, 0);
> +
> + if (!(esr & ESR_ELx_FnV)) {
> + ksft_print_msg("Expect gva to match given FnV bit is 0\n");
> + TEST_ASSERT_EQ(run->arm_sea.gva, EINJ_GVA);
> + }
> +
> + if (run->arm_sea.flags & KVM_EXIT_ARM_SEA_FLAG_GPA_VALID) {
> + ksft_print_msg("Expect gpa to match given KVM_EXIT_ARM_SEA_FLAG_GPA_VALID is set\n");
> + TEST_ASSERT_EQ(run->arm_sea.gpa, einj_gpa & PAGE_ADDR_MASK);
> + }
> +
> + far_invalid = esr & ESR_ELx_FnV;
Missing sync_global_to_guest()?
Thanks,
Zenghui
On Thu, Dec 11, 2025 at 5:02 AM Zenghui Yu <yuzenghui@huawei.com> wrote:
>
> Hi Jiaqi,
>
> I had run into several problems when testing it on different servers. I
> haven't figured them out yet but post it early for discussion.
Thanks for testing, and I will be happy to work with you to improve
this test code.
>
> On 2025/10/14 2:59, Jiaqi Yan wrote:
> > Test how KVM handles guest SEA when APEI is unable to claim it, and
> > KVM_CAP_ARM_SEA_TO_USER is enabled.
> >
> > The behavior is triggered by consuming recoverable memory error (UER)
> > injected via EINJ. The test asserts two major things:
> > 1. KVM returns to userspace with KVM_EXIT_ARM_SEA exit reason, and
> > has provided expected fault information, e.g. esr, flags, gva, gpa.
> > 2. Userspace is able to handle KVM_EXIT_ARM_SEA by injecting SEA to
> > guest and KVM injects expected SEA into the VCPU.
> >
> > Tested on a data center server running Siryn AmpereOne processor
> > that has RAS support.
> >
> > Several things to notice before attempting to run this selftest:
> > - The test relies on EINJ support in both firmware and kernel to
> > inject UER. Otherwise the test will be skipped.
> > - The under-test platform's APEI should be unable to claim the SEA.
> > Otherwise the test will be skipped.
> > - Some platform doesn't support notrigger in EINJ, which may cause
> > APEI and GHES to offline the memory before guest can consume
> > injected UER, and making test unable to trigger SEA.
> >
> > Signed-off-by: Jiaqi Yan <jiaqiyan@google.com>
>
> [...]
>
> > +static void inject_uer(uint64_t paddr)
> > +{
> > + if (access("/sys/firmware/acpi/tables/EINJ", R_OK) == -1)
> > + ksft_test_result_skip("EINJ table no available in firmware");
>
> Missing '\n'.
Thanks.
>
> We should return early (to actually skip the test) if the file can not
> be accessed, right?
Oh you mean I missed exit(KSFT_SKIP), right? Agreed.
>
> > +
> > + if (access(EINJ_ETYPE, R_OK | W_OK) == -1)
> > + ksft_test_result_skip("EINJ module probably not loaded?");
> > +
> > + write_einj_entry(EINJ_ETYPE, ERROR_TYPE_MEMORY_UER);
> > + write_einj_entry(EINJ_FLAGS, MASK_MEMORY_UER);
> > + write_einj_entry(EINJ_ADDR, paddr);
> > + write_einj_entry(EINJ_MASK, ~0x0UL);
> > + write_einj_entry(EINJ_NOTRIGGER, 1);
> > + write_einj_entry(EINJ_DOIT, 1);
> > +}
> > +
> > +/*
> > + * When host APEI successfully claims the SEA caused by guest_code, kernel
> > + * will send SIGBUS signal with BUS_MCEERR_AR to test thread.
> > + *
> > + * We set up this SIGBUS handler to skip the test for that case.
> > + */
> > +static void sigbus_signal_handler(int sig, siginfo_t *si, void *v)
> > +{
> > + ksft_print_msg("SIGBUS (%d) received, dumping siginfo...\n", sig);
> > + ksft_print_msg("si_signo=%d, si_errno=%d, si_code=%d, si_addr=%p\n",
> > + si->si_signo, si->si_errno, si->si_code, si->si_addr);
> > + if (si->si_code == BUS_MCEERR_AR)
> > + ksft_test_result_skip("SEA is claimed by host APEI\n");
> > + else
> > + ksft_test_result_fail("Exit with signal unhandled\n");
> > +
> > + exit(0);
> > +}
> > +
> > +static void setup_sigbus_handler(void)
> > +{
> > + struct sigaction act;
> > +
> > + memset(&act, 0, sizeof(act));
> > + sigemptyset(&act.sa_mask);
> > + act.sa_sigaction = sigbus_signal_handler;
> > + act.sa_flags = SA_SIGINFO;
> > + TEST_ASSERT(sigaction(SIGBUS, &act, NULL) == 0,
> > + "Failed to setup SIGBUS handler");
> > +}
> > +
> > +static void guest_code(void)
> > +{
> > + uint64_t guest_data;
> > +
> > + /* Consumes error will cause a SEA. */
> > + guest_data = *(uint64_t *)EINJ_GVA;
> > +
> > + GUEST_FAIL("Poison not protected by SEA: gva=%#lx, guest_data=%#lx\n",
> > + EINJ_GVA, guest_data);
> > +}
> > +
> > +static void expect_sea_handler(struct ex_regs *regs)
> > +{
> > + u64 esr = read_sysreg(esr_el1);
> > + u64 far = read_sysreg(far_el1);
> > + bool expect_far_invalid = far_invalid;
> > +
> > + GUEST_PRINTF("Handling Guest SEA\n");
> > + GUEST_PRINTF("ESR_EL1=%#lx, FAR_EL1=%#lx\n", esr, far);
> > +
> > + GUEST_ASSERT_EQ(ESR_ELx_EC(esr), ESR_ELx_EC_DABT_CUR);
> > + GUEST_ASSERT_EQ(esr & ESR_ELx_FSC_TYPE, ESR_ELx_FSC_EXTABT);
> > +
> > + if (expect_far_invalid) {
> > + GUEST_ASSERT_EQ(esr & ESR_ELx_FnV, ESR_ELx_FnV);
>
> I hit this ASSERT with:
>
> # Mapped 0x40000 pages: gva=0x80000000 to gpa=0xff80000000
> # Before EINJect: data=0xbaadcafe
> # EINJ_GVA=0x81234bad, einj_gpa=0xff81234bad, einj_hva=0xffff41234bad,
> einj_hpa=0x202841234bad
> # echo 0x10 > /sys/kernel/debug/apei/einj/error_type - done
> # echo 0x2 > /sys/kernel/debug/apei/einj/flags - done
> # echo 0x202841234bad > /sys/kernel/debug/apei/einj/param1 - done
> # echo 0xffffffffffffffff > /sys/kernel/debug/apei/einj/param2 - done
> # echo 0x1 > /sys/kernel/debug/apei/einj/notrigger - done
> # echo 0x1 > /sys/kernel/debug/apei/einj/error_inject - done
> # Memory UER EINJected
> # Dump kvm_run info about KVM_EXIT_ARM_SEA
> # kvm_run.arm_sea: esr=0x92000610, flags=0
> # kvm_run.arm_sea: gva=0, gpa=0
> # From guest: Handling Guest SEA
> # From guest: ESR_EL1=0x96000010, FAR_EL1=0xaaaadf254828
> # Guest aborted!
> ==== Test Assertion Failure ====
> arm64/sea_to_user.c:172: esr & ESR_ELx_FnV == ESR_ELx_FnV
> pid=38112 tid=38112 errno=4 - Interrupted system call
> 1 0x0000000000402f9b: run_vm at sea_to_user.c:246
> 2 0x0000000000402467: main at sea_to_user.c:330
> 3 0x0000ffff8e22b03f: ?? ??:0
> 4 0x0000ffff8e22b117: ?? ??:0
> 5 0x00000000004026ef: _start at ??:?
> 0x0 != 0x400 (esr & ESR_ELx_FnV != ESR_ELx_FnV)
>
> It seems that KVM doesn't emulate FnV when injecting an abort.
I believe so; this happened to me when I tested on an architecture
that doesn't provide valid FAR. I tried to fix this in [1] in the
past, but didn't get any traction and somehow escaped my attention...
Oliver and Marc, what do you think about [1]? If it sounds like a
valid fix, I can re-send it out as an individual patch.
[1] https://lore.kernel.org/kvmarm/20250604050902.3944054-3-jiaqiyan@google.com
>
> > + GUEST_PRINTF("Guest observed garbage value in FAR\n");
> > + } else {
> > + GUEST_ASSERT_EQ(esr & ESR_ELx_FnV, 0);
> > + GUEST_ASSERT_EQ(far, EINJ_GVA);
> > + }
> > +
> > + GUEST_DONE();
> > +}
> > +
> > +static void vcpu_inject_sea(struct kvm_vcpu *vcpu)
> > +{
> > + struct kvm_vcpu_events events = {};
> > +
> > + events.exception.ext_dabt_pending = true;
> > + vcpu_events_set(vcpu, &events);
> > +}
> > +
> > +static void run_vm(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
> > +{
> > + struct ucall uc;
> > + bool guest_done = false;
> > + struct kvm_run *run = vcpu->run;
> > + u64 esr;
> > +
> > + /* Resume the vCPU after error injection to consume the error. */
> > + vcpu_run(vcpu);
> > +
> > + ksft_print_msg("Dump kvm_run info about KVM_EXIT_%s\n",
> > + exit_reason_str(run->exit_reason));
> > + 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);
>
> I can also hit this ASSERT with:
>
> Random seed: 0x6b8b4567
> # Mapped 0x40000 pages: gva=0x80000000 to gpa=0xff80000000
> # Before EINJect: data=0xbaadcafe
> # EINJ_GVA=0x81234bad, einj_gpa=0xff81234bad, einj_hva=0xffff41234bad,
> einj_hpa=0x2841234bad
> # echo 0x10 > /sys/kernel/debug/apei/einj/error_type - done
> # echo 0x2 > /sys/kernel/debug/apei/einj/flags - done
> # echo 0x2841234bad > /sys/kernel/debug/apei/einj/param1 - done
> # echo 0xffffffffffffffff > /sys/kernel/debug/apei/einj/param2 - done
> # echo 0x1 > /sys/kernel/debug/apei/einj/notrigger - done
> # echo 0x1 > /sys/kernel/debug/apei/einj/error_inject - done
> # Memory UER EINJected
> # Dump kvm_run info about KVM_EXIT_MMIO
> # kvm_run.arm_sea: esr=0xffff90ba0040, flags=0x691000
> # kvm_run.arm_sea: gva=0x100000008, gpa=0
> ==== Test Assertion Failure ====
> arm64/sea_to_user.c:207: exit_reason == (41)
> pid=38023 tid=38023 errno=4 - Interrupted system call
> 1 0x0000000000402d1b: run_vm at sea_to_user.c:207
> 2 0x0000000000402467: main at sea_to_user.c:330
> 3 0x0000ffff9122b03f: ?? ??:0
> 4 0x0000ffff9122b117: ?? ??:0
> 5 0x00000000004026ef: _start at ??:?
> Wanted KVM exit reason: 41 (ARM_SEA), got: 6 (MMIO)
>
> Not sure what's wrong it..
Does your test machine have SDEI or SCI enabled for host APEI? Do you
see any kernel log from "Memory failure:" saying hugetlb page
recovered, and recovered significant earlier than the KVM exit here.
It maybe the kernel has already unmapped hugepage in response to SDEI
or SCI before this test actually consumes memory error, so no SEA is
actually triggered.
>
> > +
> > + 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);
> > + TEST_ASSERT_EQ(ESR_ELx_ISS2(esr), 0);
> > + TEST_ASSERT_EQ((esr & ESR_ELx_INST_SYNDROME), 0);
> > + TEST_ASSERT_EQ(esr & ESR_ELx_VNCR, 0);
> > +
> > + if (!(esr & ESR_ELx_FnV)) {
> > + ksft_print_msg("Expect gva to match given FnV bit is 0\n");
> > + TEST_ASSERT_EQ(run->arm_sea.gva, EINJ_GVA);
> > + }
> > +
> > + if (run->arm_sea.flags & KVM_EXIT_ARM_SEA_FLAG_GPA_VALID) {
> > + ksft_print_msg("Expect gpa to match given KVM_EXIT_ARM_SEA_FLAG_GPA_VALID is set\n");
> > + TEST_ASSERT_EQ(run->arm_sea.gpa, einj_gpa & PAGE_ADDR_MASK);
> > + }
> > +
> > + far_invalid = esr & ESR_ELx_FnV;
>
> Missing sync_global_to_guest()?
Ah, yes, and I can add sync_global_to_guest and get rid of
expect_far_invalid in expect_sea_handler.
>
> Thanks,
> Zenghui
On 2025/12/12 9:53, Jiaqi Yan wrote: > On Thu, Dec 11, 2025 at 5:02 AM Zenghui Yu <yuzenghui@huawei.com> wrote: > > > > I can also hit this ASSERT with: > > > > Random seed: 0x6b8b4567 > > # Mapped 0x40000 pages: gva=0x80000000 to gpa=0xff80000000 > > # Before EINJect: data=0xbaadcafe > > # EINJ_GVA=0x81234bad, einj_gpa=0xff81234bad, einj_hva=0xffff41234bad, > > einj_hpa=0x2841234bad > > # echo 0x10 > /sys/kernel/debug/apei/einj/error_type - done > > # echo 0x2 > /sys/kernel/debug/apei/einj/flags - done > > # echo 0x2841234bad > /sys/kernel/debug/apei/einj/param1 - done > > # echo 0xffffffffffffffff > /sys/kernel/debug/apei/einj/param2 - done > > # echo 0x1 > /sys/kernel/debug/apei/einj/notrigger - done > > # echo 0x1 > /sys/kernel/debug/apei/einj/error_inject - done > > # Memory UER EINJected > > # Dump kvm_run info about KVM_EXIT_MMIO > > # kvm_run.arm_sea: esr=0xffff90ba0040, flags=0x691000 > > # kvm_run.arm_sea: gva=0x100000008, gpa=0 > > ==== Test Assertion Failure ==== > > arm64/sea_to_user.c:207: exit_reason == (41) > > pid=38023 tid=38023 errno=4 - Interrupted system call > > 1 0x0000000000402d1b: run_vm at sea_to_user.c:207 > > 2 0x0000000000402467: main at sea_to_user.c:330 > > 3 0x0000ffff9122b03f: ?? ??:0 > > 4 0x0000ffff9122b117: ?? ??:0 > > 5 0x00000000004026ef: _start at ??:? > > Wanted KVM exit reason: 41 (ARM_SEA), got: 6 (MMIO) > > > > Not sure what's wrong it.. > > Does your test machine have SDEI or SCI enabled for host APEI? Do you > see any kernel log from "Memory failure:" saying hugetlb page > recovered, and recovered significant earlier than the KVM exit here. > It maybe the kernel has already unmapped hugepage in response to SDEI > or SCI before this test actually consumes memory error, so no SEA is > actually triggered. No kernel log was printed when I saw this failure. Thanks, Zenghui
On Fri, Dec 12, 2025 at 1:22 AM Zenghui Yu <yuzenghui@huawei.com> wrote:
>
> On 2025/12/12 9:53, Jiaqi Yan wrote:
> > On Thu, Dec 11, 2025 at 5:02 AM Zenghui Yu <yuzenghui@huawei.com> wrote:
> > >
> > > I can also hit this ASSERT with:
> > >
> > > Random seed: 0x6b8b4567
> > > # Mapped 0x40000 pages: gva=0x80000000 to gpa=0xff80000000
> > > # Before EINJect: data=0xbaadcafe
> > > # EINJ_GVA=0x81234bad, einj_gpa=0xff81234bad, einj_hva=0xffff41234bad,
> > > einj_hpa=0x2841234bad
> > > # echo 0x10 > /sys/kernel/debug/apei/einj/error_type - done
> > > # echo 0x2 > /sys/kernel/debug/apei/einj/flags - done
> > > # echo 0x2841234bad > /sys/kernel/debug/apei/einj/param1 - done
> > > # echo 0xffffffffffffffff > /sys/kernel/debug/apei/einj/param2 - done
> > > # echo 0x1 > /sys/kernel/debug/apei/einj/notrigger - done
> > > # echo 0x1 > /sys/kernel/debug/apei/einj/error_inject - done
> > > # Memory UER EINJected
> > > # Dump kvm_run info about KVM_EXIT_MMIO
> > > # kvm_run.arm_sea: esr=0xffff90ba0040, flags=0x691000
> > > # kvm_run.arm_sea: gva=0x100000008, gpa=0
> > > ==== Test Assertion Failure ====
> > > arm64/sea_to_user.c:207: exit_reason == (41)
> > > pid=38023 tid=38023 errno=4 - Interrupted system call
> > > 1 0x0000000000402d1b: run_vm at sea_to_user.c:207
> > > 2 0x0000000000402467: main at sea_to_user.c:330
> > > 3 0x0000ffff9122b03f: ?? ??:0
> > > 4 0x0000ffff9122b117: ?? ??:0
> > > 5 0x00000000004026ef: _start at ??:?
> > > Wanted KVM exit reason: 41 (ARM_SEA), got: 6 (MMIO)
> > >
> > > Not sure what's wrong it..
> >
> > Does your test machine have SDEI or SCI enabled for host APEI? Do you
> > see any kernel log from "Memory failure:" saying hugetlb page
> > recovered, and recovered significant earlier than the KVM exit here.
> > It maybe the kernel has already unmapped hugepage in response to SDEI
> > or SCI before this test actually consumes memory error, so no SEA is
> > actually triggered.
>
> No kernel log was printed when I saw this failure.
Hmm, and even no CPER logged by APEI/GHES? That makes me suspect the
error wasn't injected successfully.
In that case, I found a bug in sea_to_user.c: GUEST_FAIL is not
handled by run_vm and results in unhandled MMIO.
Here is a fix I tested on my side, with some other minor fixes. Do you
mind trying it?
commit b96a92d1006fbe2752ba133eb76b0c45c9c54265
Author: Jiaqi Yan <jiaqiyan@google.com>
Date: Fri Dec 12 22:27:53 2025 +0000
KVM: selftests: Improve sea_to_user test
Several improvments to the test for KVM_EXIT_ARM_SEA:
- refactor run_vm to catch GUEST_FAIL, instead of causing confusing
kvm exit with unhandled MMIO
- sync far_invalid to guest
- exit with KSFT_SKIP or KSFT_FAIL when should
Change-Id: I8b735de3b669297f0638bea2d32a0b36211f7f5c
diff --git a/tools/testing/selftests/kvm/arm64/sea_to_user.c
b/tools/testing/selftests/kvm/arm64/sea_to_user.c
index 573dd790aeb8e..6fd3cd881b415 100644
--- a/tools/testing/selftests/kvm/arm64/sea_to_user.c
+++ b/tools/testing/selftests/kvm/arm64/sea_to_user.c
@@ -98,11 +98,15 @@ static void write_einj_entry(const char
*einj_path, uint64_t val)
static void inject_uer(uint64_t paddr)
{
- if (access("/sys/firmware/acpi/tables/EINJ", R_OK) == -1)
- ksft_test_result_skip("EINJ table no available in firmware");
+ if (access("/sys/firmware/acpi/tables/EINJ", R_OK) == -1) {
+ ksft_test_result_skip("EINJ table not available in firmware\n");
+ exit(KSFT_SKIP);
+ }
- if (access(EINJ_ETYPE, R_OK | W_OK) == -1)
+ if (access(EINJ_ETYPE, R_OK | W_OK) == -1) {
ksft_test_result_skip("EINJ module probably not loaded?\n");
+ exit(KSFT_SKIP);
+ }
write_einj_entry(EINJ_ETYPE, ERROR_TYPE_MEMORY_UER);
write_einj_entry(EINJ_FLAGS, MASK_MEMORY_UER);
@@ -123,12 +127,13 @@ static void sigbus_signal_handler(int sig,
siginfo_t *si, void *v)
ksft_print_msg("SIGBUS (%d) received, dumping siginfo...\n", sig);
ksft_print_msg("si_signo=%d, si_errno=%d, si_code=%d, si_addr=%p\n",
si->si_signo, si->si_errno, si->si_code, si->si_addr);
- if (si->si_code == BUS_MCEERR_AR)
+ if (si->si_code == BUS_MCEERR_AR) {
ksft_test_result_skip("SEA is claimed by host APEI\n");
- else
+ exit(KSFT_SKIP);
+ } else {
ksft_test_result_fail("Exit with signal unhandled\n");
-
- exit(0);
+ exit(KSFT_FAIL);
+ }
}
static void setup_sigbus_handler(void)
@@ -158,7 +163,6 @@ static void expect_sea_handler(struct ex_regs *regs)
{
u64 esr = read_sysreg(esr_el1);
u64 far = read_sysreg(far_el1);
- bool expect_far_invalid = far_invalid;
GUEST_PRINTF("Handling Guest SEA\n");
GUEST_PRINTF("ESR_EL1=%#lx, FAR_EL1=%#lx\n", esr, far);
@@ -166,7 +170,7 @@ static void expect_sea_handler(struct ex_regs *regs)
GUEST_ASSERT_EQ(ESR_ELx_EC(esr), ESR_ELx_EC_DABT_CUR);
GUEST_ASSERT_EQ(esr & ESR_ELx_FSC_TYPE, ESR_ELx_FSC_EXTABT);
- if (expect_far_invalid) {
+ if (far_invalid) {
GUEST_ASSERT_EQ(esr & ESR_ELx_FnV, ESR_ELx_FnV);
GUEST_PRINTF("Guest observed garbage value in FAR\n");
} else {
@@ -185,25 +189,19 @@ static void vcpu_inject_sea(struct kvm_vcpu *vcpu)
vcpu_events_set(vcpu, &events);
}
-static void run_vm(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
+static void validate_kvm_exit_arm_sea(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
{
- struct ucall uc;
- bool guest_done = false;
struct kvm_run *run = vcpu->run;
u64 esr;
- /* Resume the vCPU after error injection to consume the error. */
- vcpu_run(vcpu);
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_ARM_SEA);
- ksft_print_msg("Dump kvm_run info about KVM_EXIT_%s\n",
- exit_reason_str(run->exit_reason));
+ ksft_print_msg("Dumping kvm_run as arm_sea:\n");
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);
@@ -211,39 +209,48 @@ static void run_vm(struct kvm_vm *vm, struct
kvm_vcpu *vcpu)
TEST_ASSERT_EQ((esr & ESR_ELx_INST_SYNDROME), 0);
TEST_ASSERT_EQ(esr & ESR_ELx_VNCR, 0);
- if (!(esr & ESR_ELx_FnV)) {
- ksft_print_msg("Expect gva to match given FnV bit is 0\n");
+ far_invalid = esr & ESR_ELx_FnV;
+ sync_global_to_guest(vm, far_invalid);
+
+ if (!far_invalid) {
+ ksft_print_msg("Expect gva to match\n");
TEST_ASSERT_EQ(run->arm_sea.gva, EINJ_GVA);
}
if (run->arm_sea.flags & KVM_EXIT_ARM_SEA_FLAG_GPA_VALID) {
- ksft_print_msg("Expect gpa to match given
KVM_EXIT_ARM_SEA_FLAG_GPA_VALID is set\n");
+ ksft_print_msg("Expect gpa to match\n");
TEST_ASSERT_EQ(run->arm_sea.gpa, einj_gpa & PAGE_ADDR_MASK);
}
+}
- far_invalid = esr & ESR_ELx_FnV;
-
- /* Inject a SEA into guest and expect handled in SEA handler. */
- vcpu_inject_sea(vcpu);
+static void run_vm(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
+{
+ struct ucall uc;
+ bool guest_done = false;
/* Expect the guest to reach GUEST_DONE gracefully. */
do {
vcpu_run(vcpu);
- switch (get_ucall(vcpu, &uc)) {
- case UCALL_PRINTF:
- ksft_print_msg("From guest: %s", uc.buffer);
- break;
- case UCALL_DONE:
- ksft_print_msg("Guest done gracefully!\n");
- guest_done = 1;
- break;
- case UCALL_ABORT:
- ksft_print_msg("Guest aborted!\n");
- guest_done = 1;
- REPORT_GUEST_ASSERT(uc);
- break;
- default:
- TEST_FAIL("Unexpected ucall: %lu\n", uc.cmd);
+ if (vcpu->run->exit_reason == KVM_EXIT_ARM_SEA) {
+ validate_kvm_exit_arm_sea(vm, vcpu);
+ vcpu_inject_sea(vcpu);
+ } else {
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_PRINTF:
+ ksft_print_msg("From guest: %s", uc.buffer);
+ break;
+ case UCALL_DONE:
+ ksft_print_msg("Guest done gracefully!\n");
+ guest_done = 1;
+ break;
+ case UCALL_ABORT:
+ ksft_print_msg("Guest aborted!\n");
+ guest_done = 1;
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ default:
+ TEST_FAIL("Unexpected ucall: %lu\n", uc.cmd);
+ }
}
} while (!guest_done);
}
>
> Thanks,
> Zenghui
© 2016 - 2025 Red Hat, Inc.