* [PATCH 0/1] target/arm/kvm: handle KVM_EXIT_ARM_SEA
@ 2026-10-16 23:05 Deepak Gupta
2026-10-16 23:05 ` [PATCH 1/1] target/arm/kvm: handle KVM_EXIT_ARM_SEA and report BUS_MCEERR_AO Deepak Gupta
0 siblings, 1 reply; 2+ messages in thread
From: Deepak Gupta @ 2026-10-16 23:05 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, kvm, kvmarm, peter.maydell, pbonzini, mchehab+huawei,
jiaqiyan, oliver.upton
Hi,
Linux 6.19 added KVM_CAP_ARM_SEA_TO_USER [1]: with the capability
enabled, KVM returns a guest synchronous external abort that the host
could not claim through APEI to userspace as KVM_EXIT_ARM_SEA, instead
of injecting a virtual SError. QEMU does not consume the exit yet, so on
a host whose HEST has no SEA-notified GHES source every guest-consumed
memory error still ends in a guest "Asynchronous SError Interrupt"
panic.
This patch enables the capability, handles the exit by recording a GHES
memory error (when the exit carries an address backed by guest RAM) and
injecting a synchronous external abort that mirrors the original one,
and wires up the BUS_MCEERR_AO SIGBUS case so the guest learns about the
poisoned page asynchronously when the exit itself carried no address.
Points I would like feedback on:
* Injection: I reuse the existing kvm_inject_arm_sea() route
(arm_cpu_do_interrupt() after kvm_cpu_synchronize_state()) and let it
pass the sanitised ESR fields (EC, IL, FnV, EA, CM, WnR, xFSC, SET)
and the FAR through to the guest. KVM_SET_VCPU_EVENTS with
ext_dabt_pending can only produce a data abort with FAR taken from
the last exit, and the documentation restricts it to MMIO/NISV/
LDST64B exits, so it did not seem a good fit; happy to switch if that
is preferred.
* The BUS_MCEERR_AO record goes on ACPI_HEST_SRC_ID_QMP, which ghes.h
documents as being for QMP-injected errors, because it is the only
GED-notified source the virt machine has. If a dedicated source id
for host-originated asynchronous records is preferred I can add one.
* With the pre-10.1 single-block HEST layout (no GED-notified source)
the AO record is dropped with a warning rather than written into the
synchronous source's block.
Testing: arm64 host whose HEST has only GSIV/SDEI-notified sources,
EINJ uncorrectable memory error into a page mapped by a guest, consumed
by a guest process (-machine virt,ras=on). Before: guest panic. After:
SIGBUS to the process, GHES record and hwpoison in the guest, guest
stays up. Also checked: the pre-existing BUS_MCEERR_AR SIGBUS path still
works (it is taken when the host's memory_failure() wins the race),
ras=off (bare external abort to the guest, no record), -machine
virt-10.0 (the AR path still delivers the synchronous record, the AO
record is dropped with the warning),
check-unit and check-qtest-aarch64 on the host.
[1] https://lore.kernel.org/r/20251013185903.1372553-1-jiaqiyan@google.com
Deepak Gupta (1):
target/arm/kvm: handle KVM_EXIT_ARM_SEA and report BUS_MCEERR_AO
target/arm/kvm.c | 164 +++++++++++++++++++++++++++++++++++-----
target/arm/trace-events | 1 +
2 files changed, 146 insertions(+), 19 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 2+ messages in thread
* [PATCH 1/1] target/arm/kvm: handle KVM_EXIT_ARM_SEA and report BUS_MCEERR_AO
2026-10-16 23:05 [PATCH 0/1] target/arm/kvm: handle KVM_EXIT_ARM_SEA Deepak Gupta
@ 2026-10-16 23:05 ` Deepak Gupta
0 siblings, 0 replies; 2+ messages in thread
From: Deepak Gupta @ 2026-10-16 23:05 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, kvm, kvmarm, peter.maydell, pbonzini, mchehab+huawei,
jiaqiyan, oliver.upton
Linux 6.19 added KVM_CAP_ARM_SEA_TO_USER: a guest synchronous external
abort (SEA) that the host cannot attribute to a firmware-first (APEI)
error record is returned to userspace as KVM_EXIT_ARM_SEA instead of
being injected as a virtual SError. Linux guests treat that SError as
fatal, so on a host whose HEST has no SEA-notified GHES source every
guest-consumed memory error currently panics the guest.
Enable the capability when KVM offers it and handle the exit:
- If the exit carries a guest physical address backed by guest RAM,
mark the page for remapping on reset and record a memory error on
the synchronous GHES source, as the BUS_MCEERR_AR SIGBUS path does.
- Inject a synchronous external abort into the guest. KVM hands us
ESR_EL2 sanitised to EC, IL, FnV, EA, CM, WnR, xFSC and SET, and
FAR_EL2 when FnV is clear. Generalise kvm_inject_arm_sea() to take
syndrome and FAR and pass them through, so the guest sees an
instruction or data abort with the real fault status code and a
valid FAR when the hardware provided one. The SIGBUS path keeps
injecting the data abort with FnV set.
- The exit may carry no address at all (FnV set, no HPFAR_EL2). The
host's memory_failure() tells us the page later via BUS_MCEERR_AO,
so far only acknowledged with a TODO: report it on the GED-notified
GHES source so the guest offlines the page too. Machine types up to
virt-10.0 have no such source; warn and drop the record there.
The exception is emulated with arm_cpu_do_interrupt() rather than via
KVM_SET_VCPU_EVENTS' ext_dabt_pending, which can only produce a data
abort with FAR taken from the last exit, while the exit may be an
instruction abort or have no valid FAR. With ras=off the guest only
gets the external abort, still better than the SError. A host without
the capability is unaffected. A trace event records every exit.
Tested on an arm64 host whose HEST has only GSIV- and SDEI-notified
sources, injecting an uncorrectable memory error with EINJ into a page
mapped by a guest and consuming it from a guest process: without this
patch the guest panics with an SError; with it the process gets SIGBUS,
the guest marks the page hwpoison and stays up.
Link: https://lore.kernel.org/r/20251013185903.1372553-2-jiaqiyan@google.com
Signed-off-by: Deepak Gupta <deepagupta@nvidia.com>
---
target/arm/kvm.c | 164 +++++++++++++++++++++++++++++++++++-----
target/arm/trace-events | 1 +
2 files changed, 146 insertions(+), 19 deletions(-)
diff --git a/target/arm/kvm.c b/target/arm/kvm.c
index 3d905f8565..8f18bee285 100644
--- a/target/arm/kvm.c
+++ b/target/arm/kvm.c
@@ -38,6 +38,7 @@
#include "hw/core/irq.h"
#include "qapi/visitor.h"
#include "qemu/log.h"
+#include "qemu/rcu.h"
#include "hw/acpi/acpi.h"
#include "hw/acpi/ghes.h"
#include "target/arm/gtimer.h"
@@ -52,6 +53,8 @@ static bool cap_has_mp_state;
static bool cap_has_inject_serror_esr;
static bool cap_has_inject_ext_dabt;
+static int kvm_arm_handle_sea(ARMCPU *cpu, struct kvm_run *run);
+
/**
* ARMHostCPUFeatures: information about the host CPU (identified
* by asking the host kernel)
@@ -674,6 +677,21 @@ int kvm_arch_init(MachineState *ms, KVMState *s)
}
}
+ /*
+ * Ask KVM to return a guest synchronous external abort that the host
+ * could not attribute to a firmware-first (APEI) error record as
+ * KVM_EXIT_ARM_SEA, instead of injecting an SError with an
+ * IMPLEMENTATION DEFINED syndrome into the guest. Linux guests treat
+ * that SError as fatal, so on a host whose HEST has no SEA-notified
+ * GHES source every guest-consumed memory error would otherwise be a
+ * guest kernel panic. See kvm_arm_handle_sea().
+ */
+ if (kvm_check_extension(s, KVM_CAP_ARM_SEA_TO_USER)) {
+ if (kvm_vm_enable_cap(s, KVM_CAP_ARM_SEA_TO_USER, 0)) {
+ warn_report("Failed to enable KVM_CAP_ARM_SEA_TO_USER cap");
+ }
+ }
+
if (s->kvm_eager_split_size) {
uint32_t sizes;
@@ -1599,6 +1617,10 @@ int kvm_arch_handle_exit(CPUState *cs, struct kvm_run *run)
ret = EXCP_DEBUG;
} /* otherwise return to guest */
break;
+ case KVM_EXIT_ARM_SEA:
+ /* Guest SEA that the host could not claim through APEI */
+ ret = kvm_arm_handle_sea(cpu, run);
+ break;
case KVM_EXIT_ARM_NISV:
/* External DABT with no valid iss to decode */
ret = kvm_arm_handle_dabt_nisv(cpu, run->arm_nisv.esr_iss,
@@ -2106,29 +2128,107 @@ int kvm_arch_destroy_vcpu(CPUState *cs)
return 0;
}
-/* Callers must hold the iothread mutex lock */
-static void kvm_inject_arm_sea(CPUState *c)
+/*
+ * Inject a synchronous external abort into the guest, targeting EL1.
+ *
+ * @esr: the syndrome to present. Its EC selects an instruction or a data
+ * abort (the same-EL variant is chosen here); IL and the ISS bits
+ * (FnV, EA, CM, WnR, xFSC, SET) are passed through to the guest.
+ * @far: the faulting virtual address, only meaningful when ISS.FnV is 0.
+ *
+ * Callers must hold the BQL.
+ */
+static void kvm_inject_arm_sea(CPUState *c, uint32_t esr, uint64_t far)
{
ARMCPU *cpu = ARM_CPU(c);
CPUARMState *env = &cpu->env;
- uint32_t esr;
+ uint32_t ec = syn_get_ec(esr);
+ bool iabt = ec == EC_INSNABORT || ec == EC_INSNABORT_SAME_EL;
bool same_el;
- c->exception_index = EXCP_DATA_ABORT;
+ c->exception_index = iabt ? EXCP_PREFETCH_ABORT : EXCP_DATA_ABORT;
env->exception.target_el = 1;
+ env->exception.vaddress = far;
- /*
- * Set the DFSC to synchronous external abort and set FnV to not valid,
- * this will tell guest the FAR_ELx is UNKNOWN for this abort.
- */
same_el = arm_current_el(env) == env->exception.target_el;
- esr = syn_data_abort_no_iss(same_el, 1, 0, 0, 0, 0, 0x10);
-
- env->exception.syndrome = esr;
+ ec = (iabt ? EC_INSNABORT : EC_DATAABORT) + same_el;
+ env->exception.syndrome = syn_set_ec(esr, ec);
arm_cpu_do_interrupt(c);
}
+/*
+ * Handle KVM_EXIT_ARM_SEA: the guest took a synchronous external abort,
+ * typically by consuming a poisoned memory location, and the host kernel
+ * found no APEI error record to claim it with. Without
+ * KVM_CAP_ARM_SEA_TO_USER, KVM would inject a virtual SError here, which a
+ * Linux guest treats as fatal.
+ *
+ * Do what the BUS_MCEERR_AR SIGBUS path does: when the exit carries a guest
+ * physical address that is backed by guest RAM, remember the page so it is
+ * remapped on reset and record a memory error on the synchronous GHES
+ * source (if the machine has one, i.e. ras=on), so the guest's
+ * memory_failure() can isolate the page and kill only the affected task.
+ * Then inject a synchronous external abort that reflects the original one:
+ * the kernel hands us ESR_EL2 sanitised to EC, IL, FnV, EA, CM, WnR, xFSC
+ * and SET, which are exactly the fields that are meaningful at EL1, and the
+ * faulting virtual address when FnV is clear.
+ *
+ * kvm_arch_handle_exit() runs without the BQL; both the GHES update and
+ * arm_cpu_do_interrupt() need it.
+ */
+static int kvm_arm_handle_sea(ARMCPU *cpu, struct kvm_run *run)
+{
+ CPUState *cs = CPU(cpu);
+ uint64_t flags = run->arm_sea.flags;
+ uint64_t esr = run->arm_sea.esr;
+ uint64_t gva = run->arm_sea.gva;
+ hwaddr gpa = run->arm_sea.gpa;
+ ram_addr_t ram_addr = RAM_ADDR_INVALID;
+ AcpiGhesState *ags;
+
+ trace_kvm_arm_handle_sea(flags, esr, gva, gpa);
+
+ if (flags & KVM_EXIT_ARM_SEA_FLAG_GPA_VALID) {
+ MemoryRegion *mr;
+ hwaddr xlat, len = 1;
+
+ WITH_RCU_READ_LOCK_GUARD() {
+ mr = address_space_translate(&address_space_memory, gpa, &xlat,
+ &len, false, MEMTXATTRS_UNSPECIFIED);
+ if (memory_region_is_ram(mr)) {
+ ram_addr = memory_region_get_ram_addr(mr) + xlat;
+ }
+ }
+ }
+
+ bql_lock();
+ kvm_cpu_synchronize_state(cs);
+
+ ags = acpi_ghes_get_state();
+ if (ram_addr != RAM_ADDR_INVALID) {
+ kvm_hwpoison_page_add(ram_addr);
+ if (ags) {
+ Error *err = NULL;
+
+ if (!acpi_ghes_memory_errors(ags, ACPI_HEST_SRC_ID_SYNC, gpa,
+ &err)) {
+ warn_reportf_err(err, "KVM_EXIT_ARM_SEA at GPA 0x%" HWADDR_PRIx
+ ": ", gpa);
+ }
+ } else {
+ warn_report_once("KVM_EXIT_ARM_SEA at GPA 0x%" HWADDR_PRIx
+ " but the machine has no GHES (ras=off): the "
+ "guest only gets an external abort", gpa);
+ }
+ }
+
+ kvm_inject_arm_sea(cs, esr, gva);
+ bql_unlock();
+
+ return 0;
+}
+
#define AARCH64_CORE_REG(x) (KVM_REG_ARM64 | KVM_REG_SIZE_U64 | \
KVM_REG_ARM_CORE | KVM_REG_ARM_CORE_REG(x))
@@ -2525,19 +2625,45 @@ void kvm_arch_on_sigbus_vcpu(CPUState *c, int code, void *addr)
/*
* If this is a BUS_MCEERR_AR, we know we have been called
* synchronously from the vCPU thread, so we can easily
- * synchronize the state and inject an error.
- *
- * TODO: we currently don't tell the guest at all about
- * BUS_MCEERR_AO. In that case we might either be being
- * called synchronously from the vCPU thread, or a bit
- * later from the main thread, so doing the injection of
- * the error would be more complicated.
+ * synchronize the state and inject an error: a data abort
+ * with DFSC = synchronous external abort and FnV set, which
+ * tells the guest that FAR_ELx is UNKNOWN for this abort.
*/
if (code == BUS_MCEERR_AR) {
kvm_cpu_synchronize_state(c);
acpi_ghes_memory_errors(ags, ACPI_HEST_SRC_ID_SYNC,
paddr, &error_fatal);
- kvm_inject_arm_sea(c);
+ kvm_inject_arm_sea(c, syn_data_abort_no_iss(0, 1, 0, 0, 0, 0,
+ 0x10), 0);
+ return;
+ }
+
+ /*
+ * BUS_MCEERR_AO: the host has retired a page of guest RAM that
+ * no vCPU was accessing at the time. We may be on a vCPU thread
+ * or on the main thread, in both cases under the BQL, so there
+ * is no vCPU to inject into. Instead record an action-optional
+ * memory error on the GED-notified GHES source, so the guest
+ * can offline the page as well. That source only exists with
+ * the HEST layout that reports its address to QEMU (virt
+ * machine versions after 10.0); with the older single-block
+ * layout the record would land in the synchronous source's
+ * block, unnoticed by the guest, and block the next
+ * synchronous record.
+ */
+ if (!ags->use_hest_addr) {
+ warn_report_once("BUS_MCEERR_AO at GPA 0x%" HWADDR_PRIx
+ ": this machine type has no GED-notified "
+ "GHES source, the guest is not told",
+ paddr);
+ } else {
+ Error *err = NULL;
+
+ if (!acpi_ghes_memory_errors(ags, ACPI_HEST_SRC_ID_QMP,
+ paddr, &err)) {
+ warn_reportf_err(err, "BUS_MCEERR_AO at GPA 0x%"
+ HWADDR_PRIx ": ", paddr);
+ }
}
return;
}
diff --git a/target/arm/trace-events b/target/arm/trace-events
index 8502fb3265..5c78d1255c 100644
--- a/target/arm/trace-events
+++ b/target/arm/trace-events
@@ -13,6 +13,7 @@ arm_gt_update_irq(int timer, int irqstate) "gt_update_irq: timer %d irqstate %d"
# kvm.c
kvm_arm_fixup_msi_route(uint64_t iova, uint64_t gpa) "MSI iova = 0x%"PRIx64" is translated into 0x%"PRIx64
+kvm_arm_handle_sea(uint64_t flags, uint64_t esr, uint64_t gva, uint64_t gpa) "KVM_EXIT_ARM_SEA flags 0x%"PRIx64" esr 0x%"PRIx64" gva 0x%"PRIx64" gpa 0x%"PRIx64
# cpu.c
arm_cpu_reset(uint64_t mp_aff) "cpu %" PRIu64
--
2.53.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-10-16 23:05 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-16 23:05 [PATCH 0/1] target/arm/kvm: handle KVM_EXIT_ARM_SEA Deepak Gupta
2026-10-16 23:05 ` [PATCH 1/1] target/arm/kvm: handle KVM_EXIT_ARM_SEA and report BUS_MCEERR_AO Deepak Gupta
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.