diff --git a/.gitignore b/.gitignore index 47438c97a..6054dbead 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,10 @@ CTestTestfile.cmake # Local build artifacts build_m7/ .codex +build_r52/ +__pycache__/ +*.pyc +build_r52_vfp/ +build_r52_uart/ +build_r52_mpu/ +build_r52_all/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f6be6d25..956f2f101 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,13 @@ endif() option(THREADX_SMP "Build ThreadX SMP version" OFF) +# Enable testing at the top level so that tests registered by ports and +# example builds further down the tree are discoverable with ctest from the +# build root. Without this, add_test() in a subdirectory still generates a +# CTestTestfile.cmake there, but the root does not reference it and +# "ctest --test-dir " reports no tests at all. +enable_testing() + if(THREADX_SMP) set(TX_PORT_DIR "ports_smp") set(TX_COMMON_DIR "common_smp") diff --git a/cmake/cortex_r52.cmake b/cmake/cortex_r52.cmake new file mode 100644 index 000000000..07ba935c4 --- /dev/null +++ b/cmake/cortex_r52.cmake @@ -0,0 +1,45 @@ +# Copyright (c) 2026-present Eclipse ThreadX contributors +# SPDX-License-Identifier: MIT +# Some portions generated by Claude Code (Opus 5). +# +# Toolchain file for Arm Cortex-R52 (Armv8-R, AArch32) using GNU tools. + +# Name of the target +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR cortex-r52) + +set(THREADX_ARCH "cortex_r52") +set(THREADX_TOOLCHAIN "gnu") + +# Floating-point ABI. Cortex-R52 always implements at least a single-precision +# FPU -- GCC rejects "+nofp" for this core and offers only "+nofp.dp" (drop +# double precision). There is therefore no FPU-less R52, so the soft-float +# baseline selects the soft *ABI* rather than removing the FPU. The hard-float +# variant with lazy VFP context save arrives with AR1 milestone M5. +if(NOT DEFINED TX_R52_FLOAT_ABI) + set(TX_R52_FLOAT_ABI "soft" CACHE STRING "R52 float ABI: soft | hard") +endif() + +set(MCPU_FLAGS "-marm -mcpu=cortex-r52") +if(TX_R52_FLOAT_ABI STREQUAL "hard") + set(VFP_FLAGS "-mfpu=fpv5-d16 -mfloat-abi=hard") +else() + set(VFP_FLAGS "-mfloat-abi=soft") +endif() +set(SPEC_FLAGS "--specs=nosys.specs") + +include(${CMAKE_CURRENT_LIST_DIR}/arm-none-eabi.cmake) + +# Pin the reference cross toolchain (see AGENTS.md, "Compiler"). Absolute paths +# are used deliberately so the build does not depend on PATH ordering. Override +# with -DARM_TOOLCHAIN_PATH= to build with a +# different compiler -- for example the advisory newest-compiler lane. +if(NOT DEFINED ARM_TOOLCHAIN_PATH) + set(ARM_TOOLCHAIN_PATH + "$ENV{HOME}/toolchains/arm-gnu-toolchain-14.3.rel1-x86_64-arm-none-eabi/bin") +endif() +if(EXISTS "${ARM_TOOLCHAIN_PATH}/arm-none-eabi-gcc") + set(CMAKE_C_COMPILER "${ARM_TOOLCHAIN_PATH}/arm-none-eabi-gcc") + set(CMAKE_CXX_COMPILER "${ARM_TOOLCHAIN_PATH}/arm-none-eabi-g++") + set(CMAKE_ASM_COMPILER "${ARM_TOOLCHAIN_PATH}/arm-none-eabi-gcc") +endif() diff --git a/ports/cortex_r52/gnu/CMakeLists.txt b/ports/cortex_r52/gnu/CMakeLists.txt new file mode 100644 index 000000000..38696814b --- /dev/null +++ b/ports/cortex_r52/gnu/CMakeLists.txt @@ -0,0 +1,77 @@ +# Copyright (c) 2026-present Eclipse ThreadX contributors +# SPDX-License-Identifier: MIT +# Some portions generated by Claude Code (Opus 5). + +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} + 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) + target_compile_definitions(${PROJECT_NAME} PUBLIC TX_ENABLE_VFP_SUPPORT) + if(NOT TX_R52_FLOAT_ABI STREQUAL "hard") + message(WARNING + "TX_R52_ENABLE_VFP is on but TX_R52_FLOAT_ABI is '${TX_R52_FLOAT_ABI}'. " + "The compiler will not emit floating-point instructions, so the VFP " + "context path will never be exercised. Use -DTX_R52_FLOAT_ABI=hard.") + endif() +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() 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 000000000..dc49de74a --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt @@ -0,0 +1,289 @@ +# Copyright (c) 2026-present Eclipse ThreadX contributors +# SPDX-License-Identifier: MIT +# Some portions generated by Claude Code (Opus 5). +# +# Example builds for Cortex-R52 on the Armv8-R AEM FVP (FVP_BaseR_AEMv8R). + +set(FVP_DIR ${CMAKE_CURRENT_LIST_DIR}) + +# 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 + # A bare-metal image has one flat DRAM region and no OS page permissions; + # access control belongs to the MPU, so the RWX-segment note is expected. + -Wl,--no-warn-rwx-segments +) + +# 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 + -Wl,--no-warn-rwx-segments +) + +# 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 + -Wl,--no-warn-rwx-segments +) + +# 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 + -Wl,--no-warn-rwx-segments +) + +# 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 + -Wl,--no-warn-rwx-segments + ) +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 + -Wl,--no-warn-rwx-segments +) + +# 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) + +# 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() + +# 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() + +# 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 $ + 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...") + + # 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}) + + enable_testing() + foreach(image IN LISTS R52_TEST_IMAGES) + add_test(NAME r52-fvp-${image} + COMMAND ${Python3_EXECUTABLE} ${R52_RUNNER} + --elf $ + --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 $ + --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 000000000..ab25f4b80 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/board.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 */ +/* */ +/* board.h Cortex-R52/GNU */ +/* */ +/* 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 once EL1 is set up. 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); + +/* 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; + +/* 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 000000000..3e1af9a36 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/bsp_boot.c @@ -0,0 +1,166 @@ +/*************************************************************************** + * 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 */ +/* */ +/* 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/console.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/console.c new file mode 100644 index 000000000..9900eb996 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/console.c @@ -0,0 +1,166 @@ +/*************************************************************************** + * 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 */ +/* */ +/* 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 000000000..beda4d374 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/console.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 */ +/* */ +/* console.h Cortex-R52/GNU */ +/* */ +/* 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_m2.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_m2.c new file mode 100644 index 000000000..6cc302c1a --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_m2.c @@ -0,0 +1,220 @@ +/*************************************************************************** + * 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 */ +/* */ +/* 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 000000000..4286a8448 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_m3.c @@ -0,0 +1,245 @@ +/*************************************************************************** + * 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 */ +/* */ +/* 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 000000000..53004aaa2 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_m5.c @@ -0,0 +1,280 @@ +/*************************************************************************** + * 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 */ +/* */ +/* 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 000000000..f6c16fe6b --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_mpu.c @@ -0,0 +1,241 @@ +/*************************************************************************** + * 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 */ +/* */ +/* 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; + + +/**************************************************************************/ +/* 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); + } + + /* 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_threadx.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_threadx.c new file mode 100644 index 000000000..8dd9a4d42 --- /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 */ +/***************************************************************************/ +// Some portions generated by Claude Code (Opus 5). + +/* 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 000000000..5b6e3741d --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_verify.c @@ -0,0 +1,181 @@ +/*************************************************************************** + * 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 */ +/* */ +/* 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 000000000..5086aae6b --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_verify.h @@ -0,0 +1,37 @@ +/*************************************************************************** + * 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 */ +/* */ +/* 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 000000000..c7208db3c --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/entry.S @@ -0,0 +1,482 @@ +/*************************************************************************** + * 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 */ +/* */ +/* 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" + + .arch armv8-r + .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 */ + +/* 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 */ + b el1_trap_svc /* 0x08 supervisor call */ + 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 + ldr r0, =(PSR_A | PSR_I | PSR_F | MODE_SVC) + 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: + bl board_irq_handler + 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 +el1_trap_pabt: FAULT_REPORT msg_el1_pabt + +/* 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 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: FAULT_REPORT msg_el1_fiq + +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 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 000000000..3a6536654 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/gicv3.c @@ -0,0 +1,211 @@ +/*************************************************************************** + * 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 */ +/* */ +/* 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_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_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; + + /* 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); + 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 */ +/**************************************************************************/ + +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 000000000..4f758b20f --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/gicv3.h @@ -0,0 +1,57 @@ +/*************************************************************************** + * 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 */ +/* */ +/* 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); + +/* 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 000000000..985b2621b --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/irq_dispatch.c @@ -0,0 +1,109 @@ +/*************************************************************************** + * 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 */ +/* */ +/* 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; + + +/* 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); + timer_init(); +} + + +/**************************************************************************/ +/* board_irq_handler */ +/**************************************************************************/ + +void board_irq_handler(void) +{ + unsigned long intid = gicv3_acknowledge(); + + if (intid == GICV3_SPURIOUS_INTID) + { + /* No interrupt was actually pending; must not be acknowledged. */ + + board_spurious_count++; + return; + } + + 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(); + _tx_timer_interrupt(); + } + else + { + board_unexpected_intid = intid; + } + + gicv3_end_of_interrupt(intid); +} 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 000000000..3522cb619 --- /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 000000000..2ac714f0e --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/mpu.c @@ -0,0 +1,349 @@ +/*************************************************************************** + * 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 */ +/* */ +/* 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], SH[5:4], AP[3:2], XN[1]. */ + + prbar = (region_ptr->mpu_region_base & 0xFFFFFFC0UL) + | (((unsigned long) region_ptr->mpu_region_shareability & 0x3UL) << 4) + | (((unsigned long) region_ptr->mpu_region_ap & 0x3UL) << 2) + | (((unsigned long) region_ptr->mpu_region_execute_never & 0x1UL) << 1); + + 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_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; + } + + /* 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); + } + + 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 000000000..6567d1eea --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/mpu.h @@ -0,0 +1,109 @@ +/*************************************************************************** + * 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 */ +/* */ +/* 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. + */ + +#define MPU_AP_RW_EL1 0U /* EL1 read/write, EL0 no access */ +#define MPU_AP_RO_EL1 1U /* EL1 read-only, EL0 no access */ +#define MPU_AP_RW_EL1_EL0 2U /* EL1 read/write, EL0 read/write */ +#define MPU_AP_RO_EL1_EL0 3U /* 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); + +#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 000000000..cdb1fa292 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/platform.h @@ -0,0 +1,78 @@ +/*************************************************************************** + * 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 */ +/* */ +/* 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 + +#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 000000000..f47e42289 --- /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-present Eclipse ThreadX contributors +# SPDX-License-Identifier: MIT +# Some portions generated by Claude Code (Opus 5). +# +"""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 000000000..c0e406bf2 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/timer.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 */ +/* */ +/* timer.c Cortex-R52/GNU */ +/* */ +/* 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 000000000..1eca96c44 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/timer.h @@ -0,0 +1,63 @@ +/*************************************************************************** + * 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 */ +/* */ +/* 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 000000000..fcdd47b5c --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/tx_initialize_low_level.S @@ -0,0 +1,98 @@ +/*************************************************************************** + * 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 */ +/** */ +/**************************************************************************/ +/**************************************************************************/ + + .arch armv8-r + .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 */ +/* */ +/* 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 000000000..762cc7d81 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/uart_pl011.c @@ -0,0 +1,123 @@ +/*************************************************************************** + * 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 */ +/* */ +/* 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 an integer address to a volatile pointer 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 000000000..c550d9bb4 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/uart_pl011.h @@ -0,0 +1,43 @@ +/*************************************************************************** + * 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 */ +/* */ +/* 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/inc/tx_port.h b/ports/cortex_r52/gnu/inc/tx_port.h new file mode 100644 index 000000000..188b5e58d --- /dev/null +++ b/ports/cortex_r52/gnu/inc/tx_port.h @@ -0,0 +1,335 @@ +/*************************************************************************** + * Copyright (c) 2024 Microsoft Corporation + * Copyright (c) 2026-present 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 + **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). + + +/**************************************************************************/ +/**************************************************************************/ +/** */ +/** ThreadX Component */ +/** */ +/** Port Specific */ +/** */ +/**************************************************************************/ +/**************************************************************************/ + + +/**************************************************************************/ +/* */ +/* PORT SPECIFIC C INFORMATION RELEASE */ +/* */ +/* tx_port.h Cortex-R52/GNU */ +/* 6.1.12 */ +/* */ +/* AUTHOR */ +/* */ +/* 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 +#include + + +/* 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 the 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 the 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 if the ARM architecture has the CLZ instruction. This is available on + architectures v5 and above. If available, redefine the macro for calculating the + lowest bit set. */ + +#if __TARGET_ARCH_ARM > 4 + +#ifndef __thumb__ + +#define TX_LOWEST_SET_BIT_CALCULATE(m, b) m = m & ((ULONG) (-((LONG) m))); \ + 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 utilized 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.1.202602a *"; +#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 000000000..ccc2c8d60 --- /dev/null +++ b/ports/cortex_r52/gnu/readme_threadx.txt @@ -0,0 +1,172 @@ + 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= + +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 + -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. Pair it with +TX_R52_FLOAT_ABI=hard, otherwise the compiler emits no floating-point +instructions and the VFP path is never exercised. + +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 + 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_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. + + +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 used here was calibrated against the hardware. + The low bit is read-only and the high bit grants EL0 access, which is + the reverse of the widely-published Armv8-R AArch64 macro set. Getting + it wrong produces regions that report as read-only and accept writes, + because region coverage is still enforced: an unmapped address faults + while a "read-only" region does not. Re-calibrate before trusting + these values on a different implementation. + + - 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 000000000..dfacaf89b --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_port_offset_check.c @@ -0,0 +1,82 @@ +/*************************************************************************** + * 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 */ +/* */ +/* 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 + +/* 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 000000000..d4480c991 --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_context_restore.S @@ -0,0 +1,244 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * Copyright (c) 2026-present 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 +@ **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** 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.1 */ +@/* AUTHOR */ +@/* */ +@/* 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 000000000..2547b5b0d --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_context_save.S @@ -0,0 +1,190 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * Copyright (c) 2026-present 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 +@ **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** 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.1 */ +@/* AUTHOR */ +@/* */ +@/* 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 000000000..f9eff1cef --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_fiq_context_restore.S @@ -0,0 +1,234 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * Copyright (c) 2026-present 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 +@ **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** 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.1 */ +@/* AUTHOR */ +@/* */ +@/* 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 000000000..72aa8f2ab --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_fiq_context_save.S @@ -0,0 +1,192 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * Copyright (c) 2026-present 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 +@ **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** 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.1 */ +@/* AUTHOR */ +@/* */ +@/* 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 000000000..450b5d00c --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_fiq_nesting_end.S @@ -0,0 +1,104 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * Copyright (c) 2026-present 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 +@ **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** 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.1 */ +@/* AUTHOR */ +@/* */ +@/* 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 000000000..0a64f3042 --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_fiq_nesting_start.S @@ -0,0 +1,96 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * Copyright (c) 2026-present 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 +@ **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** 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.1 */ +@/* AUTHOR */ +@/* */ +@/* 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 000000000..68b105ff0 --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_interrupt_control.S @@ -0,0 +1,105 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * Copyright (c) 2026-present 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 +@ **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** 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.1 */ +@/* AUTHOR */ +@/* */ +@/* 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 000000000..0798c7628 --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_interrupt_disable.S @@ -0,0 +1,102 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * Copyright (c) 2026-present 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 +@ **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** 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.1 */ +@/* AUTHOR */ +@/* */ +@/* 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 000000000..f08fafc3e --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_interrupt_restore.S @@ -0,0 +1,93 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * Copyright (c) 2026-present 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 +@ **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** 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.1 */ +@/* AUTHOR */ +@/* */ +@/* 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 000000000..51c6e2aeb --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_irq_nesting_end.S @@ -0,0 +1,103 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * Copyright (c) 2026-present 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 +@ **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** 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.1 */ +@/* AUTHOR */ +@/* */ +@/* 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 000000000..ca3be1648 --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_irq_nesting_start.S @@ -0,0 +1,96 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * Copyright (c) 2026-present 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 +@ **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** 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.1 */ +@/* AUTHOR */ +@/* */ +@/* 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 000000000..a8404bb60 --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_schedule.S @@ -0,0 +1,237 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * Copyright (c) 2026-present 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 +@ **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** 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.1 */ +@/* AUTHOR */ +@/* */ +@/* 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 000000000..0fd2aae31 --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_stack_build.S @@ -0,0 +1,167 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * Copyright (c) 2026-present 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 +@ **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** 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.1 */ +@/* AUTHOR */ +@/* */ +@/* 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 000000000..8f220c06c --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_system_return.S @@ -0,0 +1,166 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * Copyright (c) 2026-present 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 +@ **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** 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.1 */ +@/* AUTHOR */ +@/* */ +@/* 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 000000000..7353066fb --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_thread_vectored_context_save.S @@ -0,0 +1,178 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * Copyright (c) 2026-present 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 +@ **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** 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.1 */ +@/* AUTHOR */ +@/* */ +@/* 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 000000000..e00251bd5 --- /dev/null +++ b/ports/cortex_r52/gnu/src/tx_timer_interrupt.S @@ -0,0 +1,267 @@ +@/*************************************************************************** +@ * Copyright (c) 2024 Microsoft Corporation +@ * Copyright (c) 2026-present 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 +@ **************************************************************************/ +// Some portions generated by Claude Code (Opus 5). +@ +@ +@/**************************************************************************/ +@/**************************************************************************/ +@/** */ +@/** 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.1 */ +@/* AUTHOR */ +@/* */ +@/* 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 +@ +@} +