From e979f858685e3f3bc5f41d155d063143f3ae9526 Mon Sep 17 00:00:00 2001 From: Brendan Moran Date: Tue, 28 Jul 2026 10:36:36 +0100 Subject: [PATCH 1/4] Test: Run ABI checks as Zephyr applications Build the ABI checker and assembly sources directly with Zephyr's target toolchain. Select the Armv8.1-M checker for M55 and preserve OPT/AUTO through the run stage so the checker is actually executed. Signed-off-by: Brendan Moran --- scripts/tests | 5 +++++ test/abicheck/abicheck.c | 10 +++++++++- test/mk/components.mk | 25 +++++++++++++++---------- test/mk/rules.mk | 4 +--- test/zephyr/app/CMakeLists.txt | 14 ++++++++------ test/zephyr/platform.mk | 6 ++++++ 6 files changed, 44 insertions(+), 20 deletions(-) diff --git a/scripts/tests b/scripts/tests index 6e6f13399f..3e28fca1a9 100755 --- a/scripts/tests +++ b/scripts/tests @@ -560,6 +560,11 @@ class Tests: log = logger(test_type, scheme_str, self.args.cross_prefix, opt) args = ["make", test_type.make_run_target(scheme)] + # run_abicheck is conditional on OPT in the Makefile. Keep its run + # invocation aligned with the build; otherwise a Zephyr platform's + # OPT=0 default turns this into an empty, successful target. + if test_type == TEST_TYPES.ABICHECK: + args += [f"OPT={int(opt)}", f"AUTO={int(self.args.auto)}"] if test_type.is_benchmark() is False and test_type.is_example() is False: args += self.make_j() if test_type.make_dir() != "": diff --git a/test/abicheck/abicheck.c b/test/abicheck/abicheck.c index 8b03501939..e3ed632da2 100644 --- a/test/abicheck/abicheck.c +++ b/test/abicheck/abicheck.c @@ -22,12 +22,20 @@ static const abicheck_entry_t all_checks[] = {{NULL, NULL}}; /* Return-code convention: see abicheck_common.h. SKIPPED means the kernel * built but the host lacks the runtime capability; the generated check * decides this via mld_sys_check_capability. */ -int main(void) +/* Zephyr renames main to mld_test_main, making it an ordinary global function + * for which -Wmissing-prototypes requires a prior declaration. Its arguments + * match the Zephyr shim that invokes the renamed entry point. */ +int main(int argc, char **argv); + +int main(int argc, char **argv) { int failed_tests = 0; int selftest_failures; const abicheck_entry_t *entry; + (void)argc; + (void)argv; + /* Meta-test the ABI checker before trusting kernel verdicts (see selftest.h). */ selftest_failures = abicheck_selftest(); diff --git a/test/mk/components.mk b/test/mk/components.mk index b61a8185ab..e6328e8a80 100644 --- a/test/mk/components.mk +++ b/test/mk/components.mk @@ -177,16 +177,10 @@ endif # ABI checker ABICHECK_DIR = $(BUILD_DIR)/abicheck -# Map $(ARCH) to the abicheck per-arch subdir name. For most architectures -# the subdir matches $(ARCH); one exception: -# - arm-none-eabi- targets: $(ARCH) = arm (a generic label for the -# bare-metal Cortex-M family). The abicheck subdir is the more specific -# armv81m. -ifeq ($(ARCH),arm) -ABICHECK_ARCH := armv81m -else -ABICHECK_ARCH := $(ARCH) -endif +# A platform can select the ABI independently of make's host/cross ARCH (for +# example, Zephyr owns its target toolchain). Otherwise, use the architecture +# inferred from the compiler prefix. +ABICHECK_ARCH ?= $(ARCH) ABICHECK_SOURCES = test/abicheck/abicheck.c test/abicheck/selftest.c ABICHECK_SOURCES += $(wildcard test/abicheck/$(ABICHECK_ARCH)/abicheck_$(ABICHECK_ARCH).c) @@ -268,4 +262,15 @@ ifneq ($(EXTRA_SOURCES),) $(ABICHECK_EXTRA_OBJS): CFLAGS += $(EXTRA_SOURCES_CFLAGS) endif +ifndef CUSTOM_BUILD $(ABICHECK_DIR)/bin/abicheck: $(ABICHECK_OBJS) $(ABICHECK_EXTRA_OBJS) +else +# Custom builds compile sources through their own build system. Pass the ABI +# checker inputs and flags through the same interface as the other tests, and +# mark this as a standalone checker so the platform does not add libmldsa. +$(ABICHECK_DIR)/bin/abicheck: CUSTOM_BUILD_ABICHECK := 1 +$(ABICHECK_DIR)/bin/abicheck: TEST_SRCS += $(ABICHECK_ALL_SOURCES) +$(ABICHECK_DIR)/bin/abicheck: CFLAGS += \ + $(ABICHECK_ASM_CFLAGS) $(ABICHECK_FULL_API_CFLAGS) +$(ABICHECK_DIR)/bin/abicheck: $(ABICHECK_ALL_SOURCES) $(CUSTOM_BUILD_DEPS) +endif diff --git a/test/mk/rules.mk b/test/mk/rules.mk index 835cbfc326..637fdefb3c 100644 --- a/test/mk/rules.mk +++ b/test/mk/rules.mk @@ -148,9 +148,8 @@ $(BUILD_DIR)/mldsa87/sign_hook/%.S.o: %.S $(CONFIG) $(Q)$(CC) -c -o $@ $(CFLAGS) $< $(BUILD_DIR)/abicheck/bin/%: $(CONFIG) - $(Q)echo " LD $@" $(Q)[ -d $(@D) ] || mkdir -p $(@D) - $(Q)$(LD) $(LDFLAGS) -o $@ $(filter %.o,$^) $(LDLIBS) + $(Q)$(LINK) $(BUILD_DIR)/abicheck/%.c.o: %.c $(CONFIG) $(Q)echo " CC $@" @@ -161,4 +160,3 @@ $(BUILD_DIR)/abicheck/%.S.o: %.S $(CONFIG) $(Q)echo " AS $@" $(Q)[ -d $(@D) ] || mkdir -p $(@D) $(Q)$(CC) -c -o $@ $(CFLAGS) $< - diff --git a/test/zephyr/app/CMakeLists.txt b/test/zephyr/app/CMakeLists.txt index b5d56d4761..5df00ce7ec 100644 --- a/test/zephyr/app/CMakeLists.txt +++ b/test/zephyr/app/CMakeLists.txt @@ -13,15 +13,17 @@ project(mldsa_native_zephyr) # ZEPHYR_NATIVE_ROOT, space separated # ZEPHYR_TEST_CFLAGS - the test binary's CFLAGS (see below); includes the # parameter set (-DMLD_CONFIG_PARAMETER_SET=...) +# ZEPHYR_ABICHECK - build the standalone ABI checker sources without the +# normal mldsa amalgamation set(R ${ZEPHYR_NATIVE_ROOT}) separate_arguments(_test_srcs UNIX_COMMAND "${ZEPHYR_TEST_SRCS}") list(TRANSFORM _test_srcs PREPEND ${R}/) -target_sources(app PRIVATE - ${R}/mldsa/mldsa_native.c - ${_test_srcs} -) +target_sources(app PRIVATE ${_test_srcs}) +if(NOT ZEPHYR_ABICHECK) + target_sources(app PRIVATE ${R}/mldsa/mldsa_native.c) +endif() target_include_directories(app PRIVATE ${R}/mldsa @@ -85,14 +87,14 @@ set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/shim_nucleo_n657x0_q.c # Optional native FIPS202 backend (e.g. Armv8.1-M MVE on Cortex-M55). The # monolithic mldsa_native.c already includes the backend C sources; its # assembly counterpart comes from mldsa_native_asm.S. -if(ZEPHYR_FIPS202_BACKEND) +if(ZEPHYR_FIPS202_BACKEND AND NOT ZEPHYR_ABICHECK) target_sources(app PRIVATE ${R}/mldsa/mldsa_native_asm.S) target_compile_definitions(app PRIVATE MLD_CONFIG_USE_NATIVE_BACKEND_FIPS202 "MLD_CONFIG_FIPS202_BACKEND_FILE=\"${ZEPHYR_FIPS202_BACKEND}\"") endif() -# Each test brings its own int main(void); rename it so the selected Zephyr +# Each test brings its own main(); rename it so the selected Zephyr # shim owns main() and can return the test's exit code through the target's # runner. Only the test entrypoint in ZEPHYR_TEST_SRCS defines main(); the # support sources (notrandombytes, hal) don't, so applying the define to all is diff --git a/test/zephyr/platform.mk b/test/zephyr/platform.mk index 03b863ff8c..c4e600da3d 100644 --- a/test/zephyr/platform.mk +++ b/test/zephyr/platform.mk @@ -36,6 +36,10 @@ ZEPHYR_BOARD_nucleo-n657x0-q := nucleo_n657x0_q # Cortex-M55 (hardware) ZEPHYR_FIPS202_BACKEND_mps3-an547 := fips202/native/armv81m/mve.h ZEPHYR_FIPS202_BACKEND_nucleo-n657x0-q := fips202/native/armv81m/mve.h +# Zephyr owns target selection, so do not infer its ABI from make's host ARCH. +ZEPHYR_ABICHECK_ARCH_mps3-an547 := armv81m +ABICHECK_ARCH := $(strip $(ZEPHYR_ABICHECK_ARCH_$(ZEPHYR_TARGET))) + ZEPHYR_TARGETS := mps2-an385 mps2-an386 mps2-an500 mps2-an521 mps3-an547 nucleo-n657x0-q ZEPHYR_BOARD := $(ZEPHYR_BOARD_$(ZEPHYR_TARGET)) @@ -120,6 +124,7 @@ ZEPHYR_TEST_CFLAGS = $(subst \",\\\",$(patsubst -Imldsa,-I$(abspath mldsa),$(CFL ZEPHYR_CMAKE_ENV := env -u CFLAGS -u CXXFLAGS -u CPPFLAGS -u LDFLAGS CUSTOM_BUILD = \ + $(if $(CUSTOM_BUILD_ABICHECK),$(if $(ABICHECK_ARCH),,$(error ABI checking is not supported for ZEPHYR_TARGET=$(ZEPHYR_TARGET)))) \ echo " ZEPHYR $(ZEPHYR_TARGET): $(notdir $@)" && \ $(ZEPHYR_CMAKE_ENV) cmake -GNinja -S $(ZEPHYR_APP) -B $(ZEPHYR_OUT) \ -DBOARD=$(ZEPHYR_BOARD) \ @@ -127,6 +132,7 @@ CUSTOM_BUILD = \ -DZEPHYR_TEST_SRCS="$(strip $(TEST_SRCS))" \ -DZEPHYR_TEST_CFLAGS="$(ZEPHYR_TEST_CFLAGS)" \ -DZEPHYR_FIPS202_BACKEND=$(ZEPHYR_FIPS202_BACKEND) \ + -DZEPHYR_ABICHECK=$(if $(CUSTOM_BUILD_ABICHECK),ON,OFF) \ $(if $(ZEPHYR_FIPS202_BACKEND),-DCONFIG_FIPS202_MVE_BACKEND=y) \ $(ZEPHYR_TARGET_CMAKE_ARGS) \ -DUSER_CACHE_DIR=$(abspath $(ZEPHYR_OUT)/.cache) \ From afccd5fb3a059c839a6d16e913a6265416ddb385 Mon Sep 17 00:00:00 2001 From: Brendan Moran Date: Wed, 29 Jul 2026 13:35:49 +0100 Subject: [PATCH 2/4] Zephyr: Track test configuration in build key Include OPT, the selected FIPS202 backend, and configurable test counts in the active build marker so changes to those inputs rebuild stale Zephyr binaries. Track the native assembly amalgamation and its direct development-source include as explicit dependencies, and allow QEMU execution timeouts to be overridden. Signed-off-by: Brendan Moran --- test/zephyr/exec_wrapper.py | 5 +++- test/zephyr/platform.mk | 47 +++++++++++++++++++++++++++---------- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/test/zephyr/exec_wrapper.py b/test/zephyr/exec_wrapper.py index 6e339da7d9..ab66108188 100755 --- a/test/zephyr/exec_wrapper.py +++ b/test/zephyr/exec_wrapper.py @@ -15,6 +15,7 @@ binpath = sys.argv[1] args = sys.argv[2:] machine = os.environ.get("QEMU_MACHINE", "mps3-an547") +timeout = float(os.environ.get("QEMU_TIMEOUT", "300")) semihosting_args = [binpath] + args semihosting_config = "enable=on," + ",".join(f"arg={a}" for a in semihosting_args) @@ -32,7 +33,9 @@ binpath, ] -result = subprocess.run(qemu_cmd, encoding="utf-8", capture_output=True, timeout=300) +result = subprocess.run( + qemu_cmd, encoding="utf-8", capture_output=True, timeout=timeout +) sys.stdout.write(result.stdout) if result.returncode != 0: sys.stderr.write(result.stderr) diff --git a/test/zephyr/platform.mk b/test/zephyr/platform.mk index c4e600da3d..420ea6adce 100644 --- a/test/zephyr/platform.mk +++ b/test/zephyr/platform.mk @@ -59,12 +59,27 @@ endif # file to source prerequisites (it is: the top-level Makefile includes us first). OPT ?= 0 +# Shrink the test iteration counts (the sources default them higher, sized for +# native hardware): QEMU is far slower. Keep these defaults ahead of the build +# key so command-line overrides invalidate binaries built with different counts. +NTESTS ?= 3 +NUM_RANDOM_TESTS ?= 100 + +# Forward the QEMU execution deadline to exec_wrapper.py. Direct wrapper +# invocations use the same default when the variable is absent. +QEMU_TIMEOUT ?= 300 +export QEMU_TIMEOUT + # Native backends are an OPT=1 feature (an547 builds the Armv8.1-M MVE backend). ZEPHYR_FIPS202_BACKEND := $(if $(filter 1,$(OPT)),$(strip $(ZEPHYR_FIPS202_BACKEND_$(ZEPHYR_TARGET)))) ZEPHYR_APP := $(PLATFORM_PATH)/app ZEPHYR_BUILD_DIR := $(BUILD_DIR)/zephyr/$(ZEPHYR_TARGET) ZEPHYR_ACTIVE_TARGET := $(BUILD_DIR)/zephyr/.active-target +ZEPHYR_BUILD_KEY := $(ZEPHYR_TARGET)|OPT=$(OPT) +ZEPHYR_BUILD_KEY := $(ZEPHYR_BUILD_KEY)|FIPS202=$(ZEPHYR_FIPS202_BACKEND) +ZEPHYR_BUILD_KEY := $(ZEPHYR_BUILD_KEY)|NTESTS=$(NTESTS) +ZEPHYR_BUILD_KEY := $(ZEPHYR_BUILD_KEY)|NUM_RANDOM_TESTS=$(NUM_RANDOM_TESTS) ZEPHYR_APP_INPUTS := \ $(ZEPHYR_APP)/CMakeLists.txt \ $(ZEPHYR_APP)/Kconfig \ @@ -83,14 +98,15 @@ ZEPHYR_TARGET_CMAKE_ARGS := $(if $(ZEPHYR_IS_NUCLEO_N657X0_Q),\ # Test binary output paths are shared across ZEPHYR_TARGET values, while the # CMake build directory is target-specific. Keep a lightweight marker containing -# the last requested target, and only touch it when the target changes. Binaries -# depending on this marker are then rebuilt after a target switch without -# forcing a clean rebuild when the target is unchanged. +# the last requested build configuration, and only touch it when that +# configuration changes. Binaries depending on this marker are then rebuilt +# after a target, backend, or test-count switch without forcing a clean rebuild +# when the configuration is unchanged. .PHONY: zephyr_target_marker_force $(ZEPHYR_ACTIVE_TARGET): zephyr_target_marker_force $(Q)[ -d $(@D) ] || mkdir -p $(@D) - $(Q)if [ ! -f $@ ] || [ "$$(cat $@)" != "$(ZEPHYR_TARGET)" ]; then \ - echo "$(ZEPHYR_TARGET)" > $@; \ + $(Q)if [ ! -f $@ ] || [ "$$(cat $@)" != "$(ZEPHYR_BUILD_KEY)" ]; then \ + echo "$(ZEPHYR_BUILD_KEY)" > $@; \ fi # Per-binary CMake build dir, keyed on $(notdir $@) so binaries build in @@ -101,11 +117,10 @@ ZEPHYR_OUT = $(ZEPHYR_BUILD_DIR)/$(notdir $@) # fit comfortably within the Zephyr main-thread stack (see app/prj.conf). CFLAGS += -DMLD_CONFIG_REDUCE_RAM -# Shrink the test iteration counts (the sources default them higher, sized for -# native hardware): QEMU is far slower. On CFLAGS so they forward below. -CFLAGS += -DNTESTS=3 \ +# Put the configurable test counts on CFLAGS so they forward below. +CFLAGS += -DNTESTS=$(NTESTS) \ -DMLD_BENCHMARK_NTESTS=10 -DMLD_BENCHMARK_NITERATIONS=10 -DMLD_BENCHMARK_NWARMUP=10 \ - -DNUM_RANDOM_TESTS=100 + -DNUM_RANDOM_TESTS=$(NUM_RANDOM_TESTS) # The binary's CFLAGS, forwarded to the CMake build (which applies them to the # mldsa amalgamation and test sources alike). '=' not ':=', so the recipe-time @@ -140,12 +155,20 @@ CUSTOM_BUILD = \ $(ZEPHYR_CMAKE_ENV) cmake --build $(ZEPHYR_OUT) >/dev/null && \ cp $(ZEPHYR_OUT)/zephyr/zephyr.elf $@ +# A native assembly amalgamation can directly include development sources that +# do not appear in LIB_SRCS. The wildcard is empty before the Armv8.1-M x1 +# backend lands and becomes an explicit dependency when that source is present. +ZEPHYR_NATIVE_ASM_INPUTS := mldsa/mldsa_native_asm.S \ + $(wildcard dev/fips202/armv81m_clean/src/keccak_f1600_x1_armv7m.S) + # A custom build links the test sources directly rather than from objects, so # nothing otherwise makes the bins depend on the Zephyr app inputs or the # active-target marker. components.mk attaches CUSTOM_BUILD_DEPS to every test -# binary (in its CUSTOM_BUILD branch), so a CMakeLists/shim/overlay edit or a -# target switch forces a rebuild. Set here (before components.mk is included). -CUSTOM_BUILD_DEPS := $(ZEPHYR_ACTIVE_TARGET) $(ZEPHYR_APP_INPUTS) +# binary (in its CUSTOM_BUILD branch), so an application input, native assembly +# input, target, backend, or test-count change forces a rebuild. Set here before +# components.mk is included. +CUSTOM_BUILD_DEPS := $(ZEPHYR_ACTIVE_TARGET) $(ZEPHYR_APP_INPUTS) \ + $(if $(ZEPHYR_FIPS202_BACKEND),$(ZEPHYR_NATIVE_ASM_INPUTS)) ifeq ($(ZEPHYR_IS_NUCLEO_N657X0_Q),) EXEC_WRAPPER := $(abspath $(PLATFORM_PATH)/exec_wrapper.py) From 5ee4dbbfde97161bee224ba1beb304479d2bbede Mon Sep 17 00:00:00 2001 From: Brendan Moran Date: Mon, 3 Aug 2026 07:21:27 +0100 Subject: [PATCH 3/4] Test: Preserve build mode when running tests Signed-off-by: Brendan Moran --- scripts/tests | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/tests b/scripts/tests index 3e28fca1a9..59a512acb0 100755 --- a/scripts/tests +++ b/scripts/tests @@ -560,10 +560,7 @@ class Tests: log = logger(test_type, scheme_str, self.args.cross_prefix, opt) args = ["make", test_type.make_run_target(scheme)] - # run_abicheck is conditional on OPT in the Makefile. Keep its run - # invocation aligned with the build; otherwise a Zephyr platform's - # OPT=0 default turns this into an empty, successful target. - if test_type == TEST_TYPES.ABICHECK: + if test_type.is_example() is False: args += [f"OPT={int(opt)}", f"AUTO={int(self.args.auto)}"] if test_type.is_benchmark() is False and test_type.is_example() is False: args += self.make_j() From b77c35afde1c6981fd84c967517f53757feffec1 Mon Sep 17 00:00:00 2001 From: Brendan Moran Date: Thu, 6 Aug 2026 07:05:26 +0100 Subject: [PATCH 4/4] Test: Align ABI checker main declaration Signed-off-by: Brendan Moran --- test/abicheck/abicheck.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/abicheck/abicheck.c b/test/abicheck/abicheck.c index e3ed632da2..2400e07a00 100644 --- a/test/abicheck/abicheck.c +++ b/test/abicheck/abicheck.c @@ -22,12 +22,12 @@ static const abicheck_entry_t all_checks[] = {{NULL, NULL}}; /* Return-code convention: see abicheck_common.h. SKIPPED means the kernel * built but the host lacks the runtime capability; the generated check * decides this via mld_sys_check_capability. */ -/* Zephyr renames main to mld_test_main, making it an ordinary global function - * for which -Wmissing-prototypes requires a prior declaration. Its arguments - * match the Zephyr shim that invokes the renamed entry point. */ -int main(int argc, char **argv); +/* Prototype for a re-#define'd main, to satisfy -Wmissing-prototypes. */ +#if defined(main) +int main(int argc, char *argv[]); +#endif -int main(int argc, char **argv) +int main(int argc, char *argv[]) { int failed_tests = 0; int selftest_failures;