diff options
Diffstat (limited to 'ports/cortex_r52/gnu')
81 files changed, 19164 insertions, 0 deletions
diff --git a/ports/cortex_r52/gnu/CMakeLists.txt b/ports/cortex_r52/gnu/CMakeLists.txt new file mode 100644 index 00000000..6444a063 --- /dev/null +++ b/ports/cortex_r52/gnu/CMakeLists.txt @@ -0,0 +1,89 @@ +# Copyright (c) 2026 Eclipse ThreadX contributors +# SPDX-License-Identifier: MIT +# Portions of this file were generated with AI assistance. + +target_sources(${PROJECT_NAME} + PRIVATE + # {{BEGIN_TARGET_SOURCES}} + ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_context_restore.S + ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_context_save.S + ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_fiq_context_restore.S + ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_fiq_context_save.S + ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_fiq_nesting_end.S + ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_fiq_nesting_start.S + ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_interrupt_control.S + ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_interrupt_disable.S + ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_interrupt_restore.S + ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_irq_nesting_end.S + ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_irq_nesting_start.S + ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_schedule.S + ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_stack_build.S + ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_system_return.S + ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_vectored_context_save.S + ${CMAKE_CURRENT_LIST_DIR}/src/tx_timer_interrupt.S + ${CMAKE_CURRENT_LIST_DIR}/src/tx_port_offset_check.c + # {{END_TARGET_SOURCES}} +) + +target_include_directories(${PROJECT_NAME} + SYSTEM + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/inc +) + +# Lazy floating-point context save and restore. PUBLIC because the assembly +# in the library and the application must agree: the flag changes which +# registers the context switch saves, so a mismatched application would either +# lose floating-point state or misread the thread control block. Requires a +# floating-point ABI, so pair it with -DTX_R52_FLOAT_ABI=hard. +option(TX_R52_ENABLE_VFP "Build with lazy VFP context save/restore" OFF) + +# FIQ support and interrupt nesting. These change which registers the +# assembly saves and what tx_port.h reports in TX_PORT_SPECIFIC_BUILD_OPTIONS, +# so like the VFP flag they must be PUBLIC: library and application have to +# agree. FIQ nesting additionally requires FIQ support. +option(TX_R52_ENABLE_FIQ "Build with FIQ support" OFF) +option(TX_R52_ENABLE_IRQ_NESTING "Build with nested IRQ support" OFF) +option(TX_R52_ENABLE_FIQ_NESTING "Build with nested FIQ support" OFF) + +if(TX_R52_ENABLE_FIQ) + target_compile_definitions(${PROJECT_NAME} PUBLIC TX_ENABLE_FIQ_SUPPORT) +endif() +if(TX_R52_ENABLE_IRQ_NESTING) + target_compile_definitions(${PROJECT_NAME} PUBLIC TX_ENABLE_IRQ_NESTING) +endif() +if(TX_R52_ENABLE_FIQ_NESTING) + if(NOT TX_R52_ENABLE_FIQ) + message(FATAL_ERROR + "TX_R52_ENABLE_FIQ_NESTING requires TX_R52_ENABLE_FIQ: nested FIQ " + "handling is meaningless without FIQ support compiled in.") + endif() + target_compile_definitions(${PROJECT_NAME} PUBLIC TX_ENABLE_FIQ_NESTING) +endif() +if(TX_R52_ENABLE_VFP) + if(NOT TX_R52_FLOAT_ABI STREQUAL "hard") + message(FATAL_ERROR + "TX_R52_ENABLE_VFP requires TX_R52_FLOAT_ABI=hard, but it is " + "'${TX_R52_FLOAT_ABI}'. TX_ENABLE_VFP_SUPPORT enables the VMRS, " + "VSTMDB and VLDMIA blocks in the port assembly, and a soft float " + "ABI leaves the assembler with no FPU to accept them, so this " + "combination fails to assemble. Use -DTX_R52_FLOAT_ABI=hard.") + endif() + target_compile_definitions(${PROJECT_NAME} PUBLIC TX_ENABLE_VFP_SUPPORT) +endif() + +# Armv8-R AEM FVP example builds (boot check now, kernel demo from AR1/M2). +# Off by default so the default build stays a pure static-library build. +option(TX_R52_BUILD_FVP_EXAMPLE "Build the Armv8-R AEM FVP example targets" OFF) +if(TX_R52_BUILD_FVP_EXAMPLE) + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/example_build/fvp_baser_aemv8r) +endif() + +# NXP S32Z280-594EVB example build. Separate option from the FVP example so +# that FVP regression stays green while silicon bring-up is in progress, and so +# neither build can perturb the other: the two boards differ in reset state, +# memory map and instruction set at reset. +option(TX_R52_BUILD_S32Z280_EXAMPLE "Build the NXP S32Z280-594EVB example targets" OFF) +if(TX_R52_BUILD_S32Z280_EXAMPLE) + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/example_build/s32z280_evb) +endif() diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt new file mode 100644 index 00000000..ee01eb3e --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt @@ -0,0 +1,727 @@ +# Copyright (c) 2026 Eclipse ThreadX contributors +# SPDX-License-Identifier: MIT +# Portions of this file were generated with AI assistance. +# +# Example builds for Cortex-R52 on the Armv8-R AEM FVP (FVP_BaseR_AEMv8R). + +set(FVP_DIR ${CMAKE_CURRENT_LIST_DIR}) + +# A bare-metal image has one flat DRAM region and no OS page permissions; access +# control belongs to the MPU, so an RWX segment is expected here rather than a +# mistake. GNU ld has warned about them since binutils 2.39 and takes this flag +# to stay quiet. ld.lld does not warn and rejects the flag outright, failing the +# link with "unknown argument", so the suppression is chosen by toolchain +# instead of being spelled into every target. +if(CMAKE_C_COMPILER_ID STREQUAL "GNU") + set(R52_LINK_QUIET_RWX -Wl,--no-warn-rwx-segments) +else() + set(R52_LINK_QUIET_RWX) +endif() + +# Console sources. Both backends are always compiled; console.c selects one at +# compile time and --gc-sections drops the unused one, which keeps every image +# building in either configuration without per-target source juggling. +set(R52_CONSOLE_SOURCES + ${FVP_DIR}/console.c + ${FVP_DIR}/uart_pl011.c + ${FVP_DIR}/mpu.c +) + +# AR1/M1 -- boot check. Verifies the EL2 configuration, the drop to EL1 and +# the EL2 HVC seam. Standalone by design: it does not link ThreadX, so a +# failure here is unambiguously a boot problem rather than a kernel problem. +add_executable(boot_check.elf EXCLUDE_FROM_ALL + ${FVP_DIR}/entry.S + ${R52_CONSOLE_SOURCES} + ${FVP_DIR}/bsp_boot.c +) + +target_include_directories(boot_check.elf PRIVATE ${FVP_DIR}) + +target_link_options(boot_check.elf PRIVATE + -T${FVP_DIR}/link.lds + -nostartfiles + -Wl,-Map=boot_check.map + ${R52_LINK_QUIET_RWX} +) + +# AR1/M2 -- cooperative two-thread demo. Links ThreadX and exercises the +# ported context-switch assembly with no interrupts and no timer tick. +add_executable(demo_m2.elf EXCLUDE_FROM_ALL + ${FVP_DIR}/entry.S + ${R52_CONSOLE_SOURCES} + ${FVP_DIR}/tx_initialize_low_level.S + ${FVP_DIR}/demo_m2.c +) + +target_link_libraries(demo_m2.elf PRIVATE threadx) + +target_include_directories(demo_m2.elf PRIVATE + ${FVP_DIR} + ${CMAKE_SOURCE_DIR}/common/inc + ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc +) + +target_link_options(demo_m2.elf PRIVATE + -T${FVP_DIR}/link.lds + -nostartfiles + -Wl,-Map=demo_m2.map + ${R52_LINK_QUIET_RWX} +) + +# AR1/M3 -- periodic tick and preemptive scheduling. Adds GICv3, the generic +# timer and the ThreadX IRQ path. TX_R52_USE_THREADX_IRQ routes the EL1 IRQ +# vector into _tx_thread_context_save and has _tx_initialize_low_level bring up +# the interrupt controller; images without it keep the fault reporter. +add_executable(demo_m3.elf EXCLUDE_FROM_ALL + ${FVP_DIR}/entry.S + ${R52_CONSOLE_SOURCES} + ${FVP_DIR}/gicv3.c + ${FVP_DIR}/timer.c + ${FVP_DIR}/irq_dispatch.c + ${FVP_DIR}/tx_initialize_low_level.S + ${FVP_DIR}/demo_m3.c +) + +target_compile_definitions(demo_m3.elf PRIVATE TX_R52_USE_THREADX_IRQ) + +target_link_libraries(demo_m3.elf PRIVATE threadx) + +target_include_directories(demo_m3.elf PRIVATE + ${FVP_DIR} + ${CMAKE_SOURCE_DIR}/common/inc + ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc +) + +target_link_options(demo_m3.elf PRIVATE + -T${FVP_DIR}/link.lds + -nostartfiles + -Wl,-Map=demo_m3.map + ${R52_LINK_QUIET_RWX} +) + +# AR1/M4 -- the standard eight-thread ThreadX demo. demo_threadx.c is the +# shipped sample kept byte-identical apart from its header note and one call +# into the verification harness, which lives in demo_verify.c. +add_executable(demo_threadx.elf EXCLUDE_FROM_ALL + ${FVP_DIR}/entry.S + ${R52_CONSOLE_SOURCES} + ${FVP_DIR}/gicv3.c + ${FVP_DIR}/timer.c + ${FVP_DIR}/irq_dispatch.c + ${FVP_DIR}/tx_initialize_low_level.S + ${FVP_DIR}/demo_threadx.c + ${FVP_DIR}/demo_verify.c +) + +target_compile_definitions(demo_threadx.elf PRIVATE TX_R52_USE_THREADX_IRQ) + +target_link_libraries(demo_threadx.elf PRIVATE threadx) + +target_include_directories(demo_threadx.elf PRIVATE + ${FVP_DIR} + ${CMAKE_SOURCE_DIR}/common/inc + ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc +) + +target_link_options(demo_threadx.elf PRIVATE + -T${FVP_DIR}/link.lds + -nostartfiles + -Wl,-Map=demo_threadx.map + ${R52_LINK_QUIET_RWX} +) + +# CLZ lowest-set-bit regression. tx_port.h replaces tx_thread.h's portable +# priority search with a CLZ instruction, and that macro decides which thread +# runs next on every suspend and resume -- so it gets an image of its own rather +# than being covered incidentally by demos that would merely hang if it broke. +# Needs the tick because the checks run in a thread; needs nothing else. +add_executable(demo_clz.elf EXCLUDE_FROM_ALL + ${FVP_DIR}/entry.S + ${R52_CONSOLE_SOURCES} + ${FVP_DIR}/gicv3.c + ${FVP_DIR}/timer.c + ${FVP_DIR}/irq_dispatch.c + ${FVP_DIR}/tx_initialize_low_level.S + ${FVP_DIR}/demo_clz.c +) + +target_compile_definitions(demo_clz.elf PRIVATE TX_R52_USE_THREADX_IRQ) + +target_link_libraries(demo_clz.elf PRIVATE threadx) + +target_include_directories(demo_clz.elf PRIVATE + ${FVP_DIR} + ${CMAKE_SOURCE_DIR}/common/inc + ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc +) + +target_link_options(demo_clz.elf PRIVATE + -T${FVP_DIR}/link.lds + -nostartfiles + -Wl,-Map=demo_clz.map + ${R52_LINK_QUIET_RWX} +) + +# AR1/M5 -- lazy VFP context save and restore. Only meaningful when the +# library was built with TX_R52_ENABLE_VFP and a floating-point ABI, so the +# target exists only in that configuration. +if(TX_R52_ENABLE_VFP) + add_executable(demo_m5.elf EXCLUDE_FROM_ALL + ${FVP_DIR}/entry.S + ${R52_CONSOLE_SOURCES} + ${FVP_DIR}/gicv3.c + ${FVP_DIR}/timer.c + ${FVP_DIR}/irq_dispatch.c + ${FVP_DIR}/tx_initialize_low_level.S + ${FVP_DIR}/demo_m5.c + ) + + target_compile_definitions(demo_m5.elf PRIVATE TX_R52_USE_THREADX_IRQ) + + target_link_libraries(demo_m5.elf PRIVATE threadx) + + target_include_directories(demo_m5.elf PRIVATE + ${FVP_DIR} + ${CMAKE_SOURCE_DIR}/common/inc + ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc + ) + + target_link_options(demo_m5.elf PRIVATE + -T${FVP_DIR}/link.lds + -nostartfiles + -Wl,-Map=demo_m5.map + ${R52_LINK_QUIET_RWX} + ) +endif() + +# AR1/M5 -- PMSAv8-R protection and caches. Verifies enforcement, so it needs +# the recoverable data-abort path in entry.S (TX_R52_MPU_FAULT_TEST) and the +# protection itself (TX_R52_ENABLE_MPU), independently of the global option. +add_executable(demo_mpu.elf EXCLUDE_FROM_ALL + ${FVP_DIR}/entry.S + ${R52_CONSOLE_SOURCES} + ${FVP_DIR}/gicv3.c + ${FVP_DIR}/timer.c + ${FVP_DIR}/irq_dispatch.c + ${FVP_DIR}/tx_initialize_low_level.S + ${FVP_DIR}/demo_mpu.c +) + +target_compile_definitions(demo_mpu.elf PRIVATE + TX_R52_USE_THREADX_IRQ + TX_R52_ENABLE_MPU + TX_R52_MPU_FAULT_TEST +) + +target_link_libraries(demo_mpu.elf PRIVATE threadx) + +target_include_directories(demo_mpu.elf PRIVATE + ${FVP_DIR} + ${CMAKE_SOURCE_DIR}/common/inc + ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc +) + +target_link_options(demo_mpu.elf PRIVATE + -T${FVP_DIR}/link.lds + -nostartfiles + -Wl,-Map=demo_mpu.map + ${R52_LINK_QUIET_RWX} +) + +# Nested IRQ handling. Needs the library built with TX_R52_ENABLE_IRQ_NESTING, +# because _tx_thread_irq_nesting_start only re-enables IRQ usefully when +# tx_port.h agrees the configuration supports it, and entry.S only emits the +# start/end pairing under the same macro. The image therefore exists in that +# configuration only, as demo_m5.elf does for VFP. +if(TX_R52_ENABLE_IRQ_NESTING) + add_executable(demo_nesting.elf EXCLUDE_FROM_ALL + ${FVP_DIR}/entry.S + ${R52_CONSOLE_SOURCES} + ${FVP_DIR}/gicv3.c + ${FVP_DIR}/timer.c + ${FVP_DIR}/irq_dispatch.c + ${FVP_DIR}/tx_initialize_low_level.S + ${FVP_DIR}/demo_nesting.c + ) + + target_compile_definitions(demo_nesting.elf PRIVATE TX_R52_USE_THREADX_IRQ) + + target_link_libraries(demo_nesting.elf PRIVATE threadx) + + target_include_directories(demo_nesting.elf PRIVATE + ${FVP_DIR} + ${CMAKE_SOURCE_DIR}/common/inc + ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc + ) + + target_link_options(demo_nesting.elf PRIVATE + -T${FVP_DIR}/link.lds + -nostartfiles + -Wl,-Map=demo_nesting.map + ${R52_LINK_QUIET_RWX} + ) +endif() + +# Nested FIQ handling. Needs TX_R52_ENABLE_FIQ_NESTING, which the port-level +# CMakeLists already requires TX_R52_ENABLE_FIQ alongside, so the image exists +# only where both the FIQ vector path and the nesting pairing are compiled in. +if(TX_R52_ENABLE_FIQ_NESTING) + add_executable(demo_fiq.elf EXCLUDE_FROM_ALL + ${FVP_DIR}/entry.S + ${R52_CONSOLE_SOURCES} + ${FVP_DIR}/gicv3.c + ${FVP_DIR}/timer.c + ${FVP_DIR}/irq_dispatch.c + ${FVP_DIR}/tx_initialize_low_level.S + ${FVP_DIR}/demo_fiq.c + ) + + target_compile_definitions(demo_fiq.elf PRIVATE TX_R52_USE_THREADX_IRQ) + + target_link_libraries(demo_fiq.elf PRIVATE threadx) + + target_include_directories(demo_fiq.elf PRIVATE + ${FVP_DIR} + ${CMAKE_SOURCE_DIR}/common/inc + ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc + ) + + target_link_options(demo_fiq.elf PRIVATE + -T${FVP_DIR}/link.lds + -nostartfiles + -Wl,-Map=demo_fiq.map + ${R52_LINK_QUIET_RWX} + ) +endif() + +# Every image built here, in the order they should be exercised. +set(R52_IMAGES boot_check.elf demo_m2.elf demo_m3.elf demo_threadx.elf demo_mpu.elf + demo_clz.elf) + +# The linker script is passed with -T, which CMake does not treat as a +# dependency, so editing it would not trigger a relink and stale images would +# be tested against new region boundaries. Declare it explicitly. +foreach(image IN LISTS R52_IMAGES) + set_target_properties(${image} PROPERTIES LINK_DEPENDS ${FVP_DIR}/link.lds) +endforeach() +if(TX_R52_ENABLE_VFP) + list(APPEND R52_IMAGES demo_m5.elf) +endif() +if(TX_R52_ENABLE_IRQ_NESTING) + list(APPEND R52_IMAGES demo_nesting.elf) +endif() +if(TX_R52_ENABLE_FIQ_NESTING) + list(APPEND R52_IMAGES demo_fiq.elf) +endif() + +# Console backend. Semihosting is the default because it needs no peripheral +# and so cannot be broken by a wrong memory map; the PL011 path is what real +# silicon will use. +# Protection and caches for every image. Off by default so a bring-up failure +# can always be reproduced with the simplest possible memory configuration. +option(TX_R52_ENABLE_MPU + "Enable the PMSAv8-R MPU and caches in every image" OFF) +if(TX_R52_ENABLE_MPU) + foreach(image IN LISTS R52_IMAGES) + target_compile_definitions(${image} PRIVATE TX_R52_ENABLE_MPU) + endforeach() +endif() + +option(TX_R52_CONSOLE_PL011 + "Use the PL011 UART for console output instead of semihosting" OFF) +if(TX_R52_CONSOLE_PL011) + foreach(image IN LISTS R52_IMAGES) + target_compile_definitions(${image} PRIVATE TX_R52_CONSOLE_PL011) + endforeach() +endif() + +# --------------------------------------------------------------------------- +# ThreadX modules: the module manager, with a sample module linked into the +# module area and loaded in place from there. +# +# This is the AR2 deliverable that the S32Z280 example cannot be: the silicon +# module demonstration is judged by a person reading a console through a debug +# probe, and this one is judged by ctest. The image below is the same port +# under the same three passes, reporting one result line the runner matches. +# +# This target deliberately does NOT link the threadx library's port assembly. +# The module port carries its own copies of the scheduler, context restore and +# stack build, because the region switch happens in the scheduler and a module +# thread starts in a different processor mode. Linking both would give two +# definitions of every one of them. +# +# Guarded by an option, off by default, because it is the only target that needs +# TXM_MODULE_MANAGER in entry.S and the only one that links common_modules. +# --------------------------------------------------------------------------- + +option(TX_R52_BUILD_FVP_MODULE_EXAMPLE + "Build the Armv8-R AEM FVP ThreadX module manager example" OFF) + +if(TX_R52_BUILD_FVP_MODULE_EXAMPLE) + + set(MOD_DIR ${CMAKE_SOURCE_DIR}/ports_module/cortex_r52/gnu) + set(MOD_FVP ${MOD_DIR}/example_build/fvp_baser_aemv8r) + + # --------------------------------------------------------------------- + # A second ThreadX library, built with the module port's headers. + # + # This is not optional and not a convenience. The module port's tx_port.h + # adds the owning module instance to TX_QUEUE, TX_SEMAPHORE, + # TX_EVENT_FLAGS_GROUP and TX_TIMER, and the module fields to TX_THREAD. + # Measured: TX_QUEUE is 68 bytes against the base port's 60, TX_SEMAPHORE 40 + # against 32, TX_THREAD 236 against 184. A kernel compiled against the base + # port and a manager compiled against this one disagree about every object, + # and they link without complaint, because C linking does not compare struct + # layouts. The result would be memory corruption at run time with nothing in + # the build to hint at it. + # + # Guarded by if(NOT TARGET) because the S32Z280 module example defines the + # same library from its own directory, and the two example options can be on + # at once. The definitions are identical -- both describe the module port, + # not a board -- so whichever directory CMake reaches first may create it. + # --------------------------------------------------------------------- + + if(NOT TARGET threadx_module) + + file(GLOB TX_COMMON_SRC ${CMAKE_SOURCE_DIR}/common/src/*.c) + + set(TX_R52_PORT_SRC ${CMAKE_SOURCE_DIR}/ports/cortex_r52/gnu/src) + + # The base port's assembly is reused except for the five files the module + # port replaces, and tx_port_offset_check.c is included deliberately: if + # the module headers moved any offset that assembly depends on, that check + # is what says so. + add_library(threadx_module STATIC EXCLUDE_FROM_ALL + ${TX_COMMON_SRC} + ${TX_R52_PORT_SRC}/tx_port_offset_check.c + ${TX_R52_PORT_SRC}/tx_thread_fiq_context_restore.S + ${TX_R52_PORT_SRC}/tx_thread_fiq_context_save.S + ${TX_R52_PORT_SRC}/tx_thread_fiq_nesting_end.S + ${TX_R52_PORT_SRC}/tx_thread_fiq_nesting_start.S + ${TX_R52_PORT_SRC}/tx_thread_interrupt_control.S + ${TX_R52_PORT_SRC}/tx_thread_interrupt_disable.S + ${TX_R52_PORT_SRC}/tx_thread_interrupt_restore.S + ${TX_R52_PORT_SRC}/tx_thread_irq_nesting_end.S + ${TX_R52_PORT_SRC}/tx_thread_irq_nesting_start.S + ${TX_R52_PORT_SRC}/tx_thread_vectored_context_save.S + ${TX_R52_PORT_SRC}/tx_timer_interrupt.S + ) + + target_include_directories(threadx_module PUBLIC + ${MOD_DIR}/inc + ${CMAKE_SOURCE_DIR}/common/inc + ${CMAKE_SOURCE_DIR}/common_modules/module_manager/inc + ${CMAKE_SOURCE_DIR}/common_modules/module_lib/inc + ${CMAKE_SOURCE_DIR}/common_modules/inc + ) + + target_compile_definitions(threadx_module PUBLIC TXM_MODULE_MANAGER) + + endif() + + # The portable half of the module manager. Globbed rather than listed: it is + # a whole upstream component, not a selection from one, and a file added to it + # upstream should not need a change here to be compiled. + + file(GLOB TXM_MANAGER_SRC ${CMAKE_SOURCE_DIR}/common_modules/module_manager/src/*.c) + + # The module-side library: the API shims that run inside the module. These + # belong to the module's image and must not reach the manager's link -- they + # define the same names as the kernel's own entry points, and as objects they + # beat the kernel's static library, so in one link every service call the + # manager makes is redirected into the module. + + file(GLOB TXM_MODULE_LIB_SRC ${CMAKE_SOURCE_DIR}/common_modules/module_lib/src/*.c) + + # ------------------------------------------------------------------------ + # The module, built as its own image and reduced to a raw binary. + # ------------------------------------------------------------------------ + + add_executable(fvp_demo_module.elf EXCLUDE_FROM_ALL + ${MOD_FVP}/txm_module_preamble.S + ${MOD_FVP}/sample_threadx_module.c + ${MOD_DIR}/module_lib/src/txm_module_thread_shell_entry.c + ${MOD_DIR}/module_lib/src/txm_module_gcc_setup.S + ${TXM_MODULE_LIB_SRC} + ) + + # No TXM_MODULE_MANAGER here: that define selects the kernel side of the + # shared headers, and this is the other side. + + target_include_directories(fvp_demo_module.elf PRIVATE + ${FVP_DIR} + ${MOD_DIR}/inc + ${CMAKE_SOURCE_DIR}/common_modules/module_lib/inc + ${CMAKE_SOURCE_DIR}/common_modules/inc + ${CMAKE_SOURCE_DIR}/common/inc + ) + + # Position independent, and only here. These five flags are what make the + # module relocatable, and putting any of them on the manager would be a bug: + # the manager is the resident image, linked absolutely at the address it + # boots from, and -msingle-pic-base in particular would have it treat r9 as + # a PIC base that nothing sets up. + # + # -fpic data references go through the GOT + # -msingle-pic-base r9 is the GOT base, and the caller sets + # it -- which is what the manager's + # thread stack build does + # -mno-pic-data-is-text-relative the module's data is NOT at a fixed + # offset from its code: code is loaded in + # place in the module area and data is + # allocated from the byte pool, so the gap + # between them is decided at run time + # -fno-plt no procedure linkage table, which a + # module has no loader to populate + # -mno-long-calls see below + # + # -mno-long-calls is the interesting one. The project-wide C flags carry + # -mlong-calls, and under -fpic that makes GCC route EVERY call through the + # GOT -- R_ARM_GOT32 instead of R_ARM_CALL -- including the shell entry's + # call to _gcc_setup, which is the function that fills the GOT in. The + # module would load, enter its shell entry, read a zero out of the + # not-yet-written GOT and branch to address 0. A module never needs long + # calls: it is one contiguous blob and it never calls out of itself, because + # kernel services go through the dispatcher function pointer in its entry + # info. + + target_compile_options(fvp_demo_module.elf PRIVATE + -g + -fpic + -fno-plt + -mno-pic-data-is-text-relative + -msingle-pic-base + -mno-long-calls + ) + + target_link_options(fvp_demo_module.elf PRIVATE + -T${MOD_FVP}/link_demo_module.lds + -nostartfiles + -nostdlib + -Wl,-Map=fvp_demo_module.map + ${R52_LINK_QUIET_RWX} + ) + + set_target_properties(fvp_demo_module.elf PROPERTIES + LINK_DEPENDS ${MOD_FVP}/link_demo_module.lds) + + # The raw image the manager embeds. Written into the binary directory + # because that is the include path module_blob.S searches. + + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/demo_module.bin + COMMAND ${CMAKE_OBJCOPY} -O binary + $<TARGET_FILE:fvp_demo_module.elf> + ${CMAKE_CURRENT_BINARY_DIR}/demo_module.bin + DEPENDS fvp_demo_module.elf + COMMENT "Converting the demonstration module to a raw image" + VERBATIM + ) + + add_custom_target(fvp_demo_module_bin + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/demo_module.bin + ) + + # ------------------------------------------------------------------------ + # The manager images. + # + # Two of them, and everything except the application file is the same, so + # the shared part is a list and a function rather than a second copy. The + # duplicate that a copy would create is not a style question here: an image + # built with a different set of module manager sources, or without + # TX_R52_ENABLE_MPU, would still link and would test something other than + # what its name says. + # ------------------------------------------------------------------------ + + set(TX_R52_MODULE_IMAGE_SRC + # Board support, shared with the other FVP examples + ${FVP_DIR}/entry.S + ${FVP_DIR}/console.c + ${FVP_DIR}/uart_pl011.c + ${FVP_DIR}/mpu.c + ${FVP_DIR}/cache.c + ${FVP_DIR}/gicv3.c + ${FVP_DIR}/timer.c + ${FVP_DIR}/irq_dispatch.c + ${FVP_DIR}/tx_initialize_low_level.S + + # Module manager port + ${MOD_DIR}/module_manager/src/tx_thread_schedule.S + ${MOD_DIR}/module_manager/src/tx_thread_context_save.S + ${MOD_DIR}/module_manager/src/tx_thread_context_restore.S + ${MOD_DIR}/module_manager/src/tx_thread_stack_build.S + ${MOD_DIR}/module_manager/src/tx_thread_system_return.S + ${MOD_DIR}/module_manager/src/txm_module_manager_svc_handler.S + ${MOD_DIR}/module_manager/src/txm_module_manager_fault_capture.S + ${MOD_DIR}/module_manager/src/txm_module_manager_user_mode_entry.S + ${MOD_DIR}/module_manager/src/txm_module_manager_thread_stack_build.S + ${MOD_DIR}/module_manager/src/txm_module_manager_mm_register_setup.c + ${MOD_DIR}/module_manager/src/txm_module_manager_memory_fault_handler.c + ${MOD_DIR}/module_manager/src/txm_module_manager_memory_fault_notify.c + ${MOD_DIR}/module_manager/src/txm_module_manager_alignment_adjust.c + ${MOD_DIR}/module_manager/src/txm_module_manager_external_memory_enable.c + ${MOD_DIR}/module_manager/src/txm_module_manager_offset_check.c + + # The module, as bytes rather than as objects. module_blob.S includes the + # raw image built by fvp_demo_module.elf, so none of the module's symbols + # enter this link. + ${MOD_FVP}/module_blob.S + + # The portable module manager + ${TXM_MANAGER_SRC} + ) + + # .incbin searches the assembler's include paths, not the source tree, and + # demo_module.bin is generated -- so the build directory has to be on that + # path or module_blob.S cannot find the image. Set on the file rather than + # per target, because both images include the same blob the same way. + + set_source_files_properties(${MOD_FVP}/module_blob.S PROPERTIES + OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/demo_module.bin + COMPILE_OPTIONS "-Wa,-I${CMAKE_CURRENT_BINARY_DIR}" + ) + + function(threadx_r52_add_module_image target_name application) + + add_executable(${target_name} EXCLUDE_FROM_ALL + ${TX_R52_MODULE_IMAGE_SRC} + ${application} + ) + + target_include_directories(${target_name} PRIVATE + ${FVP_DIR} + ${MOD_DIR}/inc + ${CMAKE_SOURCE_DIR}/common_modules/module_manager/inc + ${CMAKE_SOURCE_DIR}/common_modules/module_lib/inc + ${CMAKE_SOURCE_DIR}/common_modules/inc + ) + + # TX_R52_ENABLE_MPU is not optional for these images, unlike the AR1 demos + # where it is a switch: without protection there is no module boundary to + # test, and the manager's load window over the module area is programmed by + # mpu_init. TX_R52_USE_THREADX_IRQ brings up the tick, which the module + # needs for the tx_thread_sleep that proves a kernel call returns. + + target_compile_definitions(${target_name} PRIVATE + TXM_MODULE_MANAGER + TX_R52_USE_THREADX_IRQ + TX_R52_ENABLE_MPU + ) + + target_compile_options(${target_name} PRIVATE -g) + + add_dependencies(${target_name} fvp_demo_module_bin) + + target_link_options(${target_name} PRIVATE + -T${MOD_FVP}/link_module.lds + -nostartfiles + -Wl,-Map=${target_name}.map + ${R52_LINK_QUIET_RWX} + ) + + set_target_properties(${target_name} PROPERTIES + LINK_DEPENDS ${MOD_FVP}/link_module.lds) + + target_link_libraries(${target_name} PRIVATE threadx_module) + + endfunction() + + # AR2. What the hardware does about a module that misbehaves. + + threadx_r52_add_module_image(fvp_module.elf + ${MOD_FVP}/sample_threadx_module_manager.c) + + # And what the LOADER does about a module whose property word is not the + # 0x02000003 every example in the tree ships. A separate image because four + # of its six cases never run a module at all, so it has a different subject + # and a different verdict; it shares the blob and nothing else. + + threadx_r52_add_module_image(fvp_module_properties.elf + ${MOD_FVP}/sample_threadx_module_properties.c) + +endif() + +# Run a target on the FVP. Each image exits by itself through the +# semihosting SYS_EXIT call, so no host-side timeout is needed. UART0 is +# routed to stdout unconditionally so that PL011-console images are visible +# too; it is harmless for semihosting images. +find_program(FVP_BASER_AEMV8R FVP_BaseR_AEMv8R + HINTS $ENV{HOME}/FVP_Base_AEMv8R_11.32_19/bin +) +if(FVP_BASER_AEMV8R) + function(threadx_r52_add_fvp_run target_name run_target description) + add_custom_target(${run_target} + COMMAND ${FVP_BASER_AEMV8R} + -C cluster0.NUM_CORES=1 + -C bp.vis.disable_visualisation=1 + -C bp.terminal_0.start_telnet=0 + -C bp.pl011_uart0.out_file=- + -C bp.pl011_uart0.unbuffered_output=1 + -a $<TARGET_FILE:${target_name}> + DEPENDS ${target_name} + USES_TERMINAL + COMMENT "${description}" + ) + endfunction() + + threadx_r52_add_fvp_run(boot_check.elf run-boot-check-r52 + "Running AR1/M1 boot check on FVP_BaseR_AEMv8R...") + threadx_r52_add_fvp_run(demo_m2.elf run-demo-m2-r52 + "Running AR1/M2 cooperative switch demo on FVP_BaseR_AEMv8R...") + threadx_r52_add_fvp_run(demo_m3.elf run-demo-m3-r52 + "Running AR1/M3 tick and preemption demo on FVP_BaseR_AEMv8R...") + threadx_r52_add_fvp_run(demo_threadx.elf run-demo-threadx-r52 + "Running the standard ThreadX demo on FVP_BaseR_AEMv8R...") + threadx_r52_add_fvp_run(demo_clz.elf run-demo-clz-r52 + "Running the CLZ lowest-set-bit regression on FVP_BaseR_AEMv8R...") + if(TX_R52_BUILD_FVP_MODULE_EXAMPLE) + threadx_r52_add_fvp_run(fvp_module.elf run-module-r52 + "Running the AR2 module isolation example on FVP_BaseR_AEMv8R...") + threadx_r52_add_fvp_run(fvp_module_properties.elf run-module-properties-r52 + "Running the AR2 module property contract regression on FVP_BaseR_AEMv8R...") + endif() + + # Automated checks. The runner judges each image by its self-reported + # result and treats a missing result line as failure, so a hang cannot + # pass. Registered with CTest when Python is available, plus a plain + # target so the suite is runnable without CTest. + find_package(Python3 COMPONENTS Interpreter) + if(Python3_FOUND) + set(R52_RUNNER ${FVP_DIR}/test/run_fvp_test.py) + set(R52_TEST_IMAGES ${R52_IMAGES}) + + # AR2. Kept out of R52_IMAGES above rather than added to it, because + # that list is what the TX_R52_ENABLE_MPU and TX_R52_CONSOLE_PL011 + # options are applied to and neither is optional for this image: the + # module boundary IS the MPU, and the result line has to reach the + # runner whichever console the rest of the suite was built with. + if(TX_R52_BUILD_FVP_MODULE_EXAMPLE) + list(APPEND R52_TEST_IMAGES fvp_module.elf fvp_module_properties.elf) + endif() + + enable_testing() + foreach(image IN LISTS R52_TEST_IMAGES) + add_test(NAME r52-fvp-${image} + COMMAND ${Python3_EXECUTABLE} ${R52_RUNNER} + --elf $<TARGET_FILE:${image}> + --fvp ${FVP_BASER_AEMV8R}) + endforeach() + + add_custom_target(check-r52-fvp + COMMENT "Running every Cortex-R52 FVP image through the test runner..." + ) + foreach(image IN LISTS R52_TEST_IMAGES) + add_custom_command(TARGET check-r52-fvp POST_BUILD + COMMAND ${Python3_EXECUTABLE} ${R52_RUNNER} + --elf $<TARGET_FILE:${image}> + --fvp ${FVP_BASER_AEMV8R} + ) + add_dependencies(check-r52-fvp ${image}) + endforeach() + else() + message(STATUS "Python3 not found; check-r52-fvp unavailable.") + endif() +else() + message(STATUS "FVP_BaseR_AEMv8R not found; FVP run targets unavailable.") +endif() diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/board.h b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/board.h new file mode 100644 index 00000000..d5e50b80 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/board.h @@ -0,0 +1,117 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* board.h Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Declarations for the objects that cross the C/assembly boundary in */ +/* this board support package. */ +/* */ +/* These exist because assembly callers do not provide prototypes: */ +/* entry.S calls bsp_main, _tx_initialize_low_level calls board_init, */ +/* and __tx_irq_processing_return calls board_irq_handler. Without a */ +/* header the definitions have external linkage and no visible */ +/* declaration, which MISRA C:2012 Rule 8.4 prohibits and which */ +/* -Wmissing-prototypes reports. Declaring them in one place also */ +/* means a signature change cannot silently disagree with the assembly.*/ +/* */ +/**************************************************************************/ + +#ifndef BOARD_H +#define BOARD_H + +/* Application entry, called from entry.S after EL1 setup; never returns. */ + +void bsp_main(void); + +/* Interrupt controller and periodic tick bring-up, called from + _tx_initialize_low_level while interrupts are still masked. */ + +void board_init(void); + +/* Interrupt dispatch, called from __tx_irq_processing_return in entry.S + after the interrupted context has been saved. */ + +void board_irq_handler(void); + +/* The nesting path splits the handler in three, because the acknowledge has to + happen in IRQ mode before nesting starts and the end-of-interrupt after it + finishes. board_irq_service does the middle part on an INTID that has + already been acknowledged, and does not acknowledge or EOI itself. */ + +void board_irq_service(unsigned long intid); + +/* Interrupt observability, maintained by board_irq_handler. */ + +extern volatile unsigned long board_irq_count; +extern volatile unsigned long board_timer_intid; +extern volatile unsigned long board_spurious_count; +extern volatile unsigned long board_unexpected_intid; + +/* Nesting observability, maintained by board_irq_handler. All of this is inert + unless the image was built with TX_ENABLE_IRQ_NESTING: without it the handler + runs in IRQ mode with interrupts masked and can never be re-entered. */ + +extern volatile unsigned long board_nest_depth; +extern volatile unsigned long board_nest_max; +extern volatile unsigned long board_sgi_count; +extern volatile unsigned long board_sgi_nested_count; + +/* Set by demo_nesting to make the timer handler provoke a nested SGI. Left at + zero every other image behaves exactly as before. */ + +extern volatile unsigned long board_nest_provoke; + +#define BOARD_NEST_SGI_INTID 8U + +/* FIQ observability. Two Group 0 SGIs, because FIQ nesting means an FIQ taken + while an FIQ handler is running, so a single source cannot show it. The + higher-priority one is raised from inside the lower one's handler. */ + +void board_fiq_handler(void); +void board_fiq_service(unsigned long intid); + +extern volatile unsigned long board_fiq_count; +extern volatile unsigned long board_fiq_high_count; +extern volatile unsigned long board_fiq_nested_count; +extern volatile unsigned long board_fiq_depth; +extern volatile unsigned long board_fiq_max_depth; +extern volatile unsigned long board_fiq_unexpected; +extern volatile unsigned long board_fiq_provoke; + +#define BOARD_FIQ_LOW_INTID 10U +#define BOARD_FIQ_HIGH_INTID 11U + +/* Counted by the EL2 hyp-trap handler in entry.S; the ZoneX seam. */ + +extern volatile unsigned long _hvc_call_count; + +/* Recoverable data-abort support for the MPU enforcement self-test, handled + in entry.S. Setting mpu_expect_abort makes the next data abort resume at + the instruction following the faulting one instead of halting. */ + +extern volatile unsigned long mpu_expect_abort; +extern volatile unsigned long mpu_abort_count; + +#endif /* BOARD_H */ diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/bsp_boot.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/bsp_boot.c new file mode 100644 index 00000000..4c60de8c --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/bsp_boot.c @@ -0,0 +1,170 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* bsp_boot.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* AR1 milestone M1 boot check. Verifies that the reset path really */ +/* configured EL2 and then dropped to EL1, and that the EL2 HVC seam */ +/* used by ZoneX (AR3) is reachable from EL1 and returns correctly. */ +/* */ +/* This deliberately asserts the resulting exception level rather than */ +/* merely printing a message: "it printed something" does not prove a */ +/* correct EL2-to-EL1 transition, and AR3 depends on that transition. */ +/* */ +/* MISRA C:2012 deviations (justified) */ +/* */ +/* Directive 4.3 -- the two asm statements below read CPSR and issue */ +/* HVC; neither has a standard C equivalent. Both are encapsulated */ +/* in dedicated one-line functions. */ +/* */ +/**************************************************************************/ + +#include "board.h" +#include "console.h" + +/* AArch32 CPSR mode field encodings. */ + +#define CPSR_MODE_MASK 0x1FUL +#define CPSR_MODE_SVC 0x13UL /* Supervisor -- an EL1 mode */ +#define CPSR_MODE_HYP 0x1AUL /* Hyp -- EL2 */ + +/* Incremented by the EL2 HVC handler in entry.S. */ + + + +/**************************************************************************/ +/* read_cpsr -- current program status, including the mode field. */ +/**************************************************************************/ + +static unsigned long read_cpsr(void) +{ + unsigned long value; + + __asm__ volatile("mrs %0, cpsr" : "=r"(value)); + + return value; +} + + +/**************************************************************************/ +/* issue_hvc -- call into EL2 through the ZoneX seam and return. */ +/**************************************************************************/ + +static void issue_hvc(void) +{ + __asm__ volatile("hvc #0" : : : "memory"); +} + + +/**************************************************************************/ +/* report -- print one labelled check result. */ +/**************************************************************************/ + +static void report(const char *label_ptr, unsigned int passed) +{ + console_puts(label_ptr); + console_puts(passed != 0U ? "PASS\n" : "FAIL\n"); +} + + +/**************************************************************************/ +/* bsp_main -- entered at EL1 from entry.S. Does not return. */ +/**************************************************************************/ + +void bsp_main(void) +{ + unsigned long cpsr; + unsigned long mode; + unsigned long hvc_before; + unsigned long hvc_after; + unsigned int failures = 0U; + + console_puts("\n=== ThreadX AR1/M1 boot check " + "(Cortex-R52, Armv8-R AArch32, Armv8-R AEM FVP) ===\n"); + + /* Check 1: we are executing at EL1, not still at EL2. */ + + cpsr = read_cpsr(); + mode = cpsr & CPSR_MODE_MASK; + + console_puts("[EL1] CPSR = "); + console_puthex(cpsr); + console_puts("\n[EL1] CPSR.M = "); + console_puthex(mode); + console_puts("\n"); + + if (mode == CPSR_MODE_SVC) + { + report("[EL1] running in Supervisor mode (EL1) ", 1U); + } + else + { + failures++; + if (mode == CPSR_MODE_HYP) + { + console_puts("[EL1] still in Hyp mode -- ERET to EL1 did not happen FAIL\n"); + } + else + { + report("[EL1] unexpected CPSR mode ", 0U); + } + } + + /* Check 2: the EL2 HVC seam is reachable from EL1 and returns. This is + what AR3 (ZoneX Phase-0) builds on, so it is verified from AR1. */ + + hvc_before = _hvc_call_count; + issue_hvc(); + hvc_after = _hvc_call_count; + + console_puts("[EL1] HVC count before/after = "); + console_puthex(hvc_before); + console_puts(" / "); + console_puthex(hvc_after); + console_puts("\n"); + + if (hvc_after == (hvc_before + 1UL)) + { + report("[EL1] EL2 HVC seam reachable and returns to EL1 ", 1U); + } + else + { + failures++; + report("[EL1] EL2 HVC seam did not take effect ", 0U); + } + + /* Summary. The exact strings are what the automated runner greps for. */ + + if (failures == 0U) + { + console_puts("\nM1 RESULT: ALL CHECKS PASSED\n"); + } + else + { + console_puts("\nM1 RESULT: FAILED\n"); + } + + console_exit(failures); +} diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/cache.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/cache.c new file mode 100644 index 00000000..6632096a --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/cache.c @@ -0,0 +1,149 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* cache.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Cache maintenance for a loader that copies code. See cache.h for */ +/* why only these three functions exist. */ +/* */ +/* MISRA C:2012 deviations (justified) */ +/* */ +/* Directive 4.3 -- cache maintenance and CTR are reachable only */ +/* through CP15; every access is encapsulated in a one-line accessor */ +/* below and nowhere else in this file. */ +/* Rule 11.6 (conversion between a pointer and an integer) -- a cache */ +/* maintenance operation takes a virtual address as a register value, */ +/* so the conversion is what the instruction requires. */ +/* */ +/**************************************************************************/ + +#include "cache.h" + +/* The smallest data-cache line in the machine, from CTR.DminLine. Held as a + log2 of the number of 32-bit words, so the byte count is 4 << DminLine. + DminLine and not the L1 geometry: a maintenance-by-address walk has to step + by the smallest line any level implements, or it skips lines in that level. */ + +#define CTR_DMINLINE_SHIFT 16U +#define CTR_DMINLINE_MASK 0xFUL + + +/**************************************************************************/ +/* CP15 accessors. The only assembly in this file (MISRA C:2012 Dir 4.3).*/ +/**************************************************************************/ + +static unsigned long read_ctr(void) +{ + unsigned long value; + __asm__ volatile("mrc p15, 0, %0, c0, c0, 1" : "=r"(value)); + return value; +} + +static void clean_dcache_line(unsigned long address) +{ + __asm__ volatile("mcr p15, 0, %0, c7, c10, 1" :: "r"(address) : "memory"); +} + +static void invalidate_icache_all_op(void) +{ + unsigned long zero = 0UL; + __asm__ volatile("mcr p15, 0, %0, c7, c5, 0" :: "r"(zero) : "memory"); +} + +static void data_sync_barrier(void) +{ + __asm__ volatile("dsb sy" ::: "memory"); +} + +static void instruction_barrier(void) +{ + __asm__ volatile("isb" ::: "memory"); +} + + +/**************************************************************************/ +/* cache_dcache_line_bytes */ +/**************************************************************************/ + +unsigned long cache_dcache_line_bytes(void) +{ + return 4UL << ((read_ctr() >> CTR_DMINLINE_SHIFT) & CTR_DMINLINE_MASK); +} + + +/**************************************************************************/ +/* cache_clean_range */ +/* */ +/* Clean by virtual address over [start, start + length). */ +/* */ +/* The start is rounded DOWN to a line boundary and the walk continues */ +/* past the end until the last line containing a requested byte has been */ +/* cleaned. Rounding the start up instead would leave the first partial */ +/* line dirty, which is the whole failure this exists to prevent and is */ +/* invisible whenever the caller happens to be line aligned. */ +/**************************************************************************/ + +void cache_clean_range(const void *start_address, unsigned long length) +{ + unsigned long line = cache_dcache_line_bytes(); + unsigned long address; + unsigned long end; + + if (length == 0UL) + { + return; + } + + address = (unsigned long) start_address; + end = address + length; + + address &= ~(line - 1UL); + + while (address < end) + { + clean_dcache_line(address); + address += line; + } + + /* The stores have to be complete before anything fetches from the range. */ + + data_sync_barrier(); +} + + +/**************************************************************************/ +/* cache_invalidate_icache_all */ +/**************************************************************************/ + +void cache_invalidate_icache_all(void) +{ + invalidate_icache_all_op(); + + /* DSB then ISB: the invalidate must complete, and the pipeline must be + flushed of anything fetched before it did. */ + + data_sync_barrier(); + instruction_barrier(); +} diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/cache.h b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/cache.h new file mode 100644 index 00000000..0ed8dcfc --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/cache.h @@ -0,0 +1,72 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* cache.h Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* The cache maintenance a loader owes the instruction side, and */ +/* nothing else. */ +/* */ +/* The S32Z280 board support carries a full cache driver -- enable, */ +/* disable, geometry, set/way sweeps -- because silicon bring-up needed */ +/* to ask the hardware what it had. None of that is needed here: the */ +/* caches are turned on once in mpu_init and never turned off, and the */ +/* model's geometry is not in question. What IS needed is the pair of */ +/* operations that make copied code executable, so that is all this is. */ +/* */ +/* Why it is needed at all: the module area is Normal write-back memory, */ +/* so a byte copy of module code leaves the bytes in dirty data-cache */ +/* lines while the instruction side -- which is not coherent with the */ +/* data cache on this core -- fetches whatever main memory still holds. */ +/* Cleaning by address range and then invalidating the instruction cache */ +/* is what closes that gap. */ +/* */ +/* By range rather than by set/way, deliberately. A set/way sweep needs */ +/* the cache geometry and touches every line in the machine; the loader */ +/* knows exactly which bytes it wrote, so the range form is both tighter */ +/* and shorter to get right. */ +/* */ +/**************************************************************************/ + +#ifndef CACHE_H +#define CACHE_H + +/* Clean (write back) the data cache over one address range, so that main + memory holds what the copy wrote. Rounds outwards to cache-line + boundaries; a length of zero does nothing. */ + +void cache_clean_range(const void *start_address, unsigned long length); + +/* Invalidate the whole instruction cache, so no stale line from a previous + image at the same address can be served. Invalidate-all rather than by + range because it is one register write and this runs once per load. */ + +void cache_invalidate_icache_all(void); + +/* Data cache line length in bytes, from CTR.DminLine. Published because the + range walk above depends on it and a caller may want to report it. */ + +unsigned long cache_dcache_line_bytes(void); + +#endif /* CACHE_H */ diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/console.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/console.c new file mode 100644 index 00000000..2f75f605 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/console.c @@ -0,0 +1,170 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* console.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Semihosting console for the Armv8-R AEM FVP. Armv8 AArch32 uses */ +/* HLT 0xF000 as the semihosting trap; the model implements it with no */ +/* peripheral configuration, which keeps early bring-up independent of */ +/* the platform memory map. */ +/* */ +/* MISRA C:2012 deviations (justified) */ +/* */ +/* Directive 4.3 (assembly language shall be encapsulated and isolated)*/ +/* -- observed rather than violated: the single asm statement lives */ +/* in semihost_call() and nowhere else in the port. */ +/* Rule 1.1 / 1.2 (language extensions) */ +/* -- register-asm bindings and inline assembly are unavoidable to */ +/* invoke a semihosting trap; no standard C construct expresses it. */ +/* */ +/**************************************************************************/ + +#include "console.h" + +#ifdef TX_R52_CONSOLE_PL011 +#include "uart_pl011.h" +#endif + +/* Semihosting operation numbers (Arm semihosting specification). */ + +#define SYS_WRITE0 0x04U +#define SYS_EXIT 0x18U + +/* SYS_EXIT reason codes. On AArch32 the parameter is a reason, not an exit + status, so a failing run is reported by its printed result line. */ + +#define ADP_STOPPED_APPLICATION_EXIT 0x20026U +#define ADP_STOPPED_RUN_TIME_ERROR 0x20023U + +/* Number of hexadecimal digits in a 32-bit value. */ + +#define HEX_DIGITS 8U + + +/**************************************************************************/ +/* semihost_call */ +/* */ +/* Issues a single semihosting operation. This is the only assembly in */ +/* the console driver (MISRA C:2012 Dir 4.3). */ +/**************************************************************************/ + +static int semihost_call(int operation, const void *argument_ptr) +{ + register int result __asm__("r0") = operation; + register const void *argument __asm__("r1") = argument_ptr; + + __asm__ volatile("hlt 0xf000" + : "+r"(result) + : "r"(argument) + : "memory"); + + return result; +} + + +/**************************************************************************/ +/* console_puts */ +/**************************************************************************/ + +void console_puts(const char *string_ptr) +{ + if (string_ptr == 0) + { + return; + } + +#ifdef TX_R52_CONSOLE_PL011 + + /* Initialise on first use rather than from board_init, so that images + with no ThreadX initialisation path (the M1 boot check) also get a + working UART console. */ + + static unsigned int initialised = 0U; + + if (initialised == 0U) + { + pl011_init(); + initialised = 1U; + } + + pl011_puts(string_ptr); + +#else + + (void) semihost_call((int) SYS_WRITE0, string_ptr); + +#endif +} + + +/**************************************************************************/ +/* console_puthex */ +/**************************************************************************/ + +void console_puthex(unsigned long value) +{ + static const char digits[] = "0123456789abcdef"; + char buffer[3U + HEX_DIGITS]; /* "0x" + digits + NUL */ + unsigned int index; + + buffer[0] = '0'; + buffer[1] = 'x'; + + for (index = 0U; index < HEX_DIGITS; index++) + { + unsigned int shift = (HEX_DIGITS - 1U - index) * 4U; + + buffer[2U + index] = digits[(value >> shift) & 0xFUL]; + } + + buffer[2U + HEX_DIGITS] = '\0'; + + console_puts(buffer); +} + + +/**************************************************************************/ +/* console_exit */ +/**************************************************************************/ + +/* Note this always uses semihosting, even with the PL011 console selected: + SYS_EXIT is how the model is told to stop, which is a debug-channel + operation rather than console output. */ + +void console_exit(unsigned int status) +{ + unsigned int reason = (status == 0U) ? ADP_STOPPED_APPLICATION_EXIT + : ADP_STOPPED_RUN_TIME_ERROR; + + (void) semihost_call((int) SYS_EXIT, (const void *) reason); + + /* The model terminates on the call above; loop defensively in case a + host or debug configuration ignores semihosting exit. */ + + for (;;) + { + /* Intentionally empty. */ + } +} diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/console.h b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/console.h new file mode 100644 index 00000000..a9bc5ba0 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/console.h @@ -0,0 +1,51 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* console.h Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Minimal console for the Armv8-R AEM FVP. Backed by semihosting so */ +/* that early bring-up needs no peripheral base addresses; the PL011 */ +/* UART implementation arrives in AR1/M3 behind this same interface. */ +/* */ +/**************************************************************************/ + +#ifndef CONSOLE_H +#define CONSOLE_H + +/* Write a NUL-terminated string to the console. */ + +void console_puts(const char *string_ptr); + +/* Write value as 0x-prefixed, zero-padded 32-bit hexadecimal. */ + +void console_puthex(unsigned long value); + +/* Terminate the simulation (semihosting SYS_EXIT). Does not return. + Pass/fail is reported through the printed result line rather than a host + exit status, because the AArch32 SYS_EXIT reason code is not a status. */ + +void console_exit(unsigned int status); + +#endif /* CONSOLE_H */ diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_clz.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_clz.c new file mode 100644 index 00000000..a156f4ec --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_clz.c @@ -0,0 +1,359 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* DEMONSTRATION RELEASE */ +/* */ +/* demo_clz.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Regression test for this port's CLZ implementation of */ +/* TX_LOWEST_SET_BIT_CALCULATE, which replaces the portable loop in */ +/* tx_thread.h and which the scheduler's priority search runs on */ +/* every suspend and resume. */ +/* */ +/* Why this image exists. The macro was DEAD until 20 Aug 2026: */ +/* tx_port.h guarded it with __TARGET_ARCH_ARM > 4, an Arm Compiler 5 */ +/* predefine that GCC never defines, so the test read 0 > 4 and the */ +/* portable loop ran on a core that has had CLZ since Armv5. Turning */ +/* it on means hand-written inline assembly now decides which thread */ +/* runs next, so it gets a test that pins the answer for every input */ +/* class rather than a build that merely compiles. */ +/* */ +/* What is checked, against an obvious linear-scan reference rather */ +/* than against the implementation being tested: */ +/* */ +/* 1. Each of the 32 single-bit maps returns its own bit position. */ +/* 2. Each bit position with a deterministic spray of HIGHER bits */ +/* set still returns that position -- the macro must find the */ +/* LOWEST set bit, and a single-bit test cannot tell the */ +/* difference between "lowest" and "only". */ +/* 3. A table of hand-picked patterns, including the two the */ +/* respelled isolation step could plausibly break: 0x80000000, */ +/* the one input for which upstream's signed negation was */ +/* undefined, and 0xFFFFFFFF. */ +/* 4. Every group is run twice, once through a UINT result and once */ +/* through a ULONG one, because the kernel calls the macro with */ +/* both and the closing 31 - b is where a width mistake would */ +/* hide. */ +/* 5. The m == 0 divergence is PINNED, not fixed. CLZ(0) is 32, so */ +/* this implementation yields 31 - 32 while the portable loop */ +/* yields 0. Every one of the twelve call sites in common/src */ +/* reaches the macro only on a map already tested against zero, */ +/* so the difference is unreachable -- and a test that records */ +/* it is how it stays a decision instead of becoming a surprise. */ +/* */ +/* The checks run inside a ThreadX thread on purpose. By the time the */ +/* thread's entry is reached the scheduler has already executed the */ +/* macro on the way to dispatching it, so a macro broken badly enough */ +/* to misdirect the priority search shows up as a hang -- which the */ +/* runner scores as a failure -- before any check is evaluated. */ +/* */ +/**************************************************************************/ + +#include "tx_api.h" +#include "console.h" +#include "board.h" + +/* The port's definition must be the one in scope. tx_thread.h supplies a + portable fallback under #ifndef, so an image that reached this file with the + macro undefined would be testing that fallback and reporting a pass for it -- + the exact failure mode this whole image exists to rule out. */ + +#ifndef TX_LOWEST_SET_BIT_CALCULATE +#error "demo_clz: tx_port.h did not define TX_LOWEST_SET_BIT_CALCULATE, so this image would test the portable loop and call it a pass." +#endif + +/* And it must be defined for the reason we think it is. These are the two + conditions tx_port.h uses; asserting them here means a future edit that + silently drops the CLZ path cannot leave this image passing. */ + +#if !defined(__ARM_FEATURE_CLZ) +#error "demo_clz: __ARM_FEATURE_CLZ is not defined, so the CLZ path in tx_port.h is not the definition under test." +#endif + +#if defined(__thumb__) +#error "demo_clz: built for Thumb, where tx_port.h deliberately keeps the portable loop. Build this image -marm." +#endif + + +#define DEMO_STACK_SIZE 2048 + +static TX_THREAD check_thread; +static UCHAR check_stack[DEMO_STACK_SIZE]; + +/* Failure detail, kept as counters rather than printed per case: 32 passing + lines tell a reader nothing and would bury the one failing line. */ + +static UINT failures; + + +/**************************************************************************/ +/* reference_lowest_bit */ +/* */ +/* The ground truth, written to be read rather than to be fast. It is */ +/* deliberately NOT the portable macro from tx_thread.h: comparing an */ +/* implementation against a cleverer implementation of the same idea */ +/* tests that they share their mistakes. A linear scan has nowhere to */ +/* hide one. */ +/**************************************************************************/ + +static ULONG reference_lowest_bit(ULONG map) +{ + ULONG index; + ULONG result = 0xFFFFFFFFUL; /* No bit set. */ + + for (index = 0UL; index < 32UL; index++) + { + if ((map & (1UL << index)) != 0UL) + { + result = index; + break; + } + } + + return result; +} + + +/**************************************************************************/ +/* next_pattern */ +/* */ +/* A fixed linear congruential sequence. A regression test must produce */ +/* the same inputs on every run, so the spray of high bits in group 2 is */ +/* pseudo-random and seeded, never actually random. */ +/**************************************************************************/ + +static ULONG next_pattern(ULONG state) +{ + return (state * 1103515245UL) + 12345UL; +} + + +/**************************************************************************/ +/* check_one */ +/* */ +/* Runs one map through the macro twice, once landing in a UINT and once */ +/* in a ULONG, and compares both against the reference. The macro */ +/* CONSUMES its first argument -- both implementations rewrite the map */ +/* in place -- so each call gets its own copy, which is also the trap a */ +/* caller reusing the variable afterwards would fall into. */ +/**************************************************************************/ + +static void check_one(ULONG map, ULONG expected, const char *group_ptr) +{ + ULONG working; + UINT bit_as_uint; + ULONG bit_as_ulong; + + working = map; + TX_LOWEST_SET_BIT_CALCULATE(working, bit_as_uint) + + working = map; + TX_LOWEST_SET_BIT_CALCULATE(working, bit_as_ulong) + + if (((ULONG) bit_as_uint != expected) || (bit_as_ulong != expected)) + { + failures++; + + console_puts("[FAIL] "); + console_puts(group_ptr); + console_puts(" map="); + console_puthex(map); + console_puts(" expected="); + console_puthex(expected); + console_puts(" got UINT="); + console_puthex((unsigned long) bit_as_uint); + console_puts(" ULONG="); + console_puthex(bit_as_ulong); + console_puts("\n"); + } +} + + +/**************************************************************************/ +/* report */ +/**************************************************************************/ + +static void report(const char *label_ptr, UINT failures_before) +{ + console_puts(label_ptr); + console_puts((failures == failures_before) ? "PASS\n" : "FAIL\n"); +} + + +/**************************************************************************/ +/* thread_check_entry */ +/**************************************************************************/ + +static void thread_check_entry(ULONG thread_input) +{ + ULONG index; + ULONG spray; + ULONG state; + ULONG map; + ULONG working; + ULONG bit; + UINT before; + + /* Hand-picked patterns. 0x80000000 is the one input for which upstream's + (ULONG)(-((LONG) m)) isolation was undefined behaviour, and 0xFFFFFFFF is + the densest map the scheduler can present. */ + + static const ULONG patterns[] = + { + 0xFFFFFFFFUL, 0x80000000UL, 0xAAAAAAAAUL, 0x55555555UL, + 0xF0F0F000UL, 0x00000003UL, 0x7FFFFFFFUL, 0xC0000000UL, + 0x00010000UL, 0xFFFF0000UL + }; + + (void) thread_input; + + console_puts("\n=== ThreadX Cortex-R52 CLZ lowest-set-bit regression ===\n\n"); + + /* Group 1 -- every single-bit map. */ + + before = failures; + for (index = 0UL; index < 32UL; index++) + { + check_one(1UL << index, index, "single bit"); + } + report("[check] 32 single-bit maps ................ ", before); + + /* Group 2 -- every bit position with higher bits sprayed above it. This is + the group that distinguishes "lowest set bit" from "only set bit". */ + + before = failures; + state = 0x12345678UL; + for (index = 0UL; index < 32UL; index++) + { + state = next_pattern(state); + + /* Keep only bits strictly above index, then put index back. Shifting a + 32-bit value by 32 is undefined, so the top position is special-cased + to no spray at all -- there is nothing above bit 31 to set. */ + + if (index < 31UL) + { + spray = (state << (index + 1UL)) & 0xFFFFFFFFUL; + } + else + { + spray = 0UL; + } + + check_one(spray | (1UL << index), index, "lowest of many"); + } + report("[check] 32 sprayed maps, lowest bit wins .. ", before); + + /* Group 3 -- the hand-picked table. */ + + before = failures; + for (index = 0UL; index < (ULONG) (sizeof(patterns) / sizeof(patterns[0])); index++) + { + check_one(patterns[index], reference_lowest_bit(patterns[index]), "pattern"); + } + report("[check] 10 hand-picked patterns .......... ", before); + + /* Group 4 -- the pinned divergence at zero. Not a fix: a record. CLZ(0) + is 32 and the closing subtraction is unsigned, so the answer is 31 - 32 + taken modulo 2^32. The portable loop in tx_thread.h answers 0 for the + same input. Neither is reachable from the kernel, and this check exists + so that changing which one is true has to be deliberate. */ + + before = failures; + working = 0UL; + TX_LOWEST_SET_BIT_CALCULATE(working, bit) + + if (bit != 0xFFFFFFFFUL) + { + failures++; + console_puts("[FAIL] map=0 answered "); + console_puthex(bit); + console_puts(", documented answer for the CLZ implementation is 0xFFFFFFFF\n"); + } + report("[check] map = 0 answers as documented .... ", before); + + /* Group 5 -- the macro consumes its first argument, in both + implementations. Stated as a check because it is a property callers rely + on by never relying on it, and a "fix" that stopped rewriting the map + would quietly change what every call site's variable holds afterwards. */ + + before = failures; + map = 0x00000280UL; /* Lowest set bit is 7. */ + working = map; + TX_LOWEST_SET_BIT_CALCULATE(working, bit) + + if ((bit != 7UL) || (working != (1UL << 7))) + { + failures++; + console_puts("[FAIL] expected bit 7 and map reduced to 0x80, got bit "); + console_puthex(bit); + console_puts(" map "); + console_puthex(working); + console_puts("\n"); + } + report("[check] map is reduced to its lowest bit . ", before); + + console_puts("\n[info] 75 maps checked, each through a UINT and a ULONG result.\n"); + + if (failures == 0U) + { + console_puts("\nCLZ RESULT: ALL CHECKS PASSED\n"); + } + else + { + console_puts("\nCLZ RESULT: FAILED\n"); + } + + console_exit(failures); +} + + +/**************************************************************************/ +/* tx_application_define */ +/**************************************************************************/ + +void tx_application_define(void *first_unused_memory) +{ + (void) first_unused_memory; + + tx_thread_create(&check_thread, "clz check", thread_check_entry, 0UL, + check_stack, DEMO_STACK_SIZE, + 1U, 1U, TX_NO_TIME_SLICE, TX_AUTO_START); +} + + +/**************************************************************************/ +/* bsp_main -- entered at EL1 from entry.S. Does not return. */ +/**************************************************************************/ + +void bsp_main(void) +{ + tx_kernel_enter(); + + /* Not reached. */ + + for (;;) + { + /* Nothing. */ + } +} diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_fiq.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_fiq.c new file mode 100644 index 00000000..b2899035 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_fiq.c @@ -0,0 +1,215 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* demo_fiq.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frederic Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Nested FIQ handling: _tx_thread_fiq_nesting_start and */ +/* _tx_thread_fiq_nesting_end, the last pair in this port that nothing */ +/* had ever called. */ +/* */ +/* FIQ needs more of the GIC than IRQ does. With a single security */ +/* state the controller delivers Group 0 as FIQ and Group 1 as IRQ, so */ +/* an interrupt only arrives as an FIQ if it has been moved into Group */ +/* 0, the distributor and CPU interface both have Group 0 enabled, and */ +/* it is acknowledged through the Group 0 registers. Group 1's */ +/* acknowledge returns the spurious INTID for a Group 0 interrupt and */ +/* leaves it pending, which presents as a storm rather than an error. */ +/* */ +/* FIQ nesting means an FIQ taken while an FIQ handler is running, so */ +/* one source cannot demonstrate it. Two Group 0 SGIs are used, the */ +/* second at a numerically lower priority so it can preempt the first, */ +/* and the first's handler raises it. */ +/* */ +/* WHAT EACH CHECK IS FOR */ +/* */ +/* F1 an FIQ arrives at all. Separate on purpose: it covers the whole */ +/* Group 0 chain -- IGRPEN0, the ICC_SGI0R encoding, IAR0 and */ +/* EOIR0, the EL1 FIQ vector, and F being unmasked. If F1 fails */ +/* the fault is in one of those and not in the nesting routines. */ +/* F2 an FIQ arrived while another FIQ handler was active, depth 2. */ +/* F3 the depth returned to zero, so the pairing unwound. */ +/* F4 the IRQ path is undisturbed: the tick still advances. */ +/* F5 threads still run, so FIQ context save and restore survived */ +/* being re-entered. */ +/* F6 no unexpected Group 0 INTID arrived. */ +/* */ +/**************************************************************************/ + +#include "tx_api.h" +#include "board.h" +#include "console.h" +#include "gicv3.h" + +#define DEMO_STACK_SIZE 2048 + +static TX_THREAD thread_check; +static TX_THREAD thread_spin; + +static ULONG thread_check_stack[DEMO_STACK_SIZE / sizeof(ULONG)]; +static ULONG thread_spin_stack[DEMO_STACK_SIZE / sizeof(ULONG)]; + +static volatile ULONG spin_runs; + + +static UINT report(const char *label_ptr, UINT passed) +{ + console_puts(label_ptr); + console_puts((passed != 0U) ? "PASS\n" : "FAIL\n"); + return (passed != 0U) ? 0U : 1U; +} + + +static void report_hex(const char *label_ptr, unsigned long value) +{ + console_puts(label_ptr); + console_puthex(value); + console_puts("\n"); +} + + +static void thread_spin_entry(ULONG thread_input) +{ + (void) thread_input; + + for (;;) + { + spin_runs++; + } +} + + +static void thread_check_entry(ULONG thread_input) +{ + UINT failures = 0U; + ULONG fiq_before; + ULONG ticks_start; + ULONG ticks_end; + ULONG spin_before; + + (void) thread_input; + + /* F1: the whole Group 0 chain, before nesting is involved. */ + + console_puts("[check] raising a Group 0 SGI from thread context\n"); + + fiq_before = board_fiq_count; + gicv3_send_sgi_group0(BOARD_FIQ_LOW_INTID); + tx_thread_sleep(2); + + report_hex("[check] FIQ count = ", board_fiq_count); + + failures += report("[check] F1 FIQ delivered and dispatched ", + (board_fiq_count > fiq_before) ? 1U : 0U); + + if (board_fiq_count == fiq_before) + { + /* Everything below needs an FIQ to arrive, so repeating the failure + five more times would only bury the cause. */ + + console_puts("[check] no FIQ arrived; the nesting checks would only\n" + " restate that, so they are skipped.\n"); + console_puts("\nFIQ RESULT: FAILED\n"); + console_exit(1U); + } + + /* F2 onwards: let the low-priority FIQ handler raise the high one. */ + + console_puts("[check] enabling nested provocation in the FIQ handler\n"); + + board_fiq_provoke = 1UL; + ticks_start = tx_time_get(); + spin_before = spin_runs; + + { + ULONG i; + + for (i = 0UL; i < 20UL; i++) + { + gicv3_send_sgi_group0(BOARD_FIQ_LOW_INTID); + tx_thread_sleep(2); + } + } + + ticks_end = tx_time_get(); + board_fiq_provoke = 0UL; + + report_hex("[check] low-priority FIQ count = ", board_fiq_count); + report_hex("[check] high-priority FIQ count = ", board_fiq_high_count); + report_hex("[check] nested FIQ count = ", board_fiq_nested_count); + report_hex("[check] max FIQ depth = ", board_fiq_max_depth); + report_hex("[check] FIQ depth now = ", board_fiq_depth); + + failures += report("[check] F2 FIQ nested inside an FIQ handler ", + ((board_fiq_nested_count > 0UL) && + (board_fiq_max_depth >= 2UL)) ? 1U : 0U); + + failures += report("[check] F3 FIQ nesting unwound to depth zero ", + (board_fiq_depth == 0UL) ? 1U : 0U); + + failures += report("[check] F4 IRQ tick undisturbed by FIQ work ", + ((ticks_end - ticks_start) >= 30UL) ? 1U : 0U); + + failures += report("[check] F5 lower-priority thread still scheduled ", + (spin_runs > spin_before) ? 1U : 0U); + + report_hex("[check] unexpected Group 0 INTID = ", board_fiq_unexpected); + + failures += report("[check] F6 no unexpected Group 0 INTID ", + (board_fiq_unexpected == 0UL) ? 1U : 0U); + + if (failures == 0U) + { + console_puts("\nFIQ RESULT: ALL CHECKS PASSED\n"); + } + else + { + console_puts("\nFIQ RESULT: FAILED\n"); + } + + console_exit(failures); +} + + +void tx_application_define(void *first_unused_memory) +{ + (void) first_unused_memory; + + (void) tx_thread_create(&thread_check, "fiq check", thread_check_entry, + 0UL, thread_check_stack, sizeof(thread_check_stack), + 10U, 10U, TX_NO_TIME_SLICE, TX_AUTO_START); + + (void) tx_thread_create(&thread_spin, "spinner", thread_spin_entry, + 0UL, thread_spin_stack, sizeof(thread_spin_stack), + 20U, 20U, TX_NO_TIME_SLICE, TX_AUTO_START); +} + + +void bsp_main(void) +{ + console_puts("\n=== ThreadX nested FIQ handling " + "(Cortex-R52, Armv8-R AArch32, Armv8-R AEM FVP) ===\n"); + + tx_kernel_enter(); +} diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_m2.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_m2.c new file mode 100644 index 00000000..e6b7da2c --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_m2.c @@ -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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* DEMONSTRATION RELEASE */ +/* */ +/* demo_m2.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* AR1 milestone M2: two threads switching cooperatively, with no */ +/* interrupts and no timer tick. This is the first real exercise of */ +/* the ported assembly -- _tx_thread_stack_build builds each thread's */ +/* initial frame, _tx_thread_schedule restores it, and */ +/* _tx_thread_system_return saves a solicited context on every */ +/* tx_thread_relinquish call. */ +/* */ +/* The threads record their execution order in a trace buffer, and the */ +/* expected alternating sequence is asserted at the end. Counting */ +/* iterations alone would not prove a context switch happened: if */ +/* switching were broken, one thread could run to completion by itself.*/ +/* */ +/* Only tx_thread_relinquish is used. tx_thread_sleep would hang */ +/* without a tick, which AR1/M3 adds. */ +/* */ +/**************************************************************************/ + +#include "tx_api.h" +#include "board.h" +#include "console.h" + +#define DEMO_STACK_SIZE 2048 +#define DEMO_ITERATIONS 5U +#define TRACE_LENGTH (2U * DEMO_ITERATIONS) + +static TX_THREAD thread_0; +static TX_THREAD thread_1; + +static ULONG thread_0_stack[DEMO_STACK_SIZE / sizeof(ULONG)]; +static ULONG thread_1_stack[DEMO_STACK_SIZE / sizeof(ULONG)]; + +static volatile ULONG thread_0_counter; +static volatile ULONG thread_1_counter; + +/* Execution order, one character per slice. */ + +static char trace[TRACE_LENGTH + 1U]; +static volatile UINT trace_index; + + +/**************************************************************************/ +/* trace_record -- append one thread's mark to the execution trace. */ +/**************************************************************************/ + +static void trace_record(char mark) +{ + if (trace_index < TRACE_LENGTH) + { + trace[trace_index] = mark; + trace_index++; + } +} + + +/**************************************************************************/ +/* report -- print one labelled check result. */ +/**************************************************************************/ + +static void report(const char *label_ptr, UINT passed) +{ + console_puts(label_ptr); + console_puts((passed != 0U) ? "PASS\n" : "FAIL\n"); +} + + +/**************************************************************************/ +/* thread_1_entry -- yields back to thread 0 on every slice. */ +/**************************************************************************/ + +static void thread_1_entry(ULONG thread_input) +{ + (void) thread_input; + + while (thread_1_counter < DEMO_ITERATIONS) + { + thread_1_counter++; + trace_record('1'); + + console_puts("[thread 1] slice "); + console_puthex(thread_1_counter); + console_puts("\n"); + + tx_thread_relinquish(); + } +} + + +/**************************************************************************/ +/* thread_0_entry -- yields to thread 1, then checks the outcome. */ +/**************************************************************************/ + +static void thread_0_entry(ULONG thread_input) +{ + UINT failures = 0U; + UINT index; + + (void) thread_input; + + while (thread_0_counter < DEMO_ITERATIONS) + { + thread_0_counter++; + trace_record('0'); + + console_puts("[thread 0] slice "); + console_puthex(thread_0_counter); + console_puts("\n"); + + tx_thread_relinquish(); + } + + /* Check 1: both threads ran the expected number of slices. A broken + context switch would leave thread 1 at zero. */ + + console_puts("\n[check] slices thread 0 / thread 1 = "); + console_puthex(thread_0_counter); + console_puts(" / "); + console_puthex(thread_1_counter); + console_puts("\n"); + + if ((thread_0_counter == (ULONG) DEMO_ITERATIONS) && + (thread_1_counter == (ULONG) DEMO_ITERATIONS)) + { + report("[check] both threads ran every slice ", 1U); + } + else + { + failures++; + report("[check] both threads ran every slice ", 0U); + } + + /* Check 2: execution strictly alternated, proving each relinquish + really switched context rather than returning to the same thread. */ + + trace[TRACE_LENGTH] = '\0'; + console_puts("[check] execution order = "); + console_puts(trace); + console_puts(" (expected 0101010101)\n"); + + for (index = 0U; index < TRACE_LENGTH; index++) + { + char expected = ((index % 2U) == 0U) ? '0' : '1'; + + if (trace[index] != expected) + { + failures++; + break; + } + } + + report("[check] threads alternated on every relinquish ", + (UINT) ((failures == 0U) ? 1U : 0U)); + + if (failures == 0U) + { + console_puts("\nM2 RESULT: ALL CHECKS PASSED\n"); + } + else + { + console_puts("\nM2 RESULT: FAILED\n"); + } + + console_exit(failures); +} + + +/**************************************************************************/ +/* tx_application_define -- create the demo threads. */ +/* */ +/* Static stacks are used rather than a byte pool so that M2 exercises */ +/* only the context-switch path, with no allocator in the way. */ +/**************************************************************************/ + +void tx_application_define(void *first_unused_memory) +{ + (void) first_unused_memory; + + (void) tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0UL, + thread_0_stack, sizeof(thread_0_stack), + 16U, 16U, TX_NO_TIME_SLICE, TX_AUTO_START); + + (void) tx_thread_create(&thread_1, "thread 1", thread_1_entry, 0UL, + thread_1_stack, sizeof(thread_1_stack), + 16U, 16U, TX_NO_TIME_SLICE, TX_AUTO_START); +} + + +/**************************************************************************/ +/* bsp_main -- entered at EL1 from entry.S. Does not return. */ +/**************************************************************************/ + +void bsp_main(void) +{ + console_puts("\n=== ThreadX AR1/M2 cooperative switch " + "(Cortex-R52, Armv8-R AArch32, Armv8-R AEM FVP) ===\n"); + + tx_kernel_enter(); +} diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_m3.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_m3.c new file mode 100644 index 00000000..96beac48 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_m3.c @@ -0,0 +1,249 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* DEMONSTRATION RELEASE */ +/* */ +/* demo_m3.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* AR1 milestone M3: periodic tick and preemptive scheduling. This */ +/* exercises the parts of the port that M2 could not reach -- */ +/* _tx_thread_context_save, _tx_timer_interrupt and */ +/* _tx_thread_context_restore -- by driving them from a real generic */ +/* timer interrupt routed through GICv3. */ +/* */ +/* The checks build on each other, so a failure localises the cause: */ +/* 1. CNTFRQ is non-zero -> entry.S programmed it at EL2 */ +/* 2. counter is enabled -> the control frame was started */ +/* 3. CNTPCT advances -> the counter really runs */ +/* 4. interrupts arrive -> GICv3 + PPI + vector wiring work */ +/* 5. tx_time_get advances -> _tx_timer_interrupt drives the tick*/ +/* 6. tx_thread_sleep returns -> timer-driven thread resumption */ +/* 7. a lower-priority thread ran while we slept -> preemption and */ +/* context save/restore across an interrupt */ +/* */ +/* The timer PPI INTID is reported, not assumed: the whole PPI range is*/ +/* enabled and whichever INTID the model drives is recorded. */ +/* */ +/**************************************************************************/ + +#include "tx_api.h" +#include "board.h" +#include "console.h" +#include "timer.h" + +#define DEMO_STACK_SIZE 2048 +#define SLEEP_TICKS 20UL +#define SLEEP_SLACK_TICKS 10UL + +static TX_THREAD thread_main; +static TX_THREAD thread_busy; + +static ULONG thread_main_stack[DEMO_STACK_SIZE / sizeof(ULONG)]; +static ULONG thread_busy_stack[DEMO_STACK_SIZE / sizeof(ULONG)]; + +static volatile ULONG busy_counter; + +/* Recorded by the interrupt dispatcher. */ + + + +/**************************************************************************/ +/* report -- print one labelled check result. */ +/**************************************************************************/ + +static UINT report(const char *label_ptr, UINT passed) +{ + console_puts(label_ptr); + console_puts((passed != 0U) ? "PASS\n" : "FAIL\n"); + + return (passed != 0U) ? 0U : 1U; +} + + +/**************************************************************************/ +/* thread_busy_entry -- lowest priority; only runs when nothing else can.*/ +/**************************************************************************/ + +static void thread_busy_entry(ULONG thread_input) +{ + (void) thread_input; + + for (;;) + { + busy_counter++; + } +} + + +/**************************************************************************/ +/* thread_main_entry -- runs the checks and terminates the simulation. */ +/**************************************************************************/ + +static void thread_main_entry(ULONG thread_input) +{ + UINT failures = 0U; + unsigned long frequency; + unsigned long long counter_first; + unsigned long long counter_second; + ULONG time_before; + ULONG time_after; + ULONG elapsed; + ULONG busy_before; + ULONG busy_after; + unsigned long irq_before; + + (void) thread_input; + + /* Check 1: CNTFRQ was programmed at EL2 (the model resets it to zero). */ + + frequency = timer_frequency(); + console_puts("[check] CNTFRQ = "); + console_puthex(frequency); + console_puts(" Hz\n"); + failures += report("[check] CNTFRQ programmed at EL2 (non-zero) ", + (frequency != 0UL) ? 1U : 0U); + + /* Check 2: the system counter was started (stopped at reset). */ + + failures += report("[check] system counter enabled by BSP ", + timer_counter_enabled()); + + /* Check 3: the counter actually advances. */ + + counter_first = timer_counter(); + { + volatile unsigned int spin; + for (spin = 0U; spin < 20000U; spin++) + { + /* Burn a little time. */ + } + } + counter_second = timer_counter(); + failures += report("[check] CNTPCT advancing ", + (counter_second > counter_first) ? 1U : 0U); + + /* Check 4: timer interrupts are being delivered through GICv3. */ + + irq_before = board_irq_count; + time_before = tx_time_get(); + busy_before = busy_counter; + + /* Check 6: a timer-driven sleep returns. While suspended, the only + runnable thread is the lower-priority busy thread. */ + + tx_thread_sleep(SLEEP_TICKS); + + time_after = tx_time_get(); + busy_after = busy_counter; + + console_puts("[check] timer PPI INTID observed = "); + console_puthex(board_timer_intid); + console_puts("\n[check] timer interrupts taken = "); + console_puthex(board_irq_count); + console_puts("\n"); + + failures += report("[check] timer interrupts delivered via GICv3 ", + (board_irq_count > irq_before) ? 1U : 0U); + + /* Check 5: the ThreadX clock is driven by those interrupts. */ + + elapsed = time_after - time_before; + console_puts("[check] tx_time_get elapsed = "); + console_puthex(elapsed); + console_puts(" ticks (slept "); + console_puthex(SLEEP_TICKS); + console_puts(")\n"); + + failures += report("[check] tx_time_get advancing ", + (elapsed >= SLEEP_TICKS) ? 1U : 0U); + + failures += report("[check] sleep lasted about the requested time ", + (elapsed <= (SLEEP_TICKS + SLEEP_SLACK_TICKS)) ? 1U : 0U); + + /* Check 7: the lower-priority thread ran while we were suspended, then + was preempted when the tick made this thread ready again. That + requires a correct context save and restore across the interrupt. */ + + console_puts("[check] busy-thread counter delta = "); + console_puthex(busy_after - busy_before); + console_puts("\n"); + + failures += report("[check] lower-priority thread ran, then preempted ", + (busy_after > busy_before) ? 1U : 0U); + + /* Nothing unexpected should have arrived on the interrupt path. */ + + if (board_unexpected_intid != 0UL) + { + console_puts("[check] unexpected INTID = "); + console_puthex(board_unexpected_intid); + console_puts("\n"); + } + failures += report("[check] no unexpected interrupt IDs ", + (board_unexpected_intid == 0UL) ? 1U : 0U); + + if (failures == 0U) + { + console_puts("\nM3 RESULT: ALL CHECKS PASSED\n"); + } + else + { + console_puts("\nM3 RESULT: FAILED\n"); + } + + console_exit(failures); +} + + +/**************************************************************************/ +/* tx_application_define */ +/**************************************************************************/ + +void tx_application_define(void *first_unused_memory) +{ + (void) first_unused_memory; + + /* Priority 10 runs the checks; priority 20 only gets the processor when + the checking thread is suspended. */ + + (void) tx_thread_create(&thread_main, "check thread", thread_main_entry, + 0UL, thread_main_stack, sizeof(thread_main_stack), + 10U, 10U, TX_NO_TIME_SLICE, TX_AUTO_START); + + (void) tx_thread_create(&thread_busy, "busy thread", thread_busy_entry, + 0UL, thread_busy_stack, sizeof(thread_busy_stack), + 20U, 20U, TX_NO_TIME_SLICE, TX_AUTO_START); +} + + +/**************************************************************************/ +/* bsp_main -- entered at EL1 from entry.S. Does not return. */ +/**************************************************************************/ + +void bsp_main(void) +{ + console_puts("\n=== ThreadX AR1/M3 tick and preemption " + "(Cortex-R52, Armv8-R AArch32, Armv8-R AEM FVP) ===\n"); + + tx_kernel_enter(); +} diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_m5.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_m5.c new file mode 100644 index 00000000..783bed06 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_m5.c @@ -0,0 +1,285 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* DEMONSTRATION RELEASE */ +/* */ +/* demo_m5.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* AR1 milestone M5: lazy floating-point context save and restore. */ +/* */ +/* Two paths must be covered, and they use different register sets: */ +/* */ +/* solicited tx_thread_system_return saves D8-D15 and FPSCR, because*/ +/* the callee-saved half is all a voluntary switch can */ +/* lose. Exercised by the checking thread, which keeps */ +/* eight doubles live across tx_thread_sleep. */ +/* interrupt tx_thread_context_restore restores D0-D15 and FPSCR, */ +/* because an asynchronous interrupt can land anywhere. */ +/* Exercised by a lower-priority thread doing continuous */ +/* floating-point work while the tick fires into it. */ +/* */ +/* Every constant is an exact binary fraction, so the comparisons are */ +/* exact and a single corrupted register shows up as a mismatch rather */ +/* than as rounding noise. The two threads use disjoint value ranges, */ +/* so leakage from one context into the other is also caught. */ +/* */ +/* Both threads call tx_thread_vfp_enable(): with lazy save/restore, a */ +/* thread that has not asked for floating-point support does not get */ +/* it, so forgetting the call would show up as corruption rather than */ +/* as a build error. */ +/* */ +/* MISRA C:2012 / warning deviations (justified) */ +/* */ +/* Rule 13.3 style equality on floating point (-Wfloat-equal) */ +/* -- exact comparison is the point of this test, not an oversight. */ +/* Every constant is an exact binary fraction and every operation is */ +/* an addition of 0.5, so the arithmetic is exact and the expected */ +/* results are representable. A tolerance-based comparison would */ +/* mask precisely the corruption being looked for: a restored */ +/* register that is close but wrong would pass. Confined to this */ +/* test file. */ +/* */ +/**************************************************************************/ + +#include "tx_api.h" +#include "board.h" +#include "console.h" + +#define DEMO_STACK_SIZE 2048 +#define FILEX_PTR_SENTINEL ((void *) 0xF11EF11EUL) +#define FP_ITERATIONS 50UL +#define FP_STEP 0.5 /* exactly representable */ + +static TX_THREAD thread_check; +static TX_THREAD thread_fp_busy; + +static ULONG thread_check_stack[DEMO_STACK_SIZE / sizeof(ULONG)]; +static ULONG thread_fp_busy_stack[DEMO_STACK_SIZE / sizeof(ULONG)]; + +/* Set by the busy thread if its floating-point state was ever seen wrong.*/ + +static volatile ULONG busy_iterations; +static volatile ULONG busy_corruptions; + + +/**************************************************************************/ +/* report */ +/**************************************************************************/ + +static UINT report(const char *label_ptr, UINT passed) +{ + console_puts(label_ptr); + console_puts((passed != 0U) ? "PASS\n" : "FAIL\n"); + + return (passed != 0U) ? 0U : 1U; +} + + +/**************************************************************************/ +/* thread_fp_busy_entry */ +/* */ +/* Lowest priority, never sleeps: the periodic tick therefore interrupts */ +/* it in the middle of floating-point work, which is what exercises the */ +/* interrupt half of the VFP context path (D0-D15 + FPSCR). Its values */ +/* are checked every iteration against exactly representable constants. */ +/**************************************************************************/ + +static void thread_fp_busy_entry(ULONG thread_input) +{ + (void) thread_input; + + tx_thread_vfp_enable(); + + for (;;) + { + /* Disjoint from the checking thread's range, so cross-contamination + between contexts is detectable. */ + + double v0 = 1024.5; + double v1 = 2048.25; + double v2 = 4096.125; + double v3 = 8192.0625; + + v0 = (v0 * 2.0) - 1024.5; /* 1024.5 */ + v1 = (v1 * 2.0) - 2048.25; /* 2048.25 */ + v2 = (v2 * 2.0) - 4096.125; /* 4096.125 */ + v3 = (v3 * 2.0) - 8192.0625; /* 8192.0625*/ + + if ((v0 != 1024.5) || (v1 != 2048.25) || + (v2 != 4096.125) || (v3 != 8192.0625)) + { + busy_corruptions++; + } + + busy_iterations++; + } +} + + +/**************************************************************************/ +/* thread_check_entry */ +/**************************************************************************/ + +static void thread_check_entry(ULONG thread_input) +{ + UINT failures = 0U; + ULONG iteration; + ULONG expected_steps; + TX_THREAD *self; + + /* Eight live doubles held across a blocking call. Eight is chosen so + the compiler must use the callee-saved bank D8-D15 (or spill), which + is exactly what a solicited context switch has to preserve. */ + + double a = 1.5; + double b = 2.25; + double c = 3.125; + double d = 4.0625; + double e = 5.03125; + double f = 6.015625; + double g = 7.0078125; + double h = 8.00390625; + + (void) thread_input; + + /* Guard against the VFP enable flag aliasing another thread member. The + flag is reached from assembly by hard-coded offset, and on the + Cortex-R4/R5 ports that offset lands on tx_thread_filex_ptr because + their TX_THREAD_EXTENSION_2 is empty. Planting a sentinel here turns + that class of mistake into a visible failure rather than corruption + that only surfaces once FileX is added. */ + + self = tx_thread_identify(); + self->tx_thread_filex_ptr = FILEX_PTR_SENTINEL; + + tx_thread_vfp_enable(); + + console_puts("[check] running floating-point work across "); + console_puthex(FP_ITERATIONS); + console_puts(" solicited switches...\n"); + + for (iteration = 0UL; iteration < FP_ITERATIONS; iteration++) + { + /* Suspend: the switch must preserve every one of these values, and + the lower-priority floating-point thread runs meanwhile. */ + + tx_thread_sleep(1); + + a += FP_STEP; + b += FP_STEP; + c += FP_STEP; + d += FP_STEP; + e += FP_STEP; + f += FP_STEP; + g += FP_STEP; + h += FP_STEP; + } + + expected_steps = FP_ITERATIONS; + + /* Exact comparisons: every value above is an exact binary fraction and + FP_STEP is 0.5, so no rounding is involved. */ + + failures += report("[check] D-register value a preserved exactly ", + (a == (1.5 + (0.5 * (double) expected_steps))) ? 1U : 0U); + failures += report("[check] D-register value b preserved exactly ", + (b == (2.25 + (0.5 * (double) expected_steps))) ? 1U : 0U); + failures += report("[check] D-register value c preserved exactly ", + (c == (3.125 + (0.5 * (double) expected_steps))) ? 1U : 0U); + failures += report("[check] D-register value d preserved exactly ", + (d == (4.0625 + (0.5 * (double) expected_steps))) ? 1U : 0U); + failures += report("[check] D-register value e preserved exactly ", + (e == (5.03125 + (0.5 * (double) expected_steps))) ? 1U : 0U); + failures += report("[check] D-register value f preserved exactly ", + (f == (6.015625 + (0.5 * (double) expected_steps))) ? 1U : 0U); + failures += report("[check] D-register value g preserved exactly ", + (g == (7.0078125 + (0.5 * (double) expected_steps))) ? 1U : 0U); + failures += report("[check] D-register value h preserved exactly ", + (h == (8.00390625 + (0.5 * (double) expected_steps))) ? 1U : 0U); + + /* The busy thread must have run, and never seen its own state damaged + by an interrupt landing inside its floating-point sequence. */ + + console_puts("[check] busy-thread iterations = "); + console_puthex(busy_iterations); + console_puts("\n[check] busy-thread corruptions = "); + console_puthex(busy_corruptions); + console_puts("\n"); + + failures += report("[check] interrupted FP thread made progress ", + (busy_iterations > 0UL) ? 1U : 0U); + failures += report("[check] no FP corruption across interrupts ", + (busy_corruptions == 0UL) ? 1U : 0U); + + /* The VFP flag must not have aliased tx_thread_filex_ptr. */ + + console_puts("[check] tx_thread_filex_ptr = "); + console_puthex((unsigned long) self->tx_thread_filex_ptr); + console_puts(" (expected 0xf11ef11e)\n"); + + failures += report("[check] VFP flag did not alias filex_ptr ", + (self->tx_thread_filex_ptr == FILEX_PTR_SENTINEL) ? 1U : 0U); + + if (failures == 0U) + { + console_puts("\nM5 RESULT: ALL CHECKS PASSED\n"); + } + else + { + console_puts("\nM5 RESULT: FAILED\n"); + } + + console_exit(failures); +} + + +/**************************************************************************/ +/* tx_application_define */ +/**************************************************************************/ + +void tx_application_define(void *first_unused_memory) +{ + (void) first_unused_memory; + + (void) tx_thread_create(&thread_check, "fp check", thread_check_entry, 0UL, + thread_check_stack, sizeof(thread_check_stack), + 10U, 10U, TX_NO_TIME_SLICE, TX_AUTO_START); + + (void) tx_thread_create(&thread_fp_busy, "fp busy", thread_fp_busy_entry, + 0UL, thread_fp_busy_stack, + sizeof(thread_fp_busy_stack), + 20U, 20U, TX_NO_TIME_SLICE, TX_AUTO_START); +} + + +/**************************************************************************/ +/* bsp_main -- entered at EL1 from entry.S. Does not return. */ +/**************************************************************************/ + +void bsp_main(void) +{ + console_puts("\n=== ThreadX AR1/M5 lazy VFP context switch " + "(Cortex-R52, Armv8-R AArch32, Armv8-R AEM FVP) ===\n"); + + tx_kernel_enter(); +} diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_mpu.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_mpu.c new file mode 100644 index 00000000..c96afc08 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_mpu.c @@ -0,0 +1,284 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* DEMONSTRATION RELEASE */ +/* */ +/* demo_mpu.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* AR1 milestone M5: PMSAv8-R protection and caches. */ +/* */ +/* The important check here is enforcement, not configuration. Reading*/ +/* SCTLR back only proves a bit was set; it says nothing about whether */ +/* the region table actually describes memory correctly. So the test */ +/* provokes a real permission fault by writing to the read-only code */ +/* region and requires the abort to arrive, then confirms a legal write*/ +/* to the data region still succeeds. A region set that permitted */ +/* everything would pass the first kind of check and fail this one. */ +/* */ +/**************************************************************************/ + +#include "tx_api.h" +#include "board.h" +#include "console.h" +#include "mpu.h" + +#define DEMO_STACK_SIZE 2048 + +static TX_THREAD thread_check; +static ULONG thread_check_stack[DEMO_STACK_SIZE / sizeof(ULONG)]; + +/* Provided by entry.S and manipulated by its data-abort handler. */ + + +/* A known-writable location, used to show legal access still works. */ + +static volatile unsigned long writable_probe; + +/* Execute-never probe. Lives in the data region, holds one A32 "bx lr", and + is only ever reached if XN is NOT enforced -- in which case this check has + already failed and the encoded instruction is simply the most harmless + thing to land on. Not static: nothing in this file reads it, and an + optimising build would otherwise discard it. */ + +volatile unsigned long executable_probe = 0xE12FFF1EUL; + +extern void mpu_try_execute(void (*target)(void)); +extern volatile unsigned long mpu_expect_pabt; +extern volatile unsigned long mpu_pabt_count; + + +/**************************************************************************/ +/* report */ +/**************************************************************************/ + +static UINT report(const char *label_ptr, UINT passed) +{ + console_puts(label_ptr); + console_puts((passed != 0U) ? "PASS\n" : "FAIL\n"); + + return (passed != 0U) ? 0U : 1U; +} + + +/**************************************************************************/ +/* thread_check_entry */ +/**************************************************************************/ + +static void thread_check_entry(ULONG thread_input) +{ + UINT failures = 0U; + unsigned int available; + unsigned int used; + unsigned int index; + const MPU_REGION *table; + unsigned long aborts_before; + + (void) thread_input; + + /* What the implementation provides. */ + + available = mpu_region_count(); + console_puts("[check] MPUIR regions available = "); + console_puthex(available); + console_puts("\n"); + failures += report("[check] implementation reports MPU regions ", + (available > 0U) ? 1U : 0U); + + /* What this image programmed. */ + + table = mpu_region_table(&used); + console_puts("[check] regions programmed = "); + console_puthex(used); + console_puts("\n"); + + for (index = 0U; index < used; index++) + { + console_puts(" "); + console_puts(table[index].mpu_region_name); + console_puts(" base "); + console_puthex(table[index].mpu_region_base); + console_puts(" limit "); + console_puthex(table[index].mpu_region_limit); + console_puts("\n"); + } + + failures += report("[check] region table programmed ", + (used > 0U) ? 1U : 0U); + + /* Read the hardware back: if the register encodings were wrong the + writes would be silently discarded and a permissive background map + would make everything appear to work. */ + + for (index = 0U; index < used; index++) + { + unsigned long prbar = 0UL; + unsigned long prlar = 0UL; + + mpu_read_region(index, &prbar, &prlar); + console_puts(" region "); + console_puthex(index); + console_puts(" PRBAR="); + console_puthex(prbar); + console_puts(" PRLAR="); + console_puthex(prlar); + console_puts("\n"); + if ((prbar == 0UL) && (prlar == 0UL)) + { + failures++; + } + } + failures += report("[check] hardware holds the programmed regions ", + (failures == 0U) ? 1U : 0U); + + failures += report("[check] MPU enabled (SCTLR.M) ", + mpu_is_enabled()); + failures += report("[check] caches enabled (SCTLR.C and SCTLR.I) ", + mpu_caches_enabled()); + + /* Enforcement, part 1: a legal write to the data region must work. */ + + writable_probe = 0xA5A5A5A5UL; + failures += report("[check] legal write to data region succeeds ", + (writable_probe == 0xA5A5A5A5UL) ? 1U : 0U); + + /* Enforcement, part 1b: an address covered by NO region must fault. + This distinguishes "the MPU is not enforcing at all" from "the MPU + enforces coverage but not the access permission". 0x40000000 lies + between the end of the data region and the peripheral region. */ + + /* Enforcement, part 2: an address covered by no region must fault. This + distinguishes "not enforcing at all" from "enforcing coverage only". */ + + aborts_before = mpu_abort_count; + mpu_expect_abort = 1UL; + { + volatile unsigned long *gap_ptr = (volatile unsigned long *) 0x40000000UL; + + *gap_ptr = 0x11223344UL; + } + failures += report("[check] write to unmapped address faulted ", + (mpu_abort_count == (aborts_before + 1UL)) ? 1U : 0U); + mpu_expect_abort = 0UL; + + /* Enforcement, part 3: the read-only code region must reject a write. + This is the check that matters: region coverage is enforced even with a + wrong access-permission encoding, so only a write-permission test can + show that the permissions themselves are right. */ + + aborts_before = mpu_abort_count; + mpu_expect_abort = 1UL; + { + volatile unsigned long *code_ptr = + (volatile unsigned long *) table[0].mpu_region_base; + + *code_ptr = 0xDEADBEEFUL; + } + mpu_expect_abort = 0UL; + + console_puts("[check] aborts before/after write to code = "); + console_puthex(aborts_before); + console_puts(" / "); + console_puthex(mpu_abort_count); + console_puts("\n"); + + failures += report("[check] write to read-only code region faulted ", + (mpu_abort_count == (aborts_before + 1UL)) ? 1U : 0U); + + { + volatile unsigned long *code_ptr = + (volatile unsigned long *) table[0].mpu_region_base; + + failures += report("[check] code region contents unmodified ", + (*code_ptr != 0xDEADBEEFUL) ? 1U : 0U); + } + + /* Enforcement, part 4: the execute-never data region must reject an + instruction fetch. This is the counterpart to part 3 and the check that + pins down PRBAR.XN specifically. It is worth stating why it exists: an + earlier version of program_region() shifted every PRBAR field one bit + too far left, which left XN in the real AP[1] and every "non-executable" + region freely executable. Every check above still passed, because the + accidental AP bits happened to land correctly. Only fetching from a + supposedly non-executable region exposed it -- on S32Z280 silicon, + where the core cheerfully ran instructions out of .data. */ + + { + unsigned long pabts_before = mpu_pabt_count; + + mpu_expect_pabt = 1UL; + mpu_try_execute((void (*)(void)) (void *) &executable_probe); + mpu_expect_pabt = 0UL; + + console_puts("[check] prefetch aborts before/after exec from data = "); + console_puthex(pabts_before); + console_puts(" / "); + console_puthex(mpu_pabt_count); + console_puts("\n"); + + failures += report("[check] execute from non-executable data faulted ", + (mpu_pabt_count == (pabts_before + 1UL)) ? 1U : 0U); + } + + /* And the kernel is still healthy afterwards. */ + + failures += report("[check] kernel still running after the fault ", + (tx_thread_identify() == &thread_check) ? 1U : 0U); + + if (failures == 0U) + { + console_puts("\nMPU RESULT: ALL CHECKS PASSED\n"); + } + else + { + console_puts("\nMPU RESULT: FAILED\n"); + } + + console_exit(failures); +} + + +/**************************************************************************/ +/* tx_application_define */ +/**************************************************************************/ + +void tx_application_define(void *first_unused_memory) +{ + (void) first_unused_memory; + + (void) tx_thread_create(&thread_check, "mpu check", thread_check_entry, + 0UL, thread_check_stack, sizeof(thread_check_stack), + 10U, 10U, TX_NO_TIME_SLICE, TX_AUTO_START); +} + + +/**************************************************************************/ +/* bsp_main -- entered at EL1 from entry.S. Does not return. */ +/**************************************************************************/ + +void bsp_main(void) +{ + console_puts("\n=== ThreadX AR1/M5 PMSAv8-R protection and caches " + "(Cortex-R52, Armv8-R AEM FVP) ===\n"); + + tx_kernel_enter(); +} diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_nesting.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_nesting.c new file mode 100644 index 00000000..0e204d06 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_nesting.c @@ -0,0 +1,216 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* demo_nesting.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frederic Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Nested IRQ handling: _tx_thread_irq_nesting_start and */ +/* _tx_thread_irq_nesting_end. */ +/* */ +/* Those two routines have shipped in this port since it was written */ +/* and nothing had ever called them. They were compiled into every */ +/* build, never entered by any image, on the model or on silicon. */ +/* This is the image that enters them. */ +/* */ +/* HOW NESTING IS PROVOKED */ +/* */ +/* Two interrupt sources are needed and one must outrank the other. */ +/* The generic timer PPI is already there; the second is an SGI, which */ +/* a core can raise on itself, given a numerically lower priority so */ +/* the GIC lets it preempt. */ +/* */ +/* Inside the timer handler, after nesting_start has left IRQ mode for */ +/* System mode and re-enabled IRQ, board_irq_handler raises the SGI and */ +/* spins briefly. The SGI outranks the timer, so it is delivered */ +/* immediately and re-enters el1_irq_entry while the timer handler is */ +/* still on the stack. That is nesting, and the depth counter sees 2. */ +/* */ +/* The spin is bounded. Without TX_ENABLE_IRQ_NESTING the SGI can */ +/* never arrive, and a test that hangs in that configuration would be */ +/* worse than one that fails. */ +/* */ +/* WHAT EACH CHECK IS FOR */ +/* */ +/* N1 the SGI arrives at all. This is separate on purpose: it tests */ +/* the ICC_SGI1R encoding, which does not transcribe from the */ +/* AArch64 alias, so if N1 fails the fault is in gicv3_send_sgi and */ +/* not in the nesting routines. */ +/* N2 the SGI arrived while another handler was active, depth 2. */ +/* N3 depth returned to zero, so the pairing unwound. */ +/* N4 the tick still advances afterwards, so nesting did not damage */ +/* the timer path. */ +/* N5 threads still run and preempt, so the context save and restore */ +/* survived being re-entered. */ +/* */ +/**************************************************************************/ + +#include "tx_api.h" +#include "board.h" +#include "console.h" +#include "gicv3.h" + +#define DEMO_STACK_SIZE 2048 + +static TX_THREAD thread_check; +static TX_THREAD thread_spin; + +static ULONG thread_check_stack[DEMO_STACK_SIZE / sizeof(ULONG)]; +static ULONG thread_spin_stack[DEMO_STACK_SIZE / sizeof(ULONG)]; + +static volatile ULONG spin_runs; + + +static UINT report(const char *label_ptr, UINT passed) +{ + console_puts(label_ptr); + console_puts((passed != 0U) ? "PASS\n" : "FAIL\n"); + return (passed != 0U) ? 0U : 1U; +} + + +static void report_hex(const char *label_ptr, unsigned long value) +{ + console_puts(label_ptr); + console_puthex(value); + console_puts("\n"); +} + + +/* Lowest priority, never blocks: gives the tick something to preempt and shows + the scheduler is still alive after handlers have been nested. */ + +static void thread_spin_entry(ULONG thread_input) +{ + (void) thread_input; + + for (;;) + { + spin_runs++; + } +} + + +static void thread_check_entry(ULONG thread_input) +{ + UINT failures = 0U; + ULONG sgi_before; + ULONG ticks_start; + ULONG ticks_end; + ULONG spin_before; + + (void) thread_input; + + /* N1 first, with provocation still off, so this measures only whether an + SGI raised from thread context is delivered and dispatched. */ + + console_puts("[check] raising an SGI from thread context\n"); + + sgi_before = board_sgi_count; + gicv3_send_sgi(BOARD_NEST_SGI_INTID); + tx_thread_sleep(2); + + failures += report("[check] N1 SGI delivered and dispatched ", + (board_sgi_count > sgi_before) ? 1U : 0U); + + if (board_sgi_count == sgi_before) + { + /* Everything below depends on the SGI working, so say so rather than + reporting four more failures that all have the same cause. */ + + console_puts("[check] SGI never arrived; the remaining checks would\n" + " only restate that, so they are skipped.\n"); + console_puts("\nNESTING RESULT: FAILED\n"); + console_exit(1U); + } + + /* Now let the timer handler provoke a nested SGI, and give it time for a + good number of ticks. */ + + console_puts("[check] enabling nested provocation in the timer handler\n"); + + board_nest_provoke = 1UL; + ticks_start = tx_time_get(); + spin_before = spin_runs; + tx_thread_sleep(50); + ticks_end = tx_time_get(); + board_nest_provoke = 0UL; + + report_hex("[check] max nesting depth observed = ", board_nest_max); + report_hex("[check] nested SGI count = ", board_sgi_nested_count); + report_hex("[check] nesting depth now = ", board_nest_depth); + + failures += report("[check] N2 SGI nested inside another handler ", + (board_sgi_nested_count > 0UL) && + (board_nest_max >= 2UL) ? 1U : 0U); + + failures += report("[check] N3 nesting unwound to depth zero ", + (board_nest_depth == 0UL) ? 1U : 0U); + + failures += report("[check] N4 tick still advancing after nesting ", + ((ticks_end - ticks_start) >= 40UL) ? 1U : 0U); + + failures += report("[check] N5 lower-priority thread still scheduled ", + (spin_runs > spin_before) ? 1U : 0U); + + report_hex("[check] spurious interrupts = ", board_spurious_count); + report_hex("[check] unexpected INTID = ", board_unexpected_intid); + + failures += report("[check] no spurious or unexpected interrupts ", + ((board_spurious_count == 0UL) && + (board_unexpected_intid == 0UL)) ? 1U : 0U); + + if (failures == 0U) + { + console_puts("\nNESTING RESULT: ALL CHECKS PASSED\n"); + } + else + { + console_puts("\nNESTING RESULT: FAILED\n"); + } + + console_exit(failures); +} + + +void tx_application_define(void *first_unused_memory) +{ + (void) first_unused_memory; + + (void) tx_thread_create(&thread_check, "nest check", thread_check_entry, + 0UL, thread_check_stack, sizeof(thread_check_stack), + 10U, 10U, TX_NO_TIME_SLICE, TX_AUTO_START); + + (void) tx_thread_create(&thread_spin, "spinner", thread_spin_entry, + 0UL, thread_spin_stack, sizeof(thread_spin_stack), + 20U, 20U, TX_NO_TIME_SLICE, TX_AUTO_START); +} + + +void bsp_main(void) +{ + console_puts("\n=== ThreadX nested IRQ handling " + "(Cortex-R52, Armv8-R AArch32, Armv8-R AEM FVP) ===\n"); + + tx_kernel_enter(); +} diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_threadx.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_threadx.c new file mode 100644 index 00000000..90fe257d --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_threadx.c @@ -0,0 +1,393 @@ +/**************************************************************************/ +/* Copyright (c) 2024 Microsoft Corporation */ +/* 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. */ +/* */ +/* SPDX-License-Identifier: MIT */ +/**************************************************************************/ +// Portions of this file were generated with AI assistance. + +/* Copied unmodified from samples/demo_threadx.c for the Armv8-R AEM FVP + example build, then extended with a verification thread and a bsp_main + entry point. Keeping the demo body byte-identical to the shipped sample + is the point of AR1 milestone M4: it shows the standard ThreadX demo runs + on Cortex-R52 with no demo-side changes. The additions are confined to + the end of this file and one call in tx_application_define. */ + +/* This is a small demo of the high-performance ThreadX kernel. It includes examples of eight + threads of different priorities, using a message queue, semaphore, mutex, event flags group, + byte pool, and block pool. */ + +#include "tx_api.h" +#include "demo_verify.h" + +#define DEMO_STACK_SIZE 1024 +#define DEMO_BYTE_POOL_SIZE 9120 +#define DEMO_BLOCK_POOL_SIZE 100 +#define DEMO_QUEUE_SIZE 100 + + +/* Define the ThreadX object control blocks... */ + +TX_THREAD thread_0; +TX_THREAD thread_1; +TX_THREAD thread_2; +TX_THREAD thread_3; +TX_THREAD thread_4; +TX_THREAD thread_5; +TX_THREAD thread_6; +TX_THREAD thread_7; +TX_QUEUE queue_0; +TX_SEMAPHORE semaphore_0; +TX_MUTEX mutex_0; +TX_EVENT_FLAGS_GROUP event_flags_0; +TX_BYTE_POOL byte_pool_0; +TX_BLOCK_POOL block_pool_0; +UCHAR memory_area[DEMO_BYTE_POOL_SIZE]; + + +/* Define the counters used in the demo application... */ + +ULONG thread_0_counter; +ULONG thread_1_counter; +ULONG thread_1_messages_sent; +ULONG thread_2_counter; +ULONG thread_2_messages_received; +ULONG thread_3_counter; +ULONG thread_4_counter; +ULONG thread_5_counter; +ULONG thread_6_counter; +ULONG thread_7_counter; + + +/* Define thread prototypes. */ + +void thread_0_entry(ULONG thread_input); +void thread_1_entry(ULONG thread_input); +void thread_2_entry(ULONG thread_input); +void thread_3_and_4_entry(ULONG thread_input); +void thread_5_entry(ULONG thread_input); +void thread_6_and_7_entry(ULONG thread_input); + + +/* Define main entry point. */ + +int main() +{ + + /* Enter the ThreadX kernel. */ + tx_kernel_enter(); +} + + +/* Define what the initial system looks like. */ + +void tx_application_define(void *first_unused_memory) +{ + +CHAR *pointer = TX_NULL; + + + /* Create a byte memory pool from which to allocate the thread stacks. */ + tx_byte_pool_create(&byte_pool_0, "byte pool 0", memory_area, DEMO_BYTE_POOL_SIZE); + + /* Put system definition stuff in here, e.g. thread creates and other assorted + create information. */ + + /* Allocate the stack for thread 0. */ + tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT); + + /* Create the main thread. */ + tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0, + pointer, DEMO_STACK_SIZE, + 1, 1, TX_NO_TIME_SLICE, TX_AUTO_START); + + + /* Allocate the stack for thread 1. */ + tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT); + + /* Create threads 1 and 2. These threads pass information through a ThreadX + message queue. It is also interesting to note that these threads have a time + slice. */ + tx_thread_create(&thread_1, "thread 1", thread_1_entry, 1, + pointer, DEMO_STACK_SIZE, + 16, 16, 4, TX_AUTO_START); + + /* Allocate the stack for thread 2. */ + tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT); + + tx_thread_create(&thread_2, "thread 2", thread_2_entry, 2, + pointer, DEMO_STACK_SIZE, + 16, 16, 4, TX_AUTO_START); + + /* Allocate the stack for thread 3. */ + tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT); + + /* Create threads 3 and 4. These threads compete for a ThreadX counting semaphore. + An interesting thing here is that both threads share the same instruction area. */ + tx_thread_create(&thread_3, "thread 3", thread_3_and_4_entry, 3, + pointer, DEMO_STACK_SIZE, + 8, 8, TX_NO_TIME_SLICE, TX_AUTO_START); + + /* Allocate the stack for thread 4. */ + tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT); + + tx_thread_create(&thread_4, "thread 4", thread_3_and_4_entry, 4, + pointer, DEMO_STACK_SIZE, + 8, 8, TX_NO_TIME_SLICE, TX_AUTO_START); + + /* Allocate the stack for thread 5. */ + tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT); + + /* Create thread 5. This thread simply pends on an event flag which will be set + by thread_0. */ + tx_thread_create(&thread_5, "thread 5", thread_5_entry, 5, + pointer, DEMO_STACK_SIZE, + 4, 4, TX_NO_TIME_SLICE, TX_AUTO_START); + + /* Allocate the stack for thread 6. */ + tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT); + + /* Create threads 6 and 7. These threads compete for a ThreadX mutex. */ + tx_thread_create(&thread_6, "thread 6", thread_6_and_7_entry, 6, + pointer, DEMO_STACK_SIZE, + 8, 8, TX_NO_TIME_SLICE, TX_AUTO_START); + + /* Allocate the stack for thread 7. */ + tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT); + + tx_thread_create(&thread_7, "thread 7", thread_6_and_7_entry, 7, + pointer, DEMO_STACK_SIZE, + 8, 8, TX_NO_TIME_SLICE, TX_AUTO_START); + + /* Allocate the message queue. */ + tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_QUEUE_SIZE*sizeof(ULONG), TX_NO_WAIT); + + /* Create the message queue shared by threads 1 and 2. */ + tx_queue_create(&queue_0, "queue 0", TX_1_ULONG, pointer, DEMO_QUEUE_SIZE*sizeof(ULONG)); + + /* Create the semaphore used by threads 3 and 4. */ + tx_semaphore_create(&semaphore_0, "semaphore 0", 1); + + /* Create the event flags group used by threads 1 and 5. */ + tx_event_flags_create(&event_flags_0, "event flags 0"); + + /* Create the mutex used by thread 6 and 7 without priority inheritance. */ + tx_mutex_create(&mutex_0, "mutex 0", TX_NO_INHERIT); + + /* Allocate the memory for a small block pool. */ + tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_BLOCK_POOL_SIZE, TX_NO_WAIT); + + /* Create a block memory pool to allocate a message buffer from. */ + tx_block_pool_create(&block_pool_0, "block pool 0", sizeof(ULONG), pointer, DEMO_BLOCK_POOL_SIZE); + + /* Allocate a block and release the block memory. */ + tx_block_allocate(&block_pool_0, (VOID **) &pointer, TX_NO_WAIT); + + /* Release the block back to the pool. */ + tx_block_release(pointer); + + /* AR1/M4 addition: create the verification thread. */ + demo_verify_create(); +} + + + +/* Define the test threads. */ + +void thread_0_entry(ULONG thread_input) +{ + +UINT status; + + + /* This thread simply sits in while-forever-sleep loop. */ + while(1) + { + + /* Increment the thread counter. */ + thread_0_counter++; + + /* Sleep for 10 ticks. */ + tx_thread_sleep(10); + + /* Set event flag 0 to wakeup thread 5. */ + status = tx_event_flags_set(&event_flags_0, 0x1, TX_OR); + + /* Check status. */ + if (status != TX_SUCCESS) + break; + } +} + + +void thread_1_entry(ULONG thread_input) +{ + +UINT status; + + + /* This thread simply sends messages to a queue shared by thread 2. */ + while(1) + { + + /* Increment the thread counter. */ + thread_1_counter++; + + /* Send message to queue 0. */ + status = tx_queue_send(&queue_0, &thread_1_messages_sent, TX_WAIT_FOREVER); + + /* Check completion status. */ + if (status != TX_SUCCESS) + break; + + /* Increment the message sent. */ + thread_1_messages_sent++; + } +} + + +void thread_2_entry(ULONG thread_input) +{ + +ULONG received_message; +UINT status; + + /* This thread retrieves messages placed on the queue by thread 1. */ + while(1) + { + + /* Increment the thread counter. */ + thread_2_counter++; + + /* Retrieve a message from the queue. */ + status = tx_queue_receive(&queue_0, &received_message, TX_WAIT_FOREVER); + + /* Check completion status and make sure the message is what we + expected. */ + if ((status != TX_SUCCESS) || (received_message != thread_2_messages_received)) + break; + + /* Otherwise, all is okay. Increment the received message count. */ + thread_2_messages_received++; + } +} + + +void thread_3_and_4_entry(ULONG thread_input) +{ + +UINT status; + + + /* This function is executed from thread 3 and thread 4. As the loop + below shows, these function compete for ownership of semaphore_0. */ + while(1) + { + + /* Increment the thread counter. */ + if (thread_input == 3) + thread_3_counter++; + else + thread_4_counter++; + + /* Get the semaphore with suspension. */ + status = tx_semaphore_get(&semaphore_0, TX_WAIT_FOREVER); + + /* Check status. */ + if (status != TX_SUCCESS) + break; + + /* Sleep for 2 ticks to hold the semaphore. */ + tx_thread_sleep(2); + + /* Release the semaphore. */ + status = tx_semaphore_put(&semaphore_0); + + /* Check status. */ + if (status != TX_SUCCESS) + break; + } +} + + +void thread_5_entry(ULONG thread_input) +{ + +UINT status; +ULONG actual_flags; + + + /* This thread simply waits for an event in a forever loop. */ + while(1) + { + + /* Increment the thread counter. */ + thread_5_counter++; + + /* Wait for event flag 0. */ + status = tx_event_flags_get(&event_flags_0, 0x1, TX_OR_CLEAR, + &actual_flags, TX_WAIT_FOREVER); + + /* Check status. */ + if ((status != TX_SUCCESS) || (actual_flags != 0x1)) + break; + } +} + + +void thread_6_and_7_entry(ULONG thread_input) +{ + +UINT status; + + + /* This function is executed from thread 6 and thread 7. As the loop + below shows, these function compete for ownership of mutex_0. */ + while(1) + { + + /* Increment the thread counter. */ + if (thread_input == 6) + thread_6_counter++; + else + thread_7_counter++; + + /* Get the mutex with suspension. */ + status = tx_mutex_get(&mutex_0, TX_WAIT_FOREVER); + + /* Check status. */ + if (status != TX_SUCCESS) + break; + + /* Get the mutex again with suspension. This shows + that an owning thread may retrieve the mutex it + owns multiple times. */ + status = tx_mutex_get(&mutex_0, TX_WAIT_FOREVER); + + /* Check status. */ + if (status != TX_SUCCESS) + break; + + /* Sleep for 2 ticks to hold the mutex. */ + tx_thread_sleep(2); + + /* Release the mutex. */ + status = tx_mutex_put(&mutex_0); + + /* Check status. */ + if (status != TX_SUCCESS) + break; + + /* Release the mutex again. This will actually + release ownership since it was obtained twice. */ + status = tx_mutex_put(&mutex_0); + + /* Check status. */ + if (status != TX_SUCCESS) + break; + } +} diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_verify.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_verify.c new file mode 100644 index 00000000..a22c7e13 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_verify.c @@ -0,0 +1,186 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* DEMONSTRATION RELEASE */ +/* */ +/* demo_verify.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* AR1 milestone M4: verifies that the standard eight-thread ThreadX */ +/* demo makes progress on Cortex-R52, then terminates the simulation */ +/* so the run can be automated. */ +/* */ +/* Where M3 proved the tick and a single preemption, this exercises */ +/* the demo's full object set -- queue, semaphore, mutex, event flags, */ +/* byte pool and block pool -- across eight threads at five priorities.*/ +/* Each of the ten demo counters is required to have advanced: a */ +/* stalled thread (a lost wakeup, a mishandled priority inversion) */ +/* shows up as a counter still at zero rather than as a plausible- */ +/* looking run. */ +/* */ +/* The verification thread runs at priority 0 so it can always preempt */ +/* the demo, and spends nearly all its life suspended. */ +/* */ +/**************************************************************************/ + +#include "tx_api.h" +#include "board.h" +#include "console.h" +#include "demo_verify.h" + +#define VERIFY_STACK_SIZE 2048 + +/* Ticks to let the demo run before checking. thread_0 sleeps 10 ticks per + iteration and thread_5 waits on the event flag it sets, so these two are + the slowest; 300 ticks (3 s at 100 Hz) gives them ~30 iterations each. */ + +#define VERIFY_SETTLE_TICKS 300UL + +/* Counters defined by the standard demo. */ + +extern ULONG thread_0_counter; +extern ULONG thread_1_counter; +extern ULONG thread_1_messages_sent; +extern ULONG thread_2_counter; +extern ULONG thread_2_messages_received; +extern ULONG thread_3_counter; +extern ULONG thread_4_counter; +extern ULONG thread_5_counter; +extern ULONG thread_6_counter; +extern ULONG thread_7_counter; + +static TX_THREAD verify_thread; +static ULONG verify_stack[VERIFY_STACK_SIZE / sizeof(ULONG)]; + + +/**************************************************************************/ +/* check_counter -- report one counter and whether it advanced. */ +/**************************************************************************/ + +static UINT check_counter(const char *label_ptr, ULONG value) +{ + console_puts(label_ptr); + console_puthex(value); + console_puts((value > 0UL) ? " PASS\n" : " FAIL\n"); + + return (value > 0UL) ? 0U : 1U; +} + + +/**************************************************************************/ +/* verify_thread_entry */ +/**************************************************************************/ + +static void verify_thread_entry(ULONG thread_input) +{ + UINT failures = 0U; + ULONG elapsed; + + (void) thread_input; + + console_puts("[verify] letting the standard demo run for "); + console_puthex(VERIFY_SETTLE_TICKS); + console_puts(" ticks...\n"); + + tx_thread_sleep(VERIFY_SETTLE_TICKS); + + elapsed = tx_time_get(); + + console_puts("\n[verify] demo counters after settling:\n"); + + failures += check_counter(" thread 0 counter = ", thread_0_counter); + failures += check_counter(" thread 1 counter = ", thread_1_counter); + failures += check_counter(" thread 1 messages sent = ", thread_1_messages_sent); + failures += check_counter(" thread 2 counter = ", thread_2_counter); + failures += check_counter(" thread 2 messages received = ", thread_2_messages_received); + failures += check_counter(" thread 3 counter = ", thread_3_counter); + failures += check_counter(" thread 4 counter = ", thread_4_counter); + failures += check_counter(" thread 5 counter = ", thread_5_counter); + failures += check_counter(" thread 6 counter = ", thread_6_counter); + failures += check_counter(" thread 7 counter = ", thread_7_counter); + + /* The queue must not have silently dropped or duplicated messages. */ + + console_puts("\n[verify] queue balance (sent >= received) "); + if (thread_1_messages_sent >= thread_2_messages_received) + { + console_puts("PASS\n"); + } + else + { + failures++; + console_puts("FAIL\n"); + } + + console_puts("[verify] elapsed ticks = "); + console_puthex(elapsed); + console_puts("\n[verify] tick advanced past the settle period "); + if (elapsed >= VERIFY_SETTLE_TICKS) + { + console_puts("PASS\n"); + } + else + { + failures++; + console_puts("FAIL\n"); + } + + if (failures == 0U) + { + console_puts("\nM4 RESULT: ALL CHECKS PASSED\n"); + } + else + { + console_puts("\nM4 RESULT: FAILED\n"); + } + + console_exit(failures); +} + + +/**************************************************************************/ +/* demo_verify_create */ +/**************************************************************************/ + +void demo_verify_create(void) +{ + /* Priority 0 and its own static stack: the verification thread must be + able to preempt every demo thread, and must not perturb the demo's + byte-pool allocations. */ + + (void) tx_thread_create(&verify_thread, "verify", verify_thread_entry, 0UL, + verify_stack, sizeof(verify_stack), + 0U, 0U, TX_NO_TIME_SLICE, TX_AUTO_START); +} + + +/**************************************************************************/ +/* bsp_main -- entered at EL1 from entry.S. Does not return. */ +/**************************************************************************/ + +void bsp_main(void) +{ + console_puts("\n=== ThreadX AR1/M4 standard eight-thread demo " + "(Cortex-R52, Armv8-R AArch32, Armv8-R AEM FVP) ===\n"); + + tx_kernel_enter(); +} diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_verify.h b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_verify.h new file mode 100644 index 00000000..71ebe6be --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_verify.h @@ -0,0 +1,41 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* DEMONSTRATION RELEASE */ +/* */ +/* demo_verify.h Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Verification harness for the standard ThreadX demo, kept separate */ +/* so demo_threadx.c stays byte-identical to the shipped sample apart */ +/* from its header note and a single call to demo_verify_create(). */ +/* */ +/**************************************************************************/ + +#ifndef DEMO_VERIFY_H +#define DEMO_VERIFY_H + +/* Create the verification thread. Called from tx_application_define. */ + +void demo_verify_create(void); + +#endif /* DEMO_VERIFY_H */ diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/entry.S b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/entry.S new file mode 100644 index 00000000..77d81336 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/entry.S @@ -0,0 +1,710 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* entry.S Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Reset entry for Cortex-R52 (Armv8-R, AArch32) on the Armv8-R AEM */ +/* FVP. The R52 always implements EL2 and resets into it, so this */ +/* file configures EL2 first, installs both the EL2 and EL1 vector */ +/* tables, and only then drops to EL1 (Supervisor mode) to run the */ +/* kernel. The HVC entry in the EL2 table is the seam that ZoneX */ +/* (roadmap AR3) grows into -- it is deliberately present from AR1 so */ +/* that partitioning does not require re-architecting the boot path. */ +/* */ +/* Define TX_R52_BOOT_AT_EL1 to skip EL2 configuration, for targets */ +/* where an earlier boot stage or a vendor EL2 monitor (for example */ +/* NXP's EL2M on S32Z2) has already dropped privilege to EL1. */ +/* */ +/**************************************************************************/ + +#include "platform.h" + + /* No .arch directive here. GNU as accepts "armv8-r", LLVM's integrated + assembler accepts no spelling of it at all -- not armv8-r, not armv8r -- + and fails with "Unknown Arch". The architecture is already selected by + -mcpu=cortex-r52 on the command line, which both toolchains require and + both toolchain files pass, so the directive only restated it. */ + + .syntax unified + .arm + +/* AArch32 CPSR mode encodings and interrupt mask bits. */ + + .equ MODE_USR, 0x10 + .equ MODE_FIQ, 0x11 + .equ MODE_IRQ, 0x12 + .equ MODE_SVC, 0x13 + .equ MODE_ABT, 0x17 + .equ MODE_HYP, 0x1A + .equ MODE_UND, 0x1B + .equ MODE_SYS, 0x1F + .equ PSR_A, 0x100 /* asynchronous abort mask */ + .equ PSR_I, 0x80 /* IRQ mask */ + .equ PSR_F, 0x40 /* FIQ mask */ + +/* HCR (Hyp Configuration Register) bits used here. */ + + .equ HCR_VM, (1 << 0) /* stage-2 MPU enable */ + .equ HCR_AMO, (1 << 3) /* route aborts to EL2 */ + .equ HCR_IMO, (1 << 4) /* route IRQ to EL2 */ + .equ HCR_FMO, (1 << 5) /* route FIQ to EL2 */ + .equ HCR_TGE, (1 << 27) /* trap general exceptions to EL2 */ + .equ HCR_HCD, (1 << 29) /* HVC disable -- must be clear */ + + .equ HCPTR_TCP, (3 << 10) /* trap EL1 access to CP10/CP11 */ + +/* CNTHCTL: permit EL1/EL0 use of the physical counter and timer. */ + + .equ CNTHCTL_PL1PCTEN, (1 << 0) + .equ CNTHCTL_PL1PCEN, (1 << 1) + +/* ICC_HSRE: enable the GICv3 system-register interface for EL1. */ + + .equ ICC_HSRE_SRE, (1 << 0) + .equ ICC_HSRE_ENABLE, (1 << 3) + +/* CPACR grants EL1/EL0 access to CP10 and CP11 (the floating-point unit); + FPEXC.EN then enables floating-point execution. */ + + .equ CPACR_CP10, (3 << 20) + .equ CPACR_CP11, (3 << 22) + .equ FPEXC_EN, (1 << 30) + + .equ SYS_WRITE0, 0x04 /* semihosting: write NUL-string */ + +/* HSR (Hyp Syndrome Register) exception classes. Every exception taken to + EL2 from EL1/EL0 arrives at the single Hyp Trap Entry vector, so the cause + must be decoded from HSR.EC (bits [31:26]). */ + + .equ HSR_EC_SHIFT, 26 + .equ HSR_EC_HVC, 0x12 /* HVC executed in AArch32 */ + +#ifdef TXM_MODULE_MANAGER + +/* The module manager owns three of the EL1 vectors in a manager build: the + supervisor call, which is the privilege boundary a module crosses to reach + the kernel, and the two aborts, which are how a module that reaches outside + its regions is caught. Declared rather than merely referenced so that a + build with the manager half-configured fails at the link with a name in it. */ + + .extern __tx_module_svc_interrupt + .extern _txm_module_manager_data_abort + .extern _txm_module_manager_prefetch_abort + +#endif + +/* Report which vector was taken, then halt. The FVP offers no GDB stub + (Iris only), so a self-identifying fault is the primary debugging tool + for this port. Uses no stack: only r0/r1 and a semihosting call. */ + + .macro FAULT_REPORT msglabel + ldr r1, =\msglabel + mov r0, #SYS_WRITE0 + hlt 0xf000 + b fault_halt + .endm + +/**************************************************************************/ +/* EL2 (Hyp) vector table. Must be 32-byte aligned for HVBAR. */ +/**************************************************************************/ + + .section .vectors_el2, "ax" + .balign 32 + .global el2_vectors +el2_vectors: +/* Offsets 0x04-0x10 are exceptions taken *from* Hyp mode itself. Offset + 0x14 is the Hyp Trap Entry: every exception routed to EL2 from EL1/EL0 -- + HVC, stage-2 faults, trapped register accesses -- funnels through it and + is decoded from HSR. That single funnel is the ZoneX (AR3) seam. */ + + b el2_trap_reset /* 0x00 reset (unused at EL2) */ + b el2_trap_undef /* 0x04 undef, from Hyp */ + b el2_trap_svc /* 0x08 SVC, from Hyp */ + b el2_trap_pabt /* 0x0C prefetch abort, Hyp */ + b el2_trap_dabt /* 0x10 data abort, from Hyp */ + b el2_hyp_trap_entry /* 0x14 from EL1/EL0 <-- seam */ + b el2_trap_irq /* 0x18 IRQ */ + b el2_trap_fiq /* 0x1C FIQ */ + +/**************************************************************************/ +/* EL1 vector table. Must be 32-byte aligned for VBAR. */ +/**************************************************************************/ + + .section .vectors_el1, "ax" + .balign 32 + .global el1_vectors +el1_vectors: + b el1_trap_reset /* 0x00 reset */ + b el1_trap_undef /* 0x04 undefined instruction */ +#ifdef TXM_MODULE_MANAGER + b __tx_module_svc_interrupt /* 0x08 module kernel boundary */ +#else + b el1_trap_svc /* 0x08 supervisor call */ +#endif + b el1_trap_pabt /* 0x0C prefetch abort */ + b el1_trap_dabt /* 0x10 data abort */ + b el1_trap_reserved /* 0x14 reserved */ + b el1_irq_entry /* 0x18 IRQ */ + b el1_trap_fiq /* 0x1C FIQ */ + + .text + .balign 4 + +/**************************************************************************/ +/* _start -- reset entry. Entered at EL2 (Hyp mode) on Cortex-R52. */ +/**************************************************************************/ + + .global _start + .type _start, %function +_start: +#ifndef TX_R52_BOOT_AT_EL1 + + /* Hyp-mode stack must exist before anything can be pushed at EL2. */ + + ldr sp, =__hyp_stack_top + + /* Install the EL2 vector table (HVBAR). */ + + ldr r0, =el2_vectors + mcr p15, 4, r0, c12, c0, 0 + isb + + /* Configure EL2: keep exceptions and interrupts routed to EL1, and + leave the stage-2 MPU disabled for AR1 (the FVP background memory + map already permits execution and data access in low DRAM). ZoneX + enables HCR_VM and programs stage 2 in AR3. + + HCR_HCD is cleared explicitly so HVC is enabled from EL1 rather than + relying on its reset value -- the ZoneX seam depends on it. */ + + mrc p15, 4, r0, c1, c1, 0 /* HCR */ + ldr r1, =(HCR_TGE | HCR_AMO | HCR_IMO | HCR_FMO | HCR_VM | HCR_HCD) + bic r0, r0, r1 + mcr p15, 4, r0, c1, c1, 0 + isb + + /* Let EL1 use the FPU: clear HCPTR.TCP11/TCP10. R52 always implements + at least a single-precision FPU, so these traps must be cleared even + for a soft-float kernel build if application code uses the FPU. */ + + mrc p15, 4, r0, c1, c1, 2 /* HCPTR */ + ldr r1, =HCPTR_TCP + bic r0, r0, r1 + mcr p15, 4, r0, c1, c1, 2 + isb + + /* Generic timer, part 1 of 2 (part 2 is timer_init at EL1). + + CNTFRQ resets to ZERO on this model and is writable only at the + highest implemented exception level, so it must be programmed here at + EL2. The value is CNTFID0 read back from the counter control frame. + Software that derived a tick interval from CNTFRQ without this would + divide by zero. */ + + ldr r0, =SYSTEM_COUNTER_HZ + mcr p15, 0, r0, c14, c0, 0 /* CNTFRQ */ + + /* Permit EL1/EL0 to use the physical counter and the physical timer. + CNTHCTL.PL1PCTEN/PL1PCEN reset disabled, so without this every EL1 + access to CNTPCT or CNTP_* would trap to EL2. */ + + mrc p15, 4, r0, c14, c1, 0 /* CNTHCTL */ + orr r0, r0, #(CNTHCTL_PL1PCTEN | CNTHCTL_PL1PCEN) + mcr p15, 4, r0, c14, c1, 0 + isb + + /* Permit EL1 to reach the GICv3 CPU interface through its system + registers (ICC_*), which is how the R52 acknowledges interrupts. */ + + mrc p15, 4, r0, c12, c9, 5 /* ICC_HSRE */ + orr r0, r0, #(ICC_HSRE_SRE | ICC_HSRE_ENABLE) + mcr p15, 4, r0, c12, c9, 5 + isb + + /* Progress marker: proves we reached EL2 and semihosting works. */ + + ldr r1, =msg_el2 + mov r0, #SYS_WRITE0 + hlt 0xf000 + + /* Drop to EL1 (Supervisor), interrupts still masked. */ + + ldr r0, =el1_entry + msr ELR_hyp, r0 +#ifdef TX_ENABLE_FIQ_SUPPORT + + /* FIQ left unmasked, IRQ still masked. ThreadX unmasks IRQ when it + * schedules the first thread; FIQ is different, because tx_thread_stack_build + * only clears the F bit in a thread's CPSR when the library was built with + * FIQ support, and nothing else ever clears it. Leaving PSR_F set here + * would mask FIQ until the first thread ran, which is survivable but makes + * an FIQ raised from bsp_main silently do nothing. + */ + + ldr r0, =(PSR_A | PSR_I | MODE_SVC) +#else + ldr r0, =(PSR_A | PSR_I | PSR_F | MODE_SVC) +#endif + msr SPSR_hyp, r0 + isb + eret + +#else + b el1_entry +#endif + +/**************************************************************************/ +/* el1_entry -- first instruction executed at EL1. */ +/**************************************************************************/ + + .global el1_entry + .type el1_entry, %function +el1_entry: + + /* Install the EL1 vector table (VBAR). */ + + ldr r0, =el1_vectors + mcr p15, 0, r0, c12, c0, 0 + isb + + /* Give every EL1 mode its own stack. Interrupts stay masked: CPS + changes mode only. System mode shares SP_usr, so it is set last + before returning to Supervisor mode. */ + + cps #MODE_IRQ + ldr sp, =__irq_stack_top + cps #MODE_FIQ + ldr sp, =__fiq_stack_top + cps #MODE_ABT + ldr sp, =__abt_stack_top + cps #MODE_UND + ldr sp, =__und_stack_top + cps #MODE_SYS + ldr sp, =__sys_stack_top + cps #MODE_SVC + ldr sp, =__svc_stack_top + +#ifdef __ARM_FP + + /* Enable the floating-point unit for EL1. + * + * Two separate gates must be opened and both reset closed: + * CPACR grants EL1/EL0 access to CP10/CP11. It reads 0 out of reset, + * so the first floating-point instruction would otherwise raise + * an Undefined Instruction exception. + * FPEXC .EN enables floating-point execution itself. + * EL2 has already cleared HCPTR.TCP10/TCP11 so neither access traps to EL2. + * + * This is a BSP responsibility, not the kernel's: tx_thread_vfp_enable() + * only sets the per-thread software flag that makes the context switch + * save and restore floating-point state. It does not turn the hardware + * on, so without the code below a VFP-enabled build faults on its first + * context switch that touches D-registers. + * + * Guarded by __ARM_FP, which the compiler defines exactly when the build + * can emit floating-point instructions. In a soft-float build "vmsr + * fpexc" is not even assemblable for this target. + */ + + mrc p15, 0, r0, c1, c0, 2 /* CPACR */ + orr r0, r0, #(CPACR_CP10 | CPACR_CP11) + mcr p15, 0, r0, c1, c0, 2 + isb + + mov r0, #FPEXC_EN + vmsr fpexc, r0 + isb + +#endif + + /* Zero .bss before any C code runs. */ + + ldr r0, =__bss_start__ + ldr r1, =__bss_end__ + mov r2, #0 +bss_zero_loop: + cmp r0, r1 + bhs bss_zero_done + str r2, [r0], #4 + b bss_zero_loop +bss_zero_done: + +#ifdef TX_R52_ENABLE_MPU + + /* Protection and caches on before any application code runs. This is + after .bss is cleared because mpu_init keeps its region table there. */ + + bl mpu_init + +#endif + + bl bsp_main + + /* bsp_main is not expected to return. */ +_start_hang: + b _start_hang + +/**************************************************************************/ +/* el1_irq_entry -- IRQ vector. */ +/* */ +/* With ThreadX linked in, the IRQ vector branches straight into */ +/* _tx_thread_context_save, which saves the interrupted context and */ +/* branches back to __tx_irq_processing_return. The branch must be a */ +/* plain B: context save adjusts lr itself to find the point of */ +/* interrupt. Interrupts are still disabled on return from it. */ +/* */ +/* Images that do not link ThreadX (the M1 boot check) keep the */ +/* self-identifying fault reporter instead. */ +/**************************************************************************/ + + .global el1_irq_entry + .type el1_irq_entry, %function +el1_irq_entry: +#ifdef TX_R52_USE_THREADX_IRQ + b _tx_thread_context_save + + .global __tx_irq_processing_return +__tx_irq_processing_return: +#ifdef TX_ENABLE_IRQ_NESTING + + /* Nested IRQ handling. _tx_thread_irq_nesting_start leaves IRQ mode for + * System mode and re-enables IRQ, so an interrupt of higher priority can + * preempt this handler and re-enter at el1_irq_entry above. It is paired + * with _tx_thread_irq_nesting_end before _tx_thread_context_restore. + * + * The ORDER here is the whole difficulty, and getting it wrong does not + * fail gracefully. The interrupt must be ACKNOWLEDGED BEFORE nesting is + * started. Reading ICC_IAR1 is what raises the GIC's running priority to + * this interrupt's own, which is what masks it and everything of equal or + * lower priority; only then is re-enabling IRQ safe. Acknowledge after + * nesting_start instead and the still-pending, still-level-asserted timer + * is taken again the instant IRQ is enabled, and again, until the IRQ and + * System stacks are destroyed. That presents as garbage on the console and + * a hang, with no fault to point at it. The Cortex-R5 example BSP says the + * same thing in one line: "ensure all IRQ interrupts are cleared prior to + * enabling nested IRQ interrupts". + * + * The INTID is carried in r4, which survives the mode switch because only + * SP and LR are banked, and is also pushed on the IRQ stack so a nested + * level that reuses r4 cannot lose this level's value. End-of-interrupt is + * left until after nesting_end, in IRQ mode with interrupts masked, so + * dropping the running priority cannot re-admit this same interrupt. + */ + + bl gicv3_acknowledge /* IRQ mode, interrupts masked */ + mov r4, r0 + stmdb sp!, {r4, r5} /* r5 keeps 8-byte alignment */ + + bl _tx_thread_irq_nesting_start + mov r0, r4 + bl board_irq_service + bl _tx_thread_irq_nesting_end + + ldmia sp!, {r4, r5} + mov r0, r4 + bl gicv3_end_of_interrupt +#else + bl board_irq_handler +#endif + b _tx_thread_context_restore +#else + FAULT_REPORT msg_el1_irq +#endif + +/**************************************************************************/ +/* el2_hyp_trap_entry -- the ZoneX (AR3) seam. */ +/* */ +/* Reached for every exception routed to EL2 from EL1/EL0, so the cause */ +/* is decoded from HSR.EC before dispatch. AR1 handles only HVC, which */ +/* it counts before returning to EL1; that proves EL2 is live and */ +/* reachable without the boot path changing later. AR3 grows the */ +/* dispatch table here (stage-2 faults, trapped accesses, IRQ paravirt). */ +/* */ +/* EL1's registers must be preserved -- this runs on the Hyp stack. */ +/**************************************************************************/ + + .global el2_hyp_trap_entry + .type el2_hyp_trap_entry, %function +el2_hyp_trap_entry: + push {r0, r1} + mrc p15, 4, r0, c5, c2, 0 /* HSR */ + lsr r1, r0, #HSR_EC_SHIFT + cmp r1, #HSR_EC_HVC + bne el2_hyp_trap_unhandled + + /* HVC from EL1: AR1 placeholder for the ZoneX hypercall dispatch. */ + + ldr r0, =_hvc_call_count + ldr r1, [r0] + add r1, r1, #1 + str r1, [r0] + pop {r0, r1} + eret + +el2_hyp_trap_unhandled: + pop {r0, r1} + FAULT_REPORT msg_el2_hyp + +/**************************************************************************/ +/* Unhandled exceptions. Each records a distinct code in r12 so a halted*/ +/* model or a later fault dump (AR1/M5) can identify the cause. Real */ +/* handlers arrive with the interrupt work in M3. */ +/**************************************************************************/ + +/* Unhandled exceptions. Real handlers arrive with the interrupt work in + AR1/M3; until then each vector identifies itself and halts. */ + +el2_trap_reset: FAULT_REPORT msg_el2_reset +el2_trap_undef: FAULT_REPORT msg_el2_undef +el2_trap_svc: FAULT_REPORT msg_el2_svc +el2_trap_pabt: FAULT_REPORT msg_el2_pabt +el2_trap_dabt: FAULT_REPORT msg_el2_dabt +el2_trap_irq: FAULT_REPORT msg_el2_irq +el2_trap_fiq: FAULT_REPORT msg_el2_fiq + +el1_trap_reset: FAULT_REPORT msg_el1_reset +el1_trap_undef: FAULT_REPORT msg_el1_undef +el1_trap_svc: FAULT_REPORT msg_el1_svc +/* Prefetch abort. Fatal, except during the MPU self-test's execute-never + check. This one cannot recover the way the data-abort path does: skipping + "the faulting instruction" is impossible when the instruction is precisely + what could not be fetched. So mpu_try_execute below records where to land + and this handler returns there, restoring the pre-fault mode from SPSR. */ + +el1_trap_pabt: +#ifdef TXM_MODULE_MANAGER + + /* A fault from User mode is a module reaching outside its code region, and + it belongs to the module manager, which records it and terminates the + thread. A fault from a privileged mode falls through to the paths below, + which the MPU self-test depends on. + + r0 is pushed and popped around the test rather than simply used: the + capture in the manager records the faulting registers, and clobbering one + here would have it record ours instead. LDM does not affect the flags, + so the comparison still holds after the pop. */ + + stmdb sp!, {r0} + mrs r0, spsr + and r0, r0, #0x1F + cmp r0, #0x10 /* User mode? */ + ldmia sp!, {r0} + beq _txm_module_manager_prefetch_abort + +#endif +#ifdef TX_R52_MPU_FAULT_TEST + push {r0, r1} + ldr r0, =mpu_expect_pabt + ldr r1, [r0] + cmp r1, #0 + beq el1_pabt_fatal + mov r1, #0 + str r1, [r0] /* consume the expectation */ + ldr r0, =mpu_pabt_count + ldr r1, [r0] + add r1, r1, #1 + str r1, [r0] + dsb + ldr r0, =mpu_abort_return + ldr r1, [r0] + mov lr, r1 /* land point, set before pop */ + pop {r0, r1} + subs pc, lr, #0 /* return, restoring CPSR */ +el1_pabt_fatal: + pop {r0, r1} +#endif + FAULT_REPORT msg_el1_pabt + + +#ifdef TX_R52_MPU_FAULT_TEST +/* mpu_try_execute(void (*target)(void)) + Call target and return normally if the fetch is permitted. If it is not, + el1_trap_pabt returns to the same place, so the caller continues either way + and can tell the two apart by mpu_pabt_count. SP is untouched by the + faulting branch, so the saved LR is still on the stack when we land. */ + + .global mpu_try_execute + .type mpu_try_execute, %function +mpu_try_execute: + push {r4, lr} + ldr r1, =mpu_abort_return + adr r2, mpu_try_execute_land + str r2, [r1] + dsb + isb + blx r0 +mpu_try_execute_land: + pop {r4, lr} + bx lr + .size mpu_try_execute, . - mpu_try_execute +#endif + +/* Data abort. Normally fatal and self-reporting, but the MPU self-test + needs to provoke a permission fault and carry on: without that, "the MPU + is enabled" is an unverified claim. When mpu_expect_abort is non-zero the + fault is counted and execution resumes at the instruction *after* the one + that faulted. On a data abort LR_abt holds the faulting address plus 8, + so returning to LR_abt-4 skips it, where LR_abt-8 would retry it forever. + Runs on the Abort-mode stack set up above. */ + +el1_trap_dabt: +#ifdef TXM_MODULE_MANAGER + + /* A fault from User mode is a module reaching outside its data region. See + el1_trap_pabt above for why r0 is saved across the test. */ + + stmdb sp!, {r0} + mrs r0, spsr + and r0, r0, #0x1F + cmp r0, #0x10 /* User mode? */ + ldmia sp!, {r0} + beq _txm_module_manager_data_abort + +#endif +#ifdef TX_R52_MPU_FAULT_TEST + push {r0, r1} + ldr r0, =mpu_expect_abort + ldr r1, [r0] + cmp r1, #0 + beq el1_dabt_fatal + mov r1, #0 + str r1, [r0] /* consume the expectation */ + ldr r0, =mpu_abort_count + ldr r1, [r0] + add r1, r1, #1 + str r1, [r0] + dsb + pop {r0, r1} + subs pc, lr, #4 /* resume after the fault */ +el1_dabt_fatal: + pop {r0, r1} +#endif + FAULT_REPORT msg_el1_dabt +el1_trap_reserved: FAULT_REPORT msg_el1_reserved +el1_trap_fiq: +#if defined(TX_ENABLE_FIQ_SUPPORT) && defined(TX_R52_USE_THREADX_IRQ) + + /* Both guards are needed, and the second is not obvious. + * TX_ENABLE_FIQ_SUPPORT is PUBLIC on the threadx target, so it reaches every + * image the moment the library is built with FIQ, including images that link + * no interrupt controller at all. demo_m2 is one: it has no gicv3.c and no + * irq_dispatch.c, so referencing gicv3_acknowledge_group0 and + * board_fiq_service from here broke its link outright. Images without the + * interrupt infrastructure keep the fault reporter on this vector, which is + * how the IRQ vector above is gated too. + */ + + /* FIQ has its own context save and restore in the port, and its own nesting + * pair. The shape mirrors the IRQ path, and so does the trap: the interrupt + * must be acknowledged before nesting starts. Group 0 has its own + * acknowledge and end-of-interrupt registers -- using the Group 1 ones here + * returns the spurious INTID and leaves the interrupt pending, which + * presents as an FIQ storm rather than as an error. + * + * FIQ mode banks r8-r12 as well as SP and LR, so the INTID cannot ride in a + * high register across the switch to System mode the way r4 does on the IRQ + * path. r4 is not banked in FIQ mode either, so it still works, and it is + * pushed on the FIQ stack as well so a nested level cannot lose it. + */ + + b _tx_thread_fiq_context_save + + .global __tx_fiq_processing_return +__tx_fiq_processing_return: +#ifdef TX_ENABLE_FIQ_NESTING + bl gicv3_acknowledge_group0 + mov r4, r0 + stmdb sp!, {r4, r5} + + bl _tx_thread_fiq_nesting_start + mov r0, r4 + bl board_fiq_service + bl _tx_thread_fiq_nesting_end + + ldmia sp!, {r4, r5} + mov r0, r4 + bl gicv3_end_of_interrupt_group0 +#else + bl board_fiq_handler +#endif + b _tx_thread_fiq_context_restore +#else + FAULT_REPORT msg_el1_fiq +#endif + +fault_halt: b fault_halt + + .section .rodata + .balign 4 +msg_el2: + .asciz "[EL2] reset entry reached; configuring EL2 and dropping to EL1\n" + +msg_el2_reset: .asciz "[FAULT] EL2 reset vector\n" +msg_el2_undef: .asciz "[FAULT] EL2 undefined instruction\n" +msg_el2_svc: .asciz "[FAULT] EL2 supervisor call from Hyp mode\n" +msg_el2_pabt: .asciz "[FAULT] EL2 prefetch abort\n" +msg_el2_dabt: .asciz "[FAULT] EL2 data abort\n" +msg_el2_hyp: .asciz "[FAULT] EL2 hyp trap with unhandled HSR.EC\n" +msg_el2_irq: .asciz "[FAULT] EL2 IRQ (unexpected: IRQs route to EL1)\n" +msg_el2_fiq: .asciz "[FAULT] EL2 FIQ (unexpected: FIQs route to EL1)\n" + +msg_el1_reset: .asciz "[FAULT] EL1 reset vector\n" +msg_el1_undef: .asciz "[FAULT] EL1 undefined instruction\n" +msg_el1_svc: .asciz "[FAULT] EL1 supervisor call (no handler yet)\n" +msg_el1_pabt: .asciz "[FAULT] EL1 prefetch abort\n" +msg_el1_dabt: .asciz "[FAULT] EL1 data abort\n" +msg_el1_reserved: .asciz "[FAULT] EL1 reserved vector\n" +msg_el1_irq: .asciz "[FAULT] EL1 IRQ (no handler yet)\n" +msg_el1_fiq: .asciz "[FAULT] EL1 FIQ (no handler yet)\n" + + .section .data + .balign 4 + .global _hvc_call_count +_hvc_call_count: + .word 0 + +/* Used by the MPU enforcement self-test (see el1_trap_dabt). */ + + .global mpu_expect_abort +mpu_expect_abort: + .word 0 + + .global mpu_abort_count +mpu_abort_count: + .word 0 + +/* Used by the execute-never self-test (see el1_trap_pabt). */ + + .global mpu_expect_pabt +mpu_expect_pabt: + .word 0 + + .global mpu_pabt_count +mpu_pabt_count: + .word 0 + + .global mpu_abort_return +mpu_abort_return: + .word 0 diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/gicv3.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/gicv3.c new file mode 100644 index 00000000..4771625d --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/gicv3.c @@ -0,0 +1,360 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* gicv3.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* GICv3 bring-up for Cortex-R52 on the Armv8-R AEM FVP. */ +/* */ +/* Model characteristics that shape this code, taken from its own */ +/* parameters rather than assumed: */ +/* */ +/* has-two-security-states=0 single security state, so GICD_CTLR.DS */ +/* reads 1 and the Group 1 enable is bit 1*/ +/* ARE-fixed-to-one=1 affinity routing cannot be turned off */ +/* priority-bits=5 only the top 5 priority bits exist, so */ +/* priorities must be multiples of 8 */ +/* */ +/* MISRA C:2012 deviations (justified) */ +/* */ +/* Directive 4.3 -- the CPU interface is only reachable through CP15 */ +/* system registers, so inline assembly is unavoidable. Every such */ +/* access is encapsulated in a one-line accessor below and appears */ +/* nowhere else. */ +/* Rule 11.4/11.6 -- casting integer addresses to volatile pointers is */ +/* inherent to memory-mapped device access; confined to REG32. */ +/* */ +/**************************************************************************/ + +#include "platform.h" +#include "gicv3.h" + +/* Distributor registers. */ + +#define GICD_CTLR 0x0000U +#define GICD_CTLR_ENABLE_GRP0 (1UL << 0) +#define GICD_CTLR_ENABLE_GRP1 (1UL << 1) +#define GICD_CTLR_ARE (1UL << 4) + +/* Redistributor RD frame. */ + +#define GICR_WAKER 0x0014U +#define GICR_WAKER_PROC_SLEEP (1UL << 1) +#define GICR_WAKER_CHILD_ASLEEP (1UL << 2) + +/* Redistributor SGI frame (SGIs and PPIs, INTID 0-31). */ + +#define GICR_IGROUPR0 0x0080U +#define GICR_ISENABLER0 0x0100U +#define GICR_IPRIORITYR 0x0400U +#define GICR_ICFGR1 0x0C04U + +#define ICC_PMR_UNMASK_ALL 0xFFUL +#define ICC_SRE_SRE (1UL << 0) +#define ICC_IGRPEN1_ENABLE (1UL << 0) + +#define PPI_FIRST_INTID 16U + + +/**************************************************************************/ +/* CPU interface accessors (AArch32 ICC_* system registers). */ +/**************************************************************************/ + +static unsigned long read_icc_sre(void) +{ + unsigned long value; + __asm__ volatile("mrc p15, 0, %0, c12, c12, 5" : "=r"(value)); + return value; +} + +static void write_icc_sre(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c12, c12, 5" : : "r"(value) : "memory"); +} + +static void write_icc_pmr(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c4, c6, 0" : : "r"(value) : "memory"); +} + +static void write_icc_bpr1(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c12, c12, 3" : : "r"(value) : "memory"); +} + +static void write_icc_igrpen0(unsigned long value) +{ + /* ICC_IGRPEN0 sits at c12, c12, 6, beside IGRPEN1 at opc2 7. */ + + __asm__ volatile("mcr p15, 0, %0, c12, c12, 6" : : "r"(value) : "memory"); +} + +static void write_icc_bpr0(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c12, c8, 3" : : "r"(value) : "memory"); +} + +static void write_icc_igrpen1(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c12, c12, 7" : : "r"(value) : "memory"); +} + +static void instruction_barrier(void) +{ + __asm__ volatile("isb" : : : "memory"); +} + + +/**************************************************************************/ +/* gicv3_init */ +/**************************************************************************/ + +void gicv3_init(void) +{ + unsigned long waker; + + /* Distributor: affinity routing first, then the Group 1 enable. With a + single security state Group 1 is bit 1 of GICD_CTLR. */ + + REG32(GICD_BASE + GICD_CTLR) |= GICD_CTLR_ARE; + REG32(GICD_BASE + GICD_CTLR) |= GICD_CTLR_ENABLE_GRP1; + + /* Group 0 as well, which is what the GIC delivers as FIQ. Enabling it costs + nothing while no interrupt is assigned to that group, and images that never + use FIQ see no change: assignment is per interrupt, in GICR_IGROUPR0. */ + + REG32(GICD_BASE + GICD_CTLR) |= GICD_CTLR_ENABLE_GRP0; + + /* Redistributor: clear ProcessorSleep and wait for the redistributor to + report that its children are awake, otherwise no interrupt can be + delivered to this core. */ + + waker = REG32(GICR_RD_BASE + GICR_WAKER); + waker &= ~GICR_WAKER_PROC_SLEEP; + REG32(GICR_RD_BASE + GICR_WAKER) = waker; + + while ((REG32(GICR_RD_BASE + GICR_WAKER) & GICR_WAKER_CHILD_ASLEEP) != 0UL) + { + /* Wait for the redistributor to wake. */ + } + + /* CPU interface: system-register access, then unmask all priorities and + enable Group 1. EL2 has already permitted EL1 system-register access + via ICC_HSRE in entry.S. */ + + write_icc_sre(read_icc_sre() | ICC_SRE_SRE); + instruction_barrier(); + + write_icc_pmr(ICC_PMR_UNMASK_ALL); + write_icc_bpr1(0UL); + write_icc_igrpen1(ICC_IGRPEN1_ENABLE); + + /* The CPU interface half of Group 0: a Group 0 interrupt reaches the core + only if both the distributor and this are enabled. BPR0 at zero gives no + subpriority grouping, so priority alone decides preemption, which is what + the FIQ nesting test depends on. */ + + write_icc_bpr0(0UL); + write_icc_igrpen0(ICC_IGRPEN1_ENABLE); + instruction_barrier(); +} + + +/**************************************************************************/ +/* gicv3_enable_ppi */ +/**************************************************************************/ + +void gicv3_enable_ppi(unsigned int intid, unsigned int priority) +{ + unsigned long shift; + + if ((intid < PPI_FIRST_INTID) || (intid > 31U)) + { + return; + } + + /* Group 1. */ + + REG32(GICR_SGI_BASE + GICR_IGROUPR0) |= (1UL << intid); + + /* Priority is a byte per INTID. Only the top 5 bits are implemented on + this model, so callers pass multiples of 8. */ + + REG32(GICR_SGI_BASE + GICR_IPRIORITYR + (intid & ~3U)) &= + ~(0xFFUL << ((intid & 3U) * 8U)); + REG32(GICR_SGI_BASE + GICR_IPRIORITYR + (intid & ~3U)) |= + ((unsigned long) priority & 0xFFUL) << ((intid & 3U) * 8U); + + /* Level-sensitive: two configuration bits per INTID, 00 = level. The + generic timer asserts a level, not an edge. */ + + shift = ((unsigned long) intid - PPI_FIRST_INTID) * 2UL; + REG32(GICR_SGI_BASE + GICR_ICFGR1) &= ~(3UL << shift); + + /* Enable. */ + + REG32(GICR_SGI_BASE + GICR_ISENABLER0) = (1UL << intid); +} + + +/**************************************************************************/ +/* gicv3_acknowledge */ +/**************************************************************************/ + +/**************************************************************************/ +/* gicv3_enable_sgi */ +/* */ +/* An SGI lives in the same redistributor frame as a PPI, so this is */ +/* gicv3_enable_ppi without the ICFGR step: INTIDs 0-15 have no */ +/* configurable edge/level, they are always edge-triggered. */ +/**************************************************************************/ + +void gicv3_enable_sgi(unsigned int intid, unsigned int priority) +{ + if (intid > 15U) + { + return; + } + + REG32(GICR_SGI_BASE + GICR_IGROUPR0) |= (1UL << intid); + + REG32(GICR_SGI_BASE + GICR_IPRIORITYR + (intid & ~3U)) &= + ~(0xFFUL << ((intid & 3U) * 8U)); + REG32(GICR_SGI_BASE + GICR_IPRIORITYR + (intid & ~3U)) |= + ((unsigned long) priority & 0xFFUL) << ((intid & 3U) * 8U); + + REG32(GICR_SGI_BASE + GICR_ISENABLER0) = (1UL << intid); +} + + +/**************************************************************************/ +/* gicv3_send_sgi */ +/* */ +/* ICC_SGI1R is 64-bit, so in AArch32 it is an MCRR rather than an MCR. */ +/* Fields: INTID in [27:24], TargetList in [15:0], Aff1/2/3 and IRM zero */ +/* for this single-core configuration, so TargetList = 1 selects core 0. */ +/* */ +/* The AArch64 name for this register is S3_0_C12_C11_5, which the */ +/* Cortex-A72 example uses, but that encoding does not transcribe to the */ +/* AArch32 64-bit CP15 space. The CRm here was confirmed by observing */ +/* that the SGI is actually delivered and acknowledged with the expected */ +/* INTID rather than by reading it off the A-profile alias. */ +/**************************************************************************/ + +void gicv3_send_sgi(unsigned int intid) +{ + unsigned long low = (((unsigned long) intid & 0xFUL) << 24) | 1UL; + unsigned long high = 0UL; + + __asm volatile ("mcrr p15, 0, %0, %1, c12" :: "r" (low), "r" (high) : "memory"); + instruction_barrier(); +} + + +/**************************************************************************/ +/* gicv3_enable_sgi_group0 */ +/* */ +/* As gicv3_enable_sgi, but leaves the interrupt in Group 0 so it arrives */ +/* as an FIQ. The group is chosen by clearing the GICR_IGROUPR0 bit; the */ +/* Group 1 version sets it. */ +/**************************************************************************/ + +void gicv3_enable_sgi_group0(unsigned int intid, unsigned int priority) +{ + if (intid > 15U) + { + return; + } + + REG32(GICR_SGI_BASE + GICR_IGROUPR0) &= ~(1UL << intid); + + REG32(GICR_SGI_BASE + GICR_IPRIORITYR + (intid & ~3U)) &= + ~(0xFFUL << ((intid & 3U) * 8U)); + REG32(GICR_SGI_BASE + GICR_IPRIORITYR + (intid & ~3U)) |= + ((unsigned long) priority & 0xFFUL) << ((intid & 3U) * 8U); + + REG32(GICR_SGI_BASE + GICR_ISENABLER0) = (1UL << intid); +} + + +/**************************************************************************/ +/* gicv3_send_sgi_group0 */ +/* */ +/* ICC_SGI0R rather than ICC_SGI1R: the two differ only in opc1, 2 against */ +/* 0, and each raises the SGI into its own group. Raising a Group 0 */ +/* interrupt through the Group 1 register does not deliver it as an FIQ. */ +/**************************************************************************/ + +void gicv3_send_sgi_group0(unsigned int intid) +{ + unsigned long low = (((unsigned long) intid & 0xFUL) << 24) | 1UL; + unsigned long high = 0UL; + + __asm volatile ("mcrr p15, 2, %0, %1, c12" :: "r" (low), "r" (high) : "memory"); + instruction_barrier(); +} + + +/**************************************************************************/ +/* gicv3_acknowledge_group0 / gicv3_end_of_interrupt_group0 */ +/* */ +/* Group 0 has its own pair at c12, c8, where Group 1 uses c12, c12. */ +/* Acknowledging a Group 0 interrupt through IAR1 returns the spurious */ +/* INTID and leaves the interrupt pending, which presents as an FIQ storm. */ +/**************************************************************************/ + +unsigned long gicv3_acknowledge_group0(void) +{ + unsigned long intid; + + __asm__ volatile("mrc p15, 0, %0, c12, c8, 0" : "=r"(intid)); + return intid & 0xFFFFFFUL; +} + + +void gicv3_end_of_interrupt_group0(unsigned long intid) +{ + instruction_barrier(); + __asm__ volatile("mcr p15, 0, %0, c12, c8, 1" : : "r"(intid) : "memory"); +} + + +unsigned long gicv3_acknowledge(void) +{ + unsigned long intid; + + __asm__ volatile("mrc p15, 0, %0, c12, c12, 0" : "=r"(intid)); + + return intid; +} + + +/**************************************************************************/ +/* gicv3_end_of_interrupt */ +/**************************************************************************/ + +void gicv3_end_of_interrupt(unsigned long intid) +{ + __asm__ volatile("mcr p15, 0, %0, c12, c12, 1" : : "r"(intid) : "memory"); +} diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/gicv3.h b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/gicv3.h new file mode 100644 index 00000000..1a998019 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/gicv3.h @@ -0,0 +1,81 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* gicv3.h Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* GICv3 interrupt controller for Cortex-R52. The CPU interface is */ +/* reached through AArch32 ICC_* system registers; the Distributor and */ +/* Redistributor are memory mapped. */ +/* */ +/**************************************************************************/ + +#ifndef GICV3_H +#define GICV3_H + +/* Spurious interrupt ID returned by the CPU interface when no interrupt is + pending. It must be neither dispatched nor acknowledged with EOI. */ + +#define GICV3_SPURIOUS_INTID 1023UL + +/* Bring up the Distributor, this core's Redistributor and the CPU + interface. Interrupts remain masked at the PSTATE level. */ + +void gicv3_init(void); + +/* Enable one private peripheral interrupt (PPI, INTID 16-31) as a + level-sensitive Group 1 interrupt at the given priority. */ + +void gicv3_enable_ppi(unsigned int intid, unsigned int priority); + +/* Enable a software-generated interrupt, INTID 0-15. Same registers as a PPI: + the redistributor's SGI/PPI frame covers INTIDs 0-31. */ + +void gicv3_enable_sgi(unsigned int intid, unsigned int priority); + +/* Raise an SGI on this core. */ + +void gicv3_send_sgi(unsigned int intid); + +/* Group 0 support, which is what produces FIQ rather than IRQ. With a single + security state the GIC routes Group 0 to FIQ and Group 1 to IRQ, so an + interrupt has to be moved into Group 0 to arrive as an FIQ at all. Group 0 + has its own acknowledge and end-of-interrupt registers; using the Group 1 ones + on a Group 0 interrupt does not work. */ + +void gicv3_enable_sgi_group0(unsigned int intid, unsigned int priority); +void gicv3_send_sgi_group0(unsigned int intid); +unsigned long gicv3_acknowledge_group0(void); +void gicv3_end_of_interrupt_group0(unsigned long intid); + +/* Acknowledge the highest-priority pending Group 1 interrupt, returning its + INTID (possibly GICV3_SPURIOUS_INTID). */ + +unsigned long gicv3_acknowledge(void); + +/* Signal completion of the interrupt previously acknowledged. */ + +void gicv3_end_of_interrupt(unsigned long intid); + +#endif /* GICV3_H */ diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/irq_dispatch.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/irq_dispatch.c new file mode 100644 index 00000000..f244c3d7 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/irq_dispatch.c @@ -0,0 +1,306 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* irq_dispatch.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Interrupt dispatch for Cortex-R52 on the Armv8-R AEM FVP. Called */ +/* from __tx_irq_processing_return in entry.S, that is after */ +/* _tx_thread_context_save has saved the interrupted context and with */ +/* interrupts still disabled. */ +/* */ +/* _tx_timer_interrupt is safe to call from C: it uses only r0-r3 and */ +/* returns through lr like an ordinary function. */ +/* */ +/**************************************************************************/ + +#include "board.h" +#include "gicv3.h" +#include "timer.h" + +/* ThreadX periodic timer entry point (assembly, AAPCS-compatible). */ + +extern void _tx_timer_interrupt(void); + +/* Observability for the M3 checks. board_timer_intid records the INTID that + actually arrived -- this is how the timer PPI was identified during bring-up + (see TIMER_PPI_INTID) and it keeps reporting it, so a platform that assigns + the timer elsewhere shows up as an unexpected INTID rather than a silent + absence of ticks. */ + +volatile unsigned long board_irq_count; +volatile unsigned long board_timer_intid; +volatile unsigned long board_spurious_count; +volatile unsigned long board_unexpected_intid; + +volatile unsigned long board_nest_depth; +volatile unsigned long board_nest_max; +volatile unsigned long board_sgi_count; +volatile unsigned long board_sgi_nested_count; +volatile unsigned long board_nest_provoke; + +volatile unsigned long board_fiq_count; +volatile unsigned long board_fiq_high_count; +volatile unsigned long board_fiq_nested_count; +volatile unsigned long board_fiq_depth; +volatile unsigned long board_fiq_max_depth; +volatile unsigned long board_fiq_unexpected; +volatile unsigned long board_fiq_provoke; + + +/* Timer PPI priority. Only the top 5 priority bits are implemented on this + model, so the value is a multiple of 8. */ + +#define TIMER_PPI_PRIORITY 0xA0U + + +/**************************************************************************/ +/* board_init */ +/* */ +/* Called from _tx_initialize_low_level. Interrupts are still masked at */ +/* this point, so the tick cannot be delivered before the kernel is */ +/* ready for it. */ +/**************************************************************************/ + +void board_init(void) +{ + gicv3_init(); + gicv3_enable_ppi(TIMER_PPI_INTID, TIMER_PPI_PRIORITY); + + /* The nesting SGI must outrank the timer or it could never preempt it: + in the GIC a numerically lower priority value wins. Enabling it costs + nothing in images that never raise it. */ + + gicv3_enable_sgi(BOARD_NEST_SGI_INTID, TIMER_PPI_PRIORITY / 2U); + +#ifdef TX_ENABLE_FIQ_SUPPORT + + /* Two Group 0 SGIs for the FIQ path. Group 0 is what the GIC delivers as + FIQ; the high one is given a numerically lower priority so it can preempt + the low one, which is what FIQ nesting means. Both sit above the timer so + the two paths do not interfere. */ + + gicv3_enable_sgi_group0(BOARD_FIQ_LOW_INTID, 0x40U); + gicv3_enable_sgi_group0(BOARD_FIQ_HIGH_INTID, 0x20U); +#endif + + timer_init(); +} + + +/**************************************************************************/ +/* board_irq_handler */ +/**************************************************************************/ + +/**************************************************************************/ +/* board_irq_service -- service an already-acknowledged INTID. */ +/* */ +/* Split out so the nesting path in entry.S can acknowledge in IRQ mode */ +/* before nesting starts. Does not acknowledge and does not EOI. */ +/**************************************************************************/ + +void board_irq_service(unsigned long intid) +{ + if (intid == GICV3_SPURIOUS_INTID) + { + /* No interrupt was actually pending; must not be acknowledged. */ + + board_spurious_count++; + return; + } + + /* Depth is incremented after the spurious check so a spurious entry does + not register as nesting. It reaches 2 only if a second interrupt is + taken while this handler is still on the stack, which cannot happen + without TX_ENABLE_IRQ_NESTING. */ + + board_nest_depth++; + if (board_nest_depth > board_nest_max) + { + board_nest_max = board_nest_depth; + } + + if (intid == (unsigned long) TIMER_PPI_INTID) + { + board_timer_intid = intid; + board_irq_count++; + + /* Re-arm before servicing so the next interval starts immediately; + this also deasserts the level-sensitive timer output. */ + + timer_reload(); + + /* Provoke the nesting, if this image asked for it. Raising the SGI + here is only useful because _tx_thread_irq_nesting_start has already + re-enabled IRQ; the bounded spin gives the GIC the chance to deliver + it before this handler returns, so the nesting is deterministic + rather than a race we hope wins. The bound matters: without + TX_ENABLE_IRQ_NESTING the SGI can never arrive and this must not + become a hang. */ + + if (board_nest_provoke != 0UL) + { + unsigned long seen = board_sgi_count; + unsigned long guard; + + gicv3_send_sgi(BOARD_NEST_SGI_INTID); + + for (guard = 0UL; guard < 100000UL; guard++) + { + if (board_sgi_count != seen) + { + break; + } + } + } + + _tx_timer_interrupt(); + } + else if (intid == (unsigned long) BOARD_NEST_SGI_INTID) + { + board_sgi_count++; + + /* Depth above 1 means this SGI arrived inside another handler, which + is the whole point of the test. */ + + if (board_nest_depth > 1UL) + { + board_sgi_nested_count++; + } + } + else + { + board_unexpected_intid = intid; + } + + board_nest_depth--; +} + + +/**************************************************************************/ +/* board_irq_handler -- the non-nesting entry point. */ +/* */ +/* Acknowledges, services and ends the interrupt, all in IRQ mode with */ +/* interrupts masked. entry.S calls this when the image was built without */ +/* TX_ENABLE_IRQ_NESTING, so the behaviour of every existing image is */ +/* exactly what it was. */ +/**************************************************************************/ + +void board_irq_handler(void) +{ + unsigned long intid = gicv3_acknowledge(); + + board_irq_service(intid); + + if (intid != GICV3_SPURIOUS_INTID) + { + gicv3_end_of_interrupt(intid); + } +} + + +#ifdef TX_ENABLE_FIQ_SUPPORT +/**************************************************************************/ +/* board_fiq_service -- service an already-acknowledged Group 0 INTID. */ +/* */ +/* The FIQ counterpart of board_irq_service, and split for the same */ +/* reason: the acknowledge has to happen before nesting starts. Does not */ +/* acknowledge and does not EOI. */ +/**************************************************************************/ + +void board_fiq_service(unsigned long intid) +{ + if (intid == GICV3_SPURIOUS_INTID) + { + return; + } + + board_fiq_depth++; + if (board_fiq_depth > board_fiq_max_depth) + { + board_fiq_max_depth = board_fiq_depth; + } + + if (intid == (unsigned long) BOARD_FIQ_LOW_INTID) + { + board_fiq_count++; + + /* Provoke the nested FIQ from inside this handler. Only reachable + because _tx_thread_fiq_nesting_start has re-enabled FIQ; the bounded + spin lets the GIC deliver the higher-priority Group 0 SGI before this + handler returns. Bounded, because with nesting compiled out the + second FIQ cannot arrive and this must not hang. */ + + if (board_fiq_provoke != 0UL) + { + unsigned long seen = board_fiq_high_count; + unsigned long guard; + + gicv3_send_sgi_group0(BOARD_FIQ_HIGH_INTID); + + for (guard = 0UL; guard < 100000UL; guard++) + { + if (board_fiq_high_count != seen) + { + break; + } + } + } + } + else if (intid == (unsigned long) BOARD_FIQ_HIGH_INTID) + { + board_fiq_high_count++; + + /* Depth above 1 means this FIQ arrived inside another FIQ handler. */ + + if (board_fiq_depth > 1UL) + { + board_fiq_nested_count++; + } + } + else + { + board_fiq_unexpected = intid; + } + + board_fiq_depth--; +} + + +/**************************************************************************/ +/* board_fiq_handler -- the non-nesting FIQ entry point. */ +/**************************************************************************/ + +void board_fiq_handler(void) +{ + unsigned long intid = gicv3_acknowledge_group0(); + + board_fiq_service(intid); + + if (intid != GICV3_SPURIOUS_INTID) + { + gicv3_end_of_interrupt_group0(intid); + } +} +#endif diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/link.lds b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/link.lds new file mode 100644 index 00000000..b7aac5c8 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/link.lds @@ -0,0 +1,141 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/* Linker script for Cortex-R52 on the Armv8-R AEM FVP (BaseR platform). + * + * The BaseR memory map is the Base platform map with its two 2 GB halves + * swapped: Base DRAM at 0x80000000 appears at 0x00000000 here, and the + * 0x80000000-0xFFFFFFFF half holds peripherals and is NOT executable by + * default. Code must therefore be linked into low DRAM -- linking at + * 0x80000000 produces a silent fault loop with no output. + */ + +ENTRY(_start) + +__hyp_stack_size__ = 0x0800; +__svc_stack_size__ = 0x1000; +__irq_stack_size__ = 0x0800; +__fiq_stack_size__ = 0x0400; +__abt_stack_size__ = 0x0400; +__und_stack_size__ = 0x0400; +__sys_stack_size__ = 0x0800; + +MEMORY +{ + DRAM (rwx) : ORIGIN = 0x00000000, LENGTH = 0x08000000 /* 128 MB */ +} + +SECTIONS +{ + /* PMSAv8-R regions have a 64-byte granule and their limits are inclusive, + so the code and data areas are separated on a 64-byte boundary. Without + this a single region would have to span both, forcing writable code or + executable data. */ + + . = ALIGN(64); + __code_start__ = .; + + .text : + { + KEEP(*(.vectors_el2)) + KEEP(*(.vectors_el1)) + *(.text*) + *(.glue_7) + *(.glue_7t) + } > DRAM + + .rodata : + { + . = ALIGN(4); + *(.rodata*) + . = ALIGN(4); + } > DRAM + + /* Start the writable area on a protection-region granule. + * + * The alignment belongs on the output section itself. Two things that + * look equivalent do not work: an assignment such as ". = ALIGN(64);" + * between sections does not advance a MEMORY region's allocation + * pointer, and ". = ALIGN(64);" at the end of .rodata does not extend + * that section when no data follows it. In both cases .data keeps its + * unaligned address while __data_start__ reports the aligned one, so the + * first bytes of .data fall inside the read-only code region. The MPU + * caught precisely that: writes to the two variables at the start of + * .data faulted, and since one of them is the flag the abort handler + * consults, the fault turned itself fatal. + */ + + .data ALIGN(64) : + { + __code_end__ = .; /* code region ends where writable data begins */ + __data_start__ = .; + *(.data*) + . = ALIGN(4); + } > DRAM + + .bss (NOLOAD) : + { + . = ALIGN(4); + __bss_start__ = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + __bss_end__ = .; + } > DRAM + + /* One stack per AArch32 processor mode. SP must stay 8-byte aligned + (AAPCS), so each region is aligned before its top symbol is taken. */ + + .stacks (NOLOAD) : + { + . = ALIGN(8); + . = . + __hyp_stack_size__; + __hyp_stack_top = .; + + . = ALIGN(8); + . = . + __svc_stack_size__; + __svc_stack_top = .; + + . = ALIGN(8); + . = . + __irq_stack_size__; + __irq_stack_top = .; + + . = ALIGN(8); + . = . + __fiq_stack_size__; + __fiq_stack_top = .; + + . = ALIGN(8); + . = . + __abt_stack_size__; + __abt_stack_top = .; + + . = ALIGN(8); + . = . + __und_stack_size__; + __und_stack_top = .; + + . = ALIGN(8); + . = . + __sys_stack_size__; + __sys_stack_top = .; + } > DRAM + + . = ALIGN(8); + _end = .; + PROVIDE(end = .); + + /* Writable area for the MPU: data, bss, stacks and the heap that + tx_application_define carves out of everything above _end. */ + + . = ALIGN(64); + __data_end__ = . + 0x00100000; /* 1 MB of heap beyond the image */ +} diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/mpu.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/mpu.c new file mode 100644 index 00000000..4fad2f7e --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/mpu.c @@ -0,0 +1,456 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* mpu.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* PMSAv8-R memory protection and cache enable for Cortex-R52. */ +/* */ +/* Register model (AArch32): a region is selected through PRSELR and */ +/* then described by PRBAR (base, shareability, access permission, */ +/* execute-never) and PRLAR (inclusive limit, attribute index, enable).*/ +/* Both addresses have a 64-byte granule, so the low six bits of each */ +/* register hold attributes rather than address. Memory types come */ +/* from MAIR through the attribute index, not from the region itself. */ +/* */ +/* Caches are invalidated before being enabled. On this model they */ +/* come out of reset invalid, but silicon does not guarantee that, and */ +/* this code is meant to be the template the S32Z280 port starts from. */ +/* */ +/* MISRA C:2012 deviations (justified) */ +/* */ +/* Directive 4.3 -- the MPU, MAIR, SCTLR and cache maintenance are only*/ +/* reachable through CP15; every access is encapsulated in a one-line*/ +/* accessor below. */ +/* */ +/**************************************************************************/ + +#include "platform.h" +#include "mpu.h" + +/* SCTLR bits. */ + +#define SCTLR_M (1UL << 0) /* MPU enable */ +#define SCTLR_C (1UL << 2) /* data cache enable */ +#define SCTLR_I (1UL << 12) /* instruction cache enable */ + +/* MAIR attribute bytes. Index 0 is Normal write-back read/write-allocate, + index 1 is Device-nGnRnE, matching MPU_ATTR_* in the header. */ + +#define MAIR_ATTR_NORMAL_WB 0xFFUL +#define MAIR_ATTR_DEVICE 0x00UL + +/* Boundaries supplied by the linker script, all 64-byte aligned. */ + +extern char __code_start__; +extern char __code_end__; +extern char __data_start__; +extern char __data_end__; + + +/**************************************************************************/ +/* The region table. */ +/* */ +/* Deliberately minimal and readable: code is read-only and executable, */ +/* everything writable is non-executable, and the peripheral half of the */ +/* address map is Device. A write-protected code region is the point -- */ +/* it is what makes the MPU do something observable, and AR2 extends the */ +/* same table with per-module regions. */ +/**************************************************************************/ + +static MPU_REGION mpu_regions[3]; +static unsigned int mpu_regions_used; + + +/**************************************************************************/ +/* CP15 accessors. */ +/**************************************************************************/ + +static unsigned long read_mpuir(void) +{ + unsigned long value; + __asm__ volatile("mrc p15, 0, %0, c0, c0, 4" : "=r"(value)); + return value; +} + +static unsigned long read_sctlr(void) +{ + unsigned long value; + __asm__ volatile("mrc p15, 0, %0, c1, c0, 0" : "=r"(value)); + return value; +} + +static void write_sctlr(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c1, c0, 0" : : "r"(value) : "memory"); +} + +static void write_prselr(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c6, c2, 1" : : "r"(value) : "memory"); +} + +static void write_prbar(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c6, c3, 0" : : "r"(value) : "memory"); +} + +static void write_prlar(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c6, c3, 1" : : "r"(value) : "memory"); +} + +static unsigned long read_prbar(void) +{ + unsigned long value; + __asm__ volatile("mrc p15, 0, %0, c6, c3, 0" : "=r"(value)); + return value; +} + +static unsigned long read_prlar(void) +{ + unsigned long value; + __asm__ volatile("mrc p15, 0, %0, c6, c3, 1" : "=r"(value)); + return value; +} + +static void write_mair0(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c10, c2, 0" : : "r"(value) : "memory"); +} + +static void write_mair1(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c10, c2, 1" : : "r"(value) : "memory"); +} + +static void invalidate_icache_all(void) +{ + __asm__ volatile("mcr p15, 0, r0, c7, c5, 0" : : : "memory"); /* ICIALLU */ +} + +static void data_sync_barrier(void) +{ + __asm__ volatile("dsb" : : : "memory"); +} + +static void instruction_barrier(void) +{ + __asm__ volatile("isb" : : : "memory"); +} + + +/**************************************************************************/ +/* mpu_region_count */ +/**************************************************************************/ + +unsigned int mpu_region_count(void) +{ + /* MPUIR.REGION occupies bits [15:8]. */ + + return (unsigned int) ((read_mpuir() >> 8) & 0xFFUL); +} + + +/**************************************************************************/ +/* mpu_is_enabled / mpu_caches_enabled */ +/**************************************************************************/ + +unsigned int mpu_is_enabled(void) +{ + return ((read_sctlr() & SCTLR_M) != 0UL) ? 1U : 0U; +} + +unsigned int mpu_caches_enabled(void) +{ + unsigned long sctlr = read_sctlr(); + + return (((sctlr & SCTLR_C) != 0UL) && ((sctlr & SCTLR_I) != 0UL)) ? 1U : 0U; +} + + +/**************************************************************************/ +/* mpu_region_table */ +/**************************************************************************/ + +const MPU_REGION *mpu_region_table(unsigned int *count_ptr) +{ + if (count_ptr != 0) + { + *count_ptr = mpu_regions_used; + } + + return mpu_regions; +} + + +/**************************************************************************/ +/* mpu_read_region */ +/**************************************************************************/ + +void mpu_read_region(unsigned int index, unsigned long *prbar_ptr, + unsigned long *prlar_ptr) +{ + write_prselr((unsigned long) index); + instruction_barrier(); + + if (prbar_ptr != 0) + { + *prbar_ptr = read_prbar(); + } + if (prlar_ptr != 0) + { + *prlar_ptr = read_prlar(); + } +} + + +/**************************************************************************/ +/* build_table -- describe this image's memory. */ +/**************************************************************************/ + +static void build_table(void) +{ + /* Code: read-only and executable. */ + + mpu_regions[0].mpu_region_base = (unsigned long) &__code_start__; + mpu_regions[0].mpu_region_limit = (unsigned long) &__code_end__ - 1UL; + mpu_regions[0].mpu_region_ap = MPU_AP_RO_EL1; + mpu_regions[0].mpu_region_execute_never = 0U; + mpu_regions[0].mpu_region_shareability = MPU_SH_NON; + mpu_regions[0].mpu_region_attr_index = MPU_ATTR_NORMAL_WB; + mpu_regions[0].mpu_region_name = "code RO X normal-wb"; + + /* Data, bss, stacks and heap: writable, never executable. */ + + mpu_regions[1].mpu_region_base = (unsigned long) &__data_start__; + mpu_regions[1].mpu_region_limit = (unsigned long) &__data_end__ - 1UL; + mpu_regions[1].mpu_region_ap = MPU_AP_RW_EL1; + mpu_regions[1].mpu_region_execute_never = 1U; + mpu_regions[1].mpu_region_shareability = MPU_SH_NON; + mpu_regions[1].mpu_region_attr_index = MPU_ATTR_NORMAL_WB; + mpu_regions[1].mpu_region_name = "data RW NX normal-wb"; + + /* Peripherals occupy the upper half of the BaseR map. */ + + mpu_regions[2].mpu_region_base = 0x80000000UL; + mpu_regions[2].mpu_region_limit = 0xFFFFFFFFUL; + mpu_regions[2].mpu_region_ap = MPU_AP_RW_EL1; + mpu_regions[2].mpu_region_execute_never = 1U; + mpu_regions[2].mpu_region_shareability = MPU_SH_NON; + mpu_regions[2].mpu_region_attr_index = MPU_ATTR_DEVICE; + mpu_regions[2].mpu_region_name = "dev RW NX device"; + + mpu_regions_used = 3U; +} + + +/**************************************************************************/ +/* program_region */ +/**************************************************************************/ + +static void program_region(unsigned int index, const MPU_REGION *region_ptr) +{ + unsigned long prbar; + unsigned long prlar; + + /* PRBAR: BASE[31:6], RES0[5], SH[4:3], AP[2:1], XN[0]. + Cortex-R52 TRM r1p3 figure 3-39 / table 3-80. */ + + prbar = (region_ptr->mpu_region_base & 0xFFFFFFC0UL) + | (((unsigned long) region_ptr->mpu_region_shareability & 0x3UL) << 3) + | (((unsigned long) region_ptr->mpu_region_ap & 0x3UL) << 1) + | ((unsigned long) region_ptr->mpu_region_execute_never & 0x1UL); + + prlar = (region_ptr->mpu_region_limit & 0xFFFFFFC0UL) + | (((unsigned long) region_ptr->mpu_region_attr_index & 0x7UL) << 1) + | 1UL; /* region enable */ + + write_prselr((unsigned long) index); + instruction_barrier(); + write_prbar(prbar); + write_prlar(prlar); +} + + +#ifdef TXM_MODULE_MANAGER + +/**************************************************************************/ +/* mpu_module_window_init */ +/* */ +/* A window over the module area, for the manager to load through. */ +/* */ +/* No region in the table above covers the module area, which is what */ +/* stops every thread from reaching a module's memory -- but the manager */ +/* has to read the preamble and write the module's data to load it at */ +/* all. Without this the load faults on its first read of the image. */ +/* */ +/* Not opened and closed around the load. It is left enabled here and */ +/* the SCHEDULER owns it from then on: it turns this region on for every */ +/* thread that owns no module and off for every thread that does, which */ +/* is what keeps it and a module's own regions -- which cover the same */ +/* memory -- from ever being enabled together. PMSAv8-R has no region */ +/* priority, so an access hitting more than one enabled region takes a */ +/* translation fault (TRM 8.1), and mutual exclusion by ownership is the */ +/* only form of it that does not depend on remembering to bracket a call. */ +/* */ +/* EL1 read/write with no EL0 access, and execute-never: the manager can */ +/* load through it, and a module cannot use it to reach anything. */ +/**************************************************************************/ + +unsigned long mpu_module_window_prbar; +unsigned long mpu_module_window_prlar; + + +unsigned int mpu_module_window_init(void) +{ + MPU_REGION window; + + /* Asked of the hardware rather than assumed. See MPU_MODULE_REGIONS_REQUIRED + in mpu.h: this model reports a region count no real Cortex-R52 can have, + so the number is checked here and the shortfall is a refusal. */ + + if (mpu_region_count() < MPU_MODULE_REGIONS_REQUIRED) + { + return 0U; + } + + window.mpu_region_base = FVP_MODULE_AREA_BASE; + window.mpu_region_limit = FVP_MODULE_AREA_BASE + + FVP_MODULE_AREA_SIZE - 1UL; + window.mpu_region_ap = MPU_AP_RW_EL1; + window.mpu_region_execute_never = 1U; + window.mpu_region_shareability = MPU_SH_NON; + window.mpu_region_attr_index = MPU_ATTR_NORMAL_WB; + window.mpu_region_name = "module window RW NX EL1"; + + /* Published for the scheduler, which turns this region on and off on every + dispatch and has no business computing register layouts in assembly. */ + + mpu_module_window_prbar = (window.mpu_region_base & 0xFFFFFFC0UL) + | (((unsigned long) window.mpu_region_shareability & 0x3UL) << 3) + | (((unsigned long) window.mpu_region_ap & 0x3UL) << 1) + | ((unsigned long) window.mpu_region_execute_never & 0x1UL); + + mpu_module_window_prlar = (window.mpu_region_limit & 0xFFFFFFC0UL) + | (((unsigned long) window.mpu_region_attr_index & 0x7UL) << 1) + | 1UL; + + program_region(MPU_MODULE_LOAD_REGION, &window); + + data_sync_barrier(); + instruction_barrier(); + + return 1U; +} + +#endif /* TXM_MODULE_MANAGER */ + + +/**************************************************************************/ +/* mpu_init */ +/**************************************************************************/ + +unsigned int mpu_init(void) +{ + unsigned long sctlr; + unsigned int index; + unsigned int available = mpu_region_count(); + + build_table(); + + if (available < mpu_regions_used) + { + return 0U; + } + +#ifdef TXM_MODULE_MANAGER + + /* A module manager build needs regions the table above does not describe: + the eight handed to a module and the window over the module area. If the + implementation cannot supply them, stop here rather than enable a + protection map that is missing the half a module depends on. */ + + if (available < MPU_MODULE_REGIONS_REQUIRED) + { + return 0U; + } + +#endif + + /* Memory types first: a region's attribute index is meaningless until + MAIR is populated. */ + + write_mair0((MAIR_ATTR_DEVICE << 8) | MAIR_ATTR_NORMAL_WB); + write_mair1(0UL); + + for (index = 0U; index < mpu_regions_used; index++) + { + program_region(index, &mpu_regions[index]); + } + + /* Disable any regions this image does not use, so that stale enables + cannot grant access we did not intend. */ + + for (index = mpu_regions_used; index < available; index++) + { + write_prselr((unsigned long) index); + instruction_barrier(); + write_prlar(0UL); + } + +#ifdef TXM_MODULE_MANAGER + + /* After the loop above, which would otherwise disable it again: the module + window lives above the regions this table uses, so it counts as unused + here. The region count was checked at the top of this function, so the + call cannot fail at this point. */ + + if (mpu_module_window_init() == 0U) + { + return 0U; + } + +#endif + + data_sync_barrier(); + + /* Caches are invalidated before enabling. The instruction cache has a + single invalidate-all; the data cache is left to the model's reset + state, which is invalid, because a set/way sweep belongs with the + silicon bring-up where it can be verified against the real cache + geometry. */ + + invalidate_icache_all(); + data_sync_barrier(); + instruction_barrier(); + + sctlr = read_sctlr(); + sctlr |= SCTLR_M | SCTLR_C | SCTLR_I; + write_sctlr(sctlr); + data_sync_barrier(); + instruction_barrier(); + + return mpu_regions_used; +} diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/mpu.h b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/mpu.h new file mode 100644 index 00000000..d52a03a6 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/mpu.h @@ -0,0 +1,169 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* mpu.h Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* PMSAv8-R memory protection and cache enable for Cortex-R52. */ +/* */ +/* The region set is described by a table rather than a sequence of */ +/* register writes. That is deliberate and is a roadmap decision, not */ +/* a style preference: AR2 programs a Stage-1 region set per ThreadX */ +/* module and AR3 programs Stage-2 regions per ZoneX partition, and */ +/* both reuse this shape. A table can be swapped at a partition */ +/* switch; open-coded register writes cannot. */ +/* */ +/**************************************************************************/ + +#ifndef MPU_H +#define MPU_H + +/* Access permissions, PRBAR.AP (bits [2:1]). + * + * AP[2] selects read-only, AP[1] grants EL0 access -- the standard Armv8-R + * encoding, as published. An earlier version of this file claimed the two + * bits were reversed, on the strength of a calibration that programmed four + * regions and attempted a privileged write to each: + * + * AP=0b00 write allowed AP=0b01 write faulted + * AP=0b10 write allowed AP=0b11 write faulted + * + * That table is real, but the cause was not the AP ordering. program_region() + * shifted every PRBAR field one bit too far left, so the AP value's low bit + * landed in the real AP[2] -- the read-only bit -- making writes fault exactly + * when that bit was set. The "high bit grants EL0 access" half was never + * tested; under the old shift that bit landed in SH[0], programming a + * shareability the TRM defines as UNPREDICTABLE. + * + * The shift is fixed in program_region(). These values are now the + * architectural ones and need no calibration. Verified on S32Z280 silicon: + * a write to an RO region faults, and execution from an XN region takes a + * prefetch abort -- the latter never worked under the old shift, because the + * intended XN bit landed in AP[1] instead. + */ + +#define MPU_AP_RW_EL1 0U /* 0b00 EL1 read/write, no EL0 */ +#define MPU_AP_RW_EL1_EL0 1U /* 0b01 EL1 read/write, EL0 read/write */ +#define MPU_AP_RO_EL1 2U /* 0b10 EL1 read-only, no EL0 */ +#define MPU_AP_RO_EL1_EL0 3U /* 0b11 EL1 read-only, EL0 read-only */ + +/* Shareability, PRBAR.SH. */ + +#define MPU_SH_NON 0U +#define MPU_SH_OUTER 2U +#define MPU_SH_INNER 3U + +/* Attribute indices into MAIR, PRLAR.AttrIndx. */ + +#define MPU_ATTR_NORMAL_WB 0U /* Normal, inner/outer write-back */ +#define MPU_ATTR_DEVICE 1U /* Device-nGnRnE */ + +/* One protection region. The limit is inclusive, matching the hardware. */ + +typedef struct MPU_REGION_STRUCT +{ + unsigned long mpu_region_base; + unsigned long mpu_region_limit; + unsigned char mpu_region_ap; + unsigned char mpu_region_execute_never; + unsigned char mpu_region_shareability; + unsigned char mpu_region_attr_index; + const char *mpu_region_name; +} MPU_REGION; + +/* Number of regions this implementation provides, from MPUIR. */ + +unsigned int mpu_region_count(void); + +/* Program the region table, then enable the MPU and the caches. Returns + the number of regions programmed, or 0 if the table does not fit. */ + +unsigned int mpu_init(void); + +/* True once SCTLR reports the MPU and both caches enabled. */ + +unsigned int mpu_is_enabled(void); +unsigned int mpu_caches_enabled(void); + +/* The active table, for reporting. */ + +const MPU_REGION *mpu_region_table(unsigned int *count_ptr); + +/* Read back what the hardware holds for one region, so that programming can + be verified rather than assumed. */ + +void mpu_read_region(unsigned int index, unsigned long *prbar_ptr, + unsigned long *prlar_ptr); + +/* --------------------------------------------------------------------------- + ThreadX modules. + + Kept in this header rather than behind TXM_MODULE_MANAGER because + txm_module_manager_offset_check.c includes it to check the region number + against the one tx_thread_schedule.S writes in assembly, and a definition + that appears only in some builds cannot be checked in the others. + --------------------------------------------------------------------------- */ + +/* Region index for the manager's load window over the module area. Above the + kernel's 0-2 and above the eight the manager hands to a module (8-15), so + neither the boot table nor the scheduler's per-thread region load can + disturb it. tx_thread_schedule.S spells the same number in an MCR to + PRSELR; the offset check asserts the two agree. */ + +#define MPU_MODULE_LOAD_REGION 16U + +/* How many EL1 regions a module manager build needs: 0-2 kernel, 8-15 for the + module block, 16 for the window. Region 16 is the highest index used, so + seventeen regions is the requirement. + + Checked against MPUIR rather than assumed, even though the AEMv8-R model + reports 32. The model reports a region count NO Cortex-R52 can have -- the + TRM gives MPUIR.DREGION as 16, 20 or 24 -- so a green run here says nothing + about whether the budget fits silicon, and a part configured with 16 regions + cannot run this port at all. The check is here so that the shortfall is a + refusal rather than a region that silently does not exist. */ + +#define MPU_MODULE_REGIONS_REQUIRED 17U + +/* The module area window, which is what lets privileged code reach module + memory at all -- no other kernel region covers it. + + PMSAv8-R has no region priority, so this must never be enabled at the same + time as the regions a module is given, which cover the same memory. That is + guaranteed by who owns it rather than by careful calling: the scheduler turns + this region on for every thread that is not a module thread and off for every + thread that is, so the window is enabled exactly when no module regions are + loaded. The two register words are published for it below. + + Enabled at boot, because everything before the first module thread is + privileged code that may need to reach module memory. Returns 0 if the + implementation has fewer than MPU_MODULE_REGIONS_REQUIRED regions, in which + case nothing was programmed. */ + +unsigned int mpu_module_window_init(void); + +extern unsigned long mpu_module_window_prbar; +extern unsigned long mpu_module_window_prlar; + +#endif /* MPU_H */ diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/platform.h b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/platform.h new file mode 100644 index 00000000..151f732d --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/platform.h @@ -0,0 +1,135 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* platform.h Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Memory map for the Armv8-R AEM FVP (BaseR platform). */ +/* */ +/* Every address here was VERIFIED IN-MODEL, not taken from */ +/* documentation or recollection. The BaseR map is the Base platform */ +/* map with its two 2 GB halves swapped (so a Base peripheral at X */ +/* below 0x80000000 appears at X + 0x80000000), and each base was then */ +/* confirmed by reading its identification register: */ +/* */ +/* PL011 UART0 peripheral ID 0x11,0x10,0x24,0x00, and a marker */ +/* written to it appeared on the host console */ +/* GIC GICD_PIDR2 = 0x3B and GICR_PIDR2 = 0x3B, i.e. */ +/* architecture revision 3 = GICv3 */ +/* Counter CNTFID0 = 0x05F5E100 = 100 MHz, matching the */ +/* model's bp.refcounter.base_frequency parameter */ +/* */ +/* Values are written without integer suffixes so that this header can */ +/* also be included from preprocessed assembly. */ +/* */ +/**************************************************************************/ + +#ifndef PLATFORM_H +#define PLATFORM_H + +/* PL011 UART0. */ + +#define PL011_UART0_BASE 0x9C090000 + +/* GICv3. A redistributor is two consecutive 64 KB frames per core: the RD + frame carries the identification and wake-up registers, the SGI frame the + per-interrupt enable, priority and configuration registers for SGIs/PPIs. */ + +#define GICD_BASE 0xAF000000 +#define GICR_RD_BASE 0xAF100000 +#define GICR_SGI_BASE 0xAF110000 + +/* System counter control frame (CNTControlBase). */ + +#define CNT_CONTROL_BASE 0xAA430000 + +/* The model resets CNTFRQ to zero and leaves the system counter stopped -- + bp.refcounter.non_arch_start_at_default=0, documented as "firmware is + expected to enable the timer at boot time". This BSP therefore programs + both. The value is CNTFID0 read back from the counter control frame. */ + +#define SYSTEM_COUNTER_HZ 100000000 + +/* --------------------------------------------------------------------------- + Memory for ThreadX modules. + + Only the module manager build uses this, but the addresses are stated here + rather than in the module example because they are a property of the board's + memory map: a module's code and data are handed to it as MPU regions, and + PMSAv8-R will not allow those to overlap a kernel region. So module memory + has to be memory NO kernel region covers, and deciding that is the memory + map's job. + + link_module.lds ends the kernel's DRAM region at this base and gives the + 64 KB above it to the module area; mpu.c covers exactly the same range with + the manager's load window (region 16), which is the only mapping privileged + code has over it. The three have to agree, so all three read these two + values. + + Chosen at 0x003F0000 -- just under 4 MB -- because the manager image with its + stacks and its 1 MB of heap is far smaller than that, and the link script + asserts the two do not meet rather than trusting the margin. */ + +#define FVP_MODULE_AREA_BASE 0x003F0000 +#define FVP_MODULE_AREA_SIZE 0x00010000 /* 64 KB */ + +/* The shared granules the sample module reports its progress through, at the + base of the module area. + + The first of them exists because the FVP has no debugger seam: on silicon a + GDB harness reads the module's own progress variable out of its data area, + and here nothing outside the image can read anything. So the manager grants + the module a shared region over this word and reads it back afterwards, which + is what lets the FVP test judge what the module actually managed to do rather + than only that it faulted. + + The rest exist to exercise the shared-region machinery itself. A module may + be granted TXM_MODULE_MPU_SHARED_ENTRIES regions, and one grant proves only + that the first entry works, so the area holds one granule per entry -- plus + one more that the manager NEVER grants, sandwiched between two that it does. + A limit register masked the wrong way, or a base off by a granule, leaks into + that gap from one side or the other, and a module that can write it is a + module whose grant covered more than was asked for. + + FVP_MODULE_STATUS_SIZE is the size of ONE granule, which is the length of + each individual grant; the area is FVP_MODULE_STATUS_GRANULES of them. + + Fixed addresses on both sides, and checked at run time rather than trusted: + the manager grants the regions from the linker's symbol and refuses to + continue if that symbol is not this address. */ + +#define FVP_MODULE_STATUS_BASE 0x003F0000 +#define FVP_MODULE_STATUS_SIZE 0x40 /* one MPU granule */ +#define FVP_MODULE_STATUS_GRANULES 6 /* five granted, one not */ +#define FVP_MODULE_STATUS_UNGRANTED 2 /* the one never granted */ + +#ifndef __ASSEMBLER__ + +/* 32-bit device register access. */ + +#define REG32(address) (*(volatile unsigned long *)(unsigned long)(address)) + +#endif /* __ASSEMBLER__ */ + +#endif /* PLATFORM_H */ diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/test/run_fvp_test.py b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/test/run_fvp_test.py new file mode 100644 index 00000000..37aa0adc --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/test/run_fvp_test.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2026 Eclipse ThreadX contributors +# SPDX-License-Identifier: MIT +# Portions of this file were generated with AI assistance. +# +"""Run a Cortex-R52 image on the Armv8-R AEM FVP and assert its result. + +This runner is deliberately much simpler than the RISC-V/QEMU one, because +the platform allows it: the FVP exposes only an Iris server and no GDB stub, +so there is nothing to script through a debugger, and every image terminates +itself with a semihosting SYS_EXIT. The runner therefore just launches the +model, captures its console, and judges the self-reported result. + +A missing result line is treated as FAILURE, never as success: a silent hang +or a crash before the first check must not be able to masquerade as a pass. +""" + +import argparse +import subprocess +import sys + +PASS_MARK = "ALL CHECKS PASSED" +FAIL_MARK = "RESULT: FAILED" +FAULT_MARK = "[FAULT]" + + +def as_text(stream): + """Normalise a captured stream to str. + + subprocess.TimeoutExpired carries its partial output undecoded even when + the call used text=True, so the timeout path can hand us bytes where the + normal path hands us str. Decoding defensively here keeps the hang + report readable instead of raising TypeError inside the error handler. + """ + if stream is None: + return "" + if isinstance(stream, bytes): + return stream.decode(errors="replace") + return stream + + +def run(elf, fvp, timeout): + command = [ + fvp, + "-C", "cluster0.NUM_CORES=1", + "-C", "bp.vis.disable_visualisation=1", + "-C", "bp.terminal_0.start_telnet=0", + # Route UART0 to stdout so PL011-console images are captured too. + # Harmless for semihosting images, which do not touch the UART. + "-C", "bp.pl011_uart0.out_file=-", + "-C", "bp.pl011_uart0.unbuffered_output=1", + "-a", elf, + ] + + print("Running:", " ".join(command), flush=True) + + try: + completed = subprocess.run( + command, capture_output=True, text=True, timeout=timeout + ) + output = as_text(completed.stdout) + as_text(completed.stderr) + except subprocess.TimeoutExpired as expired: + print(as_text(expired.stdout) + as_text(expired.stderr)) + print( + f"FAIL: the simulation did not terminate within {timeout}s. " + "Every image is expected to exit through semihosting SYS_EXIT, " + "so this is a hang, not a slow run.", + file=sys.stderr, + ) + return 1 + + print(output) + + if FAULT_MARK in output: + faults = [line for line in output.splitlines() if FAULT_MARK in line] + print("FAIL: unhandled exception reported:", file=sys.stderr) + for line in faults: + print(" " + line.strip(), file=sys.stderr) + return 1 + + if FAIL_MARK in output: + print("FAIL: the image reported failing checks.", file=sys.stderr) + return 1 + + if PASS_MARK not in output: + print( + "FAIL: no result line found. The image neither passed nor " + "reported failure, so it did not reach its checks.", + file=sys.stderr, + ) + return 1 + + print("PASS") + return 0 + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--elf", required=True, help="image to run") + parser.add_argument("--fvp", default="FVP_BaseR_AEMv8R", help="model binary") + parser.add_argument( + "--timeout", type=int, default=180, help="seconds before declaring a hang" + ) + arguments = parser.parse_args() + + return run(arguments.elf, arguments.fvp, arguments.timeout) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/timer.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/timer.c new file mode 100644 index 00000000..eac0b3dc --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/timer.c @@ -0,0 +1,174 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* timer.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Arm generic timer tick for Cortex-R52 on the Armv8-R AEM FVP. */ +/* */ +/* Two model behaviours make this longer than a bare timer setup, both */ +/* confirmed by reading the hardware rather than assumed: */ +/* */ +/* 1. The system counter is STOPPED at reset (CNTCR = 0, CNTCV = 0). */ +/* The model documents that firmware is expected to start it, so */ +/* the counter control frame is enabled here. Without this the */ +/* timer never counts and no tick is ever delivered. */ +/* 2. CNTFRQ resets to ZERO. It is writable only at the highest */ +/* implemented exception level, so entry.S programs it at EL2; */ +/* this file only reads it. Deriving the tick interval from an */ +/* unprogrammed CNTFRQ would divide by zero. */ +/* */ +/* EL1 access to the physical timer and counter also depends on */ +/* CNTHCTL.PL1PCEN/PL1PCTEN, which reset disabled and are set by */ +/* entry.S at EL2; otherwise these accesses would trap to EL2. */ +/* */ +/* MISRA C:2012 deviations (justified) */ +/* */ +/* Directive 4.3 -- the generic timer is only reachable through CP15 */ +/* registers; every such access is encapsulated in an accessor below.*/ +/* */ +/**************************************************************************/ + +#include "tx_api.h" +#include "platform.h" +#include "timer.h" + +/* Counter control frame (CNTControlBase). */ + +#define CNTCR 0x0000U +#define CNTCR_EN (1UL << 0) +#define CNTSR 0x0004U + +/* CNTP_CTL bits. */ + +#define CNTP_CTL_ENABLE (1UL << 0) +#define CNTP_CTL_IMASK (1UL << 1) + +static unsigned long tick_interval; + + +/**************************************************************************/ +/* Generic timer accessors (AArch32 CP15). */ +/**************************************************************************/ + +static unsigned long read_cntfrq(void) +{ + unsigned long value; + __asm__ volatile("mrc p15, 0, %0, c14, c0, 0" : "=r"(value)); + return value; +} + +static void write_cntp_tval(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c14, c2, 0" : : "r"(value) : "memory"); +} + +static void write_cntp_ctl(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c14, c2, 1" : : "r"(value) : "memory"); +} + +static unsigned long long read_cntpct(void) +{ + unsigned long low; + unsigned long high; + + __asm__ volatile("mrrc p15, 0, %0, %1, c14" : "=r"(low), "=r"(high)); + + return ((unsigned long long) high << 32) | (unsigned long long) low; +} + + +/**************************************************************************/ +/* timer_frequency */ +/**************************************************************************/ + +unsigned long timer_frequency(void) +{ + return read_cntfrq(); +} + + +/**************************************************************************/ +/* timer_counter */ +/**************************************************************************/ + +unsigned long long timer_counter(void) +{ + return read_cntpct(); +} + + +/**************************************************************************/ +/* timer_counter_enabled */ +/**************************************************************************/ + +unsigned int timer_counter_enabled(void) +{ + return ((REG32(CNT_CONTROL_BASE + CNTCR) & CNTCR_EN) != 0UL) ? 1U : 0U; +} + + +/**************************************************************************/ +/* timer_reload */ +/**************************************************************************/ + +void timer_reload(void) +{ + /* Writing CNTP_TVAL restarts the down-count, which also deasserts the + level-sensitive timer output. */ + + write_cntp_tval(tick_interval); +} + + +/**************************************************************************/ +/* timer_init */ +/**************************************************************************/ + +void timer_init(void) +{ + unsigned long frequency; + + /* Start the system counter -- it is stopped at reset on this model. */ + + REG32(CNT_CONTROL_BASE + CNTCR) |= CNTCR_EN; + + /* Derive the tick interval. CNTFRQ was programmed at EL2; fall back to + the known counter frequency rather than dividing by zero should that + ever not have happened. */ + + frequency = read_cntfrq(); + if (frequency == 0UL) + { + frequency = (unsigned long) SYSTEM_COUNTER_HZ; + } + + tick_interval = frequency / TX_TIMER_TICKS_PER_SECOND; + + /* Enable the timer, unmasked, and arm the first interval. */ + + write_cntp_tval(tick_interval); + write_cntp_ctl(CNTP_CTL_ENABLE); +} diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/timer.h b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/timer.h new file mode 100644 index 00000000..5b7968a3 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/timer.h @@ -0,0 +1,67 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* timer.h Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Arm generic timer tick for Cortex-R52. */ +/* */ +/**************************************************************************/ + +#ifndef TIMER_H +#define TIMER_H + +/* PPI driven by the EL1 physical timer. + * + * This was not assumed. Bring-up enabled the entire PPI range 25-31 and + * recorded whichever INTID came back from ICC_IAR1; the model drove 30, which + * matches the architectural assignment for the EL1 physical timer. Only that + * line is enabled now, and irq_dispatch.c still records any other INTID so a + * different platform assignment would be reported rather than silently + * mishandled. Re-verify when moving to S32Z2 silicon. */ + +#define TIMER_PPI_INTID 30U + +/* Enable the system counter, then start the periodic tick at + TX_TIMER_TICKS_PER_SECOND. gicv3_init() must have run first. */ + +void timer_init(void); + +/* Re-arm the timer for the next tick. Called from the interrupt path. */ + +void timer_reload(void); + +/* CNTFRQ as programmed by entry.S at EL2. */ + +unsigned long timer_frequency(void); + +/* Current 64-bit physical counter value, for verifying the counter runs. */ + +unsigned long long timer_counter(void); + +/* True once the counter control frame reports the counter enabled. */ + +unsigned int timer_counter_enabled(void); + +#endif /* TIMER_H */ diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/tx_initialize_low_level.S b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/tx_initialize_low_level.S new file mode 100644 index 00000000..c8d3f188 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/tx_initialize_low_level.S @@ -0,0 +1,107 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/**************************************************************************/ +/** */ +/** ThreadX Component */ +/** */ +/** Initialize */ +/** */ +/**************************************************************************/ +/**************************************************************************/ + + /* No .arch directive here. GNU as accepts "armv8-r", LLVM's integrated + assembler accepts no spelling of it at all -- not armv8-r, not armv8r -- + and fails with "Unknown Arch". The architecture is already selected by + -mcpu=cortex-r52 on the command line, which both toolchains require and + both toolchain files pass, so the directive only restated it. */ + + .syntax unified + .arm + + .global _tx_thread_system_stack_ptr + .global _tx_initialize_unused_memory + + .text + .balign 4 + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _tx_initialize_low_level Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Low-level initialization for Cortex-R52 on the Armv8-R AEM FVP. */ +/* Called by _tx_initialize_kernel_enter with the processor already in */ +/* Supervisor mode at EL1. */ +/* */ +/* This is deliberately shorter than the A-profile reference ports: */ +/* entry.S has already given every AArch32 mode its own stack from */ +/* dedicated linker-script regions, so there is no stack carving to do */ +/* here and no run-time stack-overlap check to perform -- the linker */ +/* guarantees the regions do not overlap. */ +/* */ +/* When built with TX_R52_USE_THREADX_IRQ, board_init() brings up GICv3*/ +/* and the generic timer tick here. That is safe this early because */ +/* interrupts stay masked until _tx_thread_schedule enables them, so no*/ +/* tick can be delivered before the kernel is ready to take one. */ +/* Without that symbol (the M2 cooperative demo) no tick is created, */ +/* which keeps M2 free of any interrupt dependency. */ +/* */ +/* CALLED BY */ +/* */ +/* _tx_initialize_kernel_enter ThreadX entry function */ +/* */ +/**************************************************************************/ + + .global _tx_initialize_low_level + .type _tx_initialize_low_level, %function +_tx_initialize_low_level: + + /* Record the system stack. The interrupt path uses it when an + exception arrives while no thread is running. */ + + ldr r0, =__sys_stack_top + ldr r1, =_tx_thread_system_stack_ptr + str r0, [r1] + + /* Publish the first free memory address for tx_application_define. + _end sits above the image, .bss and every per-mode stack. Round up + to an 8-byte boundary so pool control blocks are aligned. */ + + ldr r0, =_end + add r0, r0, #7 + bic r0, r0, #7 + ldr r1, =_tx_initialize_unused_memory + str r0, [r1] + +#ifdef TX_R52_USE_THREADX_IRQ + + /* Bring up the interrupt controller and the periodic tick. */ + + push {lr} + bl board_init + pop {lr} + +#endif + + bx lr diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/uart_pl011.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/uart_pl011.c new file mode 100644 index 00000000..b4c78127 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/uart_pl011.c @@ -0,0 +1,127 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* uart_pl011.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* PL011 UART output for the Armv8-R AEM FVP, used as the console when */ +/* the image is built with TX_R52_CONSOLE_PL011. */ +/* */ +/* Semihosting remains the default because it needs no peripheral at */ +/* all, which keeps early bring-up independent of the memory map. The */ +/* UART matters because it is what real silicon will use: exercising it*/ +/* here means the S32Z280 console differs only in its base address and */ +/* clocking, not in structure. */ +/* */ +/* The model leaves UART0 disabled at reset */ +/* (bp.pl011_uart0.uart_enable=0), so the control register must be */ +/* written before the first character. Baud rate is left alone: the */ +/* model does not emulate serial timing, and inventing a divisor here */ +/* would only be misleading. */ +/* */ +/* MISRA C:2012 deviations (justified) */ +/* */ +/* Rule 11.4/11.6 -- casting integer addresses to volatile pointers is */ +/* inherent to memory-mapped device access; confined to REG32. */ +/* */ +/**************************************************************************/ + +#include "platform.h" +#include "uart_pl011.h" + +/* PL011 register offsets. */ + +#define UART_DR 0x000U /* data */ +#define UART_FR 0x018U /* flags */ +#define UART_LCR_H 0x02CU /* line control */ +#define UART_CR 0x030U /* control */ + +#define UART_FR_TXFF (1UL << 5) /* transmit FIFO full */ + +#define UART_LCR_H_WLEN_8 (3UL << 5) /* 8 data bits */ +#define UART_LCR_H_FEN (1UL << 4) /* enable FIFOs */ + +#define UART_CR_UARTEN (1UL << 0) /* UART enable */ +#define UART_CR_TXE (1UL << 8) /* transmit enable */ +#define UART_CR_RXE (1UL << 9) /* receive enable */ + + +/**************************************************************************/ +/* pl011_init */ +/**************************************************************************/ + +void pl011_init(void) +{ + /* Disable while reconfiguring, as the programming model requires. */ + + REG32(PL011_UART0_BASE + UART_CR) = 0UL; + + REG32(PL011_UART0_BASE + UART_LCR_H) = UART_LCR_H_WLEN_8 | UART_LCR_H_FEN; + + REG32(PL011_UART0_BASE + UART_CR) = + UART_CR_UARTEN | UART_CR_TXE | UART_CR_RXE; +} + + +/**************************************************************************/ +/* pl011_putc */ +/**************************************************************************/ + +void pl011_putc(char character) +{ + /* Expand newlines so the host console shows discrete lines. */ + + if (character == '\n') + { + pl011_putc('\r'); + } + + while ((REG32(PL011_UART0_BASE + UART_FR) & UART_FR_TXFF) != 0UL) + { + /* Wait for room in the transmit FIFO. */ + } + + REG32(PL011_UART0_BASE + UART_DR) = (unsigned long) (unsigned char) character; +} + + +/**************************************************************************/ +/* pl011_puts */ +/**************************************************************************/ + +void pl011_puts(const char *string_ptr) +{ + const char *cursor = string_ptr; + + if (cursor == 0) + { + return; + } + + while (*cursor != '\0') + { + pl011_putc(*cursor); + cursor++; + } +} diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/uart_pl011.h b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/uart_pl011.h new file mode 100644 index 00000000..89e416ce --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/uart_pl011.h @@ -0,0 +1,47 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* uart_pl011.h Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* PL011 UART console backend for the Armv8-R AEM FVP. */ +/* */ +/**************************************************************************/ + +#ifndef UART_PL011_H +#define UART_PL011_H + +/* Configure and enable UART0. The model leaves it disabled at reset. */ + +void pl011_init(void); + +/* Send one character, expanding a newline into CR LF. */ + +void pl011_putc(char character); + +/* Send a NUL-terminated string. */ + +void pl011_puts(const char *string_ptr); + +#endif /* UART_PL011_H */ diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/CMakeLists.txt b/ports/cortex_r52/gnu/example_build/s32z280_evb/CMakeLists.txt new file mode 100644 index 00000000..85dd29d6 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/CMakeLists.txt @@ -0,0 +1,521 @@ +# Copyright (c) 2026 Eclipse ThreadX contributors +# SPDX-License-Identifier: MIT +# Portions of this file were generated with AI assistance. +# +# Example build for Cortex-R52 on the NXP S32Z280-594EVB, RTU0 core 0. +# +# Unlike the FVP example there is no automated test target here: the board is +# reached through a debug probe and a vendor GDB server, not by launching a +# model, so running an image is a manual step. tools/read_identity.gdb loads +# an image, runs it and reports what it found. + +set(EVB_DIR ${CMAKE_CURRENT_LIST_DIR}) + +# A bare-metal image has one flat SRAM region and no OS page permissions; access +# control belongs to the MPU, so an RWX segment is expected rather than a mistake. +# GNU ld has warned about them since binutils 2.39 and takes this flag to stay +# quiet; ld.lld does not warn and rejects the flag outright, failing the link with +# "unknown argument". Chosen by toolchain, as in the FVP example. +if(CMAKE_C_COMPILER_ID STREQUAL "GNU") + set(EVB_LINK_QUIET_RWX -Wl,--no-warn-rwx-segments) +else() + set(EVB_LINK_QUIET_RWX) +endif() + +# Silicon M0 -- boot verification. Deliberately does not link ThreadX: a +# failure here is then unambiguously a boot or board problem rather than a +# kernel one, which is the same reasoning as the FVP's boot_check.elf. +add_executable(s32z280_boot.elf EXCLUDE_FROM_ALL + ${EVB_DIR}/entry.S + ${EVB_DIR}/bsp_boot.c + ${EVB_DIR}/linflexd.c + ${EVB_DIR}/timer.c + ${EVB_DIR}/gic_probe.c + ${EVB_DIR}/mpu.c + ${EVB_DIR}/gicv3.c + ${EVB_DIR}/irq_dispatch.c + ${EVB_DIR}/cache.c + ${EVB_DIR}/tcm.c +) + +target_include_directories(s32z280_boot.elf PRIVATE ${EVB_DIR}) + +# -g only: this image exists to be inspected through a debugger, and without +# DWARF the identity structure has no type for GDB to walk. No effect on the +# generated code. +target_compile_options(s32z280_boot.elf PRIVATE -g) + +target_link_options(s32z280_boot.elf PRIVATE + -T${EVB_DIR}/link.lds + -nostartfiles + -Wl,-Map=s32z280_boot.map + # Bare metal: one code region and one data region, with access control + # belonging to the MPU rather than to segment permissions. + ${EVB_LINK_QUIET_RWX} +) + + +# Same image as s32z280_boot.elf with the interrupt service body placed in ATCM +# instead of code RAM, so the two can be compared on the same board in the same +# session. Separate target rather than an option so both exist at once and a +# result can be reproduced without reconfiguring. +add_executable(s32z280_atcm.elf EXCLUDE_FROM_ALL + ${EVB_DIR}/entry.S + ${EVB_DIR}/bsp_boot.c + ${EVB_DIR}/linflexd.c + ${EVB_DIR}/timer.c + ${EVB_DIR}/gic_probe.c + ${EVB_DIR}/mpu.c + ${EVB_DIR}/gicv3.c + ${EVB_DIR}/irq_dispatch.c + ${EVB_DIR}/cache.c + ${EVB_DIR}/tcm.c +) + +target_include_directories(s32z280_atcm.elf PRIVATE ${EVB_DIR}) + +# -g only: this image exists to be inspected through a debugger, and without +# DWARF the identity structure has no type for GDB to walk. No effect on the +# generated code. +target_compile_options(s32z280_atcm.elf PRIVATE -g) + +target_link_options(s32z280_atcm.elf PRIVATE + -T${EVB_DIR}/link.lds + -nostartfiles + -Wl,-Map=s32z280_atcm.map + # Bare metal: one code region and one data region, with access control + # belonging to the MPU rather than to segment permissions. + ${EVB_LINK_QUIET_RWX} +) + + +target_compile_definitions(s32z280_atcm.elf PRIVATE TX_R52_ATCM_ISR) + + +# ThreadX demo on silicon: threads, preemption and the timer tick. Separate +# target from s32z280_boot.elf, which deliberately does not link the kernel so +# that a boot failure there is unambiguously a board problem. +add_executable(s32z280_demo.elf EXCLUDE_FROM_ALL + ${EVB_DIR}/entry.S + ${EVB_DIR}/tx_initialize_low_level.S + ${EVB_DIR}/linflexd.c + ${EVB_DIR}/timer.c + ${EVB_DIR}/mpu.c + ${EVB_DIR}/gicv3.c + ${EVB_DIR}/irq_dispatch.c + ${EVB_DIR}/gic_probe.c + ${EVB_DIR}/cache.c + ${EVB_DIR}/tcm.c + ${EVB_DIR}/thread_mpu.c + ${EVB_DIR}/demo_s32z280.c +) + +# TX_R52_USE_THREADX_IRQ routes the IRQ vector through _tx_thread_context_save +# instead of the standalone handler, and makes _tx_initialize_low_level call +# board_init(). +target_compile_definitions(s32z280_demo.elf PRIVATE TX_R52_USE_THREADX_IRQ=1) +target_compile_options(s32z280_demo.elf PRIVATE -g) + +target_link_libraries(s32z280_demo.elf PRIVATE threadx) + +target_include_directories(s32z280_demo.elf PRIVATE + ${EVB_DIR} + ${CMAKE_SOURCE_DIR}/common/inc + ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc +) + +target_link_options(s32z280_demo.elf PRIVATE + -T${EVB_DIR}/link.lds + -nostartfiles + -Wl,-Map=s32z280_demo.map + ${EVB_LINK_QUIET_RWX} +) + +# Lazy VFP context save and restore. Only meaningful when the library was built +# with TX_R52_ENABLE_VFP and a floating-point ABI, so like the FVP's demo_m5.elf +# the target exists only in that configuration. The FPU hardware enable lives in +# entry.S under __ARM_FP; tx_thread_vfp_enable() sets only the per-thread flag. +if(TX_R52_ENABLE_VFP) + add_executable(s32z280_vfp.elf EXCLUDE_FROM_ALL + ${EVB_DIR}/entry.S + ${EVB_DIR}/tx_initialize_low_level.S + ${EVB_DIR}/linflexd.c + ${EVB_DIR}/timer.c + ${EVB_DIR}/mpu.c + ${EVB_DIR}/gicv3.c + ${EVB_DIR}/irq_dispatch.c + ${EVB_DIR}/gic_probe.c + ${EVB_DIR}/cache.c + ${EVB_DIR}/tcm.c + ${EVB_DIR}/demo_vfp_s32z280.c + ) + + target_compile_definitions(s32z280_vfp.elf PRIVATE TX_R52_USE_THREADX_IRQ=1) + target_compile_options(s32z280_vfp.elf PRIVATE -g) + + target_link_libraries(s32z280_vfp.elf PRIVATE threadx) + + target_include_directories(s32z280_vfp.elf PRIVATE + ${EVB_DIR} + ${CMAKE_SOURCE_DIR}/common/inc + ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc + ) + + target_link_options(s32z280_vfp.elf PRIVATE + -T${EVB_DIR}/link.lds + -nostartfiles + -Wl,-Map=s32z280_vfp.map + ${EVB_LINK_QUIET_RWX} + ) +endif() + +# Nested IRQ handling on silicon. Needs the library built with +# TX_R52_ENABLE_IRQ_NESTING, since entry.S only emits the nesting_start and +# nesting_end pairing under that macro, so the image exists in that +# configuration only -- as s32z280_vfp.elf does for the floating-point ABI. +if(TX_R52_ENABLE_IRQ_NESTING) + add_executable(s32z280_nesting.elf EXCLUDE_FROM_ALL + ${EVB_DIR}/entry.S + ${EVB_DIR}/tx_initialize_low_level.S + ${EVB_DIR}/linflexd.c + ${EVB_DIR}/timer.c + ${EVB_DIR}/mpu.c + ${EVB_DIR}/gicv3.c + ${EVB_DIR}/irq_dispatch.c + ${EVB_DIR}/gic_probe.c + ${EVB_DIR}/cache.c + ${EVB_DIR}/tcm.c + ${EVB_DIR}/demo_nesting_s32z280.c + ) + + target_compile_definitions(s32z280_nesting.elf PRIVATE TX_R52_USE_THREADX_IRQ=1) + target_compile_options(s32z280_nesting.elf PRIVATE -g) + + target_link_libraries(s32z280_nesting.elf PRIVATE threadx) + + target_include_directories(s32z280_nesting.elf PRIVATE + ${EVB_DIR} + ${CMAKE_SOURCE_DIR}/common/inc + ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc + ) + + target_link_options(s32z280_nesting.elf PRIVATE + -T${EVB_DIR}/link.lds + -nostartfiles + -Wl,-Map=s32z280_nesting.map + ${EVB_LINK_QUIET_RWX} + ) +endif() + +# Nested FIQ handling on silicon. Needs TX_R52_ENABLE_FIQ_NESTING, which the +# port-level CMakeLists already requires TX_R52_ENABLE_FIQ alongside, so the image +# exists only where both the FIQ vector path and the nesting pairing are compiled +# in -- as s32z280_nesting.elf does for the IRQ side. +if(TX_R52_ENABLE_FIQ_NESTING) + add_executable(s32z280_fiq.elf EXCLUDE_FROM_ALL + ${EVB_DIR}/entry.S + ${EVB_DIR}/tx_initialize_low_level.S + ${EVB_DIR}/linflexd.c + ${EVB_DIR}/timer.c + ${EVB_DIR}/mpu.c + ${EVB_DIR}/gicv3.c + ${EVB_DIR}/irq_dispatch.c + ${EVB_DIR}/gic_probe.c + ${EVB_DIR}/cache.c + ${EVB_DIR}/tcm.c + ${EVB_DIR}/demo_fiq_s32z280.c + ) + + target_compile_definitions(s32z280_fiq.elf PRIVATE TX_R52_USE_THREADX_IRQ=1) + target_compile_options(s32z280_fiq.elf PRIVATE -g) + + target_link_libraries(s32z280_fiq.elf PRIVATE threadx) + + target_include_directories(s32z280_fiq.elf PRIVATE + ${EVB_DIR} + ${CMAKE_SOURCE_DIR}/common/inc + ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc + ) + + target_link_options(s32z280_fiq.elf PRIVATE + -T${EVB_DIR}/link.lds + -nostartfiles + -Wl,-Map=s32z280_fiq.map + ${EVB_LINK_QUIET_RWX} + ) +endif() + +# --------------------------------------------------------------------------- +# ThreadX modules: the module manager, with a sample module linked into the +# module area and loaded in place from there. +# +# This target deliberately does NOT link the threadx library's port assembly. +# The module port carries its own copies of the scheduler, context restore and +# stack build, because the region switch happens in the scheduler and a module +# thread starts in a different processor mode. Linking both would give two +# definitions of every one of them. +# +# Guarded by an option, off by default, because it is the only target that needs +# TXM_MODULE_MANAGER in entry.S and the only one that links common_modules. +# --------------------------------------------------------------------------- + +option(TX_R52_BUILD_S32Z280_MODULE_EXAMPLE + "Build the NXP S32Z280-594EVB ThreadX module manager example" OFF) + +if(TX_R52_BUILD_S32Z280_MODULE_EXAMPLE) + + set(MOD_DIR ${CMAKE_SOURCE_DIR}/ports_module/cortex_r52/gnu) + set(MOD_EVB ${MOD_DIR}/example_build/s32z280_evb) + + # --------------------------------------------------------------------- + # A second ThreadX library, built with the module port's headers. + # + # This is not optional and not a convenience. The module port's tx_port.h + # adds the owning module instance to TX_QUEUE, TX_SEMAPHORE, + # TX_EVENT_FLAGS_GROUP and TX_TIMER, and the module fields to TX_THREAD. + # Measured: TX_QUEUE is 60 bytes against the base port's, TX_SEMAPHORE 40 + # against 32, TX_THREAD 236 against 184. A kernel compiled against the base + # port and a manager compiled against this one disagree about every object, + # and they link without complaint, because C linking does not compare struct + # layouts. The result would be memory corruption at run time with nothing in + # the build to hint at it. + # + # The base port's assembly is reused except for the five files the module port + # replaces, and tx_port_offset_check.c is included deliberately: if the module + # headers moved any offset that assembly depends on, that check is what says + # so, and it is better to find out here than on the board. + # --------------------------------------------------------------------- + + # Guarded by if(NOT TARGET) because the FVP module example defines the same + # library from its own directory, and the two example options can be on at + # once. The definitions are identical -- both describe the module port, not + # a board -- so whichever directory CMake reaches first may create it. + + if(NOT TARGET threadx_module) + + file(GLOB TX_COMMON_SRC ${CMAKE_SOURCE_DIR}/common/src/*.c) + + set(TX_R52_PORT_SRC ${CMAKE_SOURCE_DIR}/ports/cortex_r52/gnu/src) + + add_library(threadx_module STATIC EXCLUDE_FROM_ALL + ${TX_COMMON_SRC} + ${TX_R52_PORT_SRC}/tx_port_offset_check.c + ${TX_R52_PORT_SRC}/tx_thread_fiq_context_restore.S + ${TX_R52_PORT_SRC}/tx_thread_fiq_context_save.S + ${TX_R52_PORT_SRC}/tx_thread_fiq_nesting_end.S + ${TX_R52_PORT_SRC}/tx_thread_fiq_nesting_start.S + ${TX_R52_PORT_SRC}/tx_thread_interrupt_control.S + ${TX_R52_PORT_SRC}/tx_thread_interrupt_disable.S + ${TX_R52_PORT_SRC}/tx_thread_interrupt_restore.S + ${TX_R52_PORT_SRC}/tx_thread_irq_nesting_end.S + ${TX_R52_PORT_SRC}/tx_thread_irq_nesting_start.S + ${TX_R52_PORT_SRC}/tx_thread_vectored_context_save.S + ${TX_R52_PORT_SRC}/tx_timer_interrupt.S + ) + + target_include_directories(threadx_module PUBLIC + ${MOD_DIR}/inc + ${CMAKE_SOURCE_DIR}/common/inc + ${CMAKE_SOURCE_DIR}/common_modules/module_manager/inc + ${CMAKE_SOURCE_DIR}/common_modules/module_lib/inc + ${CMAKE_SOURCE_DIR}/common_modules/inc + ) + + target_compile_definitions(threadx_module PUBLIC TXM_MODULE_MANAGER) + + endif() + + # The portable half of the module manager. Globbed rather than listed: it is + # a whole upstream component, not a selection from one, and a file added to it + # upstream should not need a change here to be compiled. + + file(GLOB TXM_MANAGER_SRC ${CMAKE_SOURCE_DIR}/common_modules/module_manager/src/*.c) + + # The module-side library: the API shims that run inside the module. These + # belong to the module's image and must not reach the manager's link -- they + # define the same names as the kernel's own entry points, and as objects they + # beat the kernel's static library, so in one link every service call the + # manager makes is redirected into the module. See link_demo_module.lds. + + file(GLOB TXM_MODULE_LIB_SRC ${CMAKE_SOURCE_DIR}/common_modules/module_lib/src/*.c) + + # ------------------------------------------------------------------------ + # The module, built as its own image and reduced to a raw binary. + # ------------------------------------------------------------------------ + + add_executable(s32z280_demo_module.elf EXCLUDE_FROM_ALL + ${MOD_EVB}/txm_module_preamble.S + ${MOD_EVB}/sample_threadx_module.c + ${MOD_DIR}/module_lib/src/txm_module_thread_shell_entry.c + ${MOD_DIR}/module_lib/src/txm_module_gcc_setup.S + ${TXM_MODULE_LIB_SRC} + ) + + # No TXM_MODULE_MANAGER here: that define selects the kernel side of the + # shared headers, and this is the other side. + + target_include_directories(s32z280_demo_module.elf PRIVATE + ${EVB_DIR} + ${MOD_DIR}/inc + ${CMAKE_SOURCE_DIR}/common_modules/module_lib/inc + ${CMAKE_SOURCE_DIR}/common_modules/inc + ${CMAKE_SOURCE_DIR}/common/inc + ) + + # Position independent, and only here. These four flags are what make the + # module relocatable, and putting any of them on the manager would be a bug: + # the manager is the resident image, linked absolutely at the address it + # boots from, and -msingle-pic-base in particular would have it treat r9 as + # a PIC base that nothing sets up. + # + # -fpic data references go through the GOT + # -msingle-pic-base r9 is the GOT base, and the caller sets + # it -- which is what the manager's + # thread stack build now does + # -mno-pic-data-is-text-relative the module's data is NOT at a fixed + # offset from its code. It is not: code + # is loaded in place in the module area + # and data is allocated from the byte + # pool, so the gap between them is + # decided at run time + # -fno-plt no procedure linkage table, which a + # module has no loader to populate + # -mno-long-calls see below -- this one is ours, not the + # reference port's, and without it the + # module cannot start at all + # + # -mno-long-calls is the interesting one, and it is here because of a bug + # this build found before silicon did. The project-wide C flags carry + # -mlong-calls, which the manager needs: its image spans TCM at 0x30000000 + # and code at 0x79900000, far beyond the +/-32 MB a direct BL reaches. But + # -mlong-calls under -fpic makes GCC route EVERY call through the GOT -- + # R_ARM_GOT32 instead of R_ARM_CALL -- and one of those calls is the shell + # entry's call to _gcc_setup, which is the function that fills the GOT in. + # The module would have loaded, entered its shell entry, loaded a zero out + # of the not-yet-written GOT and branched to address 0. + # + # A module does not need long calls. It is one contiguous blob under two + # kilobytes, every internal call is a few hundred bytes away, and it never + # calls out of itself: kernel services go through the dispatcher function + # pointer in its entry info, which is data and reached through the GOT + # either way. So the flag is pure cost here, and dropping it is what makes + # the bootstrap possible rather than a workaround for it. + # + # The same four, in the same order, are what the Cortex-A7 GNU module + # example uses. Verified on the generated code rather than assumed: with + # these flags every reference to a module global comes out as an R_ARM_GOT32 + # and loads through LDR rX, [r9, rY], while calls stay direct R_ARM_CALL. + + target_compile_options(s32z280_demo_module.elf PRIVATE + -g + -fpic + -fno-plt + -mno-pic-data-is-text-relative + -msingle-pic-base + -mno-long-calls + ) + + target_link_options(s32z280_demo_module.elf PRIVATE + -T${MOD_EVB}/link_demo_module.lds + -nostartfiles + -nostdlib + -Wl,-Map=s32z280_demo_module.map + ${EVB_LINK_QUIET_RWX} + ) + + # The raw image the manager embeds. Written into the binary directory + # because that is the include path module_blob.S searches. + + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/demo_module.bin + COMMAND ${CMAKE_OBJCOPY} -O binary + $<TARGET_FILE:s32z280_demo_module.elf> + ${CMAKE_CURRENT_BINARY_DIR}/demo_module.bin + DEPENDS s32z280_demo_module.elf + COMMENT "Converting the demonstration module to a raw image" + VERBATIM + ) + + add_custom_target(s32z280_demo_module_bin + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/demo_module.bin + ) + + add_executable(s32z280_module.elf EXCLUDE_FROM_ALL + # Board support, shared with the other examples + ${EVB_DIR}/entry.S + ${EVB_DIR}/linflexd.c + ${EVB_DIR}/timer.c + ${EVB_DIR}/mpu.c + ${EVB_DIR}/gicv3.c + ${EVB_DIR}/irq_dispatch.c + ${EVB_DIR}/gic_probe.c + ${EVB_DIR}/cache.c + ${EVB_DIR}/tcm.c + ${EVB_DIR}/thread_mpu.c + ${EVB_DIR}/tx_initialize_low_level.S + + # Module manager port + ${MOD_DIR}/module_manager/src/tx_thread_schedule.S + ${MOD_DIR}/module_manager/src/tx_thread_context_save.S + ${MOD_DIR}/module_manager/src/tx_thread_context_restore.S + ${MOD_DIR}/module_manager/src/tx_thread_stack_build.S + ${MOD_DIR}/module_manager/src/tx_thread_system_return.S + ${MOD_DIR}/module_manager/src/txm_module_manager_svc_handler.S + ${MOD_DIR}/module_manager/src/txm_module_manager_fault_capture.S + ${MOD_DIR}/module_manager/src/txm_module_manager_user_mode_entry.S + ${MOD_DIR}/module_manager/src/txm_module_manager_thread_stack_build.S + ${MOD_DIR}/module_manager/src/txm_module_manager_mm_register_setup.c + ${MOD_DIR}/module_manager/src/txm_module_manager_memory_fault_handler.c + ${MOD_DIR}/module_manager/src/txm_module_manager_memory_fault_notify.c + ${MOD_DIR}/module_manager/src/txm_module_manager_alignment_adjust.c + ${MOD_DIR}/module_manager/src/txm_module_manager_external_memory_enable.c + ${MOD_DIR}/module_manager/src/txm_module_manager_offset_check.c + + # The module, as bytes rather than as objects. module_blob.S includes the + # raw image built by s32z280_demo_module.elf, so none of the module's + # symbols enter this link. + ${MOD_EVB}/module_blob.S + + # The application + ${MOD_EVB}/sample_threadx_module_manager.c + + # The portable module manager + ${TXM_MANAGER_SRC} + ) + + target_include_directories(s32z280_module.elf PRIVATE + ${EVB_DIR} + ${MOD_DIR}/inc + ${CMAKE_SOURCE_DIR}/common_modules/module_manager/inc + ${CMAKE_SOURCE_DIR}/common_modules/module_lib/inc + ${CMAKE_SOURCE_DIR}/common_modules/inc + ) + + target_compile_definitions(s32z280_module.elf PRIVATE + TXM_MODULE_MANAGER + TX_R52_USE_THREADX_IRQ + ) + + target_compile_options(s32z280_module.elf PRIVATE -g) + + # .incbin searches the assembler's include paths, not the source tree, and + # demo_module.bin is generated -- so the build directory has to be on that + # path or module_blob.S cannot find the image. + + set_source_files_properties(${MOD_EVB}/module_blob.S PROPERTIES + OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/demo_module.bin + COMPILE_OPTIONS "-Wa,-I${CMAKE_CURRENT_BINARY_DIR}" + ) + + add_dependencies(s32z280_module.elf s32z280_demo_module_bin) + + target_link_options(s32z280_module.elf PRIVATE + -T${MOD_EVB}/link_module.lds + -nostartfiles + -Wl,-Map=s32z280_module.map + ${EVB_LINK_QUIET_RWX} + ) + + target_link_libraries(s32z280_module.elf PRIVATE threadx_module) + +endif() diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/board.h b/ports/cortex_r52/gnu/example_build/s32z280_evb/board.h new file mode 100644 index 00000000..8c520d65 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/board.h @@ -0,0 +1,145 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* board.h Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Interface between the S32Z280 reset path and the C code it enters, */ +/* plus the layout of the structure a debugger reads to confirm what */ +/* the core reported about itself. */ +/* */ +/* Field order here is load-bearing: the debug script reads the */ +/* structure by offset, so members must not be reordered without */ +/* updating tools/read_identity.gdb alongside. */ +/* */ +/**************************************************************************/ + +#ifndef BOARD_H +#define BOARD_H + +/* Written to r52_identity.magic only after every other field is set, so a + reader can tell a complete structure from a half-filled one. */ + +#define R52_IDENTITY_MAGIC 0x52520001UL + +/* What the core reports about itself. EL2-only registers are captured by + entry.S before the drop to EL1; the rest are read in bsp_main. */ + +typedef struct r52_identity_struct +{ + unsigned int magic; + unsigned int midr; /* expect part 0xD13 = Cortex-R52 */ + unsigned int mpuir; /* EL1 MPU, DREGION in bits [15:8] */ + unsigned int ctr; + unsigned int mpidr; + unsigned int id_pfr0; + unsigned int id_pfr1; /* virtualisation field => EL2 */ + unsigned int sctlr_el1; /* MPU and cache enables as found */ + unsigned int hmpuir_el2; /* EL2 MPU, captured at EL2 */ + unsigned int cntfrq_el2; /* recorded, not programmed */ + unsigned int cpsr_el2; /* mode bits should read 0x1A, Hyp */ + unsigned int cpsr_el1; /* mode bits should read 0x13, SVC */ + unsigned int mpu_regions_el1; /* decoded from mpuir */ + unsigned int console_status; /* linflexd_init() result, 0 = OK */ +} r52_identity_t; + +extern r52_identity_t r52_identity; + +/* Entered from entry.S once EL1 is set up; never returns. Declared here to + satisfy MISRA Rule 8.4, which entry.S cannot do for itself. */ + +void bsp_main(void); + +/* Breakpoint target for the debug script; reached once r52_identity is + complete. Declared here so it has a prototype (MISRA Rule 8.4). */ + +void bsp_done(void); + +/* SCTLR as it reads after mpu_init(); M should be set. */ + +unsigned int read_sctlr_after_mpu(void); + +/* Called from el1_irq_entry in entry.S; declared here to satisfy MISRA + Rule 8.4, which assembly cannot do for itself. */ + +void board_irq_handler(void); + +/* The nesting path splits the handler in three: the acknowledge happens in IRQ + mode before nesting starts, and the end-of-interrupt after it finishes. + board_irq_service does the middle part on an already-acknowledged INTID and + neither acknowledges nor EOIs. */ + +void board_irq_service(unsigned long intid); + +/* Called from _tx_initialize_low_level when the kernel is linked. */ + +void board_init(void); + +/* Branches to a non-executable address to provoke a prefetch abort. Returns + via the abort handler, not normally. */ + +void try_execute_data(unsigned long address); + +/* Interrupt observations, written by the handler. */ + +extern volatile unsigned long board_irq_count; +extern volatile unsigned long board_timer_intid; +extern volatile unsigned long board_spurious_count; +extern volatile unsigned long board_unexpected_intid; +extern volatile unsigned long board_first_intid; + +/* Nesting observability. Inert unless the image was built with + TX_ENABLE_IRQ_NESTING: without it the handler runs in IRQ mode with + interrupts masked and can never be re-entered. */ + +extern volatile unsigned long board_nest_depth; +extern volatile unsigned long board_nest_max; +extern volatile unsigned long board_sgi_count; +extern volatile unsigned long board_sgi_nested_count; +extern volatile unsigned long board_nest_provoke; +extern volatile unsigned long board_priority_bits; +extern volatile unsigned long board_timer_priority; +extern volatile unsigned long board_sgi_priority; + +#define BOARD_NEST_SGI_INTID 8U + +/* FIQ observability. Two Group 0 SGIs, because FIQ nesting means an FIQ taken + while an FIQ handler is running, so a single source cannot show it. The + higher-priority one is raised from inside the lower one's handler. */ + +void board_fiq_handler(void); +void board_fiq_service(unsigned long intid); + +extern volatile unsigned long board_fiq_count; +extern volatile unsigned long board_fiq_high_count; +extern volatile unsigned long board_fiq_nested_count; +extern volatile unsigned long board_fiq_depth; +extern volatile unsigned long board_fiq_max_depth; +extern volatile unsigned long board_fiq_unexpected; +extern volatile unsigned long board_fiq_provoke; + +#define BOARD_FIQ_LOW_INTID 10U +#define BOARD_FIQ_HIGH_INTID 11U + +#endif /* BOARD_H */ diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/bsp_boot.c b/ports/cortex_r52/gnu/example_build/s32z280_evb/bsp_boot.c new file mode 100644 index 00000000..13ea498b --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/bsp_boot.c @@ -0,0 +1,1037 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* bsp_boot.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* First-boot verification for the NXP S32Z280-594EVB. Records what */ +/* the core reports about itself into a structure a debugger reads. */ +/* */ +/* There is no console on this board yet: LIN9 reaches the host through*/ +/* the daughtercard USB-UART, but its baud rate depends on clock */ +/* configuration that is not yet established, and a console that prints*/ +/* at the wrong rate produces garbage indistinguishable from a crash. */ +/* So this milestone reports through memory instead, which cannot be */ +/* mis-configured. The console arrives in the next step. */ +/* */ +/* Only EL1-accessible registers are read here. EL2-only ones */ +/* (HMPUIR, CNTFRQ) are captured by entry.S before the drop to EL1, */ +/* because reading them at EL1 would trap rather than return a value. */ +/* */ +/* MISRA C:2012 deviations (justified) */ +/* */ +/* Dir 4.3 -- assembly language shall be encapsulated and isolated. */ +/* Each MRC is wrapped in its own single-statement static function */ +/* below, which is the encapsulation the directive asks for. There */ +/* is no other way to read a coprocessor register from C. */ +/* */ +/* Rule 8.4 -- bsp_main is called from assembly and so has no C caller */ +/* to provide a prototype; it is declared in board.h, which entry.S */ +/* cannot include. */ +/* */ +/**************************************************************************/ + +#include "board.h" +#include "platform.h" +#include "linflexd.h" +#include "timer.h" +#include "gic_probe.h" +#include "mpu.h" +#include "gicv3.h" +#include "cache.h" +#include "tcm.h" + +/* Captured at EL2 by entry.S, before the drop to EL1. */ + +extern unsigned int boot_stage; +extern unsigned int cpsr_at_el2; +extern unsigned int cpsr_at_el1; +extern unsigned int cntfrq_at_el2; +extern unsigned int hmpuir_at_el2; +extern unsigned int probe_stage; +extern unsigned int fault_expected; +extern unsigned int fault_taken; + +/* Mirror of the console markers into memory. The UART has twice gone away at + the moment a marker mattered -- once when a stalled access took the debug + connection down with it, once when the USB passthrough detached -- and a + stalled access records no fault, so boot_stage alone cannot say how far the + risky sequence got. This can be read post-mortem without a console. */ + +#define MARK(n) do { probe_stage = (unsigned int)(n); } while (0) + +/* Filled in below and read by the debugger. Not static: the whole point is + for something outside this translation unit to find it. */ + +r52_identity_t r52_identity; + + +/* One MRC per function, each isolated, per MISRA Dir 4.3. */ + +static unsigned int read_midr(void) +{ + unsigned int value; + __asm volatile ("mrc p15, 0, %0, c0, c0, 0" : "=r" (value)); + return value; +} + +static unsigned int read_mpuir(void) +{ + unsigned int value; + __asm volatile ("mrc p15, 0, %0, c0, c0, 4" : "=r" (value)); + return value; +} + +static unsigned int read_ctr(void) +{ + unsigned int value; + __asm volatile ("mrc p15, 0, %0, c0, c0, 1" : "=r" (value)); + return value; +} + +static unsigned int read_mpidr(void) +{ + unsigned int value; + __asm volatile ("mrc p15, 0, %0, c0, c0, 5" : "=r" (value)); + return value; +} + +static unsigned int read_id_pfr0(void) +{ + unsigned int value; + __asm volatile ("mrc p15, 0, %0, c0, c1, 0" : "=r" (value)); + return value; +} + +static unsigned int read_id_pfr1(void) +{ + unsigned int value; + __asm volatile ("mrc p15, 0, %0, c0, c1, 1" : "=r" (value)); + return value; +} + +static unsigned int read_sctlr(void) +{ + unsigned int value; + __asm volatile ("mrc p15, 0, %0, c1, c0, 0" : "=r" (value)); + return value; +} + + +/**************************************************************************/ +/* report -- one "NAME = VALUE" line on the console. */ +/**************************************************************************/ + +static void report(const char *name, unsigned int value) +{ + linflexd_puts(name); + linflexd_puts(" = 0x"); + linflexd_put_hex32(value); + linflexd_putc('\n'); +} + + +/**************************************************************************/ +/* bsp_done -- breakpoint target, reached once the structure is complete.*/ +/* */ +/* Deliberately not static and deliberately not inlined: it exists purely*/ +/* so a debugger can break on a symbol. An optimising build would remove*/ +/* an empty static function, and then the script would have nothing to */ +/* attach to. */ +/**************************************************************************/ + +/* Clear CPSR.I so the GIC can deliver to this core. Interrupts stay masked + from reset all the way to here on purpose: nothing before this point is + prepared to service one. */ + +static void enable_irq_at_el1(void) +{ + __asm volatile ("cpsie i" ::: "memory"); + __asm volatile ("isb"); +} + + +/* Cache benchmark. + * + * Two things have to be right for a cache to show a measurable effect, and the + * first version of this test got both wrong. It used a 4 KB static buffer in + * DRAM0, a full-core-speed bank, and reported a 0.036% difference -- noise -- + * as a pass. A bank running at core speed leaves a cache almost nothing to + * save. DRAM2 runs at half core speed, which is why the buffer lives there. + * + * 1. The memory must be slower than the core. This uses the extended SRAM at + * S32Z_DRAM2_BASE, the RTU data bank that runs at half the core + * frequency, so caching it has something to show. + * + * 2. The working set must fit in the cache and be revisited. It is sized + * from CCSIDR at run time -- half the reported L1 data cache -- rather + * than from a guessed constant. A set larger than the cache would stream + * and evict, and would show little benefit even over slow memory. + * + * Reported alongside the timings so the numbers can be judged, not just + * believed. + */ + +/* Decimal, because a measurement read by a person should not need converting. + report() stays hex for register values, where hex is the right form. */ + +static void report_dec(unsigned long value) +{ + char digits[12]; + unsigned int n = 0U; + + if (value == 0UL) { linflexd_putc('0'); return; } + + while ((value > 0UL) && (n < 12U)) + { + digits[n] = (char) ('0' + (value % 10UL)); + value /= 10UL; + n++; + } + while (n > 0U) { n--; linflexd_putc(digits[n]); } +} + +static void report_dec_small(unsigned int value) +{ + report_dec((unsigned long) value); +} + +#define BUFFER_BASE S32Z_DRAM2_BASE + +static unsigned long cache_bench_words; + +/* Aligned to a cache line, and that alignment is load-bearing rather than + cosmetic. Without it this measurement is bimodal: it reports either about + 24% cache benefit or none at all, decided purely by where the loop happens + to fall relative to a 64-byte line, and therefore by any unrelated change + that shifts code ahead of it. Adding two nop instructions to the startup + file was enough to flip it, which is how a run of conclusions drawn from + this probe -- including a defect report to NXP -- turned out to be measuring + code layout rather than the part. + + Pinning the entry point does not make the workload fast or slow, it makes it + the same across builds, which is the only property that lets two numbers be + compared. */ + +/* Four copies of the same loop, each 64-byte aligned and then displaced by a + different offset within the line. The measurement runs all four and reports + the spread. + + One copy is not enough, and a single number from this workload is actively + misleading. Unpinned, it reported either about 24% cache benefit or none at + all depending on where the loop fell relative to a 64-byte line -- two nop + instructions added to the startup file flipped it. Pinning the entry to a + line does not fix that, it just picks one of the two modes and hides the + other: aligned to 64 the loop sits permanently in the low mode. + + The variation is real behaviour, not noise. Both modes reproduce exactly + across runs. The cold pass runs with both caches off, so it is heavily + instruction-fetch bound out of code RAM at half the core frequency, and how + the loop straddles lines decides how much of the data cache's contribution + is visible at all. Reporting min and max across alignments says that out + loud instead of letting one arbitrary placement stand in for the part. */ + +#define CACHE_WORKLOAD_COPIES 4U + +#define MAKE_CACHE_WORKLOAD(name, pad_words) \ +__attribute__((aligned(64), noinline)) \ +static void name(volatile unsigned int *buffer) \ +{ \ + unsigned long pass; \ + unsigned long i; \ + \ + __asm__ volatile(".rept " #pad_words "\n\tnop\n\t.endr"); \ + \ + for (pass = 0UL; pass < 32UL; pass++) \ + { \ + for (i = 0UL; i < cache_bench_words; i++) \ + { \ + buffer[i] = buffer[i] + i + pass; \ + } \ + } \ +} + +MAKE_CACHE_WORKLOAD(cache_workload_a, 0) +MAKE_CACHE_WORKLOAD(cache_workload_b, 4) +MAKE_CACHE_WORKLOAD(cache_workload_c, 8) +MAKE_CACHE_WORKLOAD(cache_workload_d, 12) + +static void (*const cache_workloads[CACHE_WORKLOAD_COPIES])(volatile unsigned int *) = +{ + cache_workload_a, /* loop at line offset 0 */ + cache_workload_b, /* loop at line offset 16 */ + cache_workload_c, /* loop at line offset 32 */ + cache_workload_d /* loop at line offset 48 */ +}; + +static void cache_workload(void) +{ + cache_workloads[0]((volatile unsigned int *) BUFFER_BASE); +} + + +unsigned int fault_syndrome_value(void) +{ + extern unsigned int fault_syndrome; + return fault_syndrome; +} + + +unsigned int fault_address_value(void) +{ + extern unsigned int fault_address; + return fault_address; +} + + +unsigned int read_sctlr_after_mpu(void) +{ + return read_sctlr(); +} + + +void bsp_done(void) +{ + __asm volatile ("nop"); +} + + +/**************************************************************************/ +/* bsp_main -- entered at EL1 (Supervisor) from entry.S. Never returns. */ +/**************************************************************************/ + +void bsp_main(void) +{ + /* Written last, so a debugger that finds the magic knows every other + field is already valid. A partially filled structure would otherwise + be indistinguishable from a complete one. */ + + r52_identity.magic = 0U; + + r52_identity.midr = read_midr(); + r52_identity.mpuir = read_mpuir(); + r52_identity.ctr = read_ctr(); + r52_identity.mpidr = read_mpidr(); + r52_identity.id_pfr0 = read_id_pfr0(); + r52_identity.id_pfr1 = read_id_pfr1(); + r52_identity.sctlr_el1 = read_sctlr(); + + r52_identity.hmpuir_el2 = hmpuir_at_el2; + r52_identity.cntfrq_el2 = cntfrq_at_el2; + r52_identity.cpsr_el2 = cpsr_at_el2; + r52_identity.cpsr_el1 = cpsr_at_el1; + + /* Derived counts, so the debugger script does not have to know where the + field sits in each register. MPUIR reports DREGION in bits [15:8]. + HMPUIR is recorded raw as well, because on this part the count appears + in bits [7:0] instead and that layout still needs confirming against + the Armv8-R specification. */ + + r52_identity.mpu_regions_el1 = (r52_identity.mpuir >> 8) & 0xFFU; + + r52_identity.magic = R52_IDENTITY_MAGIC; + + /* Report over the console as well as through memory. The memory copy + stays: it is what proved the boot path before a console existed, and it + still works if the console itself is misconfigured. */ + + r52_identity.console_status = linflexd_init(); + linflexd_puts("\n=== ThreadX Cortex-R52 :: NXP S32Z280-594EVB ===\n"); + report("MIDR ", r52_identity.midr); + report("MPUIR ", r52_identity.mpuir); + report("HMPUIR ", r52_identity.hmpuir_el2); + report("CTR ", r52_identity.ctr); + report("MPIDR ", r52_identity.mpidr); + report("ID_PFR0 ", r52_identity.id_pfr0); + report("ID_PFR1 ", r52_identity.id_pfr1); + report("SCTLR ", r52_identity.sctlr_el1); + report("CNTFRQ ", r52_identity.cntfrq_el2); + report("CPSR@EL2 ", r52_identity.cpsr_el2); + report("CPSR@EL1 ", r52_identity.cpsr_el1); + linflexd_puts("EL1 MPU regions: "); + linflexd_put_hex32(r52_identity.mpu_regions_el1); + linflexd_putc('\n'); + report("CONSOLE ", r52_identity.console_status); + + /* --- generic timer ---------------------------------------------------- + CNTFRQ is now programmed at EL2 (8 MHz, measured and cross-checked + against CFG_CNTDV and FXOSC). Prove the timer end to end: read it back, + then arm a one-second one-shot and poll for it, bracketed by CNTPCT + samples. If the counter and the compare agree, the elapsed count is + CNTFRQ; if the host also sees one second pass, the rate itself is right + rather than merely self-consistent. + + Interrupt stays masked -- no GIC yet. Polling first means a later + interrupt problem cannot be confused with the timer being wrong. */ + + report("CNTFRQ ", timer_read_cntfrq()); + + { + unsigned long long before; + unsigned long long after; + unsigned int hz = timer_read_cntfrq(); + + /* Sample AFTER the message, not before: at 115200 the line itself is + about 2 ms, and sampling first puts that inside the measurement. */ + + linflexd_puts("TIMER arming 1s one-shot\n"); + before = timer_read_cntpct(); + timer_start_oneshot(hz); + + while (timer_fired() == 0U) + { + /* poll */ + } + + after = timer_read_cntpct(); + timer_stop(); + + linflexd_puts("TIMER fired, elapsed counts = 0x"); + linflexd_put_hex32((unsigned int)((after - before) >> 32)); + linflexd_put_hex32((unsigned int)(after - before)); + linflexd_puts("\n"); + } + + /* --- where is the GIC, and is the port to it open? --------------------- + System-register reads only: a load from the GIC window stalls the core, + so nothing here touches memory. */ + + report("IMP_CBAR ", gic_read_cbar()); + report("PERIPHPRG", gic_read_periphpregionr()); + + /* The peripheral port is now enabled at EL2, so RTU peripheral space + should be reachable. Marker first: if this still stalls, the run dies + here and says so. */ + + linflexd_puts("P1 RTU0.GPR CFG_CNTDV via LLPP\n"); + report("CFG_CNTDV", *(volatile unsigned int *)S32Z_RTU0_GPR_CFG_CNTDV); + + /* --- TCM: what the hardware reports about itself --------------------- + Reads only. platform.h has recorded since bring-up that the TCM windows + fault at reset, and the Cortex-R52 TRM says why: every bit of the region + registers resets to zero apart from SIZE and WAITSTATES, so the banks sit + at base 0 with both enables clear. This confirms that on the part rather + than trusting it, and cross-checks the reported sizes against the S32Z2 + reference manual, which documents TCMA 64KB, TCMB 16KB and TCMC 16KB. + + Nothing is written here. Enabling TCM also requires initialising the ECC + bits before any read (TRM 6.2.2), with ATCM needing 64-bit aligned stores + where BTCM and CTCM accept 32-bit, so the enable belongs in its own step + once these values are known. */ + + linflexd_puts("T1 TCM configuration as reported by the core\n"); + { + static const char *const bank_name[3] = { "ATCM", "BTCM", "CTCM" }; + static const unsigned long expect_bytes[3] = { 0x10000UL, 0x4000UL, 0x4000UL }; + unsigned int i; + + for (i = 0U; i < 3U; i++) + { + tcm_bank_t bank; + + tcm_read_bank(i, &bank); + + linflexd_puts(bank_name[i]); + linflexd_puts(" raw=0x"); + linflexd_put_hex32((unsigned int) bank.raw); + linflexd_puts(" base=0x"); + linflexd_put_hex32((unsigned int) bank.base); + linflexd_puts(" size=0x"); + linflexd_put_hex32((unsigned int) bank.size_bytes); + linflexd_puts(" ws="); + linflexd_putc((char) ('0' + (char) bank.wait_states)); + linflexd_puts(" en2="); + linflexd_putc((char) ('0' + (char) bank.enable_el2)); + linflexd_puts(" en10="); + linflexd_putc((char) ('0' + (char) bank.enable_el10)); + linflexd_puts((bank.size_bytes == expect_bytes[i]) + ? " size matches the RM\n" + : " SIZE DISAGREES WITH THE RM\n"); + } + + report("MEMPROTCTLR", (unsigned int) tcm_read_memprotctlr()); + linflexd_puts("ECC implemented = "); + linflexd_putc((char) ('0' + (char) tcm_ecc_implemented())); + linflexd_puts(", enabled = "); + linflexd_putc((char) ('0' + (char) tcm_ecc_enabled())); + linflexd_putc('\n'); + } + + /* --- TCM: enable, preload, prove ------------------------------------ + Done before the MPU goes on, so a failure here is not confused with a + missing MPU region. The preload is not optional: ECC is enabled on this + part, so a location read before it has been written reports an error, and + that error would look exactly like an inaccessible TCM. */ + + MARK(0x20); + linflexd_puts("T2 TCM state as entry.S left it\n"); + { + static const char *const bank_name[3] = { "ATCM", "BTCM", "CTCM" }; + unsigned int i; + unsigned int atcm_ok; + + /* Verification only. Nothing is programmed here: ENABLEEL2 is ignored + when written from EL1, so TCM setup belongs at EL2 in entry.S, and + calling tcm_enable() from here would additionally switch on the banks + entry.S deliberately leaves off. */ + + for (i = 0U; i < 3U; i++) + { + tcm_bank_t bank; + + tcm_read_bank(i, &bank); + + linflexd_puts(bank_name[i]); + linflexd_puts(" base=0x"); + linflexd_put_hex32((unsigned int) bank.base); + linflexd_puts(" en2="); + linflexd_putc((char) ('0' + (char) bank.enable_el2)); + linflexd_puts(" en10="); + linflexd_putc((char) ('0' + (char) bank.enable_el10)); + linflexd_puts((bank.enable_el10 != 0U) + ? " enabled\n" + : " off on purpose, see entry.S\n"); + } + + tcm_read_bank(0U, &(tcm_bank_t){0}); + { + tcm_bank_t atcm; + + tcm_read_bank(0U, &atcm); + atcm_ok = ((atcm.base == S32Z_ATCM_BASE) && + (atcm.enable_el2 != 0U) && (atcm.enable_el10 != 0U)) ? 1U : 0U; + } + + if (atcm_ok != 0U) + { + MARK(0x21); + linflexd_puts("T3 preloading ATCM to establish ECC check bits\n"); + tcm_preload(0U, S32Z_ATCM_BASE, S32Z_ATCM_SIZE); + + /* BTCM too, now that entry.S enables it. Whole bank: nothing is + placed there at link time, and with ECC on, a location must be + written before it can be read. BTCM accepts 32-bit stores where + ATCM needs 64-bit (TRM 6.2.2); tcm_preload handles the + difference. */ + + linflexd_puts("T3b preloading BTCM\n"); + tcm_preload(1U, S32Z_BTCM_BASE, S32Z_BTCM_SIZE); + + MARK(0x22); + linflexd_puts("T4 write and read back ATCM\n"); + { + volatile unsigned int *first = (volatile unsigned int *) S32Z_ATCM_BASE; + volatile unsigned int *last = + (volatile unsigned int *) (S32Z_ATCM_BASE + S32Z_ATCM_SIZE - 4UL); + + *first = 0xA5A5A5A5U; + *last = 0x5A5A5A5AU; + + linflexd_puts(((*first == 0xA5A5A5A5U) && (*last == 0x5A5A5A5AU)) + ? "ATCM PASS first and last word hold\n" + : "ATCM FAIL readback mismatch\n"); + } + } + else + { + linflexd_puts("T3 skipped: ATCM is not enabled as expected\n"); + } + } + + /* --- MPU, then the GIC ------------------------------------------------ + The GIC needs its region mapped Device nGnRnE (Cortex-R52 TRM), which + needs the MPU on. Markers around the enable: if the console survives it, + the UART region is right; if it does not, that is the answer. */ + + MARK(0x10); + linflexd_puts("M1 enabling MPU\n"); + { + unsigned int regions = mpu_init(); + + MARK(0x11); + + /* Read back what the region programming actually did. Five regions die on + the SCTLR.M write while three survive, and the difference is the region + COUNT rather than the Device attribute -- so the question is whether + PRSELR/PRBAR/PRLAR programming takes effect at all for indices 3 and 4, + which nothing has ever exercised: the FVP example this code came from + only ever programs three regions. The MPU is not enabled in this build, + so this cannot stall. */ + + linflexd_puts("R idx PRBAR PRLAR\n"); + { + unsigned int i; + for (i = 0U; i < 8U; i++) + { + unsigned long prbar = 0UL; + unsigned long prlar = 0UL; + + mpu_read_region(i, &prbar, &prlar); + linflexd_puts("R "); + linflexd_put_hex32(i); + linflexd_puts(" "); + linflexd_put_hex32((unsigned int) prbar); + linflexd_puts(" "); + linflexd_put_hex32((unsigned int) prlar); + linflexd_putc('\n'); + } + } + + + report("MPU rgns ", regions); + report("SCTLR ", read_sctlr_after_mpu()); + MARK(0x12); + linflexd_puts("M2 MPU enabled, console alive\n"); + + linflexd_puts("M3 GICD_PIDR2 (was stalling before the MPU)\n"); + MARK(0x13); + report("GICD_PIDR2", *(volatile unsigned int *)(S32Z_GIC_BASE + 0xFFE8UL)); + MARK(0x14); + report("GICD_TYPER", *(volatile unsigned int *)(S32Z_GIC_BASE + 0x0004UL)); + MARK(0x15); + report("GICR_PIDR2", *(volatile unsigned int *)(S32Z_GIC_BASE + 0x10FFE8UL)); + MARK(0x16); + linflexd_puts("M4 GIC reachable\n"); + + /* TCM again, now that the MPU is enforcing. The TRM requires an MPU + region per TCM before it can be used, so this is the check that those + three regions are right -- an enabled TCM with no region faults here + and nowhere earlier. */ + + MARK(0x23); + linflexd_puts("T5 ATCM still reachable with the MPU on\n"); + { + volatile unsigned int *p = (volatile unsigned int *) S32Z_ATCM_BASE; + tcm_bank_t atcm; + unsigned int answers; + + tcm_read_bank(0U, &atcm); + + *p = 0x5A5A0000U; + answers = (*p == 0x5A5A0000U) ? 1U : 0U; + + /* Both halves matter. A disabled TCM's range is serviced through + AXIM, so memory answering there says nothing about the TCM: an + earlier version of this check reported every bank accessible while + two of them were disabled. */ + + if ((answers != 0U) && (atcm.enable_el10 != 0U)) + { + linflexd_puts("ATCM PASS enabled and holding data under MPU\n"); + } + else if (answers != 0U) + { + linflexd_puts("ATCM NOT TCM: address answers but the bank is\n" + " disabled, so this is AXIM behind it\n"); + } + else + { + linflexd_puts("ATCM FAIL address does not answer\n"); + } + } + + /* --- interrupts ------------------------------------------------- + Bring up the GIC properly, enable the timer PPI, arm a 10 ms + one-shot and unmask IRQ at EL1. Then wait and see whether the + handler runs. Polling proved the timer counts and fires; this + proves the interrupt is delivered, which is a different claim. */ + + MARK(0x50); + /* Copy the handler into ATCM now, and not earlier: T4 and T5 write test + patterns to the first and last words of the bank, which would land on top + of this code. The ECC preload in T3 has already established check bits + for the whole bank, and the 64-bit stores in the copy maintain them for + every location it writes. */ + + linflexd_puts("T6 copying .atcm_text into ATCM\n"); + { + unsigned long copied = tcm_copy_atcm_text(); + + report("atcm bytes", (unsigned int) copied); + if (copied == 0UL) + { + linflexd_puts(" nothing placed in ATCM in this image\n"); + } + } + + timer_cycles_enable(); + + linflexd_puts("I1 gicv3_init\n"); + gicv3_init(); + + MARK(0x51); + /* Enable the WHOLE PPI range rather than just 30. All three counters + reading zero means nothing reached the CPU interface, and an enabled + but wrong INTID produces exactly that silence -- so let the hardware + say which PPI the timer actually drives. This is how INTID 30 was + established on the FVP too: assume nothing, record what arrives. */ + + linflexd_puts("I2 enable PPIs 16..31\n"); + { + unsigned int ppi; + for (ppi = 16U; ppi <= 31U; ppi++) + { + gicv3_enable_ppi(ppi, 0xA0U); + } + } + + MARK(0x52); + linflexd_puts("I3 arm timer, unmask IRQ\n"); + timer_start_oneshot_irq(timer_read_cntfrq() / 100U); /* 10 ms */ + enable_irq_at_el1(); + + MARK(0x53); + { + extern unsigned int board_service_count; + unsigned int spins = 0U; + + /* Wait for ticks. Bounded: a missing interrupt must report + itself rather than hang the boot. */ + + /* Wait for a full set of latency samples, not just a few ticks: + the figure of interest is the worst case, and a handful of + samples cannot show it. Still bounded, so a missing interrupt + reports itself rather than hanging the boot. */ + + while ((board_service_count < 64U) && (spins < 400U)) + { + timer_spin(200000U); + spins++; + } + } + + MARK(0x54); + report("IRQ count", (unsigned int) board_irq_count); + report("timer INTID", (unsigned int) board_timer_intid); + report("spurious ", (unsigned int) board_spurious_count); + report("unexpected", (unsigned int) board_unexpected_intid); + + /* Handler body execution time. Reported as min, max and mean rather + than mean alone: the spread is the point. */ + + { + extern unsigned int board_service_cycles[]; + extern unsigned int board_service_count; + + unsigned int i; + unsigned int lo = 0xFFFFFFFFU; + unsigned int hi = 0U; + unsigned long sum = 0UL; + unsigned int n; + + /* Stop the source and snapshot the count before reading any of it. + The timer re-arms inside the handler, so samples keep arriving + otherwise: the first version of this read let the count grow + between computing the extremes and dividing for the mean, and + printed a mean below the minimum. */ + + timer_stop(); + n = board_service_count; + + for (i = 0U; i < n; i++) + { + unsigned int v = board_service_cycles[i]; + + if (v < lo) { lo = v; } + if (v > hi) { hi = v; } + sum += (unsigned long) v; + } + + linflexd_puts("I5 handler body cycles, four placements "); +#ifdef TX_R52_ATCM_ISR + linflexd_puts("(bodies in ATCM)\n"); +#else + linflexd_puts("(bodies in code RAM)\n"); +#endif + report("samples ", n); + + /* Per placement, because the aggregate hides exactly the thing that + invalidated the previous version of this comparison. 16 samples + each, in the order the wrapper rotated through them. */ + + { + unsigned int slot; + unsigned int each = 64U / 4U; + + for (slot = 0U; slot < 4U; slot++) + { + unsigned int s_lo = 0xFFFFFFFFU; + unsigned int s_hi = 0U; + unsigned long s_sum = 0UL; + unsigned int c = 0U; + unsigned int m; + + for (m = slot * each; (m < ((slot + 1U) * each)) && (m < n); m++) + { + unsigned int v = board_service_cycles[m]; + + if (v < s_lo) { s_lo = v; } + if (v > s_hi) { s_hi = v; } + s_sum += (unsigned long) v; + c++; + } + + linflexd_puts(" offset "); + report_dec((unsigned long) (slot * 16U)); + linflexd_puts(": "); + if (c == 0U) + { + linflexd_puts("no samples\n"); + } + else + { + linflexd_puts("min "); + report_dec((unsigned long) s_lo); + linflexd_puts(" max "); + report_dec((unsigned long) s_hi); + linflexd_puts(" mean "); + report_dec(s_sum / c); + linflexd_puts("\n"); + } + } + } + } + report("first INTID", (unsigned int) board_first_intid); + linflexd_puts("I4 interrupt test done\n"); + } + + /* --- GIC: not reachable yet, probe deliberately removed ---------------- + The GIC window NXP's memory map gives for this cluster, 0x47800000, is + unreachable from BOTH paths in the current state. The debugger cannot + read any of it, and a load from the core stalls the core outright: the + run dies at the marker before the access, with no fault recorded and the + debug connection dropped -- the same signature as RTU0.GPR. + + The Reference Manual contains no GIC base address anywhere, so that + address rests entirely on NXP's debugger script. What the manual does + mention is the R52's "Arm low-latency peripheral port". On Cortex-R52 + the LLPP is an implementation-defined region that software programs and + enables; if it is disabled out of reset, or covers a different base, + accesses to a peripheral in that window stall exactly like this. + Settling it needs the Cortex-R52 TRM, which documents the region + registers -- NXP's manual does not. + + The probe is removed rather than left disabled: while it was present the + image stalled here every run, which costs the timer and console results + that come before it. */ + + /* --- caches ------------------------------------------------------------ + Enabled last, and measured rather than merely switched on. Reading + SCTLR back proves a bit was set; timing the same workload before and + after proves the caches are actually doing something. The generic timer + supplies the clock -- 8 MHz, established earlier -- so the comparison is + in real units rather than loop counts. */ + + MARK(0x70); + report("CLIDR ", (unsigned int) cache_read_clidr()); + report("D line ", (unsigned int) cache_dcache_line_bytes()); + report("D ways ", (unsigned int) cache_dcache_ways()); + report("D sets ", (unsigned int) cache_dcache_sets()); + report("D bytes ", (unsigned int) cache_dcache_bytes()); + + /* Half the data cache, in words: fits comfortably and is revisited every + pass, which is the case a cache is meant to win. */ + + cache_bench_words = cache_dcache_bytes() / (2UL * sizeof(unsigned int)); + report("bench wds", (unsigned int) cache_bench_words); + + /* Measured at four loop alignments, not one, and the spread is reported + because it is large and real. See the comment on the workload copies. */ + + /* Three memories, four loop alignments each. The memories are the point: + DRAM2 runs at half the core frequency, DRAM0 at full, and BTCM at full + with zero wait states and no cache in the path at all -- an enabled TCM is + Non-cacheable Non-shareable Normal memory whatever the MPU says. + + The alignment sweep is what makes the three comparable. A single figure + per memory would be picking one of two modes per memory and calling it a + result, which is how this example previously produced a defect report that + had to be withdrawn. */ + + linflexd_puts("C1 timing three memories, four loop alignments each\n"); + { + static const struct + { + const char *name; + unsigned long base; + } memories[3] = + { + { "DRAM2 half-speed", S32Z_DRAM2_BASE }, + { "DRAM0 full-speed", S32Z_DRAM0_BASE + 0x10000UL }, + { "BTCM 0 wait ", S32Z_BTCM_BASE } + }; + + unsigned long overall_hi = 0UL; + unsigned int m; + + for (m = 0U; m < 3U; m++) + { + unsigned int k; + + linflexd_puts(" "); + linflexd_puts(memories[m].name); + linflexd_puts(" at 0x"); + linflexd_put_hex32((unsigned int) memories[m].base); + linflexd_puts("\n"); + + for (k = 0U; k < CACHE_WORKLOAD_COPIES; k++) + { + volatile unsigned int *buf = + (volatile unsigned int *) memories[m].base; + unsigned long long before; + unsigned long long after; + unsigned long cold; + unsigned long warm; + unsigned long gain; + + cache_disable_all(); + + before = timer_read_cntpct(); + cache_workloads[k](buf); + after = timer_read_cntpct(); + cold = (unsigned long) (after - before); + + cache_enable(); + + before = timer_read_cntpct(); + cache_workloads[k](buf); + after = timer_read_cntpct(); + warm = (unsigned long) (after - before); + + gain = (cold > warm) ? (((cold - warm) * 1000UL) / cold) : 0UL; + if (gain > overall_hi) { overall_hi = gain; } + + linflexd_puts(" offset "); + report_dec((unsigned long) (k * 16U)); + linflexd_puts(": cold "); + report_dec(cold); + linflexd_puts(" warm "); + report_dec(warm); + linflexd_puts(" gain/1000 "); + report_dec(gain); + linflexd_puts("\n"); + } + } + + MARK(0x72); + if (cache_enabled() == 1U) + { + linflexd_puts("C3 PASS caches enabled (SCTLR.C and SCTLR.I set)\n"); + } + else + { + linflexd_puts("C3 FAIL caches did not enable\n"); + } + + /* The claim is that the caches can help somewhere, which needs one + memory at one alignment to show it. BTCM is expected to show nothing: + it is not cached, so there is nothing for enabling the cache to + improve, and a zero there is the correct answer rather than a + failure. */ + + if (overall_hi >= 100UL) + { + linflexd_puts("C4 PASS caches measurably faster somewhere\n"); + } + else + { + linflexd_puts("C4 no memory and alignment showed a 10% speedup\n"); + } + } + + /* --- protection test --------------------------------------------------- + The map claims code is read-only. Prove it: arm the recoverable abort + path, write to the code region, and check that a fault was actually + taken. Reading SCTLR back or listing the regions would only show what + was programmed; provoking the violation shows it is enforced. + + This is worth doing now for a second reason: until SCTLR.TE was cleared + no fault handler could run at all, so no protection claim about this + board could be checked. */ + + MARK(0x60); + linflexd_puts("X1 write to read-only code region\n"); + fault_taken = 0U; + fault_expected = 1U; + + *(volatile unsigned int *)S32Z_CODE_SRAM_BASE = 0xDEADBEEFUL; + + MARK(0x61); + report("faults ", fault_taken); + report("fault DFSR", fault_syndrome_value()); + report("fault addr", fault_address_value()); + + if (fault_taken == 1U) + { + linflexd_puts("X2 PASS write to code faulted and was recovered\n"); + } + else + { + linflexd_puts("X2 FAIL write to code did NOT fault -- code is writable\n"); + } + + /* --- second protection case: execute from data ------------------------- + The map marks the data region execute-never. Prove that too: branch + into it and check a prefetch abort was taken. Separate from the + write-to-code case because they exercise different permissions and + different fault paths -- a map could plausibly enforce one and not the + other, and DFSR/IFSR come from different registers. */ + + MARK(0x62); + linflexd_puts("X3 execute from non-executable data region\n"); + fault_taken = 0U; + + try_execute_data(S32Z_DATA_SRAM_BASE); + + MARK(0x63); + report("faults ", fault_taken); + report("fault IFSR", fault_syndrome_value()); + report("fault addr", fault_address_value()); + + if (fault_taken == 1U) + { + linflexd_puts("X4 PASS execute from data faulted and was recovered\n"); + } + else + { + linflexd_puts("X4 FAIL data region is executable\n"); + } + + /* Deliberate breakpoint target. The debug script sets a hardware + breakpoint here, so "the image ran to completion" is something the script + observes rather than infers from a timeout. */ + + bsp_done(); + + /* Park. The debugger halts the core and reads r52_identity. + A busy loop rather than WFI: this core's debug behaviour is still being + characterised, and a core sitting in a low-power state is one more + variable between "the code ran" and "the debugger sees it". The nop + also stops the compiler discarding a side-effect-free infinite loop. */ + + for (;;) + { + __asm volatile ("nop"); + } +} diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/cache.c b/ports/cortex_r52/gnu/example_build/s32z280_evb/cache.c new file mode 100644 index 00000000..a7b0d6a1 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/cache.c @@ -0,0 +1,346 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* cache.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frederic Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Cache maintenance and enable for the S32Z280-594EVB. */ +/* */ +/* The FVP example enables SCTLR.C and SCTLR.I but only invalidates the*/ +/* instruction cache, with a note that a data-cache set/way sweep */ +/* belongs with silicon bring-up "where it can be verified against the */ +/* real cache geometry". This is that: the sweep here reads CLIDR and */ +/* CCSIDR and walks every set and way the hardware reports, rather than*/ +/* assuming a size. */ +/* */ +/* The sweep matters more here than on a model. This image is written */ +/* into SRAM by a debugger with the caches off, so nothing has been */ +/* allocated yet -- but the caches are not architecturally guaranteed */ +/* to be invalid out of reset, and enabling a write-back data cache */ +/* holding stale valid lines would evict them over live memory. */ +/* */ +/* A CONSEQUENCE WORTH KNOWING */ +/* */ +/* With the write-back data cache on, values this image writes may sit */ +/* dirty in cache where a DEBUGGER READING SRAM CANNOT SEE THEM. The */ +/* memory-resident progress markers and fault records used throughout */ +/* this bring-up are read exactly that way. They stay reliable only */ +/* because cache_clean_all() is called before parking; without that a */ +/* post-mortem read could report stale values and look like a fault */ +/* that never happened. */ +/* */ +/* MISRA C:2012 deviations (justified) */ +/* */ +/* Dir 4.3 -- assembly encapsulated one operation per function. */ +/* */ +/**************************************************************************/ + +#include "cache.h" + +#define SCTLR_C (1UL << 2) +#define SCTLR_I (1UL << 12) + + +static unsigned long read_sctlr(void) +{ + unsigned long value; + __asm volatile ("mrc p15, 0, %0, c1, c0, 0" : "=r" (value)); + return value; +} + +static void write_sctlr(unsigned long value) +{ + __asm volatile ("mcr p15, 0, %0, c1, c0, 0" :: "r" (value)); +} + +static void data_sync_barrier(void) +{ + __asm volatile ("dsb sy" ::: "memory"); +} + +static void instruction_barrier(void) +{ + __asm volatile ("isb sy" ::: "memory"); +} + + +unsigned long cache_read_clidr(void) +{ + unsigned long value; + __asm volatile ("mrc p15, 1, %0, c0, c0, 1" : "=r" (value)); + return value; +} + + +unsigned long cache_read_ccsidr(void) +{ + unsigned long value; + __asm volatile ("mrc p15, 1, %0, c0, c0, 0" : "=r" (value)); + return value; +} + + +/* Select a cache level and type for CCSIDR to describe. Level is zero-based; + type 0 is data/unified, 1 is instruction. */ + +static void select_cache(unsigned long level, unsigned long instruction) +{ + unsigned long csselr = (level << 1) | (instruction & 1UL); + + __asm volatile ("mcr p15, 2, %0, c0, c0, 0" :: "r" (csselr)); + instruction_barrier(); +} + + +void cache_invalidate_icache_all(void) +{ + unsigned long zero = 0UL; + + __asm volatile ("mcr p15, 0, %0, c7, c5, 0" :: "r" (zero)); + data_sync_barrier(); + instruction_barrier(); +} + + +/**************************************************************************/ +/* cache_invalidate_dcache_all -- set/way sweep over real geometry */ +/**************************************************************************/ + +void cache_invalidate_dcache_all(void) +{ + unsigned long clidr = cache_read_clidr(); + unsigned long level; + + for (level = 0UL; level < 7UL; level++) + { + unsigned long ctype = (clidr >> (level * 3UL)) & 7UL; + unsigned long ccsidr; + unsigned long line_shift; + unsigned long ways; + unsigned long sets; + unsigned long way_shift; + unsigned long way; + + /* 0 = none, 1 = instruction only: neither needs a data sweep. */ + + if ((ctype == 0UL) || (ctype == 1UL)) + { + continue; + } + + select_cache(level, 0UL); + ccsidr = cache_read_ccsidr(); + + line_shift = (ccsidr & 7UL) + 4UL; /* log2 line bytes */ + ways = ((ccsidr >> 3) & 0x3FFUL) + 1UL; + sets = ((ccsidr >> 13) & 0x7FFFUL) + 1UL; + + /* Way number sits in the top bits of the DCISW operand, positioned by + the associativity: log2(ways) rounded up. */ + + way_shift = 32UL; + { + unsigned long probe = ways - 1UL; + way_shift = 0UL; + while (probe != 0UL) + { + probe >>= 1; + way_shift++; + } + way_shift = 32UL - way_shift; + } + + for (way = 0UL; way < ways; way++) + { + unsigned long set; + + for (set = 0UL; set < sets; set++) + { + unsigned long operand = (way << way_shift) + | (set << line_shift) + | (level << 1); + + /* DCISW: invalidate data cache line by set/way. */ + + __asm volatile ("mcr p15, 0, %0, c7, c6, 2" :: "r" (operand)); + } + } + } + + data_sync_barrier(); + instruction_barrier(); +} + + +/**************************************************************************/ +/* cache_clean_all -- write dirty lines back to memory */ +/* */ +/* Needed before a debugger reads anything this image wrote: with a */ +/* write-back cache the value may exist only in cache. */ +/**************************************************************************/ + +void cache_clean_all(void) +{ + unsigned long clidr = cache_read_clidr(); + unsigned long level; + + for (level = 0UL; level < 7UL; level++) + { + unsigned long ctype = (clidr >> (level * 3UL)) & 7UL; + unsigned long ccsidr; + unsigned long line_shift; + unsigned long ways; + unsigned long sets; + unsigned long way_shift; + unsigned long way; + + if ((ctype == 0UL) || (ctype == 1UL)) + { + continue; + } + + select_cache(level, 0UL); + ccsidr = cache_read_ccsidr(); + + line_shift = (ccsidr & 7UL) + 4UL; + ways = ((ccsidr >> 3) & 0x3FFUL) + 1UL; + sets = ((ccsidr >> 13) & 0x7FFFUL) + 1UL; + + { + unsigned long probe = ways - 1UL; + way_shift = 0UL; + while (probe != 0UL) + { + probe >>= 1; + way_shift++; + } + way_shift = 32UL - way_shift; + } + + for (way = 0UL; way < ways; way++) + { + unsigned long set; + + for (set = 0UL; set < sets; set++) + { + unsigned long operand = (way << way_shift) + | (set << line_shift) + | (level << 1); + + /* DCCSW: clean data cache line by set/way. */ + + __asm volatile ("mcr p15, 0, %0, c7, c10, 2" :: "r" (operand)); + } + } + } + + data_sync_barrier(); +} + + +/**************************************************************************/ +/* cache_enable -- invalidate both caches, then turn them on */ +/**************************************************************************/ + +void cache_enable(void) +{ + unsigned long sctlr; + + cache_invalidate_dcache_all(); + cache_invalidate_icache_all(); + + sctlr = read_sctlr(); + sctlr |= SCTLR_C | SCTLR_I; + write_sctlr(sctlr); + + data_sync_barrier(); + instruction_barrier(); +} + + +/* L1 data cache geometry, decoded from CCSIDR with level 0 selected. */ + +static unsigned long dcache_ccsidr(void) +{ + select_cache(0UL, 0UL); + return cache_read_ccsidr(); +} + + +unsigned long cache_dcache_line_bytes(void) +{ + return 1UL << ((dcache_ccsidr() & 7UL) + 4UL); +} + + +unsigned long cache_dcache_ways(void) +{ + return ((dcache_ccsidr() >> 3) & 0x3FFUL) + 1UL; +} + + +unsigned long cache_dcache_sets(void) +{ + return ((dcache_ccsidr() >> 13) & 0x7FFFUL) + 1UL; +} + + +unsigned long cache_dcache_bytes(void) +{ + return cache_dcache_line_bytes() * cache_dcache_ways() * cache_dcache_sets(); +} + + +unsigned int cache_enabled(void) +{ + unsigned long sctlr = read_sctlr(); + + return (((sctlr & SCTLR_C) != 0UL) && ((sctlr & SCTLR_I) != 0UL)) ? 1U : 0U; +} + + +void cache_disable_all(void) +{ + unsigned long sctlr; + + /* Clean before disabling, so dirty lines reach memory while the cache is + still on to write them back. Invalidate afterwards, so nothing stale is + left to be hit if the caches are enabled again. */ + + cache_clean_all(); + __asm__ volatile("dsb sy" ::: "memory"); + + __asm__ volatile("mrc p15, 0, %0, c1, c0, 0" : "=r"(sctlr)); + sctlr &= ~((1UL << 2) | (1UL << 12)); /* SCTLR.C, SCTLR.I */ + __asm__ volatile("mcr p15, 0, %0, c1, c0, 0" : : "r"(sctlr) : "memory"); + + __asm__ volatile("dsb sy" ::: "memory"); + __asm__ volatile("isb" ::: "memory"); + + cache_invalidate_dcache_all(); + cache_invalidate_icache_all(); + + __asm__ volatile("dsb sy" ::: "memory"); + __asm__ volatile("isb" ::: "memory"); +} diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/cache.h b/ports/cortex_r52/gnu/example_build/s32z280_evb/cache.h new file mode 100644 index 00000000..3202c97e --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/cache.h @@ -0,0 +1,70 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* cache.h Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frederic Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Cache maintenance and enable for the S32Z280-594EVB. */ +/* */ +/**************************************************************************/ + +#ifndef CACHE_H +#define CACHE_H + +/* Geometry as the hardware reports it; printed during bring-up so the sweep + below can be checked against real numbers rather than assumed ones. */ + +unsigned long cache_read_clidr(void); +unsigned long cache_read_ccsidr(void); + +void cache_invalidate_icache_all(void); +void cache_invalidate_dcache_all(void); + +/* Write dirty lines back. Call before a debugger reads memory this image + wrote: with a write-back cache the value may exist only in cache. */ + +void cache_clean_all(void); + +/* Invalidate both caches, then set SCTLR.C and SCTLR.I. */ + +void cache_enable(void); + +/* Clean, then disable both caches, then invalidate. Needed by any measurement + that wants more than one cold pass in a single run: cache_enable is + one-way, and a second cold reading is otherwise impossible. */ + +void cache_disable_all(void); + +unsigned int cache_enabled(void); + +/* Decoded L1 data-cache geometry, from CCSIDR. Sizing a cache benchmark + against real numbers rather than a guess is the difference between measuring + the cache and measuring nothing. */ + +unsigned long cache_dcache_line_bytes(void); +unsigned long cache_dcache_ways(void); +unsigned long cache_dcache_sets(void); +unsigned long cache_dcache_bytes(void); + +#endif /* CACHE_H */ diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/demo_fiq_s32z280.c b/ports/cortex_r52/gnu/example_build/s32z280_evb/demo_fiq_s32z280.c new file mode 100644 index 00000000..ad359704 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/demo_fiq_s32z280.c @@ -0,0 +1,253 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* demo_fiq_s32z280.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frederic Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Nested FIQ handling: _tx_thread_fiq_nesting_start and */ +/* _tx_thread_fiq_nesting_end, the last pair in this port that nothing */ +/* had ever called. */ +/* */ +/* The FVP established that the Group 0 chain works and what breaks it. */ +/* What it cannot show is whether a real GIC-600 routes Group 0 to FIQ */ +/* the same way, which is the point of running this here. */ +/* */ +/* FIQ needs more of the GIC than IRQ does. With a single security */ +/* state the controller delivers Group 0 as FIQ and Group 1 as IRQ, so */ +/* an interrupt only arrives as an FIQ if it has been moved into Group */ +/* 0, the distributor and CPU interface both have Group 0 enabled, and */ +/* it is acknowledged through the Group 0 registers. Group 1's */ +/* acknowledge returns the spurious INTID for a Group 0 interrupt and */ +/* leaves it pending, which presents as a storm rather than an error. */ +/* */ +/* FIQ nesting means an FIQ taken while an FIQ handler is running, so */ +/* one source cannot demonstrate it. Two Group 0 SGIs are used, the */ +/* second at a numerically lower priority so it can preempt the first, */ +/* and the first's handler raises it. */ +/* */ +/* WHAT EACH CHECK IS FOR */ +/* */ +/* F1 an FIQ arrives at all. Separate on purpose: it covers the whole */ +/* Group 0 chain -- IGRPEN0, the ICC_SGI0R encoding, IAR0 and */ +/* EOIR0, the EL1 FIQ vector, and F being unmasked. If F1 fails */ +/* the fault is in one of those and not in the nesting routines. */ +/* F2 an FIQ arrived while another FIQ handler was active, depth 2. */ +/* F3 the depth returned to zero, so the pairing unwound. */ +/* F4 the IRQ path is undisturbed: the tick still advances. */ +/* F5 threads still run, so FIQ context save and restore survived */ +/* being re-entered. */ +/* F6 no unexpected Group 0 INTID arrived. */ +/* */ +/**************************************************************************/ + +#include "tx_api.h" +#include "board.h" +#include "linflexd.h" +#include "gicv3.h" + +#define DEMO_STACK_SIZE 2048 + +static TX_THREAD thread_check; +static TX_THREAD thread_spin; + +static ULONG thread_check_stack[DEMO_STACK_SIZE / sizeof(ULONG)]; +static ULONG thread_spin_stack[DEMO_STACK_SIZE / sizeof(ULONG)]; + +static volatile ULONG spin_runs; + + +/* Breakpoint target for tools/read_identity.gdb. Not static and not empty so an + optimising build cannot discard it. */ + +void bsp_done(void) +{ + __asm volatile ("nop"); +} + + +static UINT report(const char *label_ptr, UINT passed) +{ + linflexd_puts(label_ptr); + linflexd_puts((passed != 0U) ? "PASS\n" : "FAIL\n"); + return (passed != 0U) ? 0U : 1U; +} + + +static void report_hex(const char *label_ptr, unsigned long value) +{ + linflexd_puts(label_ptr); + linflexd_puts("0x"); + linflexd_put_hex32((unsigned int) value); + linflexd_putc('\n'); +} + + +static void thread_spin_entry(ULONG thread_input) +{ + (void) thread_input; + + for (;;) + { + spin_runs++; + } +} + + +static void thread_check_entry(ULONG thread_input) +{ + UINT failures = 0U; + ULONG fiq_before; + ULONG ticks_start; + ULONG ticks_end; + ULONG spin_before; + + (void) thread_input; + + /* F1: the whole Group 0 chain, before nesting is involved. */ + + linflexd_puts("raising a Group 0 SGI from thread context\n"); + + fiq_before = board_fiq_count; + gicv3_send_sgi_group0(BOARD_FIQ_LOW_INTID); + tx_thread_sleep(2); + + report_hex("FIQ count = ", board_fiq_count); + + failures += report("F1 FIQ delivered and dispatched ", + (board_fiq_count > fiq_before) ? 1U : 0U); + + if (board_fiq_count == fiq_before) + { + /* Everything below needs an FIQ to arrive, so repeating the failure + five more times would only bury the cause. */ + + linflexd_puts("no FIQ arrived; the nesting checks would only\n" + " restate that, so they are skipped.\n"); + linflexd_puts("FAIL nested FIQ handling\n"); + linflexd_puts("=== fiq demo complete ===\n"); + bsp_done(); + for (;;) + { + tx_thread_sleep(100UL); + } + } + + /* F2 onwards: let the low-priority FIQ handler raise the high one. */ + + linflexd_puts("enabling nested provocation in the FIQ handler\n"); + + board_fiq_provoke = 1UL; + ticks_start = tx_time_get(); + spin_before = spin_runs; + + { + ULONG i; + + for (i = 0UL; i < 20UL; i++) + { + gicv3_send_sgi_group0(BOARD_FIQ_LOW_INTID); + tx_thread_sleep(2); + } + } + + ticks_end = tx_time_get(); + board_fiq_provoke = 0UL; + + report_hex("low-priority FIQ count = ", board_fiq_count); + report_hex("high-priority FIQ count = ", board_fiq_high_count); + report_hex("nested FIQ count = ", board_fiq_nested_count); + report_hex("max FIQ depth = ", board_fiq_max_depth); + report_hex("FIQ depth now = ", board_fiq_depth); + + failures += report("F2 FIQ nested inside an FIQ handler ", + ((board_fiq_nested_count > 0UL) && + (board_fiq_max_depth >= 2UL)) ? 1U : 0U); + + failures += report("F3 FIQ nesting unwound to depth zero ", + (board_fiq_depth == 0UL) ? 1U : 0U); + + failures += report("F4 IRQ tick undisturbed by FIQ work ", + ((ticks_end - ticks_start) >= 30UL) ? 1U : 0U); + + failures += report("F5 lower-priority thread still scheduled ", + (spin_runs > spin_before) ? 1U : 0U); + + report_hex("unexpected Group 0 INTID = ", board_fiq_unexpected); + + failures += report("F6 no unexpected Group 0 INTID ", + (board_fiq_unexpected == 0UL) ? 1U : 0U); + + if (failures == 0U) + { + linflexd_puts("PASS nested FIQ handling verified on silicon\n"); + } + else + { + linflexd_puts("FAIL nested FIQ handling\n"); + } + + linflexd_puts("=== fiq demo complete ===\n"); + + bsp_done(); + + for (;;) + { + tx_thread_sleep(100UL); + } +} + + +void tx_application_define(void *first_unused_memory) +{ + (void) first_unused_memory; + + (void) tx_thread_create(&thread_check, "fiq check", thread_check_entry, + 0UL, thread_check_stack, sizeof(thread_check_stack), + 10U, 10U, TX_NO_TIME_SLICE, TX_AUTO_START); + + (void) tx_thread_create(&thread_spin, "spinner", thread_spin_entry, + 0UL, thread_spin_stack, sizeof(thread_spin_stack), + 20U, 20U, TX_NO_TIME_SLICE, TX_AUTO_START); +} + + +void bsp_main(void) +{ + unsigned int console_status = linflexd_init(); + + linflexd_puts("\n=== ThreadX Cortex-R52 :: S32Z280-594EVB nested FIQ ===\n"); + report_hex("console = ", (unsigned long) console_status); + linflexd_puts("entering kernel\n"); + + tx_kernel_enter(); + + /* Not reached. */ + + linflexd_puts("FAIL tx_kernel_enter returned\n"); + + for (;;) + { + __asm volatile ("nop"); + } +} diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/demo_nesting_s32z280.c b/ports/cortex_r52/gnu/example_build/s32z280_evb/demo_nesting_s32z280.c new file mode 100644 index 00000000..ca2a6775 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/demo_nesting_s32z280.c @@ -0,0 +1,240 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* demo_nesting_s32z280.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frederic Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Nested IRQ handling on S32Z280 silicon. */ +/* */ +/* The FVP established that the nesting routines work and, more */ +/* usefully, what breaks them: the interrupt has to be acknowledged */ +/* before nesting starts, or the still-pending level-asserted timer is */ +/* retaken the moment IRQ is enabled and recurses until the stacks are */ +/* gone. entry.S here has the same ordering for the same reason. */ +/* */ +/* What the model could not answer is whether a real GIC-600 agrees. */ +/* Two things differ from a model and both are visible here: */ +/* */ +/* P1 how many priority bits the GIC implements. Equal priorities */ +/* do not preempt, and it is the low bits that vanish, so if the */ +/* timer and the SGI collapse to the same value after truncation */ +/* nesting cannot happen at all. board_init discovers the count */ +/* by write and readback rather than assuming the model's five, */ +/* and this demo reports it. */ +/* */ +/* P2 whether an SGI raised on real silicon is delivered at all. */ +/* ICC_SGI1R is a 64-bit AArch32 CP15 register and its encoding */ +/* does not transcribe from the AArch64 alias, so N1 tests SGI */ +/* delivery on its own before nesting is involved. */ +/* */ +/**************************************************************************/ + +#include "tx_api.h" +#include "linflexd.h" +#include "board.h" +#include "gicv3.h" + +#define DEMO_STACK_SIZE 2048 + +static TX_THREAD thread_check; +static TX_THREAD thread_spin; + +static ULONG thread_check_stack[DEMO_STACK_SIZE / sizeof(ULONG)]; +static ULONG thread_spin_stack[DEMO_STACK_SIZE / sizeof(ULONG)]; + +static volatile ULONG spin_runs; + + +/* Breakpoint target for tools/read_identity.gdb. Not static and not empty so + an optimising build cannot discard it. */ + +void bsp_done(void) +{ + __asm volatile ("nop"); +} + + +static UINT report(const char *label_ptr, UINT passed) +{ + linflexd_puts(label_ptr); + linflexd_puts((passed != 0U) ? "PASS\n" : "FAIL\n"); + return (passed != 0U) ? 0U : 1U; +} + + +static void report_hex(const char *label_ptr, unsigned long value) +{ + linflexd_puts(label_ptr); + linflexd_puts(" = 0x"); + linflexd_put_hex32((unsigned int) value); + linflexd_putc('\n'); +} + + +static void thread_spin_entry(ULONG thread_input) +{ + (void) thread_input; + + for (;;) + { + spin_runs++; + } +} + + +static void thread_check_entry(ULONG thread_input) +{ + UINT failures = 0U; + ULONG sgi_before; + ULONG ticks_start; + ULONG ticks_end; + ULONG spin_before; + + (void) thread_input; + + /* P1: what the GIC actually implements, and whether the two priorities this + test depends on survive truncation. */ + + report_hex("priority bits", board_priority_bits); + report_hex("timer effective", board_timer_priority); + report_hex("sgi effective", board_sgi_priority); + + failures += report("P1 SGI outranks timer after truncation ", + (board_sgi_priority < board_timer_priority) ? 1U : 0U); + + /* N1: SGI delivery on its own, before nesting is involved. */ + + linflexd_puts("N1 raising an SGI from thread context\n"); + + sgi_before = board_sgi_count; + gicv3_send_sgi(BOARD_NEST_SGI_INTID); + tx_thread_sleep(2); + + failures += report("N1 SGI delivered and dispatched ", + (board_sgi_count > sgi_before) ? 1U : 0U); + + if (board_sgi_count == sgi_before) + { + /* Everything below needs the SGI, so reporting four more failures with + the same cause would only obscure it. */ + + linflexd_puts("SGI never arrived; the nesting checks would only\n" + "restate that, so they are skipped.\n"); + linflexd_puts("FAIL nested IRQ handling\n"); + linflexd_puts("=== nesting demo complete ===\n"); + bsp_done(); + for (;;) + { + tx_thread_sleep(100UL); + } + } + + /* N2 onwards: let the timer handler provoke a nested SGI. */ + + linflexd_puts("N2 enabling nested provocation in the timer handler\n"); + + board_nest_provoke = 1UL; + ticks_start = tx_time_get(); + spin_before = spin_runs; + tx_thread_sleep(50); + ticks_end = tx_time_get(); + board_nest_provoke = 0UL; + + report_hex("max depth", board_nest_max); + report_hex("nested SGIs", board_sgi_nested_count); + report_hex("depth now", board_nest_depth); + + failures += report("N2 SGI nested inside another handler ", + ((board_sgi_nested_count > 0UL) && + (board_nest_max >= 2UL)) ? 1U : 0U); + + failures += report("N3 nesting unwound to depth zero ", + (board_nest_depth == 0UL) ? 1U : 0U); + + failures += report("N4 tick still advancing after nesting ", + ((ticks_end - ticks_start) >= 40UL) ? 1U : 0U); + + failures += report("N5 lower-priority thread still scheduled ", + (spin_runs > spin_before) ? 1U : 0U); + + report_hex("spurious", board_spurious_count); + report_hex("unexpected", board_unexpected_intid); + + failures += report("N6 no spurious or unexpected interrupts ", + ((board_spurious_count == 0UL) && + (board_unexpected_intid == 0UL)) ? 1U : 0U); + + if (failures == 0U) + { + linflexd_puts("PASS nested IRQ handling verified on silicon\n"); + } + else + { + linflexd_puts("FAIL nested IRQ handling\n"); + } + + linflexd_puts("=== nesting demo complete ===\n"); + + bsp_done(); + + for (;;) + { + tx_thread_sleep(100UL); + } +} + + +void tx_application_define(void *first_unused_memory) +{ + (void) first_unused_memory; + + (void) tx_thread_create(&thread_check, "nest check", thread_check_entry, + 0UL, thread_check_stack, sizeof(thread_check_stack), + 10U, 10U, TX_NO_TIME_SLICE, TX_AUTO_START); + + (void) tx_thread_create(&thread_spin, "spinner", thread_spin_entry, + 0UL, thread_spin_stack, sizeof(thread_spin_stack), + 20U, 20U, TX_NO_TIME_SLICE, TX_AUTO_START); +} + + +void bsp_main(void) +{ + unsigned int console_status = linflexd_init(); + + linflexd_puts("\n=== ThreadX Cortex-R52 :: S32Z280-594EVB nested IRQ ===\n"); + report_hex("console", (unsigned long) console_status); + linflexd_puts("entering kernel\n"); + + tx_kernel_enter(); + + /* Not reached. */ + + linflexd_puts("FAIL tx_kernel_enter returned\n"); + + for (;;) + { + __asm volatile ("nop"); + } +} diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/demo_s32z280.c b/ports/cortex_r52/gnu/example_build/s32z280_evb/demo_s32z280.c new file mode 100644 index 00000000..9f09d8d3 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/demo_s32z280.c @@ -0,0 +1,812 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* demo_s32z280.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frederic Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* ThreadX running on S32Z280 silicon: threads, preemption and the */ +/* timer tick. */ +/* */ +/* The demo judges itself and says why it failed, because a hang and a */ +/* wrong answer are equally likely on first silicon and look the same */ +/* from a console. Each check is separate: */ +/* */ +/* ticks -- the tick advanced, so the GIC delivers the timer PPI */ +/* and _tx_timer_interrupt is reached */ +/* sleeper -- tx_thread_sleep returned, so tick-driven scheduling */ +/* works, not merely the interrupt */ +/* spinner -- the low-priority thread ran, so the sleeper actually */ +/* yielded the core rather than blocking it */ +/* preempt -- the sleeper resumed while the spinner was runnable, */ +/* which is preemption rather than cooperative handoff */ +/* */ +/* Counting alone would not distinguish these: a demo that only checked*/ +/* that both threads ran would pass with a broken tick if the threads */ +/* happened to yield to each other. */ +/* */ +/**************************************************************************/ + +#include "tx_api.h" +#include "linflexd.h" +#include "timer.h" +#include "cache.h" +#include "thread_mpu.h" +#include "board.h" + +#define DEMO_STACK_SIZE 2048 + +static TX_THREAD sleeper_thread; +static TX_THREAD spinner_thread; +static TX_THREAD judge_thread; + +/* One stack in BTCM, the rest in DRAM0, so a run exercises both and the + difference is visible if one of them is wrong. + + BTCM is 16 KB at zero wait states and is never cached, whatever the MPU says + about it. That is the point for a stack: access cost does not depend on + whether a line happens to be resident, which is what a determinism argument + needs. Measured on this part, uncached BTCM comes within 6.6% of the best + cached case, where uncached DRAM0 is 22% off it. + + entry.S writes every location in the bank before any C runs, which is + required before a read because ECC is enabled (TRM 6.2.2). A stack is read + before it is written -- the first context restore pops what tx_thread_create + built -- so that preload is not optional here. */ + +__attribute__((section(".btcm_bss"), aligned(8))) +static unsigned char sleeper_stack[DEMO_STACK_SIZE]; +static unsigned char spinner_stack[DEMO_STACK_SIZE]; +static unsigned char judge_stack[DEMO_STACK_SIZE]; + +static volatile unsigned long sleeper_runs; +static volatile unsigned long spinner_runs; +static volatile unsigned long preempt_observed; + + +/* Breakpoint target for tools/read_identity.gdb. Defined here rather than + pulled from bsp_boot.c, whose bsp_main would collide with this file's. + Not static and not empty, so an optimising build cannot discard it. */ + +void bsp_done(void) +{ + __asm volatile ("nop"); +} + + +static void report(const char *name, unsigned long value) +{ + linflexd_puts(name); + linflexd_puts(" = 0x"); + linflexd_put_hex32((unsigned int) value); + linflexd_putc('\n'); +} + + +/* Highest priority. Sleeps, so it can only run again if the tick advances + and the scheduler resumes it. */ + +/**************************************************************************/ +/* Context-switch cost, stack in BTCM against stack in DRAM0. */ +/* */ +/* Two pairs of equal-priority threads hand control back and forth with */ +/* tx_thread_relinquish, and the measuring thread of each pair times the */ +/* round trip. One pair has both stacks in BTCM, the other in DRAM0. */ +/* */ +/* A round trip is two context switches plus the partner's loop, and the */ +/* partner's code is the same for both pairs, so the difference between */ +/* the pairs is the memory holding the stacks and nothing else. */ +/* */ +/* Both pairs run in one image from one copy of the measuring code, which */ +/* is what makes this comparison safe. The alignment trap that invalid- */ +/* ated earlier work here bites when two builds with different layouts */ +/* are compared; a code shift moves both pairs equally and cancels. */ +/* */ +/* Cycles, from the PMU counter: CNTPCT at 8 MHz cannot resolve a context */ +/* switch. Priorities 2 and 3 put the BTCM pair first and keep both */ +/* above judge and spinner. The sleeper at priority 1 still preempts */ +/* occasionally, which can land in max; min and mean are the robust */ +/* figures and all three are reported. */ +/**************************************************************************/ + +static void demo_dec(unsigned long value) +{ + char digits[12]; + unsigned int n = 0U; + + if (value == 0UL) { linflexd_putc('0'); return; } + + while ((value > 0UL) && (n < 12U)) + { + digits[n] = (char) ('0' + (value % 10UL)); + value /= 10UL; + n++; + } + while (n > 0U) { n--; linflexd_putc(digits[n]); } +} + +#define CTX_SAMPLES 256U +#define CTX_PAIRS 2U + +static TX_THREAD ctx_btcm_a_thread; +static TX_THREAD ctx_btcm_b_thread; +static TX_THREAD ctx_dram_a_thread; +static TX_THREAD ctx_dram_b_thread; + +__attribute__((section(".btcm_bss"), aligned(8))) +static unsigned char ctx_btcm_a_stack[DEMO_STACK_SIZE]; +__attribute__((section(".btcm_bss"), aligned(8))) +static unsigned char ctx_btcm_b_stack[DEMO_STACK_SIZE]; + +static unsigned char ctx_dram_a_stack[DEMO_STACK_SIZE]; +static unsigned char ctx_dram_b_stack[DEMO_STACK_SIZE]; + +static volatile unsigned int ctx_done[CTX_PAIRS]; +static unsigned int ctx_lo[CTX_PAIRS]; +static unsigned int ctx_hi[CTX_PAIRS]; +static unsigned int ctx_mean[CTX_PAIRS]; +static unsigned int ctx_over[CTX_PAIRS]; + +/* A round trip costs about 1400 cycles. Anything past this is not a context + switch, it is a switch that got interrupted -- the sleeper at priority 1 + preempts either pair when a tick lands mid-measurement. Counted rather than + averaged in, because max is otherwise a report on which pair was unlucky: + across three runs the maximum appeared in the BTCM pair once and the DRAM0 + pair twice, while min and mean held to the cycle. */ + +#define CTX_OUTLIER_CYCLES 2000U + +/* Cache state, and why it is forced rather than left to chance. + + With the cache left alone this workload never evicts anything: the DRAM0 + stack stays resident in the 16 KB data cache, every access is a hit, and + DRAM0 measures beautifully. That is a true result for an idle system and a + misleading one for the argument TCM is usually made for, which is about worst + case when other work has pushed your stack out. + + Loading the partner thread with a cache walk does not work either: the timed + round trip includes the partner, so the walk dominates every sample. It was + tried and discarded, and it put every one of 256 samples past the outlier + threshold. + + Instead the cache is cleaned and invalidated immediately before each timed + switch, outside the timed region. Every switch then runs from a cold cache, + which is the state a stack in DRAM0 finds itself in after other work has run + and the state a stack in BTCM is immune to, because an enabled TCM is never + cached. Both pairs get the same treatment, so the difference is the memory. */ + + +/* One copy, used by both pairs. */ + +static void ctx_measure_entry(ULONG which) +{ + unsigned int lo = 0xFFFFFFFFU; + unsigned int hi = 0U; + unsigned long sum = 0UL; + unsigned int over = 0U; + unsigned int counted = 0U; + unsigned int i; + + for (i = 0U; i < CTX_SAMPLES; i++) + { + unsigned int before; + unsigned int after; + unsigned int delta; + + /* Cold cache for every sample, and clean before invalidate so dirty + stack lines reach memory rather than being discarded. Outside the + timestamps, so its own cost is not in the measurement. */ + + cache_clean_all(); + cache_invalidate_dcache_all(); + + before = timer_read_cycles(); + tx_thread_relinquish(); + after = timer_read_cycles(); + + delta = after - before; + + if (delta > CTX_OUTLIER_CYCLES) + { + /* Preempted mid-switch. Excluded from min, max and mean alike: a + maximum that reports whichever pair a tick landed on is not a + measurement of anything, and reporting it as jitter was wrong + once already. */ + + over++; + } + else + { + if (delta < lo) { lo = delta; } + if (delta > hi) { hi = delta; } + sum += (unsigned long) delta; + counted++; + } + } + + ctx_lo[which] = lo; + ctx_hi[which] = hi; + ctx_mean[which] = (counted > 0U) ? (unsigned int) (sum / counted) : 0U; + ctx_over[which] = over; + + ctx_done[which] = 1U; /* releases the partner */ +} + +static void ctx_partner_entry(ULONG which) +{ + while (ctx_done[which] == 0U) + { + tx_thread_relinquish(); + } +} + +/**************************************************************************/ +/* Stack-heavy work, stack in BTCM against stack in DRAM0. */ +/* */ +/* #635 measured context switches and found BTCM worth about 7.4%, with */ +/* no determinism advantage, and said why: a switch saves sixteen */ +/* registers, roughly one cache line, so the stack's cache state has */ +/* almost nothing to contribute. It also said the interesting case was */ +/* work with a large stack working set, that it had not been measured, and */ +/* that it should not be assumed. This measures it. */ +/* */ +/* deep_touch recurses, writing a frame on the way down and reading it on */ +/* the way up, so the stack working set is the whole descent rather than */ +/* one frame. The cache is cleaned and invalidated before each sample, so */ +/* the descent starts cold: a stack in DRAM0 must fill a line per frame, */ +/* while a stack in BTCM has no cache in the path to miss. */ +/* */ +/* No partner threads and no relinquish here. The measurement is entirely */ +/* within one thread, which removes the confound that made the context */ +/* switch figure hard to read -- there, the timed region included the */ +/* partner's execution. */ +/* */ +/* Samples are kept and post-processed rather than filtered against a */ +/* fixed threshold, because the cost of this workload was not known in */ +/* advance and a guessed threshold discarded every sample once already. */ +/**************************************************************************/ + +#define DEEP_SAMPLES 64U +#define DEEP_DEPTH 24U +#define DEEP_FRAME 16U /* words: 64 bytes, one cache line per frame */ +#define DEEP_PLACEMENTS 4U + +static TX_THREAD deep_btcm_thread; +static TX_THREAD deep_dram_thread; + +/* 4 KB: 24 frames of 64 bytes plus call overhead is about 2.1 KB, and ThreadX + needs its own room on top. */ + +__attribute__((section(".btcm_bss"), aligned(8))) +static unsigned char deep_btcm_stack[4096]; +static unsigned char deep_dram_stack[4096]; + +static unsigned int deep_samples[CTX_PAIRS][DEEP_SAMPLES]; +static unsigned int deep_lo[CTX_PAIRS][DEEP_PLACEMENTS]; +static unsigned int deep_hi[CTX_PAIRS][DEEP_PLACEMENTS]; +static unsigned int deep_mean[CTX_PAIRS][DEEP_PLACEMENTS]; +static unsigned int deep_over[CTX_PAIRS][DEEP_PLACEMENTS]; +static volatile unsigned int deep_done[CTX_PAIRS]; + +/* Four placements of the recursive body, each 64-byte aligned and then + displaced within its line, and the measurement sweeps all four. + + The first version of this measurement had one placement, which is the + methodology this example spent two changes correcting elsewhere. It showed + BTCM with a threefold tighter spread than DRAM0; that did not survive an + image with two more threads in it, where both spreads came out near 6500 and + the minima moved 15%. A single placement was reporting where the linker had + put the code. + + The pad nops execute on every recursion level rather than once, so each + placement carries a slightly different constant cost -- about 0.6% at the + widest pad. That cancels in the comparison that matters, because BTCM and + DRAM0 are measured at the same placement, and it does not affect the spread + within a placement. */ + +#define MAKE_DEEP_TOUCH(name, pad_words) \ +__attribute__((aligned(64), noinline)) \ +static unsigned int name(unsigned int depth) \ +{ \ + volatile unsigned int frame[DEEP_FRAME]; \ + unsigned int acc = 0U; \ + unsigned int i; \ + \ + __asm__ volatile(".rept " #pad_words "\n\tnop\n\t.endr"); \ + \ + for (i = 0U; i < DEEP_FRAME; i++) \ + { \ + frame[i] = depth + i; \ + } \ + \ + if (depth > 0U) \ + { \ + acc = name(depth - 1U); \ + } \ + \ + for (i = 0U; i < DEEP_FRAME; i++) \ + { \ + acc += frame[i]; \ + } \ + \ + return acc; \ +} + +MAKE_DEEP_TOUCH(deep_touch_a, 0) +MAKE_DEEP_TOUCH(deep_touch_b, 4) +MAKE_DEEP_TOUCH(deep_touch_c, 8) +MAKE_DEEP_TOUCH(deep_touch_d, 12) + +static unsigned int (*const deep_touches[DEEP_PLACEMENTS])(unsigned int) = +{ + deep_touch_a, deep_touch_b, deep_touch_c, deep_touch_d +}; + +static void deep_entry(ULONG which) +{ + unsigned int i; + unsigned int lo; + unsigned long sum; + unsigned int counted; + unsigned int over; + unsigned int hi; + + for (i = 0U; i < DEEP_SAMPLES; i++) + { + unsigned int before; + unsigned int after; + unsigned int slot = i / (DEEP_SAMPLES / DEEP_PLACEMENTS); + + if (slot >= DEEP_PLACEMENTS) + { + slot = DEEP_PLACEMENTS - 1U; + } + + cache_clean_all(); + cache_invalidate_dcache_all(); + + before = timer_read_cycles(); + (void) deep_touches[slot](DEEP_DEPTH); + after = timer_read_cycles(); + + deep_samples[which][i] = after - before; + } + + /* Post-processed per placement. A figure spanning placements would mix the + four constant costs and hide exactly what this sweep exists to show. */ + + { + unsigned int slot; + unsigned int each = DEEP_SAMPLES / DEEP_PLACEMENTS; + + for (slot = 0U; slot < DEEP_PLACEMENTS; slot++) + { + unsigned int s; + unsigned int lo = 0xFFFFFFFFU; + unsigned int hi = 0U; + unsigned long sum = 0UL; + unsigned int counted = 0U; + unsigned int over = 0U; + + for (s = slot * each; s < ((slot + 1U) * each); s++) + { + if (deep_samples[which][s] < lo) { lo = deep_samples[which][s]; } + } + + for (s = slot * each; s < ((slot + 1U) * each); s++) + { + unsigned int v = deep_samples[which][s]; + + if (v > (lo * 2U)) + { + over++; + } + else + { + if (v > hi) { hi = v; } + sum += (unsigned long) v; + counted++; + } + } + + deep_lo[which][slot] = lo; + deep_hi[which][slot] = hi; + deep_mean[which][slot] = (counted > 0U) ? (unsigned int) (sum / counted) : 0U; + deep_over[which][slot] = over; + } + } + + deep_done[which] = 1U; +} + +/**************************************************************************/ +/* Per-thread memory protection, demonstrated rather than asserted. */ +/* */ +/* Two threads each own a 4 KB window at the top of DRAM2, carved out of */ +/* the broad data region in mpu.c so that nothing else grants access to */ +/* them. Each thread writes its own window, which must succeed, and then */ +/* reaches for the other thread's, which must fault. */ +/* */ +/* The second half is the part that matters. A test that only shows a */ +/* thread reaching its own memory proves nothing about isolation: it */ +/* would pass just as well with no protection at all. */ +/* */ +/* The fault is made survivable the same way the boot probes do it -- */ +/* fault_expected tells the data abort handler to record the violation */ +/* and resume at the instruction after the faulting access, rather than */ +/* treating it as fatal. That works in thread context because the */ +/* handler returns to where it came from rather than to a fixed recovery */ +/* point. */ +/**************************************************************************/ + +#define ISO_THREADS 2U + +static TX_THREAD iso_thread[ISO_THREADS]; + +static unsigned char iso_stack[ISO_THREADS][DEMO_STACK_SIZE]; + +static volatile unsigned int iso_done[ISO_THREADS]; +static unsigned int iso_own_ok[ISO_THREADS]; +static unsigned int iso_other_faulted[ISO_THREADS]; +static unsigned long iso_own_base[ISO_THREADS]; + +static void iso_entry(ULONG which) +{ + extern unsigned int fault_expected; + extern unsigned int fault_taken; + + unsigned long own = thread_mpu_window_base((unsigned int) which); + unsigned long other = thread_mpu_window_base((unsigned int) (1UL - which)); + unsigned int before; + unsigned int pattern = 0xC0DE0000U + (unsigned int) which; + + iso_own_base[which] = own; + + /* Install this thread's window. */ + + thread_mpu_activate(tx_thread_identify()); + + /* Its own window: must be reachable. */ + + *((volatile unsigned int *) own) = pattern; + iso_own_ok[which] = + (*((volatile unsigned int *) own) == pattern) ? 1U : 0U; + + /* The other thread's window: must not be. Only the write is attempted -- + reading it back would fault a second time, and the point is already + made. */ + + before = fault_taken; + fault_expected = 1U; + *((volatile unsigned int *) other) = 0xBADU; + fault_expected = 0U; + + iso_other_faulted[which] = (fault_taken > before) ? 1U : 0U; + + iso_done[which] = 1U; +} + +static void sleeper_entry(ULONG input) +{ + unsigned long spinner_before; + + (void) input; + + for (;;) + { + spinner_before = spinner_runs; + + tx_thread_sleep(5UL); /* 50 ms at a 10 ms tick */ + + sleeper_runs++; + + /* If the spinner advanced while this thread slept, and this thread is + the higher priority of the two, then resuming it took the core away + from a runnable lower-priority thread. That is preemption. */ + + if (spinner_runs != spinner_before) + { + preempt_observed++; + } + } +} + + +/* Lowest priority. Never blocks, so it only runs when nothing else can. */ + +static void spinner_entry(ULONG input) +{ + (void) input; + + for (;;) + { + spinner_runs++; + } +} + + +/* Middle priority. Waits, then judges and reports. */ + +static void judge_entry(ULONG input) +{ + ULONG ticks_start; + ULONG ticks_end; + UINT failures = 0U; + + (void) input; + + ticks_start = tx_time_get(); + tx_thread_sleep(100UL); /* about one second */ + ticks_end = tx_time_get(); + + linflexd_puts("\n=== ThreadX on S32Z280: results ===\n"); + + linflexd_puts("per-thread MPU isolation\n"); + { + unsigned int w; + unsigned int all_ok = 1U; + + for (w = 0U; w < ISO_THREADS; w++) + { + linflexd_puts(" thread "); + demo_dec(w); + linflexd_puts(" window 0x"); + linflexd_put_hex32((unsigned int) iso_own_base[w]); + if (iso_done[w] == 0U) + { + linflexd_puts(" did not finish\n"); + all_ok = 0U; + } + else + { + linflexd_puts(iso_own_ok[w] ? " own: reachable" : " own: UNREACHABLE"); + linflexd_puts(iso_other_faulted[w] ? " other: faulted\n" + : " other: REACHED\n"); + if ((iso_own_ok[w] == 0U) || (iso_other_faulted[w] == 0U)) + { + all_ok = 0U; + } + } + } + + linflexd_puts(all_ok ? " PASS each thread reached its own window and faulted on the other\n" + : " FAIL isolation not demonstrated\n"); + + linflexd_puts(" region switch cost, cycles: min "); + demo_dec(thread_mpu_min_cycles()); + linflexd_puts(" max "); + demo_dec(thread_mpu_max_cycles()); + linflexd_puts(" switches "); + demo_dec(thread_mpu_switch_count()); + linflexd_puts("\n"); + } + + linflexd_puts("stack-heavy work, cycles (24 frames, cold cache, 4 placements)\n"); + { + static const char *const dwhere[CTX_PAIRS] = { "BTCM ", "DRAM0" }; + unsigned int q; + unsigned int slot; + + for (q = 0U; q < CTX_PAIRS; q++) + { + if (deep_done[q] == 0U) + { + linflexd_puts(" "); + linflexd_puts(dwhere[q]); + linflexd_puts(": did not finish\n"); + continue; + } + + for (slot = 0U; slot < DEEP_PLACEMENTS; slot++) + { + linflexd_puts(" "); + linflexd_puts(dwhere[q]); + linflexd_puts(" offset "); + demo_dec((unsigned long) (slot * 16U)); + linflexd_puts(": min "); + demo_dec(deep_lo[q][slot]); + linflexd_puts(" mean "); + demo_dec(deep_mean[q][slot]); + linflexd_puts(" max "); + demo_dec(deep_hi[q][slot]); + linflexd_puts(" spread "); + demo_dec(deep_hi[q][slot] - deep_lo[q][slot]); + linflexd_puts(" cut "); + demo_dec(deep_over[q][slot]); + linflexd_puts("\n"); + } + } + } + + linflexd_puts("context switch round trip, cycles\n"); + { + static const char *const where[CTX_PAIRS] = { "stacks in BTCM ", + "stacks in DRAM0" }; + unsigned int q; + + for (q = 0U; q < CTX_PAIRS; q++) + { + linflexd_puts(" "); + linflexd_puts(where[q]); + if (ctx_done[q] == 0U) + { + linflexd_puts(": did not finish\n"); + } + else + { + linflexd_puts(": min "); + demo_dec(ctx_lo[q]); + linflexd_puts(" mean "); + demo_dec(ctx_mean[q]); + linflexd_puts(" max "); + demo_dec(ctx_hi[q]); + linflexd_puts(" preempted "); + demo_dec(ctx_over[q]); + linflexd_puts(" of "); + demo_dec(CTX_SAMPLES); + linflexd_puts("\n"); + } + } + } + report("ticks ", (unsigned long) (ticks_end - ticks_start)); + report("sleeper ", sleeper_runs); + report("spinner ", spinner_runs); + report("preempt ", preempt_observed); + + if ((ticks_end - ticks_start) < 50UL) + { + linflexd_puts("FAIL tick did not advance as expected\n"); + failures++; + } + + if (sleeper_runs == 0UL) + { + linflexd_puts("FAIL sleeper never resumed\n"); + failures++; + } + + if (spinner_runs == 0UL) + { + linflexd_puts("FAIL spinner never ran\n"); + failures++; + } + + if (preempt_observed == 0UL) + { + linflexd_puts("FAIL no preemption observed\n"); + failures++; + } + + if (failures == 0U) + { + linflexd_puts("PASS threads, tick and preemption all verified\n"); + } + + linflexd_puts("=== demo complete ===\n"); + + bsp_done(); + + for (;;) + { + tx_thread_sleep(100UL); + } +} + + +void tx_application_define(void *first_unused_memory) +{ + (void) first_unused_memory; + + linflexd_puts("stacks: sleeper 0x"); + linflexd_put_hex32((unsigned int) (unsigned long) sleeper_stack); + linflexd_puts(" (BTCM) spinner 0x"); + linflexd_put_hex32((unsigned int) (unsigned long) spinner_stack); + linflexd_puts(" judge 0x"); + linflexd_put_hex32((unsigned int) (unsigned long) judge_stack); + linflexd_puts("\n"); + + (void) tx_thread_create(&sleeper_thread, "sleeper", sleeper_entry, 0UL, + sleeper_stack, DEMO_STACK_SIZE, + 1U, 1U, TX_NO_TIME_SLICE, TX_AUTO_START); + + (void) tx_thread_create(&judge_thread, "judge", judge_entry, 0UL, + judge_stack, DEMO_STACK_SIZE, + 10U, 10U, TX_NO_TIME_SLICE, TX_AUTO_START); + + /* The PMU cycle counter has to be started here. bsp_boot.c starts it for + the probe image, and this image does not run bsp_boot.c, so without this + every reading below would be zero -- which would read as a free context + switch rather than as a dead counter. */ + + timer_cycles_enable(); + + /* BTCM pair first at priority 2, DRAM0 pair after it at 3. */ + + (void) tx_thread_create(&ctx_btcm_b_thread, "ctxB btcm", ctx_partner_entry, 0UL, + ctx_btcm_b_stack, DEMO_STACK_SIZE, + 2U, 2U, TX_NO_TIME_SLICE, TX_AUTO_START); + (void) tx_thread_create(&ctx_btcm_a_thread, "ctxA btcm", ctx_measure_entry, 0UL, + ctx_btcm_a_stack, DEMO_STACK_SIZE, + 2U, 2U, TX_NO_TIME_SLICE, TX_AUTO_START); + + (void) tx_thread_create(&ctx_dram_b_thread, "ctxB dram", ctx_partner_entry, 1UL, + ctx_dram_b_stack, DEMO_STACK_SIZE, + 3U, 3U, TX_NO_TIME_SLICE, TX_AUTO_START); + (void) tx_thread_create(&ctx_dram_a_thread, "ctxA dram", ctx_measure_entry, 1UL, + ctx_dram_a_stack, DEMO_STACK_SIZE, + 3U, 3U, TX_NO_TIME_SLICE, TX_AUTO_START); + + /* Isolation pair. Priority 6 and 7, after the measurement threads. */ + + { + unsigned int w; + + for (w = 0U; w < ISO_THREADS; w++) + { + (void) tx_thread_create(&iso_thread[w], "iso", iso_entry, + (ULONG) w, + iso_stack[w], DEMO_STACK_SIZE, + 6U + w, 6U + w, + TX_NO_TIME_SLICE, TX_AUTO_START); + (void) thread_mpu_grant(&iso_thread[w], w); + } + } + + /* Stack-heavy pair, after the context-switch pairs. */ + + (void) tx_thread_create(&deep_btcm_thread, "deep btcm", deep_entry, 0UL, + deep_btcm_stack, sizeof(deep_btcm_stack), + 4U, 4U, TX_NO_TIME_SLICE, TX_AUTO_START); + (void) tx_thread_create(&deep_dram_thread, "deep dram", deep_entry, 1UL, + deep_dram_stack, sizeof(deep_dram_stack), + 5U, 5U, TX_NO_TIME_SLICE, TX_AUTO_START); + + (void) tx_thread_create(&spinner_thread, "spinner", spinner_entry, 0UL, + spinner_stack, DEMO_STACK_SIZE, + 20U, 20U, TX_NO_TIME_SLICE, TX_AUTO_START); +} + + +/**************************************************************************/ +/* bsp_main -- entered at EL1 from entry.S; hands over to the kernel. */ +/**************************************************************************/ + +void bsp_main(void) +{ + unsigned int console_status = linflexd_init(); + + linflexd_puts("\n=== ThreadX Cortex-R52 :: S32Z280-594EVB ===\n"); + report("console ", (unsigned long) console_status); + linflexd_puts("entering kernel\n"); + + tx_kernel_enter(); + + /* Not reached. */ + + linflexd_puts("FAIL tx_kernel_enter returned\n"); + + for (;;) + { + __asm volatile ("nop"); + } +} diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/demo_vfp_s32z280.c b/ports/cortex_r52/gnu/example_build/s32z280_evb/demo_vfp_s32z280.c new file mode 100644 index 00000000..456ad319 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/demo_vfp_s32z280.c @@ -0,0 +1,283 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* demo_vfp_s32z280.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frederic Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Lazy floating-point context save and restore on S32Z280 silicon. */ +/* */ +/* This is the FVP's demo_m5.c test design, reporting over the */ +/* LINFlexD console instead of semihosting. The design is kept */ +/* deliberately: the model established that the two halves of the VFP */ +/* context path need separate provocation, and silicon does not change */ +/* that, it only changes whether the hardware agrees. */ +/* */ +/* fp check holds eight live doubles across tx_thread_sleep. Eight */ +/* forces the compiler onto the callee-saved bank D8-D15, */ +/* which is what a SOLICITED switch must preserve. */ +/* */ +/* fp busy lowest priority and never sleeps, so the periodic tick */ +/* interrupts it mid-computation. That exercises the */ +/* INTERRUPT half, D0-D15 plus FPSCR, and it checks its */ +/* own values every iteration. */ +/* */ +/* Both call tx_thread_vfp_enable(). With lazy save and restore a */ +/* thread that has not asked for floating point does not get its */ +/* registers saved, so a test where every thread opts in would not */ +/* show whether the opt-in is what does the work. */ +/* */ +/* Every constant is an exact binary fraction and the step is 0.5, so */ +/* the comparisons are exact and no epsilon is needed. MISRA C:2012 */ +/* Dir 4.6 and Rule 13.3 would normally object to == on floating */ +/* point; here it is the point, since any inexactness means a register */ +/* was not preserved. */ +/* */ +/* What silicon adds over the model: the FPU must actually be enabled */ +/* by the BSP. entry.S opens CPACR and FPEXC.EN at EL1 under */ +/* __ARM_FP, because tx_thread_vfp_enable() sets only the per-thread */ +/* software flag and never touches the hardware. Without that, the */ +/* first D-register access after a switch takes an Undefined */ +/* Instruction exception -- which on this board was invisible until */ +/* SCTLR.TE was cleared. */ +/* */ +/**************************************************************************/ + +#include "tx_api.h" +#include "linflexd.h" +#include "board.h" + +#define DEMO_STACK_SIZE 2048 +#define FILEX_PTR_SENTINEL ((void *) 0xF11EF11EUL) +#define FP_ITERATIONS 50UL +#define FP_STEP 0.5 /* exactly representable */ + +static TX_THREAD thread_check; +static TX_THREAD thread_fp_busy; + +static ULONG thread_check_stack[DEMO_STACK_SIZE / sizeof(ULONG)]; +static ULONG thread_fp_busy_stack[DEMO_STACK_SIZE / sizeof(ULONG)]; + +static volatile ULONG busy_iterations; +static volatile ULONG busy_corruptions; + + +/* Breakpoint target for tools/read_identity.gdb. Not static and not empty so + an optimising build cannot discard it. */ + +void bsp_done(void) +{ + __asm volatile ("nop"); +} + + +static UINT report(const char *label_ptr, UINT passed) +{ + linflexd_puts(label_ptr); + linflexd_puts((passed != 0U) ? "PASS\n" : "FAIL\n"); + return (passed != 0U) ? 0U : 1U; +} + + +static void report_hex(const char *label_ptr, unsigned long value) +{ + linflexd_puts(label_ptr); + linflexd_puts(" = 0x"); + linflexd_put_hex32((unsigned int) value); + linflexd_putc('\n'); +} + + +/**************************************************************************/ +/* thread_fp_busy_entry -- the interrupt half of the VFP path. */ +/**************************************************************************/ + +static void thread_fp_busy_entry(ULONG thread_input) +{ + (void) thread_input; + + tx_thread_vfp_enable(); + + for (;;) + { + /* Deliberately disjoint from the checking thread's range, so that + state leaking from one context into the other is detectable rather + than coincidentally correct. */ + + double v0 = 1024.5; + double v1 = 2048.25; + double v2 = 4096.125; + double v3 = 8192.0625; + + v0 = (v0 * 2.0) - 1024.5; /* 1024.5 */ + v1 = (v1 * 2.0) - 2048.25; /* 2048.25 */ + v2 = (v2 * 2.0) - 4096.125; /* 4096.125 */ + v3 = (v3 * 2.0) - 8192.0625; /* 8192.0625 */ + + if ((v0 != 1024.5) || (v1 != 2048.25) || + (v2 != 4096.125) || (v3 != 8192.0625)) + { + busy_corruptions++; + } + + busy_iterations++; + } +} + + +/**************************************************************************/ +/* thread_check_entry -- the solicited half, and the judge. */ +/**************************************************************************/ + +static void thread_check_entry(ULONG thread_input) +{ + UINT failures = 0U; + ULONG iteration; + TX_THREAD *self; + + double a = 1.5; + double b = 2.25; + double c = 3.125; + double d = 4.0625; + double e = 5.03125; + double f = 6.015625; + double g = 7.0078125; + double h = 8.00390625; + + (void) thread_input; + + /* The VFP enable flag is reached from assembly by hard-coded offset. On the + Cortex-R4 and R5 ports that offset lands on tx_thread_filex_ptr, because + their TX_THREAD_EXTENSION_2 is empty. A sentinel turns that class of + mistake into a visible failure instead of corruption that only appears + once something actually uses FileX. */ + + self = tx_thread_identify(); + self->tx_thread_filex_ptr = FILEX_PTR_SENTINEL; + + tx_thread_vfp_enable(); + + linflexd_puts("V1 floating-point work across solicited switches\n"); + + for (iteration = 0UL; iteration < FP_ITERATIONS; iteration++) + { + /* Suspending here is the provocation: the switch must preserve all + eight values, and the lower-priority thread runs meanwhile. */ + + tx_thread_sleep(1); + + a += FP_STEP; + b += FP_STEP; + c += FP_STEP; + d += FP_STEP; + e += FP_STEP; + f += FP_STEP; + g += FP_STEP; + h += FP_STEP; + } + + { + const double delta = 0.5 * (double) FP_ITERATIONS; + + failures += report("V2 D8-D15 bank preserved across switches ", + ((a == (1.5 + delta)) && + (b == (2.25 + delta)) && + (c == (3.125 + delta)) && + (d == (4.0625 + delta)) && + (e == (5.03125 + delta)) && + (f == (6.015625 + delta)) && + (g == (7.0078125 + delta)) && + (h == (8.00390625 + delta))) ? 1U : 0U); + } + + report_hex("iterations", busy_iterations); + report_hex("corruptions", busy_corruptions); + + failures += report("V3 interrupted FP thread made progress ", + (busy_iterations > 0UL) ? 1U : 0U); + failures += report("V4 no FP corruption across interrupts ", + (busy_corruptions == 0UL) ? 1U : 0U); + + report_hex("filex_ptr", (unsigned long) self->tx_thread_filex_ptr); + + failures += report("V5 VFP flag did not alias filex_ptr ", + (self->tx_thread_filex_ptr == FILEX_PTR_SENTINEL) ? 1U : 0U); + + if (failures == 0U) + { + linflexd_puts("PASS lazy VFP context switch verified on silicon\n"); + } + else + { + linflexd_puts("FAIL lazy VFP context switch\n"); + } + + linflexd_puts("=== vfp demo complete ===\n"); + + bsp_done(); + + for (;;) + { + tx_thread_sleep(100UL); + } +} + + +void tx_application_define(void *first_unused_memory) +{ + (void) first_unused_memory; + + (void) tx_thread_create(&thread_check, "fp check", thread_check_entry, 0UL, + thread_check_stack, sizeof(thread_check_stack), + 10U, 10U, TX_NO_TIME_SLICE, TX_AUTO_START); + + (void) tx_thread_create(&thread_fp_busy, "fp busy", thread_fp_busy_entry, + 0UL, thread_fp_busy_stack, + sizeof(thread_fp_busy_stack), + 20U, 20U, TX_NO_TIME_SLICE, TX_AUTO_START); +} + + +/**************************************************************************/ +/* bsp_main -- entered at EL1 from entry.S; hands over to the kernel. */ +/**************************************************************************/ + +void bsp_main(void) +{ + unsigned int console_status = linflexd_init(); + + linflexd_puts("\n=== ThreadX Cortex-R52 :: S32Z280-594EVB lazy VFP ===\n"); + report_hex("console", (unsigned long) console_status); + linflexd_puts("entering kernel\n"); + + tx_kernel_enter(); + + /* Not reached. */ + + linflexd_puts("FAIL tx_kernel_enter returned\n"); + + for (;;) + { + __asm volatile ("nop"); + } +} diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/entry.S b/ports/cortex_r52/gnu/example_build/s32z280_evb/entry.S new file mode 100644 index 00000000..6601b25e --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/entry.S @@ -0,0 +1,1034 @@ +@/*************************************************************************** +@ * 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). +@ * 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 +@ **************************************************************************/ +@ Portions of this file were generated with AI assistance. +@ +@/**************************************************************************/ +@/* */ +@/* BOARD SUPPORT RELEASE */ +@/* */ +@/* entry.S Cortex-R52/GNU */ +@/* 6.5.2 */ +@/* AUTHOR */ +@/* */ +@/* Frédéric Desbiens, Eclipse Foundation */ +@/* */ +@/* DESCRIPTION */ +@/* */ +@/* Reset path for the NXP S32Z280-594EVB, RTU0 core 0: EL2 setup, the */ +@/* drop to EL1, stacks, .bss and the call into bsp_main. */ +@/* */ +@/* Two things differ from the Armv8-R AEM FVP entry path and both are */ +@/* silicon facts rather than choices: */ +@/* */ +@/* 1. The core resets in THUMB state. CPSR reads 0x1FA on this board */ +@/* -- Hyp mode with T set -- because the RTU boot instruction is a */ +@/* T32 branch (NXP writes R52_B0_INSTR_T32 to the boot address). */ +@/* So _start must be T32 and switch to A32 itself. An A32 entry */ +@/* executes the first halfword of its own instruction as Thumb and */ +@/* goes anywhere. */ +@/* */ +@/* 2. There is no semihosting here, so progress cannot be printed. */ +@/* Instead each stage records a marker and the identity registers */ +@/* into .data, where a debugger reads them. boot_stage says how */ +@/* far the reset path got; if a fault handler runs it records the */ +@/* vector, syndrome and return address before parking. Without */ +@/* this a fault would be indistinguishable from a hang. */ +@/* */ +@/* Define TX_R52_BOOT_AT_EL1 to skip the EL2 configuration, for */ +@/* targets where an earlier boot stage or a vendor EL2 monitor (for */ +@/* example NXP's EL2M, or Eclipse ThreadX ZoneX) has already dropped */ +@/* privilege to EL1. The kernel then enters at el1_entry in A32 */ +@/* state, and the register list that becomes the monitor's */ +@/* responsibility is enumerated at the #ifndef itself rather than */ +@/* here, next to the code it replaces. */ +@/* */ +@/* The Armv8-R AEM FVP entry path has carried the same option since */ +@/* it was written; this file gained it later, which is why the two */ +@/* brackets are not identical in shape -- the EL2 work this board */ +@/* does is considerably larger. */ +@/* */ +@/* CNTFRQ is read and recorded rather than written. The FVP leaves it */ +@/* zero and its BSP programs a known value, but the correct frequency */ +@/* for this board is not yet established, and writing a wrong one would*/ +@/* silently mis-scale every tick interval derived from it. */ +@/* */ +@/**************************************************************************/ + + .syntax unified + +/* HCR (Hyp Configuration Register) bits cleared below. */ + + .equ HCR_VM, (1 << 0) /* stage-2 MPU enable */ + .equ HCR_AMO, (1 << 3) /* route aborts to EL2 */ + .equ HCR_IMO, (1 << 4) /* route IRQ to EL2 */ + .equ HCR_FMO, (1 << 5) /* route FIQ to EL2 */ + .equ HCR_TGE, (1 << 27) /* trap general exceptions to EL2 */ + .equ HCR_HCD, (1 << 29) /* HVC disable -- must be clear */ + + .equ HCPTR_TCP, (3 << 10) /* trap EL1 access to CP10/CP11 */ + +/* CPACR grants EL1/EL0 access to CP10 and CP11, the floating-point unit; + FPEXC.EN then enables floating-point execution itself. */ + + .equ CPACR_CP10, (3 << 20) + .equ CPACR_CP11, (3 << 22) + .equ FPEXC_EN, (1 << 30) + + .equ CNTHCTL_PL1PCTEN, (1 << 0) + .equ CNTHCTL_PL1PCEN, (1 << 1) + + .equ ICC_HSRE_SRE, (1 << 0) + .equ ICC_HSRE_ENABLE, (1 << 3) + +/* IMP_PERIPHPREGIONR: peripheral port enables. Both reset to 0. */ + + .equ PERIPHP_ENABLE_EL10, (1 << 0) + .equ PERIPHP_ENABLE_EL2, (1 << 1) + +/* SCTLR/HSCTLR.TE -- Thumb Exception enable. Set means exceptions are entered + in T32 state. It RESETS SET on this part, and every vector table here is + A32, so it must be cleared or no handler is reachable. */ + + .equ SCTLR_TE, (1 << 30) + +/* CPSR mode and mask bits. */ + + .equ MODE_USR, 0x10 + .equ MODE_FIQ, 0x11 + .equ MODE_IRQ, 0x12 + .equ MODE_SVC, 0x13 + .equ MODE_ABT, 0x17 + .equ MODE_UND, 0x1B + .equ MODE_SYS, 0x1F + .equ PSR_F, 0x40 + .equ PSR_I, 0x80 + .equ PSR_A, 0x100 + +/* System counter frequency; see where it is programmed below. */ + + .equ SYSTEM_COUNTER_HZ, 8000000 + +/* Stage markers, recorded in boot_stage. */ + + .equ STAGE_A32, 1 /* switched out of Thumb */ + .equ STAGE_EL2_DONE, 2 /* EL2 configured */ + .equ STAGE_EL1, 3 /* running at EL1 */ + .equ STAGE_MAIN, 4 /* about to enter bsp_main */ + .equ STAGE_RETURNED, 5 /* bsp_main returned (unexpected) */ + .equ STAGE_FAULT, 0xFF /* a fault handler ran */ + +@/**************************************************************************/ +@/* Observation area. In .data so the debugger's load initialises it and */ +@/* a store needs no stack. */ +@/**************************************************************************/ + + .section .data + .align 2 + + .global boot_stage +boot_stage: .word 0 + .global cpsr_at_el2 +cpsr_at_el2: .word 0 + .global cpsr_at_el1 +cpsr_at_el1: .word 0 + .global cntfrq_at_el2 +cntfrq_at_el2: .word 0 + .global hmpuir_at_el2 +hmpuir_at_el2: .word 0 + .global probe_stage +probe_stage: .word 0 +#ifdef TXM_MODULE_MANAGER + .extern __tx_module_svc_interrupt + .extern _txm_module_manager_data_abort + .extern _txm_module_manager_prefetch_abort +#endif + + .global fault_expected +fault_expected: .word 0 + .global fault_taken +fault_taken: .word 0 + .global fault_expected_pabt +fault_expected_pabt: .word 0 + .global abort_return +abort_return: .word 0 + .global fault_vector +fault_vector: .word 0 + .global fault_syndrome +fault_syndrome: .word 0 + .global fault_address +fault_address: .word 0 + +@/**************************************************************************/ +@/* EL2 (Hyp) vector table. 32-byte aligned for HVBAR. */ +@/* */ +@/* Offsets 0x04-0x10 are exceptions taken FROM Hyp mode. Everything */ +@/* routed to EL2 from EL1 or EL0 arrives at 0x14 instead and is decoded */ +@/* from HSR.EC -- the single funnel that partitioning work will use. */ +@/**************************************************************************/ + + .section .vectors_el2, "ax" + .align 5 + .arm + .global el2_vectors +el2_vectors: + b fault_el2_reset /* 0x00 */ + b fault_el2_undef /* 0x04 */ + b fault_el2_svc /* 0x08 */ + b fault_el2_pabt /* 0x0C */ + b fault_el2_dabt /* 0x10 */ + b fault_el2_hyp /* 0x14 from EL1/EL0 */ + b fault_el2_irq /* 0x18 */ + b fault_el2_fiq /* 0x1C */ + +@/**************************************************************************/ +@/* EL1 vector table. */ +@/**************************************************************************/ + + .section .vectors_el1, "ax" + .align 5 + .arm + .global el1_vectors +el1_vectors: + b fault_el1_reset /* 0x00 */ + b fault_el1_undef /* 0x04 */ +#ifdef TXM_MODULE_MANAGER + b __tx_module_svc_interrupt /* 0x08 module kernel boundary */ +#else + b fault_el1_svc /* 0x08 */ +#endif + b fault_el1_pabt /* 0x0C */ + b fault_el1_dabt /* 0x10 */ + b fault_el1_reserved /* 0x14 */ + b el1_irq_entry /* 0x18 IRQ */ + b el1_fiq_entry /* 0x1C FIQ */ + +#ifndef TX_R52_BOOT_AT_EL1 + +@/**************************************************************************/ +@/* THE EL2 RESET PATH, which a guest build skips entirely. */ +@/* */ +@/* Everything between this #ifndef and its #else configures EL2 and then */ +@/* drops to EL1. With TX_R52_BOOT_AT_EL1 defined, an earlier boot stage */ +@/* or a vendor EL2 monitor has already done that, so the kernel starts */ +@/* straight at el1_entry and none of it runs. */ +@/* */ +@/* WHAT THE MONITOR MUST THEN DO, because a guest at EL1 cannot: */ +@/* */ +@/* CNTFRQ writable only at the highest implemented */ +@/* exception level, and it reads ZERO out of */ +@/* reset on this part. A guest deriving a */ +@/* tick interval from it divides by zero. */ +@/* HCPTR.TCP10/TCP11 both reset SET, trapping every EL1 and EL0 */ +@/* floating-point access to EL2. */ +@/* HSCTLR.TE an EL2 register; a guest cannot write it. */ +@/* (SCTLR.TE is EL1's and el1_entry clears it */ +@/* below, so the guest still handles its own.) */ +@/* ICC_HSRE.SRE until it is set, every other ICC_* and */ +@/* ICH_* system register is UNDEFINED -- so an */ +@/* EL1 kernel cannot acknowledge an interrupt */ +@/* at all. */ +@/* IMP_PERIPHPREGIONR the low-latency peripheral port enables */ +@/* reset to zero and an EL1 write to this */ +@/* register traps to EL2 when */ +@/* HACTLR.PERIPHPREGIONR is clear. A guest */ +@/* that needs the RTU peripheral window at */ +@/* 0x76000000 therefore depends on the monitor */ +@/* having opened it; one that does not need it */ +@/* is unaffected. */ +@/* IMP_ATCMREGIONR and the TCM bases and enables are per-core, and */ +@/* IMP_BTCMREGIONR ENABLEEL2 is silently IGNORED when written */ +@/* from EL1 -- measured on this part, on both */ +@/* BTCM and CTCM: the base took and bit 0 took */ +@/* while bit 1 stayed clear. A guest that */ +@/* places anything in a TCM therefore needs */ +@/* the monitor to program and ECC-preload it, */ +@/* because ECC is enabled here and a TCM */ +@/* location must be WRITTEN before it can be */ +@/* read (TRM 6.2.2). */ +@/* */ +@/* CNTHCTL.PL1PCTEN and PL1PCEN are the interesting omission from that */ +@/* list. This path opens both, because a standalone kernel owns the */ +@/* physical timer. A monitor that TIME-partitions its guests must NOT */ +@/* open them: a partition's physical time keeps running while it is */ +@/* descheduled, so a guest reading it can observe that it was not */ +@/* running. Such a monitor gives its guests the virtual timer and a */ +@/* per-guest CNTVOFF instead. That is the monitor's decision to make, */ +@/* not this file's, which is why this list says what a guest CANNOT do */ +@/* rather than what a monitor SHOULD do. */ +@/**************************************************************************/ +@/**************************************************************************/ +@/* _start -- reset entry. Entered in THUMB state, Hyp mode (EL2). */ +@/**************************************************************************/ + + .section .text.boot, "ax" + .thumb + .align 1 + .global _start + .type _start, %function +_start: + /* Leave Thumb before anything else. start_a32 is an A32 symbol, so the + linker clears bit 0 of its address and BX switches state. */ + + ldr r0, =start_a32 + bx r0 + + .arm + .align 2 + .type start_a32, %function +start_a32: + ldr r0, =boot_stage + mov r1, #STAGE_A32 + str r1, [r0] + + /* Hyp stack, before anything can be pushed at EL2. */ + + ldr sp, =__hyp_stack_top + + /* Install the EL2 vector table. */ + + ldr r0, =el2_vectors + mcr p15, 4, r0, c12, c0, 0 /* HVBAR */ + isb + + /* Keep exceptions and interrupts routed to EL1 and the stage-2 MPU off. + HCR_HCD is cleared explicitly so HVC works from EL1 rather than + depending on its reset value. */ + + mrc p15, 4, r0, c1, c1, 0 /* HCR */ + ldr r1, =(HCR_TGE | HCR_AMO | HCR_IMO | HCR_FMO | HCR_VM | HCR_HCD) + bic r0, r0, r1 + mcr p15, 4, r0, c1, c1, 0 + isb + + /* TCM: program the bases and enable all three banks, here at EL2. + * + * This is at EL2 deliberately. Writing ENABLEEL10 from EL1 works, but + * ENABLEEL2 is silently ignored there -- measured, on both BTCM and CTCM: + * the base took and bit 0 took while bit 1 stayed clear. The TRM does not + * say so outright, but EL1 being unable to change what EL2 can see is the + * sensible reading, and doing the whole job at EL2 avoids the question. + * + * ATCM boots enabled at address 0 because CFGTCMBOOTx is tied high here, so + * this also moves it off zero, which makes a null-pointer write fault rather + * than quietly land in tightly-coupled memory. Each base must be + * size-aligned (TRM r1p3 6.2); 64KB at 0x30000000 and 16KB at 0x30100000 + * and 0x30200000 satisfy that. + * + * SIZE and WAITSTATES are read-only descriptions of the hardware, so the + * values written here keep the bits the banks already report: ATCM 64KB with + * one wait state, BTCM 16KB with none, CTCM 16KB with one. + * + * Nothing may READ the TCMs after this until they have been written, because + * ECC is enabled on this part and the check bits are not initialised by the + * core (TRM 6.2.2). bsp_main preloads them. + */ + + ldr r0, =(0x30000000 | (0x07 << 2) | (1 << 8) | (1 << 1) | (1 << 0)) + mcr p15, 0, r0, c9, c1, 0 /* IMP_ATCMREGIONR */ + + /* BTCM as well. 16 KB at zero wait states, where ATCM has one, so it is the + * faster bank for data -- which is what it is wanted for here. The claim + * that a second bank costs the data cache was withdrawn; see the note below. + * Preloaded by bsp_main before any read, as ECC requires, and BTCM accepts + * 32-bit stores where ATCM needs 64-bit (TRM 6.2.2). + */ + ldr r0, =(0x30100000 | (0x05 << 2) | (0 << 8) | (1 << 1) | (1 << 0)) + mcr p15, 0, r0, c9, c1, 1 /* IMP_BTCMREGIONR */ + isb + + /* Preload the whole bank. ECC is enabled on this part, so a TCM location + * must be written before it can be read (TRM 6.2.2), and this has to happen + * before any C runs: the demo images place thread stacks here and do not run + * bsp_boot.c, which is where the ATCM preload lives. 32-bit stores are + * sufficient for BTCM where ATCM needs 64-bit. + */ + + ldr r1, =0x30100000 + ldr r2, =0x00004000 + mov r3, #0 + +3: str r3, [r1], #4 + subs r2, r2, #4 + bgt 3b + + dsb sy + /* BTCM and CTCM are not enabled, because nothing in this example uses them. + * That is the whole reason, and it is worth stating plainly because the + * reason given here previously was wrong. + * + * This comment used to claim that enabling a second bank removed all + * measurable data-cache benefit, and it listed five configurations to prove + * it: ATCM alone showing a 24% cache gain, and four combinations involving a + * second bank showing none. It concluded that the trigger was not a + * particular bank, not the bank's address and not the ECC preload, but + * enabling a second bank at all. It cited the Cortex-R52 and S32Z2 errata as + * not covering it, and offered a shared RAM pool between the LLC and the TCMs + * as an explanation. A defect report went to NXP on that basis. + * + * All of it was an artifact of the benchmark that produced the numbers. That + * benchmark was bimodal with respect to where its loop fell inside a 64-byte + * cache line, reporting either 24% or nothing at all for identical silicon, + * and every one of those five configurations was an edit to this file, so + * every one shifted the code that followed and flipped the mode. Adding two + * nop instructions here reproduces the "second bank" result exactly, to the + * digit. The report to NXP has been withdrawn. + * + * Measured again with a benchmark that sweeps four loop alignments and + * reports the distribution, ATCM alone and ATCM plus CTCM are + * indistinguishable: + * + * loop offset in line ATCM only ATCM + CTCM + * 0 gain 0 gain 0 + * 16 gain 0 gain 0 + * 32 gain 240/1000 gain 240/1000 + * 48 gain 240/1000 gain 240/1000 + * + * So enabling a second bank costs nothing measurable. Enabling one is a + * single line, and the only prerequisites are the ECC preload before any read + * (TRM 6.2.2) and an MPU region, both of which tcm.c and mpu.c already + * handle for ATCM. They stay off until something needs them. + */ + + isb + + /* Let EL1 reach the FPU: clear HCPTR.TCP10/TCP11. The R52 always has at + least a single-precision FPU, so this matters even for a soft-float + kernel if application code uses it. */ + + mrc p15, 4, r0, c1, c1, 2 /* HCPTR */ + ldr r1, =HCPTR_TCP + bic r0, r0, r1 + mcr p15, 4, r0, c1, c1, 2 + isb + + /* Record CNTFRQ as found. Deliberately not written: see the file + header. */ + + mrc p15, 0, r0, c14, c0, 0 /* CNTFRQ as found */ + ldr r1, =cntfrq_at_el2 + str r0, [r1] + + /* Program CNTFRQ. It is writable only at the highest implemented + exception level, so it must happen here rather than in bsp_main. + CNTFRQ is a software-declared constant: nothing derives it from the + hardware, and it reads zero out of reset on this silicon exactly as it + does on the FVP. Any code computing a tick interval from it would + divide by zero. + + 8 MHz is not a guess. The counter was measured against host wall-clock + time at 8.0227 MHz over a 32-second interval, and the clock tree agrees: + RTU.GPR CFG_CNTDV reads 4, so the divider is (4+1) = 5, and the board's + FXOSC is 40 MHz -- itself confirmed independently by the LINFlexD baud + divisors the BootROM left behind. 40 / 5 = 8. */ + + ldr r0, =SYSTEM_COUNTER_HZ + mcr p15, 0, r0, c14, c0, 0 /* CNTFRQ */ + + /* HMPUIR is EL2-only, so it has to be read here rather than in bsp_main. + It gives the EL2 MPU region count, which stage-2 partitioning needs and + which the EL1 MPUIR does not report. */ + + mrc p15, 4, r0, c0, c0, 4 /* HMPUIR */ + ldr r1, =hmpuir_at_el2 + str r0, [r1] + + /* Permit EL1/EL0 use of the physical counter and timer; both reset + disabled, so an EL1 access to CNTPCT or CNTP_* would otherwise trap. */ + + mrc p15, 4, r0, c14, c1, 0 /* CNTHCTL */ + orr r0, r0, #(CNTHCTL_PL1PCTEN | CNTHCTL_PL1PCEN) + mcr p15, 4, r0, c14, c1, 0 + isb + + /* Permit EL1 to reach the GIC CPU interface through ICC_* system + registers. Needs no GIC base address, so it is safe to do here even + though the distributor address for this part is not yet established. */ + + mrc p15, 4, r0, c12, c9, 5 /* ICC_HSRE */ + orr r0, r0, #(ICC_HSRE_SRE | ICC_HSRE_ENABLE) + mcr p15, 4, r0, c12, c9, 5 + isb + + /* Clear HSCTLR.TE so EL2 exceptions are entered in A32, matching the EL2 + vector table. See the EL1 equivalent below for why this matters. */ + + mrc p15, 4, r0, c1, c0, 0 /* HSCTLR */ + bic r0, r0, #SCTLR_TE + mcr p15, 4, r0, c1, c0, 0 + isb + + /* Open the low-latency peripheral port. + IMP_PERIPHPREGIONR (Cortex-R52 TRM r1p3, 3.3.80) describes a region at + 0x76000000 of 4 MB on this part -- the RTU peripheral space -- with + separate enables for EL2 and EL1/0 that BOTH RESET TO ZERO. Until they + are set, any access in that window stalls the core: no abort, no fault + handler, no exception at all, and the debug connection drops with it. + Nothing in the SoC reference manual mentions this; it cost a run before + the TRM identified it. Written here because EL1 writes to this register + trap to EL2 when HACTLR.PERIPHPREGIONR is clear. */ + + mrc p15, 0, r0, c15, c0, 0 /* IMP_PERIPHPREGIONR */ + orr r0, r0, #(PERIPHP_ENABLE_EL2 | PERIPHP_ENABLE_EL10) + mcr p15, 0, r0, c15, c0, 0 + isb + + /* Record CPSR while still at EL2: mode bits should read 0x1A (Hyp). */ + + mrs r0, cpsr + ldr r1, =cpsr_at_el2 + str r0, [r1] + + ldr r0, =boot_stage + mov r1, #STAGE_EL2_DONE + str r1, [r0] + + /* Drop to EL1 (Supervisor), interrupts still masked. */ + + ldr r0, =el1_entry + msr ELR_hyp, r0 +#ifdef TX_ENABLE_FIQ_SUPPORT + + /* FIQ left unmasked, IRQ still masked. tx_thread_stack_build only clears a + * thread's F bit when the library was built with FIQ support and nothing + * else ever clears it, so leaving PSR_F set here would mask FIQ until the + * first thread ran and an FIQ raised from bsp_main would silently do + * nothing. + */ + + ldr r0, =(PSR_A | PSR_I | MODE_SVC) +#else + ldr r0, =(PSR_A | PSR_I | PSR_F | MODE_SVC) +#endif + msr SPSR_hyp, r0 + isb + eret + +#else + +@/**************************************************************************/ +@/* _start, when an EL2 monitor has already dropped privilege. */ +@/* */ +@/* A32, not T32, and that is the one real difference from the standalone */ +@/* entry above. The core resets in Thumb state here because the RTU boot */ +@/* instruction NXP plants is a T32 branch -- but a guest is not reached */ +@/* by reset. It is reached by the monitor's ERET, and the monitor */ +@/* chooses the state through SPSR.T. Both ZoneX and NXP's EL2M enter a */ +@/* guest in A32, matching the vector tables in this file, so this entry */ +@/* is A32 and needs no state switch of its own. */ +@/* */ +@/* SPSR.T MUST AGREE WITH THIS. If it does not, the guest dies on its */ +@/* first instruction with an undefined-instruction exception -- which */ +@/* looks exactly like a bad entry address and sends the reader to the */ +@/* loader instead of to the ERET. */ +@/**************************************************************************/ + + .section .text.boot, "ax" + .arm + .align 2 + .global _start + .type _start, %function +_start: + b el1_entry + .size _start, . - _start + +#endif + +@/**************************************************************************/ +@/* el1_entry -- first instruction at EL1. */ +@/**************************************************************************/ + + .align 2 + .type el1_entry, %function +el1_entry: + ldr r0, =boot_stage + mov r1, #STAGE_EL1 + str r1, [r0] + + /* EL1 vector table. */ + + ldr r0, =el1_vectors + mcr p15, 0, r0, c12, c0, 0 /* VBAR */ + isb + +#ifdef __ARM_FP + + /* Enable the floating-point unit for EL1. + * + * Two gates must be opened and both reset closed: + * CPACR grants EL1/EL0 access to CP10/CP11. It reads 0 out of reset, + * so the first floating-point instruction would otherwise raise + * an Undefined Instruction exception. + * FPEXC .EN enables floating-point execution itself. + * EL2 has already cleared HCPTR.TCP10/TCP11 above, so neither traps to EL2. + * + * This is the BSP's job, not the kernel's. tx_thread_vfp_enable() only sets + * the per-thread flag that makes the context switch save and restore + * floating-point state; it does not turn the hardware on. Without the code + * below, a VFP build faults on the first context switch that touches a + * D-register -- and on this silicon that fault was invisible until + * SCTLR.TE was cleared, which is the trap described further down. + * + * Guarded by __ARM_FP, which the compiler defines exactly when the build can + * emit floating-point instructions. In a soft-float build "vmsr fpexc" is + * not assemblable for this target at all. + */ + + mrc p15, 0, r0, c1, c0, 2 /* CPACR */ + orr r0, r0, #(CPACR_CP10 | CPACR_CP11) + mcr p15, 0, r0, c1, c0, 2 + isb + + mov r0, #FPEXC_EN + vmsr fpexc, r0 + isb + +#endif + + /* Clear SCTLR.TE. This is not cosmetic and it cost a long detour. + * + * TE resets SET on this silicon -- SCTLR reads 0x70C50838, bit 30 high -- + * which makes the core enter every exception in T32 state. All the vector + * tables here are A32. So an exception would branch into the table, decode + * the A32 branch as Thumb, land mid-instruction at a misaligned address and + * take an undefined-instruction exception, which then did the same thing + * again. No handler ever ran. + * + * The consequence was worse than a crash: every fault became invisible. + * boot_stage stayed at its last value, fault_vector stayed zero, and the + * core sat at el1_vectors+4 -- so a data abort from an unmapped peripheral, + * an abort from a bad MPU map, and a correctly delivered timer interrupt all + * presented identically, as an unexplained hang with nothing recorded. Two + * real bugs were diagnosed through that fog before its cause was found. + * + * The Armv8-R AEM FVP resets with TE clear, which is why the same vector + * tables work there and why this never appeared during the FVP work. */ + + mrc p15, 0, r0, c1, c0, 0 /* SCTLR */ + bic r0, r0, #SCTLR_TE + mcr p15, 0, r0, c1, c0, 0 + isb + + /* One stack per mode: an exception taken inside a handler would + otherwise reuse the interrupted mode's stack pointer. */ + + mrs r0, cpsr + bic r0, r0, #0x1F + + orr r1, r0, #MODE_IRQ + msr cpsr_c, r1 + ldr sp, =__irq_stack_top + + orr r1, r0, #MODE_FIQ + msr cpsr_c, r1 + ldr sp, =__fiq_stack_top + + orr r1, r0, #MODE_ABT + msr cpsr_c, r1 + ldr sp, =__abt_stack_top + + orr r1, r0, #MODE_UND + msr cpsr_c, r1 + ldr sp, =__und_stack_top + + orr r1, r0, #MODE_SYS + msr cpsr_c, r1 + ldr sp, =__sys_stack_top + + orr r1, r0, #MODE_SVC + msr cpsr_c, r1 + ldr sp, =__svc_stack_top + + /* Zero .bss. Nothing above this point may rely on a .bss object. */ + + ldr r0, =__bss_start__ + ldr r1, =__bss_end__ + mov r2, #0 +bss_loop: + cmp r0, r1 + strlo r2, [r0], #4 + blo bss_loop + + /* Record CPSR at EL1: mode bits should read 0x13 (Supervisor), which is + what proves the EL2 to EL1 drop actually happened. */ + + mrs r0, cpsr + ldr r1, =cpsr_at_el1 + str r0, [r1] + + ldr r0, =boot_stage + mov r1, #STAGE_MAIN + str r1, [r0] + + bl bsp_main + + /* bsp_main is not expected to return. */ + + ldr r0, =boot_stage + mov r1, #STAGE_RETURNED + str r1, [r0] +park: + b park + +@/**************************************************************************/ +@/**************************************************************************/ +@/* el1_irq_entry -- IRQ vector, standalone. */ +@/* */ +@/* The FVP example routes IRQ through _tx_thread_context_save and */ +@/* __tx_irq_processing_return, which this image cannot do: it does not */ +@/* link ThreadX, deliberately, so that a failure here is a board problem */ +@/* and not a kernel one. This is the classic A32 form instead. */ +@/* */ +@/* lr is adjusted by 4 because an IRQ is taken with lr pointing one */ +@/* instruction past the one that was interrupted. The caret on the ldm */ +@/* restores CPSR from SPSR_irq as it loads pc, which is what returns to */ +@/* the interrupted mode rather than staying in IRQ mode. */ +@/**************************************************************************/ + + .align 2 + .global el1_irq_entry + .type el1_irq_entry, %function +el1_irq_entry: +#ifdef TX_R52_USE_THREADX_IRQ + + /* With the kernel linked, the vector goes straight into + _tx_thread_context_save, which saves the interrupted context and + branches back to __tx_irq_processing_return. A plain B, not BL: + context save adjusts lr itself to find the point of interrupt. */ + + b _tx_thread_context_save + + .global __tx_irq_processing_return +__tx_irq_processing_return: +#ifdef TX_ENABLE_IRQ_NESTING + + /* Nested IRQ handling, the same shape as the FVP example, and the ordering + * matters as much here. The interrupt is acknowledged in IRQ mode with + * interrupts still masked, BEFORE nesting starts: reading ICC_IAR1 raises + * the GIC running priority to this interrupt's own, which masks it and + * everything of equal or lower priority, and only then is re-enabling IRQ + * safe. Start nesting first and the still-pending, still-level-asserted + * timer is retaken the instant IRQ is enabled, recursing until the IRQ and + * System stacks are gone -- a hang with no fault to point at. + * + * The INTID rides in r4, which survives the mode switch because only SP and + * LR are banked, and is pushed on the IRQ stack as well so a nested level + * reusing r4 cannot lose this level's value. End-of-interrupt waits for + * IRQ mode with interrupts masked again, so dropping the running priority + * cannot re-admit this same interrupt. + */ + + bl gicv3_acknowledge + mov r4, r0 + stmdb sp!, {r4, r5} /* r5 keeps 8-byte alignment */ + + bl _tx_thread_irq_nesting_start + mov r0, r4 + bl board_irq_service + bl _tx_thread_irq_nesting_end + + ldmia sp!, {r4, r5} + mov r0, r4 + bl gicv3_end_of_interrupt +#else + bl board_irq_handler +#endif + b _tx_thread_context_restore + + +#else + + /* Standalone: the classic A32 form. lr is adjusted by 4 because an IRQ + is taken with lr one instruction past the interrupted one, and the + caret on the ldm restores CPSR from SPSR_irq as it loads pc. */ + + sub lr, lr, #4 + stmfd sp!, {r0-r3, r12, lr} + bl board_irq_handler + ldmfd sp!, {r0-r3, r12, pc}^ + +#endif + +@/**************************************************************************/ +@/* el1_fiq_entry -- FIQ vector at EL1. */ +@/**************************************************************************/ + +el1_fiq_entry: +#if defined(TX_ENABLE_FIQ_SUPPORT) && defined(TX_R52_USE_THREADX_IRQ) + + /* Both guards are needed. TX_ENABLE_FIQ_SUPPORT is PUBLIC on the threadx + * target, so it reaches every image once the library is built with FIQ, + * including images that link no interrupt controller; referencing the GIC + * and board symbols below from those breaks the link outright. That is why + * the IRQ vector is gated the same way. + * + * Group 0 is what the GIC delivers as FIQ, and it has its own acknowledge + * and end-of-interrupt registers -- the Group 1 pair returns the spurious + * INTID for a Group 0 interrupt and leaves it pending, which looks like a + * storm rather than a mistake. As on the IRQ path, the acknowledge happens + * before nesting starts, so the running priority masks this interrupt before + * FIQ is re-enabled. + */ + + b _tx_thread_fiq_context_save + + .global __tx_fiq_processing_return +__tx_fiq_processing_return: +#ifdef TX_ENABLE_FIQ_NESTING + bl gicv3_acknowledge_group0 + mov r4, r0 + stmdb sp!, {r4, r5} + + bl _tx_thread_fiq_nesting_start + mov r0, r4 + bl board_fiq_service + bl _tx_thread_fiq_nesting_end + + ldmia sp!, {r4, r5} + mov r0, r4 + bl gicv3_end_of_interrupt_group0 +#else + bl board_fiq_handler +#endif + b _tx_thread_fiq_context_restore +#else + b fault_el1_fiq +#endif + +@/**************************************************************************/ +@/* try_execute_data -- branch to a non-executable address on purpose. */ +@/* */ +@/* Saves its own return address for the prefetch-abort handler to use, */ +@/* arms that handler, then branches to the address in r0. It never */ +@/* returns by itself: the abort handler returns on its behalf. Written */ +@/* in assembly because the return path is not something C can express. */ +@/**************************************************************************/ + + .align 2 + .global try_execute_data + .type try_execute_data, %function +try_execute_data: + ldr r1, =abort_return + str lr, [r1] + ldr r1, =fault_expected_pabt + mov r2, #1 + str r2, [r1] + dsb + isb + bx r0 /* fetch from XN memory: aborts */ + +@/* Fault reporters. Each records which vector ran, a syndrome and a */ +@/* faulting address, then parks. Recording before parking is what makes */ +@/* a fault distinguishable from a hang when there is no console. */ +@/**************************************************************************/ + +@/* Each handler loads its syndrome into r0 and a faulting or return */ +@/* address into r3, then falls into FAULT_TAIL. The instructions are not*/ +@/* passed as macro arguments: GNU as macro arguments are textual, and */ +@/* an instruction containing a comma cannot be passed without quoting, */ +@/* whose handling differs between assembler versions. */ + + .macro FAULT_TAIL vecid + ldr r1, =fault_vector + mov r2, #\vecid + str r2, [r1] + ldr r1, =fault_syndrome + str r0, [r1] + ldr r1, =fault_address + str r3, [r1] + ldr r1, =boot_stage + mov r2, #STAGE_FAULT + str r2, [r1] +1: b 1b + .endm + + .align 2 + +fault_el2_reset: + mov r0, #0 + mov r3, lr + FAULT_TAIL 0x20 +fault_el2_undef: + mov r0, #0 + mov r3, lr + FAULT_TAIL 0x24 +fault_el2_svc: + mov r0, #0 + mov r3, lr + FAULT_TAIL 0x28 +fault_el2_pabt: + mrc p15, 4, r0, c5, c1, 0 /* HSR */ + mov r3, lr + FAULT_TAIL 0x2C +fault_el2_dabt: + mrc p15, 4, r0, c5, c1, 0 /* HSR */ + mrc p15, 4, r3, c6, c0, 0 /* HDFAR */ + FAULT_TAIL 0x30 +fault_el2_hyp: + mrc p15, 4, r0, c5, c2, 0 /* HSR (EC identifies the trap) */ + mrc p15, 4, r3, c6, c0, 0 /* HDFAR */ + FAULT_TAIL 0x34 +fault_el2_irq: + mov r0, #0 + mov r3, lr + FAULT_TAIL 0x38 +fault_el2_fiq: + mov r0, #0 + mov r3, lr + FAULT_TAIL 0x3C + +fault_el1_reset: + mov r0, #0 + mov r3, lr + FAULT_TAIL 0x40 +fault_el1_undef: + mov r0, #0 + mov r3, lr + FAULT_TAIL 0x44 +fault_el1_svc: + mov r0, #0 + mov r3, lr + FAULT_TAIL 0x48 +fault_el1_pabt: +#ifdef TXM_MODULE_MANAGER + /* A fault from User mode is a module reaching outside its regions, and it + belongs to the module manager, which will record it and terminate the + thread. A fault from a privileged mode continues on the recoverable path + below, which the boot probes depend on. + + r0 is pushed and popped around the test rather than simply used. The code + below clobbers r0 through r2 without saving them, which is tolerable for a + probe that provokes its own fault, but the module capture records the + faulting registers and would record ours instead. LDM does not affect the + flags, so the comparison still holds after the pop. */ + + stmdb sp!, {r0} + mrs r0, spsr + and r0, r0, #0x1F + cmp r0, #0x10 /* User mode? */ + ldmia sp!, {r0} + beq _txm_module_manager_prefetch_abort +#endif + + /* Recoverable when a test has armed fault_expected_pabt. + * + * Recovery differs from the data-abort case in kind, not degree. There, + * the faulting instruction is a load or store and skipping it is enough. + * Here the fault is on the instruction FETCH at the target address, so + * there is no instruction to skip -- resuming anywhere near the fault + * just faults again. So the test saves the address it wants to come back + * to in abort_return, and this handler returns there on its behalf. */ + + ldr r0, =fault_expected_pabt + ldr r1, [r0] + cmp r1, #0 + beq fault_el1_pabt_fatal + + mov r1, #0 + str r1, [r0] /* one-shot: disarm */ + + mrc p15, 0, r2, c5, c0, 1 /* IFSR */ + ldr r0, =fault_syndrome + str r2, [r0] + mrc p15, 0, r2, c6, c0, 2 /* IFAR */ + ldr r0, =fault_address + str r2, [r0] + ldr r0, =fault_vector + mov r1, #0x4C + str r1, [r0] + ldr r0, =fault_taken + ldr r1, [r0] + add r1, r1, #1 + str r1, [r0] + dsb + + ldr lr, =abort_return + ldr lr, [lr] + subs pc, lr, #0 /* return to the test, not here */ + +fault_el1_pabt_fatal: + mrc p15, 0, r0, c5, c0, 1 /* IFSR */ + mrc p15, 0, r3, c6, c0, 2 /* IFAR */ + FAULT_TAIL 0x4C +fault_el1_dabt: +#ifdef TXM_MODULE_MANAGER + /* A fault from User mode is a module reaching outside its regions, and it + belongs to the module manager, which will record it and terminate the + thread. A fault from a privileged mode continues on the recoverable path + below, which the boot probes depend on. + + r0 is pushed and popped around the test rather than simply used. The code + below clobbers r0 through r2 without saving them, which is tolerable for a + probe that provokes its own fault, but the module capture records the + faulting registers and would record ours instead. LDM does not affect the + flags, so the comparison still holds after the pop. */ + + stmdb sp!, {r0} + mrs r0, spsr + and r0, r0, #0x1F + cmp r0, #0x10 /* User mode? */ + ldmia sp!, {r0} + beq _txm_module_manager_data_abort +#endif + + /* Recoverable when a test has armed fault_expected. The protection map is + only meaningful if a violation actually faults, so the test provokes one + deliberately and this path lets it continue: record the syndrome and + address, count it, then resume at the instruction AFTER the faulting one. + lr on data-abort entry is faulting_address + 8, so subs pc, lr, #4 skips + the access rather than retrying it forever. + + Without the flag set, a data abort is still fatal and reported, which is + what any real bug should get. */ + + ldr r0, =fault_expected + ldr r1, [r0] + cmp r1, #0 + beq fault_el1_dabt_fatal + + mov r1, #0 + str r1, [r0] /* one-shot: disarm */ + + mrc p15, 0, r2, c5, c0, 0 /* DFSR */ + ldr r0, =fault_syndrome + str r2, [r0] + mrc p15, 0, r2, c6, c0, 0 /* DFAR */ + ldr r0, =fault_address + str r2, [r0] + ldr r0, =fault_vector + mov r1, #0x50 + str r1, [r0] + ldr r0, =fault_taken + ldr r1, [r0] + add r1, r1, #1 + str r1, [r0] + dsb + subs pc, lr, #4 /* resume after the access */ + +fault_el1_dabt_fatal: + mrc p15, 0, r0, c5, c0, 0 /* DFSR */ + mrc p15, 0, r3, c6, c0, 0 /* DFAR */ + FAULT_TAIL 0x50 +fault_el1_reserved: + mov r0, #0 + mov r3, lr + FAULT_TAIL 0x54 +fault_el1_irq: + mov r0, #0 + mov r3, lr + FAULT_TAIL 0x58 +fault_el1_fiq: + mov r0, #0 + mov r3, lr + FAULT_TAIL 0x5C diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/gic_probe.c b/ports/cortex_r52/gnu/example_build/s32z280_evb/gic_probe.c new file mode 100644 index 00000000..b861ae5b --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/gic_probe.c @@ -0,0 +1,72 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* gic_probe.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frederic Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Asks the core where its GIC is, and whether it can be reached. */ +/* */ +/* A load from the GIC window NXP documents for this cluster stalls */ +/* the core outright, so nothing here touches memory. Both values */ +/* below are system registers: reading them cannot hang the bus. */ +/* */ +/* IMP_CBAR holds the physical base address of the memory-mapped GIC */ +/* distributor -- bits [31:21], wired by the SoC's CFGPERIPHBASE */ +/* input. That is the authoritative answer, as against an address */ +/* taken from a vendor debugger script. */ +/* */ +/* IMP_PERIPHPREGIONR describes the low-latency peripheral port: base */ +/* address in [31:12], size in [6:2], and separate enables for EL2 */ +/* (bit 1) and EL1/0 (bit 0). If the port is disabled, accesses in */ +/* its window go nowhere, which is what a stalled load looks like. */ +/* */ +/* Both from the Cortex-R52 TRM r1p3 (100026_0103_00_en), sections */ +/* 3.3.17 and 3.3.80 -- the SoC reference manual documents neither. */ +/* */ +/* MISRA C:2012 deviations (justified) */ +/* */ +/* Dir 4.3 -- assembly encapsulated one access per function. */ +/* */ +/**************************************************************************/ + +#include "gic_probe.h" + +unsigned int gic_read_cbar(void) +{ + unsigned int value; + + __asm volatile ("mrc p15, 1, %0, c15, c3, 0" : "=r" (value)); + + return value; +} + + +unsigned int gic_read_periphpregionr(void) +{ + unsigned int value; + + __asm volatile ("mrc p15, 0, %0, c15, c0, 0" : "=r" (value)); + + return value; +} diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/gic_probe.h b/ports/cortex_r52/gnu/example_build/s32z280_evb/gic_probe.h new file mode 100644 index 00000000..eaa992de --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/gic_probe.h @@ -0,0 +1,44 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* gic_probe.h Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frederic Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Where the GIC is, and whether the peripheral port reaches it. */ +/* */ +/**************************************************************************/ + +#ifndef GIC_PROBE_H +#define GIC_PROBE_H + +/* IMP_CBAR[31:21] = PERIPHBASE, the GIC distributor base. */ + +unsigned int gic_read_cbar(void); + +/* IMP_PERIPHPREGIONR: LLPP base [31:12], size [6:2], EL2 enable bit 1, + EL1/0 enable bit 0. */ + +unsigned int gic_read_periphpregionr(void); + +#endif /* GIC_PROBE_H */ diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/gicv3.c b/ports/cortex_r52/gnu/example_build/s32z280_evb/gicv3.c new file mode 100644 index 00000000..1bca8dde --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/gicv3.c @@ -0,0 +1,409 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* gicv3.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* GICv3 bring-up for Cortex-R52 on the Armv8-R AEM FVP. */ +/* */ +/* Model characteristics that shape this code, taken from its own */ +/* parameters rather than assumed: */ +/* */ +/* has-two-security-states=0 single security state, so GICD_CTLR.DS */ +/* reads 1 and the Group 1 enable is bit 1*/ +/* ARE-fixed-to-one=1 affinity routing cannot be turned off */ +/* priority-bits=5 only the top 5 priority bits exist, so */ +/* priorities must be multiples of 8 */ +/* */ +/* MISRA C:2012 deviations (justified) */ +/* */ +/* Directive 4.3 -- the CPU interface is only reachable through CP15 */ +/* system registers, so inline assembly is unavoidable. Every such */ +/* access is encapsulated in a one-line accessor below and appears */ +/* nowhere else. */ +/* Rule 11.4/11.6 -- casting integer addresses to volatile pointers is */ +/* inherent to memory-mapped device access; confined to REG32. */ +/* */ +/**************************************************************************/ + +#include "platform.h" +#include "gicv3.h" + +/* Distributor registers. */ + +#define GICD_CTLR 0x0000U +#define GICD_CTLR_ENABLE_GRP0 (1UL << 0) +#define GICD_CTLR_ENABLE_GRP1 (1UL << 1) +#define GICD_CTLR_ARE (1UL << 4) + +/* Redistributor RD frame. */ + +#define GICR_WAKER 0x0014U +#define GICR_WAKER_PROC_SLEEP (1UL << 1) +#define GICR_WAKER_CHILD_ASLEEP (1UL << 2) + +/* Redistributor SGI frame (SGIs and PPIs, INTID 0-31). */ + +#define GICR_IGROUPR0 0x0080U +#define GICR_ISENABLER0 0x0100U +#define GICR_IPRIORITYR 0x0400U +#define GICR_ICFGR1 0x0C04U + +#define ICC_PMR_UNMASK_ALL 0xFFUL +#define ICC_SRE_SRE (1UL << 0) +#define ICC_IGRPEN1_ENABLE (1UL << 0) + +#define PPI_FIRST_INTID 16U + + +/**************************************************************************/ +/* CPU interface accessors (AArch32 ICC_* system registers). */ +/**************************************************************************/ + +static unsigned long read_icc_sre(void) +{ + unsigned long value; + __asm__ volatile("mrc p15, 0, %0, c12, c12, 5" : "=r"(value)); + return value; +} + +static void write_icc_sre(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c12, c12, 5" : : "r"(value) : "memory"); +} + +static void write_icc_pmr(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c4, c6, 0" : : "r"(value) : "memory"); +} + +static void write_icc_bpr1(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c12, c12, 3" : : "r"(value) : "memory"); +} + +static void write_icc_igrpen0(unsigned long value) +{ + /* ICC_IGRPEN0 sits at c12, c12, 6, beside IGRPEN1 at opc2 7. */ + + __asm__ volatile("mcr p15, 0, %0, c12, c12, 6" : : "r"(value) : "memory"); +} + +static void write_icc_bpr0(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c12, c8, 3" : : "r"(value) : "memory"); +} + +static void write_icc_igrpen1(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c12, c12, 7" : : "r"(value) : "memory"); +} + +static void instruction_barrier(void) +{ + __asm__ volatile("isb" : : : "memory"); +} + + +/**************************************************************************/ +/* gicv3_init */ +/**************************************************************************/ + +void gicv3_init(void) +{ + unsigned long waker; + + /* Distributor: affinity routing first, then the Group 1 enable. With a + single security state Group 1 is bit 1 of GICD_CTLR. */ + + REG32(GICD_BASE + GICD_CTLR) |= GICD_CTLR_ARE; + REG32(GICD_BASE + GICD_CTLR) |= GICD_CTLR_ENABLE_GRP1; + + /* Group 0 as well, which is what the GIC delivers as FIQ. Enabling it costs + nothing while no interrupt is assigned to that group, and images that never + use FIQ see no change: assignment is per interrupt, in GICR_IGROUPR0. */ + + REG32(GICD_BASE + GICD_CTLR) |= GICD_CTLR_ENABLE_GRP0; + + /* Redistributor: clear ProcessorSleep and wait for the redistributor to + report that its children are awake, otherwise no interrupt can be + delivered to this core. */ + + waker = REG32(GICR_RD_BASE + GICR_WAKER); + waker &= ~GICR_WAKER_PROC_SLEEP; + REG32(GICR_RD_BASE + GICR_WAKER) = waker; + + while ((REG32(GICR_RD_BASE + GICR_WAKER) & GICR_WAKER_CHILD_ASLEEP) != 0UL) + { + /* Wait for the redistributor to wake. */ + } + + /* CPU interface: system-register access, then unmask all priorities and + enable Group 1. EL2 has already permitted EL1 system-register access + via ICC_HSRE in entry.S. */ + + write_icc_sre(read_icc_sre() | ICC_SRE_SRE); + instruction_barrier(); + + write_icc_pmr(ICC_PMR_UNMASK_ALL); + write_icc_bpr1(0UL); + write_icc_igrpen1(ICC_IGRPEN1_ENABLE); + + /* The CPU interface half of Group 0: a Group 0 interrupt reaches the core + only if both the distributor and this are enabled. BPR0 at zero gives no + subpriority grouping, so priority alone decides preemption, which is what + the FIQ nesting test depends on. */ + + write_icc_bpr0(0UL); + write_icc_igrpen0(ICC_IGRPEN1_ENABLE); + instruction_barrier(); +} + + +/**************************************************************************/ +/* gicv3_enable_ppi */ +/**************************************************************************/ + +void gicv3_enable_ppi(unsigned int intid, unsigned int priority) +{ + unsigned long shift; + + if ((intid < PPI_FIRST_INTID) || (intid > 31U)) + { + return; + } + + /* Group 1. */ + + REG32(GICR_SGI_BASE + GICR_IGROUPR0) |= (1UL << intid); + + /* Priority is a byte per INTID. Only the top 5 bits are implemented on + this model, so callers pass multiples of 8. */ + + REG32(GICR_SGI_BASE + GICR_IPRIORITYR + (intid & ~3U)) &= + ~(0xFFUL << ((intid & 3U) * 8U)); + REG32(GICR_SGI_BASE + GICR_IPRIORITYR + (intid & ~3U)) |= + ((unsigned long) priority & 0xFFUL) << ((intid & 3U) * 8U); + + /* Level-sensitive: two configuration bits per INTID, 00 = level. The + generic timer asserts a level, not an edge. */ + + shift = ((unsigned long) intid - PPI_FIRST_INTID) * 2UL; + REG32(GICR_SGI_BASE + GICR_ICFGR1) &= ~(3UL << shift); + + /* Enable. */ + + REG32(GICR_SGI_BASE + GICR_ISENABLER0) = (1UL << intid); +} + + +/**************************************************************************/ +/* gicv3_acknowledge */ +/**************************************************************************/ + +/**************************************************************************/ +/* gicv3_enable_sgi */ +/* */ +/* An SGI lives in the same redistributor frame as a PPI, so this is */ +/* gicv3_enable_ppi without the ICFGR step: INTIDs 0-15 have no */ +/* configurable edge/level, they are always edge-triggered. */ +/**************************************************************************/ + +void gicv3_enable_sgi(unsigned int intid, unsigned int priority) +{ + if (intid > 15U) + { + return; + } + + REG32(GICR_SGI_BASE + GICR_IGROUPR0) |= (1UL << intid); + + REG32(GICR_SGI_BASE + GICR_IPRIORITYR + (intid & ~3U)) &= + ~(0xFFUL << ((intid & 3U) * 8U)); + REG32(GICR_SGI_BASE + GICR_IPRIORITYR + (intid & ~3U)) |= + ((unsigned long) priority & 0xFFUL) << ((intid & 3U) * 8U); + + REG32(GICR_SGI_BASE + GICR_ISENABLER0) = (1UL << intid); +} + + +/**************************************************************************/ +/* gicv3_send_sgi */ +/* */ +/* ICC_SGI1R is 64-bit, so in AArch32 it is an MCRR rather than an MCR. */ +/* Fields: INTID in [27:24], TargetList in [15:0], Aff1/2/3 and IRM zero */ +/* for this single-core configuration, so TargetList = 1 selects core 0. */ +/* */ +/* The AArch64 name for this register is S3_0_C12_C11_5, which the */ +/* Cortex-A72 example uses, but that encoding does not transcribe to the */ +/* AArch32 64-bit CP15 space. The CRm here was confirmed by observing */ +/* that the SGI is actually delivered and acknowledged with the expected */ +/* INTID rather than by reading it off the A-profile alias. */ +/**************************************************************************/ + +void gicv3_send_sgi(unsigned int intid) +{ + unsigned long low = (((unsigned long) intid & 0xFUL) << 24) | 1UL; + unsigned long high = 0UL; + + __asm volatile ("mcrr p15, 0, %0, %1, c12" :: "r" (low), "r" (high) : "memory"); + instruction_barrier(); +} + + +/**************************************************************************/ +/* gicv3_priority_bits */ +/* */ +/* How many priority bits this GIC actually implements, discovered rather */ +/* than assumed: write 0xFF to a priority byte and see which bits stick. */ +/* The unimplemented bits are the low ones and they read as zero. */ +/* */ +/* This matters for nesting. Two interrupts only preempt one another if */ +/* their priorities differ AFTER truncation, so a test that picks values */ +/* 0x20 apart on a GIC keeping four bits is testing nothing. The FVP */ +/* keeps five; real silicon need not agree. */ +/* */ +/* Called before the SGI is configured, and it leaves the byte at zero, so */ +/* the caller must set the priority it wants afterwards. */ +/**************************************************************************/ + +unsigned int gicv3_priority_bits(unsigned int scratch_intid) +{ + unsigned long shift; + unsigned long readback; + unsigned int bits = 0U; + + if (scratch_intid > 31U) + { + return 0U; + } + + shift = (unsigned long) (scratch_intid & 3U) * 8UL; + + REG32(GICR_SGI_BASE + GICR_IPRIORITYR + (scratch_intid & ~3U)) |= + (0xFFUL << shift); + readback = (REG32(GICR_SGI_BASE + GICR_IPRIORITYR + (scratch_intid & ~3U)) + >> shift) & 0xFFUL; + REG32(GICR_SGI_BASE + GICR_IPRIORITYR + (scratch_intid & ~3U)) &= + ~(0xFFUL << shift); + + while (readback != 0UL) + { + if ((readback & 1UL) != 0UL) + { + bits++; + } + readback >>= 1; + } + + return bits; +} + + +/**************************************************************************/ +/* gicv3_enable_sgi_group0 */ +/* */ +/* As gicv3_enable_sgi, but leaves the interrupt in Group 0 so it arrives */ +/* as an FIQ. The group is chosen by clearing the GICR_IGROUPR0 bit; the */ +/* Group 1 version sets it. */ +/**************************************************************************/ + +void gicv3_enable_sgi_group0(unsigned int intid, unsigned int priority) +{ + if (intid > 15U) + { + return; + } + + REG32(GICR_SGI_BASE + GICR_IGROUPR0) &= ~(1UL << intid); + + REG32(GICR_SGI_BASE + GICR_IPRIORITYR + (intid & ~3U)) &= + ~(0xFFUL << ((intid & 3U) * 8U)); + REG32(GICR_SGI_BASE + GICR_IPRIORITYR + (intid & ~3U)) |= + ((unsigned long) priority & 0xFFUL) << ((intid & 3U) * 8U); + + REG32(GICR_SGI_BASE + GICR_ISENABLER0) = (1UL << intid); +} + + +/**************************************************************************/ +/* gicv3_send_sgi_group0 */ +/* */ +/* ICC_SGI0R rather than ICC_SGI1R: the two differ only in opc1, 2 against */ +/* 0, and each raises the SGI into its own group. Raising a Group 0 */ +/* interrupt through the Group 1 register does not deliver it as an FIQ. */ +/**************************************************************************/ + +void gicv3_send_sgi_group0(unsigned int intid) +{ + unsigned long low = (((unsigned long) intid & 0xFUL) << 24) | 1UL; + unsigned long high = 0UL; + + __asm volatile ("mcrr p15, 2, %0, %1, c12" :: "r" (low), "r" (high) : "memory"); + instruction_barrier(); +} + + +/**************************************************************************/ +/* gicv3_acknowledge_group0 / gicv3_end_of_interrupt_group0 */ +/* */ +/* Group 0 has its own pair at c12, c8, where Group 1 uses c12, c12. */ +/* Acknowledging a Group 0 interrupt through IAR1 returns the spurious */ +/* INTID and leaves the interrupt pending, which presents as an FIQ storm. */ +/**************************************************************************/ + +unsigned long gicv3_acknowledge_group0(void) +{ + unsigned long intid; + + __asm__ volatile("mrc p15, 0, %0, c12, c8, 0" : "=r"(intid)); + return intid & 0xFFFFFFUL; +} + + +void gicv3_end_of_interrupt_group0(unsigned long intid) +{ + instruction_barrier(); + __asm__ volatile("mcr p15, 0, %0, c12, c8, 1" : : "r"(intid) : "memory"); +} + + +unsigned long gicv3_acknowledge(void) +{ + unsigned long intid; + + __asm__ volatile("mrc p15, 0, %0, c12, c12, 0" : "=r"(intid)); + + return intid; +} + + +/**************************************************************************/ +/* gicv3_end_of_interrupt */ +/**************************************************************************/ + +void gicv3_end_of_interrupt(unsigned long intid) +{ + __asm__ volatile("mcr p15, 0, %0, c12, c12, 1" : : "r"(intid) : "memory"); +} diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/gicv3.h b/ports/cortex_r52/gnu/example_build/s32z280_evb/gicv3.h new file mode 100644 index 00000000..184a5b6d --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/gicv3.h @@ -0,0 +1,86 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* gicv3.h Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* GICv3 interrupt controller for Cortex-R52. The CPU interface is */ +/* reached through AArch32 ICC_* system registers; the Distributor and */ +/* Redistributor are memory mapped. */ +/* */ +/**************************************************************************/ + +#ifndef GICV3_H +#define GICV3_H + +/* Spurious interrupt ID returned by the CPU interface when no interrupt is + pending. It must be neither dispatched nor acknowledged with EOI. */ + +#define GICV3_SPURIOUS_INTID 1023UL + +/* Bring up the Distributor, this core's Redistributor and the CPU + interface. Interrupts remain masked at the PSTATE level. */ + +void gicv3_init(void); + +/* Enable one private peripheral interrupt (PPI, INTID 16-31) as a + level-sensitive Group 1 interrupt at the given priority. */ + +void gicv3_enable_ppi(unsigned int intid, unsigned int priority); + +/* Enable a software-generated interrupt, INTID 0-15. Same registers as a PPI: + the redistributor's SGI/PPI frame covers INTIDs 0-31. */ + +void gicv3_enable_sgi(unsigned int intid, unsigned int priority); + +/* Raise an SGI on this core. */ + +void gicv3_send_sgi(unsigned int intid); + +/* Group 0 support, which is what produces FIQ rather than IRQ. With a single + security state the GIC routes Group 0 to FIQ and Group 1 to IRQ, so an + interrupt has to be moved into Group 0 to arrive as an FIQ at all. Group 0 + has its own acknowledge and end-of-interrupt registers; using the Group 1 ones + on a Group 0 interrupt does not work. */ + +void gicv3_enable_sgi_group0(unsigned int intid, unsigned int priority); +void gicv3_send_sgi_group0(unsigned int intid); +unsigned long gicv3_acknowledge_group0(void); +void gicv3_end_of_interrupt_group0(unsigned long intid); + +/* Implemented priority bits, discovered by write and readback. Leaves the + scratch INTID's priority byte at zero. */ + +unsigned int gicv3_priority_bits(unsigned int scratch_intid); + +/* Acknowledge the highest-priority pending Group 1 interrupt, returning its + INTID (possibly GICV3_SPURIOUS_INTID). */ + +unsigned long gicv3_acknowledge(void); + +/* Signal completion of the interrupt previously acknowledged. */ + +void gicv3_end_of_interrupt(unsigned long intid); + +#endif /* GICV3_H */ diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/irq_dispatch.c b/ports/cortex_r52/gnu/example_build/s32z280_evb/irq_dispatch.c new file mode 100644 index 00000000..92fbe97b --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/irq_dispatch.c @@ -0,0 +1,462 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* irq_dispatch.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frederic Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Interrupt dispatch for the S32Z280-594EVB, standalone. */ +/* */ +/* Called from el1_irq_entry in entry.S. Unlike the FVP example this */ +/* does not call _tx_timer_interrupt: the image does not link ThreadX, */ +/* so a failure here is a board or GIC problem and not a kernel one. */ +/* It counts ticks instead, which is what makes the interrupt visible. */ +/* */ +/* Every count is volatile and read by the debugger as well as printed.*/ +/* An interrupt that never arrives and an interrupt that arrives and is*/ +/* mishandled look identical from the console alone, and the separate */ +/* spurious and unexpected counters distinguish them. */ +/* */ +/**************************************************************************/ + +#include "gicv3.h" +#include "timer.h" +#include "board.h" +#include "mpu.h" +#ifdef TX_R52_USE_THREADX_IRQ +#include "tx_api.h" + +/* ThreadX periodic timer entry point. Assembly, and internal to the kernel, + so it is not declared by tx_api.h. Safe to call from C: it uses only r0-r3 + and returns through lr like an ordinary function. */ + +extern void _tx_timer_interrupt(void); +#endif + +/* Observable from both the console and a debugger. */ + +volatile unsigned long board_irq_count; +volatile unsigned long board_timer_intid; +volatile unsigned long board_spurious_count; +volatile unsigned long board_unexpected_intid; +volatile unsigned long board_first_intid = 0xFFFFFFFFUL; + +volatile unsigned long board_nest_depth; +volatile unsigned long board_nest_max; +volatile unsigned long board_sgi_count; +volatile unsigned long board_sgi_nested_count; +volatile unsigned long board_nest_provoke; + +volatile unsigned long board_fiq_count; +volatile unsigned long board_fiq_high_count; +volatile unsigned long board_fiq_nested_count; +volatile unsigned long board_fiq_depth; +volatile unsigned long board_fiq_max_depth; +volatile unsigned long board_fiq_unexpected; +volatile unsigned long board_fiq_provoke; +volatile unsigned long board_priority_bits; +volatile unsigned long board_timer_priority; +volatile unsigned long board_sgi_priority; + +/* The EL1 physical timer PPI. 30 is the architectural assignment and is what + the FVP turned out to use, but it was discovered there by enabling the whole + PPI range and recording whichever INTID arrived in ICC_IAR1 rather than by + assuming. board_timer_intid records what actually arrives here, so a wrong + value shows up as an unexpected INTID rather than as silence. */ + +#define TIMER_PPI_INTID 30U + +/* Only the top priority bits are implemented; hence a multiple of 8. */ + +#define TIMER_PPI_PRIORITY 0xA0U + + +#ifdef TX_R52_USE_THREADX_IRQ + +/**************************************************************************/ +/* board_init -- called from _tx_initialize_low_level. */ +/* */ +/* Order matters. mpu_init() must run before anything touches the GIC: */ +/* the distributor is unreachable until its region is mapped Device */ +/* nGnRnE, and an access before that aborts. Arming the timer here is */ +/* safe because interrupts stay masked until _tx_thread_schedule enables */ +/* them, so no tick can arrive before the kernel can service one. */ +/**************************************************************************/ + +void board_init(void) +{ + (void) mpu_init(); + gicv3_init(); + gicv3_enable_ppi(TIMER_PPI_INTID, TIMER_PPI_PRIORITY); + + /* How many priority bits this GIC keeps decides whether the SGI can preempt + the timer at all: equal priorities do not preempt, and it is the low bits + that vanish. Discovered rather than assumed, because the FVP keeps five + and real silicon need not agree. Recorded so the demo can report it, and + a nesting failure caused by the two priorities colliding is visible + instead of mysterious. */ + + board_priority_bits = gicv3_priority_bits(BOARD_NEST_SGI_INTID); + + /* Half the timer's value so the SGI outranks it: numerically lower wins. + With TIMER_PPI_PRIORITY at 0xA0 that is 0x50, which stays distinct even + if only three priority bits survive truncation. */ + + gicv3_enable_sgi(BOARD_NEST_SGI_INTID, TIMER_PPI_PRIORITY / 2U); + +#ifdef TX_ENABLE_FIQ_SUPPORT + + /* Two Group 0 SGIs for the FIQ path. Group 0 is what the GIC delivers as + FIQ; the high one takes a numerically lower priority so it can preempt the + low one, which is what FIQ nesting means. Both sit above the timer so the + IRQ and FIQ paths do not interfere. */ + + gicv3_enable_sgi_group0(BOARD_FIQ_LOW_INTID, 0x40U); + gicv3_enable_sgi_group0(BOARD_FIQ_HIGH_INTID, 0x20U); +#endif + + /* The priorities as the GIC will actually compare them. Only the top + board_priority_bits survive; the low ones read back as zero, so two values + that differ only there would collapse together and never preempt. Recorded + here because this is where the constants live. */ + + { + unsigned long keep = (board_priority_bits >= 8UL) + ? 0xFFUL + : ((0xFFUL << (8UL - board_priority_bits)) & 0xFFUL); + + board_timer_priority = (unsigned long) TIMER_PPI_PRIORITY & keep; + board_sgi_priority = ((unsigned long) TIMER_PPI_PRIORITY / 2UL) & keep; + } + + /* One tick every 10 ms at the measured 8 MHz counter rate. */ + + timer_start_oneshot_irq(timer_read_cntfrq() / 100U); +} + +#endif + + +/**************************************************************************/ +/* board_irq_service -- service an already-acknowledged INTID. */ +/* */ +/* Split out so the nesting path in entry.S can acknowledge in IRQ mode */ +/* before nesting starts. Does not acknowledge and does not EOI. */ +/**************************************************************************/ + +/**************************************************************************/ +/* Handler execution time, and where the handler's instructions live. */ +/* */ +/* TX_R52_ATCM_ISR places the service routine in ATCM. ATCM runs at full */ +/* core speed with one wait state; .text lives in RTU code RAM, which runs */ +/* at half the core frequency (S32Z2 RM 6.3.6). ATCM also has no cache to */ +/* miss, which is the property that matters for a determinism argument. */ +/* */ +/* Measured in cycles from the PMU counter, not CNTPCT: at 8 MHz CNTPCT */ +/* cannot resolve a handler body, let alone the variation in one. */ +/* */ +/* The timing wrapper below stays in .text in both configurations, so its */ +/* own cost appears in every sample and cancels when the two are compared. */ +/* Only the body moves. What matters in the result is the spread: a warm */ +/* instruction cache can match ATCM on the mean and cannot match it on the */ +/* worst case. */ +/**************************************************************************/ + +#define BOARD_LATENCY_SAMPLES 64U + +unsigned int board_service_cycles[BOARD_LATENCY_SAMPLES]; +unsigned int board_service_count; + +#ifdef TX_R52_ATCM_ISR +#define BOARD_ISR_SECTION __attribute__((section(".atcm_text"))) +#else +#define BOARD_ISR_SECTION +#endif + +static inline __attribute__((always_inline)) +void board_irq_service_impl(unsigned long intid); + + + + +/* Aligned for the same reason as cache_workload: this body is timed, and + without a fixed alignment the figure moves with unrelated code changes + elsewhere in the image. */ + +/* Four placements of one implementation. The logic below is inlined into each + of the four wrappers further down, each 64-byte aligned and then displaced by + a different offset within the line, and the measurement rotates through them. + + A single placement is not measurable. The cache benchmark in this file's + sibling turned out to be bimodal with respect to exactly this, reporting 24% + or 0% for identical silicon depending on where a loop fell in a line, and it + invalidated a handler comparison and a defect report to NXP before anyone + noticed. Pinning one alignment does not fix that, it just chooses a mode. */ + +static inline __attribute__((always_inline)) +void board_irq_service_impl(unsigned long intid) +{ + if (intid == GICV3_SPURIOUS_INTID) + { + /* Nothing was actually pending. Must not be acknowledged with an + end-of-interrupt write, so return without one. */ + + board_spurious_count++; + return; + } + + /* After the spurious check, so a spurious entry does not read as nesting. + Reaches 2 only if a second interrupt is taken while this one is still on + the stack, which cannot happen without TX_ENABLE_IRQ_NESTING. */ + + board_nest_depth++; + if (board_nest_depth > board_nest_max) + { + board_nest_max = board_nest_depth; + } + + if (board_first_intid == 0xFFFFFFFFUL) + { + board_first_intid = intid; /* whatever actually arrived */ + } + + if (intid == (unsigned long) TIMER_PPI_INTID) + { + board_timer_intid = intid; + board_irq_count++; + + /* Re-arm first. The generic timer output is level-sensitive, so until + the compare is moved forward the condition stays asserted and the + interrupt would be taken again immediately on return. */ + + timer_start_oneshot_irq(timer_read_cntfrq() / 100U); + + /* Provoke nesting, if this image asked for it. Only useful because + _tx_thread_irq_nesting_start has already re-enabled IRQ; the bounded + spin gives the GIC time to deliver the higher-priority SGI before this + handler returns, so the nesting is deterministic rather than a race we + hope wins. The bound matters: with nesting compiled out the SGI can + never arrive here and this must not become a hang. */ + + if (board_nest_provoke != 0UL) + { + unsigned long seen = board_sgi_count; + unsigned long guard; + + gicv3_send_sgi(BOARD_NEST_SGI_INTID); + + for (guard = 0UL; guard < 100000UL; guard++) + { + if (board_sgi_count != seen) + { + break; + } + } + } + +#ifdef TX_R52_USE_THREADX_IRQ + /* Drive the kernel's time base. Called after the re-arm so the next + interval is already running while the kernel does its bookkeeping. */ + + _tx_timer_interrupt(); +#endif + } + else if (intid == (unsigned long) BOARD_NEST_SGI_INTID) + { + board_sgi_count++; + + if (board_nest_depth > 1UL) + { + board_sgi_nested_count++; + } + } + else + { + board_unexpected_intid = intid; + } + + board_nest_depth--; +} + + +/* The four placements. pad nops shift the inlined body within its line. */ + +#define MAKE_ISR_BODY(name, pad) \ +__attribute__((aligned(64), noinline)) \ +static BOARD_ISR_SECTION void name(unsigned long intid) \ +{ \ + __asm__ volatile(".rept " #pad "\n\tnop\n\t.endr"); \ + board_irq_service_impl(intid); \ +} + +MAKE_ISR_BODY(board_irq_body_a, 0) +MAKE_ISR_BODY(board_irq_body_b, 4) +MAKE_ISR_BODY(board_irq_body_c, 8) +MAKE_ISR_BODY(board_irq_body_d, 12) + +#define BOARD_ISR_PLACEMENTS 4U +#define BOARD_SAMPLES_EACH (BOARD_LATENCY_SAMPLES / BOARD_ISR_PLACEMENTS) + +static void (*const board_irq_bodies[BOARD_ISR_PLACEMENTS])(unsigned long) = +{ + board_irq_body_a, /* body at line offset 0 */ + board_irq_body_b, /* body at line offset 16 */ + board_irq_body_c, /* body at line offset 32 */ + board_irq_body_d /* body at line offset 48 */ +}; + + +void board_irq_service(unsigned long intid) +{ + unsigned int before; + unsigned int after; + + /* A block of samples per placement, so each alignment is measured under the + same conditions rather than interleaved with the others. */ + + { + unsigned int slot = board_service_count / BOARD_SAMPLES_EACH; + + if (slot >= BOARD_ISR_PLACEMENTS) + { + slot = BOARD_ISR_PLACEMENTS - 1U; + } + + before = timer_read_cycles(); + board_irq_bodies[slot](intid); + after = timer_read_cycles(); + } + + if (board_service_count < BOARD_LATENCY_SAMPLES) + { + board_service_cycles[board_service_count] = after - before; + board_service_count++; + } +} + + +/**************************************************************************/ +/* board_irq_handler -- the non-nesting entry point. */ +/* */ +/* Acknowledges, services and ends the interrupt in IRQ mode with */ +/* interrupts masked, which is what every image built without */ +/* TX_ENABLE_IRQ_NESTING did before this split. */ +/**************************************************************************/ + +void board_irq_handler(void) +{ + unsigned long intid = gicv3_acknowledge(); + + board_irq_service(intid); + + if (intid != GICV3_SPURIOUS_INTID) + { + gicv3_end_of_interrupt(intid); + } +} + + +#ifdef TX_ENABLE_FIQ_SUPPORT +/**************************************************************************/ +/* board_fiq_service -- service an already-acknowledged Group 0 INTID. */ +/* */ +/* The FIQ counterpart of board_irq_service, and split for the same */ +/* reason: the acknowledge has to happen before nesting starts. Does not */ +/* acknowledge and does not EOI. */ +/**************************************************************************/ + +void board_fiq_service(unsigned long intid) +{ + if (intid == GICV3_SPURIOUS_INTID) + { + return; + } + + board_fiq_depth++; + if (board_fiq_depth > board_fiq_max_depth) + { + board_fiq_max_depth = board_fiq_depth; + } + + if (intid == (unsigned long) BOARD_FIQ_LOW_INTID) + { + board_fiq_count++; + + /* Provoke the nested FIQ from inside this handler. Only reachable + because _tx_thread_fiq_nesting_start has re-enabled FIQ; the bounded + spin lets the GIC deliver the higher-priority Group 0 SGI before this + handler returns. Bounded, because with nesting compiled out the + second FIQ cannot arrive and this must not hang. */ + + if (board_fiq_provoke != 0UL) + { + unsigned long seen = board_fiq_high_count; + unsigned long guard; + + gicv3_send_sgi_group0(BOARD_FIQ_HIGH_INTID); + + for (guard = 0UL; guard < 100000UL; guard++) + { + if (board_fiq_high_count != seen) + { + break; + } + } + } + } + else if (intid == (unsigned long) BOARD_FIQ_HIGH_INTID) + { + board_fiq_high_count++; + + /* Depth above 1 means this FIQ arrived inside another FIQ handler. */ + + if (board_fiq_depth > 1UL) + { + board_fiq_nested_count++; + } + } + else + { + board_fiq_unexpected = intid; + } + + board_fiq_depth--; +} + + +/**************************************************************************/ +/* board_fiq_handler -- the non-nesting FIQ entry point. */ +/**************************************************************************/ + +void board_fiq_handler(void) +{ + unsigned long intid = gicv3_acknowledge_group0(); + + board_fiq_service(intid); + + if (intid != GICV3_SPURIOUS_INTID) + { + gicv3_end_of_interrupt_group0(intid); + } +} +#endif diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/linflexd.c b/ports/cortex_r52/gnu/example_build/s32z280_evb/linflexd.c new file mode 100644 index 00000000..cc6a7773 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/linflexd.c @@ -0,0 +1,276 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* linflexd.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Polled console on LINFlex_9, which is the instance wired to the */ +/* S32Z280-594EVB daughtercard USB-UART through jumper J248. Not */ +/* LINFlex_0: that is simply the first instance in the Reference */ +/* Manual's list and reaches no connector on this board. */ +/* */ +/* BAUD RATE */ +/* */ +/* LINFlex_9 is clocked by P5_LIN_BAUD_CLK, which MC_CGM_5 MUX 2 */ +/* drives. Read from the board: MUX_2_CSS selects source 2 with the */ +/* divider set to 1, and the EVB carries a 40 MHz crystal (UG10268 */ +/* section 3.3.1.1), so the clock is 40 MHz. */ +/* */ +/* LFDIV = 40000000 / (16 * 115200) = 21.7014 */ +/* LINIBRR = 21 */ +/* LINFBRR = round(0.7014 * 16) = 11 */ +/* actual = 40000000 / (16 * 21.6875) = 115274 baud, 0.06% error */ +/* */ +/* Those are exactly the values the BootROM had already left in the */ +/* registers, which is what confirms the 40 MHz figure rather than */ +/* merely assuming it. They are programmed here anyway: inheriting */ +/* another program's register state makes this image's behaviour */ +/* depend on how the board was last booted. */ +/* */ +/* The BootROM's configuration is NOT reused for a second reason: it */ +/* leaves PCE set (parity enabled) and TxEn clear, because it is */ +/* listening for a serial-boot download rather than printing. A host */ +/* terminal set to 8N1 against that would show framing errors. */ +/* */ +/* MISRA C:2012 deviations (justified) */ +/* */ +/* Rule 11.4/11.6 -- casting integer addresses to volatile pointers is */ +/* inherent to memory-mapped device access; confined to REG32. */ +/* */ +/**************************************************************************/ + +#include "linflexd.h" +#include "platform.h" + +/* Register offsets, from the S32Z2 Reference Manual section 75.5.1. */ + +#define LINFLEXD_LINCR1 0x00U +#define LINFLEXD_LINSR 0x08U +#define LINFLEXD_UARTCR 0x10U +#define LINFLEXD_UARTSR 0x14U +#define LINFLEXD_LINFBRR 0x24U +#define LINFLEXD_LINIBRR 0x28U +#define LINFLEXD_BDRL 0x38U + +/* LINCR1 */ +#define LINCR1_INIT (1U << 0) /* initialisation mode */ +#define LINCR1_SLEEP (1U << 1) + +/* UARTCR. Bit positions verified against the register diagram; PCE at bit 2 + and TxEn at bit 4 are the two that matter here and are easy to transpose. */ +#define UARTCR_UART (1U << 0) /* UART rather than LIN mode */ +#define UARTCR_WL0 (1U << 1) /* with WL1 clear: 8-bit */ +#define UARTCR_PCE (1U << 2) /* parity enable */ +#define UARTCR_TXEN (1U << 4) +#define UARTCR_RXEN (1U << 5) + +/* UARTSR */ +#define UARTSR_DTF (1U << 1) /* transmit complete, w1c */ + +/* LINSR[15:12] is the LIN state; 0x1 is initialisation mode. Confirmed by + reading 0x2000 (idle) from the BootROM-configured module. */ +#define LINSR_LINS_MASK (0xFU << 12) +#define LINSR_LINS_INIT (0x1U << 12) + +/* Bounded rather than infinite: a console that hangs the boot is worse than + one that reports it could not configure itself. */ +#define LINFLEXD_INIT_GUARD 100000U + +/* 115200 from a 40 MHz P5_LIN_BAUD_CLK; see the file header. */ +#define LINFLEXD_IBRR_115200 21U +#define LINFLEXD_FBRR_115200 11U + +#define CONSOLE_BASE S32Z_LINFLEX_9_BASE + + +/* One full pass of the configuration sequence. Called twice by + linflexd_init; see the comment there for why. */ + +static unsigned int linflexd_configure_once(void) +{ + unsigned int status = LINFLEXD_INIT_OK; + unsigned int guard; + unsigned int wanted; + unsigned int got; + + /* Enter initialisation mode and WAIT until the module reports it. + Most of UARTCR is writable only in initialisation mode, and the module + does not enter it in the same cycle as the LINCR1 write. Configuring + without waiting silently half-works: bits being *set* take effect while + bits being *cleared* do not, so PCE stays enabled and the line runs 8E1 + against a host expecting 8N1 -- which corrupts only those characters + whose parity bit happens to be 0 and leaves the rest readable, looking + for all the world like a marginal baud rate. */ + + REG32(CONSOLE_BASE + LINFLEXD_LINCR1) = LINCR1_INIT; + + guard = LINFLEXD_INIT_GUARD; + while (((REG32(CONSOLE_BASE + LINFLEXD_LINSR) & LINSR_LINS_MASK) + != LINSR_LINS_INIT) && (guard > 0U)) + { + guard--; + } + + if (guard == 0U) + { + status |= LINFLEXD_INIT_NO_INITMODE; + } + + /* The UART bit gates writes to the rest of UARTCR, so it has to be set + before the fields that depend on it, not alongside them. */ + + REG32(CONSOLE_BASE + LINFLEXD_UARTCR) = UARTCR_UART; + + /* 8N1, transmit and receive. PCE deliberately left clear: the BootROM + enables parity for serial boot, and a host on 8N1 would see framing + errors. */ + + wanted = UARTCR_UART | UARTCR_WL0 | UARTCR_TXEN | UARTCR_RXEN; + REG32(CONSOLE_BASE + LINFLEXD_UARTCR) = wanted; + + REG32(CONSOLE_BASE + LINFLEXD_LINFBRR) = LINFLEXD_FBRR_115200; + REG32(CONSOLE_BASE + LINFLEXD_LINIBRR) = LINFLEXD_IBRR_115200; + + /* Read back before leaving initialisation mode. A write that was + silently ignored is the failure this driver already made once, so it is + checked rather than assumed. */ + + got = REG32(CONSOLE_BASE + LINFLEXD_UARTCR) & 0xFFU; + if (got != wanted) + { + status |= LINFLEXD_INIT_UARTCR_MISMATCH; + } + + /* Leave initialisation mode; the module starts operating. */ + + REG32(CONSOLE_BASE + LINFLEXD_LINCR1) = 0U; + + return status; +} + + +unsigned int linflexd_init(void) +{ + unsigned int status; + + /* The sequence runs twice, and one pass is genuinely not enough. + A single pass gives a working console when this file is compiled at -O0 + and a corrupted one at -O2: every character partially wrong, while UARTCR + reads back exactly the value written, LINIBRR and LINFBRR read back + exactly the values written, and the returned status is + LINFLEXD_INIT_OK. The registers are right and the line is wrong, so the + failure is invisible to the caller -- the worst property a console can + have, and the reason this is worth two passes at boot. + + The mechanism is not understood. Tested and rejected as explanations: + the wait for initialisation mode (a bound 100x larger changes nothing), + a settling delay before the first LINSR read, a settling delay after + leaving initialisation mode, a barrier and read-back between the two + UARTCR writes, and waiting for LINSR to report the exit from + initialisation mode. None of those makes a single pass work at -O2. + A second pass does, reliably, at both optimisation levels. + + Verified on the S32Z280-594EVB with the whole BSP at -O0 and at -O2. + If the underlying behaviour is ever identified, revisit this. */ + + status = linflexd_configure_once(); + status |= linflexd_configure_once(); + + return status; +} + + +void linflexd_putc(char c) +{ + if (c == '\n') + { + linflexd_putc('\r'); + } + + unsigned int guard; + + /* Start the byte, wait for completion, clear the flag, then wait for the + clear to actually take effect. + * + * The last step is the subtle one. DTF is write-one-to-clear and does + * not de-assert in the same cycle as the clearing write, so without it + * the *next* byte's poll can observe this byte's flag, conclude the line + * is free while it is still busy, and have its write silently dropped by + * the transmitter. That cost exactly one character after every "\r\n" + * pair -- the only place two bytes are sent back to back -- and showed + * up as the first letter of every line going missing. + * + * Clearing *before* the write instead does not fix it and is worse: the + * write then lands while the previous byte is still shifting and is + * dropped, and the poll afterwards sees the previous byte's completion, + * so most of the output disappears. Order matters in both directions. + */ + + REG32(CONSOLE_BASE + LINFLEXD_BDRL) = (unsigned int)(unsigned char)c; + + /* Polled on purpose: this console runs before any interrupt controller is + configured, and it must work inside a fault handler. */ + + while ((REG32(CONSOLE_BASE + LINFLEXD_UARTSR) & UARTSR_DTF) == 0U) + { + /* wait for this byte to go out */ + } + + REG32(CONSOLE_BASE + LINFLEXD_UARTSR) = UARTSR_DTF; + + guard = LINFLEXD_INIT_GUARD; + while (((REG32(CONSOLE_BASE + LINFLEXD_UARTSR) & UARTSR_DTF) != 0U) + && (guard > 0U)) + { + guard--; + } +} + + +void linflexd_puts(const char *string) +{ + const char *p = string; + + if (p != (const char *)0) + { + while (*p != '\0') + { + linflexd_putc(*p); + p++; + } + } +} + + +void linflexd_put_hex32(unsigned int value) +{ + static const char digits[] = "0123456789ABCDEF"; + unsigned int shift = 32U; + + while (shift > 0U) + { + shift -= 4U; + linflexd_putc(digits[(value >> shift) & 0xFU]); + } +} diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/linflexd.h b/ports/cortex_r52/gnu/example_build/s32z280_evb/linflexd.h new file mode 100644 index 00000000..e1945733 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/linflexd.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* linflexd.h Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Polled transmit-only console on LINFlexD, for the S32Z280-594EVB. */ +/* */ +/**************************************************************************/ + +#ifndef LINFLEXD_H +#define LINFLEXD_H + +/* Status bits returned by linflexd_init(). */ + +#define LINFLEXD_INIT_OK 0x00U +#define LINFLEXD_INIT_NO_INITMODE 0x01U /* never entered init mode */ +#define LINFLEXD_INIT_UARTCR_MISMATCH 0x02U /* a write was ignored */ + +/* Configure LINFlex_9 for 115200 8N1 and enable the transmitter. Returns + LINFLEXD_INIT_OK, or a mask of the above. It is worth checking: writes to + UARTCR outside initialisation mode are silently dropped, which produces + output that looks like a baud-rate problem rather than a framing one. */ + +unsigned int linflexd_init(void); + +/* Blocking single character. Translates \n to \r\n so the output is + readable on a terminal that does not add the carriage return itself. */ + +void linflexd_putc(char c); + +void linflexd_puts(const char *string); + +/* Eight hex digits, no 0x prefix. Printing register values is the whole + reason this console exists, and a printf would drag in a C library this + image deliberately does not link. */ + +void linflexd_put_hex32(unsigned int value); + +#endif /* LINFLEXD_H */ diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/link.lds b/ports/cortex_r52/gnu/example_build/s32z280_evb/link.lds new file mode 100644 index 00000000..796cb88d --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/link.lds @@ -0,0 +1,198 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/* Link map for the NXP S32Z280-594EVB, RTU0 core 0. + * + * Code goes to the RTU code SRAM at its instruction-fetch address, 0x79900000, + * which is also where the core resets: MC_ME_PRTN0_CORE0_ADDR reads 0x79900000 + * on this board, so a debugger-loaded image needs no relocation to be reached + * by the reset vector. That window is writable over the debug AXI port even + * though NXP's debugger memory map declares it read-only, so plain `load` + * works and no LMA alias is required. A flash-booted image is different: it + * would have to be written through the data alias at 0x32100000. + * + * Data, bss and the per-mode stacks go to the RTU-local data SRAM at + * 0x31780000. Keeping them out of the code SRAM avoids the boundary problem + * the FVP build hit, where an aligned .data start straddled the code/data + * split, and RTU-local traffic does not leave the RTU. + * + * The TCMs are deliberately unused: they are not accessible at reset until + * their region registers are programmed, so nothing needed for early boot can + * live there. + */ + +__hyp_stack_size__ = 0x0800; +__svc_stack_size__ = 0x1000; +__irq_stack_size__ = 0x0800; +__fiq_stack_size__ = 0x0400; +__abt_stack_size__ = 0x0400; +__und_stack_size__ = 0x0400; +__sys_stack_size__ = 0x0800; + +MEMORY +{ + CODE (rx) : ORIGIN = 0x79900000, LENGTH = 0x00700000 /* 7 MB */ + /* ATCM. entry.S programmes IMP_ATCMREGIONR to this base and enables the + bank before anything runs from it, and mpu.c maps it executable. Code + placed here runs at full core speed with one wait state, where CODE is + RTU code RAM and runs at half the core frequency (S32Z2 RM 6.3.6). */ + ATCM (rwx) : ORIGIN = 0x30000000, LENGTH = 0x00010000 /* 64 KB */ + /* BTCM. 16 KB at zero wait states, enabled by entry.S, and preloaded there + as well because ECC means a location must be written before it is read. + Used for data that wants deterministic access without depending on a + cache -- an enabled TCM is never cached. */ + BTCM (rw) : ORIGIN = 0x30100000, LENGTH = 0x00004000 /* 16 KB */ + DATA (rwx) : ORIGIN = 0x31780000, LENGTH = 0x00070000 /* 448 KB: DRAM0 + DRAM1 below the module area */ + /* Module memory, uncovered by any kernel MPU region on purpose. See + S32Z_MODULE_AREA_BASE in platform.h. */ + MODULE (rwx) : ORIGIN = 0x317F0000, LENGTH = 0x00010000 /* 64 KB */ +} + +ENTRY(_start) + +SECTIONS +{ + /* .text.boot must come FIRST: MC_ME_PRTN0_CORE0_ADDR is 0x79900000 on + * this board, so whatever lands at the start of CODE is what the core + * executes out of reset, and that has to be _start. It must also be a + * T32 instruction, since the core resets in Thumb state. The vector + * tables need alignment but not a fixed address, because HVBAR and VBAR + * are programmed at run time -- so they follow rather than lead. + */ + /* No ALIGN() address expression on any output section below. Written as + * "<name> ALIGN(n) : { }" the ALIGN is the section's *address*, evaluated + * from a location counter that starts at zero, and it silently overrides + * "> CODE" -- every section then lands near address 0 and the link fails + * with "not within region". The FVP script uses that form harmlessly only + * because its region starts at 0x00000000. Alignment comes from the input + * sections (the vector tables carry .align 5) or from an explicit + * ". = ALIGN(n);" inside the braces. + */ + .boot : + { + KEEP(*(.text.boot)) + } > CODE + + .vectors_el2 : + { + KEEP(*(.vectors_el2)) + } > CODE + + .vectors_el1 : + { + KEEP(*(.vectors_el1)) + } > CODE + + .text : + { + *(.text*) + *(.glue_7) + *(.glue_7t) + } > CODE + + /* Code that runs from tightly-coupled memory. Loaded into CODE and copied + to ATCM by atcm_copy_text() before it is called, because ECC is enabled + on this part and a TCM location must be written before it is read + (Cortex-R52 TRM 6.2.2). The copy uses 64-bit stores for the same + reason: ATCM requires them where BTCM and CTCM accept 32-bit. + + ALIGN(8) at both ends so the copy can move whole 64-bit units without a + tail case, which is what keeps the ECC requirement satisfied for every + location written. */ + + .atcm_text : ALIGN(8) + { + __atcm_text_start__ = .; + *(.atcm_text*) + . = ALIGN(8); + __atcm_text_end__ = .; + } > ATCM AT> CODE + + __atcm_text_load__ = LOADADDR(.atcm_text); + + .rodata : + { + *(.rodata*) + . = ALIGN(4); + __code_end__ = .; + } > CODE + + .data : + { + . = ALIGN(8); + __data_start__ = .; + *(.data*) + . = ALIGN(8); + __data_end__ = .; + } > DATA + + /* Data placed in BTCM. NOLOAD: nothing is copied in, and entry.S has + already written every location in the bank to establish ECC check bits, + so anything here is safe to read before it is written by the program. */ + + .btcm_bss (NOLOAD) : ALIGN(8) + { + __btcm_bss_start__ = .; + *(.btcm_bss*) + . = ALIGN(8); + __btcm_bss_end__ = .; + } > BTCM + + .bss (NOLOAD) : + { + . = ALIGN(8); + __bss_start__ = .; + *(.bss*) + *(COMMON) + . = ALIGN(8); + __bss_end__ = .; + } > DATA + + /* Per-mode stacks. Each mode needs its own on an R-profile core: an + * exception taken while a handler is running would otherwise reuse the + * interrupted mode's stack pointer. + */ + .stacks (NOLOAD) : + { + . = ALIGN(8); + . = . + __hyp_stack_size__; + __hyp_stack_top = .; + . = ALIGN(8); + . = . + __svc_stack_size__; + __svc_stack_top = .; + . = ALIGN(8); + . = . + __irq_stack_size__; + __irq_stack_top = .; + . = ALIGN(8); + . = . + __fiq_stack_size__; + __fiq_stack_top = .; + . = ALIGN(8); + . = . + __abt_stack_size__; + __abt_stack_top = .; + . = ALIGN(8); + . = . + __und_stack_size__; + __und_stack_top = .; + . = ALIGN(8); + . = . + __sys_stack_size__; + __sys_stack_top = .; + } > DATA + + /* Both spellings: _tx_initialize_low_level publishes the first free + address to tx_application_define from _end, while C library conventions + use end. */ + + PROVIDE(end = .); + PROVIDE(_end = .); +} diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/mpu.c b/ports/cortex_r52/gnu/example_build/s32z280_evb/mpu.c new file mode 100644 index 00000000..c8277fc4 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/mpu.c @@ -0,0 +1,622 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* mpu.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* PMSAv8-R memory protection for the NXP S32Z280-594EVB. */ +/* */ +/* Register model (AArch32): a region is selected through PRSELR and */ +/* then described by PRBAR (base, shareability, access permission, */ +/* execute-never) and PRLAR (inclusive limit, attribute index, enable).*/ +/* Both addresses have a 64-byte granule, so the low six bits of each */ +/* register hold attributes rather than address. Memory types come */ +/* from MAIR through the attribute index, not from the region itself. */ +/* */ +/* Caches are invalidated before being enabled. On this model they */ +/* come out of reset invalid, but silicon does not guarantee that, and */ +/* this code is meant to be the template the S32Z280 port starts from. */ +/* */ +/* MISRA C:2012 deviations (justified) */ +/* */ +/* Directive 4.3 -- the MPU, MAIR, SCTLR and cache maintenance are only*/ +/* reachable through CP15; every access is encapsulated in a one-line*/ +/* accessor below. */ +/* */ +/**************************************************************************/ + +#include "platform.h" + +/* Progress marker, mirrored into memory. Enabling the MPU can take the core + down in a way that records no fault and takes the console with it, so each + step here is stamped where a debugger can read it afterwards. */ + +extern unsigned int probe_stage; +#define MARK(n) do { probe_stage = (unsigned int)(n); } while (0) +#include "mpu.h" +#include "platform.h" + +/* Progress marker, mirrored into memory. Enabling the MPU can take the core + down in a way that records no fault and takes the console with it, so each + step here is stamped where a debugger can read it afterwards. */ + +extern unsigned int probe_stage; +#define MARK(n) do { probe_stage = (unsigned int)(n); } while (0) + +/* SCTLR bits. */ + +#define SCTLR_M (1UL << 0) /* MPU enable */ +#define SCTLR_C (1UL << 2) /* data cache enable */ +#define SCTLR_I (1UL << 12) /* instruction cache enable */ + +/* MAIR attribute bytes. Index 0 is Normal write-back read/write-allocate, + index 1 is Device-nGnRnE, matching MPU_ATTR_* in the header. */ + +#define MAIR_ATTR_NORMAL_WB 0xFFUL +#define MAIR_ATTR_DEVICE 0x00UL + +/* Boundaries supplied by the linker script, all 64-byte aligned. */ + +extern char __code_end__; +extern char __data_start__; +extern char __data_end__; + + +/**************************************************************************/ +/* The region table. */ +/* */ +/* Deliberately minimal and readable: code is read-only and executable, */ +/* everything writable is non-executable, and the peripheral half of the */ +/* address map is Device. A write-protected code region is the point -- */ +/* it is what makes the MPU do something observable, and AR2 extends the */ +/* same table with per-module regions. */ +/**************************************************************************/ + +/* Table capacity. The FVP example this file came from declared this as [3], + matching the three regions it programs; a five-region table then wrote past + the end of it, and the region-3 base assignment landed outside the array. + The region read back with base 0 and limit 0x763FFFFF, overlapping regions + 0-2, and PMSAv8-R makes overlapping regions UNPREDICTABLE -- which is what + killed the core on the SCTLR.M write. Named and guarded so the next map + cannot repeat it. */ + +#define MPU_TABLE_REGIONS 16U + +static MPU_REGION mpu_regions[MPU_TABLE_REGIONS]; +static unsigned int mpu_regions_used; + + +/**************************************************************************/ +/* CP15 accessors. */ +/**************************************************************************/ + +static unsigned long read_mpuir(void) +{ + unsigned long value; + __asm__ volatile("mrc p15, 0, %0, c0, c0, 4" : "=r"(value)); + return value; +} + +static unsigned long read_sctlr(void) +{ + unsigned long value; + __asm__ volatile("mrc p15, 0, %0, c1, c0, 0" : "=r"(value)); + return value; +} + +static void write_sctlr(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c1, c0, 0" : : "r"(value) : "memory"); +} + +static void write_prselr(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c6, c2, 1" : : "r"(value) : "memory"); +} + +static void write_prbar(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c6, c3, 0" : : "r"(value) : "memory"); +} + +static void write_prlar(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c6, c3, 1" : : "r"(value) : "memory"); +} + +static unsigned long read_prbar(void) +{ + unsigned long value; + __asm__ volatile("mrc p15, 0, %0, c6, c3, 0" : "=r"(value)); + return value; +} + +static unsigned long read_prlar(void) +{ + unsigned long value; + __asm__ volatile("mrc p15, 0, %0, c6, c3, 1" : "=r"(value)); + return value; +} + +static void write_mair0(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c10, c2, 0" : : "r"(value) : "memory"); +} + +static void write_mair1(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c10, c2, 1" : : "r"(value) : "memory"); +} + +static void invalidate_icache_all(void) +{ + __asm__ volatile("mcr p15, 0, r0, c7, c5, 0" : : : "memory"); /* ICIALLU */ +} + +static void data_sync_barrier(void) +{ + __asm__ volatile("dsb" : : : "memory"); +} + +static void instruction_barrier(void) +{ + __asm__ volatile("isb" : : : "memory"); +} + + +/**************************************************************************/ +/* mpu_region_count */ +/**************************************************************************/ + +unsigned int mpu_region_count(void) +{ + /* MPUIR.REGION occupies bits [15:8]. */ + + return (unsigned int) ((read_mpuir() >> 8) & 0xFFUL); +} + + +/**************************************************************************/ +/* mpu_is_enabled / mpu_caches_enabled */ +/**************************************************************************/ + +unsigned int mpu_is_enabled(void) +{ + return ((read_sctlr() & SCTLR_M) != 0UL) ? 1U : 0U; +} + +unsigned int mpu_caches_enabled(void) +{ + unsigned long sctlr = read_sctlr(); + + return (((sctlr & SCTLR_C) != 0UL) && ((sctlr & SCTLR_I) != 0UL)) ? 1U : 0U; +} + + +/**************************************************************************/ +/* mpu_region_table */ +/**************************************************************************/ + +const MPU_REGION *mpu_region_table(unsigned int *count_ptr) +{ + if (count_ptr != 0) + { + *count_ptr = mpu_regions_used; + } + + return mpu_regions; +} + + +/**************************************************************************/ +/* mpu_read_region */ +/**************************************************************************/ + +void mpu_read_region(unsigned int index, unsigned long *prbar_ptr, + unsigned long *prlar_ptr) +{ + write_prselr((unsigned long) index); + instruction_barrier(); + + if (prbar_ptr != 0) + { + *prbar_ptr = read_prbar(); + } + if (prlar_ptr != 0) + { + *prlar_ptr = read_prlar(); + } +} + + +/**************************************************************************/ +/* build_table -- describe this image's memory. */ +/**************************************************************************/ + +/* Bisection switch. probe_stage stops at 0x42 with the real five-region map, + meaning the SCTLR.M write itself kills the core with no exception taken. + That has two very different causes: the map is wrong for the first fetch or + stack access, or enabling the MPU on this part needs something we have not + done at all. A single region spanning the whole address space, permissive + and Normal, distinguishes them: if the core survives with this, the map is + at fault; if it dies anyway, enabling M is. */ + +static void build_table(void) +{ + /* Protection map: narrow regions with real permissions, so that anything + this image should not do actually faults. + * + * This supersedes the permissive bring-up map (everything Normal RW X, + * whole address space covered). That map existed because a narrow one + * had failed and the reason was unknown -- the failure looked like a + * stall with nothing recorded, and coverage was blamed. Coverage was + * never the problem: an out-of-bounds write to a [3]-sized table put a + * region at base 0 overlapping everything, and SCTLR.TE being set meant + * the resulting abort could not run a handler. Both are fixed, so a + * narrow map is now both correct and diagnosable: if something outside + * these regions is touched, the fault handler records vector, syndrome + * and faulting address instead of hanging mutely. + * + * Anything not described below is unmapped and faults on access. That + * is the point. + */ + + /* Code: read-only and executable. A write here must fault. */ + + mpu_regions[0].mpu_region_base = S32Z_CODE_SRAM_BASE; + mpu_regions[0].mpu_region_limit = (unsigned long) &__code_end__ - 1UL; + mpu_regions[0].mpu_region_ap = MPU_AP_RO_EL1; + mpu_regions[0].mpu_region_execute_never = 0U; + mpu_regions[0].mpu_region_shareability = MPU_SH_NON; + mpu_regions[0].mpu_region_attr_index = MPU_ATTR_NORMAL_WB; + mpu_regions[0].mpu_region_name = "code RO X normal-wb"; + + /* Data, bss and every per-mode stack: writable, never executable. Sized + from the SRAM banks, not from __data_end__: the stacks are NOLOAD and sit + above that symbol, so sizing from it would leave them unmapped and the + first push after enabling the MPU would abort. + + This covers DRAM0 and DRAM1 together -- 512 KB, contiguous, both at full + core speed. DRAM1 went undeclared until the banks were read out of the + Reference Manual and confirmed on the board. */ + + mpu_regions[1].mpu_region_base = S32Z_DATA_SRAM_BASE; + /* Stops before the module area at the top of DRAM1. A module's memory must + not be covered by a kernel region, or every thread could reach it and the + manager's own regions would be decoration. */ + + mpu_regions[1].mpu_region_limit = S32Z_DATA_SRAM_BASE + + S32Z_DATA_SRAM_SHARED_SIZE - 1UL; + mpu_regions[1].mpu_region_ap = MPU_AP_RW_EL1; + mpu_regions[1].mpu_region_execute_never = 1U; + mpu_regions[1].mpu_region_shareability = MPU_SH_NON; + mpu_regions[1].mpu_region_attr_index = MPU_ATTR_NORMAL_WB; + mpu_regions[1].mpu_region_name = "data RW NX normal-wb"; + + /* Console. Mapped narrowly: one 64 KB LINFlexD instance. */ + + mpu_regions[2].mpu_region_base = S32Z_LINFLEX_9_BASE; + mpu_regions[2].mpu_region_limit = S32Z_LINFLEX_9_BASE + 0xFFFFUL; + mpu_regions[2].mpu_region_ap = MPU_AP_RW_EL1; + mpu_regions[2].mpu_region_execute_never = 1U; + mpu_regions[2].mpu_region_shareability = MPU_SH_NON; + mpu_regions[2].mpu_region_attr_index = MPU_ATTR_DEVICE; + mpu_regions[2].mpu_region_name = "uart RW NX device"; + + /* GIC. Device nGnRnE is required by the Cortex-R52 TRM, and confirmed + here: mapped Normal the distributor stalls the core. */ + + mpu_regions[3].mpu_region_base = S32Z_GIC_BASE; + mpu_regions[3].mpu_region_limit = S32Z_GIC_BASE + S32Z_GIC_SIZE - 1UL; + mpu_regions[3].mpu_region_ap = MPU_AP_RW_EL1; + mpu_regions[3].mpu_region_execute_never = 1U; + mpu_regions[3].mpu_region_shareability = MPU_SH_NON; + mpu_regions[3].mpu_region_attr_index = MPU_ATTR_DEVICE; + mpu_regions[3].mpu_region_name = "gic RW NX device"; + + /* RTU peripheral window behind the low-latency peripheral port. */ + + mpu_regions[4].mpu_region_base = S32Z_RTU_PERIPH_BASE; + mpu_regions[4].mpu_region_limit = S32Z_RTU_PERIPH_BASE + + S32Z_RTU_PERIPH_SIZE - 1UL; + mpu_regions[4].mpu_region_ap = MPU_AP_RW_EL1; + mpu_regions[4].mpu_region_execute_never = 1U; + mpu_regions[4].mpu_region_shareability = MPU_SH_NON; + mpu_regions[4].mpu_region_attr_index = MPU_ATTR_DEVICE; + mpu_regions[4].mpu_region_name = "rtu RW NX device"; + + /* DRAM2. Deliberately mapped so the cache benchmark has slower memory to + work against: this bank runs at half the core frequency, where region 1's + banks run at full speed and caching them shows little. Normal write-back + and never executable. + + Sized 512 KB, which is the whole bank. An earlier revision mapped 2 MB + here; the upper 1.5 MB of that aliased back onto the bank, so anything + placed above 0x31880000 would have silently shared storage with the + bottom of the region -- no fault, just two objects on one address. */ + + mpu_regions[5].mpu_region_base = S32Z_DRAM2_BASE; + /* Stops short of the per-thread windows at the top of the bank. Those are + mapped one at a time by the thread-entry hook; leaving them inside this + region would make them reachable from every thread and the isolation + below would prove nothing. */ + + mpu_regions[5].mpu_region_limit = S32Z_DRAM2_BASE + + S32Z_DRAM2_SHARED_SIZE - 1UL; + mpu_regions[5].mpu_region_ap = MPU_AP_RW_EL1; + mpu_regions[5].mpu_region_execute_never = 1U; + mpu_regions[5].mpu_region_shareability = MPU_SH_NON; + mpu_regions[5].mpu_region_attr_index = MPU_ATTR_NORMAL_WB; + mpu_regions[5].mpu_region_name = "dram2 RW NX normal-wb"; + + /* One region per TCM bank. The TRM is explicit that an enabled TCM still + needs an MPU region before it can be used, and that an enabled TCM always + behaves as Non-cacheable Non-shareable Normal memory whatever attributes + the MPU carries -- so the attribute index below does not describe the TCM, + only the permissions do. Kept as three regions rather than one spanning + 0x30000000 to 0x30203FFF, because a single region would also make the + 1MB gaps between banks accessible, and those are not TCM. + + Left executable on purpose: the point of TCM is deterministic access for + code as much as data, so an image that wants a TCM-resident handler can + have one. That makes these regions writable and executable, which for + tightly-coupled memory is the normal arrangement rather than an + oversight. */ + + mpu_regions[6].mpu_region_base = S32Z_ATCM_BASE; + mpu_regions[6].mpu_region_limit = S32Z_ATCM_BASE + S32Z_ATCM_SIZE - 1UL; + mpu_regions[6].mpu_region_ap = MPU_AP_RW_EL1; + mpu_regions[6].mpu_region_execute_never = 0U; + mpu_regions[6].mpu_region_shareability = MPU_SH_NON; + mpu_regions[6].mpu_region_attr_index = MPU_ATTR_NORMAL_WB; + mpu_regions[6].mpu_region_name = "atcm RW X tcm"; + + /* No regions for BTCM and CTCM: entry.S leaves those banks disabled, and a + region covering a disabled TCM would map its address range to whatever + AXIM provides there, which is not TCM and would read as success. See the + measurement in entry.S. */ + + /* BTCM. Data only, so execute-never, unlike the ATCM region above which + holds code. An enabled TCM behaves as Non-cacheable Non-shareable Normal + memory whatever the MPU says, so the attribute index below describes the + permissions and nothing else -- but a region is still required before the + bank can be touched at all. */ + + mpu_regions[7].mpu_region_base = S32Z_BTCM_BASE; + mpu_regions[7].mpu_region_limit = S32Z_BTCM_BASE + S32Z_BTCM_SIZE - 1UL; + mpu_regions[7].mpu_region_ap = MPU_AP_RW_EL1; + mpu_regions[7].mpu_region_execute_never = 1U; + mpu_regions[7].mpu_region_shareability = MPU_SH_NON; + mpu_regions[7].mpu_region_attr_index = MPU_ATTR_NORMAL_WB; + mpu_regions[7].mpu_region_name = "btcm RW NX tcm"; + + mpu_regions_used = 8U; +} + + +/**************************************************************************/ +/* program_region */ +/**************************************************************************/ + +static void program_region(unsigned int index, const MPU_REGION *region_ptr) +{ + unsigned long prbar; + unsigned long prlar; + + /* PRBAR: BASE[31:6], RES0[5], SH[4:3], AP[2:1], XN[0]. + * + * Per Cortex-R52 TRM r1p3 figure 3-39. Note the field positions: the + * FVP example this file came from shifts every field one bit too far + * left -- SH<<4, AP<<2, XN<<1 -- which silently produces a different + * region than intended. With XN written to bit 1 it lands in the real + * AP[1] (EL0 access) and the region stays EXECUTABLE, which is how this + * board ended up running instructions out of .data. + * + * That bug is also the origin of the "PRBAR.AP bit order is reversed" + * claim recorded during the FVP work. Calibrating AP empirically under + * the wrong shift puts the intended AP's low bit into the real AP[2] + * (the read-only bit) and the intended XN into the real AP[1], which + * looks exactly like a reversed encoding. The architectural order is + * correct as published; there was no reversal. + */ + + prbar = (region_ptr->mpu_region_base & 0xFFFFFFC0UL) + | (((unsigned long) region_ptr->mpu_region_shareability & 0x3UL) << 3) + | (((unsigned long) region_ptr->mpu_region_ap & 0x3UL) << 1) + | ((unsigned long) region_ptr->mpu_region_execute_never & 0x1UL); + + prlar = (region_ptr->mpu_region_limit & 0xFFFFFFC0UL) + | (((unsigned long) region_ptr->mpu_region_attr_index & 0x7UL) << 1) + | 1UL; /* region enable */ + + write_prselr((unsigned long) index); + instruction_barrier(); + write_prbar(prbar); + write_prlar(prlar); +} + + +/**************************************************************************/ +/* mpu_module_load_window_open / mpu_module_load_window_close */ +/* */ +/* A window over the module area, for the manager to load through. */ +/* */ +/* No kernel region covers the module area, which is what stops every */ +/* thread from reaching a module's memory -- but the manager has to read */ +/* the preamble and write the module's data to load it at all. Without */ +/* this the load faulted on its first read of the image, and because a */ +/* privileged data abort ends in a handler that only spins, that looked */ +/* exactly like the load hanging. */ +/* */ +/* Opened around the load and closed straight after, rather than left in */ +/* place, because PMSAv8-R has no region priority: if this region were */ +/* still enabled when a module thread ran it would overlap the module's */ +/* own regions, and an access hitting both takes a translation fault */ +/* (TRM 8.1). */ +/* Closing it before any module thread starts is what keeps the two from */ +/* ever being enabled together. */ +/* */ +/* Region 16, above both the kernel's 0-7 and the eight the manager */ +/* hands to a module, so neither the scheduler's per-thread region load */ +/* nor the boot table can disturb it. MPUIR reports 20 EL1 regions on */ +/* this part. EL1 read/write with no EL0 access: the manager can load */ +/* through it and a module cannot use it to reach anything. */ +/**************************************************************************/ + +unsigned long mpu_module_window_prbar; +unsigned long mpu_module_window_prlar; + + +void mpu_module_window_init(void) +{ + MPU_REGION window; + + window.mpu_region_base = S32Z_MODULE_AREA_BASE; + window.mpu_region_limit = S32Z_MODULE_AREA_BASE + + S32Z_MODULE_AREA_SIZE - 1UL; + window.mpu_region_ap = MPU_AP_RW_EL1; + window.mpu_region_execute_never = 1U; + window.mpu_region_shareability = MPU_SH_NON; + window.mpu_region_attr_index = MPU_ATTR_NORMAL_WB; + window.mpu_region_name = "module window RW NX EL1"; + + /* Published for the scheduler, which turns this region on and off on every + dispatch and has no business computing register layouts in assembly. */ + + mpu_module_window_prbar = (window.mpu_region_base & 0xFFFFFFC0UL) + | (((unsigned long) window.mpu_region_shareability & 0x3UL) << 3) + | (((unsigned long) window.mpu_region_ap & 0x3UL) << 1) + | ((unsigned long) window.mpu_region_execute_never & 0x1UL); + + mpu_module_window_prlar = (window.mpu_region_limit & 0xFFFFFFC0UL) + | (((unsigned long) window.mpu_region_attr_index & 0x7UL) << 1) + | 1UL; + + /* Enabled now, because everything running before the first module thread is + privileged code that may need to reach module memory -- the manager loads + a module from a kernel thread. */ + + program_region(MPU_MODULE_LOAD_REGION, &window); + + data_sync_barrier(); + instruction_barrier(); +} + + +/**************************************************************************/ +/* mpu_init */ +/**************************************************************************/ + +unsigned int mpu_init(void) +{ + unsigned long sctlr; + unsigned int index; + unsigned int available = mpu_region_count(); + + build_table(); + + if (mpu_regions_used > MPU_TABLE_REGIONS) + { + return 0U; /* build_table overran the table */ + } + + if (available < mpu_regions_used) + { + return 0U; /* hardware has fewer regions than we need */ + } + + /* Memory types first: a region's attribute index is meaningless until + MAIR is populated. */ + + MARK(0x20); + write_mair0((MAIR_ATTR_DEVICE << 8) | MAIR_ATTR_NORMAL_WB); + write_mair1(0UL); + MARK(0x21); + + for (index = 0U; index < mpu_regions_used; index++) + { + program_region(index, &mpu_regions[index]); + MARK(0x30U + index); + } + + /* Disable any regions this image does not use, so that stale enables + cannot grant access we did not intend. */ + + for (index = mpu_regions_used; index < available; index++) + { + write_prselr((unsigned long) index); + instruction_barrier(); + write_prlar(0UL); + } + +#ifdef TXM_MODULE_MANAGER + + /* After the loop above, which would otherwise disable it again: the module + window lives above the regions this table uses, so it counts as unused + here. */ + + mpu_module_window_init(); + +#endif + + MARK(0x40); + data_sync_barrier(); + + /* Caches are invalidated before enabling. The instruction cache has a + single invalidate-all; the data cache is left to the model's reset + state, which is invalid, because a set/way sweep belongs with the + silicon bring-up where it can be verified against the real cache + geometry. */ + + invalidate_icache_all(); + data_sync_barrier(); + instruction_barrier(); + + /* MPU only. Caches are deliberately left off on this first pass: the + point here is reaching the GIC, and enabling caches at the same time + would add coherency questions -- the debugger writes this image straight + into SRAM behind the caches' back -- to a change whose result is meant to + be unambiguous. C and I are a separate step with their own check. */ + + MARK(0x41); + sctlr = read_sctlr(); + MARK(0x42); +#if !defined(MPU_READBACK_ONLY) + sctlr |= SCTLR_M; + write_sctlr(sctlr); +#else + (void) sctlr; /* readback experiment: MPU deliberately NOT enabled */ +#endif + + /* If the map is wrong, the core dies HERE -- the next instruction fetch or + the next stack access is the first thing the new map has to satisfy. A + marker written after this point therefore proves the map holds. */ + + MARK(0x43); + data_sync_barrier(); + instruction_barrier(); + MARK(0x44); + + return mpu_regions_used; +} diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/mpu.h b/ports/cortex_r52/gnu/example_build/s32z280_evb/mpu.h new file mode 100644 index 00000000..19d183cc --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/mpu.h @@ -0,0 +1,142 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* mpu.h Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* PMSAv8-R memory protection and cache enable for Cortex-R52. */ +/* */ +/* The region set is described by a table rather than a sequence of */ +/* register writes. That is deliberate and is a roadmap decision, not */ +/* a style preference: AR2 programs a Stage-1 region set per ThreadX */ +/* module and AR3 programs Stage-2 regions per ZoneX partition, and */ +/* both reuse this shape. A table can be swapped at a partition */ +/* switch; open-coded register writes cannot. */ +/* */ +/**************************************************************************/ + +#ifndef MPU_H +#define MPU_H + +/* Access permissions, PRBAR.AP (bits [3:2]). + * + * These values were CALIBRATED AGAINST THE HARDWARE, not copied from a + * reference, because the widely-published Armv8-R AArch64 macro set has the + * two bits the other way round (its "read-only, no EL0 access" is 0x2). + * Programming four disjoint regions, one per encoding, and attempting a + * privileged write to each gave: + * + * AP=0b00 write allowed AP=0b01 write faulted + * AP=0b10 write allowed AP=0b11 write faulted + * + * so the low bit is read-only and the high bit grants EL0 access. Using the + * AArch64 ordering here silently produces writable "read-only" regions: the + * MPU still enforces region coverage, so everything appears to work and only + * an explicit write-permission test exposes it. Re-calibrate on S32Z280 + * silicon before trusting these values there. + */ + +/* PRBAR.AP[2:1] as the architecture defines it: AP[2] selects read-only, + AP[1] grants EL0 access. These are the published values -- the "reversed + bit order" recorded during the FVP work was an artefact of that code + shifting AP into the wrong bits, not a property of the hardware. */ + +#define MPU_AP_RW_EL1 0U /* 0b00 EL1 read/write, no EL0 */ +#define MPU_AP_RW_EL1_EL0 1U /* 0b01 EL1 read/write, EL0 read/write */ +#define MPU_AP_RO_EL1 2U /* 0b10 EL1 read-only, no EL0 */ +#define MPU_AP_RO_EL1_EL0 3U /* 0b11 EL1 read-only, EL0 read-only */ + +/* Shareability, PRBAR.SH. */ + +#define MPU_SH_NON 0U +#define MPU_SH_OUTER 2U +#define MPU_SH_INNER 3U + +/* Attribute indices into MAIR, PRLAR.AttrIndx. */ + +#define MPU_ATTR_NORMAL_WB 0U /* Normal, inner/outer write-back */ +#define MPU_ATTR_DEVICE 1U /* Device-nGnRnE */ + +/* One protection region. The limit is inclusive, matching the hardware. */ + +typedef struct MPU_REGION_STRUCT +{ + unsigned long mpu_region_base; + unsigned long mpu_region_limit; + unsigned char mpu_region_ap; + unsigned char mpu_region_execute_never; + unsigned char mpu_region_shareability; + unsigned char mpu_region_attr_index; + const char *mpu_region_name; +} MPU_REGION; + +/* Number of regions this implementation provides, from MPUIR. */ + +unsigned int mpu_region_count(void); + +/* Program the region table, then enable the MPU and the caches. Returns + the number of regions programmed, or 0 if the table does not fit. */ + +unsigned int mpu_init(void); + +/* True once SCTLR reports the MPU and both caches enabled. */ + +unsigned int mpu_is_enabled(void); +unsigned int mpu_caches_enabled(void); + +/* The active table, for reporting. */ + +const MPU_REGION *mpu_region_table(unsigned int *count_ptr); + +/* Read back what the hardware holds for one region, so that programming can + be verified rather than assumed. */ + +void mpu_read_region(unsigned int index, unsigned long *prbar_ptr, + unsigned long *prlar_ptr); + +/* Region index for the manager's load window over the module area. Above the + kernel's 0-7 and above the eight the manager hands to a module, so neither + the boot table nor the scheduler's per-thread region load can disturb it. */ + +#define MPU_MODULE_LOAD_REGION 16U + +/* The module area window, which is what lets privileged code reach module + memory at all -- no other kernel region covers it. + + PMSAv8-R has no region priority, so this must never be enabled at the same + time as the regions a module is given, which cover the same memory. That is + guaranteed by who owns it rather than by careful calling: the scheduler turns + this region on for every thread that is not a module thread and off for every + thread that is, so the window is enabled exactly when no module regions are + loaded. The two register words are published for it below. + + Enabled here at boot, because everything before the first module thread is + privileged code that may need to reach module memory. */ + +void mpu_module_window_init(void); + +extern unsigned long mpu_module_window_prbar; +extern unsigned long mpu_module_window_prlar; + +#endif /* MPU_H */ diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/platform.h b/ports/cortex_r52/gnu/example_build/s32z280_evb/platform.h new file mode 100644 index 00000000..1440fbd1 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/platform.h @@ -0,0 +1,274 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* platform.h Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Memory map and peripheral addresses for the NXP S32Z280-594EVB, */ +/* RTU0 core 0 (R52_0_0). */ +/* */ +/* Every address below was either read out of the S32Z2 Reference */ +/* Manual (S32Z27RM Rev. 5.1) or confirmed on the board itself, and */ +/* each is marked accordingly. The distinction matters because this */ +/* part differs from the Armv8-R AEM FVP in ways that are silent */ +/* rather than loud: an address that is merely wrong tends to read */ +/* back zero instead of faulting. */ +/* */ +/* MEMORY */ +/* */ +/* The RTU exposes its code SRAM through two windows onto the same */ +/* physical memory: 0x79900000 for instruction fetch (AXI-F) and */ +/* 0x32100000 as data. Confirmed by writing a sentinel to one and */ +/* reading it back through the other. NXP's debugger memory map */ +/* declares the fetch window read-only, but it is writable over the */ +/* debug AXI port -- verified -- so a debugger-loaded image can be */ +/* placed there directly and no load-address alias is needed. A */ +/* self-booting image loaded from flash would still need the data */ +/* alias, since the core's own data side may not write through AXI-F. */ +/* */ +/* The TCMs need care, and the earlier note here was wrong about why. */ +/* Reads of 0x30000000, 0x30100000 and 0x30200000 do fault, but not */ +/* because the TCMs are disabled. Reading the region registers on */ +/* this part gives: */ +/* */ +/* ATCM 0x0000011F 64KB, 1 wait state, ENABLED at EL2 and EL1/0 */ +/* BTCM 0x00000014 16KB, 0 wait states, disabled */ +/* CTCM 0x00000114 16KB, 1 wait state, disabled */ +/* */ +/* Every BASEADDRESS field is zero, so ATCM is live as 64KB at address */ +/* 0x00000000 -- not at 0x30000000. The faulting reads were of an */ +/* address the TCM is not at. ATCM's enables reset set because */ +/* CFGTCMBOOTx is tied high on this part (Cortex-R52 TRM r1p3 3.3.94). */ +/* The sizes and wait states match the S32Z2 reference manual exactly. */ +/* */ +/* ECC is implemented and enabled: IMP_MEMPROTCTLR reads 0x00000011, */ +/* so RAMPROTIMP and RAMPROTEN are both set. TRM 6.2.2 therefore */ +/* applies -- a TCM location must be written before it is read, and */ +/* ATCM needs 64-bit aligned stores where BTCM and CTCM accept 32-bit. */ +/* */ +/* The DDR window at 0x7A000000 is dark until DDR is initialised. */ +/* */ +/**************************************************************************/ + +#ifndef PLATFORM_H +#define PLATFORM_H + +/* TCM bases. The values the S32Z2 reference manual documents, which the core + does not use until they are programmed -- ATCM boots at 0x00000000 and the + other two boot disabled with an UNKNOWN base. Each must be size-aligned + (Cortex-R52 TRM r1p3 6.2), which these are: 64KB, 16KB and 16KB banks at + 64KB, 1MB and 1MB alignment respectively. Programming ATCM here also moves + it off address 0, so a null-pointer write faults instead of quietly landing + in tightly-coupled memory. */ + +#define S32Z_ATCM_BASE 0x30000000UL +#define S32Z_ATCM_SIZE 0x00010000UL /* 64 KB, 1 wait state */ +#define S32Z_BTCM_BASE 0x30100000UL +#define S32Z_BTCM_SIZE 0x00004000UL /* 16 KB, 0 wait states */ +#define S32Z_CTCM_BASE 0x30200000UL +#define S32Z_CTCM_SIZE 0x00004000UL /* 16 KB, 1 wait state */ + +/* Memory-mapped register access. Kept here rather than per-driver so that + every peripheral in this BSP goes through one definition. + MISRA C:2012 Rule 11.4/11.6 deviation: casting an integer address to a + volatile pointer is inherent to memory-mapped device access. */ + +#define REG32(address) (*(volatile unsigned long *)(unsigned long)(address)) + +/* Code SRAM, instruction-fetch view. 7 MB. This is where the core */ +/* resets to, and where .text is linked and loaded. [verified on board] */ + +#define S32Z_CODE_SRAM_BASE 0x79900000UL +#define S32Z_CODE_SRAM_SIZE 0x00700000UL + +/* The same physical SRAM seen as data. Kept here because the boot-from- */ +/* flash path will need it, and because it is how the core's reset */ +/* instruction is planted by an external debugger. [verified on board] */ + +#define S32Z_CODE_SRAM_DATA_ALIAS 0x32100000UL + +/* RTU data SRAM. The RTU has 1 MB of it, in three contiguous banks that */ +/* are all RTU-local -- when an RTU accesses its own blocks the traffic */ +/* stays inside the RTU. The banks differ in speed, not in locality: */ +/* */ +/* DRAM0 0x31780000 256 KB full core speed */ +/* DRAM1 0x317C0000 256 KB full core speed */ +/* DRAM2 0x31800000 512 KB half core speed */ +/* */ +/* S32Z2 Reference Manual Rev. 5, section 6.3.6 and Table 13. Confirmed */ +/* on the board: writing distinct values to all three banks and reading */ +/* them back shows 1 MB of independent storage, and the first address */ +/* past DRAM2 aliases back onto its base, which is what fixes its size at */ +/* 512 KB rather than the 2 MB an earlier revision of this file claimed. */ +/* */ +/* Note that NXP's own debugger memory map, s32z2e2_memory_regions.py in */ +/* S32 Design Studio, describes this last bank as 2 MB. The Reference */ +/* Manual and the silicon agree it is 512 KB; the script is wrong here. */ + +#define S32Z_DRAM0_BASE 0x31780000UL +#define S32Z_DRAM0_SIZE 0x00040000UL +#define S32Z_DRAM1_BASE 0x317C0000UL +#define S32Z_DRAM1_SIZE 0x00040000UL +/* Memory for loadable modules: the top 64 KB of DRAM1, deliberately left out of + the broad data region in mpu.c. + + It has to be outside every region the board support package programs. The + manager gives a module its own regions, and if the kernel's map already covered + that memory then every thread could reach the module and back -- the isolation + would be nominal. Carving a hole is the only way to make it real. + + DRAM1 rather than DRAM2 because it runs at full core speed (S32Z2 RM 6.3.6), + and because code and data can be contiguous here. Putting module code in the + code RAM region instead would have been the obvious choice and does not work: + that region is read-only, so a module's data would have to live somewhere else + and the module would straddle two carve-outs. + + 64 KB is arbitrary but not accidental: it leaves 448 KB of full-speed RAM for + the kernel's data, stacks and bss, which currently use about 14 KB. */ + +#define S32Z_MODULE_AREA_BASE 0x317F0000UL +#define S32Z_MODULE_AREA_SIZE 0x00010000UL /* 64 KB */ + +/* The shared granules the sample module reports its progress through, at the + base of the module area. + + A module cannot print -- the console belongs to the board support package and + lies outside every region a module owns -- so the manager grants it shared + regions and reads them back. On this board a GDB harness reads the module's + own progress variable as well, out of the data area the manager allocated; + these granules are the channel that needs no debugger, and having both means + the two agree or the run says so. + + Six granules rather than one, because a module may be granted + TXM_MODULE_MPU_SHARED_ENTRIES regions and one grant only ever exercises the + first of them. Five are granted, one granule per entry, and + S32Z_MODULE_STATUS_UNGRANTED -- index 2, so a granted granule sits on either + side of it -- is never granted to anything. A limit register masked the wrong + way, or a base off by a granule, extends a region into that gap from one side + or the other, and a module that can write it was given more than was asked + for. + + S32Z_MODULE_STATUS_SIZE is the size of ONE granule, which is also the length + of each individual grant; the area is GRANULES of them. Fixed addresses on + both sides, checked at run time against the linker's symbol rather than + trusted. */ + +#define S32Z_MODULE_STATUS_BASE 0x317F0000UL +#define S32Z_MODULE_STATUS_SIZE 0x40UL /* one MPU granule */ +#define S32Z_MODULE_STATUS_GRANULES 6UL /* five granted, one not */ +#define S32Z_MODULE_STATUS_UNGRANTED 2UL /* the one never granted */ + +/* What is left of the full-speed pair for the kernel itself. link.lds sizes its + DATA region from this, so the linker cannot place kernel data in the module + area by accident. */ + +#define S32Z_DATA_SRAM_SHARED_SIZE (S32Z_MODULE_AREA_BASE - S32Z_DRAM0_BASE) + +/* The top 8 KB of DRAM2 is deliberately left out of the broad data region in + mpu.c and mapped one window at a time, per thread, instead. Isolation is only + meaningful in memory that no other region already grants access to, and every + other region in that map is a wide RW window -- a private buffer placed inside + one of them would be reachable by every thread no matter what else was + programmed. */ + +#define S32Z_THREAD_WINDOW_BASE 0x3187E000UL +#define S32Z_THREAD_WINDOW_SIZE 0x00001000UL /* 4 KB per thread */ +#define S32Z_THREAD_WINDOW_COUNT 2UL +#define S32Z_DRAM2_SHARED_SIZE (S32Z_THREAD_WINDOW_BASE - S32Z_DRAM2_BASE) + +#define S32Z_DRAM2_BASE 0x31800000UL +#define S32Z_DRAM2_SIZE 0x00080000UL + +/* The full-speed pair, contiguous, and the right home for stacks and */ +/* .data. This is what link.lds calls DATA. */ + +#define S32Z_DATA_SRAM_BASE S32Z_DRAM0_BASE +#define S32Z_DATA_SRAM_SIZE (S32Z_DRAM0_SIZE + S32Z_DRAM1_SIZE) + +/* Console. The EVB user guide (UG10268) dedicates LIN9 to the */ +/* daughtercard USB-UART, selected by jumper J248, so LINFlex_9 is the */ +/* instance that reaches the host -- not LINFlex_0, which is the first */ +/* one in the Reference Manual's list and the tempting wrong choice. */ +/* Base addresses from the RM; LINFlex_0's LINCR1 reset value 0x00000082 */ +/* was read back from the board, which is what confirms the address space.*/ + +#define S32Z_LINFLEX_9_BASE 0x42980000UL /* USB-UART [RM] */ +#define S32Z_LINFLEX_0_BASE 0x40170000UL /* [RM+board] */ + +/* Mode Entry and Reset Generation, used by the debugger's init sequence */ +/* to release and clock the RTU cores. Recorded because their contents */ +/* were what proved the core was released rather than held in reset. [RM]*/ + +#define S32Z_MC_ME_PRTN0_PCONF 0x41900300UL +#define S32Z_MC_ME_PRTN0_CORE0_ADDR 0x4190034CUL +#define S32Z_RGM_PRST0 0x41850048UL + +/* Debug system as seen from a core: MDM-AP holds the per-core external */ +/* debug request bits that must be clear for the core to execute at all. */ +/* Reachable from the core context only. [RM + verified on board] */ + +/* RTU0 general-purpose registers. CFG_CNTDV holds the divider that makes*/ +/* the Cortex-R52 generic timer clock-enable, CNTCLKEN, from a cluster */ +/* clock. Reset value of the CNTDV field is 4. [RM section 9.2.1.6] */ + +/* RTU peripheral window, as described by IMP_PERIPHPREGIONR: 4 MB at */ +/* 0x76000000, reached through the low-latency peripheral port. */ + +#define S32Z_RTU_PERIPH_BASE 0x76000000UL +#define S32Z_RTU_PERIPH_SIZE 0x00400000UL + +#define S32Z_RTU0_GPR_BASE 0x76120000UL +#define S32Z_RTU0_GPR_CFG_CNTDV (S32Z_RTU0_GPR_BASE + 0x10UL) + +/* GICv3 for this R52 cluster. IMP_CBAR reports the distributor base in */ +/* bits [31:21] and reads 0x47800000 on this board, confirming NXP's map */ +/* from the hardware -- the SoC reference manual gives no GIC address at */ +/* all, and its note that the "R52 Cluster set addr [31:21]" is just */ +/* CFGPERIPHBASE[31:21] restated. Frame layout from Cortex-R52 TRM Table */ +/* 9-1: distributor at +0x000000, redistributor control at +0x100000, */ +/* redistributor SGI/PPI at +0x110000, then +0x20000 per further core. */ +/* The region must be mapped Device nGnRnE before it can be read; with the*/ +/* MPU disabled an access stalls the core. */ + +#define S32Z_GIC_BASE 0x47800000UL +#define S32Z_GIC_SIZE 0x00200000UL + +/* Frames within that window, from Cortex-R52 TRM Table 9-1: distributor at + +0x000000, redistributor control at +0x100000, redistributor SGI/PPI at + +0x110000, then +0x20000 per further core. Both frames verified on the + board -- GICD_PIDR2 and GICR_PIDR2 both read 0x3B. + + These must be mapped Device nGnRnE before use. With the region Normal, or + with the MPU disabled, an access stalls the core outright: no abort, no + fault handler, and the debug connection drops. mpu.c maps them. */ + +#define GICD_BASE (S32Z_GIC_BASE + 0x000000UL) +#define GICR_RD_BASE (S32Z_GIC_BASE + 0x100000UL) +#define GICR_SGI_BASE (S32Z_GIC_BASE + 0x110000UL) + +#define S32Z_MDM_AP_BASE 0x4DC11000UL +#define S32Z_MDM_AP_CONTROL2 (S32Z_MDM_AP_BASE + 0x44UL) +#define S32Z_MDM_AP_RTU0_CORE0_EDBGREQ 0x00010000UL + +#endif /* PLATFORM_H */ diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/readme_s32z280.txt b/ports/cortex_r52/gnu/example_build/s32z280_evb/readme_s32z280.txt new file mode 100644 index 00000000..30aef5dc --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/readme_s32z280.txt @@ -0,0 +1,178 @@ +Cortex-R52 example build for the NXP S32Z280-594EVB +=================================================== + +RTU0 core 0 (R52_0_0) on the S32Z280-594EVB daughtercard, reached through an +S32 Debug Probe. This is silicon bring-up: the FVP example under +example_build/fvp_baser_aemv8r validates the architecture, and this one +validates the implementation. MIDR here is 0x411FD133 (Cortex-R52 r1p3); +the FVP reports 0x410FD0F0, part 0xD0F, which is an architecture envelope +model and not an R52 at all. + + +1. Build + + cmake -B build_s32z -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE=cmake/cortex_r52.cmake \ + -DTX_R52_BUILD_S32Z280_EXAMPLE=ON . + ninja -C build_s32z s32z280_boot.elf + + The option is separate from TX_R52_BUILD_FVP_EXAMPLE so that FVP + regression stays green regardless of what bring-up is doing, and so the + two boards' differing reset state and memory maps cannot interact. + + +2. Run + + S32Z280_ELF=build_s32z/ports/cortex_r52/gnu/example_build/s32z280_evb/s32z280_boot.elf \ + arm-none-eabi-gdb-py -batch -x tools/read_identity.gdb + + The host side is not optional and not obvious; see setup.md section 9. In + short: a CCS must be listening on 41475 and it must be the *Windows* CCS, + because its code pushes to the probe complete while the Linux CCS's stall + after "Sending code ... done". Under WSL2 mirrored networking both share + one port namespace, so a stray Linux CCS silently blocks the Windows one. + GTA runs on 45000, and arm-none-eabi-gdb-py needs a source-built Python + 3.10.11 in PYTHONHOME. + + +3. Three silicon facts this build exists to encode + + a. The core resets in THUMB state. CPSR reads 0x1FA out of reset -- Hyp + mode with T set -- because the RTU boot instruction NXP plants at the + boot address is a T32 branch. _start is therefore T32 and switches to + A32 itself. An A32 entry would execute the first halfword of its own + instruction as Thumb and end up anywhere. + + b. The debugger holds every core in debug state. NXP's + _reset_to_first_instruction() asserts MDM_AP CONTROL2[19:16] = + CR52_RTU0_{3,2,1,0}_EDBGREQ and nothing ever clears them, despite the + comment in NXP's own source claiming start_debug_by_core_name() does. + While EDBGREQ is asserted the core cannot execute at all, yet registers + and memory still respond and MC_ME/RGM report the core released and + clocked -- so it presents as working hardware that ignores every + attempt to run. tools/read_identity.gdb clears core 0's bit and + verifies the clear took. + + c. CNTFRQ reads zero, the same as on the FVP. It is writable only at the + highest implemented exception level, so it must be programmed at EL2. + entry.S deliberately records it rather than writing a value, because + the correct frequency for this board is not yet established and a wrong + one would silently mis-scale every interval derived from it. Any timer + work here must program CNTFRQ first. + + +4. Memory map + + Code SRAM is visible through two windows onto the same physical memory: + 0x79900000 for instruction fetch, 0x32100000 as data. The core resets to + 0x79900000 -- MC_ME_PRTN0_CORE0_ADDR reads exactly that -- so .text is + linked and loaded there. That window is writable over the debug AXI port + even though NXP's debugger memory map declares it read-only, so a + debugger-loaded image needs no alias. A flash-booted image would. + + Data, bss and the per-mode stacks live in RTU-local data SRAM at + 0x31780000 (256 KB); 0x31800000 gives 2 MB if an image outgrows it. + + Reads of 0x30000000/0x30100000/0x30200000 do fault, but the earlier note + here had the reason wrong. Reading the region registers on this part shows + ATCM already ENABLED at both exception levels, 64 KB with one wait state, + and every BASEADDRESS field zero -- so ATCM is live at address 0x00000000, + not at 0x30000000. The faulting reads were of an address the TCM is not + at. ATCM's enables reset set because CFGTCMBOOTx is tied high here. BTCM + (16 KB, 0 wait states) and CTCM (16 KB, 1 wait state) really are disabled. + Sizes and wait states agree with the S32Z2 reference manual exactly. + + ECC is implemented and enabled -- IMP_MEMPROTCTLR reads 0x11 -- so per + Cortex-R52 TRM 6.2.2 a TCM location must be written before it is read, and + ATCM needs 64-bit aligned stores where BTCM and CTCM accept 32-bit. Using + TCM therefore takes more than setting an enable bit. + + entry.S now programs ATCM to 0x30000000 and enables it at both exception + levels, at EL2 -- writing ENABLEEL2 from EL1 is silently ignored, measured on + two banks. The boot image preloads it (ECC check bits are not initialised by + the core) and proves it holds data both before and after the MPU is enabled. + + BTCM and CTCM are left disabled because nothing in this example uses them. + + An earlier version of this file said something much stronger and it was + wrong. It claimed that enabling any second bank removes all measurable + data-cache benefit on this part, and gave five configurations as evidence: + ATCM alone at a 24% cache gain, and four combinations involving a second bank + at none. It concluded the cause was not a particular bank, not its address + and not the ECC preload, but enabling a second bank at all. A defect report + went to NXP on that basis and has since been withdrawn. + + The benchmark that produced those numbers was bimodal with respect to where + its timing loop fell inside a 64-byte cache line. The same workload on the + same silicon reported either 24% or nothing at all depending on that + placement, and each of the five configurations was an edit to entry.S, so + each shifted the code that followed and moved the loop between the two + modes. Adding two nop instructions to entry.S reproduces the "second bank" + figure exactly. + + Re-measured with a benchmark that sweeps four loop alignments and reports the + distribution, one bank and two are indistinguishable: + + loop offset in line ATCM only ATCM + CTCM + 0 gain 0 gain 0 + 16 gain 0 gain 0 + 32 gain 240/1000 gain 240/1000 + 48 gain 240/1000 gain 240/1000 + + Enabling a second bank costs nothing measurable. It is one line in entry.S, + and the prerequisites are the ECC preload before any read (TRM 6.2.2) and an + MPU region, both already handled for ATCM. + + The wider lesson is worth more than the TCM detail: a single-figure timing + result from this example cannot be compared across builds unless the timed + loop's alignment is controlled, because almost any change shifts code. The + cache and interrupt-handler measurements here now sweep alignments and report + the spread for that reason. + + The DDR window at 0x7A000000 is dark until DDR is initialised. TCM is still + absent from link.lds: nothing is placed there yet. + + +5. What this image reports + + boot_stage says how far the reset path got, so a fault is distinguishable + from a hang; if a fault handler ran it records the vector, syndrome and + faulting address before parking. r52_identity.magic is written last, so a + half-filled structure cannot be mistaken for a complete one. bsp_done() is + a deliberate breakpoint target, so "the image finished" is observed rather + than inferred from a timeout. + + Verified on the board: + + MIDR 0x411FD133 Cortex-R52 r1p3 + MPUIR 0x00001400 20 MPU regions at EL1 + HMPUIR 0x00000014 EL2; the count field sits in bits [7:0] here + rather than [15:8] as in MPUIR, which still + needs confirming against the Armv8-R spec + CTR 0x8144C004 + MPIDR 0x80000000 + ID_PFR0 0x00000131 + ID_PFR1 0x10111001 virtualisation field 1: EL2 implemented + SCTLR 0x70C50838 MPU, D-cache and I-cache all off at reset + CNTFRQ 0x00000000 + + CPSR at EL2 0x000001DA mode 0x1A, Hyp + CPSR at EL1 0x600001D3 mode 0x13, Supervisor + + The two CPSR values are the point of the milestone: they are the EL2 to EL1 + drop performed by this code on silicon rather than on a model. + + +6. Not here yet + + No console. LIN9 reaches the host through the daughtercard USB-UART + (jumper J248), which is LINFlex_9 at 0x42980000 -- not LINFlex_0 at + 0x40170000, the tempting wrong choice as the first instance in the + Reference Manual's list. What is missing is the clock configuration needed + to compute a baud rate; a console at the wrong rate produces garbage + indistinguishable from a crash, which is why this milestone reports through + memory instead. + + No timer, no GIC, no MPU programming and no ThreadX: this image does not + link the kernel, so a failure here is unambiguously a boot or board problem + rather than a kernel one. diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.c b/ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.c new file mode 100644 index 00000000..081dc414 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.c @@ -0,0 +1,304 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* tcm.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frederic Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Reads what the TCM hardware reports about itself. See tcm.h for */ +/* the register layouts and where they come from; nothing here is */ +/* inferred from a neighbouring register's shape. */ +/* */ +/* Reading only. Programming a base address and setting the enables */ +/* comes after these values have been seen and agree with both the */ +/* Cortex-R52 TRM and the S32Z2 reference manual, because a disagreement*/ +/* would mean one of the two documents does not describe this part and */ +/* guessing which would be the whole PRBAR mistake again. */ +/* */ +/* MISRA C:2012 deviations (justified) */ +/* */ +/* Dir 4.3 -- assembly encapsulated one operation per function. */ +/* */ +/**************************************************************************/ + +#include "tcm.h" + +#define TCM_SIZE_SHIFT 2U +#define TCM_SIZE_MASK 0x1FUL +#define TCM_WAITSTATES_SHIFT 8U +#define TCM_ENABLE_EL2 (1UL << 1) +#define TCM_ENABLE_EL10 (1UL << 0) +#define TCM_BASE_SHIFT 13U /* TRM table 3-136: [31:13] */ + +#define MEMPROT_RAMPROTIMP (1UL << 4) +#define MEMPROT_RAMPROTEN (1UL << 0) + + +static unsigned long read_atcmregionr(void) +{ + unsigned long value; + __asm volatile ("mrc p15, 0, %0, c9, c1, 0" : "=r" (value)); + return value; +} + +static unsigned long read_btcmregionr(void) +{ + unsigned long value; + __asm volatile ("mrc p15, 0, %0, c9, c1, 1" : "=r" (value)); + return value; +} + +static unsigned long read_ctcmregionr(void) +{ + unsigned long value; + __asm volatile ("mrc p15, 0, %0, c9, c1, 2" : "=r" (value)); + return value; +} + + +/* SIZE is an encoding, not a shift count: 4 means 8KB and each step doubles, + so 8KB << (field - 4). Anything outside the documented range yields 0 + rather than a plausible-looking wrong number. */ + +static unsigned long decode_size(unsigned int field) +{ + unsigned long bytes = 0UL; + + if ((field >= 4U) && (field <= 11U)) + { + bytes = 0x2000UL << (field - 4U); + } + + return bytes; +} + + +void tcm_read_bank(unsigned int index, tcm_bank_t *bank_ptr) +{ + unsigned long raw; + + if (bank_ptr == 0) + { + return; + } + + switch (index) + { + case 0U: raw = read_atcmregionr(); break; + case 1U: raw = read_btcmregionr(); break; + case 2U: raw = read_ctcmregionr(); break; + default: raw = 0UL; break; + } + + bank_ptr->raw = raw; + bank_ptr->base = raw & ~((1UL << TCM_BASE_SHIFT) - 1UL); + bank_ptr->size_field = (unsigned int) ((raw >> TCM_SIZE_SHIFT) & TCM_SIZE_MASK); + bank_ptr->size_bytes = decode_size(bank_ptr->size_field); + bank_ptr->wait_states = (unsigned int) ((raw >> TCM_WAITSTATES_SHIFT) & 1UL); + bank_ptr->enable_el2 = ((raw & TCM_ENABLE_EL2) != 0UL) ? 1U : 0U; + bank_ptr->enable_el10 = ((raw & TCM_ENABLE_EL10) != 0UL) ? 1U : 0U; +} + + +unsigned long tcm_read_memprotctlr(void) +{ + unsigned long value; + + /* Note opc1 is 1 here, not 0 as for the region registers. */ + + __asm volatile ("mrc p15, 1, %0, c9, c1, 2" : "=r" (value)); + return value; +} + + +unsigned int tcm_ecc_implemented(void) +{ + return ((tcm_read_memprotctlr() & MEMPROT_RAMPROTIMP) != 0UL) ? 1U : 0U; +} + + +unsigned int tcm_ecc_enabled(void) +{ + unsigned long ctlr = tcm_read_memprotctlr(); + + /* RAMPROTEN is ignored when RAMPROTIMP is 0: the TRM says the design + behaves as if it were 0, so report it that way rather than echoing a + bit that has no effect. */ + + if ((ctlr & MEMPROT_RAMPROTIMP) == 0UL) + { + return 0U; + } + + return ((ctlr & MEMPROT_RAMPROTEN) != 0UL) ? 1U : 0U; +} + + +static void write_regionr(unsigned int index, unsigned long value) +{ + switch (index) + { + case 0U: + __asm volatile ("mcr p15, 0, %0, c9, c1, 0" :: "r" (value) : "memory"); + break; + case 1U: + __asm volatile ("mcr p15, 0, %0, c9, c1, 1" :: "r" (value) : "memory"); + break; + case 2U: + __asm volatile ("mcr p15, 0, %0, c9, c1, 2" :: "r" (value) : "memory"); + break; + default: + return; + } + + __asm volatile ("isb sy" ::: "memory"); +} + + +unsigned int tcm_enable(unsigned int index, unsigned long base) +{ + tcm_bank_t bank; + unsigned long value; + + if (index > 2U) + { + return 0U; + } + + tcm_read_bank(index, &bank); + + if (bank.size_bytes == 0UL) + { + return 0U; /* not implemented; nothing to enable */ + } + + /* Size-aligned, per TRM 6.2. Rejecting a misaligned base is better than + programming it and discovering later that the bank answers somewhere + unexpected. */ + + if ((base & (bank.size_bytes - 1UL)) != 0UL) + { + return 0U; + } + + /* Preserve the read-only SIZE and WAITSTATES fields as read: they describe + the hardware and writing a different value there would be meaningless. */ + + value = (base & ~((1UL << TCM_BASE_SHIFT) - 1UL)) + | (bank.raw & ((TCM_SIZE_MASK << TCM_SIZE_SHIFT) | (1UL << TCM_WAITSTATES_SHIFT))) + | TCM_ENABLE_EL2 + | TCM_ENABLE_EL10; + + write_regionr(index, value); + + /* Read back rather than trusting the write. */ + + tcm_read_bank(index, &bank); + + /* ENABLEEL10 is the criterion, not both enables. Writing ENABLEEL2 from + EL1 is ignored -- measured on BTCM and CTCM, where the base and bit 0 took + while bit 1 stayed clear -- so requiring it here would report failure for + a bank that is perfectly usable at the level this code runs at. EL2 + access is arranged in entry.S, before the drop. */ + + return ((bank.base == (base & ~((1UL << TCM_BASE_SHIFT) - 1UL))) && + (bank.enable_el10 != 0U)) ? 1U : 0U; +} + + +void tcm_preload(unsigned int index, unsigned long base, unsigned long bytes) +{ + if (index == 0U) + { + /* ATCM: 64-bit stores at 64-bit alignment. TRM 6.2.2 is explicit that + STR of a word is not sufficient here, so this writes unsigned long + long and the generated code is checked to be STRD or STM rather than + two word stores. */ + + volatile unsigned long long *p = (volatile unsigned long long *) base; + unsigned long count = bytes / 8UL; + unsigned long i; + + for (i = 0UL; i < count; i++) + { + p[i] = 0ULL; + } + } + else + { + /* BTCM and CTCM: 32-bit stores at 32-bit alignment are enough. */ + + volatile unsigned int *p = (volatile unsigned int *) base; + unsigned long count = bytes / 4UL; + unsigned long i; + + for (i = 0UL; i < count; i++) + { + p[i] = 0U; + } + } + + __asm volatile ("dsb sy" ::: "memory"); +} + + +/**************************************************************************/ +/* Copy .atcm_text into ATCM. */ +/**************************************************************************/ + +extern char __atcm_text_start__; +extern char __atcm_text_end__; +extern char __atcm_text_load__; + +unsigned long tcm_copy_atcm_text(void) +{ + /* The linker aligns both ends of the section to 8, so the whole extent is a + whole number of 64-bit units and there is no tail to handle narrowly. + That matters here rather than being tidiness: a 32-bit store to ATCM does + not establish the ECC check bits for its location, so a tail copied 32 + bits at a time would leave words that fault when read. */ + + volatile unsigned long long *dst; + const volatile unsigned long long *src; + unsigned long bytes; + unsigned long i; + + dst = (volatile unsigned long long *) (void *) &__atcm_text_start__; + src = (const volatile unsigned long long *) (void *) &__atcm_text_load__; + bytes = (unsigned long) (&__atcm_text_end__ - &__atcm_text_start__); + + for (i = 0UL; i < (bytes / 8UL); i++) + { + dst[i] = src[i]; + } + + /* Complete the writes, then discard any stale instruction-side copy of the + destination before it is executed. */ + + __asm__ volatile("dsb sy" ::: "memory"); + __asm__ volatile("mcr p15, 0, r0, c7, c5, 0" : : : "memory"); /* ICIALLU */ + __asm__ volatile("dsb sy" ::: "memory"); + __asm__ volatile("isb" ::: "memory"); + + return bytes; +} diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.h b/ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.h new file mode 100644 index 00000000..b80f226d --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.h @@ -0,0 +1,161 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* tcm.h Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frederic Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Tightly-coupled memory on the S32Z280. This header is the reading */ +/* half: what the hardware reports about itself. Enabling comes next */ +/* and depends on what these reads say. */ +/* */ +/* WHERE THE FACTS COME FROM */ +/* */ +/* Cortex-R52 TRM r1p3 (100026_0103_00_en), section 3.3.94, table */ +/* 3-136. IMP_ATCMREGIONR, IMP_BTCMREGIONR and IMP_CTCMREGIONR are */ +/* at MRC/MCR p15, 0, <Rt>, c9, c1, {0,1,2} and are laid out: */ +/* */ +/* [31:13] BASEADDRESS bits [31:13] of the TCM base address */ +/* [12:9] RES0 */ +/* [8] WAITSTATES wait states for TCM accesses */ +/* [7] RES0 */ +/* [6:2] SIZE size indicator, read-only in effect */ +/* [1] ENABLEEL2 enable at EL2 */ +/* [0] ENABLEEL10 enable at EL1 and EL0 */ +/* */ +/* ⚠ BASEADDRESS is [31:13], not [31:12]. IMP_PERIPHPREGIONR uses */ +/* [31:12] and inferring the same here would be wrong by one bit -- */ +/* the same class of mistake that made PRBAR silently drop XN. The */ +/* base is therefore 8KB-aligned, not 4KB. */ +/* */ +/* SIZE encodings: 0 none, 4 8KB, 5 16KB, 6 32KB, 7 64KB, 8 128KB, */ +/* 9 256KB, 10 512KB, 11 1MB. */ +/* */ +/* "At reset all bits are 0 apart from SIZE and WAITSTATES", unless */ +/* CFGTCMBOOTx is high, which resets the ATCM enables to 1. So a */ +/* part that has not been told otherwise has every TCM based at 0 with */ +/* both enables clear, which is exactly why reads of the documented */ +/* TCM windows fault on this board. */ +/* */ +/* IMP_MEMPROTCTLR is at p15, 1, <Rt>, c9, c1, 2 (TRM 3.3.76, table */ +/* 3-114): RAMPROTIMP [4] says whether RAM protection exists at all, */ +/* RAMPROTEN [0] whether it is on, and RAMPROTEN is ignored when */ +/* RAMPROTIMP is 0. */ +/* */ +/* S32Z2 reference manual: TCMA 64KB with 1 wait state, TCMB 16KB with */ +/* 0, TCMC 16KB with 1, per core. Those give expected SIZE values of */ +/* 7, 5 and 5, which is a cross-check between the two documents and */ +/* the silicon rather than a restatement of either. */ +/* */ +/* WHY ENABLING IS NOT ENOUGH */ +/* */ +/* TRM section 6.2.2: the ECC bits in TCM RAM are not initialised by */ +/* the processor, and a location must be written before it is read */ +/* with ECC enabled. An uninitialised read reports an error, which */ +/* looks exactly like "the TCM is not accessible" and would send the */ +/* reader back to these region registers, which were already right. */ +/* */ +/* The preload widths are not uniform: BTCM and CTCM take STR, STRD */ +/* or STM at 32-bit alignment, but ATCM needs STRD or STM at 64-bit */ +/* alignment. A C loop storing 32-bit words would leave ATCM's check */ +/* bits invalid. */ +/* */ +/**************************************************************************/ + +#ifndef TCM_H +#define TCM_H + +/* One bank's reported configuration, decoded from its region register. */ + +typedef struct +{ + unsigned long raw; /* register as read */ + unsigned long base; /* BASEADDRESS << 13 */ + unsigned int size_field; /* SIZE, as encoded */ + unsigned long size_bytes; /* decoded, 0 if the encoding is unknown */ + unsigned int wait_states; + unsigned int enable_el2; + unsigned int enable_el10; +} tcm_bank_t; + +/* Read and decode one bank. Index 0 is ATCM, 1 BTCM, 2 CTCM; any other + index yields a zeroed structure. */ + +void tcm_read_bank(unsigned int index, tcm_bank_t *bank_ptr); + +/* IMP_MEMPROTCTLR, raw. */ + +unsigned long tcm_read_memprotctlr(void); + +/* Whether RAM protection is implemented, and whether it is enabled. Both are + decoded from IMP_MEMPROTCTLR, and the second is meaningless when the first + is zero -- the TRM says the design behaves as if RAMPROTEN were 0. */ + +unsigned int tcm_ecc_implemented(void); +unsigned int tcm_ecc_enabled(void); + +/**************************************************************************/ +/* ENABLING */ +/* */ +/* Constraints, all from Cortex-R52 TRM r1p3 section 6.2 and 3.3.94: */ +/* */ +/* - The base address must be SIZE-ALIGNED, not merely 8KB-aligned as */ +/* the BASEADDRESS field's width alone would suggest. */ +/* - A disabled TCM's base address is UNKNOWN, not zero, so every bank */ +/* must be programmed explicitly rather than adjusted from what it */ +/* appears to hold. */ +/* - "Before using the TCM you must program MPU regions to cover the */ +/* TCM regions to give access." An enabled TCM with no MPU region */ +/* still faults once the MPU is on. */ +/* - An enabled TCM always behaves as Non-cacheable Non-shareable */ +/* Normal memory whatever the MPU says; the MPU supplies only the */ +/* permissions. So the MPU region's attribute index is irrelevant */ +/* here and its AP and XN bits are not. */ +/* - With ECC on, every location must be written before it is read. */ +/* */ +/**************************************************************************/ + +/* Program one bank's base and enable it at both exception levels. Returns 1 on + success, 0 if the index is out of range, the bank reports no size, or the base + is not size-aligned. Verifies by reading the register back. */ + +unsigned int tcm_enable(unsigned int index, unsigned long base); + +/* Write every location of a bank so its ECC check bits become valid. The store + width is chosen from the bank: index 0 is ATCM and needs 64-bit stores, and + the others take 32-bit. Must be called while the bank is enabled and + accessible, and before anything reads it. */ + +void tcm_preload(unsigned int index, unsigned long base, unsigned long bytes); + +/* Copy the .atcm_text section from its load address in CODE to its run address + in ATCM. Must run before anything in that section is called, and after the + bank is enabled. Returns the number of bytes copied, which is zero when the + section is empty -- the normal case for an image that places nothing there. + + Uses 64-bit stores because ECC is enabled on this part and ATCM requires + them; see tcm_preload and Cortex-R52 TRM 6.2.2. */ + +unsigned long tcm_copy_atcm_text(void); + +#endif diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/thread_mpu.c b/ports/cortex_r52/gnu/example_build/s32z280_evb/thread_mpu.c new file mode 100644 index 00000000..5681754f --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/thread_mpu.c @@ -0,0 +1,237 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* thread_mpu.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Per-thread memory protection. See thread_mpu.h for what this is */ +/* and is not. */ +/* */ +/* MISRA C:2012 deviations (justified) */ +/* */ +/* Directive 4.3 -- the MPU is only reachable through CP15; every */ +/* access is encapsulated in a one-line accessor below. */ +/* */ +/**************************************************************************/ + +#include "tx_api.h" +#include "platform.h" +#include "mpu.h" +#include "timer.h" +#include "thread_mpu.h" + +/* The region reprogrammed on every thread entry. mpu.c programs 0 to 7, and + MPUIR on this part reports 20, so 8 is free. Chosen high on purpose: the + shared regions keep their indices, so a thread that reaches this hook with no + window still has code, stacks, console and GIC exactly as before. */ + +#define THREAD_MPU_REGION 8UL + +typedef struct +{ + TX_THREAD *owner; + unsigned long base; +} thread_window_t; + +static thread_window_t windows[S32Z_THREAD_WINDOW_COUNT]; + +static unsigned long switch_count; +static unsigned int last_cycles; +static unsigned int min_cycles = 0xFFFFFFFFU; +static unsigned int max_cycles; + + +/**************************************************************************/ +/* CP15 accessors. */ +/**************************************************************************/ + +/* Direct per-region access rather than PRSELR. + + The Cortex-R52 TRM 8.4 encodes the region number into CRm and opc2: CRm is + 0b1rrr where rrr is region_number[3:1], with opc2 0 and 1 for an even region + and 4 and 5 for an odd one. Regions 0 through 15 take CP15 op1 0 and regions + 16 through 24 take op1 1. PRBAR and PRLAR without a number are the indirect + view selected by PRSELR. + + Region 8 is therefore op1 0, CRm c12, opc2 0 and 1. Using it removes the + PRSELR write and, more importantly, the ISB that has to follow PRSELR before + the region registers can be written. + + Measured on this part: 542 to 604 cycles through PRSELR against 434 to 470 + direct, for the same region and the same isolation result. The saving matters + most where several regions are programmed at once, which is what a module + switch does -- there the PRSELR route pays an ISB per region while the direct + route pays one barrier pair for the whole block. + + The TRM disagrees with itself about the range, and the next reader should not + have to rediscover that. The register descriptions at 3.3.85 and 3.3.86 say + "Direct access is provided to PRBAR0 to PRBAR15" and list only the op1 0 + form; 8.4 and Table 8-9 list both forms. Table 8-9 also prints opc2 0 for + the even limit registers where the prose of 8.4 says 1. What is written here + uses op1 0 only, which is the half of the manual this part has been measured + against. */ + +/* Kept for regions 16 and above. Those have direct op1 1 encodings in TRM 8.4, + but only the selector route is proven on this part, and nothing here needs a + region that high while the per-thread window lives at region 8. */ + +__attribute__((unused)) +static void write_prselr(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c6, c2, 1" : : "r"(value) : "memory"); +} + +static void write_prbar8_direct(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c6, c12, 0" : : "r"(value) : "memory"); +} + +static void write_prlar8_direct(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c6, c12, 1" : : "r"(value) : "memory"); +} + +__attribute__((unused)) +static void write_prbar(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c6, c3, 0" : : "r"(value) : "memory"); +} + +__attribute__((unused)) +static void write_prlar(unsigned long value) +{ + __asm__ volatile("mcr p15, 0, %0, c6, c3, 1" : : "r"(value) : "memory"); +} + + +/**************************************************************************/ +/* Registration. */ +/**************************************************************************/ + +unsigned long thread_mpu_window_base(unsigned int window) +{ + if ((unsigned long) window >= S32Z_THREAD_WINDOW_COUNT) + { + return 0UL; + } + + return S32Z_THREAD_WINDOW_BASE + + ((unsigned long) window * S32Z_THREAD_WINDOW_SIZE); +} + +unsigned long thread_mpu_grant(TX_THREAD *thread_ptr, unsigned int window) +{ + unsigned long base = thread_mpu_window_base(window); + + if ((base == 0UL) || (thread_ptr == TX_NULL)) + { + return 0UL; + } + + windows[window].owner = thread_ptr; + windows[window].base = base; + + return base; +} + + +/**************************************************************************/ +/* Activation. */ +/* */ +/* Called explicitly by a thread rather than from the scheduler, and that */ +/* is a deliberate limitation of this step rather than the intended */ +/* design. */ +/* */ +/* The port's scheduler does call _tx_execution_thread_enter under */ +/* TX_ENABLE_EXECUTION_CHANGE_NOTIFY, which would make this automatic on */ +/* every switch. That macro is read by the port assembly compiled into */ +/* the shared threadx library, so enabling it would oblige all nine */ +/* example targets in this port -- including the FVP ones -- to supply */ +/* the four execution hooks. A ThreadX module port carries its own */ +/* copies of the port assembly for exactly this reason, and that is where */ +/* the switch belongs. */ +/* */ +/* What this step establishes without that machinery: that a PMSAv8-R */ +/* region can be reprogrammed per thread on this part, what it costs, and */ +/* that a violation faults. Those are the questions worth answering */ +/* before writing a module manager on top of them. */ +/**************************************************************************/ + +void thread_mpu_activate(TX_THREAD *thread_ptr) +{ + unsigned int before; + unsigned int after; + unsigned int delta; + unsigned int i; + unsigned long base = 0UL; + + before = timer_read_cycles(); + + for (i = 0U; (unsigned long) i < S32Z_THREAD_WINDOW_COUNT; i++) + { + if (windows[i].owner == thread_ptr) + { + base = windows[i].base; + break; + } + } + + if (base == 0UL) + { + /* No window for this thread: disable the region rather than leaving the + previous thread's window in place. Forgetting this is how per-thread + protection silently becomes no protection -- the last thread to run + would leave its window open to whatever ran next. */ + + write_prlar8_direct(0UL); + } + else + { + write_prbar8_direct((base & ~0x3FUL) + | ((unsigned long) MPU_SH_NON << 3) + | ((unsigned long) MPU_AP_RW_EL1 << 1) + | 1UL); /* XN: data only */ + + write_prlar8_direct(((base + S32Z_THREAD_WINDOW_SIZE - 1UL) & ~0x3FUL) + | ((unsigned long) MPU_ATTR_NORMAL_WB << 1) + | 1UL); /* EN */ + } + + __asm__ volatile("dsb sy" ::: "memory"); + __asm__ volatile("isb" ::: "memory"); + + after = timer_read_cycles(); + delta = after - before; + + switch_count++; + last_cycles = delta; + if (delta < min_cycles) { min_cycles = delta; } + if (delta > max_cycles) { max_cycles = delta; } +} + + +unsigned long thread_mpu_switch_count(void) { return switch_count; } +unsigned int thread_mpu_last_cycles(void) { return last_cycles; } +unsigned int thread_mpu_min_cycles(void) { return min_cycles; } +unsigned int thread_mpu_max_cycles(void) { return max_cycles; } diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/thread_mpu.h b/ports/cortex_r52/gnu/example_build/s32z280_evb/thread_mpu.h new file mode 100644 index 00000000..cba6226e --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/thread_mpu.h @@ -0,0 +1,79 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* thread_mpu.h Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Per-thread memory protection for the NXP S32Z280-594EVB. */ +/* */ +/* One MPU region is reprogrammed on every thread entry to grant the */ +/* incoming thread access to its own window and nothing else. A thread */ +/* with no window registered runs with that region disabled, so it */ +/* reaches none of them. */ +/* */ +/* This is a step towards a ThreadX module port for this core, not a */ +/* substitute for one. It demonstrates that PMSAv8-R regions can be */ +/* switched per thread on this part, at a measured cost, and that a */ +/* violation faults -- which are the questions worth answering before */ +/* building a module manager on top of them. There is no user mode, no */ +/* syscall boundary and no loader here. */ +/* */ +/* The hook is _tx_execution_thread_enter, which the port's scheduler */ +/* already calls under TX_ENABLE_EXECUTION_CHANGE_NOTIFY, after the */ +/* stack pointer has been switched. No port assembly is modified. */ +/* */ +/**************************************************************************/ + +#ifndef THREAD_MPU_H +#define THREAD_MPU_H + +#include "tx_api.h" + +/* Apply a thread's window, or disable the region if it has none. Called by the + thread itself in this step; see the comment in thread_mpu.c for why it is not + yet driven from the scheduler. */ + +void thread_mpu_activate(TX_THREAD *thread_ptr); + +/* Give a thread exclusive access to one of the per-thread windows. The index + selects the window, 0 to S32Z_THREAD_WINDOW_COUNT - 1. Returns the window's + base address, or 0 if the index is out of range or the table is full. */ + +unsigned long thread_mpu_grant(TX_THREAD *thread_ptr, unsigned int window); + +/* Base address of a window, whether or not it is granted to anyone. Lets a + test reach for a window it does not own. */ + +unsigned long thread_mpu_window_base(unsigned int window); + +/* How many thread entries have reprogrammed the region, and what the last one + cost in cycles. The cost is the price of per-thread protection on this core + and is the reason the hook records it. */ + +unsigned long thread_mpu_switch_count(void); +unsigned int thread_mpu_last_cycles(void); +unsigned int thread_mpu_min_cycles(void); +unsigned int thread_mpu_max_cycles(void); + +#endif diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/timer.c b/ports/cortex_r52/gnu/example_build/s32z280_evb/timer.c new file mode 100644 index 00000000..9fc5c0a1 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/timer.c @@ -0,0 +1,186 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* timer.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Arm generic timer access for the S32Z280-594EVB. */ +/* */ +/* CNTFRQ reads zero on this silicon, exactly as it does on the */ +/* Armv8-R AEM FVP. CNTFRQ is only a software-populated constant -- */ +/* nothing derives the counter's real rate from it -- so a zero there */ +/* says nothing about whether the counter is running. The only way to */ +/* find that out is to read CNTPCT twice and see whether it moved, */ +/* which is what this file exists to support. */ +/* */ +/* The rate itself is measured rather than computed. The RTU divides */ +/* a cluster clock by RTU.GPR CFG_CNTDV to generate the timer's */ +/* clock-enable, so deriving it needs the divider AND the cluster clock*/ +/* AND the PLL configuration feeding it -- three places to be wrong. */ +/* Sampling CNTPCT against host wall-clock time needs none of them and */ +/* is checkable against the clock tree afterwards. */ +/* */ +/* MISRA C:2012 deviations (justified) */ +/* */ +/* Dir 4.3 -- assembly language shall be encapsulated and isolated. */ +/* Each coprocessor access is wrapped in its own function below. */ +/* */ +/**************************************************************************/ + +#include "timer.h" + +unsigned long long timer_read_cntpct(void) +{ + unsigned int low; + unsigned int high; + + /* CNTPCT is 64-bit and read as a pair through MRRC. The read is + atomic in the architecture, so no high/low re-read dance is needed. */ + + __asm volatile ("mrrc p15, 0, %0, %1, c14" : "=r" (low), "=r" (high)); + + return ((unsigned long long)high << 32) | (unsigned long long)low; +} + + +unsigned int timer_read_cntfrq(void) +{ + unsigned int value; + + __asm volatile ("mrc p15, 0, %0, c14, c0, 0" : "=r" (value)); + + return value; +} + + +void timer_spin(unsigned int iterations) +{ + unsigned int i; + + for (i = 0U; i < iterations; i++) + { + /* Kept from being optimised away, and kept from being turned into + something clever: the loop's only job is to burn a repeatable + amount of time between two counter samples. */ + + __asm volatile ("nop" ::: "memory"); + } +} + + +/* CNTP_CTL bits. IMASK is set so the compare raises no interrupt: there is + no GIC configured yet, and an unhandled PPI would be taken as an exception + rather than politely ignored. */ + +#define CNTP_CTL_ENABLE (1U << 0) +#define CNTP_CTL_IMASK (1U << 1) +#define CNTP_CTL_ISTATUS (1U << 2) + + +void timer_start_oneshot_irq(unsigned int ticks) +{ + /* Same as timer_start_oneshot but with IMASK CLEAR, so the compare raises + an interrupt instead of only setting ISTATUS. Keeping the two arming + functions separate rather than adding a flag: the polled path must never + accidentally unmask before the GIC is up, and an interrupt-driven path + that silently leaves IMASK set produces a timer that fires internally + while no interrupt is ever delivered -- IRQ count 0 with no spurious and + no unexpected INTID, which is exactly how that mistake presents. */ + + unsigned int control = CNTP_CTL_ENABLE; + + __asm volatile ("mcr p15, 0, %0, c14, c2, 0" :: "r" (ticks)); + __asm volatile ("mcr p15, 0, %0, c14, c2, 1" :: "r" (control)); + __asm volatile ("isb"); +} + + +void timer_start_oneshot(unsigned int ticks) +{ + unsigned int control = CNTP_CTL_ENABLE | CNTP_CTL_IMASK; + + /* TVAL is a down-counter loaded relative to now, which avoids the 64-bit + read-modify-write that setting CVAL absolutely would need. */ + + __asm volatile ("mcr p15, 0, %0, c14, c2, 0" :: "r" (ticks)); + __asm volatile ("mcr p15, 0, %0, c14, c2, 1" :: "r" (control)); + __asm volatile ("isb"); +} + + +unsigned int timer_fired(void) +{ + unsigned int control; + + __asm volatile ("mrc p15, 0, %0, c14, c2, 1" : "=r" (control)); + + return ((control & CNTP_CTL_ISTATUS) != 0U) ? 1U : 0U; +} + + +void timer_stop(void) +{ + unsigned int control = 0U; + + __asm volatile ("mcr p15, 0, %0, c14, c2, 1" :: "r" (control)); + __asm volatile ("isb"); +} + + +/**************************************************************************/ +/* PMU cycle counter. */ +/* */ +/* PMCR.E gates all counters including the dedicated cycle counter, and */ +/* PMCNTENSET bit 31 enables that counter specifically. Both are needed. */ +/* PMCR.D is left clear so the counter advances every cycle rather than */ +/* every 64th, which is the resolution these measurements want. */ +/**************************************************************************/ + +#define PMCR_E (1UL << 0) /* enable all counters */ +#define PMCR_P (1UL << 1) /* reset event counters */ +#define PMCR_C (1UL << 2) /* reset cycle counter */ +#define PMCNTENSET_C (1UL << 31) /* cycle counter enable */ + +void timer_cycles_enable(void) +{ + unsigned long pmcr; + + /* Allow EL0/EL1 access and enable, resetting the counter as we go. */ + + __asm__ volatile("mrc p15, 0, %0, c9, c12, 0" : "=r"(pmcr)); + pmcr |= (PMCR_E | PMCR_C); + __asm__ volatile("mcr p15, 0, %0, c9, c12, 0" : : "r"(pmcr) : "memory"); + + __asm__ volatile("mcr p15, 0, %0, c9, c12, 1" + : : "r"((unsigned long) PMCNTENSET_C) : "memory"); + + __asm__ volatile("isb" ::: "memory"); +} + +unsigned int timer_read_cycles(void) +{ + unsigned long value; + __asm__ volatile("mrc p15, 0, %0, c9, c13, 0" : "=r"(value)); + return (unsigned int) value; +} diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/timer.h b/ports/cortex_r52/gnu/example_build/s32z280_evb/timer.h new file mode 100644 index 00000000..0a0866b8 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/timer.h @@ -0,0 +1,72 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* timer.h Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Arm generic timer access for the S32Z280-594EVB. */ +/* */ +/**************************************************************************/ + +#ifndef TIMER_H +#define TIMER_H + +/* The physical count, CNTPCT. 64-bit, read as a pair. Reading it is the + only way to find out whether the system counter is running at all: CNTFRQ + reads zero on this part and so says nothing about the counter itself. */ + +unsigned long long timer_read_cntpct(void); + +/* CNTFRQ as currently programmed. Zero out of reset on this silicon. */ + +unsigned int timer_read_cntfrq(void); + +/* Crude calibrated-by-nothing delay, used only to separate counter samples + far enough apart to measure a rate. Not a time delay: the core clock is + not yet established, which is precisely what the measurement is for. */ + +void timer_spin(unsigned int iterations); + +/* One-shot compare on the EL1 physical timer, interrupt masked. Polled + deliberately: this proves the timer counts and fires before the GIC is + configured, so a later interrupt failure cannot be confused with the timer + itself being wrong. */ + +void timer_start_oneshot(unsigned int ticks); + +/* Arms with the interrupt UNMASKED, for the interrupt-driven path. */ + +void timer_start_oneshot_irq(unsigned int ticks); +unsigned int timer_fired(void); +void timer_stop(void); + +/* PMU cycle counter. CNTPCT runs at 8 MHz on this part, so its 125 ns + granularity is too coarse to resolve interrupt latency, let alone the jitter + in it. The cycle counter runs at the core clock and is what those + measurements use. timer_cycles_enable must be called before any read. */ + +void timer_cycles_enable(void); +unsigned int timer_read_cycles(void); + +#endif /* TIMER_H */ diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/tools/read_identity.gdb b/ports/cortex_r52/gnu/example_build/s32z280_evb/tools/read_identity.gdb new file mode 100644 index 00000000..1bb0890e --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/tools/read_identity.gdb @@ -0,0 +1,160 @@ +# Copyright (c) 2026 Eclipse ThreadX contributors +# SPDX-License-Identifier: MIT +# Portions of this file were generated with AI assistance. +# +# Load s32z280_boot.elf onto the S32Z280-594EVB, run it, and report what the +# core said about itself. +# +# Usage: +# S32Z280_ELF=<path to s32z280_boot.elf> \ +# arm-none-eabi-gdb-py -batch -x tools/read_identity.gdb +# +# Requires, and does not do for you: +# * a CCS listening on 41475 -- use the *Windows* CCS ("ccs.exe -noportquit"), +# whose code pushes to the probe complete; the Linux CCS's push stalls. +# Under WSL2 mirrored networking both share one port namespace, so kill any +# Linux CCS first ("pkill -x ccs", not -f: it runs as ./ccs). +# * GTA on 45000: Server/gta/gta -p 45000 -k +# * PYTHONHOME pointing at a source-built Python 3.10.11, which is what +# arm-none-eabi-gdb-py's embedded interpreter needs. +# +# Two silicon quirks are handled below rather than assumed away: the debugger +# holds every RTU core in debug state through MDM_AP CONTROL2, and the core +# resets in Thumb state. + +py _PROBE_IP = "s32dbg:192.168.50.238" +py _SOC_NAME = "S32Z280" +py _CORE_NAME = "R52_0_0" +py _GDB_SERVER_PORT = 45000 + +source /home/fdesbiens/NXP/S32DS.3.6.10/S32DS/tools/S32Debugger/Debugger/scripts/s32z2e2/s32z2e2_generic_bareboard_all_cores.py + +py board_init() +py s32z2e2_cores.init(_CORE_NAME) +py s32z2e2_cores.start_debug_by_core_name(_CORE_NAME) + +# Select the core context before asking about core state. NXP's core_init() +# omits this and then stop_core() reads the mode of the SoC context, which has +# none, and reports "Core can't be stopped. State is undefined/unknown". +py gta_lib.set_context(context.CORE_CTX[_SOC_NAME + "_" + _CORE_NAME]).unwrap() +py gta_lib.stop_core().unwrap() +py gta_lib.attach().unwrap() + +python +import gdb, os + +# --------------------------------------------------------------------------- +# Release the core from the debugger's external debug request. +# +# _reset_to_first_instruction() asserts MDM_AP CONTROL2[19:16] = +# CR52_RTU0_{3,2,1,0}_EDBGREQ (0x000F0000) and nothing clears them, so every +# RTU0 core is pinned in debug state and executes nothing -- registers and +# memory still respond, which is what makes it look like working hardware. +# --------------------------------------------------------------------------- +CONTROL2 = 0x4DC11044 +EDBGREQ_CORE0 = 0x00010000 + +def rd(addr): + return int(gdb.parse_and_eval("*(unsigned int *)0x%x" % addr)) & 0xFFFFFFFF + +c2 = rd(CONTROL2) +gdb.execute("set *(unsigned int *)0x%x = 0x%x" % (CONTROL2, c2 & ~EDBGREQ_CORE0)) +if rd(CONTROL2) & EDBGREQ_CORE0: + raise gdb.GdbError("CR52_RTU0_0_EDBGREQ is still set; the core will not run") +print("EDBGREQ cleared: CONTROL2 0x%08X -> 0x%08X" % (c2, rd(CONTROL2))) + +elf = os.environ.get("S32Z280_ELF", "s32z280_boot.elf") +gdb.execute("file %s" % elf) +gdb.execute("load") + +# The core resets in Thumb state and CPSR cannot be written through this +# connection, so entry must be the T32 _start. Mask bit 0: GDB reports a Thumb +# function's address with it set, but PC itself must be even. +start = int(gdb.parse_and_eval("(unsigned int)&_start")) & ~1 +gdb.execute("set $pc = 0x%x" % start) +print("PC set to 0x%08X" % start) + +gdb.execute("hbreak bsp_done") +gdb.execute("continue") +end + +echo \n===== boot progress =====\n +python +import gdb + +def rd(addr): + return int(gdb.parse_and_eval("*(unsigned int *)0x%x" % addr)) & 0xFFFFFFFF + +STAGES = {1: "switched out of Thumb", 2: "EL2 configured", 3: "running at EL1", + 4: "entered bsp_main", 5: "bsp_main returned (unexpected)", + 0xFF: "A FAULT HANDLER RAN"} + +stage = int(gdb.parse_and_eval("*(unsigned int *)&boot_stage")) & 0xFFFFFFFF +print(" boot_stage = %d (%s)" % (stage, STAGES.get(stage, "unknown"))) + +ident = None + +if stage == 0xFF: + print(" fault_vector = 0x%02X" % (int(gdb.parse_and_eval("*(unsigned int *)&fault_vector")) & 0xFFFFFFFF)) + print(" fault_syndrome = 0x%08X" % (int(gdb.parse_and_eval("*(unsigned int *)&fault_syndrome")) & 0xFFFFFFFF)) + print(" fault_address = 0x%08X" % (int(gdb.parse_and_eval("*(unsigned int *)&fault_address")) & 0xFFFFFFFF)) +else: + # r52_identity belongs to the boot image, which is what this script is for. + # The kernel demo shares the same breakpoint and progress marker but carries + # no identity structure, so against that image the lookup raised and gdb + # exited 1 -- a failure report for a run that had already passed, which is + # worse than printing nothing. + try: + ident = gdb.parse_and_eval("r52_identity") + except gdb.error: + print("\n This image has no r52_identity structure, so there is no " + "identity report to print.") + print(" Expected for the kernel demo, which reports its own result " + "over the console.") + +if ident is not None: + def f(name): + return int(ident[name]) & 0xFFFFFFFF + + magic = f("magic") + if magic != 0x52520001: + print(" magic = 0x%08X -- structure INCOMPLETE, values below are not " + "trustworthy" % magic) + else: + print(" magic = 0x%08X (complete)" % magic) + + print("\n===== S32Z280 R52_0_0, reported by the core itself =====") + midr = f("midr") + part = (midr >> 4) & 0xFFF + print(" MIDR = 0x%08X implementer 0x%02X variant 0x%X part 0x%03X rev 0x%X" + % (midr, (midr >> 24) & 0xFF, (midr >> 20) & 0xF, part, midr & 0xF)) + print(" part 0x%03X -> %s" % (part, + {0xD13: "Cortex-R52", 0xD16: "Cortex-R52+"}.get(part, "UNRECOGNISED"))) + print(" MPUIR = 0x%08X EL1 MPU regions = %d" + % (f("mpuir"), f("mpu_regions_el1"))) + print(" HMPUIR = 0x%08X (EL2; count field position still to be confirmed)" + % f("hmpuir_el2")) + print(" CTR = 0x%08X" % f("ctr")) + print(" MPIDR = 0x%08X" % f("mpidr")) + print(" ID_PFR0 = 0x%08X" % f("id_pfr0")) + pfr1 = f("id_pfr1") + print(" ID_PFR1 = 0x%08X virtualisation field 0x%X -> EL2 %s" + % (pfr1, (pfr1 >> 12) & 0xF, + "implemented" if ((pfr1 >> 12) & 0xF) != 0 else "ABSENT")) + sctlr = f("sctlr_el1") + print(" SCTLR = 0x%08X M(MPU)=%d C(dcache)=%d I(icache)=%d" + % (sctlr, sctlr & 1, (sctlr >> 2) & 1, (sctlr >> 12) & 1)) + print(" CNTFRQ = 0x%08X (%s)" % (f("cntfrq_el2"), + "zero as on the FVP; a tick interval derived from it would divide by zero" + if f("cntfrq_el2") == 0 else "non-zero, programmed before we got here")) + + print("\n===== exception levels traversed =====") + for label, field, want, wname in (("at EL2 ", "cpsr_el2", 0x1A, "Hyp"), + ("at EL1 ", "cpsr_el1", 0x13, "Supervisor")): + v = f(field) + mode = v & 0x1F + print(" CPSR %s = 0x%08X mode 0x%02X (%s) %s" + % (label, v, mode, + {0x1A: "Hyp", 0x13: "Supervisor", 0x1F: "System"}.get(mode, "?"), + "as expected" if mode == want else "EXPECTED %s (0x%02X)" % (wname, want))) +end diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/tx_initialize_low_level.S b/ports/cortex_r52/gnu/example_build/s32z280_evb/tx_initialize_low_level.S new file mode 100644 index 00000000..097eeafe --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/tx_initialize_low_level.S @@ -0,0 +1,110 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/**************************************************************************/ +/** */ +/** ThreadX Component */ +/** */ +/** Initialize */ +/** */ +/**************************************************************************/ +/**************************************************************************/ + + /* No .arch directive. GNU as accepts "armv8-r"; LLVM's integrated + assembler accepts no spelling of it at all -- not armv8-r, not armv8r -- + and fails with "Unknown Arch". The architecture comes from + -mcpu=cortex-r52 on the command line, which every toolchain file passes, + so the directive only restated it. */ + + .syntax unified + .arm + + .global _tx_thread_system_stack_ptr + .global _tx_initialize_unused_memory + + .text + .balign 4 + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _tx_initialize_low_level Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Low-level initialization for Cortex-R52 on the S32Z280-594EVB. */ +/* Called by _tx_initialize_kernel_enter with the processor already in */ +/* Supervisor mode at EL1. */ +/* */ +/* This is deliberately shorter than the A-profile reference ports: */ +/* entry.S has already given every AArch32 mode its own stack from */ +/* dedicated linker-script regions, so there is no stack carving to do */ +/* here and no run-time stack-overlap check to perform -- the linker */ +/* guarantees the regions do not overlap. */ +/* */ +/* When built with TX_R52_USE_THREADX_IRQ, board_init() brings up the */ +/* MPU, GICv3 and the generic timer tick here. The MPU comes first on */ +/* this board: the GIC region has to be mapped Device nGnRnE before any*/ +/* GIC register can be touched at all. */ +/* and the generic timer tick here. That is safe this early because */ +/* interrupts stay masked until _tx_thread_schedule enables them, so no*/ +/* tick can be delivered before the kernel is ready to take one. */ +/* Without that symbol (the M2 cooperative demo) no tick is created, */ +/* which keeps M2 free of any interrupt dependency. */ +/* */ +/* CALLED BY */ +/* */ +/* _tx_initialize_kernel_enter ThreadX entry function */ +/* */ +/**************************************************************************/ + + .global _tx_initialize_low_level + .type _tx_initialize_low_level, %function +_tx_initialize_low_level: + + /* Record the system stack. The interrupt path uses it when an + exception arrives while no thread is running. */ + + ldr r0, =__sys_stack_top + ldr r1, =_tx_thread_system_stack_ptr + str r0, [r1] + + /* Publish the first free memory address for tx_application_define. + _end sits above the image, .bss and every per-mode stack. Round up + to an 8-byte boundary so pool control blocks are aligned. */ + + ldr r0, =_end + add r0, r0, #7 + bic r0, r0, #7 + ldr r1, =_tx_initialize_unused_memory + str r0, [r1] + +#ifdef TX_R52_USE_THREADX_IRQ + + /* Bring up the interrupt controller and the periodic tick. */ + + push {lr} + bl board_init + pop {lr} + +#endif + + bx lr diff --git a/ports/cortex_r52/gnu/inc/tx_port.h b/ports/cortex_r52/gnu/inc/tx_port.h new file mode 100644 index 00000000..afc67c11 --- /dev/null +++ b/ports/cortex_r52/gnu/inc/tx_port.h @@ -0,0 +1,393 @@ +/*************************************************************************** + * Copyright (c) 2024 Microsoft Corporation + * 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. + * + * SPDX-License-Identifier: MIT + **************************************************************************/ +// Portions of this file were generated with AI assistance. + + +/**************************************************************************/ +/**************************************************************************/ +/** */ +/** ThreadX Component */ +/** */ +/** Port Specific */ +/** */ +/**************************************************************************/ +/**************************************************************************/ + + +/**************************************************************************/ +/* */ +/* PORT SPECIFIC C INFORMATION RELEASE */ +/* */ +/* tx_port.h Cortex-R52/GNU */ +/* 6.5.2 */ +/* */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* Derived from the Cortex-R5/GNU port originally written by */ +/* William E. Lamie, Microsoft Corporation. */ +/* */ +/* DESCRIPTION */ +/* */ +/* This file contains data type definitions that make the ThreadX */ +/* real-time kernel function identically on a variety of different */ +/* processor architectures. For example, the size or number of bits */ +/* in an "int" data type vary between microprocessor architectures and */ +/* even C compilers for the same microprocessor. ThreadX does not */ +/* directly use native C data types. Instead, ThreadX creates its */ +/* own special types that can be mapped to actual data types by this */ +/* file to guarantee consistency in the interface and functionality. */ +/* */ +/**************************************************************************/ + +#ifndef TX_PORT_H +#define TX_PORT_H + + +/* Determine if the optional ThreadX user define file should be used. */ + +#ifdef TX_INCLUDE_USER_DEFINE_FILE + + +/* Yes, include the user defines in tx_user.h. The defines in this file may + alternately be defined on the command line. */ + +#include "tx_user.h" +#endif + + +/* Define compiler library include files. */ + +#include <stdlib.h> +#include <string.h> + + +/* Define ThreadX basic types for this port. */ + +#define VOID void +typedef char CHAR; +typedef unsigned char UCHAR; +typedef int INT; +typedef unsigned int UINT; +typedef long LONG; +typedef unsigned long ULONG; +typedef short SHORT; +typedef unsigned short USHORT; + + +/* Define the priority levels for ThreadX. Legal values range + from 32 to 1024 and MUST be evenly divisible by 32. */ + +#ifndef TX_MAX_PRIORITIES +#define TX_MAX_PRIORITIES 32 +#endif + + +/* Define the minimum stack for a ThreadX thread on this processor. If the size supplied during + thread creation is less than this value, the thread create call will return an error. */ + +#ifndef TX_MINIMUM_STACK +#define TX_MINIMUM_STACK 200 /* Minimum stack size for this port */ +#endif + + +/* Define the system timer thread's default stack size and priority. These are only applicable + if TX_TIMER_PROCESS_IN_ISR is not defined. */ + +#ifndef TX_TIMER_THREAD_STACK_SIZE +#define TX_TIMER_THREAD_STACK_SIZE 1024 /* Default timer thread stack size */ +#endif + +#ifndef TX_TIMER_THREAD_PRIORITY +#define TX_TIMER_THREAD_PRIORITY 0 /* Default timer thread priority */ +#endif + + +/* Define various constants for the ThreadX ARM port. */ + +#ifdef TX_ENABLE_FIQ_SUPPORT +#define TX_INT_DISABLE 0xC0 /* Disable IRQ & FIQ interrupts */ +#else +#define TX_INT_DISABLE 0x80 /* Disable IRQ interrupts */ +#endif +#define TX_INT_ENABLE 0x00 /* Enable IRQ interrupts */ + + +/* Define the clock source for trace event entry time stamp. The following two item are port specific. + For example, if the time source is at the address 0x0a800024 and is 16-bits in size, the clock + source constants would be: + +#define TX_TRACE_TIME_SOURCE *((ULONG *) 0x0a800024) +#define TX_TRACE_TIME_MASK 0x0000FFFFUL + +*/ + +#ifndef TX_TRACE_TIME_SOURCE +#define TX_TRACE_TIME_SOURCE ++_tx_trace_simulated_time +#endif +#ifndef TX_TRACE_TIME_MASK +#define TX_TRACE_TIME_MASK 0xFFFFFFFFUL +#endif + + +/* Define the port specific options for the _tx_build_options variable. This variable indicates + how the ThreadX library was built. */ + +#ifdef TX_ENABLE_FIQ_SUPPORT +#define TX_FIQ_ENABLED 1 +#else +#define TX_FIQ_ENABLED 0 +#endif + +#ifdef TX_ENABLE_IRQ_NESTING +#define TX_IRQ_NESTING_ENABLED 2 +#else +#define TX_IRQ_NESTING_ENABLED 0 +#endif + +#ifdef TX_ENABLE_FIQ_NESTING +#define TX_FIQ_NESTING_ENABLED 4 +#else +#define TX_FIQ_NESTING_ENABLED 0 +#endif + +#define TX_PORT_SPECIFIC_BUILD_OPTIONS TX_FIQ_ENABLED | TX_IRQ_NESTING_ENABLED | TX_FIQ_NESTING_ENABLED + + +/* Define the in-line initialization constant so that modules with in-line + initialization capabilities can prevent their initialization from being + a function call. */ + +#define TX_INLINE_INITIALIZATION + + +/* Determine whether or not stack checking is enabled. By default, ThreadX stack checking is + disabled. When the following is defined, ThreadX thread stack checking is enabled. If stack + checking is enabled (TX_ENABLE_STACK_CHECKING is defined), the TX_DISABLE_STACK_FILLING + define is negated, thereby forcing the stack fill which is necessary for the stack checking + logic. */ + +#ifdef TX_ENABLE_STACK_CHECKING +#undef TX_DISABLE_STACK_FILLING +#endif + + +/* Define the TX_THREAD control block extensions for this port. The main reason + for the multiple macros is so that backward compatibility can be maintained with + existing ThreadX kernel awareness modules. */ + +/* TX_THREAD_EXTENSION_2 carries the per-thread VFP enable flag used by the + lazy floating-point save and restore in tx_thread_schedule.S, + tx_thread_system_return.S and tx_thread_context_restore.S. + + It is defined UNCONDITIONALLY, not under TX_ENABLE_VFP_SUPPORT, and that is + deliberate: the assembly reaches this field through a hard-coded structure + offset, so making the field conditional would move every following member + between build configurations and leave the offset correct in only one of + them. Keeping it always present makes the layout independent of the + floating-point build options. The offset is checked at compile time in + tx_port_offset_check.c, which turns a wrong offset into a build failure + instead of silent corruption of an unrelated thread field. */ + +#define TX_THREAD_EXTENSION_0 +#define TX_THREAD_EXTENSION_1 +#define TX_THREAD_EXTENSION_2 ULONG tx_thread_vfp_enable; +#define TX_THREAD_EXTENSION_3 + + +/* Define the port extensions of the remaining ThreadX objects. */ + +#define TX_BLOCK_POOL_EXTENSION +#define TX_BYTE_POOL_EXTENSION +#define TX_EVENT_FLAGS_GROUP_EXTENSION +#define TX_MUTEX_EXTENSION +#define TX_QUEUE_EXTENSION +#define TX_SEMAPHORE_EXTENSION +#define TX_TIMER_EXTENSION + + +/* Define the user extension field of the thread control block. Nothing + additional is needed for this port so it is defined as white space. */ + +#ifndef TX_THREAD_USER_EXTENSION +#define TX_THREAD_USER_EXTENSION +#endif + + +/* Define the macros for processing extensions in tx_thread_create, tx_thread_delete, + tx_thread_shell_entry, and tx_thread_terminate. */ + + +#define TX_THREAD_CREATE_EXTENSION(thread_ptr) +#define TX_THREAD_DELETE_EXTENSION(thread_ptr) +#define TX_THREAD_COMPLETED_EXTENSION(thread_ptr) +#define TX_THREAD_TERMINATED_EXTENSION(thread_ptr) + + +/* Define the ThreadX object creation extensions for remaining objects. */ + +#define TX_BLOCK_POOL_CREATE_EXTENSION(pool_ptr) +#define TX_BYTE_POOL_CREATE_EXTENSION(pool_ptr) +#define TX_EVENT_FLAGS_GROUP_CREATE_EXTENSION(group_ptr) +#define TX_MUTEX_CREATE_EXTENSION(mutex_ptr) +#define TX_QUEUE_CREATE_EXTENSION(queue_ptr) +#define TX_SEMAPHORE_CREATE_EXTENSION(semaphore_ptr) +#define TX_TIMER_CREATE_EXTENSION(timer_ptr) + + +/* Define the ThreadX object deletion extensions for remaining objects. */ + +#define TX_BLOCK_POOL_DELETE_EXTENSION(pool_ptr) +#define TX_BYTE_POOL_DELETE_EXTENSION(pool_ptr) +#define TX_EVENT_FLAGS_GROUP_DELETE_EXTENSION(group_ptr) +#define TX_MUTEX_DELETE_EXTENSION(mutex_ptr) +#define TX_QUEUE_DELETE_EXTENSION(queue_ptr) +#define TX_SEMAPHORE_DELETE_EXTENSION(semaphore_ptr) +#define TX_TIMER_DELETE_EXTENSION(timer_ptr) + + +/* Determine whether this core has the CLZ instruction and this compiler will + admit to it, and if so replace the portable lowest-set-bit search with it. + + The guard is not upstream's. Upstream asks __TARGET_ARCH_ARM > 4, which is an + Arm Compiler 5 predefine. GCC does not define it -- it predefines the ACLE + macros __ARM_ARCH and __ARM_FEATURE_CLZ instead -- so under GCC the test reads + 0 > 4, this whole block is dropped and tx_thread.h's portable loop runs on a + core that has had the instruction since Armv5. Measured with + arm-none-eabi-gcc 14.3 on 20 Aug 2026: zero CLZ instructions in the built + scheduler objects. + + That was not a dormant path. Half the TX_LOWEST_SET_BIT_CALCULATE call sites + in tx_thread_suspend.c and tx_thread_system_suspend.c sit OUTSIDE the + TX_MAX_PRIORITIES > 32 guards, so the portable loop was running in the + scheduler's priority search in the default 32-priority configuration, which is + the one every R52 build uses. + + __ARM_FEATURE_CLZ is the ACLE answer to the question actually being asked, and + the compiler defines it exactly when the architecture has the instruction, so + a core without CLZ is excluded by construction rather than by an architecture + number. Arm Compiler 5's spelling is kept beside it, now wrapped in defined() + so the test no longer leans on an undefined identifier evaluating to zero -- + which is what -Wundef reports and how this was found. + + The __thumb__ guard stays, and it is load-bearing rather than inherited + caution: __ARM_FEATURE_CLZ describes the ARCHITECTURE, not the instruction + set. Checked on 20 Aug 2026 -- GCC defines it for -mthumb -march=armv5te, + where Thumb-1 has no CLZ at all and this asm would fail to assemble. A Thumb + build therefore keeps the portable loop on purpose. (On this core it is moot: + the R52 toolchain file builds -marm.) + + Two deliberate deviations, per AGENTS.md: + + - Rule 1.2, language extensions. Inline assembly is the entire point of the + macro; there is no conforming way to reach CLZ. Spelled __asm__ and not + asm, because the asm keyword is rejected under -std=c99 -- verified, it is + an "'asm' undeclared" error -- and AGENTS.md requires C99 compatibility. + + - Rule 10.1 / 10.3 on the isolation step, which is why it is respelled. + Upstream isolates the lowest set bit with (ULONG) (-((LONG) m)): that + converts an unsigned map to signed and negates it, which is undefined for + the one input whose top bit is set. (~(m)) + 1 is the same value in + well-defined unsigned arithmetic, and it is character-for-character what + tx_thread.h's portable version uses -- so the two implementations now + visibly compute the same thing instead of merely agreeing. + + Rule 20.7 is a straight fix rather than a deviation: upstream leaves m and b + unparenthesised in the expansion. + + PRECONDITION: m must be non-zero, and the two implementations DISAGREE when it + is not. CLZ(0) is 32, so this yields 31 - 32; the portable loop yields 0. + All twelve call sites in common/src reach the macro only on a map already + tested against zero -- every one checked on 20 Aug 2026 -- so the divergence is + unreachable today. It is written down because a new call site is exactly how + it would stop being unreachable, and demo_clz.c pins both answers so that + changing this has to be a decision. */ + +#if defined(__ARM_FEATURE_CLZ) || (defined(__TARGET_ARCH_ARM) && (__TARGET_ARCH_ARM > 4)) + +#ifndef __thumb__ + +#define TX_LOWEST_SET_BIT_CALCULATE(m, b) \ + (m) = (m) & ((~(m)) + ((ULONG) 1)); \ + __asm__ volatile (" CLZ %0,%1 " : "=r" (b) : "r" (m)); \ + (b) = 31 - (b); + +#endif +#endif + + +/* Define ThreadX interrupt lockout and restore macros for protection on + access of critical kernel information. The restore interrupt macro must + restore the interrupt posture of the running thread prior to the value + present prior to the disable macro. In most cases, the save area macro + is used to define a local function save area for the disable and restore + macros. */ + +/* Per-thread floating-point control. Implemented in tx_thread_schedule.S and + available only when the library is built with TX_ENABLE_VFP_SUPPORT. A + thread's floating-point context is saved and restored lazily: only threads + that have called tx_thread_vfp_enable() pay for it. */ + +#ifdef TX_ENABLE_VFP_SUPPORT +void tx_thread_vfp_enable(void); +void tx_thread_vfp_disable(void); +#endif + + +#ifdef __thumb__ + +unsigned int _tx_thread_interrupt_disable(void); +unsigned int _tx_thread_interrupt_restore(UINT old_posture); + + +#define TX_INTERRUPT_SAVE_AREA UINT interrupt_save; + +#define TX_DISABLE interrupt_save = _tx_thread_interrupt_disable(); +#define TX_RESTORE _tx_thread_interrupt_restore(interrupt_save); + +#else + +#define TX_INTERRUPT_SAVE_AREA UINT interrupt_save; + +#ifdef TX_ENABLE_FIQ_SUPPORT +#define TX_DISABLE asm volatile (" MRS %0,CPSR; CPSID if ": "=r" (interrupt_save) ); +#else +#define TX_DISABLE asm volatile (" MRS %0,CPSR; CPSID i ": "=r" (interrupt_save) ); +#endif + +#define TX_RESTORE asm volatile (" MSR CPSR_c,%0 "::"r" (interrupt_save) ); + +#endif + + +/* Define the interrupt lockout macros for each ThreadX object. */ + +#define TX_BLOCK_POOL_DISABLE TX_DISABLE +#define TX_BYTE_POOL_DISABLE TX_DISABLE +#define TX_EVENT_FLAGS_GROUP_DISABLE TX_DISABLE +#define TX_MUTEX_DISABLE TX_DISABLE +#define TX_QUEUE_DISABLE TX_DISABLE +#define TX_SEMAPHORE_DISABLE TX_DISABLE + + +/* Define the version ID of ThreadX. This may be used by the application.*/ + +#ifdef TX_THREAD_INIT +CHAR _tx_version_id[] = + "(c) 2024 Microsoft Corp. (c) 2026-present Eclipse ThreadX contributors. * ThreadXCortex-R52/GNU Version 6.5.2.202603 *"; +#else +extern CHAR _tx_version_id[]; +#endif + + +#endif + diff --git a/ports/cortex_r52/gnu/readme_threadx.txt b/ports/cortex_r52/gnu/readme_threadx.txt new file mode 100644 index 00000000..73e0dbfe --- /dev/null +++ b/ports/cortex_r52/gnu/readme_threadx.txt @@ -0,0 +1,201 @@ + Eclipse ThreadX for Cortex-R52 + Using GNU Tools + + +1. Building the ThreadX run-time Library + +The port is built with CMake and Ninja. From the repository root: + + cmake -B build_r52 -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE=cmake/cortex_r52.cmake . + ninja -C build_r52 + +This produces libthreadx.a for Cortex-R52 in AArch32 state. The toolchain +file pins the reference cross compiler by absolute path so that the build +does not depend on PATH ordering; override it with + + -DARM_TOOLCHAIN_PATH=<directory containing arm-none-eabi-gcc> + +Note this is the first R-profile port in the tree with a working CMake +build. cmake/cortex_a9.cmake exists but ports/cortex_a9/gnu/CMakeLists.txt +is empty, so no A- or R-profile port could previously be built this way. + + +2. Build options + + -DTX_R52_FLOAT_ABI=soft|hard floating-point ABI, default soft + -DTX_R52_ENABLE_VFP=ON lazy VFP context save and restore + (requires TX_R52_FLOAT_ABI=hard) + -DTX_R52_ENABLE_FIQ=ON FIQ support + -DTX_R52_ENABLE_IRQ_NESTING=ON nested IRQ support + -DTX_R52_ENABLE_FIQ_NESTING=ON nested FIQ support (requires the above) + -DTX_R52_BUILD_FVP_EXAMPLE=ON build the Armv8-R AEM FVP examples + -DTX_R52_ENABLE_MPU=ON PMSAv8-R protection and caches + -DTX_R52_CONSOLE_PL011=ON console on the PL011 UART, not semihosting + +TX_R52_ENABLE_VFP is PUBLIC: it changes which registers the context switch +saves, so the library and the application must agree. It requires +TX_R52_FLOAT_ABI=hard, and configure refuses the combination rather than +warning about it: the flag enables the VFP blocks in the port assembly, and a +soft float ABI leaves the assembler with no FPU to accept them, so the build +fails on the first VMRS rather than quietly skipping the VFP context path. + +Cortex-R52 always implements at least a single-precision FPU. GCC rejects +"-mcpu=cortex-r52+nofp" and offers only "+nofp.dp", so the soft-float +baseline selects the soft ABI rather than removing the FPU. + + +3. Example builds and tests + + cmake -B build_r52 -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE=cmake/cortex_r52.cmake \ + -DTX_R52_BUILD_FVP_EXAMPLE=ON . + ninja -C build_r52 boot_check.elf demo_m2.elf demo_m3.elf \ + demo_threadx.elf demo_mpu.elf demo_clz.elf + ctest --test-dir build_r52 + +The images target the free Armv8-R AEM FVP (FVP_BaseR_AEMv8R): + + boot_check.elf EL2 configuration, the drop to EL1 and the EL2 seam + demo_m2.elf cooperative context switching, no interrupts + demo_m3.elf generic timer tick, GICv3 and preemption + demo_threadx.elf the standard eight-thread demo plus verification + demo_mpu.elf PMSAv8-R protection and cache enable + demo_clz.elf the CLZ lowest-set-bit priority search + demo_m5.elf lazy VFP context save (needs TX_R52_ENABLE_VFP) + +Each image reports its own result and terminates the model through the +semihosting SYS_EXIT call, so no host-side timeout is needed. The test +runner treats a missing result line as failure, so a hang cannot pass. + + +4. System Initialization + +The entry point is _start in the example build's entry.S. Cortex-R52 +always implements EL2 and resets into it, so the reset path configures EL2 +first, installs both the EL2 and EL1 vector tables, and only then drops to +EL1 (Supervisor mode) to run the kernel. Define TX_R52_BOOT_AT_EL1 to skip +the EL2 stage where an earlier boot stage or a vendor EL2 monitor has +already dropped privilege. + +Work that must happen at EL2, because the registers are inaccessible or +read-only later: + + CNTFRQ the model resets it to zero and it is writable + only at the highest implemented exception level + CNTHCTL.PL1PCTEN/PL1PCEN otherwise every EL1 access to CNTPCT or CNTP_* + traps to EL2 + ICC_HSRE.SRE/Enable lets EL1 reach the GICv3 CPU interface through + its system registers + HCR.HCD cleared enables HVC, which the EL2 seam depends on + HCPTR.TCP10/TCP11 cleared lets EL1 use the FPU + +_tx_initialize_low_level records the system stack and publishes the first +free memory address. It is shorter than the A-profile equivalents because +entry.S has already given every AArch32 mode its own stack from dedicated +linker-script regions, so there is no stack carving and no run-time overlap +check to perform. With TX_R52_USE_THREADX_IRQ it also calls board_init to +bring up GICv3 and the periodic tick; that is safe this early because +interrupts stay masked until _tx_thread_schedule enables them. + + +5. Interrupt Handling + +The EL1 IRQ vector branches directly into _tx_thread_context_save, which +returns to __tx_irq_processing_return. A C dispatcher acknowledges the +interrupt, re-arms the timer, calls _tx_timer_interrupt and finishes with +_tx_thread_context_restore. The branch must be a plain B: context save +adjusts lr itself to locate the point of interrupt. + +Exceptions routed to EL2 from EL1 or EL0 all arrive at the Hyp Trap Entry, +vector offset 0x14, not at 0x08. Offsets 0x04 to 0x10 are exceptions taken +from Hyp mode itself, and 0x08 is specifically SVC from Hyp. The handler +therefore decodes HSR.EC and dispatches; it services HVC (EC 0x12) and +reports anything else. That single funnel is the seam that partitioning +work extends. + +Every unhandled vector identifies itself over the console. The FVP exposes +only an Iris server and no GDB stub, which makes a self-identifying fault +the primary debugging tool for this port. + + +6. Floating Point + +Floating-point context is saved lazily: only threads that call +tx_thread_vfp_enable() pay for it. Note that function sets a per-thread +software flag for the context switch; it does not enable the FPU. Enabling +the hardware is the board support package's responsibility -- CPACR grants +CP10/CP11 access and FPEXC.EN enables execution, both of which reset +disabled. The example build does this in entry.S under __ARM_FP. + +The flag lives in TX_THREAD_EXTENSION_2 and is reached from assembly by the +hard-coded offset 144. It is defined unconditionally rather than under +TX_ENABLE_VFP_SUPPORT so that the structure layout does not change with the +floating-point build options. tx_port_offset_check.c asserts that offset, +and the thread stack pointer and run counter offsets, at compile time: a +layout change becomes a build failure instead of silent corruption. + + +Also worth knowing: this port replaces tx_thread.h's portable lowest-set-bit +search with the CLZ instruction, which is what the scheduler uses to pick the +next thread to run. Upstream gates that on __TARGET_ARCH_ARM, an Arm Compiler +5 predefine that GCC does not define, so the optimisation had never once been +compiled in under GCC; the guard here asks __ARM_FEATURE_CLZ instead. It is +worth 40% of the priority search's code size (2188 to 1316 bytes in +tx_thread_system_suspend.o) and it applies in the default 32-priority +configuration, not only above 32. A Thumb build deliberately keeps the +portable loop, because __ARM_FEATURE_CLZ describes the architecture rather +than the instruction set and Thumb-1 has no CLZ. demo_clz.elf is the +regression test, and it fails to build rather than silently testing the +portable loop if the CLZ path is ever disabled again. + + +7. Memory Protection + +PMSAv8-R regions are described by a table rather than a sequence of +register writes, so that a region set can be swapped wholesale. The +example programs three regions -- code read-only and executable, all +writable memory non-executable, peripherals as Device -- and explicitly +disables the regions it does not use. + +Two cautions for anyone reusing this code on silicon: + + - The PRBAR.AP encoding is the standard Armv8-R one, as published: + AP[2] selects read-only and AP[1] grants EL0 access. No calibration is + needed, and an earlier revision of this file said otherwise -- it + claimed the two bits were reversed, on the strength of a real + measurement with a wrong cause. program_region() had been shifting + every PRBAR field one bit too far left, so the AP value's low bit + landed in the true AP[2] and writes faulted exactly when that bit was + set, which looks precisely like a reversed encoding. The shift is + fixed; see the comment on the MPU_AP_* macros in mpu.h for the full + calibration table and what it actually proved. + + The reason it took a silicon run to notice: region coverage is enforced + even when permissions are not what you asked for, so an unmapped + address faults while a "read-only" region quietly accepts writes. A + configuration-only review cannot tell the two apart -- provoke a real + fault instead. Verified on S32Z280 silicon: a write to an RO region + faults and execution from an XN region takes a prefetch abort, the + latter never having worked under the old shift. + + - The code and data regions must not share a 64-byte granule. The + linker script separates them with ".data ALIGN(64) :" on the output + section. An assignment to the location counter between sections does + not advance a MEMORY region's allocation pointer, and an ALIGN at the + end of the preceding section does not extend it when no data follows, + so both of those leave writable data inside the read-only region. + + +8. Validation + +Functional validation is performed on the Armv8-R AEM FVP through the +images listed in section 3. Structural coverage is not claimed from this +port: the platform-agnostic regression suite runs on the host, which is +where the coverage instrumentation and the certification evidence live. + +The FVP is an architecture envelope model, not a Cortex-R52 +implementation -- it reports MIDR 0x410FD0F0, part number 0xD0F. It +validates architecture; implementation details such as MPU region count, +TCM, lockstep and RAS must be validated on silicon. Nothing in this port +is gated on MIDR. diff --git a/ports/cortex_r52/gnu/src/tx_port_offset_check.c b/ports/cortex_r52/gnu/src/tx_port_offset_check.c new file mode 100644 index 00000000..5e853fca --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_port_offset_check.c @@ -0,0 +1,86 @@ +/*************************************************************************** + * 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). + * 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 + **************************************************************************/ + +/**************************************************************************/ +/**************************************************************************/ +/** */ +/** ThreadX Component */ +/** */ +/** Port Specific */ +/** */ +/**************************************************************************/ +/**************************************************************************/ + +/**************************************************************************/ +/* */ +/* PORT SPECIFIC C INFORMATION RELEASE */ +/* */ +/* tx_port_offset_check.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frédéric Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Compile-time verification of the TX_THREAD structure offsets that */ +/* this port's assembly reaches by hard-coded displacement. The file */ +/* emits no code; it exists purely so that a layout change becomes a */ +/* build failure. */ +/* */ +/* Why this is needed: tx_thread_schedule.S, */ +/* tx_thread_system_return.S and tx_thread_context_restore.S read and */ +/* write the per-thread VFP enable flag as [thread, #144]. Nothing in */ +/* the toolchain connects that literal to the C structure, so adding a */ +/* member, enabling an option that adds one, or reordering the port */ +/* extensions would silently retarget those accesses at an unrelated */ +/* field -- corrupting thread state in a way that is extremely hard to */ +/* diagnose from the symptom. The offset was measured at 144 for the */ +/* default build and is asserted here. */ +/* */ +/* A negative array dimension is used rather than _Static_assert */ +/* because this project targets C99, where _Static_assert does not */ +/* exist. If an assertion below fails, the compiler reports a negative*/ +/* or zero-sized array for the named typedef. */ +/* */ +/**************************************************************************/ + +#include "tx_api.h" +#include <stddef.h> + +/* Offset of the VFP enable flag as encoded in this port's assembly. + Overridable only so that the assertion itself can be tested. */ + +#ifndef TX_PORT_VFP_ENABLE_OFFSET +#define TX_PORT_VFP_ENABLE_OFFSET 144 +#endif + +typedef char +tx_port_assert_vfp_enable_offset_is_144[ + (offsetof(TX_THREAD, tx_thread_vfp_enable) == TX_PORT_VFP_ENABLE_OFFSET) + ? 1 : -1]; + +/* The stack pointer is read as [thread, #8] by tx_thread_schedule.S and + written by tx_thread_system_return.S, and the run counter as + [thread, #4]. Assert those too, since they are on the context-switch + critical path. */ + +typedef char +tx_port_assert_stack_ptr_offset_is_8[ + (offsetof(TX_THREAD, tx_thread_stack_ptr) == 8) ? 1 : -1]; + +typedef char +tx_port_assert_run_count_offset_is_4[ + (offsetof(TX_THREAD, tx_thread_run_count) == 4) ? 1 : -1]; diff --git a/ports/cortex_r52/gnu/src/tx_thread_context_restore.S b/ports/cortex_r52/gnu/src/tx_thread_context_restore.S new file mode 100644 index 00000000..5c0e503d --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_context_restore.S @@ -0,0 +1,247 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * 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. +@ * +@ * SPDX-License-Identifier: MIT +@ **************************************************************************/ +@ Portions of this file were generated with AI assistance. +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** ThreadX Component */ +@/** */ +@/** Thread */ +@/** */ +@/**************************************************************************/ +@/**************************************************************************/ +#ifdef TX_INCLUDE_USER_DEFINE_FILE +#include "tx_user.h" +#endif + + .arm + +#ifdef TX_ENABLE_FIQ_SUPPORT +SVC_MODE = 0xD3 @ Disable IRQ/FIQ, SVC mode +IRQ_MODE = 0xD2 @ Disable IRQ/FIQ, IRQ mode +#else +SVC_MODE = 0x93 @ Disable IRQ, SVC mode +IRQ_MODE = 0x92 @ Disable IRQ, IRQ mode +#endif +@ + .global _tx_thread_system_state + .global _tx_thread_current_ptr + .global _tx_thread_execute_ptr + .global _tx_timer_time_slice + .global _tx_thread_schedule + .global _tx_thread_preempt_disable + .global _tx_execution_isr_exit +@ +@ +@/* No 16-bit Thumb mode veneer code is needed for _tx_thread_context_restore +@ since it will never be called 16-bit mode. */ +@ + .arm + .text + .align 2 +@/**************************************************************************/ +@/* */ +@/* FUNCTION RELEASE */ +@/* */ +@/* _tx_thread_context_restore Cortex-R52/GNU */ +@/* 6.5.2 */ +@/* AUTHOR */ +@/* */ +@/* Frédéric Desbiens, Eclipse Foundation */ +@/* */ +@/* Derived from the Cortex-R5/GNU port originally written by */ +@/* William E. Lamie, Microsoft Corporation. */ +@/* */ +@/* DESCRIPTION */ +@/* */ +@/* This function restores the interrupt context if it is processing a */ +@/* nested interrupt. If not, it returns to the interrupt thread if no */ +@/* preemption is necessary. Otherwise, if preemption is necessary or */ +@/* if no thread was running, the function returns to the scheduler. */ +@/* */ +@/* INPUT */ +@/* */ +@/* None */ +@/* */ +@/* OUTPUT */ +@/* */ +@/* None */ +@/* */ +@/* CALLS */ +@/* */ +@/* _tx_thread_schedule Thread scheduling routine */ +@/* */ +@/* CALLED BY */ +@/* */ +@/* ISRs Interrupt Service Routines */ +@/* */ +@/**************************************************************************/ +@VOID _tx_thread_context_restore(VOID) +@{ + .global _tx_thread_context_restore + .type _tx_thread_context_restore,function +_tx_thread_context_restore: +@ +@ /* Lockout interrupts. */ +@ +#ifdef TX_ENABLE_FIQ_SUPPORT + CPSID if @ Disable IRQ and FIQ interrupts +#else + CPSID i @ Disable IRQ interrupts +#endif + +#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY +@ +@ /* Call the ISR exit function to indicate an ISR is complete. */ +@ + BL _tx_execution_isr_exit @ Call the ISR exit function +#endif +@ +@ /* Determine if interrupts are nested. */ +@ if (--_tx_thread_system_state) +@ { +@ + LDR r3, =_tx_thread_system_state @ Pickup address of system state variable + LDR r2, [r3] @ Pickup system state + SUB r2, r2, #1 @ Decrement the counter + STR r2, [r3] @ Store the counter + CMP r2, #0 @ Was this the first interrupt? + BEQ __tx_thread_not_nested_restore @ If so, not a nested restore +@ +@ /* Interrupts are nested. */ +@ +@ /* Just recover the saved registers and return to the point of +@ interrupt. */ +@ + LDMIA sp!, {r0, r10, r12, lr} @ Recover SPSR, POI, and scratch regs + MSR SPSR_cxsf, r0 @ Put SPSR back + LDMIA sp!, {r0-r3} @ Recover r0-r3 + MOVS pc, lr @ Return to point of interrupt +@ +@ } +__tx_thread_not_nested_restore: +@ +@ /* Determine if a thread was interrupted and no preemption is required. */ +@ else if (((_tx_thread_current_ptr) && (_tx_thread_current_ptr == _tx_thread_execute_ptr)) +@ || (_tx_thread_preempt_disable)) +@ { +@ + LDR r1, =_tx_thread_current_ptr @ Pickup address of current thread ptr + LDR r0, [r1] @ Pickup actual current thread pointer + CMP r0, #0 @ Is it NULL? + BEQ __tx_thread_idle_system_restore @ Yes, idle system was interrupted +@ + LDR r3, =_tx_thread_preempt_disable @ Pickup preempt disable address + LDR r2, [r3] @ Pickup actual preempt disable flag + CMP r2, #0 @ Is it set? + BNE __tx_thread_no_preempt_restore @ Yes, don't preempt this thread + LDR r3, =_tx_thread_execute_ptr @ Pickup address of execute thread ptr + LDR r2, [r3] @ Pickup actual execute thread pointer + CMP r0, r2 @ Is the same thread highest priority? + BNE __tx_thread_preempt_restore @ No, preemption needs to happen +@ +@ +__tx_thread_no_preempt_restore: +@ +@ /* Restore interrupted thread or ISR. */ +@ +@ /* Pickup the saved stack pointer. */ +@ tmp_ptr = _tx_thread_current_ptr -> tx_thread_stack_ptr; +@ +@ /* Recover the saved context and return to the point of interrupt. */ +@ + LDMIA sp!, {r0, r10, r12, lr} @ Recover SPSR, POI, and scratch regs + MSR SPSR_cxsf, r0 @ Put SPSR back + LDMIA sp!, {r0-r3} @ Recover r0-r3 + MOVS pc, lr @ Return to point of interrupt +@ +@ } +@ else +@ { +__tx_thread_preempt_restore: +@ + LDMIA sp!, {r3, r10, r12, lr} @ Recover temporarily saved registers + MOV r1, lr @ Save lr (point of interrupt) + MOV r2, #SVC_MODE @ Build SVC mode CPSR + MSR CPSR_c, r2 @ Enter SVC mode + STR r1, [sp, #-4]! @ Save point of interrupt + STMDB sp!, {r4-r12, lr} @ Save upper half of registers + MOV r4, r3 @ Save SPSR in r4 + MOV r2, #IRQ_MODE @ Build IRQ mode CPSR + MSR CPSR_c, r2 @ Enter IRQ mode + LDMIA sp!, {r0-r3} @ Recover r0-r3 + MOV r5, #SVC_MODE @ Build SVC mode CPSR + MSR CPSR_c, r5 @ Enter SVC mode + STMDB sp!, {r0-r3} @ Save r0-r3 on thread's stack +@ + LDR r1, =_tx_thread_current_ptr @ Pickup address of current thread ptr + LDR r0, [r1] @ Pickup current thread pointer +@ +#ifdef TX_ENABLE_VFP_SUPPORT + LDR r2, [r0, #144] @ Pickup the VFP enabled flag + CMP r2, #0 @ Is the VFP enabled? + BEQ _tx_skip_irq_vfp_save @ No, skip VFP IRQ save + VMRS r2, FPSCR @ Pickup the FPSCR + STR r2, [sp, #-4]! @ Save FPSCR + VSTMDB sp!, {D0-D15} @ Save D0-D15 +_tx_skip_irq_vfp_save: +#endif +@ + MOV r3, #1 @ Build interrupt stack type + STMDB sp!, {r3, r4} @ Save interrupt stack type and SPSR + STR sp, [r0, #8] @ Save stack pointer in thread control + @ block +@ +@ /* Save the remaining time-slice and disable it. */ +@ if (_tx_timer_time_slice) +@ { +@ + LDR r3, =_tx_timer_time_slice @ Pickup time-slice variable address + LDR r2, [r3] @ Pickup time-slice + CMP r2, #0 @ Is it active? + BEQ __tx_thread_dont_save_ts @ No, don't save it +@ +@ _tx_thread_current_ptr -> tx_thread_time_slice = _tx_timer_time_slice; +@ _tx_timer_time_slice = 0; +@ + STR r2, [r0, #24] @ Save thread's time-slice + MOV r2, #0 @ Clear value + STR r2, [r3] @ Disable global time-slice flag +@ +@ } +__tx_thread_dont_save_ts: +@ +@ +@ /* Clear the current task pointer. */ +@ _tx_thread_current_ptr = TX_NULL; +@ + MOV r0, #0 @ NULL value + STR r0, [r1] @ Clear current thread pointer +@ +@ /* Return to the scheduler. */ +@ _tx_thread_schedule(); +@ + B _tx_thread_schedule @ Return to scheduler +@ } +@ +__tx_thread_idle_system_restore: +@ +@ /* Just return back to the scheduler! */ +@ + MOV r0, #SVC_MODE @ Build SVC mode CPSR + MSR CPSR_c, r0 @ Enter SVC mode + B _tx_thread_schedule @ Return to scheduler +@} + + + diff --git a/ports/cortex_r52/gnu/src/tx_thread_context_save.S b/ports/cortex_r52/gnu/src/tx_thread_context_save.S new file mode 100644 index 00000000..26443700 --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_context_save.S @@ -0,0 +1,193 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * 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. +@ * +@ * SPDX-License-Identifier: MIT +@ **************************************************************************/ +@ Portions of this file were generated with AI assistance. +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** ThreadX Component */ +@/** */ +@/** Thread */ +@/** */ +@/**************************************************************************/ +@/**************************************************************************/ +#ifdef TX_INCLUDE_USER_DEFINE_FILE +#include "tx_user.h" +#endif + + .global _tx_thread_system_state + .global _tx_thread_current_ptr + .global _tx_irq_processing_return + .global _tx_execution_isr_enter +@ +@ +@/* No 16-bit Thumb mode veneer code is needed for _tx_thread_context_save +@ since it will never be called 16-bit mode. */ +@ + .arm + .text + .align 2 +@/**************************************************************************/ +@/* */ +@/* FUNCTION RELEASE */ +@/* */ +@/* _tx_thread_context_save Cortex-R52/GNU */ +@/* 6.5.2 */ +@/* AUTHOR */ +@/* */ +@/* Frédéric Desbiens, Eclipse Foundation */ +@/* */ +@/* Derived from the Cortex-R5/GNU port originally written by */ +@/* William E. Lamie, Microsoft Corporation. */ +@/* */ +@/* DESCRIPTION */ +@/* */ +@/* This function saves the context of an executing thread in the */ +@/* beginning of interrupt processing. The function also ensures that */ +@/* the system stack is used upon return to the calling ISR. */ +@/* */ +@/* INPUT */ +@/* */ +@/* None */ +@/* */ +@/* OUTPUT */ +@/* */ +@/* None */ +@/* */ +@/* CALLS */ +@/* */ +@/* None */ +@/* */ +@/* CALLED BY */ +@/* */ +@/* ISRs */ +@/* */ +@/**************************************************************************/ +@VOID _tx_thread_context_save(VOID) +@{ + .global _tx_thread_context_save + .type _tx_thread_context_save,function +_tx_thread_context_save: +@ +@ /* Upon entry to this routine, it is assumed that IRQ interrupts are locked +@ out, we are in IRQ mode, and all registers are intact. */ +@ +@ /* Check for a nested interrupt condition. */ +@ if (_tx_thread_system_state++) +@ { +@ + STMDB sp!, {r0-r3} @ Save some working registers +#ifdef TX_ENABLE_FIQ_SUPPORT + CPSID if @ Disable FIQ interrupts +#endif + LDR r3, =_tx_thread_system_state @ Pickup address of system state variable + LDR r2, [r3] @ Pickup system state + CMP r2, #0 @ Is this the first interrupt? + BEQ __tx_thread_not_nested_save @ Yes, not a nested context save +@ +@ /* Nested interrupt condition. */ +@ + ADD r2, r2, #1 @ Increment the interrupt counter + STR r2, [r3] @ Store it back in the variable +@ +@ /* Save the rest of the scratch registers on the stack and return to the +@ calling ISR. */ +@ + MRS r0, SPSR @ Pickup saved SPSR + SUB lr, lr, #4 @ Adjust point of interrupt + STMDB sp!, {r0, r10, r12, lr} @ Store other registers +@ +@ /* Return to the ISR. */ +@ + MOV r10, #0 @ Clear stack limit + +#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY +@ +@ /* Call the ISR enter function to indicate an ISR is executing. */ +@ + PUSH {lr} @ Save ISR lr + BL _tx_execution_isr_enter @ Call the ISR enter function + POP {lr} @ Recover ISR lr +#endif + + B __tx_irq_processing_return @ Continue IRQ processing +@ +__tx_thread_not_nested_save: +@ } +@ +@ /* Otherwise, not nested, check to see if a thread was running. */ +@ else if (_tx_thread_current_ptr) +@ { +@ + ADD r2, r2, #1 @ Increment the interrupt counter + STR r2, [r3] @ Store it back in the variable + LDR r1, =_tx_thread_current_ptr @ Pickup address of current thread ptr + LDR r0, [r1] @ Pickup current thread pointer + CMP r0, #0 @ Is it NULL? + BEQ __tx_thread_idle_system_save @ If so, interrupt occurred in + @ scheduling loop - nothing needs saving! +@ +@ /* Save minimal context of interrupted thread. */ +@ + MRS r2, SPSR @ Pickup saved SPSR + SUB lr, lr, #4 @ Adjust point of interrupt + STMDB sp!, {r2, r10, r12, lr} @ Store other registers +@ +@ /* Save the current stack pointer in the thread's control block. */ +@ _tx_thread_current_ptr -> tx_thread_stack_ptr = sp; +@ +@ /* Switch to the system stack. */ +@ sp = _tx_thread_system_stack_ptr@ +@ + MOV r10, #0 @ Clear stack limit + +#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY +@ +@ /* Call the ISR enter function to indicate an ISR is executing. */ +@ + PUSH {lr} @ Save ISR lr + BL _tx_execution_isr_enter @ Call the ISR enter function + POP {lr} @ Recover ISR lr +#endif + + B __tx_irq_processing_return @ Continue IRQ processing +@ +@ } +@ else +@ { +@ +__tx_thread_idle_system_save: +@ +@ /* Interrupt occurred in the scheduling loop. */ +@ +@ /* Not much to do here, just adjust the stack pointer, and return to IRQ +@ processing. */ +@ + MOV r10, #0 @ Clear stack limit + +#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY +@ +@ /* Call the ISR enter function to indicate an ISR is executing. */ +@ + PUSH {lr} @ Save ISR lr + BL _tx_execution_isr_enter @ Call the ISR enter function + POP {lr} @ Recover ISR lr +#endif + + ADD sp, sp, #16 @ Recover saved registers + B __tx_irq_processing_return @ Continue IRQ processing +@ +@ } +@} + + + diff --git a/ports/cortex_r52/gnu/src/tx_thread_fiq_context_restore.S b/ports/cortex_r52/gnu/src/tx_thread_fiq_context_restore.S new file mode 100644 index 00000000..f47c9662 --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_fiq_context_restore.S @@ -0,0 +1,237 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * 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. +@ * +@ * SPDX-License-Identifier: MIT +@ **************************************************************************/ +@ Portions of this file were generated with AI assistance. +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** ThreadX Component */ +@/** */ +@/** Thread */ +@/** */ +@/**************************************************************************/ +@/**************************************************************************/ +#ifdef TX_INCLUDE_USER_DEFINE_FILE +#include "tx_user.h" +#endif + +SVC_MODE = 0xD3 @ SVC mode +FIQ_MODE = 0xD1 @ FIQ mode +MODE_MASK = 0x1F @ Mode mask +THUMB_MASK = 0x20 @ Thumb bit mask +IRQ_MODE_BITS = 0x12 @ IRQ mode bits +@ +@ + .global _tx_thread_system_state + .global _tx_thread_current_ptr + .global _tx_thread_system_stack_ptr + .global _tx_thread_execute_ptr + .global _tx_timer_time_slice + .global _tx_thread_schedule + .global _tx_thread_preempt_disable + .global _tx_execution_isr_exit +@ +@ +@/* No 16-bit Thumb mode veneer code is needed for _tx_thread_fiq_context_restore +@ since it will never be called 16-bit mode. */ +@ + .arm + .text + .align 2 +@/**************************************************************************/ +@/* */ +@/* FUNCTION RELEASE */ +@/* */ +@/* _tx_thread_fiq_context_restore Cortex-R52/GNU */ +@/* 6.5.2 */ +@/* AUTHOR */ +@/* */ +@/* Frédéric Desbiens, Eclipse Foundation */ +@/* */ +@/* Derived from the Cortex-R5/GNU port originally written by */ +@/* William E. Lamie, Microsoft Corporation. */ +@/* */ +@/* DESCRIPTION */ +@/* */ +@/* This function restores the fiq interrupt context when processing a */ +@/* nested interrupt. If not, it returns to the interrupt thread if no */ +@/* preemption is necessary. Otherwise, if preemption is necessary or */ +@/* if no thread was running, the function returns to the scheduler. */ +@/* */ +@/* INPUT */ +@/* */ +@/* None */ +@/* */ +@/* OUTPUT */ +@/* */ +@/* None */ +@/* */ +@/* CALLS */ +@/* */ +@/* _tx_thread_schedule Thread scheduling routine */ +@/* */ +@/* CALLED BY */ +@/* */ +@/* FIQ ISR Interrupt Service Routines */ +@/* */ +@/**************************************************************************/ +@VOID _tx_thread_fiq_context_restore(VOID) +@{ + .global _tx_thread_fiq_context_restore + .type _tx_thread_fiq_context_restore,function +_tx_thread_fiq_context_restore: +@ +@ /* Lockout interrupts. */ +@ + CPSID if @ Disable IRQ and FIQ interrupts + +#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY +@ +@ /* Call the ISR exit function to indicate an ISR is complete. */ +@ + BL _tx_execution_isr_exit @ Call the ISR exit function +#endif +@ +@ /* Determine if interrupts are nested. */ +@ if (--_tx_thread_system_state) +@ { +@ + LDR r3, =_tx_thread_system_state @ Pickup address of system state variable + LDR r2, [r3] @ Pickup system state + SUB r2, r2, #1 @ Decrement the counter + STR r2, [r3] @ Store the counter + CMP r2, #0 @ Was this the first interrupt? + BEQ __tx_thread_fiq_not_nested_restore @ If so, not a nested restore +@ +@ /* Interrupts are nested. */ +@ +@ /* Just recover the saved registers and return to the point of +@ interrupt. */ +@ + LDMIA sp!, {r0, r10, r12, lr} @ Recover SPSR, POI, and scratch regs + MSR SPSR_cxsf, r0 @ Put SPSR back + LDMIA sp!, {r0-r3} @ Recover r0-r3 + MOVS pc, lr @ Return to point of interrupt +@ +@ } +__tx_thread_fiq_not_nested_restore: +@ +@ /* Determine if a thread was interrupted and no preemption is required. */ +@ else if (((_tx_thread_current_ptr) && (_tx_thread_current_ptr == _tx_thread_execute_ptr)) +@ || (_tx_thread_preempt_disable)) +@ { +@ + LDR r1, [sp] @ Pickup the saved SPSR + MOV r2, #MODE_MASK @ Build mask to isolate the interrupted mode + AND r1, r1, r2 @ Isolate mode bits + CMP r1, #IRQ_MODE_BITS @ Was an interrupt taken in IRQ mode before we + @ got to context save? */ + BEQ __tx_thread_fiq_no_preempt_restore @ Yes, just go back to point of interrupt + + + LDR r1, =_tx_thread_current_ptr @ Pickup address of current thread ptr + LDR r0, [r1] @ Pickup actual current thread pointer + CMP r0, #0 @ Is it NULL? + BEQ __tx_thread_fiq_idle_system_restore @ Yes, idle system was interrupted + + LDR r3, =_tx_thread_preempt_disable @ Pickup preempt disable address + LDR r2, [r3] @ Pickup actual preempt disable flag + CMP r2, #0 @ Is it set? + BNE __tx_thread_fiq_no_preempt_restore @ Yes, don't preempt this thread + LDR r3, =_tx_thread_execute_ptr @ Pickup address of execute thread ptr + LDR r2, [r3] @ Pickup actual execute thread pointer + CMP r0, r2 @ Is the same thread highest priority? + BNE __tx_thread_fiq_preempt_restore @ No, preemption needs to happen + + +__tx_thread_fiq_no_preempt_restore: +@ +@ /* Restore interrupted thread or ISR. */ +@ +@ /* Pickup the saved stack pointer. */ +@ tmp_ptr = _tx_thread_current_ptr -> tx_thread_stack_ptr; +@ +@ /* Recover the saved context and return to the point of interrupt. */ +@ + LDMIA sp!, {r0, lr} @ Recover SPSR, POI, and scratch regs + MSR SPSR_cxsf, r0 @ Put SPSR back + LDMIA sp!, {r0-r3} @ Recover r0-r3 + MOVS pc, lr @ Return to point of interrupt +@ +@ } +@ else +@ { +__tx_thread_fiq_preempt_restore: +@ + LDMIA sp!, {r3, lr} @ Recover temporarily saved registers + MOV r1, lr @ Save lr (point of interrupt) + MOV r2, #SVC_MODE @ Build SVC mode CPSR + MSR CPSR_c, r2 @ Enter SVC mode + STR r1, [sp, #-4]! @ Save point of interrupt + STMDB sp!, {r4-r12, lr} @ Save upper half of registers + MOV r4, r3 @ Save SPSR in r4 + MOV r2, #FIQ_MODE @ Build FIQ mode CPSR + MSR CPSR_c, r2 @ Reenter FIQ mode + LDMIA sp!, {r0-r3} @ Recover r0-r3 + MOV r5, #SVC_MODE @ Build SVC mode CPSR + MSR CPSR_c, r5 @ Enter SVC mode + STMDB sp!, {r0-r3} @ Save r0-r3 on thread's stack + MOV r3, #1 @ Build interrupt stack type + STMDB sp!, {r3, r4} @ Save interrupt stack type and SPSR + LDR r1, =_tx_thread_current_ptr @ Pickup address of current thread ptr + LDR r0, [r1] @ Pickup current thread pointer + STR sp, [r0, #8] @ Save stack pointer in thread control + @ block */ +@ +@ /* Save the remaining time-slice and disable it. */ +@ if (_tx_timer_time_slice) +@ { +@ + LDR r3, =_tx_timer_time_slice @ Pickup time-slice variable address + LDR r2, [r3] @ Pickup time-slice + CMP r2, #0 @ Is it active? + BEQ __tx_thread_fiq_dont_save_ts @ No, don't save it +@ +@ _tx_thread_current_ptr -> tx_thread_time_slice = _tx_timer_time_slice; +@ _tx_timer_time_slice = 0; +@ + STR r2, [r0, #24] @ Save thread's time-slice + MOV r2, #0 @ Clear value + STR r2, [r3] @ Disable global time-slice flag +@ +@ } +__tx_thread_fiq_dont_save_ts: +@ +@ +@ /* Clear the current task pointer. */ +@ _tx_thread_current_ptr = TX_NULL; +@ + MOV r0, #0 @ NULL value + STR r0, [r1] @ Clear current thread pointer +@ +@ /* Return to the scheduler. */ +@ _tx_thread_schedule(); +@ + B _tx_thread_schedule @ Return to scheduler +@ } +@ +__tx_thread_fiq_idle_system_restore: +@ +@ /* Just return back to the scheduler! */ +@ + ADD sp, sp, #24 @ Recover FIQ stack space + MOV r3, #SVC_MODE @ Build SVC mode CPSR + MSR CPSR_c, r3 @ Lockout interrupts + B _tx_thread_schedule @ Return to scheduler +@ +@} + diff --git a/ports/cortex_r52/gnu/src/tx_thread_fiq_context_save.S b/ports/cortex_r52/gnu/src/tx_thread_fiq_context_save.S new file mode 100644 index 00000000..510fe36e --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_fiq_context_save.S @@ -0,0 +1,195 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * 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. +@ * +@ * SPDX-License-Identifier: MIT +@ **************************************************************************/ +@ Portions of this file were generated with AI assistance. +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** ThreadX Component */ +@/** */ +@/** Thread */ +@/** */ +@/**************************************************************************/ +@/**************************************************************************/ +#ifdef TX_INCLUDE_USER_DEFINE_FILE +#include "tx_user.h" +#endif + + .global _tx_thread_system_state + .global _tx_thread_current_ptr + .global __tx_fiq_processing_return + .global _tx_execution_isr_enter +@ +@ +@/* No 16-bit Thumb mode veneer code is needed for _tx_thread_fiq_context_save +@ since it will never be called 16-bit mode. */ +@ + .arm + .text + .align 2 +@/**************************************************************************/ +@/* */ +@/* FUNCTION RELEASE */ +@/* */ +@/* _tx_thread_fiq_context_save Cortex-R52/GNU */ +@/* 6.5.2 */ +@/* AUTHOR */ +@/* */ +@/* Frédéric Desbiens, Eclipse Foundation */ +@/* */ +@/* Derived from the Cortex-R5/GNU port originally written by */ +@/* William E. Lamie, Microsoft Corporation. */ +@/* */ +@/* DESCRIPTION */ +@/* */ +@/* This function saves the context of an executing thread in the */ +@/* beginning of interrupt processing. The function also ensures that */ +@/* the system stack is used upon return to the calling ISR. */ +@/* */ +@/* INPUT */ +@/* */ +@/* None */ +@/* */ +@/* OUTPUT */ +@/* */ +@/* None */ +@/* */ +@/* CALLS */ +@/* */ +@/* None */ +@/* */ +@/* CALLED BY */ +@/* */ +@/* ISRs */ +@/* */ +@/**************************************************************************/ +@ VOID _tx_thread_fiq_context_save(VOID) +@{ + .global _tx_thread_fiq_context_save + .type _tx_thread_fiq_context_save,function +_tx_thread_fiq_context_save: +@ +@ /* Upon entry to this routine, it is assumed that IRQ interrupts are locked +@ out, we are in IRQ mode, and all registers are intact. */ +@ +@ /* Check for a nested interrupt condition. */ +@ if (_tx_thread_system_state++) +@ { +@ + STMDB sp!, {r0-r3} @ Save some working registers + LDR r3, =_tx_thread_system_state @ Pickup address of system state variable + LDR r2, [r3] @ Pickup system state + CMP r2, #0 @ Is this the first interrupt? + BEQ __tx_thread_fiq_not_nested_save @ Yes, not a nested context save +@ +@ /* Nested interrupt condition. */ +@ + ADD r2, r2, #1 @ Increment the interrupt counter + STR r2, [r3] @ Store it back in the variable +@ +@ /* Save the rest of the scratch registers on the stack and return to the +@ calling ISR. */ +@ + MRS r0, SPSR @ Pickup saved SPSR + SUB lr, lr, #4 @ Adjust point of interrupt + STMDB sp!, {r0, r10, r12, lr} @ Store other registers +@ +@ /* Return to the ISR. */ +@ + MOV r10, #0 @ Clear stack limit + +#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY +@ +@ /* Call the ISR enter function to indicate an ISR is executing. */ +@ + PUSH {lr} @ Save ISR lr + BL _tx_execution_isr_enter @ Call the ISR enter function + POP {lr} @ Recover ISR lr +#endif + + B __tx_fiq_processing_return @ Continue FIQ processing +@ +__tx_thread_fiq_not_nested_save: +@ } +@ +@ /* Otherwise, not nested, check to see if a thread was running. */ +@ else if (_tx_thread_current_ptr) +@ { +@ + ADD r2, r2, #1 @ Increment the interrupt counter + STR r2, [r3] @ Store it back in the variable + LDR r1, =_tx_thread_current_ptr @ Pickup address of current thread ptr + LDR r0, [r1] @ Pickup current thread pointer + CMP r0, #0 @ Is it NULL? + BEQ __tx_thread_fiq_idle_system_save @ If so, interrupt occurred in +@ @ scheduling loop - nothing needs saving! +@ +@ /* Save minimal context of interrupted thread. */ +@ + MRS r2, SPSR @ Pickup saved SPSR + SUB lr, lr, #4 @ Adjust point of interrupt + STMDB sp!, {r2, lr} @ Store other registers, Note that we don't +@ @ need to save sl and ip since FIQ has +@ @ copies of these registers. Nested +@ @ interrupt processing does need to save +@ @ these registers. +@ +@ /* Save the current stack pointer in the thread's control block. */ +@ _tx_thread_current_ptr -> tx_thread_stack_ptr = sp; +@ +@ /* Switch to the system stack. */ +@ sp = _tx_thread_system_stack_ptr; +@ + MOV r10, #0 @ Clear stack limit + +#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY +@ +@ /* Call the ISR enter function to indicate an ISR is executing. */ +@ + PUSH {lr} @ Save ISR lr + BL _tx_execution_isr_enter @ Call the ISR enter function + POP {lr} @ Recover ISR lr +#endif + + B __tx_fiq_processing_return @ Continue FIQ processing +@ +@ } +@ else +@ { +@ +__tx_thread_fiq_idle_system_save: +@ +@ /* Interrupt occurred in the scheduling loop. */ +@ +#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY +@ +@ /* Call the ISR enter function to indicate an ISR is executing. */ +@ + PUSH {lr} @ Save ISR lr + BL _tx_execution_isr_enter @ Call the ISR enter function + POP {lr} @ Recover ISR lr +#endif +@ +@ /* Not much to do here, save the current SPSR and LR for possible +@ use in IRQ interrupted in idle system conditions, and return to +@ FIQ interrupt processing. */ +@ + MRS r0, SPSR @ Pickup saved SPSR + SUB lr, lr, #4 @ Adjust point of interrupt + STMDB sp!, {r0, lr} @ Store other registers that will get used +@ @ or stripped off the stack in context +@ @ restore + B __tx_fiq_processing_return @ Continue FIQ processing +@ +@ } +@} + diff --git a/ports/cortex_r52/gnu/src/tx_thread_fiq_nesting_end.S b/ports/cortex_r52/gnu/src/tx_thread_fiq_nesting_end.S new file mode 100644 index 00000000..5b323208 --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_fiq_nesting_end.S @@ -0,0 +1,107 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * 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. +@ * +@ * SPDX-License-Identifier: MIT +@ **************************************************************************/ +@ Portions of this file were generated with AI assistance. +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** ThreadX Component */ +@/** */ +@/** Thread */ +@/** */ +@/**************************************************************************/ +@/**************************************************************************/ +#ifdef TX_INCLUDE_USER_DEFINE_FILE +#include "tx_user.h" +#endif + +#ifdef TX_ENABLE_FIQ_SUPPORT +DISABLE_INTS = 0xC0 @ Disable IRQ/FIQ interrupts +#else +DISABLE_INTS = 0x80 @ Disable IRQ interrupts +#endif +MODE_MASK = 0x1F @ Mode mask +FIQ_MODE_BITS = 0x11 @ FIQ mode bits +@ +@ +@/* No 16-bit Thumb mode veneer code is needed for _tx_thread_fiq_nesting_end +@ since it will never be called 16-bit mode. */ +@ + .arm + .text + .align 2 +@/**************************************************************************/ +@/* */ +@/* FUNCTION RELEASE */ +@/* */ +@/* _tx_thread_fiq_nesting_end Cortex-R52/GNU */ +@/* 6.5.2 */ +@/* AUTHOR */ +@/* */ +@/* Frédéric Desbiens, Eclipse Foundation */ +@/* */ +@/* Derived from the Cortex-R5/GNU port originally written by */ +@/* William E. Lamie, Microsoft Corporation. */ +@/* */ +@/* DESCRIPTION */ +@/* */ +@/* This function is called by the application from FIQ mode after */ +@/* _tx_thread_fiq_nesting_start has been called and switches the FIQ */ +@/* processing from system mode back to FIQ mode prior to the ISR */ +@/* calling _tx_thread_fiq_context_restore. Note that this function */ +@/* assumes the system stack pointer is in the same position after */ +@/* nesting start function was called. */ +@/* */ +@/* This function assumes that the system mode stack pointer was setup */ +@/* during low-level initialization (tx_initialize_low_level.s). */ +@/* */ +@/* This function returns with FIQ interrupts disabled. */ +@/* */ +@/* INPUT */ +@/* */ +@/* None */ +@/* */ +@/* OUTPUT */ +@/* */ +@/* None */ +@/* */ +@/* CALLS */ +@/* */ +@/* None */ +@/* */ +@/* CALLED BY */ +@/* */ +@/* ISRs */ +@/* */ +@/**************************************************************************/ +@VOID _tx_thread_fiq_nesting_end(VOID) +@{ + .global _tx_thread_fiq_nesting_end + .type _tx_thread_fiq_nesting_end,function +_tx_thread_fiq_nesting_end: + MOV r3,lr @ Save ISR return address + MRS r0, CPSR @ Pickup the CPSR + ORR r0, r0, #DISABLE_INTS @ Build disable interrupt value + MSR CPSR_c, r0 @ Disable interrupts + LDMIA sp!, {r1, lr} @ Pickup saved lr (and r1 throw-away for + @ 8-byte alignment logic) + BIC r0, r0, #MODE_MASK @ Clear mode bits + ORR r0, r0, #FIQ_MODE_BITS @ Build IRQ mode CPSR + MSR CPSR_c, r0 @ Reenter IRQ mode + +#ifdef __THUMB_INTERWORK + BX r3 @ Return to caller +#else + MOV pc, r3 @ Return to caller +#endif +@} + diff --git a/ports/cortex_r52/gnu/src/tx_thread_fiq_nesting_start.S b/ports/cortex_r52/gnu/src/tx_thread_fiq_nesting_start.S new file mode 100644 index 00000000..516eef63 --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_fiq_nesting_start.S @@ -0,0 +1,99 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * 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. +@ * +@ * SPDX-License-Identifier: MIT +@ **************************************************************************/ +@ Portions of this file were generated with AI assistance. +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** ThreadX Component */ +@/** */ +@/** Thread */ +@/** */ +@/**************************************************************************/ +@/**************************************************************************/ +#ifdef TX_INCLUDE_USER_DEFINE_FILE +#include "tx_user.h" +#endif + +FIQ_DISABLE = 0x40 @ FIQ disable bit +MODE_MASK = 0x1F @ Mode mask +SYS_MODE_BITS = 0x1F @ System mode bits +@ +@ +@/* No 16-bit Thumb mode veneer code is needed for _tx_thread_fiq_nesting_start +@ since it will never be called 16-bit mode. */ +@ + .arm + .text + .align 2 +@/**************************************************************************/ +@/* */ +@/* FUNCTION RELEASE */ +@/* */ +@/* _tx_thread_fiq_nesting_start Cortex-R52/GNU */ +@/* 6.5.2 */ +@/* AUTHOR */ +@/* */ +@/* Frédéric Desbiens, Eclipse Foundation */ +@/* */ +@/* Derived from the Cortex-R5/GNU port originally written by */ +@/* William E. Lamie, Microsoft Corporation. */ +@/* */ +@/* DESCRIPTION */ +@/* */ +@/* This function is called by the application from FIQ mode after */ +@/* _tx_thread_fiq_context_save has been called and switches the FIQ */ +@/* processing to the system mode so nested FIQ interrupt processing */ +@/* is possible (system mode has its own "lr" register). Note that */ +@/* this function assumes that the system mode stack pointer was setup */ +@/* during low-level initialization (tx_initialize_low_level.s). */ +@/* */ +@/* This function returns with FIQ interrupts enabled. */ +@/* */ +@/* INPUT */ +@/* */ +@/* None */ +@/* */ +@/* OUTPUT */ +@/* */ +@/* None */ +@/* */ +@/* CALLS */ +@/* */ +@/* None */ +@/* */ +@/* CALLED BY */ +@/* */ +@/* ISRs */ +@/* */ +@/**************************************************************************/ +@VOID _tx_thread_fiq_nesting_start(VOID) +@{ + .global _tx_thread_fiq_nesting_start + .type _tx_thread_fiq_nesting_start,function +_tx_thread_fiq_nesting_start: + MOV r3,lr @ Save ISR return address + MRS r0, CPSR @ Pickup the CPSR + BIC r0, r0, #MODE_MASK @ Clear the mode bits + ORR r0, r0, #SYS_MODE_BITS @ Build system mode CPSR + MSR CPSR_c, r0 @ Enter system mode + STMDB sp!, {r1, lr} @ Push the system mode lr on the system mode stack + @ and push r1 just to keep 8-byte alignment + BIC r0, r0, #FIQ_DISABLE @ Build enable FIQ CPSR + MSR CPSR_c, r0 @ Enter system mode +#ifdef __THUMB_INTERWORK + BX r3 @ Return to caller +#else + MOV pc, r3 @ Return to caller +#endif +@} + diff --git a/ports/cortex_r52/gnu/src/tx_thread_interrupt_control.S b/ports/cortex_r52/gnu/src/tx_thread_interrupt_control.S new file mode 100644 index 00000000..3bc0e54a --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_interrupt_control.S @@ -0,0 +1,108 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * 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. +@ * +@ * SPDX-License-Identifier: MIT +@ **************************************************************************/ +@ Portions of this file were generated with AI assistance. +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** ThreadX Component */ +@/** */ +@/** Thread */ +@/** */ +@/**************************************************************************/ +@/**************************************************************************/ +#ifdef TX_INCLUDE_USER_DEFINE_FILE +#include "tx_user.h" +#endif + + +INT_MASK = 0x03F + +@ +@/* Define the 16-bit Thumb mode veneer for _tx_thread_interrupt_control for +@ applications calling this function from to 16-bit Thumb mode. */ +@ + .text + .align 2 + .global $_tx_thread_interrupt_control +$_tx_thread_interrupt_control: + .thumb + BX pc @ Switch to 32-bit mode + NOP @ + .arm + STMFD sp!, {lr} @ Save return address + BL _tx_thread_interrupt_control @ Call _tx_thread_interrupt_control function + LDMFD sp!, {lr} @ Recover saved return address + BX lr @ Return to 16-bit caller +@ +@ + .text + .align 2 +@/**************************************************************************/ +@/* */ +@/* FUNCTION RELEASE */ +@/* */ +@/* _tx_thread_interrupt_control Cortex-R52/GNU */ +@/* 6.5.2 */ +@/* AUTHOR */ +@/* */ +@/* Frédéric Desbiens, Eclipse Foundation */ +@/* */ +@/* Derived from the Cortex-R5/GNU port originally written by */ +@/* William E. Lamie, Microsoft Corporation. */ +@/* */ +@/* DESCRIPTION */ +@/* */ +@/* This function is responsible for changing the interrupt lockout */ +@/* posture of the system. */ +@/* */ +@/* INPUT */ +@/* */ +@/* new_posture New interrupt lockout posture */ +@/* */ +@/* OUTPUT */ +@/* */ +@/* old_posture Old interrupt lockout posture */ +@/* */ +@/* CALLS */ +@/* */ +@/* None */ +@/* */ +@/* CALLED BY */ +@/* */ +@/* Application Code */ +@/* */ +@/**************************************************************************/ +@UINT _tx_thread_interrupt_control(UINT new_posture) +@{ + .global _tx_thread_interrupt_control + .type _tx_thread_interrupt_control,function +_tx_thread_interrupt_control: +@ +@ /* Pickup current interrupt lockout posture. */ +@ + MRS r3, CPSR @ Pickup current CPSR + MOV r2, #INT_MASK @ Build interrupt mask + AND r1, r3, r2 @ Clear interrupt lockout bits + ORR r1, r1, r0 @ Or-in new interrupt lockout bits +@ +@ /* Apply the new interrupt posture. */ +@ + MSR CPSR_c, r1 @ Setup new CPSR + BIC r0, r3, r2 @ Return previous interrupt mask +#ifdef __THUMB_INTERWORK + BX lr @ Return to caller +#else + MOV pc, lr @ Return to caller +#endif +@} + diff --git a/ports/cortex_r52/gnu/src/tx_thread_interrupt_disable.S b/ports/cortex_r52/gnu/src/tx_thread_interrupt_disable.S new file mode 100644 index 00000000..9b3f215a --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_interrupt_disable.S @@ -0,0 +1,105 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * 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. +@ * +@ * SPDX-License-Identifier: MIT +@ **************************************************************************/ +@ Portions of this file were generated with AI assistance. +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** ThreadX Component */ +@/** */ +@/** Thread */ +@/** */ +@/**************************************************************************/ +@/**************************************************************************/ +#ifdef TX_INCLUDE_USER_DEFINE_FILE +#include "tx_user.h" +#endif + +@/* Define the 16-bit Thumb mode veneer for _tx_thread_interrupt_disable for +@ applications calling this function from to 16-bit Thumb mode. */ +@ + .text + .align 2 + .global $_tx_thread_interrupt_disable +$_tx_thread_interrupt_disable: + .thumb + BX pc @ Switch to 32-bit mode + NOP @ + .arm + STMFD sp!, {lr} @ Save return address + BL _tx_thread_interrupt_disable @ Call _tx_thread_interrupt_disable function + LDMFD sp!, {lr} @ Recover saved return address + BX lr @ Return to 16-bit caller +@ +@ + .text + .align 2 +@/**************************************************************************/ +@/* */ +@/* FUNCTION RELEASE */ +@/* */ +@/* _tx_thread_interrupt_disable Cortex-R52/GNU */ +@/* 6.5.2 */ +@/* AUTHOR */ +@/* */ +@/* Frédéric Desbiens, Eclipse Foundation */ +@/* */ +@/* Derived from the Cortex-R5/GNU port originally written by */ +@/* William E. Lamie, Microsoft Corporation. */ +@/* */ +@/* DESCRIPTION */ +@/* */ +@/* This function is responsible for disabling interrupts */ +@/* */ +@/* INPUT */ +@/* */ +@/* None */ +@/* */ +@/* OUTPUT */ +@/* */ +@/* old_posture Old interrupt lockout posture */ +@/* */ +@/* CALLS */ +@/* */ +@/* None */ +@/* */ +@/* CALLED BY */ +@/* */ +@/* Application Code */ +@/* */ +@/**************************************************************************/ +@UINT _tx_thread_interrupt_disable(void) +@{ + .global _tx_thread_interrupt_disable + .type _tx_thread_interrupt_disable,function +_tx_thread_interrupt_disable: +@ +@ /* Pickup current interrupt lockout posture. */ +@ + MRS r0, CPSR @ Pickup current CPSR +@ +@ /* Mask interrupts. */ +@ +#ifdef TX_ENABLE_FIQ_SUPPORT + CPSID if @ Disable IRQ and FIQ +#else + CPSID i @ Disable IRQ +#endif + +#ifdef __THUMB_INTERWORK + BX lr @ Return to caller +#else + MOV pc, lr @ Return to caller +#endif +@} + + diff --git a/ports/cortex_r52/gnu/src/tx_thread_interrupt_restore.S b/ports/cortex_r52/gnu/src/tx_thread_interrupt_restore.S new file mode 100644 index 00000000..b014afd9 --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_interrupt_restore.S @@ -0,0 +1,96 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * 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. +@ * +@ * SPDX-License-Identifier: MIT +@ **************************************************************************/ +@ Portions of this file were generated with AI assistance. +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** ThreadX Component */ +@/** */ +@/** Thread */ +@/** */ +@/**************************************************************************/ +@/**************************************************************************/ +#ifdef TX_INCLUDE_USER_DEFINE_FILE +#include "tx_user.h" +#endif + +@/* Define the 16-bit Thumb mode veneer for _tx_thread_interrupt_restore for +@ applications calling this function from to 16-bit Thumb mode. */ +@ + .text + .align 2 + .global $_tx_thread_interrupt_restore +$_tx_thread_interrupt_restore: + .thumb + BX pc @ Switch to 32-bit mode + NOP @ + .arm + STMFD sp!, {lr} @ Save return address + BL _tx_thread_interrupt_restore @ Call _tx_thread_interrupt_restore function + LDMFD sp!, {lr} @ Recover saved return address + BX lr @ Return to 16-bit caller +@ +@ + .text + .align 2 +@/**************************************************************************/ +@/* */ +@/* FUNCTION RELEASE */ +@/* */ +@/* _tx_thread_interrupt_restore Cortex-R52/GNU */ +@/* 6.5.2 */ +@/* AUTHOR */ +@/* */ +@/* Frédéric Desbiens, Eclipse Foundation */ +@/* */ +@/* Derived from the Cortex-R5/GNU port originally written by */ +@/* William E. Lamie, Microsoft Corporation. */ +@/* */ +@/* DESCRIPTION */ +@/* */ +@/* This function is responsible for restoring interrupts to the state */ +@/* returned by a previous _tx_thread_interrupt_disable call. */ +@/* */ +@/* INPUT */ +@/* */ +@/* old_posture Old interrupt lockout posture */ +@/* */ +@/* OUTPUT */ +@/* */ +@/* None */ +@/* */ +@/* CALLS */ +@/* */ +@/* None */ +@/* */ +@/* CALLED BY */ +@/* */ +@/* Application Code */ +@/* */ +@/**************************************************************************/ +@UINT _tx_thread_interrupt_restore(UINT old_posture) +@{ + .global _tx_thread_interrupt_restore + .type _tx_thread_interrupt_restore,function +_tx_thread_interrupt_restore: +@ +@ /* Apply the new interrupt posture. */ +@ + MSR CPSR_c, r0 @ Setup new CPSR +#ifdef __THUMB_INTERWORK + BX lr @ Return to caller +#else + MOV pc, lr @ Return to caller +#endif +@} + diff --git a/ports/cortex_r52/gnu/src/tx_thread_irq_nesting_end.S b/ports/cortex_r52/gnu/src/tx_thread_irq_nesting_end.S new file mode 100644 index 00000000..f14ce5cf --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_irq_nesting_end.S @@ -0,0 +1,106 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * 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. +@ * +@ * SPDX-License-Identifier: MIT +@ **************************************************************************/ +@ Portions of this file were generated with AI assistance. +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** ThreadX Component */ +@/** */ +@/** Thread */ +@/** */ +@/**************************************************************************/ +@/**************************************************************************/ +#ifdef TX_INCLUDE_USER_DEFINE_FILE +#include "tx_user.h" +#endif + +#ifdef TX_ENABLE_FIQ_SUPPORT +DISABLE_INTS = 0xC0 @ Disable IRQ/FIQ interrupts +#else +DISABLE_INTS = 0x80 @ Disable IRQ interrupts +#endif +MODE_MASK = 0x1F @ Mode mask +IRQ_MODE_BITS = 0x12 @ IRQ mode bits +@ +@ +@/* No 16-bit Thumb mode veneer code is needed for _tx_thread_irq_nesting_end +@ since it will never be called 16-bit mode. */ +@ + .arm + .text + .align 2 +@/**************************************************************************/ +@/* */ +@/* FUNCTION RELEASE */ +@/* */ +@/* _tx_thread_irq_nesting_end Cortex-R52/GNU */ +@/* 6.5.2 */ +@/* AUTHOR */ +@/* */ +@/* Frédéric Desbiens, Eclipse Foundation */ +@/* */ +@/* Derived from the Cortex-R5/GNU port originally written by */ +@/* William E. Lamie, Microsoft Corporation. */ +@/* */ +@/* DESCRIPTION */ +@/* */ +@/* This function is called by the application from IRQ mode after */ +@/* _tx_thread_irq_nesting_start has been called and switches the IRQ */ +@/* processing from system mode back to IRQ mode prior to the ISR */ +@/* calling _tx_thread_context_restore. Note that this function */ +@/* assumes the system stack pointer is in the same position after */ +@/* nesting start function was called. */ +@/* */ +@/* This function assumes that the system mode stack pointer was setup */ +@/* during low-level initialization (tx_initialize_low_level.s). */ +@/* */ +@/* This function returns with IRQ interrupts disabled. */ +@/* */ +@/* INPUT */ +@/* */ +@/* None */ +@/* */ +@/* OUTPUT */ +@/* */ +@/* None */ +@/* */ +@/* CALLS */ +@/* */ +@/* None */ +@/* */ +@/* CALLED BY */ +@/* */ +@/* ISRs */ +@/* */ +@/**************************************************************************/ +@VOID _tx_thread_irq_nesting_end(VOID) +@{ + .global _tx_thread_irq_nesting_end + .type _tx_thread_irq_nesting_end,function +_tx_thread_irq_nesting_end: + MOV r3,lr @ Save ISR return address + MRS r0, CPSR @ Pickup the CPSR + ORR r0, r0, #DISABLE_INTS @ Build disable interrupt value + MSR CPSR_c, r0 @ Disable interrupts + LDMIA sp!, {r1, lr} @ Pickup saved lr (and r1 throw-away for + @ 8-byte alignment logic) + BIC r0, r0, #MODE_MASK @ Clear mode bits + ORR r0, r0, #IRQ_MODE_BITS @ Build IRQ mode CPSR + MSR CPSR_c, r0 @ Reenter IRQ mode +#ifdef __THUMB_INTERWORK + BX r3 @ Return to caller +#else + MOV pc, r3 @ Return to caller +#endif +@} + diff --git a/ports/cortex_r52/gnu/src/tx_thread_irq_nesting_start.S b/ports/cortex_r52/gnu/src/tx_thread_irq_nesting_start.S new file mode 100644 index 00000000..e300a5be --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_irq_nesting_start.S @@ -0,0 +1,99 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * 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. +@ * +@ * SPDX-License-Identifier: MIT +@ **************************************************************************/ +@ Portions of this file were generated with AI assistance. +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** ThreadX Component */ +@/** */ +@/** Thread */ +@/** */ +@/**************************************************************************/ +@/**************************************************************************/ +#ifdef TX_INCLUDE_USER_DEFINE_FILE +#include "tx_user.h" +#endif + +IRQ_DISABLE = 0x80 @ IRQ disable bit +MODE_MASK = 0x1F @ Mode mask +SYS_MODE_BITS = 0x1F @ System mode bits +@ +@ +@/* No 16-bit Thumb mode veneer code is needed for _tx_thread_irq_nesting_start +@ since it will never be called 16-bit mode. */ +@ + .arm + .text + .align 2 +@/**************************************************************************/ +@/* */ +@/* FUNCTION RELEASE */ +@/* */ +@/* _tx_thread_irq_nesting_start Cortex-R52/GNU */ +@/* 6.5.2 */ +@/* AUTHOR */ +@/* */ +@/* Frédéric Desbiens, Eclipse Foundation */ +@/* */ +@/* Derived from the Cortex-R5/GNU port originally written by */ +@/* William E. Lamie, Microsoft Corporation. */ +@/* */ +@/* DESCRIPTION */ +@/* */ +@/* This function is called by the application from IRQ mode after */ +@/* _tx_thread_context_save has been called and switches the IRQ */ +@/* processing to the system mode so nested IRQ interrupt processing */ +@/* is possible (system mode has its own "lr" register). Note that */ +@/* this function assumes that the system mode stack pointer was setup */ +@/* during low-level initialization (tx_initialize_low_level.s). */ +@/* */ +@/* This function returns with IRQ interrupts enabled. */ +@/* */ +@/* INPUT */ +@/* */ +@/* None */ +@/* */ +@/* OUTPUT */ +@/* */ +@/* None */ +@/* */ +@/* CALLS */ +@/* */ +@/* None */ +@/* */ +@/* CALLED BY */ +@/* */ +@/* ISRs */ +@/* */ +@/**************************************************************************/ +@VOID _tx_thread_irq_nesting_start(VOID) +@{ + .global _tx_thread_irq_nesting_start + .type _tx_thread_irq_nesting_start,function +_tx_thread_irq_nesting_start: + MOV r3,lr @ Save ISR return address + MRS r0, CPSR @ Pickup the CPSR + BIC r0, r0, #MODE_MASK @ Clear the mode bits + ORR r0, r0, #SYS_MODE_BITS @ Build system mode CPSR + MSR CPSR_c, r0 @ Enter system mode + STMDB sp!, {r1, lr} @ Push the system mode lr on the system mode stack + @ and push r1 just to keep 8-byte alignment + BIC r0, r0, #IRQ_DISABLE @ Build enable IRQ CPSR + MSR CPSR_c, r0 @ Enter system mode +#ifdef __THUMB_INTERWORK + BX r3 @ Return to caller +#else + MOV pc, r3 @ Return to caller +#endif +@} + diff --git a/ports/cortex_r52/gnu/src/tx_thread_schedule.S b/ports/cortex_r52/gnu/src/tx_thread_schedule.S new file mode 100644 index 00000000..2dfc1da7 --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_schedule.S @@ -0,0 +1,240 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * 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. +@ * +@ * SPDX-License-Identifier: MIT +@ **************************************************************************/ +@ Portions of this file were generated with AI assistance. +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** ThreadX Component */ +@/** */ +@/** Thread */ +@/** */ +@/**************************************************************************/ +@/**************************************************************************/ +#ifdef TX_INCLUDE_USER_DEFINE_FILE +#include "tx_user.h" +#endif + + .global _tx_thread_execute_ptr + .global _tx_thread_current_ptr + .global _tx_timer_time_slice + .global _tx_execution_thread_enter +@ +@ +@/* Define the 16-bit Thumb mode veneer for _tx_thread_schedule for +@ applications calling this function from to 16-bit Thumb mode. */ +@ + .text + .align 2 + .global $_tx_thread_schedule + .type $_tx_thread_schedule,function +$_tx_thread_schedule: + .thumb + BX pc @ Switch to 32-bit mode + NOP @ + .arm + STMFD sp!, {lr} @ Save return address + BL _tx_thread_schedule @ Call _tx_thread_schedule function + LDMFD sp!, {lr} @ Recover saved return address + BX lr @ Return to 16-bit caller +@ +@ + .text + .align 2 +@/**************************************************************************/ +@/* */ +@/* FUNCTION RELEASE */ +@/* */ +@/* _tx_thread_schedule Cortex-R52/GNU */ +@/* 6.5.2 */ +@/* AUTHOR */ +@/* */ +@/* Frédéric Desbiens, Eclipse Foundation */ +@/* */ +@/* Derived from the Cortex-R5/GNU port originally written by */ +@/* William E. Lamie, Microsoft Corporation. */ +@/* */ +@/* DESCRIPTION */ +@/* */ +@/* This function waits for a thread control block pointer to appear in */ +@/* the _tx_thread_execute_ptr variable. Once a thread pointer appears */ +@/* in the variable, the corresponding thread is resumed. */ +@/* */ +@/* INPUT */ +@/* */ +@/* None */ +@/* */ +@/* OUTPUT */ +@/* */ +@/* None */ +@/* */ +@/* CALLS */ +@/* */ +@/* None */ +@/* */ +@/* CALLED BY */ +@/* */ +@/* _tx_initialize_kernel_enter ThreadX entry function */ +@/* _tx_thread_system_return Return to system from thread */ +@/* _tx_thread_context_restore Restore thread's context */ +@/* */ +@/**************************************************************************/ +@VOID _tx_thread_schedule(VOID) +@{ + .global _tx_thread_schedule + .type _tx_thread_schedule,function +_tx_thread_schedule: +@ +@ /* Enable interrupts. */ +@ +#ifdef TX_ENABLE_FIQ_SUPPORT + CPSIE if @ Enable IRQ and FIQ interrupts +#else + CPSIE i @ Enable IRQ interrupts +#endif +@ +@ /* Wait for a thread to execute. */ +@ do +@ { + LDR r1, =_tx_thread_execute_ptr @ Address of thread execute ptr +@ +__tx_thread_schedule_loop: +@ + LDR r0, [r1] @ Pickup next thread to execute + CMP r0, #0 @ Is it NULL? + BEQ __tx_thread_schedule_loop @ If so, keep looking for a thread +@ +@ } +@ while(_tx_thread_execute_ptr == TX_NULL); +@ +@ /* Yes! We have a thread to execute. Lockout interrupts and +@ transfer control to it. */ +@ +#ifdef TX_ENABLE_FIQ_SUPPORT + CPSID if @ Disable IRQ and FIQ interrupts +#else + CPSID i @ Disable IRQ interrupts +#endif +@ +@ /* Setup the current thread pointer. */ +@ _tx_thread_current_ptr = _tx_thread_execute_ptr; +@ + LDR r1, =_tx_thread_current_ptr @ Pickup address of current thread + STR r0, [r1] @ Setup current thread pointer +@ +@ /* Increment the run count for this thread. */ +@ _tx_thread_current_ptr -> tx_thread_run_count++; +@ + LDR r2, [r0, #4] @ Pickup run counter + LDR r3, [r0, #24] @ Pickup time-slice for this thread + ADD r2, r2, #1 @ Increment thread run-counter + STR r2, [r0, #4] @ Store the new run counter +@ +@ /* Setup time-slice, if present. */ +@ _tx_timer_time_slice = _tx_thread_current_ptr -> tx_thread_time_slice; +@ + LDR r2, =_tx_timer_time_slice @ Pickup address of time-slice + @ variable + LDR sp, [r0, #8] @ Switch stack pointers + STR r3, [r2] @ Setup time-slice +@ +@ /* Switch to the thread's stack. */ +@ sp = _tx_thread_execute_ptr -> tx_thread_stack_ptr; +@ +#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY +@ +@ /* Call the thread entry function to indicate the thread is executing. */ +@ + BL _tx_execution_thread_enter @ Call the thread execution enter function +#endif +@ +@ /* Determine if an interrupt frame or a synchronous task suspension frame +@ is present. */ +@ + LDMIA sp!, {r4, r5} @ Pickup the stack type and saved CPSR + CMP r4, #0 @ Check for synchronous context switch + BEQ _tx_solicited_return + MSR SPSR_cxsf, r5 @ Setup SPSR for return +#ifdef TX_ENABLE_VFP_SUPPORT + LDR r1, [r0, #144] @ Pickup the VFP enabled flag + CMP r1, #0 @ Is the VFP enabled? + BEQ _tx_skip_interrupt_vfp_restore @ No, skip VFP interrupt restore + VLDMIA sp!, {D0-D15} @ Recover D0-D15 + LDR r4, [sp], #4 @ Pickup FPSCR + VMSR FPSCR, r4 @ Restore FPSCR +_tx_skip_interrupt_vfp_restore: +#endif + LDMIA sp!, {r0-r12, lr, pc}^ @ Return to point of thread interrupt +@ +_tx_solicited_return: +#ifdef TX_ENABLE_VFP_SUPPORT + LDR r1, [r0, #144] @ Pickup the VFP enabled flag + CMP r1, #0 @ Is the VFP enabled? + BEQ _tx_skip_solicited_vfp_restore @ No, skip VFP solicited restore + VLDMIA sp!, {D8-D15} @ Recover D8-D15 + LDR r4, [sp], #4 @ Pickup FPSCR + VMSR FPSCR, r4 @ Restore FPSCR +_tx_skip_solicited_vfp_restore: +#endif + MOV r0, r5 @ Move CPSR to scratch register + LDMIA sp!, {r4-r11, lr} @ Return to thread synchronously + MSR CPSR_cxsf, r0 @ Recover CPSR +@ +#ifdef __THUMB_INTERWORK + BX lr @ Return to caller +#else + MOV pc, lr @ Return to caller +#endif +@ +@} +@ +@ +#ifdef TX_ENABLE_VFP_SUPPORT + .global tx_thread_vfp_enable + .type tx_thread_vfp_enable,function +tx_thread_vfp_enable: + MRS r2, CPSR @ Pickup the CPSR +#ifdef TX_ENABLE_FIQ_SUPPORT + CPSID if @ Disable IRQ and FIQ interrupts +#else + CPSID i @ Disable IRQ interrupts +#endif + LDR r0, =_tx_thread_current_ptr @ Build current thread pointer address + LDR r1, [r0] @ Pickup current thread pointer + CMP r1, #0 @ Check for NULL thread pointer + BEQ __tx_no_thread_to_enable @ If NULL, skip VFP enable + MOV r0, #1 @ Build enable value + STR r0, [r1, #144] @ Set the VFP enable flag (tx_thread_vfp_enable field in TX_THREAD) +__tx_no_thread_to_enable: + MSR CPSR_cxsf, r2 @ Recover CPSR + BX LR @ Return to caller +@ + .global tx_thread_vfp_disable + .type tx_thread_vfp_disable,function +tx_thread_vfp_disable: + MRS r2, CPSR @ Pickup the CPSR +#ifdef TX_ENABLE_FIQ_SUPPORT + CPSID if @ Disable IRQ and FIQ interrupts +#else + CPSID i @ Disable IRQ interrupts +#endif + LDR r0, =_tx_thread_current_ptr @ Build current thread pointer address + LDR r1, [r0] @ Pickup current thread pointer + CMP r1, #0 @ Check for NULL thread pointer + BEQ __tx_no_thread_to_disable @ If NULL, skip VFP disable + MOV r0, #0 @ Build disable value + STR r0, [r1, #144] @ Clear the VFP enable flag (tx_thread_vfp_enable field in TX_THREAD) +__tx_no_thread_to_disable: + MSR CPSR_cxsf, r2 @ Recover CPSR + BX LR @ Return to caller +#endif + diff --git a/ports/cortex_r52/gnu/src/tx_thread_stack_build.S b/ports/cortex_r52/gnu/src/tx_thread_stack_build.S new file mode 100644 index 00000000..1be3b5f3 --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_stack_build.S @@ -0,0 +1,170 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * 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. +@ * +@ * SPDX-License-Identifier: MIT +@ **************************************************************************/ +@ Portions of this file were generated with AI assistance. +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** ThreadX Component */ +@/** */ +@/** Thread */ +@/** */ +@/**************************************************************************/ +@/**************************************************************************/ +#ifdef TX_INCLUDE_USER_DEFINE_FILE +#include "tx_user.h" +#endif + + .arm + +SVC_MODE = 0x13 @ SVC mode +#ifdef TX_ENABLE_FIQ_SUPPORT +CPSR_MASK = 0xDF @ Mask initial CPSR, IRQ & FIQ interrupts enabled +#else +CPSR_MASK = 0x9F @ Mask initial CPSR, IRQ interrupts enabled +#endif +@ +@ +@/* Define the 16-bit Thumb mode veneer for _tx_thread_stack_build for +@ applications calling this function from to 16-bit Thumb mode. */ +@ + .text + .align 2 + .thumb + .global $_tx_thread_stack_build + .type $_tx_thread_stack_build,function +$_tx_thread_stack_build: + BX pc @ Switch to 32-bit mode + NOP @ + .arm + STMFD sp!, {lr} @ Save return address + BL _tx_thread_stack_build @ Call _tx_thread_stack_build function + LDMFD sp!, {lr} @ Recover saved return address + BX lr @ Return to 16-bit caller +@ +@ + .text + .align 2 +@/**************************************************************************/ +@/* */ +@/* FUNCTION RELEASE */ +@/* */ +@/* _tx_thread_stack_build Cortex-R52/GNU */ +@/* 6.5.2 */ +@/* AUTHOR */ +@/* */ +@/* Frédéric Desbiens, Eclipse Foundation */ +@/* */ +@/* Derived from the Cortex-R5/GNU port originally written by */ +@/* William E. Lamie, Microsoft Corporation. */ +@/* */ +@/* DESCRIPTION */ +@/* */ +@/* This function builds a stack frame on the supplied thread's stack. */ +@/* The stack frame results in a fake interrupt return to the supplied */ +@/* function pointer. */ +@/* */ +@/* INPUT */ +@/* */ +@/* thread_ptr Pointer to thread control blk */ +@/* function_ptr Pointer to return function */ +@/* */ +@/* OUTPUT */ +@/* */ +@/* None */ +@/* */ +@/* CALLS */ +@/* */ +@/* None */ +@/* */ +@/* CALLED BY */ +@/* */ +@/* _tx_thread_create Create thread service */ +@/* */ +@/**************************************************************************/ +@VOID _tx_thread_stack_build(TX_THREAD *thread_ptr, VOID (*function_ptr)(VOID)) +@{ + .global _tx_thread_stack_build + .type _tx_thread_stack_build,function +_tx_thread_stack_build: +@ +@ +@ /* Build a fake interrupt frame. The form of the fake interrupt stack +@ on the ARM9 should look like the following after it is built: +@ +@ Stack Top: 1 Interrupt stack frame type +@ CPSR Initial value for CPSR +@ a1 (r0) Initial value for a1 +@ a2 (r1) Initial value for a2 +@ a3 (r2) Initial value for a3 +@ a4 (r3) Initial value for a4 +@ v1 (r4) Initial value for v1 +@ v2 (r5) Initial value for v2 +@ v3 (r6) Initial value for v3 +@ v4 (r7) Initial value for v4 +@ v5 (r8) Initial value for v5 +@ sb (r9) Initial value for sb +@ sl (r10) Initial value for sl +@ fp (r11) Initial value for fp +@ ip (r12) Initial value for ip +@ lr (r14) Initial value for lr +@ pc (r15) Initial value for pc +@ 0 For stack backtracing +@ +@ Stack Bottom: (higher memory address) */ +@ + LDR r2, [r0, #16] @ Pickup end of stack area + BIC r2, r2, #7 @ Ensure 8-byte alignment + SUB r2, r2, #76 @ Allocate space for the stack frame +@ +@ /* Actually build the stack frame. */ +@ + MOV r3, #1 @ Build interrupt stack type + STR r3, [r2, #0] @ Store stack type + MOV r3, #0 @ Build initial register value + STR r3, [r2, #8] @ Store initial r0 + STR r3, [r2, #12] @ Store initial r1 + STR r3, [r2, #16] @ Store initial r2 + STR r3, [r2, #20] @ Store initial r3 + STR r3, [r2, #24] @ Store initial r4 + STR r3, [r2, #28] @ Store initial r5 + STR r3, [r2, #32] @ Store initial r6 + STR r3, [r2, #36] @ Store initial r7 + STR r3, [r2, #40] @ Store initial r8 + STR r3, [r2, #44] @ Store initial r9 + LDR r3, [r0, #12] @ Pickup stack starting address + STR r3, [r2, #48] @ Store initial r10 (sl) + LDR r3,=_tx_thread_schedule @ Pickup address of _tx_thread_schedule for GDB backtrace + STR r3, [r2, #60] @ Store initial r14 (lr) + MOV r3, #0 @ Build initial register value + STR r3, [r2, #52] @ Store initial r11 + STR r3, [r2, #56] @ Store initial r12 + STR r1, [r2, #64] @ Store initial pc + STR r3, [r2, #68] @ 0 for back-trace + MRS r1, CPSR @ Pickup CPSR + BIC r1, r1, #CPSR_MASK @ Mask mode bits of CPSR + ORR r3, r1, #SVC_MODE @ Build CPSR, SVC mode, interrupts enabled + STR r3, [r2, #4] @ Store initial CPSR +@ +@ /* Setup stack pointer. */ +@ thread_ptr -> tx_thread_stack_ptr = r2; +@ + STR r2, [r0, #8] @ Save stack pointer in thread's + @ control block +#ifdef __THUMB_INTERWORK + BX lr @ Return to caller +#else + MOV pc, lr @ Return to caller +#endif +@} + + diff --git a/ports/cortex_r52/gnu/src/tx_thread_system_return.S b/ports/cortex_r52/gnu/src/tx_thread_system_return.S new file mode 100644 index 00000000..ae0eac7d --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_system_return.S @@ -0,0 +1,169 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * 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. +@ * +@ * SPDX-License-Identifier: MIT +@ **************************************************************************/ +@ Portions of this file were generated with AI assistance. +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** ThreadX Component */ +@/** */ +@/** Thread */ +@/** */ +@/**************************************************************************/ +@/**************************************************************************/ +#ifdef TX_INCLUDE_USER_DEFINE_FILE +#include "tx_user.h" +#endif + + .arm +@ +@ + .global _tx_thread_current_ptr + .global _tx_timer_time_slice + .global _tx_thread_schedule + .global _tx_execution_thread_exit +@ +@ +@ +@/* Define the 16-bit Thumb mode veneer for _tx_thread_system_return for +@ applications calling this function from to 16-bit Thumb mode. */ +@ + .text + .align 2 + .global $_tx_thread_system_return + .type $_tx_thread_system_return,function +$_tx_thread_system_return: + .thumb + BX pc @ Switch to 32-bit mode + NOP @ + .arm + STMFD sp!, {lr} @ Save return address + BL _tx_thread_system_return @ Call _tx_thread_system_return function + LDMFD sp!, {lr} @ Recover saved return address + BX lr @ Return to 16-bit caller +@ +@ + .text + .align 2 +@/**************************************************************************/ +@/* */ +@/* FUNCTION RELEASE */ +@/* */ +@/* _tx_thread_system_return Cortex-R52/GNU */ +@/* 6.5.2 */ +@/* AUTHOR */ +@/* */ +@/* Frédéric Desbiens, Eclipse Foundation */ +@/* */ +@/* Derived from the Cortex-R5/GNU port originally written by */ +@/* William E. Lamie, Microsoft Corporation. */ +@/* */ +@/* DESCRIPTION */ +@/* */ +@/* This function is target processor specific. It is used to transfer */ +@/* control from a thread back to the ThreadX system. Only a */ +@/* minimal context is saved since the compiler assumes temp registers */ +@/* are going to get slicked by a function call anyway. */ +@/* */ +@/* INPUT */ +@/* */ +@/* None */ +@/* */ +@/* OUTPUT */ +@/* */ +@/* None */ +@/* */ +@/* CALLS */ +@/* */ +@/* _tx_thread_schedule Thread scheduling loop */ +@/* */ +@/* CALLED BY */ +@/* */ +@/* ThreadX components */ +@/* */ +@/**************************************************************************/ +@VOID _tx_thread_system_return(VOID) +@{ + .global _tx_thread_system_return + .type _tx_thread_system_return,function +_tx_thread_system_return: +@ +@ /* Lockout interrupts. */ +@ + MRS r1, CPSR @ Pickup the CPSR +#ifdef TX_ENABLE_FIQ_SUPPORT + CPSID if @ Disable IRQ and FIQ interrupts +#else + CPSID i @ Disable IRQ interrupts +#endif +@ /* Save minimal context on the stack. */ +@ + STMDB sp!, {r4-r11, lr} @ Save minimal context + LDR r5, =_tx_thread_current_ptr @ Pickup address of current ptr + LDR r6, [r5, #0] @ Pickup current thread pointer +@ +#ifdef TX_ENABLE_VFP_SUPPORT + LDR r0, [r6, #144] @ Pickup the VFP enabled flag + CMP r0, #0 @ Is the VFP enabled? + BEQ _tx_skip_solicited_vfp_save @ No, skip VFP solicited save + VMRS r4, FPSCR @ Pickup the FPSCR + STR r4, [sp, #-4]! @ Save FPSCR + VSTMDB sp!, {D8-D15} @ Save D8-D15 +_tx_skip_solicited_vfp_save: +#endif +@ + MOV r0, #0 @ Build a solicited stack type + STMDB sp!, {r0-r1} @ Save type and CPSR +@ +@ +#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY +@ +@ /* Call the thread exit function to indicate the thread is no longer executing. */ +@ + BL _tx_execution_thread_exit @ Call the thread exit function +#endif +@ + LDR r2, =_tx_timer_time_slice @ Pickup address of time slice + LDR r1, [r2, #0] @ Pickup current time slice +@ +@ /* Save current stack and switch to system stack. */ +@ _tx_thread_current_ptr -> tx_thread_stack_ptr = sp; +@ sp = _tx_thread_system_stack_ptr; +@ + STR sp, [r6, #8] @ Save thread stack pointer +@ +@ /* Determine if the time-slice is active. */ +@ if (_tx_timer_time_slice) +@ { +@ + MOV r4, #0 @ Build clear value + CMP r1, #0 @ Is a time-slice active? + BEQ __tx_thread_dont_save_ts @ No, don't save the time-slice +@ +@ /* Save time-slice for the thread and clear the current time-slice. */ +@ _tx_thread_current_ptr -> tx_thread_time_slice = _tx_timer_time_slice; +@ _tx_timer_time_slice = 0; +@ + STR r4, [r2, #0] @ Clear time-slice + STR r1, [r6, #24] @ Save current time-slice +@ +@ } +__tx_thread_dont_save_ts: +@ +@ /* Clear the current thread pointer. */ +@ _tx_thread_current_ptr = TX_NULL; +@ + STR r4, [r5, #0] @ Clear current thread pointer + B _tx_thread_schedule @ Jump to scheduler! +@ +@} + diff --git a/ports/cortex_r52/gnu/src/tx_thread_vectored_context_save.S b/ports/cortex_r52/gnu/src/tx_thread_vectored_context_save.S new file mode 100644 index 00000000..60eeca7b --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_vectored_context_save.S @@ -0,0 +1,181 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * 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. +@ * +@ * SPDX-License-Identifier: MIT +@ **************************************************************************/ +@ Portions of this file were generated with AI assistance. +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** ThreadX Component */ +@/** */ +@/** Thread */ +@/** */ +@/**************************************************************************/ +@/**************************************************************************/ +#ifdef TX_INCLUDE_USER_DEFINE_FILE +#include "tx_user.h" +#endif + + .global _tx_thread_system_state + .global _tx_thread_current_ptr + .global _tx_execution_isr_enter +@ +@ +@ +@/* No 16-bit Thumb mode veneer code is needed for _tx_thread_vectored_context_save +@ since it will never be called 16-bit mode. */ +@ + .arm + .text + .align 2 +@/**************************************************************************/ +@/* */ +@/* FUNCTION RELEASE */ +@/* */ +@/* _tx_thread_vectored_context_save Cortex-R52/GNU */ +@/* 6.5.2 */ +@/* AUTHOR */ +@/* */ +@/* Frédéric Desbiens, Eclipse Foundation */ +@/* */ +@/* Derived from the Cortex-R5/GNU port originally written by */ +@/* William E. Lamie, Microsoft Corporation. */ +@/* */ +@/* DESCRIPTION */ +@/* */ +@/* This function saves the context of an executing thread in the */ +@/* beginning of interrupt processing. The function also ensures that */ +@/* the system stack is used upon return to the calling ISR. */ +@/* */ +@/* INPUT */ +@/* */ +@/* None */ +@/* */ +@/* OUTPUT */ +@/* */ +@/* None */ +@/* */ +@/* CALLS */ +@/* */ +@/* None */ +@/* */ +@/* CALLED BY */ +@/* */ +@/* ISRs */ +@/* */ +@/**************************************************************************/ +@VOID _tx_thread_vectored_context_save(VOID) +@{ + .global _tx_thread_vectored_context_save + .type _tx_thread_vectored_context_save,function +_tx_thread_vectored_context_save: +@ +@ /* Upon entry to this routine, it is assumed that IRQ interrupts are locked +@ out, we are in IRQ mode, and all registers are intact. */ +@ +@ /* Check for a nested interrupt condition. */ +@ if (_tx_thread_system_state++) +@ { +@ +#ifdef TX_ENABLE_FIQ_SUPPORT + CPSID if @ Disable IRQ and FIQ interrupts +#endif + LDR r3, =_tx_thread_system_state @ Pickup address of system state variable + LDR r2, [r3, #0] @ Pickup system state + CMP r2, #0 @ Is this the first interrupt? + BEQ __tx_thread_not_nested_save @ Yes, not a nested context save +@ +@ /* Nested interrupt condition. */ +@ + ADD r2, r2, #1 @ Increment the interrupt counter + STR r2, [r3, #0] @ Store it back in the variable +@ +@ /* Note: Minimal context of interrupted thread is already saved. */ +@ +@ /* Return to the ISR. */ +@ + MOV r10, #0 @ Clear stack limit + +#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY +@ +@ /* Call the ISR enter function to indicate an ISR is executing. */ +@ + PUSH {lr} @ Save ISR lr + BL _tx_execution_isr_enter @ Call the ISR enter function + POP {lr} @ Recover ISR lr +#endif + + MOV pc, lr @ Return to caller +@ +__tx_thread_not_nested_save: +@ } +@ +@ /* Otherwise, not nested, check to see if a thread was running. */ +@ else if (_tx_thread_current_ptr) +@ { +@ + ADD r2, r2, #1 @ Increment the interrupt counter + STR r2, [r3, #0] @ Store it back in the variable + LDR r1, =_tx_thread_current_ptr @ Pickup address of current thread ptr + LDR r0, [r1, #0] @ Pickup current thread pointer + CMP r0, #0 @ Is it NULL? + BEQ __tx_thread_idle_system_save @ If so, interrupt occurred in + @ scheduling loop - nothing needs saving! +@ +@ /* Note: Minimal context of interrupted thread is already saved. */ +@ +@ /* Save the current stack pointer in the thread's control block. */ +@ _tx_thread_current_ptr -> tx_thread_stack_ptr = sp; +@ +@ /* Switch to the system stack. */ +@ sp = _tx_thread_system_stack_ptr; +@ + MOV r10, #0 @ Clear stack limit + +#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY +@ +@ /* Call the ISR enter function to indicate an ISR is executing. */ +@ + PUSH {lr} @ Save ISR lr + BL _tx_execution_isr_enter @ Call the ISR enter function + POP {lr} @ Recover ISR lr +#endif + + MOV pc, lr @ Return to caller +@ +@ } +@ else +@ { +@ +__tx_thread_idle_system_save: +@ +@ /* Interrupt occurred in the scheduling loop. */ +@ +@ /* Not much to do here, just adjust the stack pointer, and return to IRQ +@ processing. */ +@ + MOV r10, #0 @ Clear stack limit + +#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY +@ +@ /* Call the ISR enter function to indicate an ISR is executing. */ +@ + PUSH {lr} @ Save ISR lr + BL _tx_execution_isr_enter @ Call the ISR enter function + POP {lr} @ Recover ISR lr +#endif + + ADD sp, sp, #32 @ Recover saved registers + MOV pc, lr @ Return to caller +@ +@ } +@} + diff --git a/ports/cortex_r52/gnu/src/tx_timer_interrupt.S b/ports/cortex_r52/gnu/src/tx_timer_interrupt.S new file mode 100644 index 00000000..e70a4d02 --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_timer_interrupt.S @@ -0,0 +1,270 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * 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. +@ * +@ * SPDX-License-Identifier: MIT +@ **************************************************************************/ +@ Portions of this file were generated with AI assistance. +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** ThreadX Component */ +@/** */ +@/** Timer */ +@/** */ +@/**************************************************************************/ +@/**************************************************************************/ +#ifdef TX_INCLUDE_USER_DEFINE_FILE +#include "tx_user.h" +#endif + + .arm + +@ +@/* Define Assembly language external references... */ +@ + .global _tx_timer_time_slice + .global _tx_timer_system_clock + .global _tx_timer_current_ptr + .global _tx_timer_list_start + .global _tx_timer_list_end + .global _tx_timer_expired_time_slice + .global _tx_timer_expired + .global _tx_thread_time_slice +@ +@ +@ +@/* Define the 16-bit Thumb mode veneer for _tx_timer_interrupt for +@ applications calling this function from to 16-bit Thumb mode. */ +@ + .text + .align 2 + .thumb + .global $_tx_timer_interrupt + .type $_tx_timer_interrupt,function +$_tx_timer_interrupt: + BX pc @ Switch to 32-bit mode + NOP @ + .arm + STMFD sp!, {lr} @ Save return address + BL _tx_timer_interrupt @ Call _tx_timer_interrupt function + LDMFD sp!, {lr} @ Recover saved return address + BX lr @ Return to 16-bit caller +@ +@ + .text + .align 2 +@/**************************************************************************/ +@/* */ +@/* FUNCTION RELEASE */ +@/* */ +@/* _tx_timer_interrupt Cortex-R52/GNU */ +@/* 6.5.2 */ +@/* AUTHOR */ +@/* */ +@/* Frédéric Desbiens, Eclipse Foundation */ +@/* */ +@/* Derived from the Cortex-R5/GNU port originally written by */ +@/* William E. Lamie, Microsoft Corporation. */ +@/* */ +@/* DESCRIPTION */ +@/* */ +@/* This function processes the hardware timer interrupt. This */ +@/* processing includes incrementing the system clock and checking for */ +@/* time slice and/or timer expiration. If either is found, the */ +@/* interrupt context save/restore functions are called along with the */ +@/* expiration functions. */ +@/* */ +@/* INPUT */ +@/* */ +@/* None */ +@/* */ +@/* OUTPUT */ +@/* */ +@/* None */ +@/* */ +@/* CALLS */ +@/* */ +@/* _tx_thread_time_slice Time slice interrupted thread */ +@/* _tx_timer_expiration_process Timer expiration processing */ +@/* */ +@/* CALLED BY */ +@/* */ +@/* interrupt vector */ +@/* */ +@/**************************************************************************/ +@VOID _tx_timer_interrupt(VOID) +@{ + .global _tx_timer_interrupt + .type _tx_timer_interrupt,function +_tx_timer_interrupt: +@ +@ /* Upon entry to this routine, it is assumed that context save has already +@ been called, and therefore the compiler scratch registers are available +@ for use. */ +@ +@ /* Increment the system clock. */ +@ _tx_timer_system_clock++; +@ + LDR r1, =_tx_timer_system_clock @ Pickup address of system clock + LDR r0, [r1] @ Pickup system clock + ADD r0, r0, #1 @ Increment system clock + STR r0, [r1] @ Store new system clock +@ +@ /* Test for time-slice expiration. */ +@ if (_tx_timer_time_slice) +@ { +@ + LDR r3, =_tx_timer_time_slice @ Pickup address of time-slice + LDR r2, [r3] @ Pickup time-slice + CMP r2, #0 @ Is it non-active? + BEQ __tx_timer_no_time_slice @ Yes, skip time-slice processing +@ +@ /* Decrement the time_slice. */ +@ _tx_timer_time_slice--; +@ + SUB r2, r2, #1 @ Decrement the time-slice + STR r2, [r3] @ Store new time-slice value +@ +@ /* Check for expiration. */ +@ if (__tx_timer_time_slice == 0) +@ + CMP r2, #0 @ Has it expired? + BNE __tx_timer_no_time_slice @ No, skip expiration processing +@ +@ /* Set the time-slice expired flag. */ +@ _tx_timer_expired_time_slice = TX_TRUE; +@ + LDR r3, =_tx_timer_expired_time_slice @ Pickup address of expired flag + MOV r0, #1 @ Build expired value + STR r0, [r3] @ Set time-slice expiration flag +@ +@ } +@ +__tx_timer_no_time_slice: +@ +@ /* Test for timer expiration. */ +@ if (*_tx_timer_current_ptr) +@ { +@ + LDR r1, =_tx_timer_current_ptr @ Pickup current timer pointer address + LDR r0, [r1] @ Pickup current timer + LDR r2, [r0] @ Pickup timer list entry + CMP r2, #0 @ Is there anything in the list? + BEQ __tx_timer_no_timer @ No, just increment the timer +@ +@ /* Set expiration flag. */ +@ _tx_timer_expired = TX_TRUE; +@ + LDR r3, =_tx_timer_expired @ Pickup expiration flag address + MOV r2, #1 @ Build expired value + STR r2, [r3] @ Set expired flag + B __tx_timer_done @ Finished timer processing +@ +@ } +@ else +@ { +__tx_timer_no_timer: +@ +@ /* No timer expired, increment the timer pointer. */ +@ _tx_timer_current_ptr++; +@ + ADD r0, r0, #4 @ Move to next timer +@ +@ /* Check for wraparound. */ +@ if (_tx_timer_current_ptr == _tx_timer_list_end) +@ + LDR r3, =_tx_timer_list_end @ Pickup address of timer list end + LDR r2, [r3] @ Pickup list end + CMP r0, r2 @ Are we at list end? + BNE __tx_timer_skip_wrap @ No, skip wraparound logic +@ +@ /* Wrap to beginning of list. */ +@ _tx_timer_current_ptr = _tx_timer_list_start; +@ + LDR r3, =_tx_timer_list_start @ Pickup address of timer list start + LDR r0, [r3] @ Set current pointer to list start +@ +__tx_timer_skip_wrap: +@ + STR r0, [r1] @ Store new current timer pointer +@ } +@ +__tx_timer_done: +@ +@ +@ /* See if anything has expired. */ +@ if ((_tx_timer_expired_time_slice) || (_tx_timer_expired)) +@ { +@ + LDR r3, =_tx_timer_expired_time_slice @ Pickup address of expired flag + LDR r2, [r3] @ Pickup time-slice expired flag + CMP r2, #0 @ Did a time-slice expire? + BNE __tx_something_expired @ If non-zero, time-slice expired + LDR r1, =_tx_timer_expired @ Pickup address of other expired flag + LDR r0, [r1] @ Pickup timer expired flag + CMP r0, #0 @ Did a timer expire? + BEQ __tx_timer_nothing_expired @ No, nothing expired +@ +__tx_something_expired: +@ +@ + STMDB sp!, {r0, lr} @ Save the lr register on the stack + @ and save r0 just to keep 8-byte alignment +@ +@ /* Did a timer expire? */ +@ if (_tx_timer_expired) +@ { +@ + LDR r1, =_tx_timer_expired @ Pickup address of expired flag + LDR r0, [r1] @ Pickup timer expired flag + CMP r0, #0 @ Check for timer expiration + BEQ __tx_timer_dont_activate @ If not set, skip timer activation +@ +@ /* Process timer expiration. */ +@ _tx_timer_expiration_process(); +@ + BL _tx_timer_expiration_process @ Call the timer expiration handling routine +@ +@ } +__tx_timer_dont_activate: +@ +@ /* Did time slice expire? */ +@ if (_tx_timer_expired_time_slice) +@ { +@ + LDR r3, =_tx_timer_expired_time_slice @ Pickup address of time-slice expired + LDR r2, [r3] @ Pickup the actual flag + CMP r2, #0 @ See if the flag is set + BEQ __tx_timer_not_ts_expiration @ No, skip time-slice processing +@ +@ /* Time slice interrupted thread. */ +@ _tx_thread_time_slice(); +@ + BL _tx_thread_time_slice @ Call time-slice processing +@ +@ } +@ +__tx_timer_not_ts_expiration: +@ + LDMIA sp!, {r0, lr} @ Recover lr register (r0 is just there for + @ the 8-byte stack alignment +@ +@ } +@ +__tx_timer_nothing_expired: +@ +#ifdef __THUMB_INTERWORK + BX lr @ Return to caller +#else + MOV pc, lr @ Return to caller +#endif +@ +@} + |
