diff options
| author | Frédéric Desbiens <[email protected]> | 2026-10-23 13:06:05 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-10-23 13:06:05 -0400 |
| commit | 4c2179186c5348175bbce481cfafbc3d13f54a61 (patch) | |
| tree | ece1b62db161d3aaf6b64dd67896beea5f2024d0 /test/tx/cmake/module_qemu/CMakeLists.txt | |
| parent | 3a77bc385c6cac82c71e1c36d6817e0860d12526 (diff) | |
Added a QEMU module execution harness for the Cortex-M3 and M4 ports (#821)dev
No Cortex-M module port ran in CI. gcc_check compiles each port's module manager
sources and links nothing that loads a module, and the host tests under
test/tx/module_manager compile a few portable sources against stubs. A module's
start-up, its kernel calls through the supervisor call, the MPU programming on context
switches and the fault path that terminates a misbehaving module were exercised only
on the Cortex-R52 FVP.
test/tx/cmake/module_qemu is a stand-alone CMake project, like test/tx/cmake/riscv, that
builds the kernel and Module Manager with a module port's headers, builds test modules
position-independent with the port's module linker script, embeds their raw images in
manager images, and registers each image with CTest. A Python runner starts it on
QEMU's MPS2 board for the core, mps2-an385 for cortex_m3 and mps2-an386 for cortex_m4
built for its FPU with the hard-float ABI, and judges the result line the image prints
over semihosting and the exit status it sets. Three images per port: a protected module
loaded, started, exercising queues, semaphores, a worker thread and the timer, stopped
and unloaded twice with its memory returned; the same module faulting on a read of
kernel memory, reported to the fault notification with its module, thread and address
and terminated, then stopped and unloaded; and the module built privileged and
unprotected, whose identical read succeeds. The fault image is also the check that
QEMU's MPU enforces at all. The new module_qemu workflow runs both ports on changes to
common/, common_modules/, the two module ports and the harness, installing QEMU from
the Ubuntu archive through the bounded apt helpers in scripts/tx_ci_common.sh.
Both ports pass 3/3 on QEMU 8.2.2 with Arm GNU 14.3.rel1, using the workflow's commands,
with zero compiler warnings; configure, build and run take 9 seconds for both. The AI
disclosure check passes. The workflow itself has not run on GitHub yet.
Assisted-by: Claude Code (Opus 5.5) <[email protected]>
Diffstat (limited to 'test/tx/cmake/module_qemu/CMakeLists.txt')
| -rw-r--r-- | test/tx/cmake/module_qemu/CMakeLists.txt | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/test/tx/cmake/module_qemu/CMakeLists.txt b/test/tx/cmake/module_qemu/CMakeLists.txt new file mode 100644 index 00000000..bbf7c74c --- /dev/null +++ b/test/tx/cmake/module_qemu/CMakeLists.txt @@ -0,0 +1,224 @@ +# Copyright (c) 2026 Eclipse ThreadX contributors +# +# This program and the accompanying materials are made available under the +# terms of the MIT License which is available at +# https://opensource.org/licenses/MIT. +# +# AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5.5). +# The AI-generated portions may be considered public domain (CC0-1.0) +# and not subject to the project's licence. The human contributor has +# reviewed and verified that the code is correct. +# +# SPDX-License-Identifier: MIT and CC0-1.0 + +# Module execution harness. +# +# Builds a module manager image per test, each embedding the modules it loads, +# for one Cortex-M module port, and runs every image on QEMU's MPS2 board for +# that core, asserting the result each image reports over semihosting. +# +# Why this exists: the host tests under test/tx/module_manager compile a few +# module manager sources against stubs, and nothing else in CI runs a Cortex-M +# module port at all. A module's start-up, its kernel calls through the +# supervisor call, the MPU programming on every context switch and the fault +# path that terminates a misbehaving module are only exercised by executing +# them. This is the per-architecture part of the module test strategy; the +# portable module manager code is meant to be covered on the host. +# +# Its own top-level project, like test/tx/cmake/riscv, because it cross-builds +# with a bare-metal toolchain and runs under an emulator, which the host suite +# beside it does neither of. +# +# Configure once per port: +# +# cmake -S test/tx/cmake/module_qemu -B build/module_qemu_m3 -G Ninja \ +# -DCMAKE_TOOLCHAIN_FILE=$PWD/test/tx/cmake/module_qemu/arm-none-eabi-qemu.cmake \ +# -DMODULE_QEMU_PORT=cortex_m3 +# cmake --build build/module_qemu_m3 +# ctest --test-dir build/module_qemu_m3 --output-on-failure +# +# MODULE_QEMU_PORT is cortex_m3 (QEMU mps2-an385) or cortex_m4 (mps2-an386, +# built for its FPU with the hard-float ABI). + +cmake_minimum_required(VERSION 3.20 FATAL_ERROR) + +project(threadx_module_qemu LANGUAGES C ASM) + +set(MODULE_QEMU_PORT "cortex_m3" CACHE STRING "Cortex-M module port to build and run") +set_property(CACHE MODULE_QEMU_PORT PROPERTY STRINGS cortex_m3 cortex_m4) + +if(MODULE_QEMU_PORT STREQUAL "cortex_m3") + set(PORT_CPU_FLAGS -mcpu=cortex-m3 -mthumb) + set(QEMU_MACHINE mps2-an385) +elseif(MODULE_QEMU_PORT STREQUAL "cortex_m4") + set(PORT_CPU_FLAGS -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard) + set(QEMU_MACHINE mps2-an386) +else() + message(FATAL_ERROR "MODULE_QEMU_PORT must be cortex_m3 or cortex_m4, not '${MODULE_QEMU_PORT}'") +endif() + +set(REPO_ROOT ${CMAKE_CURRENT_LIST_DIR}/../../../..) +set(PORT_DIR ${REPO_ROOT}/ports_module/${MODULE_QEMU_PORT}/gnu) +set(HARNESS_SRC ${CMAKE_CURRENT_LIST_DIR}/src) +set(HARNESS_TESTS ${CMAKE_CURRENT_LIST_DIR}/tests) + +message(STATUS "Module port: ${MODULE_QEMU_PORT}, QEMU machine: ${QEMU_MACHINE}") + +add_compile_options(${PORT_CPU_FLAGS} -g -O0 -ffunction-sections -fdata-sections) +add_link_options(${PORT_CPU_FLAGS}) + +# --------------------------------------------------------------------------- +# The kernel and the Module Manager, built with the module port's headers. +# +# One library, not the threadx library plus the manager: the module port's +# tx_port.h adds the owning module to the kernel's control blocks, so a kernel +# built against the base port and a manager built against this one would +# disagree about every object while linking without complaint. tx_misra.c is +# left out because nothing here builds with TX_MISRA_ENABLE. +# --------------------------------------------------------------------------- + +file(GLOB TX_COMMON_SRC ${REPO_ROOT}/common/src/*.c) +list(FILTER TX_COMMON_SRC EXCLUDE REGEX ".*/tx_misra\\.c$") +file(GLOB TXM_PORT_SRC + ${PORT_DIR}/module_manager/src/*.S + ${PORT_DIR}/module_manager/src/*.s + ${PORT_DIR}/module_manager/src/*.c) +file(GLOB TXM_MANAGER_SRC ${REPO_ROOT}/common_modules/module_manager/src/*.c) + +add_library(threadx_module STATIC ${TX_COMMON_SRC} ${TXM_PORT_SRC} ${TXM_MANAGER_SRC}) +target_include_directories(threadx_module PUBLIC + ${PORT_DIR}/inc + ${REPO_ROOT}/common/inc + ${REPO_ROOT}/common_modules/inc + ${REPO_ROOT}/common_modules/module_manager/inc) + +# --------------------------------------------------------------------------- +# The module-side library: the API shims that run inside a module. It belongs +# to the module images only; linked into a manager it would redirect the +# manager's own service calls into a module. +# +# Position independent, as everything in a module must be: +# -fpic / -fpie data references go through the GOT +# -msingle-pic-base r9 is the GOT base, set by the manager's +# thread stack build +# -mno-pic-data-is-text-relative data is allocated from the manager's pool, +# not at a fixed offset from the code +# -fno-plt no procedure linkage table to populate +# --------------------------------------------------------------------------- + +set(MODULE_PIC_FLAGS -fno-plt -mno-pic-data-is-text-relative -msingle-pic-base) + +file(GLOB TXM_MODULE_LIB_SRC + ${REPO_ROOT}/common_modules/module_lib/src/*.c + ${PORT_DIR}/module_lib/src/*.c) + +add_library(txm_module_lib STATIC ${TXM_MODULE_LIB_SRC}) +target_include_directories(txm_module_lib PUBLIC + ${PORT_DIR}/inc + ${REPO_ROOT}/common/inc + ${REPO_ROOT}/common_modules/inc) +target_compile_options(txm_module_lib PRIVATE -fpic ${MODULE_PIC_FLAGS}) + +# --------------------------------------------------------------------------- +# A test module: one source, built with a given preamble property word, linked +# with the port's module linker script, and reduced to the raw image a manager +# embeds. The script defines the code and data sizes the preamble reports. +# --------------------------------------------------------------------------- + +function(module_qemu_add_module name source properties) + add_executable(${name}.elf + ${HARNESS_SRC}/module_preamble.S + ${PORT_DIR}/example_build/gcc_setup.s + ${source}) + target_include_directories(${name}.elf PRIVATE ${HARNESS_SRC}) + target_compile_definitions(${name}.elf PRIVATE TEST_MODULE_PROPERTIES=${properties}) + target_compile_options(${name}.elf PRIVATE -fpie ${MODULE_PIC_FLAGS}) + target_link_libraries(${name}.elf PRIVATE txm_module_lib) + target_link_options(${name}.elf PRIVATE + -T${PORT_DIR}/example_build/sample_threadx_module.ld + -nostartfiles + -nostdlib + -Wl,-pie + -Wl,--no-dynamic-linker + -Wl,-e,_txm_module_thread_shell_entry + -Wl,--no-warn-rwx-segments + -Wl,-Map=${name}.map) + set_target_properties(${name}.elf PROPERTIES + LINK_DEPENDS ${PORT_DIR}/example_build/sample_threadx_module.ld) + + set(bin ${CMAKE_CURRENT_BINARY_DIR}/${name}.bin) + add_custom_command( + OUTPUT ${bin} + COMMAND ${CMAKE_OBJCOPY} -O binary $<TARGET_FILE:${name}.elf> ${bin} + DEPENDS ${name}.elf + COMMENT "Converting ${name} to a raw module image" + VERBATIM) + add_custom_target(${name}_bin DEPENDS ${bin}) + + # The blob that embeds the image in a manager, as bytes, under the module's + # name as its symbol. + set(BLOB_SYMBOL ${name}) + set(BLOB_FILE ${bin}) + configure_file(${HARNESS_SRC}/module_blob.S.in ${CMAKE_CURRENT_BINARY_DIR}/${name}_blob.S @ONLY) + set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/${name}_blob.S PROPERTIES OBJECT_DEPENDS ${bin}) +endfunction() + +# GNU compiler ID in bits 31-24; bit 0 user mode, bit 1 memory protection, +# bit 2 shared memory access. +module_qemu_add_module(module_services_protected ${HARNESS_TESTS}/module_services.c 0x02000007) +module_qemu_add_module(module_services_privileged ${HARNESS_TESTS}/module_services.c 0x02000000) + +# --------------------------------------------------------------------------- +# The manager images, one per test, and the CTest entries that run them. +# --------------------------------------------------------------------------- + +find_package(Python3 COMPONENTS Interpreter) +find_program(QEMU_SYSTEM_ARM qemu-system-arm) + +enable_testing() + +function(module_qemu_add_test name test_source) + set(blobs ${ARGN}) + set(blob_sources "") + foreach(blob IN LISTS blobs) + list(APPEND blob_sources ${CMAKE_CURRENT_BINARY_DIR}/${blob}_blob.S) + endforeach() + + add_executable(${name}.elf + ${HARNESS_SRC}/startup.S + ${HARNESS_SRC}/tx_initialize_low_level.S + ${HARNESS_SRC}/harness.c + ${HARNESS_TESTS}/harness_checks.c + ${test_source} + ${blob_sources}) + target_include_directories(${name}.elf PRIVATE ${HARNESS_SRC} ${HARNESS_TESTS}) + target_link_libraries(${name}.elf PRIVATE threadx_module) + target_link_options(${name}.elf PRIVATE + -T${HARNESS_SRC}/manager.ld + -nostartfiles + --specs=nano.specs + --specs=nosys.specs + -Wl,--gc-sections + -Wl,--no-warn-rwx-segments + -Wl,-Map=${name}.map) + set_target_properties(${name}.elf PROPERTIES LINK_DEPENDS ${HARNESS_SRC}/manager.ld) + foreach(blob IN LISTS blobs) + add_dependencies(${name}.elf ${blob}_bin) + endforeach() + + if(Python3_FOUND AND QEMU_SYSTEM_ARM) + add_test(NAME ${MODULE_QEMU_PORT}::${name} + COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/run_qemu_test.py + --qemu ${QEMU_SYSTEM_ARM} + --machine ${QEMU_MACHINE} + --elf $<TARGET_FILE:${name}.elf>) + endif() +endfunction() + +module_qemu_add_test(test_module_lifecycle ${HARNESS_TESTS}/test_module_lifecycle.c module_services_protected) +module_qemu_add_test(test_module_fault ${HARNESS_TESTS}/test_module_fault.c module_services_protected) +module_qemu_add_test(test_module_privileged ${HARNESS_TESTS}/test_module_privileged.c module_services_privileged) + +if(NOT (Python3_FOUND AND QEMU_SYSTEM_ARM)) + message(STATUS "qemu-system-arm or Python 3 not found; the images build but no tests are registered.") +endif() |
