summaryrefslogtreecommitdiff
path: root/test/tx/cmake/module_qemu/CMakeLists.txt
diff options
context:
space:
mode:
Diffstat (limited to 'test/tx/cmake/module_qemu/CMakeLists.txt')
-rw-r--r--test/tx/cmake/module_qemu/CMakeLists.txt224
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()