[RFC PATCH 15/18] tests: support aarch64 in the test harness (u-boot + multiboot, module)

Paulo Duarte <[email protected]>
Newsgroups gmane.os.hurd.bugs
Message-ID <[email protected]>
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.