mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2 0/3] KVM: selftests: arm64: Make sea_to_user skip cleanly
@ 2026-08-18 11:29 Like Xu
  2026-08-18 11:29 ` [PATCH v2 1/3] KVM: selftests: arm64: Fix EINJ handling in sea_to_user Like Xu
                   ` (4 more replies)
  0 siblings, 5 replies; 8+ messages in thread
From: Like Xu @ 2026-08-18 11:29 UTC (permalink / raw)
  To: Marc Zyngier, Oliver Upton
  Cc: jiaqiyan, kvmarm, Paolo Bonzini, kvm, linux-kernel

This series hardens the arm64 sea_to_user selftest so that it reports a
clean skip on hosts that cannot actually run it, instead of aborting or
silently passing.

The test drives a real recoverable memory UER through APEI EINJ and
expects the guest to consume the poison and exit to userspace with
KVM_EXIT_ARM_SEA. That relies on host and firmware support that is not
present everywhere, and each unmet dependency is now turned into a skip:

  - EINJ injection is driven directly through debugfs, and an unusable
    EINJ (not built, no firmware support, or not running as root) is
    classified from errno and skipped rather than mishandled.
  - Guest memory is backed by 1GB hugepages; with an empty pool the test
    now skips up front instead of failing an mmap() with -ENOMEM.
  - Some firmware only arms EINJ poison as part of the trigger step that
    notrigger=1 skips, so nothing consumable is left for the guest to
    read. That case is detected and skipped rather than reported as a
    spurious KVM_EXIT_MMIO failure.

Tested on an arm64 host with CONFIG_ACPI_APEI_EINJ: on a platform whose
firmware places no consumable poison under notrigger=1 the test now
skips cleanly, and the earlier -ENOMEM and abort paths are gone.

v1 [1] was a single patch doing only the 1GB hugepage check. Following
Marc's review [2], it is expanded into a series that also fixes the EINJ
handling the test depends on.

Test Results:

  Case 1 (echo 0 > /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages): 

  # timeout set to 120
  # selftests: kvm: sea_to_user
  # Random seed: 0x6b8b4567
  # 1..0 # SKIP - Requirement not met: get_free_hugepages(backing_page_size) >= VM_MEM_SIZE
  ok 1 selftests: kvm: sea_to_user # SKIP
  # 1 skipped test(s) detected.  Consider enabling relevant config options to improve coverage.
  # Totals: pass:0 fail:0 xfail:0 xpass:0 skip:1 error:0

  Case 2 (rmmod einj):

  # selftests: kvm: sea_to_user
  # 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=0x42c1234bad
  # 1..0 # SKIP need CONFIG_ACPI_APEI_EINJ and firmware EINJ support
  ok 1 selftests: kvm: sea_to_user # SKIP
  # 1 skipped test(s) detected.  Consider enabling relevant config options to improve coverage.
  # Totals: pass:0 fail:0 xfail:0 xpass:0 skip:1 error:0

  Case 3 (modprobe einj):

  # selftests: kvm: sea_to_user
  # 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=0x42c1234bad
  # # 0x10 > /sys/kernel/debug/apei/einj/error_type - done
  # # 0x2 > /sys/kernel/debug/apei/einj/flags - done
  # # 0x42c1234bad > /sys/kernel/debug/apei/einj/param1 - done
  # # 0xffffffffffffffff > /sys/kernel/debug/apei/einj/param2 - done
  # # 0x1 > /sys/kernel/debug/apei/einj/notrigger - done
  # # 0x1 > /sys/kernel/debug/apei/einj/error_inject - done
  # # Memory UER EINJected
  # # SIGBUS (7) received, dumping siginfo...
  # # si_signo=7, si_errno=0, si_code=128, si_addr=(nil)
  # not ok 1 Exit with signal unhandled
  ok 1 selftests: kvm: sea_to_user
  # Totals: pass:1 fail:0 xfail:0 xpass:0 skip:0 error:0

Changes since v1:
  - New patch 1: drive EINJ injection directly through debugfs with
    open()/write() instead of access()+popen(). Classify the open()
    errno so an unprivileged run skips as "requires root" instead of
    falsely reporting missing firmware EINJ, and report a genuine write
    failure instead of the ambiguous "Failed... Success (0)".
  - New patch 3: detect when notrigger=1 leaves no consumable poison and
    skip cleanly, instead of exiting with a confusing KVM_EXIT_MMIO.
  - Patch 2 is the original v1 change, unchanged.

Not yet addressed from [2]: dropping the hard 1GB-hugepage and 4kB
base-page requirements. Suggestions on an acceptable backing scheme are
welcome.

[1] https://lore.kernel.org/kvm/20260817080759.25601-1-likexu@tencent.com/
[2] https://lore.kernel.org/all/86y0le9cvz.wl-maz@kernel.org/

Like Xu (3):
  KVM: selftests: arm64: Fix EINJ handling in sea_to_user
  KVM: selftests: arm64: Skip sea_to_user without 1GB hugepages
  KVM: selftests: arm64: Skip sea_to_user when EINJ places no poison

 .../testing/selftests/kvm/arm64/sea_to_user.c | 75 +++++++++++++++----
 .../testing/selftests/kvm/include/test_util.h |  1 +
 tools/testing/selftests/kvm/lib/test_util.c   | 15 ++++
 3 files changed, 77 insertions(+), 14 deletions(-)

-- 
2.50.1 (Apple Git-155)


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v2 1/3] KVM: selftests: arm64: Fix EINJ handling in sea_to_user
  2026-08-18 11:29 [PATCH v2 0/3] KVM: selftests: arm64: Make sea_to_user skip cleanly Like Xu
@ 2026-08-18 11:29 ` Like Xu
  2026-08-18 11:29 ` [PATCH v2 2/3] KVM: selftests: arm64: Skip sea_to_user without 1GB hugepages Like Xu
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 8+ messages in thread
From: Like Xu @ 2026-08-18 11:29 UTC (permalink / raw)
  To: Marc Zyngier, Oliver Upton
  Cc: jiaqiyan, kvmarm, Paolo Bonzini, kvm, linux-kernel

sea_to_user drives EINJ injection through popen("echo ... > file"). A
failed write is hidden behind the shell pipeline, so a botched injection
is silently ignored and the test proceeds as if a poison were placed.
Drive the debugfs interface directly so a write failure is reported
rather than swallowed.

The EINJ support probe is also unreliable. It checks the ACPI EINJ table
with access(R_OK), but reading that table needs CAP_SYS_ADMIN, so the
check gives a false negative for unprivileged runs and skips a test that
could otherwise report a real problem. Probe by opening the debugfs
injection interface instead and classify errno: absence means EINJ is
not built or firmware lacks support, EACCES/EPERM means the test is not
running as root -- both are skips -- while anything else is a genuine
failure.

The injection path is only reachable through debugfs; the kernel EINJ
driver exposes no other interface, so the test must depend on it.

Link: https://lore.kernel.org/kvm/86y0le9cvz.wl-maz@kernel.org/
Signed-off-by: Like Xu <likexu@tencent.com>
---
 .../testing/selftests/kvm/arm64/sea_to_user.c | 45 ++++++++++++++-----
 1 file changed, 33 insertions(+), 12 deletions(-)

diff --git a/tools/testing/selftests/kvm/arm64/sea_to_user.c b/tools/testing/selftests/kvm/arm64/sea_to_user.c
index e96d8982c28b8..1c2a743ca8e23 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);
-- 
2.50.1 (Apple Git-155)


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v2 2/3] KVM: selftests: arm64: Skip sea_to_user without 1GB hugepages
  2026-08-18 11:29 [PATCH v2 0/3] KVM: selftests: arm64: Make sea_to_user skip cleanly Like Xu
  2026-08-18 11:29 ` [PATCH v2 1/3] KVM: selftests: arm64: Fix EINJ handling in sea_to_user Like Xu
@ 2026-08-18 11:29 ` Like Xu
  2026-08-23 18:42   ` Jiaqi Yan
  2026-08-18 11:29 ` [PATCH v2 3/3] KVM: selftests: arm64: Skip sea_to_user when EINJ places no poison Like Xu
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 8+ messages in thread
From: Like Xu @ 2026-08-18 11:29 UTC (permalink / raw)
  To: Marc Zyngier, Oliver Upton
  Cc: jiaqiyan, kvmarm, Paolo Bonzini, kvm, linux-kernel

sea_to_user backs guest memory with 1GB hugepages but never checks that
any are reserved. On a host with an empty pool it aborts instead of
skipping:

  kvm_syscalls.h:68: mem != MAP_FAILED, errno=12 (ENOMEM)
  not ok 1 selftests: kvm: sea_to_user # exit=254

The mmap() of the hugetlb region fails with -ENOMEM because no 1GB pages
are available, and the test treats that as a hard failure even though it
simply cannot run without the backing pages. Check the free 1GB hugepage
count up front and skip cleanly when the host cannot satisfy the region.

Signed-off-by: Like Xu <likexu@tencent.com>
---
 tools/testing/selftests/kvm/arm64/sea_to_user.c |  6 ++++++
 tools/testing/selftests/kvm/include/test_util.h |  1 +
 tools/testing/selftests/kvm/lib/test_util.c     | 15 +++++++++++++++
 3 files changed, 22 insertions(+)

diff --git a/tools/testing/selftests/kvm/arm64/sea_to_user.c b/tools/testing/selftests/kvm/arm64/sea_to_user.c
index 1c2a743ca8e23..7cf95da8e594d 100644
--- a/tools/testing/selftests/kvm/arm64/sea_to_user.c
+++ b/tools/testing/selftests/kvm/arm64/sea_to_user.c
@@ -281,6 +281,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 a56271c237ae9..0624922c2735d 100644
--- a/tools/testing/selftests/kvm/include/test_util.h
+++ b/tools/testing/selftests/kvm/include/test_util.h
@@ -168,6 +168,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 bab1bd2b775b6..29f9c1d60b1b7 100644
--- a/tools/testing/selftests/kvm/lib/test_util.c
+++ b/tools/testing/selftests/kvm/lib/test_util.c
@@ -222,6 +222,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)
 
-- 
2.50.1 (Apple Git-155)


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v2 3/3] KVM: selftests: arm64: Skip sea_to_user when EINJ places no poison
  2026-08-18 11:29 [PATCH v2 0/3] KVM: selftests: arm64: Make sea_to_user skip cleanly Like Xu
  2026-08-18 11:29 ` [PATCH v2 1/3] KVM: selftests: arm64: Fix EINJ handling in sea_to_user Like Xu
  2026-08-18 11:29 ` [PATCH v2 2/3] KVM: selftests: arm64: Skip sea_to_user without 1GB hugepages Like Xu
@ 2026-08-18 11:29 ` Like Xu
  2026-08-23 18:57   ` Jiaqi Yan
  2026-08-22 17:58 ` [PATCH v2 0/3] KVM: selftests: arm64: Make sea_to_user skip cleanly Jiaqi Yan
  2026-09-14 12:04 ` Marc Zyngier
  4 siblings, 1 reply; 8+ messages in thread
From: Like Xu @ 2026-08-18 11:29 UTC (permalink / raw)
  To: Marc Zyngier, Oliver Upton
  Cc: jiaqiyan, kvmarm, Paolo Bonzini, kvm, linux-kernel

sea_to_user injects a memory UER with EINJ notrigger=1 and expects the
guest to consume it and trap to KVM as an SEA. On some firmware the test
aborts instead:

  arm64/sea_to_user.c:223: exit_reason == (41)
  Wanted KVM exit reason: 41 (ARM_SEA), got: 6 (MMIO)

notrigger=1 asks firmware to arm the poison without consuming it, but on
these platforms the poison is only armed as part of the trigger step that
notrigger skips, so nothing consumable is left in memory. The guest reads
back the sentinel, no SEA occurs, and GUEST_FAIL fires. On arm64 a ucall
is delivered as an MMIO write, which surfaces as the KVM_EXIT_MMIO above
rather than a KVM bug.

The file already documents that the test must be skipped when firmware
cannot deliver a consumable error. Detect the guest abort that follows a
missing SEA and skip, instead of failing on a firmware limitation the
test cannot control.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Like Xu <likexu@tencent.com>
---
 .../testing/selftests/kvm/arm64/sea_to_user.c | 24 +++++++++++++++++--
 1 file changed, 22 insertions(+), 2 deletions(-)

diff --git a/tools/testing/selftests/kvm/arm64/sea_to_user.c b/tools/testing/selftests/kvm/arm64/sea_to_user.c
index 7cf95da8e594d..d4af6e0eed288 100644
--- a/tools/testing/selftests/kvm/arm64/sea_to_user.c
+++ b/tools/testing/selftests/kvm/arm64/sea_to_user.c
@@ -215,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);
-- 
2.50.1 (Apple Git-155)


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 0/3] KVM: selftests: arm64: Make sea_to_user skip cleanly
  2026-08-18 11:29 [PATCH v2 0/3] KVM: selftests: arm64: Make sea_to_user skip cleanly Like Xu
                   ` (2 preceding siblings ...)
  2026-08-18 11:29 ` [PATCH v2 3/3] KVM: selftests: arm64: Skip sea_to_user when EINJ places no poison Like Xu
@ 2026-08-22 17:58 ` Jiaqi Yan
  2026-09-14 12:04 ` Marc Zyngier
  4 siblings, 0 replies; 8+ messages in thread
From: Jiaqi Yan @ 2026-08-22 17:58 UTC (permalink / raw)
  To: Like Xu
  Cc: Marc Zyngier, Oliver Upton, kvmarm, Paolo Bonzini, kvm, linux-kernel

On Tue, Aug 18, 2026 at 4:29 AM Like Xu <like.xu.linux@gmail.com> wrote:
>
> This series hardens the arm64 sea_to_user selftest so that it reports a
> clean skip on hosts that cannot actually run it, instead of aborting or
> silently passing.
>
> The test drives a real recoverable memory UER through APEI EINJ and
> expects the guest to consume the poison and exit to userspace with
> KVM_EXIT_ARM_SEA. That relies on host and firmware support that is not
> present everywhere, and each unmet dependency is now turned into a skip:
>
>   - EINJ injection is driven directly through debugfs, and an unusable
>     EINJ (not built, no firmware support, or not running as root) is
>     classified from errno and skipped rather than mishandled.
>   - Guest memory is backed by 1GB hugepages; with an empty pool the test
>     now skips up front instead of failing an mmap() with -ENOMEM.
>   - Some firmware only arms EINJ poison as part of the trigger step that
>     notrigger=1 skips, so nothing consumable is left for the guest to
>     read. That case is detected and skipped rather than reported as a
>     spurious KVM_EXIT_MMIO failure.

Thanks for these fixes. Just wonder, do you mind also integrating two
previous fix attempts in [*] into this series:
1. Refactor run_vm to catch GUEST_FAIL, instead of causing confusing
unhandled MMIO kvm exit.
2. Sync far_invalid to guest.

, given the high level goal is to harden sea_to_user.c?

The first one will make the test result less confusing for the runner.
The 2nd one improves readability. You can re-use my code in the patch.

[*] https://lore.kernel.org/kvmarm/20260130192837.890688-1-jiaqiyan@google.com

>
> Tested on an arm64 host with CONFIG_ACPI_APEI_EINJ: on a platform whose
> firmware places no consumable poison under notrigger=1 the test now
> skips cleanly, and the earlier -ENOMEM and abort paths are gone.
>
> v1 [1] was a single patch doing only the 1GB hugepage check. Following
> Marc's review [2], it is expanded into a series that also fixes the EINJ
> handling the test depends on.
>
> Test Results:
>
>   Case 1 (echo 0 > /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages):
>
>   # timeout set to 120
>   # selftests: kvm: sea_to_user
>   # Random seed: 0x6b8b4567
>   # 1..0 # SKIP - Requirement not met: get_free_hugepages(backing_page_size) >= VM_MEM_SIZE
>   ok 1 selftests: kvm: sea_to_user # SKIP
>   # 1 skipped test(s) detected.  Consider enabling relevant config options to improve coverage.
>   # Totals: pass:0 fail:0 xfail:0 xpass:0 skip:1 error:0
>
>   Case 2 (rmmod einj):
>
>   # selftests: kvm: sea_to_user
>   # 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=0x42c1234bad
>   # 1..0 # SKIP need CONFIG_ACPI_APEI_EINJ and firmware EINJ support
>   ok 1 selftests: kvm: sea_to_user # SKIP
>   # 1 skipped test(s) detected.  Consider enabling relevant config options to improve coverage.
>   # Totals: pass:0 fail:0 xfail:0 xpass:0 skip:1 error:0
>
>   Case 3 (modprobe einj):
>
>   # selftests: kvm: sea_to_user
>   # 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=0x42c1234bad
>   # # 0x10 > /sys/kernel/debug/apei/einj/error_type - done
>   # # 0x2 > /sys/kernel/debug/apei/einj/flags - done
>   # # 0x42c1234bad > /sys/kernel/debug/apei/einj/param1 - done
>   # # 0xffffffffffffffff > /sys/kernel/debug/apei/einj/param2 - done
>   # # 0x1 > /sys/kernel/debug/apei/einj/notrigger - done
>   # # 0x1 > /sys/kernel/debug/apei/einj/error_inject - done
>   # # Memory UER EINJected
>   # # SIGBUS (7) received, dumping siginfo...
>   # # si_signo=7, si_errno=0, si_code=128, si_addr=(nil)
>   # not ok 1 Exit with signal unhandled
>   ok 1 selftests: kvm: sea_to_user
>   # Totals: pass:1 fail:0 xfail:0 xpass:0 skip:0 error:0
>
> Changes since v1:
>   - New patch 1: drive EINJ injection directly through debugfs with
>     open()/write() instead of access()+popen(). Classify the open()
>     errno so an unprivileged run skips as "requires root" instead of
>     falsely reporting missing firmware EINJ, and report a genuine write
>     failure instead of the ambiguous "Failed... Success (0)".
>   - New patch 3: detect when notrigger=1 leaves no consumable poison and
>     skip cleanly, instead of exiting with a confusing KVM_EXIT_MMIO.
>   - Patch 2 is the original v1 change, unchanged.
>
> Not yet addressed from [2]: dropping the hard 1GB-hugepage and 4kB
> base-page requirements. Suggestions on an acceptable backing scheme are
> welcome.
>
> [1] https://lore.kernel.org/kvm/20260817080759.25601-1-likexu@tencent.com/
> [2] https://lore.kernel.org/all/86y0le9cvz.wl-maz@kernel.org/
>
> Like Xu (3):
>   KVM: selftests: arm64: Fix EINJ handling in sea_to_user
>   KVM: selftests: arm64: Skip sea_to_user without 1GB hugepages
>   KVM: selftests: arm64: Skip sea_to_user when EINJ places no poison
>
>  .../testing/selftests/kvm/arm64/sea_to_user.c | 75 +++++++++++++++----
>  .../testing/selftests/kvm/include/test_util.h |  1 +
>  tools/testing/selftests/kvm/lib/test_util.c   | 15 ++++
>  3 files changed, 77 insertions(+), 14 deletions(-)
>
> --
> 2.50.1 (Apple Git-155)
>

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 2/3] KVM: selftests: arm64: Skip sea_to_user without 1GB hugepages
  2026-08-18 11:29 ` [PATCH v2 2/3] KVM: selftests: arm64: Skip sea_to_user without 1GB hugepages Like Xu
@ 2026-08-23 18:42   ` Jiaqi Yan
  0 siblings, 0 replies; 8+ messages in thread
From: Jiaqi Yan @ 2026-08-23 18:42 UTC (permalink / raw)
  To: Like Xu
  Cc: Marc Zyngier, Oliver Upton, kvmarm, Paolo Bonzini, kvm, linux-kernel

On Tue, Aug 18, 2026 at 4:29 AM Like Xu <like.xu.linux@gmail.com> wrote:
>
> sea_to_user backs guest memory with 1GB hugepages but never checks that
> any are reserved. On a host with an empty pool it aborts instead of
> skipping:
>
>   kvm_syscalls.h:68: mem != MAP_FAILED, errno=12 (ENOMEM)
>   not ok 1 selftests: kvm: sea_to_user # exit=254
>
> The mmap() of the hugetlb region fails with -ENOMEM because no 1GB pages
> are available, and the test treats that as a hard failure even though it
> simply cannot run without the backing pages. Check the free 1GB hugepage
> count up front and skip cleanly when the host cannot satisfy the region.

Looks good to me, just one nit below.

Reviewed-by: Jiaqi Yan <jiaqiyan@google.com>

>
> Signed-off-by: Like Xu <likexu@tencent.com>
> ---
>  tools/testing/selftests/kvm/arm64/sea_to_user.c |  6 ++++++
>  tools/testing/selftests/kvm/include/test_util.h |  1 +
>  tools/testing/selftests/kvm/lib/test_util.c     | 15 +++++++++++++++
>  3 files changed, 22 insertions(+)
>
> diff --git a/tools/testing/selftests/kvm/arm64/sea_to_user.c b/tools/testing/selftests/kvm/arm64/sea_to_user.c
> index 1c2a743ca8e23..7cf95da8e594d 100644
> --- a/tools/testing/selftests/kvm/arm64/sea_to_user.c
> +++ b/tools/testing/selftests/kvm/arm64/sea_to_user.c
> @@ -281,6 +281,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);

How about moving the src_type to main or global? Then we can
TEST_REQUIRE() in main().


> +
>         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 a56271c237ae9..0624922c2735d 100644
> --- a/tools/testing/selftests/kvm/include/test_util.h
> +++ b/tools/testing/selftests/kvm/include/test_util.h
> @@ -168,6 +168,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 bab1bd2b775b6..29f9c1d60b1b7 100644
> --- a/tools/testing/selftests/kvm/lib/test_util.c
> +++ b/tools/testing/selftests/kvm/lib/test_util.c
> @@ -222,6 +222,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)
>
> --
> 2.50.1 (Apple Git-155)
>

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 3/3] KVM: selftests: arm64: Skip sea_to_user when EINJ places no poison
  2026-08-18 11:29 ` [PATCH v2 3/3] KVM: selftests: arm64: Skip sea_to_user when EINJ places no poison Like Xu
@ 2026-08-23 18:57   ` Jiaqi Yan
  0 siblings, 0 replies; 8+ messages in thread
From: Jiaqi Yan @ 2026-08-23 18:57 UTC (permalink / raw)
  To: Like Xu
  Cc: Marc Zyngier, Oliver Upton, kvmarm, Paolo Bonzini, kvm, linux-kernel

On Tue, Aug 18, 2026 at 4:29 AM Like Xu <like.xu.linux@gmail.com> wrote:
>
> sea_to_user injects a memory UER with EINJ notrigger=1 and expects the
> guest to consume it and trap to KVM as an SEA. On some firmware the test
> aborts instead:
>
>   arm64/sea_to_user.c:223: exit_reason == (41)
>   Wanted KVM exit reason: 41 (ARM_SEA), got: 6 (MMIO)
>
> notrigger=1 asks firmware to arm the poison without consuming it, but on
> these platforms the poison is only armed as part of the trigger step that
> notrigger skips, so nothing consumable is left in memory. The guest reads
> back the sentinel, no SEA occurs, and GUEST_FAIL fires. On arm64 a ucall
> is delivered as an MMIO write, which surfaces as the KVM_EXIT_MMIO above
> rather than a KVM bug.

Thanks for the fixes, looks good to me.

Reviewed-by: Jiaqi Yan <jiaqiyan@google.com>

>
> The file already documents that the test must be skipped when firmware
> cannot deliver a consumable error. Detect the guest abort that follows a
> missing SEA and skip, instead of failing on a firmware limitation the
> test cannot control.
>
> Assisted-by: Claude:claude-opus-4-8
> Signed-off-by: Like Xu <likexu@tencent.com>
> ---
>  .../testing/selftests/kvm/arm64/sea_to_user.c | 24 +++++++++++++++++--
>  1 file changed, 22 insertions(+), 2 deletions(-)
>
> diff --git a/tools/testing/selftests/kvm/arm64/sea_to_user.c b/tools/testing/selftests/kvm/arm64/sea_to_user.c
> index 7cf95da8e594d..d4af6e0eed288 100644
> --- a/tools/testing/selftests/kvm/arm64/sea_to_user.c
> +++ b/tools/testing/selftests/kvm/arm64/sea_to_user.c
> @@ -215,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);
> --
> 2.50.1 (Apple Git-155)
>

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 0/3] KVM: selftests: arm64: Make sea_to_user skip cleanly
  2026-08-18 11:29 [PATCH v2 0/3] KVM: selftests: arm64: Make sea_to_user skip cleanly Like Xu
                   ` (3 preceding siblings ...)
  2026-08-22 17:58 ` [PATCH v2 0/3] KVM: selftests: arm64: Make sea_to_user skip cleanly Jiaqi Yan
@ 2026-09-14 12:04 ` Marc Zyngier
  4 siblings, 0 replies; 8+ messages in thread
From: Marc Zyngier @ 2026-09-14 12:04 UTC (permalink / raw)
  To: Oliver Upton, Like Xu; +Cc: jiaqiyan, kvmarm, Paolo Bonzini, kvm, linux-kernel

On Tue, 18 Aug 2026 19:29:17 +0800, Like Xu wrote:
> This series hardens the arm64 sea_to_user selftest so that it reports a
> clean skip on hosts that cannot actually run it, instead of aborting or
> silently passing.
> 
> The test drives a real recoverable memory UER through APEI EINJ and
> expects the guest to consume the poison and exit to userspace with
> KVM_EXIT_ARM_SEA. That relies on host and firmware support that is not
> present everywhere, and each unmet dependency is now turned into a skip:
> 
> [...]

Applied to next, thanks!

[1/3] KVM: selftests: arm64: Fix EINJ handling in sea_to_user
      commit: 05fd5844cc83c1a51b0a38113a7ee196a69bda1d
[2/3] KVM: selftests: arm64: Skip sea_to_user without 1GB hugepages
      commit: 2665ac1063e71f23888091a0bdb1b96297bd0370
[3/3] KVM: selftests: arm64: Skip sea_to_user when EINJ places no poison
      commit: 754c7b7d45074337972c5c3ad76ed9e7b103e5d0

Cheers,

	M.
-- 
Without deviation from the norm, progress is not possible.



^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-09-14 12:04 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-18 11:29 [PATCH v2 0/3] KVM: selftests: arm64: Make sea_to_user skip cleanly Like Xu
2026-08-18 11:29 ` [PATCH v2 1/3] KVM: selftests: arm64: Fix EINJ handling in sea_to_user Like Xu
2026-08-18 11:29 ` [PATCH v2 2/3] KVM: selftests: arm64: Skip sea_to_user without 1GB hugepages Like Xu
2026-08-23 18:42   ` Jiaqi Yan
2026-08-18 11:29 ` [PATCH v2 3/3] KVM: selftests: arm64: Skip sea_to_user when EINJ places no poison Like Xu
2026-08-23 18:57   ` Jiaqi Yan
2026-08-22 17:58 ` [PATCH v2 0/3] KVM: selftests: arm64: Make sea_to_user skip cleanly Jiaqi Yan
2026-09-14 12:04 ` Marc Zyngier

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®