# 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 $ ${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 $) 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()