commit 74a49a2db72bf05eebaf4d1f0e7b57be7c896dac
parent 9aa8d6e18cf373b4ec23486eeb96c824103b8997
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 16 Jun 2026 12:41:10 -0700
test-cross: enable MMU+FPU in aa64 bare stub; isolate exec-seam stdin
The freestanding-aa64 coarse lane (frontback_amalgam O0/O1) timed out: the
aa64 boot stub called main straight from reset, where the MMU is off (RAM is
Device-nGnRnE, so an unaligned wide store faults) and the FPU traps. With no
exception vectors installed the fault spins at the vector base forever -> qemu
timeout (rc 124). Harden the stub to enable FP/SIMD (CPACR_EL1) and install an
identity MMU map marking RAM as Normal memory, matching the rv32 stub and what
a real aarch64 bootloader does before running general C.
Also harden the execution seam against stdin draining: cross_test reads its run
list on a private fd 9 and feeds each runner </dev/null; exec_vm teardown stops
waiting on a poweroff the unprivileged ssh user may not be permitted to run and
SIGTERMs qemu (our exec'd child) directly.
Diffstat:
3 files changed, 79 insertions(+), 15 deletions(-)
diff --git a/scripts/cross_test.sh b/scripts/cross_test.sh
@@ -221,13 +221,18 @@ cross_run_all() {
local list="$1"
local kind label tag exe out err exp want_file rc got want barch
[ -s "$list" ] || { echo "cross: no linked artifacts to execute"; return; }
- while IFS='|' read -r kind label tag exe out err exp want_file; do
+ # Read the list on a private fd (9), not stdin: the runners below spawn
+ # children that read host stdin (qemu-system -nographic muxes the guest UART
+ # onto stdio; VM readiness ssh probes forward stdin), which would otherwise
+ # drain the list mid-loop and truncate execution after the first such case.
+ # Each runner also gets </dev/null so it can never block on or consume stdin.
+ while IFS='|' read -r kind label tag exe out err exp want_file <&9; do
case "$kind" in
bare)
barch="$(_exec_target_arch "$tag")"
- exec_bare_run_image "$barch" "$exe" "$out" "$err" ;;
+ exec_bare_run_image "$barch" "$exe" "$out" "$err" </dev/null ;;
*)
- exec_target_run "$tag" "$exe" "$out" "$err" ;;
+ exec_target_run "$tag" "$exe" "$out" "$err" </dev/null ;;
esac
rc="${RUN_RC:-127}"; got="$(cat "$out" 2>/dev/null)"
want=""; [ "$want_file" != "-" ] && want="$(cat "$want_file")"
@@ -238,7 +243,7 @@ cross_run_all() {
else
kit_pass "$label:run"
fi
- done < "$list"
+ done 9< "$list"
}
# ---- full lanes ------------------------------------------------------------
diff --git a/test/lib/exec_bare.sh b/test/lib/exec_bare.sh
@@ -187,6 +187,17 @@ exec_bare_run() { # arch obj work rcfile -> echoes reason; 0 ran / 2 build-fai
_bare_emit_aa64() {
local dir="$1"
+ # The CPU comes out of reset at EL1 with the FPU trapping and the MMU off.
+ # Two things general C needs are therefore set up before calling main:
+ # * CPACR_EL1.FPEN = 0b11 — else the first float/double op traps.
+ # * an identity MMU map marking RAM as Normal memory — with the MMU off
+ # all data accesses are Device-nGnRnE, which enforces natural alignment,
+ # so the wide unaligned stores kit emits (e.g. an 8-byte aggregate-init
+ # store landing on a 4-byte-aligned slot) take an alignment fault. We
+ # have no exception vectors, so a fault spins at the vector base forever
+ # (timeout). Normal memory permits the unaligned access, like a real OS.
+ # Sysregs use the generic S<op0>_<op1>_C<crn>_C<crm>_<op2> spelling (kit-as
+ # only names a handful by mnemonic).
cat > "$dir/start.S" <<'EOF'
.section .text.start,"ax",@progbits
.globl _start
@@ -195,7 +206,42 @@ _start:
add x0, x0, :lo12:stack_top
and x0, x0, #0xfffffffffffffff0 // AArch64 faults on a misaligned SP; the
mov sp, x0 // linker-script .bss lump may not 16-align
- bl main // stack_top, so force it here.
+ // stack_top, so force it here.
+
+ movz x0, #0x30, lsl #16 // CPACR_EL1.FPEN = 0b11 (don't trap FP/SIMD)
+ msr S3_0_C1_C0_2, x0
+ isb
+
+ movz x0, #0x00ff // MAIR_EL1 attr0 = 0xFF (Normal WB)
+ msr S3_0_C10_C2_0, x0
+ movz x0, #0x3519 // TCR_EL1: T0SZ=25, 4KB granule, WB/IS
+ movk x0, #0x0080, lsl #16 // table walks, EPD1=1 (no TTBR1),
+ movk x0, #0x0001, lsl #32 // IPS=36-bit
+ msr S3_0_C2_C0_2, x0
+ adrp x1, l1_table // fill the 512-entry L1 table with 1GB
+ add x1, x1, :lo12:l1_table // identity blocks, all Normal memory
+ mov x2, #0
+ movz x3, #0x0701 // block desc: AF | SH=inner | AttrIdx0 | 01
+.Lmap:
+ lsl x4, x2, #30 // output PA = index * 1GB == input VA
+ orr x4, x4, x3
+ str x4, [x1, x2, lsl #3]
+ add x2, x2, #1
+ cmp x2, #512
+ b.lo .Lmap
+ msr S3_0_C2_C0_0, x1 // TTBR0_EL1 = l1_table
+ dsb sy
+ isb
+ mrs x0, S3_0_C1_C0_0 // SCTLR_EL1: enable MMU + caches
+ orr x0, x0, #(1 << 0) // M (MMU)
+ orr x0, x0, #(1 << 2) // C (data cache)
+ orr x0, x0, #(1 << 12) // I (instruction cache)
+ movn x5, #(1 << 1) // A=0: allow unaligned on Normal memory
+ and x0, x0, x5
+ msr S3_0_C1_C0_0, x0
+ isb
+
+ bl main
adrp x1, semihost_args
add x1, x1, :lo12:semihost_args
str x0, [x1, #8]
@@ -210,6 +256,11 @@ semihost_args:
.quad 0x20026
.quad 0
+.section .bss.pgtbl,"aw",@nobits
+.balign 4096
+l1_table:
+ .zero 4096
+
.section .bss.stack,"aw",@nobits
.balign 16
stack_bottom:
@@ -220,10 +271,11 @@ EOF
ENTRY(_start)
SECTIONS {
. = 0x40080000;
- .text : ALIGN(8) { *(.text.start) *(.text*) }
- .rodata : ALIGN(8) { *(.rodata*) }
- .data : ALIGN(8) { *(.data*) }
- .bss : ALIGN(16) { *(.bss*) *(COMMON) . = ALIGN(., 16); }
+ .text : ALIGN(8) { *(.text.start) *(.text*) }
+ .rodata : ALIGN(8) { *(.rodata*) }
+ .data : ALIGN(8) { *(.data*) }
+ .bss.pgtbl : ALIGN(4096) { *(.bss.pgtbl) } /* L1 table needs 4K alignment */
+ .bss : ALIGN(16) { *(.bss*) *(COMMON) . = ALIGN(., 16); }
/DISCARD/ : { *(.comment) }
}
EOF
diff --git a/test/lib/exec_vm.sh b/test/lib/exec_vm.sh
@@ -138,18 +138,25 @@ exec_vm_teardown_all() {
for vmid in $EXEC_VM_STARTED; do
case "$vmid" in
windows)
- "$EXEC_VM_SCRIPTS/windows_vm.sh" stop >/dev/null 2>&1 || true ;;
+ "$EXEC_VM_SCRIPTS/windows_vm.sh" stop </dev/null >/dev/null 2>&1 || true ;;
freebsd-*)
vmarch="${vmid#freebsd-}"
- "$EXEC_VM_SCRIPTS/freebsd_vm.sh" ssh "$vmarch" \
- 'sync; shutdown -p now' >/dev/null 2>&1 || true
+ # Best-effort guest-side flush only — do NOT wait on a full
+ # poweroff: the unprivileged `kit` ssh user may not be allowed
+ # to run shutdown(8), so waiting on it just stalls teardown for
+ # the whole 30s timeout. (</dev/null so this ssh can't read the
+ # caller's stdin — e.g. the EXIT-trap's inherited terminal.)
+ "$EXEC_VM_SCRIPTS/freebsd_vm.sh" ssh "$vmarch" 'sync' \
+ </dev/null >/dev/null 2>&1 || true
if [ -f "$EXEC_VM_WORK/$vmid.pid" ]; then
pid="$(cat "$EXEC_VM_WORK/$vmid.pid")"
- for _ in $(seq 1 30); do
+ # qemu is our exec'd child (run_arch execs it, so the saved
+ # pid IS qemu): SIGTERM makes it quit promptly. Wait a few
+ # seconds, then SIGKILL as a backstop.
+ kill "$pid" 2>/dev/null || true
+ for _ in $(seq 1 5); do
kill -0 "$pid" 2>/dev/null || break; sleep 1
done
- kill -0 "$pid" 2>/dev/null && kill "$pid" 2>/dev/null || true
- sleep 1
kill -0 "$pid" 2>/dev/null && kill -9 "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
fi ;;