From 66f11bfc98f0608c6bc506c09a39cdb785eec131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Wed, 5 Aug 2026 16:13:05 -0400 Subject: [PATCH 01/10] Added Cortex-R52 port skeleton and CMake build Seeded ports/cortex_r52/gnu from the Cortex-R5 GNU port: 16 assembly sources plus tx_port.h, with the release banners retargeted to Cortex-R52/GNU. All 16 sources assemble unchanged for -mcpu=cortex-r52 in the soft-float, +nofp.dp and hard-float variants, so the AArch32 banked-mode context-switch core carries over from Armv7-R as-is; the Armv8-R semantic work (EL2, PMSAv8-R, GICv3, generic timer) lands in later milestones. Added cmake/cortex_r52.cmake and ports/cortex_r52/gnu/CMakeLists.txt. This is the first R-profile port in the tree with a working CMake+Ninja build: cmake/cortex_a9.cmake exists but ports/cortex_a9/gnu/CMakeLists.txt is empty, so no A- or R-profile port could be built this way before. The toolchain file pins the reference cross compiler by absolute path (arm-none-eabi 14.3.rel1) so the build does not depend on PATH ordering, and honours -DARM_TOOLCHAIN_PATH for other compilers. Cortex-R52 always implements at least a single-precision FPU -- GCC rejects "+nofp" for this core and offers only "+nofp.dp" -- so the soft-float baseline selects the soft ABI rather than removing the FPU. Verified: libthreadx.a builds with zero warnings and the port objects report Tag_CPU_arch v8-R with profile Realtime. Assisted-by: Claude Code (Opus 5) --- .gitignore | 1 + cmake/cortex_r52.cmake | 45 +++ ports/cortex_r52/gnu/CMakeLists.txt | 30 ++ ports/cortex_r52/gnu/inc/tx_port.h | 311 ++++++++++++++++++ .../gnu/src/tx_thread_context_restore.S | 244 ++++++++++++++ .../gnu/src/tx_thread_context_save.S | 190 +++++++++++ .../gnu/src/tx_thread_fiq_context_restore.S | 234 +++++++++++++ .../gnu/src/tx_thread_fiq_context_save.S | 192 +++++++++++ .../gnu/src/tx_thread_fiq_nesting_end.S | 104 ++++++ .../gnu/src/tx_thread_fiq_nesting_start.S | 96 ++++++ .../gnu/src/tx_thread_interrupt_control.S | 105 ++++++ .../gnu/src/tx_thread_interrupt_disable.S | 102 ++++++ .../gnu/src/tx_thread_interrupt_restore.S | 93 ++++++ .../gnu/src/tx_thread_irq_nesting_end.S | 103 ++++++ .../gnu/src/tx_thread_irq_nesting_start.S | 96 ++++++ ports/cortex_r52/gnu/src/tx_thread_schedule.S | 237 +++++++++++++ .../gnu/src/tx_thread_stack_build.S | 167 ++++++++++ .../gnu/src/tx_thread_system_return.S | 166 ++++++++++ .../gnu/src/tx_thread_vectored_context_save.S | 178 ++++++++++ ports/cortex_r52/gnu/src/tx_timer_interrupt.S | 267 +++++++++++++++ 20 files changed, 2961 insertions(+) create mode 100644 cmake/cortex_r52.cmake create mode 100644 ports/cortex_r52/gnu/CMakeLists.txt create mode 100644 ports/cortex_r52/gnu/inc/tx_port.h create mode 100644 ports/cortex_r52/gnu/src/tx_thread_context_restore.S create mode 100644 ports/cortex_r52/gnu/src/tx_thread_context_save.S create mode 100644 ports/cortex_r52/gnu/src/tx_thread_fiq_context_restore.S create mode 100644 ports/cortex_r52/gnu/src/tx_thread_fiq_context_save.S create mode 100644 ports/cortex_r52/gnu/src/tx_thread_fiq_nesting_end.S create mode 100644 ports/cortex_r52/gnu/src/tx_thread_fiq_nesting_start.S create mode 100644 ports/cortex_r52/gnu/src/tx_thread_interrupt_control.S create mode 100644 ports/cortex_r52/gnu/src/tx_thread_interrupt_disable.S create mode 100644 ports/cortex_r52/gnu/src/tx_thread_interrupt_restore.S create mode 100644 ports/cortex_r52/gnu/src/tx_thread_irq_nesting_end.S create mode 100644 ports/cortex_r52/gnu/src/tx_thread_irq_nesting_start.S create mode 100644 ports/cortex_r52/gnu/src/tx_thread_schedule.S create mode 100644 ports/cortex_r52/gnu/src/tx_thread_stack_build.S create mode 100644 ports/cortex_r52/gnu/src/tx_thread_system_return.S create mode 100644 ports/cortex_r52/gnu/src/tx_thread_vectored_context_save.S create mode 100644 ports/cortex_r52/gnu/src/tx_timer_interrupt.S diff --git a/.gitignore b/.gitignore index 47438c97a..41f526390 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ CTestTestfile.cmake # Local build artifacts build_m7/ .codex +build_r52/ 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..b163d40cc --- /dev/null +++ b/ports/cortex_r52/gnu/CMakeLists.txt @@ -0,0 +1,30 @@ +# 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 + # {{END_TARGET_SOURCES}} +) + +target_include_directories(${PROJECT_NAME} + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/inc +) 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..2c39f83bd --- /dev/null +++ b/ports/cortex_r52/gnu/inc/tx_port.h @@ -0,0 +1,311 @@ +/*************************************************************************** + * 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. */ + +#define TX_THREAD_EXTENSION_0 +#define TX_THREAD_EXTENSION_1 +#define TX_THREAD_EXTENSION_2 +#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. */ + +#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/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 +@ +@} + From ab32ec0cbcbda9bda102d9dd45963a4557503e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Wed, 5 Aug 2026 16:13:46 -0400 Subject: [PATCH 02/10] Added Armv8-R AEM FVP boot path and EL2-to-EL1 boot check Cortex-R52 always implements EL2 and resets into it, so entry.S configures EL2 first, installs both the EL2 and EL1 vector tables, and only then drops to EL1 (Supervisor) to run the kernel. Keeping the port EL2-aware from the outset is what lets ZoneX (roadmap AR3) reuse this boot path rather than replace it. TX_R52_BOOT_AT_EL1 skips the EL2 stage for targets where an earlier boot stage or a vendor EL2 monitor has already dropped privilege. Every exception routed to EL2 from EL1/EL0 arrives at the Hyp Trap Entry at vector offset 0x14, not at 0x08: offsets 0x04-0x10 are exceptions taken from Hyp mode itself, and 0x08 is SVC from Hyp. The handler therefore decodes HSR.EC and dispatches, servicing HVC (EC 0x12) and reporting anything else. That single funnel is the seam AR3 grows an HSR.EC dispatch table on. HCR.HCD is cleared explicitly so HVC is enabled rather than relying on its reset value, and HCPTR.TCP10/TCP11 are cleared so EL1 may use the FPU. Images link into low DRAM at 0x0. The BaseR memory map is the Base platform map with its two 2 GB halves swapped, and the high half is not executable by default, so linking at 0x80000000 fault-loops silently with no output. boot_check.elf asserts the resulting exception level rather than merely printing, because AR3 depends on that transition being correct: it reports CPSR mode 0x13 (Supervisor/EL1) after the ERET from EL2 and confirms the EL2 seam is entered and returns to EL1. Every unhandled vector identifies itself over semihosting, because the FVP exposes only an Iris server and no GDB stub, which makes a self-identifying fault the primary debugging tool for this port -- it located the 0x14 vector behaviour above on the first run. Runs terminate themselves through semihosting SYS_EXIT, so the run-boot-check-r52 target needs no host-side timeout. Assisted-by: Claude Code (Opus 5) --- ports/cortex_r52/gnu/CMakeLists.txt | 7 + .../fvp_baser_aemv8r/CMakeLists.txt | 47 +++ .../example_build/fvp_baser_aemv8r/bsp_boot.c | 166 +++++++++ .../example_build/fvp_baser_aemv8r/console.c | 136 ++++++++ .../example_build/fvp_baser_aemv8r/console.h | 47 +++ .../example_build/fvp_baser_aemv8r/entry.S | 329 ++++++++++++++++++ .../example_build/fvp_baser_aemv8r/link.lds | 112 ++++++ 7 files changed, 844 insertions(+) create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/bsp_boot.c create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/console.c create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/console.h create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/entry.S create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/link.lds diff --git a/ports/cortex_r52/gnu/CMakeLists.txt b/ports/cortex_r52/gnu/CMakeLists.txt index b163d40cc..fb33f08af 100644 --- a/ports/cortex_r52/gnu/CMakeLists.txt +++ b/ports/cortex_r52/gnu/CMakeLists.txt @@ -28,3 +28,10 @@ target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_LIST_DIR}/inc ) + +# 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..23816aeb4 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt @@ -0,0 +1,47 @@ +# 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}) + +# 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 + ${FVP_DIR}/console.c + ${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 +) + +# Run the boot check on the FVP. The model exits by itself through the +# semihosting SYS_EXIT call in bsp_main, so no host-side timeout is needed. +find_program(FVP_BASER_AEMV8R FVP_BaseR_AEMv8R + HINTS $ENV{HOME}/FVP_Base_AEMv8R_11.32_19/bin +) +if(FVP_BASER_AEMV8R) + add_custom_target(run-boot-check-r52 + COMMAND ${FVP_BASER_AEMV8R} + -C cluster0.NUM_CORES=1 + -C bp.vis.disable_visualisation=1 + -C bp.terminal_0.start_telnet=0 + -a $ + DEPENDS boot_check.elf + USES_TERMINAL + COMMENT "Running AR1/M1 boot check on FVP_BaseR_AEMv8R..." + ) +else() + message(STATUS "FVP_BaseR_AEMv8R not found; run-boot-check-r52 unavailable.") +endif() 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..97cfdbddb --- /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 "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. */ + +extern volatile unsigned long _hvc_call_count; + + +/**************************************************************************/ +/* 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..ae4c24001 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/console.c @@ -0,0 +1,136 @@ +/*************************************************************************** + * 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" + +/* 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) + { + (void) semihost_call((int) SYS_WRITE0, string_ptr); + } +} + + +/**************************************************************************/ +/* 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 */ +/**************************************************************************/ + +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/entry.S b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/entry.S new file mode 100644 index 000000000..6b8a6b34a --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/entry.S @@ -0,0 +1,329 @@ +/*************************************************************************** + * 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. */ +/* */ +/**************************************************************************/ + + .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 */ + + .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_trap_irq /* 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 + + /* 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 + + /* 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: + + bl bsp_main + + /* bsp_main is not expected to return. */ +_start_hang: + b _start_hang + +/**************************************************************************/ +/* 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 +el1_trap_dabt: FAULT_REPORT msg_el1_dabt +el1_trap_reserved: FAULT_REPORT msg_el1_reserved +el1_trap_irq: FAULT_REPORT msg_el1_irq +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 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..bd11873ac --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/link.lds @@ -0,0 +1,112 @@ +/*************************************************************************** + * 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 +{ + .text : + { + KEEP(*(.vectors_el2)) + KEEP(*(.vectors_el1)) + *(.text*) + *(.glue_7) + *(.glue_7t) + } > DRAM + + .rodata : + { + . = ALIGN(4); + *(.rodata*) + . = ALIGN(4); + } > DRAM + + .data : + { + . = ALIGN(4); + *(.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 = .); +} From 2b67a975fd23766beeece54f80561b91d067ee57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Wed, 5 Aug 2026 16:32:06 -0400 Subject: [PATCH 03/10] Added cooperative two-thread demo on the Armv8-R AEM FVP First execution of the ported context-switch assembly. tx_kernel_enter now runs on Cortex-R52: _tx_thread_stack_build lays down each thread's initial frame, _tx_thread_schedule restores it, and _tx_thread_system_return saves a solicited context on every tx_thread_relinquish. The Armv7-R sources carried over from the Cortex-R5 port work unchanged for this path, which confirms the AArch32 banked-mode core needed no Armv8-R adjustment. Added _tx_initialize_low_level for this board. It is markedly shorter than the A-profile reference ports because entry.S already gives every AArch32 mode its own stack from dedicated linker-script regions: there is no stack carving to redo and no run-time overlap check to make, since the linker guarantees the regions are disjoint. It records the system stack and publishes _end as the first free memory. The periodic timer is left alone deliberately -- the generic timer and GICv3 arrive in M3, and raising a tick before an interrupt path exists would only produce unhandled interrupts. The demo asserts the execution *order*, not just iteration counts: counting alone cannot distinguish working context switches from one thread running to completion by itself, so both threads append a mark to a trace buffer and the expected alternating sequence is checked. Only tx_thread_relinquish is used, as tx_thread_sleep would hang with no tick. Verified on FVP_BaseR_AEMv8R: five slices each, execution order 0101010101, all checks passed, model self-terminating. Assisted-by: Claude Code (Opus 5) --- .../fvp_baser_aemv8r/CMakeLists.txt | 57 +++-- .../example_build/fvp_baser_aemv8r/demo_m2.c | 219 ++++++++++++++++++ .../tx_initialize_low_level.S | 86 +++++++ 3 files changed, 349 insertions(+), 13 deletions(-) create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_m2.c create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/tx_initialize_low_level.S 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 index 23816aeb4..2e9790af0 100644 --- a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt @@ -26,22 +26,53 @@ target_link_options(boot_check.elf PRIVATE -Wl,--no-warn-rwx-segments ) -# Run the boot check on the FVP. The model exits by itself through the -# semihosting SYS_EXIT call in bsp_main, so no host-side timeout is needed. +# 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 + ${FVP_DIR}/console.c + ${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 +) + +# Run a target on the FVP. Each image exits by itself through the +# semihosting SYS_EXIT call, so no host-side timeout is needed. find_program(FVP_BASER_AEMV8R FVP_BaseR_AEMv8R HINTS $ENV{HOME}/FVP_Base_AEMv8R_11.32_19/bin ) if(FVP_BASER_AEMV8R) - add_custom_target(run-boot-check-r52 - COMMAND ${FVP_BASER_AEMV8R} - -C cluster0.NUM_CORES=1 - -C bp.vis.disable_visualisation=1 - -C bp.terminal_0.start_telnet=0 - -a $ - DEPENDS boot_check.elf - USES_TERMINAL - COMMENT "Running AR1/M1 boot check on 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 + -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...") else() - message(STATUS "FVP_BaseR_AEMv8R not found; run-boot-check-r52 unavailable.") + message(STATUS "FVP_BaseR_AEMv8R not found; FVP run targets unavailable.") endif() 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..8218a8a3e --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_m2.c @@ -0,0 +1,219 @@ +/*************************************************************************** + * 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 "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/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..c7a0f5aec --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/tx_initialize_low_level.S @@ -0,0 +1,86 @@ +/*************************************************************************** + * Copyright (C) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +/**************************************************************************/ +/**************************************************************************/ +/** */ +/** ThreadX Component */ +/** */ +/** 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. */ +/* */ +/* The periodic timer interrupt is NOT set up here yet: the generic */ +/* timer and GICv3 arrive in AR1/M3. Cooperative scheduling (M2) */ +/* needs no tick, and creating one before the interrupt path exists */ +/* would only produce unhandled interrupts. */ +/* */ +/* 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] + + bx lr From ab04ac2f23475bb2ba519c39e5217527c4734cca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Wed, 5 Aug 2026 17:10:32 -0400 Subject: [PATCH 04/10] Added generic timer tick, GICv3 and preemptive scheduling Completes the interrupt path: the EL1 IRQ vector now branches into _tx_thread_context_save, which returns to __tx_irq_processing_return, where a C dispatcher acknowledges the interrupt, re-arms the timer, calls _tx_timer_interrupt and ends with _tx_thread_context_restore. _tx_timer_interrupt is safe to call from C: it touches only r0-r3 and returns through lr. TX_R52_USE_THREADX_IRQ selects this wiring so images that do not link ThreadX keep the self-identifying fault reporter. Two model behaviours had to be handled, both established by reading the hardware rather than assumed: - The system counter is stopped at reset (CNTCR = 0, CNTCV = 0). The model documents that firmware is expected to start it, so timer_init enables the counter control frame. Without this the timer never counts and no tick is ever delivered. - CNTFRQ resets to zero and is writable only at the highest implemented exception level, so entry.S programs it at EL2 with the frequency read back from CNTFID0 (100 MHz). Deriving a tick interval from an unprogrammed CNTFRQ would divide by zero. entry.S also sets CNTHCTL.PL1PCTEN/PL1PCEN, without which every EL1 access to CNTPCT or CNTP_* traps to EL2, and ICC_HSRE.SRE/Enable so EL1 may reach the GICv3 CPU interface through its system registers. The timer PPI was identified empirically, not assumed: bring-up enabled the whole PPI range 25-31 and recorded whichever INTID arrived in ICC_IAR1. The model drove 30, matching the architectural EL1 physical timer assignment, so only that line is enabled now while the dispatcher still reports any other INTID. This needs re-verifying on S32Z2 silicon. All peripheral addresses in platform.h were verified in-model by reading identification registers -- GICD_PIDR2 and GICR_PIDR2 both 0x3B (GICv3), PL011 peripheral ID 0x11,0x10,0x24,0x00, CNTFID0 100 MHz -- rather than taken from documentation. GIC setup follows the model's own reported configuration: single security state, affinity routing fixed on, and five implemented priority bits, so priorities are multiples of eight. Verified on FVP_BaseR_AEMv8R: CNTFRQ 100 MHz, counter advancing, 20 timer interrupts for a 20-tick sleep, tx_time_get advancing exactly 20 ticks, and a lower-priority thread observed running during the sleep and then being preempted. M1 and M2 still pass unchanged. Assisted-by: Claude Code (Opus 5) --- .../fvp_baser_aemv8r/CMakeLists.txt | 33 +++ .../example_build/fvp_baser_aemv8r/demo_m3.c | 248 ++++++++++++++++++ .../example_build/fvp_baser_aemv8r/entry.S | 70 ++++- .../example_build/fvp_baser_aemv8r/gicv3.c | 211 +++++++++++++++ .../example_build/fvp_baser_aemv8r/gicv3.h | 57 ++++ .../fvp_baser_aemv8r/irq_dispatch.c | 108 ++++++++ .../example_build/fvp_baser_aemv8r/platform.h | 78 ++++++ .../example_build/fvp_baser_aemv8r/timer.c | 170 ++++++++++++ .../example_build/fvp_baser_aemv8r/timer.h | 63 +++++ .../tx_initialize_low_level.S | 20 +- 10 files changed, 1052 insertions(+), 6 deletions(-) create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_m3.c create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/gicv3.c create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/gicv3.h create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/irq_dispatch.c create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/platform.h create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/timer.c create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/timer.h 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 index 2e9790af0..0ecbcf65c 100644 --- a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt @@ -50,6 +50,37 @@ target_link_options(demo_m2.elf PRIVATE -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 + ${FVP_DIR}/console.c + ${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 +) + # Run a target on the FVP. Each image exits by itself through the # semihosting SYS_EXIT call, so no host-side timeout is needed. find_program(FVP_BASER_AEMV8R FVP_BaseR_AEMv8R @@ -73,6 +104,8 @@ if(FVP_BASER_AEMV8R) "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...") 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/demo_m3.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_m3.c new file mode 100644 index 000000000..bc0f59b5c --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_m3.c @@ -0,0 +1,248 @@ +/*************************************************************************** + * 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 "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. */ + +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; + + +/**************************************************************************/ +/* 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/entry.S b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/entry.S index 6b8a6b34a..f304c6e66 100644 --- a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/entry.S +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/entry.S @@ -35,6 +35,8 @@ /* */ /**************************************************************************/ +#include "platform.h" + .arch armv8-r .syntax unified .arm @@ -64,6 +66,16 @@ .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) + .equ SYS_WRITE0, 0x04 /* semihosting: write NUL-string */ /* HSR (Hyp Syndrome Register) exception classes. Every exception taken to @@ -120,7 +132,7 @@ el1_vectors: b el1_trap_pabt /* 0x0C prefetch abort */ b el1_trap_dabt /* 0x10 data abort */ b el1_trap_reserved /* 0x14 reserved */ - b el1_trap_irq /* 0x18 IRQ */ + b el1_irq_entry /* 0x18 IRQ */ b el1_trap_fiq /* 0x1C FIQ */ .text @@ -169,6 +181,34 @@ _start: 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 @@ -237,6 +277,33 @@ bss_zero_done: _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. */ /* */ @@ -294,7 +361,6 @@ el1_trap_svc: FAULT_REPORT msg_el1_svc el1_trap_pabt: FAULT_REPORT msg_el1_pabt el1_trap_dabt: FAULT_REPORT msg_el1_dabt el1_trap_reserved: FAULT_REPORT msg_el1_reserved -el1_trap_irq: FAULT_REPORT msg_el1_irq el1_trap_fiq: FAULT_REPORT msg_el1_fiq fault_halt: b fault_halt 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..d49e5fd08 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/irq_dispatch.c @@ -0,0 +1,108 @@ +/*************************************************************************** + * 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 "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/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/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 index c7a0f5aec..fcdd47b5c 100644 --- 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 @@ -51,10 +51,12 @@ /* here and no run-time stack-overlap check to perform -- the linker */ /* guarantees the regions do not overlap. */ /* */ -/* The periodic timer interrupt is NOT set up here yet: the generic */ -/* timer and GICv3 arrive in AR1/M3. Cooperative scheduling (M2) */ -/* needs no tick, and creating one before the interrupt path exists */ -/* would only produce unhandled interrupts. */ +/* 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 */ /* */ @@ -83,4 +85,14 @@ _tx_initialize_low_level: 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 From d45475df080c35fb4cc10b1f6908c96294851aa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Wed, 5 Aug 2026 17:31:43 -0400 Subject: [PATCH 05/10] Added standard ThreadX demo and an automated FVP test suite Runs the shipped eight-thread sample on Cortex-R52 and registers every image as a CTest test, so the port now has a regression suite rather than a set of manual runs. demo_threadx.c is samples/demo_threadx.c kept byte-identical apart from a header note, one include and one call into the verification harness. That is the point of the milestone: the standard demo runs on this port with no demo-side changes. The harness lives in demo_verify.c so the sample body stays untouched, and it uses its own static stack so the demo's byte-pool allocations are unperturbed. The harness requires every one of the ten demo counters to have advanced, rather than merely checking the system still runs: a lost wakeup or a mishandled priority inversion would leave one thread's counter at zero while everything else looked healthy. Observed counts are consistent with the demo's design -- thread 0 completed 30 iterations of its 10-tick sleep in 300 ticks, thread 5 tracked it through the event flag, and the threads sleeping 2 ticks reached 75-76 -- which exercises the queue, semaphore, mutex, event flags, byte pool and block pool across five priorities. The test runner is deliberately far simpler than the RISC-V/QEMU one because this platform permits it: the FVP has no GDB stub, only an Iris server, and every image terminates itself through semihosting SYS_EXIT, so there is nothing to script through a debugger. It launches the model, captures the console and judges the self-reported result. A missing result line counts as failure, never success, so a silent hang cannot masquerade as a pass. Its failure paths were exercised deliberately: hang detection and a bogus image both report a clear reason and exit non-zero. Doing so found a real defect -- TimeoutExpired carries its partial output undecoded even under text=True, so the hang path raised TypeError inside its own error handler instead of reporting the hang. enable_testing() is added to the root CMakeLists.txt. add_test() in a subdirectory does generate a CTestTestfile.cmake there, but without the top-level call the root never references it and ctest reports no tests at all from the build root. Note the five -Wextra unused-parameter warnings in demo_threadx.c are inherited from the upstream sample and are left alone on purpose: silencing them would mean the demo is no longer byte-identical. The normal build remains warning-free. Verified: all four images pass through ctest from the build root in about six seconds. Assisted-by: Claude Code (Opus 5) --- .gitignore | 2 + CMakeLists.txt | 7 + .../fvp_baser_aemv8r/CMakeLists.txt | 65 +++ .../fvp_baser_aemv8r/demo_threadx.c | 393 ++++++++++++++++++ .../fvp_baser_aemv8r/demo_verify.c | 180 ++++++++ .../fvp_baser_aemv8r/demo_verify.h | 37 ++ .../fvp_baser_aemv8r/test/run_fvp_test.py | 107 +++++ 7 files changed, 791 insertions(+) create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_threadx.c create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_verify.c create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_verify.h create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/test/run_fvp_test.py diff --git a/.gitignore b/.gitignore index 41f526390..a1ad49567 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ CTestTestfile.cmake build_m7/ .codex build_r52/ +__pycache__/ +*.pyc 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/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt index 0ecbcf65c..cba50c194 100644 --- a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt @@ -81,6 +81,37 @@ target_link_options(demo_m3.elf PRIVATE -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 + ${FVP_DIR}/console.c + ${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 +) + # Run a target on the FVP. Each image exits by itself through the # semihosting SYS_EXIT call, so no host-side timeout is needed. find_program(FVP_BASER_AEMV8R FVP_BaseR_AEMv8R @@ -106,6 +137,40 @@ if(FVP_BASER_AEMV8R) "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 boot_check.elf demo_m2.elf demo_m3.elf demo_threadx.elf) + + 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/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..843fe9c59 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_verify.c @@ -0,0 +1,180 @@ +/*************************************************************************** + * 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 "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/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..d6b1b6660 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/test/run_fvp_test.py @@ -0,0 +1,107 @@ +#!/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", + "-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()) From 1fb0e1a2645888899c302ba5aa5f5cae2d644226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Thu, 6 Aug 2026 08:40:03 -0400 Subject: [PATCH 06/10] Fixed VFP support and added floating-point build options The port could not actually be built with TX_ENABLE_VFP_SUPPORT. Its assembly reads and writes the per-thread VFP enable flag as [thread, #144], but every TX_THREAD_EXTENSION in tx_port.h was empty, so no such member existed and those accesses would have landed on an unrelated field. This is inherited from the Cortex-R5 port, which has the same gap. TX_THREAD_EXTENSION_2 now carries tx_thread_vfp_enable, matching the Armv7-A ports, and the tx_thread_vfp_enable/disable prototypes are declared. The field is defined unconditionally rather than under TX_ENABLE_VFP_SUPPORT. Because the assembly reaches it by hard-coded displacement, a conditional member would move every following field between configurations and leave the offset correct in only one of them. Added tx_port_offset_check.c, which asserts at compile time that the flag really is at 144 and that the stack pointer and run counter are at 8 and 4 as the context-switch code assumes. The offset was measured, not assumed. Nothing in the toolchain ties those literals to the C structure, so without this a future member or build option would silently retarget the accesses; now it fails the build instead. C99 has no _Static_assert, so the check uses a negative array dimension, and the assertion was verified to reject a deliberately wrong offset. Enabling VFP then faulted on the first context switch touching D-registers, with CPACR reading zero: tx_thread_vfp_enable() only sets the software flag that makes the switch save floating-point state, it does not turn the hardware on. Enabling the FPU is a BSP responsibility, so entry.S now grants CP10/CP11 access through CPACR and sets FPEXC.EN at EL1, complementing the HCPTR clearing already done at EL2. It is guarded by __ARM_FP, which the compiler defines exactly when the build can emit floating-point instructions; "vmsr fpexc" does not even assemble for this target in a soft-float build. Added CMake options for the floating-point and interrupt build variants: TX_R52_ENABLE_VFP, TX_R52_ENABLE_FIQ, TX_R52_ENABLE_IRQ_NESTING and TX_R52_ENABLE_FIQ_NESTING. All are PUBLIC because they change which registers the assembly saves, so library and application must agree. Requesting FIQ nesting without FIQ support is rejected outright rather than quietly producing a half-configured build. demo_m5.elf covers both halves of the VFP path, which use different register sets: a checking thread holds eight doubles live across tx_thread_sleep to exercise the solicited save of D8-D15, while a lower-priority thread does continuous floating-point work so the tick interrupts it mid-sequence and exercises the D0-D15 interrupt path. All constants are exact binary fractions and the two threads use disjoint ranges, so a single corrupted register or any leakage between contexts shows as a mismatch rather than rounding noise. Verified on FVP_BaseR_AEMv8R: all eight values preserved exactly across 50 solicited switches, and 780,761 iterations of the interrupted floating-point thread with zero corruption. Six build configurations (soft/hard float, with and without VFP, FIQ, IRQ nesting and FIQ nesting) all build warning-free and pass the tick and preemption demo at runtime. The five-image suite passes through ctest. Assisted-by: Claude Code (Opus 5) --- .gitignore | 1 + ports/cortex_r52/gnu/CMakeLists.txt | 40 +++ .../fvp_baser_aemv8r/CMakeLists.txt | 35 +++ .../example_build/fvp_baser_aemv8r/demo_m5.c | 248 ++++++++++++++++++ .../example_build/fvp_baser_aemv8r/entry.S | 40 +++ ports/cortex_r52/gnu/inc/tx_port.h | 26 +- .../cortex_r52/gnu/src/tx_port_offset_check.c | 82 ++++++ 7 files changed, 471 insertions(+), 1 deletion(-) create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_m5.c create mode 100644 ports/cortex_r52/gnu/src/tx_port_offset_check.c diff --git a/.gitignore b/.gitignore index a1ad49567..71fb815c5 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ build_m7/ build_r52/ __pycache__/ *.pyc +build_r52_vfp/ diff --git a/ports/cortex_r52/gnu/CMakeLists.txt b/ports/cortex_r52/gnu/CMakeLists.txt index fb33f08af..38696814b 100644 --- a/ports/cortex_r52/gnu/CMakeLists.txt +++ b/ports/cortex_r52/gnu/CMakeLists.txt @@ -21,6 +21,7 @@ target_sources(${PROJECT_NAME} ${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}} ) @@ -29,6 +30,45 @@ target_include_directories(${PROJECT_NAME} ${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) 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 index cba50c194..c1e03be77 100644 --- a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt @@ -112,6 +112,38 @@ target_link_options(demo_threadx.elf PRIVATE -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 + ${FVP_DIR}/console.c + ${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() + # Run a target on the FVP. Each image exits by itself through the # semihosting SYS_EXIT call, so no host-side timeout is needed. find_program(FVP_BASER_AEMV8R FVP_BaseR_AEMv8R @@ -148,6 +180,9 @@ if(FVP_BASER_AEMV8R) if(Python3_FOUND) set(R52_RUNNER ${FVP_DIR}/test/run_fvp_test.py) set(R52_TEST_IMAGES boot_check.elf demo_m2.elf demo_m3.elf demo_threadx.elf) + if(TX_R52_ENABLE_VFP) + list(APPEND R52_TEST_IMAGES demo_m5.elf) + endif() enable_testing() foreach(image IN LISTS R52_TEST_IMAGES) 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..cfece63ea --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_m5.c @@ -0,0 +1,248 @@ +/*************************************************************************** + * 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. */ +/* */ +/**************************************************************************/ + +#include "tx_api.h" +#include "console.h" + +#define DEMO_STACK_SIZE 2048 +#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; + + /* 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; + + 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); + + 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/entry.S b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/entry.S index f304c6e66..3d346ae0e 100644 --- a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/entry.S +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/entry.S @@ -76,6 +76,13 @@ .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 @@ -259,6 +266,39 @@ el1_entry: 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__ diff --git a/ports/cortex_r52/gnu/inc/tx_port.h b/ports/cortex_r52/gnu/inc/tx_port.h index 2c39f83bd..188b5e58d 100644 --- a/ports/cortex_r52/gnu/inc/tx_port.h +++ b/ports/cortex_r52/gnu/inc/tx_port.h @@ -182,9 +182,22 @@ typedef unsigned short USHORT; 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 +#define TX_THREAD_EXTENSION_2 ULONG tx_thread_vfp_enable; #define TX_THREAD_EXTENSION_3 @@ -261,6 +274,17 @@ typedef unsigned short USHORT; 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); 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]; From 525b448a1ee0caac3b219205d3086771d9d41e89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Thu, 6 Aug 2026 08:44:27 -0400 Subject: [PATCH 07/10] Added a PL011 UART console alongside semihosting Completes the console item deferred from the tick milestone. Semihosting stays the default because it needs no peripheral and therefore cannot be broken by a wrong memory map, which is what makes it the right tool for early bring-up. The UART matters because it is what real silicon uses: exercising it now means the S32Z280 console should differ only in base address and clocking, not in structure. Selected with -DTX_R52_CONSOLE_PL011=ON. Both backends are always compiled and console.c picks one at compile time, so --gc-sections drops the unused one and every image builds in either configuration with no per-target source juggling. UART0 is disabled at reset on this model (bp.pl011_uart0.uart_enable=0), so the control register is programmed before the first character; the baud divisor is deliberately left alone because the model does not emulate serial timing and inventing a value would only mislead. Initialisation happens on first use rather than from board_init, so images with no ThreadX initialisation path -- the boot check -- also get a working UART console. console_exit keeps using semihosting even with the UART selected: SYS_EXIT tells the model to stop, which is a debug-channel operation rather than console output. The run targets and the test runner now route UART0 to stdout unconditionally, which is harmless for semihosting images and necessary for UART ones. Verified that the UART path is genuinely used rather than silently falling back: with UART routing removed, the PL011 build prints nothing at all while the semihosting build still prints every check line, and the PL011 build prints again once routing is restored. Both configurations pass the whole four-image suite warning-free. Assisted-by: Claude Code (Opus 5) --- .gitignore | 1 + .../fvp_baser_aemv8r/CMakeLists.txt | 46 +++++-- .../example_build/fvp_baser_aemv8r/console.c | 34 ++++- .../fvp_baser_aemv8r/test/run_fvp_test.py | 4 + .../fvp_baser_aemv8r/uart_pl011.c | 123 ++++++++++++++++++ .../fvp_baser_aemv8r/uart_pl011.h | 43 ++++++ 6 files changed, 239 insertions(+), 12 deletions(-) create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/uart_pl011.c create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/uart_pl011.h diff --git a/.gitignore b/.gitignore index 71fb815c5..2381c30e8 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ build_r52/ __pycache__/ *.pyc build_r52_vfp/ +build_r52_uart/ 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 index c1e03be77..42e9839f7 100644 --- a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt @@ -6,12 +6,20 @@ 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 +) + # 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 - ${FVP_DIR}/console.c + ${R52_CONSOLE_SOURCES} ${FVP_DIR}/bsp_boot.c ) @@ -30,7 +38,7 @@ target_link_options(boot_check.elf PRIVATE # ported context-switch assembly with no interrupts and no timer tick. add_executable(demo_m2.elf EXCLUDE_FROM_ALL ${FVP_DIR}/entry.S - ${FVP_DIR}/console.c + ${R52_CONSOLE_SOURCES} ${FVP_DIR}/tx_initialize_low_level.S ${FVP_DIR}/demo_m2.c ) @@ -56,7 +64,7 @@ target_link_options(demo_m2.elf PRIVATE # the interrupt controller; images without it keep the fault reporter. add_executable(demo_m3.elf EXCLUDE_FROM_ALL ${FVP_DIR}/entry.S - ${FVP_DIR}/console.c + ${R52_CONSOLE_SOURCES} ${FVP_DIR}/gicv3.c ${FVP_DIR}/timer.c ${FVP_DIR}/irq_dispatch.c @@ -86,7 +94,7 @@ target_link_options(demo_m3.elf PRIVATE # into the verification harness, which lives in demo_verify.c. add_executable(demo_threadx.elf EXCLUDE_FROM_ALL ${FVP_DIR}/entry.S - ${FVP_DIR}/console.c + ${R52_CONSOLE_SOURCES} ${FVP_DIR}/gicv3.c ${FVP_DIR}/timer.c ${FVP_DIR}/irq_dispatch.c @@ -118,7 +126,7 @@ target_link_options(demo_threadx.elf PRIVATE if(TX_R52_ENABLE_VFP) add_executable(demo_m5.elf EXCLUDE_FROM_ALL ${FVP_DIR}/entry.S - ${FVP_DIR}/console.c + ${R52_CONSOLE_SOURCES} ${FVP_DIR}/gicv3.c ${FVP_DIR}/timer.c ${FVP_DIR}/irq_dispatch.c @@ -144,8 +152,27 @@ if(TX_R52_ENABLE_VFP) ) endif() +# Every image built here, in the order they should be exercised. +set(R52_IMAGES boot_check.elf demo_m2.elf demo_m3.elf demo_threadx.elf) +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. +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. +# 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 ) @@ -156,6 +183,8 @@ if(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 @@ -179,10 +208,7 @@ if(FVP_BASER_AEMV8R) find_package(Python3 COMPONENTS Interpreter) if(Python3_FOUND) set(R52_RUNNER ${FVP_DIR}/test/run_fvp_test.py) - set(R52_TEST_IMAGES boot_check.elf demo_m2.elf demo_m3.elf demo_threadx.elf) - if(TX_R52_ENABLE_VFP) - list(APPEND R52_TEST_IMAGES demo_m5.elf) - endif() + set(R52_TEST_IMAGES ${R52_IMAGES}) enable_testing() foreach(image IN LISTS R52_TEST_IMAGES) 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 index ae4c24001..9900eb996 100644 --- a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/console.c +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/console.c @@ -39,6 +39,10 @@ #include "console.h" +#ifdef TX_R52_CONSOLE_PL011 +#include "uart_pl011.h" +#endif + /* Semihosting operation numbers (Arm semihosting specification). */ #define SYS_WRITE0 0x04U @@ -82,10 +86,32 @@ static int semihost_call(int operation, const void *argument_ptr) void console_puts(const char *string_ptr) { - if (string_ptr != 0) + 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) { - (void) semihost_call((int) SYS_WRITE0, string_ptr); + pl011_init(); + initialised = 1U; } + + pl011_puts(string_ptr); + +#else + + (void) semihost_call((int) SYS_WRITE0, string_ptr); + +#endif } @@ -119,6 +145,10 @@ void console_puthex(unsigned long value) /* 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 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 index d6b1b6660..f47e42289 100644 --- 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 @@ -46,6 +46,10 @@ def run(elf, fvp, timeout): "-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, ] 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 */ From 2dda6008aa71624127e99624c4914390ac50cb64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Thu, 6 Aug 2026 09:33:39 -0400 Subject: [PATCH 08/10] Added a thread-member aliasing check to the VFP demo The per-thread VFP enable flag is reached from assembly by hard-coded offset, so if the corresponding TX_THREAD member is missing the access silently lands on whichever field occupies that offset. On the Cortex-R4 and R5 ports that is tx_thread_filex_ptr. The demo now plants a sentinel in tx_thread_filex_ptr before enabling floating point and verifies it afterwards, which turns that class of mistake into a visible failure. This check matters more than it looks. Reverting this port's TX_THREAD_EXTENSION_2 to the state the R4/R5 ports are in today and rerunning the demo shows every floating-point value still preserved exactly and no corruption reported across interrupts -- while tx_thread_filex_ptr reads 0x00000001, the value the enable routine writes as its flag. The floating-point feature appears to work perfectly; the only damage is to an unrelated pointer that nothing notices until FileX is added. Without this check the demo would have passed a corrupting build. Assisted-by: Claude Code (Opus 5) --- .../example_build/fvp_baser_aemv8r/demo_m5.c | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) 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 index cfece63ea..a9e435fc6 100644 --- 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 @@ -50,6 +50,7 @@ #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 */ @@ -125,9 +126,10 @@ static void thread_fp_busy_entry(ULONG thread_input) static void thread_check_entry(ULONG thread_input) { - UINT failures = 0U; - ULONG iteration; - ULONG expected_steps; + 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 @@ -144,6 +146,16 @@ static void thread_check_entry(ULONG thread_input) (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 "); @@ -203,6 +215,15 @@ static void thread_check_entry(ULONG thread_input) 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"); From eb2863a3aff24f84e9753cc7f3f0c675522b0c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Thu, 6 Aug 2026 12:06:11 -0400 Subject: [PATCH 09/10] Added PMSAv8-R memory protection and cache enable Completes AR1 milestone M5. The region set is described by a table rather than a sequence of register writes, because AR2 programs a Stage-1 region set per ThreadX module and AR3 programs Stage-2 regions per ZoneX partition: a table can be swapped at a partition switch, open-coded register writes cannot. Three regions are programmed -- code read-only and executable, everything writable non-executable, peripherals as Device -- and any regions this image does not use are explicitly disabled so a stale enable cannot grant access. The self-test provokes real faults instead of reading SCTLR back, and that distinction earned its keep three times over. Every problem below passed a configuration-only check and was caught only by attempting an access that should be refused. First, the access-permission encoding. Programming four disjoint regions, one per AP value, 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 of PRBAR.AP is read-only and the high bit grants EL0 access. That is the reverse of the widely-published Armv8-R AArch64 macro set, whose "read-only, no EL0 access" is 0x2. Using the AArch64 ordering produces regions that report as read-only and accept writes, because the MPU still enforces region coverage: an unmapped address faults while a "read-only" region does not. The values are documented as calibrated and flagged for re-calibration on silicon. Second, a memory layout error. Writes to the first two variables in .data faulted, because .data began at 0x44f4 while the code/data boundary symbol reported 0x4500, so twelve bytes of writable data sat inside the read-only code region -- including the flag the abort handler consults, which turned the fault fatal. Aligning the output section address with ".data ALIGN(64) :" fixes it. Neither an assignment between output sections nor an ALIGN at the end of the preceding section works: the former does not advance a MEMORY region's allocation pointer, and the latter does not extend a section when no data follows it. Third, a build system gap that hid the two attempted fixes. The linker script is passed with -T, which CMake does not track, so editing it did not relink and each attempt was tested against a stale image. LINK_DEPENDS is now declared for every image. The data-abort handler gained a recoverable path used only by the self-test: when mpu_expect_abort is set the fault is counted and execution resumes at the instruction after the faulting one, since LR_abt holds the faulting address plus 8 and returning to LR_abt-4 skips it where LR_abt-8 would retry forever. Verified on FVP_BaseR_AEMv8R: 32 regions available, 3 programmed and read back from hardware, MPU and both caches enabled, a legal write accepted, an unmapped write refused, a write to read-only code refused with the contents unchanged, and the kernel healthy afterwards. The full suite passes with protection and caches off, with them on, and with them on together with hard-float VFP -- six images, no warnings. Assisted-by: Claude Code (Opus 5) --- .gitignore | 2 + .../fvp_baser_aemv8r/CMakeLists.txt | 54 ++- .../example_build/fvp_baser_aemv8r/demo_mpu.c | 242 ++++++++++++ .../example_build/fvp_baser_aemv8r/entry.S | 49 ++- .../example_build/fvp_baser_aemv8r/link.lds | 33 +- .../gnu/example_build/fvp_baser_aemv8r/mpu.c | 349 ++++++++++++++++++ .../gnu/example_build/fvp_baser_aemv8r/mpu.h | 109 ++++++ 7 files changed, 834 insertions(+), 4 deletions(-) create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_mpu.c create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/mpu.c create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/mpu.h diff --git a/.gitignore b/.gitignore index 2381c30e8..6054dbead 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,5 @@ __pycache__/ *.pyc build_r52_vfp/ build_r52_uart/ +build_r52_mpu/ +build_r52_all/ 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 index 42e9839f7..dc49de74a 100644 --- a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt @@ -12,6 +12,7 @@ set(FVP_DIR ${CMAKE_CURRENT_LIST_DIR}) 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 @@ -152,8 +153,49 @@ if(TX_R52_ENABLE_VFP) ) 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) +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() @@ -161,6 +203,16 @@ 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) 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..5025520ff --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_mpu.c @@ -0,0 +1,242 @@ +/*************************************************************************** + * 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 "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. */ + +extern volatile unsigned long mpu_expect_abort; +extern volatile unsigned long mpu_abort_count; + +/* 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/entry.S b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/entry.S index 3d346ae0e..c7208db3c 100644 --- a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/entry.S +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/entry.S @@ -311,6 +311,15 @@ bss_zero_loop: 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. */ @@ -399,7 +408,35 @@ 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 -el1_trap_dabt: FAULT_REPORT msg_el1_dabt + +/* 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 @@ -433,3 +470,13 @@ msg_el1_fiq: .asciz "[FAULT] EL1 FIQ (no handler yet)\n" .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/link.lds b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/link.lds index bd11873ac..3522cb619 100644 --- a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/link.lds +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/link.lds @@ -39,6 +39,14 @@ MEMORY 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)) @@ -55,9 +63,24 @@ SECTIONS . = ALIGN(4); } > DRAM - .data : + /* 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) : { - . = ALIGN(4); + __code_end__ = .; /* code region ends where writable data begins */ + __data_start__ = .; *(.data*) . = ALIGN(4); } > DRAM @@ -109,4 +132,10 @@ SECTIONS . = 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 */ From 2b3ea7565d0d0c8351f1bb0a9932b4fbf77d161b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Thu, 6 Aug 2026 12:30:54 -0400 Subject: [PATCH 10/10] Added port documentation and fixed missing prototypes Applied the strict warning set that upstream uses for the kernel -- including -Wmissing-prototypes, -Wconversion, -Wshadow, -Wcast-qual and -Waggregate-return -- to every C source in the port and its board support package. Two real findings, both in code called from assembly and therefore lacking any visible declaration, which MISRA C:2012 Rule 8.4 prohibits: board_init and board_irq_handler. Added board.h to declare everything that crosses the C/assembly boundary -- bsp_main, board_init, board_irq_handler, the interrupt observability counters, the hypercall counter and the recoverable-abort flags -- so that a signature change cannot silently disagree with the assembly that calls it. All port and board sources are now clean under that warning set. The findings that remain are not port defects and are left alone deliberately. Passing a string literal to tx_thread_create reports a discarded const qualifier from common/inc/tx_api.h, because the name parameter is CHAR * rather than const CHAR *; that is an upstream API signature, not something this port can fix. demo_threadx.c reports a non-prototype declaration for the sample's own "int main()", and correcting it would break the byte-identity with the shipped sample that the demonstration exists to prove. The exact floating-point comparisons in demo_m5.c are the point of that test rather than an oversight, and are now documented as a deviation: a tolerance would mask a restored register that is close but wrong. Added readme_threadx.txt covering the build and its options, the EL2-aware initialisation and which registers must be programmed before dropping to EL1, the interrupt path including the Hyp Trap Entry funnel, lazy floating point and the division of responsibility for enabling the FPU, and memory protection. It records the two traps most likely to catch the next person: the calibrated PRBAR.AP encoding, whose bit order is the reverse of the published AArch64 macros and which yields writable "read-only" regions if copied from there, and the linker-script alignment needed to keep writable data out of the read-only code region. Assisted-by: Claude Code (Opus 5) --- .../example_build/fvp_baser_aemv8r/board.h | 72 ++++++++ .../example_build/fvp_baser_aemv8r/bsp_boot.c | 2 +- .../example_build/fvp_baser_aemv8r/demo_m2.c | 1 + .../example_build/fvp_baser_aemv8r/demo_m3.c | 5 +- .../example_build/fvp_baser_aemv8r/demo_m5.c | 11 ++ .../example_build/fvp_baser_aemv8r/demo_mpu.c | 3 +- .../fvp_baser_aemv8r/demo_verify.c | 1 + .../fvp_baser_aemv8r/irq_dispatch.c | 1 + ports/cortex_r52/gnu/readme_threadx.txt | 172 ++++++++++++++++++ 9 files changed, 261 insertions(+), 7 deletions(-) create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/board.h create mode 100644 ports/cortex_r52/gnu/readme_threadx.txt 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 index 97cfdbddb..3e1af9a36 100644 --- 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 @@ -37,6 +37,7 @@ /* */ /**************************************************************************/ +#include "board.h" #include "console.h" /* AArch32 CPSR mode field encodings. */ @@ -47,7 +48,6 @@ /* Incremented by the EL2 HVC handler in entry.S. */ -extern volatile unsigned long _hvc_call_count; /**************************************************************************/ 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 index 8218a8a3e..6cc302c1a 100644 --- 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 @@ -39,6 +39,7 @@ /**************************************************************************/ #include "tx_api.h" +#include "board.h" #include "console.h" #define DEMO_STACK_SIZE 2048 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 index bc0f59b5c..4286a8448 100644 --- 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 @@ -43,6 +43,7 @@ /**************************************************************************/ #include "tx_api.h" +#include "board.h" #include "console.h" #include "timer.h" @@ -60,10 +61,6 @@ static volatile ULONG busy_counter; /* Recorded by the interrupt dispatcher. */ -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; /**************************************************************************/ 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 index a9e435fc6..53004aaa2 100644 --- 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 @@ -44,9 +44,20 @@ /* 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 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 index 5025520ff..f6c16fe6b 100644 --- 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 @@ -34,6 +34,7 @@ /**************************************************************************/ #include "tx_api.h" +#include "board.h" #include "console.h" #include "mpu.h" @@ -44,8 +45,6 @@ static ULONG thread_check_stack[DEMO_STACK_SIZE / sizeof(ULONG)]; /* Provided by entry.S and manipulated by its data-abort handler. */ -extern volatile unsigned long mpu_expect_abort; -extern volatile unsigned long mpu_abort_count; /* A known-writable location, used to show legal access still works. */ 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 index 843fe9c59..5b6e3741d 100644 --- 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 @@ -38,6 +38,7 @@ /**************************************************************************/ #include "tx_api.h" +#include "board.h" #include "console.h" #include "demo_verify.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 index d49e5fd08..985b2621b 100644 --- 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 @@ -31,6 +31,7 @@ /* */ /**************************************************************************/ +#include "board.h" #include "gicv3.h" #include "timer.h" 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.