diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/CMakeLists.txt b/ports/cortex_r52/gnu/example_build/s32z280_evb/CMakeLists.txt index 3319d77f1..017aa7574 100644 --- a/ports/cortex_r52/gnu/example_build/s32z280_evb/CMakeLists.txt +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/CMakeLists.txt @@ -11,6 +11,17 @@ set(EVB_DIR ${CMAKE_CURRENT_LIST_DIR}) +# A bare-metal image has one flat SRAM region and no OS page permissions; access +# control belongs to the MPU, so an RWX segment is expected rather than a mistake. +# GNU ld has warned about them since binutils 2.39 and takes this flag to stay +# quiet; ld.lld does not warn and rejects the flag outright, failing the link with +# "unknown argument". Chosen by toolchain, as in the FVP example. +if(CMAKE_C_COMPILER_ID STREQUAL "GNU") + set(EVB_LINK_QUIET_RWX -Wl,--no-warn-rwx-segments) +else() + set(EVB_LINK_QUIET_RWX) +endif() + # Silicon M0 -- boot verification. Deliberately does not link ThreadX: a # failure here is then unambiguously a boot or board problem rather than a # kernel one, which is the same reasoning as the FVP's boot_check.elf. @@ -39,7 +50,7 @@ target_link_options(s32z280_boot.elf PRIVATE -Wl,-Map=s32z280_boot.map # Bare metal: one code region and one data region, with access control # belonging to the MPU rather than to segment permissions. - -Wl,--no-warn-rwx-segments + ${EVB_LINK_QUIET_RWX} ) @@ -77,5 +88,42 @@ target_link_options(s32z280_demo.elf PRIVATE -T${EVB_DIR}/link.lds -nostartfiles -Wl,-Map=s32z280_demo.map - -Wl,--no-warn-rwx-segments + ${EVB_LINK_QUIET_RWX} ) + +# Lazy VFP context save and restore. Only meaningful when the library was built +# with TX_R52_ENABLE_VFP and a floating-point ABI, so like the FVP's demo_m5.elf +# the target exists only in that configuration. The FPU hardware enable lives in +# entry.S under __ARM_FP; tx_thread_vfp_enable() sets only the per-thread flag. +if(TX_R52_ENABLE_VFP) + add_executable(s32z280_vfp.elf EXCLUDE_FROM_ALL + ${EVB_DIR}/entry.S + ${EVB_DIR}/tx_initialize_low_level.S + ${EVB_DIR}/linflexd.c + ${EVB_DIR}/timer.c + ${EVB_DIR}/mpu.c + ${EVB_DIR}/gicv3.c + ${EVB_DIR}/irq_dispatch.c + ${EVB_DIR}/gic_probe.c + ${EVB_DIR}/cache.c + ${EVB_DIR}/demo_vfp_s32z280.c + ) + + target_compile_definitions(s32z280_vfp.elf PRIVATE TX_R52_USE_THREADX_IRQ=1) + target_compile_options(s32z280_vfp.elf PRIVATE -g) + + target_link_libraries(s32z280_vfp.elf PRIVATE threadx) + + target_include_directories(s32z280_vfp.elf PRIVATE + ${EVB_DIR} + ${CMAKE_SOURCE_DIR}/common/inc + ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc + ) + + target_link_options(s32z280_vfp.elf PRIVATE + -T${EVB_DIR}/link.lds + -nostartfiles + -Wl,-Map=s32z280_vfp.map + ${EVB_LINK_QUIET_RWX} + ) +endif() diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/demo_vfp_s32z280.c b/ports/cortex_r52/gnu/example_build/s32z280_evb/demo_vfp_s32z280.c new file mode 100644 index 000000000..456ad3190 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/demo_vfp_s32z280.c @@ -0,0 +1,283 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* demo_vfp_s32z280.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frederic Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Lazy floating-point context save and restore on S32Z280 silicon. */ +/* */ +/* This is the FVP's demo_m5.c test design, reporting over the */ +/* LINFlexD console instead of semihosting. The design is kept */ +/* deliberately: the model established that the two halves of the VFP */ +/* context path need separate provocation, and silicon does not change */ +/* that, it only changes whether the hardware agrees. */ +/* */ +/* fp check holds eight live doubles across tx_thread_sleep. Eight */ +/* forces the compiler onto the callee-saved bank D8-D15, */ +/* which is what a SOLICITED switch must preserve. */ +/* */ +/* fp busy lowest priority and never sleeps, so the periodic tick */ +/* interrupts it mid-computation. That exercises the */ +/* INTERRUPT half, D0-D15 plus FPSCR, and it checks its */ +/* own values every iteration. */ +/* */ +/* Both call tx_thread_vfp_enable(). With lazy save and restore a */ +/* thread that has not asked for floating point does not get its */ +/* registers saved, so a test where every thread opts in would not */ +/* show whether the opt-in is what does the work. */ +/* */ +/* Every constant is an exact binary fraction and the step is 0.5, so */ +/* the comparisons are exact and no epsilon is needed. MISRA C:2012 */ +/* Dir 4.6 and Rule 13.3 would normally object to == on floating */ +/* point; here it is the point, since any inexactness means a register */ +/* was not preserved. */ +/* */ +/* What silicon adds over the model: the FPU must actually be enabled */ +/* by the BSP. entry.S opens CPACR and FPEXC.EN at EL1 under */ +/* __ARM_FP, because tx_thread_vfp_enable() sets only the per-thread */ +/* software flag and never touches the hardware. Without that, the */ +/* first D-register access after a switch takes an Undefined */ +/* Instruction exception -- which on this board was invisible until */ +/* SCTLR.TE was cleared. */ +/* */ +/**************************************************************************/ + +#include "tx_api.h" +#include "linflexd.h" +#include "board.h" + +#define DEMO_STACK_SIZE 2048 +#define FILEX_PTR_SENTINEL ((void *) 0xF11EF11EUL) +#define FP_ITERATIONS 50UL +#define FP_STEP 0.5 /* exactly representable */ + +static TX_THREAD thread_check; +static TX_THREAD thread_fp_busy; + +static ULONG thread_check_stack[DEMO_STACK_SIZE / sizeof(ULONG)]; +static ULONG thread_fp_busy_stack[DEMO_STACK_SIZE / sizeof(ULONG)]; + +static volatile ULONG busy_iterations; +static volatile ULONG busy_corruptions; + + +/* Breakpoint target for tools/read_identity.gdb. Not static and not empty so + an optimising build cannot discard it. */ + +void bsp_done(void) +{ + __asm volatile ("nop"); +} + + +static UINT report(const char *label_ptr, UINT passed) +{ + linflexd_puts(label_ptr); + linflexd_puts((passed != 0U) ? "PASS\n" : "FAIL\n"); + return (passed != 0U) ? 0U : 1U; +} + + +static void report_hex(const char *label_ptr, unsigned long value) +{ + linflexd_puts(label_ptr); + linflexd_puts(" = 0x"); + linflexd_put_hex32((unsigned int) value); + linflexd_putc('\n'); +} + + +/**************************************************************************/ +/* thread_fp_busy_entry -- the interrupt half of the VFP path. */ +/**************************************************************************/ + +static void thread_fp_busy_entry(ULONG thread_input) +{ + (void) thread_input; + + tx_thread_vfp_enable(); + + for (;;) + { + /* Deliberately disjoint from the checking thread's range, so that + state leaking from one context into the other is detectable rather + than coincidentally correct. */ + + double v0 = 1024.5; + double v1 = 2048.25; + double v2 = 4096.125; + double v3 = 8192.0625; + + v0 = (v0 * 2.0) - 1024.5; /* 1024.5 */ + v1 = (v1 * 2.0) - 2048.25; /* 2048.25 */ + v2 = (v2 * 2.0) - 4096.125; /* 4096.125 */ + v3 = (v3 * 2.0) - 8192.0625; /* 8192.0625 */ + + if ((v0 != 1024.5) || (v1 != 2048.25) || + (v2 != 4096.125) || (v3 != 8192.0625)) + { + busy_corruptions++; + } + + busy_iterations++; + } +} + + +/**************************************************************************/ +/* thread_check_entry -- the solicited half, and the judge. */ +/**************************************************************************/ + +static void thread_check_entry(ULONG thread_input) +{ + UINT failures = 0U; + ULONG iteration; + TX_THREAD *self; + + double a = 1.5; + double b = 2.25; + double c = 3.125; + double d = 4.0625; + double e = 5.03125; + double f = 6.015625; + double g = 7.0078125; + double h = 8.00390625; + + (void) thread_input; + + /* The VFP enable flag is reached from assembly by hard-coded offset. On the + Cortex-R4 and R5 ports that offset lands on tx_thread_filex_ptr, because + their TX_THREAD_EXTENSION_2 is empty. A sentinel turns that class of + mistake into a visible failure instead of corruption that only appears + once something actually uses FileX. */ + + self = tx_thread_identify(); + self->tx_thread_filex_ptr = FILEX_PTR_SENTINEL; + + tx_thread_vfp_enable(); + + linflexd_puts("V1 floating-point work across solicited switches\n"); + + for (iteration = 0UL; iteration < FP_ITERATIONS; iteration++) + { + /* Suspending here is the provocation: the switch must preserve all + eight values, and the lower-priority thread runs meanwhile. */ + + tx_thread_sleep(1); + + a += FP_STEP; + b += FP_STEP; + c += FP_STEP; + d += FP_STEP; + e += FP_STEP; + f += FP_STEP; + g += FP_STEP; + h += FP_STEP; + } + + { + const double delta = 0.5 * (double) FP_ITERATIONS; + + failures += report("V2 D8-D15 bank preserved across switches ", + ((a == (1.5 + delta)) && + (b == (2.25 + delta)) && + (c == (3.125 + delta)) && + (d == (4.0625 + delta)) && + (e == (5.03125 + delta)) && + (f == (6.015625 + delta)) && + (g == (7.0078125 + delta)) && + (h == (8.00390625 + delta))) ? 1U : 0U); + } + + report_hex("iterations", busy_iterations); + report_hex("corruptions", busy_corruptions); + + failures += report("V3 interrupted FP thread made progress ", + (busy_iterations > 0UL) ? 1U : 0U); + failures += report("V4 no FP corruption across interrupts ", + (busy_corruptions == 0UL) ? 1U : 0U); + + report_hex("filex_ptr", (unsigned long) self->tx_thread_filex_ptr); + + failures += report("V5 VFP flag did not alias filex_ptr ", + (self->tx_thread_filex_ptr == FILEX_PTR_SENTINEL) ? 1U : 0U); + + if (failures == 0U) + { + linflexd_puts("PASS lazy VFP context switch verified on silicon\n"); + } + else + { + linflexd_puts("FAIL lazy VFP context switch\n"); + } + + linflexd_puts("=== vfp demo complete ===\n"); + + bsp_done(); + + for (;;) + { + tx_thread_sleep(100UL); + } +} + + +void tx_application_define(void *first_unused_memory) +{ + (void) first_unused_memory; + + (void) tx_thread_create(&thread_check, "fp check", thread_check_entry, 0UL, + thread_check_stack, sizeof(thread_check_stack), + 10U, 10U, TX_NO_TIME_SLICE, TX_AUTO_START); + + (void) tx_thread_create(&thread_fp_busy, "fp busy", thread_fp_busy_entry, + 0UL, thread_fp_busy_stack, + sizeof(thread_fp_busy_stack), + 20U, 20U, TX_NO_TIME_SLICE, TX_AUTO_START); +} + + +/**************************************************************************/ +/* bsp_main -- entered at EL1 from entry.S; hands over to the kernel. */ +/**************************************************************************/ + +void bsp_main(void) +{ + unsigned int console_status = linflexd_init(); + + linflexd_puts("\n=== ThreadX Cortex-R52 :: S32Z280-594EVB lazy VFP ===\n"); + report_hex("console", (unsigned long) console_status); + linflexd_puts("entering kernel\n"); + + tx_kernel_enter(); + + /* Not reached. */ + + linflexd_puts("FAIL tx_kernel_enter returned\n"); + + for (;;) + { + __asm volatile ("nop"); + } +} diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/entry.S b/ports/cortex_r52/gnu/example_build/s32z280_evb/entry.S index a5b34be7c..bb90d000a 100644 --- a/ports/cortex_r52/gnu/example_build/s32z280_evb/entry.S +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/entry.S @@ -66,6 +66,13 @@ .equ HCPTR_TCP, (3 << 10) /* trap EL1 access to CP10/CP11 */ +/* CPACR grants EL1/EL0 access to CP10 and CP11, the floating-point unit; + FPEXC.EN then enables floating-point execution itself. */ + + .equ CPACR_CP10, (3 << 20) + .equ CPACR_CP11, (3 << 22) + .equ FPEXC_EN, (1 << 30) + .equ CNTHCTL_PL1PCTEN, (1 << 0) .equ CNTHCTL_PL1PCEN, (1 << 1) @@ -345,6 +352,40 @@ el1_entry: mcr p15, 0, r0, c12, c0, 0 /* VBAR */ isb +#ifdef __ARM_FP + + /* Enable the floating-point unit for EL1. + * + * Two gates must be opened and both reset closed: + * CPACR grants EL1/EL0 access to CP10/CP11. It reads 0 out of reset, + * so the first floating-point instruction would otherwise raise + * an Undefined Instruction exception. + * FPEXC .EN enables floating-point execution itself. + * EL2 has already cleared HCPTR.TCP10/TCP11 above, so neither traps to EL2. + * + * This is the BSP's job, not the kernel's. tx_thread_vfp_enable() only sets + * the per-thread flag that makes the context switch save and restore + * floating-point state; it does not turn the hardware on. Without the code + * below, a VFP build faults on the first context switch that touches a + * D-register -- and on this silicon that fault was invisible until + * SCTLR.TE was cleared, which is the trap described further down. + * + * Guarded by __ARM_FP, which the compiler defines exactly when the build can + * emit floating-point instructions. In a soft-float build "vmsr fpexc" is + * not assemblable for this target at all. + */ + + mrc p15, 0, r0, c1, c0, 2 /* CPACR */ + orr r0, r0, #(CPACR_CP10 | CPACR_CP11) + mcr p15, 0, r0, c1, c0, 2 + isb + + mov r0, #FPEXC_EN + vmsr fpexc, r0 + isb + +#endif + /* Clear SCTLR.TE. This is not cosmetic and it cost a long detour. * * TE resets SET on this silicon -- SCTLR reads 0x70C50838, bit 30 high --