The kernel-test machinery in tests/user-qemu.mk is x86-multiboot-
specific in several ways that don't generalise to aarch64.  The
shortest path that mirrors x86's "boot through a real bootloader
the way users actually boot" philosophy is u-boot:

  * tests/test-%.iso bundles the kernel + module into a GRUB-
    bootable ISO via grub-mkrescue.  GRUB's arm64-efi `linux`
    command refuses plain-image kernels (requires
    CONFIG_(U)EFI_STUB) which gnumach is not, so the multiboot ISO
    path doesn't translate.  Replace it on aarch64 with u-boot
    running under QEMU: a FAT image bundles gnumach + the test
    module + a per-test boot.scr that uses u-boot's `fdt mknod` to
    inject the multiboot,module DTB nodes documented in
    aarch64/BOOTING.  Same convention real-hardware u-boot users
    follow; gnumach reads it through the same
    load_boot_modules_from_dtb() path as production.

  * QEMU's per-arch command differs: x86 uses qemu-system-{i386,
    x86_64} + -cdrom, aarch64 uses qemu-system-aarch64 + -M virt +
    -bios u-boot.bin + -drive file=test.img,if=virtio.  Factor the
    boot-delivery flags out of run-qemu.sh.template as a new
    QEMU_BOOT_ARGS sed substitution; user-qemu.mk sets it per
    HOST_<arch> block.  Behaviour for x86 is byte-identical (the
    only template change is the `-cdrom <path>` literal being
    replaced by the QEMU_BOOT_ARGS placeholder that resolves back
    to `-cdrom <path>`).

  * Two new per-test build rules feed the aarch64 image:
    tests/boot-%.scr (the u-boot script, made by sed + mkimage)
    and tests/test-%.img (the FAT image, made by mkfs.vfat +
    mcopy).  Module load address is fixed at 0x50000000
    (RAM_BASE + 256 MB) to leave the single-segment vm_page heap
    a usable 256 MB; multi-segment heap is a follow-up that would
    lift this constraint and let users place modules anywhere.

  * QEMU -m sizing: x86 uses -m 2047 (32-bit low-memory upper
    bound).  aarch64's pmap_bootstrap maps a single 1 GB L1 block
    in TTBR1 so anything past 1 GB faults; cap at -m 512 until the
    kernel grows multi-block mappings.

  * The userland test binaries link against MIG-generated stubs
    for the per-arch <mach/<arch>/mach_<arch>.defs> interface.
    Add a HOST_aarch64 generate_mig_client(mach/aarch64,
    mach_aarch64) call alongside the existing ix86/x86_64 ones,
    and extend MIG_GEN_CC to pick up the right .user.c per host.

  * The freestanding test runtime includes a per-arch memory-ops
    file (memcpy/memset/memcmp).  i386 has its hand-tuned asm
    version; aarch64 has a plain-C one in aarch64/aarch64/
    strings.c (already imported by the kernel-port series).
    Factor as ARCH_STRINGS_C, selected by HOST_aarch64.

UBOOT_BIN is exported by the parent build system's Nix dev shell
for the aarch64 cross-target (points at pkgs.ubootQemuAarch64's
u-boot.bin).  Linux-only — nixpkgs's u-boot derivation doesn't
build on darwin (Makefile already errors on darwin hosts).

tests/Makefrag.am drops tests/test-multiboot from the TESTS list
on aarch64 — that one is a grub-file --is-x86-multiboot validator
and doesn't apply to a kernel that boots via the arm64 image
header.

USER_TESTS list is unchanged; all 11 of those C sources are
arch-agnostic at the source level (they call Mach via the
kernel_trap() macro from <mach/syscall_sw.h>, which dispatches
per-arch automatically — mach/aarch64/syscall_sw.h was already
present in the existing aarch64/include/ tree).
---
 tests/Makefrag.am           |  13 ++++-
 tests/run-qemu.sh.template  |   2 +-
 tests/uboot.script.template |  71 +++++++++++++++++++++++
 tests/user-qemu.mk          | 110 +++++++++++++++++++++++++++++++++---
 4 files changed, 183 insertions(+), 13 deletions(-)
 create mode 100644 tests/uboot.script.template

diff --git a/tests/Makefrag.am b/tests/Makefrag.am
index ef5616a6..d690adb8 100644
--- a/tests/Makefrag.am
+++ b/tests/Makefrag.am
@@ -30,13 +30,20 @@ export GNUMACH
 
 include tests/user-qemu.mk
 
-TESTS += \
-       tests/test-multiboot \
-       $(USER_TESTS)
+# tests/test-multiboot is a host-side grub-file validator that checks
+# the produced ISO is multiboot1-spec-compliant.  Multiboot1 is x86-
+# only by design, so the validator is meaningless on aarch64 (which
+# boots via the arm64 image header / linux+initrd through GRUB's
+# arm64-efi target instead).
+if !HOST_aarch64
+TESTS += tests/test-multiboot
+endif
+TESTS += $(USER_TESTS)
 
 EXTRA_DIST += \
        tests/README \
        tests/grub.cfg.single.template \
+       tests/uboot.script.template \
        tests/run-qemu.sh.template \
        tests/include/syscalls.h \
        tests/include/testlib.h \
diff --git a/tests/run-qemu.sh.template b/tests/run-qemu.sh.template
index a7ec426f..28c25936 100644
--- a/tests/run-qemu.sh.template
+++ b/tests/run-qemu.sh.template
@@ -17,7 +17,7 @@
 
 set -e
 
-cmd="QEMU_BIN QEMU_OPTS -cdrom tests/test-TESTNAME.iso"
+cmd="QEMU_BIN QEMU_OPTS QEMU_BOOT_ARGS"
 log="tests/test-TESTNAME.raw"
 
 echo "temp log $log"
diff --git a/tests/uboot.script.template b/tests/uboot.script.template
new file mode 100644
index 00000000..fb4c3fc5
--- /dev/null
+++ b/tests/uboot.script.template
@@ -0,0 +1,71 @@
+# Copyright (C) 2026 Free Software Foundation
+#
+# This program is free software ; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation ; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY ; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with the program ; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+# u-boot script: boots gnumach with a single bootstrap module via
+# the multiboot,module DTB convention documented in aarch64/BOOTING.
+# Loaded by u-boot's distro_bootcmd from boot.scr on virtio0:1.
+#
+# This script mirrors what a real-hardware u-boot user would type
+# manually: load kernel + module from storage, hand-build the
+# multiboot,module child of /chosen in the DTB, hand off via booti.
+# Tests should fail the same way real hardware would if any step
+# regresses.
+
+# Kernel goes well above u-boot's own data area and the DTB.
+setenv kernel_addr 0x42000000
+
+# Load files from the boot device's FAT partition (virtio 0:1).
+load virtio 0:1 ${kernel_addr} gnumach
+load virtio 0:1 MODULE_ADDR module-TESTNAME
+
+# u-boot resolves the DTB load address into ${fdtcontroladdr} at start-
+# up.  Reserve headroom for the new child node + properties.
+fdt addr ${fdtcontroladdr}
+fdt resize 1024
+
+# Multiboot,module convention from aarch64/BOOTING.  Two-cell
+# address/size so the reg property covers a 64-bit module address.
+fdt set /chosen \#address-cells <2>
+fdt set /chosen \#size-cells <2>
+fdt mknod /chosen module@MODULE_ADDR
+fdt set /chosen/module@MODULE_ADDR compatible "multiboot,kernel" 
"multiboot,module"
+fdt set /chosen/module@MODULE_ADDR reg <0 MODULE_ADDR 0 MODULE_SIZE>
+
+# bootargs payload mirrors tests/grub.cfg.single.template's x86
+# module line — a multi-word boot-script string so kern/bootstrap.c
+# takes the boot_script_task_create path (which threads
+# ${host-port} / ${device-port} / $(task-create) / $(task-resume)
+# through the boot-script interpreter) rather than the broken
+# single-word bootstrap_exec_compat path.  Use single quotes so
+# u-boot's hush parser doesn't expand the ${...} / $(...) tokens —
+# they need to reach /chosen/module/bootargs verbatim for the
+# kernel boot-script parser to find and substitute them.
+fdt set /chosen/module@MODULE_ADDR bootargs 'module-TESTNAME ${host-port} 
${device-port} $(task-create) $(task-resume)'
+
+# Force the DTB low.  u-boot's default booti behaviour relocates
+# the DTB to the highest available memory before entering the
+# kernel — but gnumach's aarch64 pmap excludes the DTB region from
+# the vm_page heap (heap_start is bumped above the DTB), and the
+# single-segment heap is already capped below the lowest module.
+# A relocated-high DTB pushes heap_start past the module → heap
+# becomes empty → vm_page panics during bootstrap.  Pin the DTB
+# below the kernel's load area so it lands in the heap-excluded
+# kernel image region (which is already lost to the heap) instead
+# of stealing usable heap above the module.
+setenv fdt_high 0x42000000
+
+# Hand off — `booti` takes <kernel> <initrd|"-"> <dtb>.
+booti ${kernel_addr} - ${fdtcontroladdr}
diff --git a/tests/user-qemu.mk b/tests/user-qemu.mk
index 857dbaac..a55d4f7b 100644
--- a/tests/user-qemu.mk
+++ b/tests/user-qemu.mk
@@ -86,6 +86,9 @@ endif
 if HOST_x86_64
 $(eval $(call generate_mig_client,mach/x86_64,mach_i386))
 endif
+if HOST_aarch64
+$(eval $(call generate_mig_client,mach/aarch64,mach_aarch64))
+endif
 
 # NOTE: keep in sync with the rules above
 MIG_GEN_CC = \
@@ -99,8 +102,19 @@ MIG_GEN_CC = \
        $(MIG_OUTDIR)/mach.user.c \
        $(MIG_OUTDIR)/mach_host.user.c \
        $(MIG_OUTDIR)/mach_port.user.c \
-       $(MIG_OUTDIR)/task_notify.server.c \
-       $(MIG_OUTDIR)/mach_i386.user.c
+       $(MIG_OUTDIR)/task_notify.server.c
+
+# Per-arch machine_interface stub: keep this in sync with the
+# arch-specific generate_mig_client calls above.
+if HOST_ix86
+MIG_GEN_CC += $(MIG_OUTDIR)/mach_i386.user.c
+endif
+if HOST_x86_64
+MIG_GEN_CC += $(MIG_OUTDIR)/mach_i386.user.c
+endif
+if HOST_aarch64
+MIG_GEN_CC += $(MIG_OUTDIR)/mach_aarch64.user.c
+endif
 
 #
 # compilation of user space tests and utilities
@@ -131,8 +145,16 @@ TESTSRC_TESTLIB= \
        $(srcdir)/tests/testlib.c \
        $(srcdir)/tests/testlib_thread_start.c
 
+# The testlib's per-arch memcpy/memset/memcmp: i386's hand-tuned asm
+# version vs aarch64's plain-C one in aarch64/aarch64/strings.c.
+if HOST_aarch64
+ARCH_STRINGS_C = $(srcdir)/aarch64/aarch64/strings.c
+else
+ARCH_STRINGS_C = $(srcdir)/i386/i386/strings.c
+endif
+
 SRC_TESTLIB= \
-       $(srcdir)/i386/i386/strings.c \
+       $(ARCH_STRINGS_C) \
        $(srcdir)/kern/printf.c \
        $(srcdir)/kern/strings.c \
        $(srcdir)/util/atoi.c \
@@ -167,16 +189,28 @@ tests/module-%: $(srcdir)/tests/test-%.c $(SRC_TESTLIB) 
$(MACH_TESTINSTALL)
 #
 # Avoid removal of module-% files after building the ISO file
 #
-.PRECIOUS: tests/module-%
+.PRECIOUS: tests/module-% tests/boot-%.scr tests/test-%.img
 
 #
 # packaging of qemu bootable image and test runner
 #
 
 GNUMACH_ARGS = console=com0
-QEMU_OPTS = -m 2047 -nographic -no-reboot -boot d
+QEMU_OPTS = -nographic -no-reboot -boot d
 QEMU_GDB_PORT ?= 1234
 
+# Memory size depends on what the kernel's bootstrap pmap can map.
+# x86 builds use 2047 MB historically (the upper bound of the 32-bit
+# "low" memory gnumach manages directly).  aarch64's pmap_bootstrap
+# maps a single 1 GB L1 block in TTBR1, so vm_page's allocator faults
+# on memory beyond that — keep aarch64 well under 1 GB until the
+# kernel grows multi-block mappings.
+if HOST_aarch64
+QEMU_OPTS += -m 512
+else
+QEMU_OPTS += -m 2047
+endif
+
 if HOST_ix86
 QEMU_BIN = qemu-system-i386
 QEMU_OPTS += -cpu pentium3-v1
@@ -185,10 +219,67 @@ if HOST_x86_64
 QEMU_BIN = qemu-system-x86_64
 QEMU_OPTS += -cpu core2duo-v1
 endif
+if HOST_aarch64
+QEMU_BIN = qemu-system-aarch64
+QEMU_OPTS += -M virt -cpu cortex-a72
+endif
 if enable_smp
 QEMU_OPTS += -smp 2
 endif
 
+# Per-arch boot delivery.  x86 boots from a multiboot ISO via
+# -cdrom.  aarch64 boots through u-boot running under QEMU: the
+# test FAT image contains gnumach, the test module, and a boot.scr
+# that uses u-boot's `fdt mknod` to inject the multiboot,module DTB
+# nodes gnumach's load_boot_modules_from_dtb() reads.  This is the
+# same convention real-hardware u-boot users follow per
+# aarch64/BOOTING — same kernel code path, same DTB shape on entry.
+#
+# UBOOT_BIN is exported by the Nix dev shell for HOST_aarch64; it
+# points at the u-boot.bin nixpkgs ships in pkgs.ubootQemuAarch64.
+if HOST_aarch64
+TEST_DEPS = tests/test-%.img
+QEMU_BOOT_ARGS = -bios $(UBOOT_BIN) -drive 
file=tests/test-TESTNAME.img,format=raw,if=virtio
+else
+TEST_DEPS = tests/test-%.iso
+QEMU_BOOT_ARGS = -cdrom tests/test-TESTNAME.iso
+endif
+
+# Module load address on aarch64: RAM_BASE + 256 MB.  pmap_bootstrap
+# maps a single 1 GB L1 block in TTBR1 and vm_page_load_heap caps
+# the heap below the lowest module's address, so memory above the
+# module is lost to the allocator (single-segment carve-out).
+# Placing the module at 256 MB leaves the heap a usable 256 MB —
+# enough for the vm_page bootstrap array + kernel image.  Multi-
+# segment heap is a follow-up that would lift this constraint.
+AARCH64_MODULE_ADDR = 0x50000000
+
+# Per-test u-boot script: substitutes the test name, module address,
+# and module size into the template, then wraps via mkimage as a
+# u-boot legacy script image (which u-boot's distro_bootcmd auto-
+# locates on virtio0:1 at boot).
+tests/boot-%.scr: $(srcdir)/tests/uboot.script.template tests/module-%
+       < $(srcdir)/tests/uboot.script.template                         \
+               sed -e "s|TESTNAME|$*|g"                                \
+                   -e "s|MODULE_ADDR|$(AARCH64_MODULE_ADDR)|g"         \
+                   -e "s|MODULE_SIZE|0x$$(printf '%x' $$(stat -c %s 
tests/module-$*))|g"       \
+               >tests/boot-$*.cmd
+       mkimage -A arm64 -T script -C none -d tests/boot-$*.cmd $@
+       rm -f tests/boot-$*.cmd
+
+# Per-test FAT image: 16 MB disk with a single FAT partition (MBR
+# from sfdisk; mtools writes the FAT inside at the 1 MB offset).
+# u-boot's bootflow scanner only finds boot.scr on partitioned
+# disks, hence the MBR — a bare FAT volume is silently skipped.
+tests/test-%.img: tests/module-% $(GNUMACH) tests/boot-%.scr
+       rm -f $@
+       truncate -s 16M $@
+       printf 'label: dos\n2048,, c, *\n' | sfdisk -q $@
+       mformat -i $@@@1M -v GNUMACH
+       mcopy -i $@@@1M $(GNUMACH) ::/gnumach
+       mcopy -i $@@@1M tests/module-$* ::/module-$*
+       mcopy -i $@@@1M tests/boot-$*.scr ::/boot.scr
+
 tests/test-%.iso: tests/module-% $(GNUMACH) 
$(srcdir)/tests/grub.cfg.single.template
        rm -rf $(builddir)/tests/isofiles-$*
        mkdir -p $(builddir)/tests/isofiles-$*/boot/grub/
@@ -202,10 +293,11 @@ tests/test-%.iso: tests/module-% $(GNUMACH) 
$(srcdir)/tests/grub.cfg.single.temp
        grub-mkrescue -o $@ $(builddir)/tests/isofiles-$*
        rm -rf $(builddir)/tests/isofiles-$*
 
-tests/test-%: tests/test-%.iso $(srcdir)/tests/run-qemu.sh.template
-       < $(srcdir)/tests/run-qemu.sh.template                  \
-               sed -e "s|TESTNAME|$(subst tests/test-,,$@)|g"  \
-                   -e "s/QEMU_OPTS/$(QEMU_OPTS)/g"             \
+tests/test-%: $(TEST_DEPS) $(srcdir)/tests/run-qemu.sh.template
+       < $(srcdir)/tests/run-qemu.sh.template                          \
+               sed -e 's|QEMU_BOOT_ARGS|$(QEMU_BOOT_ARGS)|g'           \
+                   -e "s|TESTNAME|$(subst tests/test-,,$@)|g"          \
+                   -e "s/QEMU_OPTS/$(QEMU_OPTS)/g"                     \
                    -e "s/QEMU_BIN/$(QEMU_BIN)/g"                       \
                    -e "s/TEST_START_MARKER/$(TEST_START_MARKER)/g"     \
                    -e "s/TEST_SUCCESS_MARKER/$(TEST_SUCCESS_MARKER)/g" \
-- 
2.54.0


Reply via email to