ADR-110 — N1: taken SError on core 1 at EL0 (pinned QEMU, -smp 2, cpu-index=1)
- Status: Accepted (implementation + evidence, opt-in). Sponsor go relayed by DSO on 2026-10-23 at 20:17 CEST (N1 from ADR-108).
- Date: 2026-10-23
- Base: main
c420fba. ADR-107 (#163, FAT32), ADR-108 (#165, the spike) and ADR-109 (#164, inbound TCP) are taken, so this is ADR-110. - Machine: amd64 workstation (TCG; builds in an
ubuntu:24.04container). CI: theQEMU NMI pin smoke (taken SError)job.
Context
The B1 pinned QEMU (v10.0.0 + patch 0001, ADR-083) gave a taken lower-EL SError only on core 0, at -smp 1. On core 1 (S4/S5, ADR-104/ADR-105) there was no SError probe. The ADR-109 spike showed that the pinned build’s HMP nmi honours cpu-index.
Decision
-
Opt-in features:
smp2-serror-probe(impliessmp2-probe): after core 1’s 13 S4 probes, core 1 printssmp: cpu1 serror-arm masked=no cpu=1and ERETs to the same standing-EL0 spin as the single-core probe, with PSTATE.A clear and the banked per-core EXPECT flags.smp2-serror-maskedis the negative build: it ERETs with A set.
-
Handler (fail-closed): in that build, core 1’s lower-EL SError handler prints its own locked line,
smp: cpu1 el0 serror taken cpu=1. It never prints the core-0el0: serrorcue. The probe printssmp: cpu1 serror taken=yes …only if the handler ran and the standing trip restored. Otherwise it printssmp: cpu1 serror park taken=no …. -
Host:
qemu-serial-inject.py, on thesmp: cpu1 serror-armcue and only whenCTOS_QEMU_NMI_CPU1=<n>is set, does QMPstop→human-monitor-command nmiwithcpu-index=<n>→cont. QMPinject-nmihas no CPU argument. -
Check:
scripts/smp2-serror-pin.sh. Every row requires the hostnmi=>ok,smp: ok cpu1 s4, and no unhandled exception.Row Build nmi to Must see Must not see P probe cpu-index=1 cpu1 el0 serror takenandtaken=yesparkN0 probe cpu-index=0 park taken=no masked=nocpu1 el0 serror takenNM masked cpu-index=1 park taken=no masked=yescpu1 el0 serror taken -
CI: a new step in
qemu-nmi-pinafter the unchanged single-core step (CTOS_REQUIRE_TAKEN_SERROR=1).
Evidence (2026-10-23, amd64 workstation, pinned QEMU 10.0.0)
smp2-serror-pin.sh: P ok (nmi(cpu-index=1)=>ok,smp: cpu1 el0 serror taken cpu=1), N0 ok, NM ok.- The full stock smoke (
CTOS_SMP2_SMOKE=1) and the single-core pin smoke (CTOS_REQUIRE_TAKEN_SERROR=1) still pass. - CI results are on the PR.
Honesty
Say: “With the repo’s pinned QEMU, a host-injected SError aimed at core 1 (cpu-index=1) is taken by ctos core 1 while it stands at EL0, on -smp 2 TCG. Aimed at core 0, or with core 1’s A bit set, it is not taken.”
Do not say:
- that stock QEMU does this;
- that it is hardware RAS;
- that the “EL0 isolated” sentence changes. A claim change needs a separate sponsor accept.
Also not shown:
- whether core 0 takes the N0 SError later: it stays pending with A masked, and nothing after unmasks it;
- concurrency with S5 (core 1 runs the probe before S5);
- KVM.
Consequences
Cargo.tomlgains two features.- Code:
src/el0.rscpu1_serror_probe, a cfg branch insrc/exception.rsfor the handler, one cfg call insrc/smp.rs, andsmp::linebecomespub(crate). - Scripts:
scripts/qemu-serial-inject.py(hmp_nmi, env-gated) andscripts/smp2-serror-pin.sh. - Workflow: one step added to
qemu-nmi-pin. - Docs: ledger row, SUMMARY, moc, brief.
- The default build, default smoke and
_startare unchanged.