diff options
Diffstat (limited to 'test/tx/cmake/riscv')
| -rw-r--r-- | test/tx/cmake/riscv/CMakeLists.txt | 11 | ||||
| -rw-r--r-- | test/tx/cmake/riscv/bsp/entry.S | 6 | ||||
| -rw-r--r-- | test/tx/cmake/riscv/bsp/hwtimer.c | 84 | ||||
| -rw-r--r-- | test/tx/cmake/riscv/bsp/hwtimer.h | 4 | ||||
| -rw-r--r-- | test/tx/cmake/riscv/bsp/link.lds | 32 | ||||
| -rw-r--r-- | test/tx/cmake/riscv/bsp/plic.c | 25 | ||||
| -rw-r--r-- | test/tx/cmake/riscv/bsp/syscalls.c | 49 | ||||
| -rw-r--r-- | test/tx/cmake/riscv/bsp/trap.c | 3 | ||||
| -rw-r--r-- | test/tx/cmake/riscv/bsp/tx_initialize_low_level.S | 35 | ||||
| -rw-r--r-- | test/tx/cmake/riscv/regression/CMakeLists.txt | 12 | ||||
| -rw-r--r-- | test/tx/cmake/riscv/regression/threadx_riscv_new_thread_fpu_state_test.c | 278 | ||||
| -rwxr-xr-x | test/tx/cmake/riscv/run.sh | 33 |
12 files changed, 529 insertions, 43 deletions
diff --git a/test/tx/cmake/riscv/CMakeLists.txt b/test/tx/cmake/riscv/CMakeLists.txt index a4f7e7e8..41b44949 100644 --- a/test/tx/cmake/riscv/CMakeLists.txt +++ b/test/tx/cmake/riscv/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_policy(SET CMP0057 NEW) project(threadx_riscv_test LANGUAGES C ASM) # Build configurations (same defines as Linux tests, minus coverage instrumentation) -set(BUILD_CONFIGURATIONS default_build disable_notify_callbacks_build +set(BUILD_CONFIGURATIONS default_build optimized_build disable_notify_callbacks_build stack_checking_build stack_checking_rand_fill_build trace_build) set(CMAKE_CONFIGURATION_TYPES ${BUILD_CONFIGURATIONS} @@ -26,14 +26,21 @@ message(STATUS "THREADX_ARCH: ${THREADX_ARCH}") # Per-configuration compile definitions set(default_build -DTX_QUEUE_MESSAGE_MAX_SIZE=32) +set(optimized_build -DTX_QUEUE_MESSAGE_MAX_SIZE=32) set(disable_notify_callbacks_build -DTX_QUEUE_MESSAGE_MAX_SIZE=32 -DTX_DISABLE_NOTIFY_CALLBACKS) set(stack_checking_build -DTX_QUEUE_MESSAGE_MAX_SIZE=32 -DTX_ENABLE_STACK_CHECKING) set(stack_checking_rand_fill_build -DTX_QUEUE_MESSAGE_MAX_SIZE=32 -DTX_ENABLE_STACK_CHECKING -DTX_ENABLE_RANDOM_NUMBER_STACK_FILLING) set(trace_build -DTX_QUEUE_MESSAGE_MAX_SIZE=32 -DTX_ENABLE_EVENT_TRACE) +if(CMAKE_BUILD_TYPE STREQUAL "optimized_build") + set(REGRESSION_OPTIMIZATION -O2) +else() + set(REGRESSION_OPTIMIZATION -O0) +endif() + add_compile_options( -std=c99 - -O0 + ${REGRESSION_OPTIMIZATION} -g3 -fdiagnostics-color -Werror diff --git a/test/tx/cmake/riscv/bsp/entry.S b/test/tx/cmake/riscv/bsp/entry.S index 2f16c052..5abe9852 100644 --- a/test/tx/cmake/riscv/bsp/entry.S +++ b/test/tx/cmake/riscv/bsp/entry.S @@ -18,6 +18,7 @@ .extern _sysstack_end .extern _bss_start .extern _bss_end +.extern __global_pointer$ _start: csrr t0, mhartid @@ -56,6 +57,11 @@ _start: li x30, 0 li x31, 0 + .option push + .option norelax + la gp, __global_pointer$ + .option pop + /* Set up stack pointer from linker symbol. */ la sp, _sysstack_end diff --git a/test/tx/cmake/riscv/bsp/hwtimer.c b/test/tx/cmake/riscv/bsp/hwtimer.c index 99821f1e..8b81268a 100644 --- a/test/tx/cmake/riscv/bsp/hwtimer.c +++ b/test/tx/cmake/riscv/bsp/hwtimer.c @@ -9,25 +9,99 @@ **************************************************************************/ #include "csr.h" +#include "tx_api.h" #include "hwtimer.h" -#include <tx_port.h> #define CLINT (0x02000000L) #define CLINT_TIME (CLINT + 0xBFF8) #define CLINT_TIMECMP(id) (CLINT + 0x4000 + 8 * (id)) +#if __riscv_xlen == 32 + +/* RV32 has no 64-bit load/store, so the 64-bit CLINT registers are + accessed as two volatile 32-bit MMIO words. */ +#define MTIME_LO (*(volatile uint32_t *)(CLINT_TIME)) +#define MTIME_HI (*(volatile uint32_t *)(CLINT_TIME + 4)) +#define MTIMECMP_LO(id) (*(volatile uint32_t *)(CLINT_TIMECMP(id))) +#define MTIMECMP_HI(id) (*(volatile uint32_t *)(CLINT_TIMECMP(id) + 4)) + +/* RV32 mtime read: re-read the high word until it is stable, so a + low-word rollover between the two loads cannot tear the value. */ +static uint64_t clint_time_read(void) +{ + uint32_t hi; + uint32_t lo; + + do + { + hi = MTIME_HI; + lo = MTIME_LO; + } while (hi != MTIME_HI); + + return ((uint64_t)hi << 32) | lo; +} + +/* Only this hart writes its own mtimecmp, so a plain two-word read + cannot tear. */ +static uint64_t clint_timecmp_read(uintptr_t hart) +{ + uint32_t lo = MTIMECMP_LO(hart); + uint32_t hi = MTIMECMP_HI(hart); + + return ((uint64_t)hi << 32) | lo; +} + +/* RV32 mtimecmp write, safe sequence: park the low word at all-ones + first, so no intermediate 64-bit value compares below mtime and + raises a spurious timer interrupt. */ +static void clint_timecmp_write(uintptr_t hart, uint64_t value) +{ + MTIMECMP_LO(hart) = 0xFFFFFFFF; + MTIMECMP_HI(hart) = (uint32_t)(value >> 32); + MTIMECMP_LO(hart) = (uint32_t)value; +} + +#else /* __riscv_xlen == 64 */ + +/* RV64: naturally aligned 64-bit MMIO accesses are single loads/stores. */ +static uint64_t clint_time_read(void) +{ + return *(volatile uint64_t *)CLINT_TIME; +} + +static uint64_t clint_timecmp_read(uintptr_t hart) +{ + return *(volatile uint64_t *)CLINT_TIMECMP(hart); +} + +static void clint_timecmp_write(uintptr_t hart, uint64_t value) +{ + *(volatile uint64_t *)CLINT_TIMECMP(hart) = value; +} + +#endif /* __riscv_xlen */ + int hwtimer_init(void) { uintptr_t hart = riscv_get_core(); - uint64_t time = *((volatile uint64_t *)CLINT_TIME); - *((volatile uint64_t *)CLINT_TIMECMP(hart)) = time + TICKNUM_PER_TIMER; + + clint_timecmp_write(hart, clint_time_read() + TICKNUM_PER_TIMER); return 0; } int hwtimer_handler(void) { uintptr_t hart = riscv_get_core(); - uint64_t time = *((volatile uint64_t *)CLINT_TIME); - *((volatile uint64_t *)CLINT_TIMECMP(hart)) = time + TICKNUM_PER_TIMER; + + /* Absolute re-arm: advance from the previous compare value, so trap + latency does not accumulate as tick drift; catch up if the next + compare already passed. */ + uint64_t next = clint_timecmp_read(hart) + TICKNUM_PER_TIMER; + uint64_t now = clint_time_read(); + + if (next <= now) + next = now + TICKNUM_PER_TIMER; + + clint_timecmp_write(hart, next); return 0; } diff --git a/test/tx/cmake/riscv/bsp/hwtimer.h b/test/tx/cmake/riscv/bsp/hwtimer.h index e6b9efa4..bde0bba5 100644 --- a/test/tx/cmake/riscv/bsp/hwtimer.h +++ b/test/tx/cmake/riscv/bsp/hwtimer.h @@ -13,8 +13,8 @@ #include <stdint.h> -#define TICKNUM_PER_SECOND 10000000 -#define TICKNUM_PER_TIMER (TICKNUM_PER_SECOND / 10) +#define TICKNUM_PER_SECOND 10000000UL +#define TICKNUM_PER_TIMER (TICKNUM_PER_SECOND / TX_TIMER_TICKS_PER_SECOND) int hwtimer_init(void); int hwtimer_handler(void); diff --git a/test/tx/cmake/riscv/bsp/link.lds b/test/tx/cmake/riscv/bsp/link.lds index 42f08fb6..717530b3 100644 --- a/test/tx/cmake/riscv/bsp/link.lds +++ b/test/tx/cmake/riscv/bsp/link.lds @@ -12,6 +12,12 @@ MEMORY RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 128M } +PHDRS +{ + text PT_LOAD FLAGS(5); + data PT_LOAD FLAGS(6); +} + SECTIONS { . = 0x80000000; @@ -21,23 +27,24 @@ SECTIONS *(.text .text.*) . = ALIGN(0x1000); PROVIDE(etext = .); - } > RAM + } > RAM :text .rodata : { . = ALIGN(16); *(.srodata .srodata.*) . = ALIGN(16); *(.rodata .rodata.*) - } > RAM + } > RAM :text .data : { - . = ALIGN(16); + . = ALIGN(0x1000); + PROVIDE(__global_pointer$ = . + 0x800); *(.sdata .sdata.*) . = ALIGN(16); *(.data .data.*) - } > RAM + } > RAM :data - .bss : { + .bss (NOLOAD) : { . = ALIGN(16); _bss_start = .; *(.sbss .sbss.*) @@ -45,14 +52,23 @@ SECTIONS *(.bss .bss.*) *(COMMON) _bss_end = .; - } > RAM + } > RAM :data - .stack : { + .stack (NOLOAD) : { . = ALIGN(4096); _sysstack_start = .; . += 0x4000; _sysstack_end = .; - } > RAM + } > RAM :data + + /* Fixed 64 KiB newlib sbrk heap window. _end stays after it, so the + ThreadX first-unused-memory pointer starts past the heap. */ + .heap (NOLOAD) : { + . = ALIGN(16); + _heap_start = .; + . += 0x10000; + _heap_end = .; + } > RAM :data PROVIDE(_end = .); diff --git a/test/tx/cmake/riscv/bsp/plic.c b/test/tx/cmake/riscv/bsp/plic.c index a2b44fec..16dad815 100644 --- a/test/tx/cmake/riscv/bsp/plic.c +++ b/test/tx/cmake/riscv/bsp/plic.c @@ -52,32 +52,47 @@ int plic_unregister_callback(int irqno) int plic_init(void) { + uintptr_t hart = riscv_get_core(); + for (int i = 0; i < MAX_CALLBACK_NUM; i++) { callbacks[i] = NULL; } + + /* Do not depend on the reset state or on a prior boot stage: accept + every priority (threshold 0) and start with all sources for this + hart disabled. */ + *(volatile uint32_t *)PLIC_MPRIORITY(hart) = 0; + for (int w = 0; w < MAX_CALLBACK_NUM / 32; w++) + *(volatile uint32_t *)(PLIC_MENABLE(hart) + w * 4) = 0; + return 0; } int plic_claim(void) { uintptr_t hart = riscv_get_core(); - return (int)(*(uint32_t *)PLIC_MCLAIM(hart)); + return (int)(*(volatile uint32_t *)PLIC_MCLAIM(hart)); } void plic_complete(int irqno) { uintptr_t hart = riscv_get_core(); - *(uint32_t *)(PLIC_MCOMPLETE(hart)) = (uint32_t)irqno; + *(volatile uint32_t *)(PLIC_MCOMPLETE(hart)) = (uint32_t)irqno; } int plic_irq_intr(void) { int ret = -1; int irqno = plic_claim(); - if (irqno <= 0 || irqno >= MAX_CALLBACK_NUM) { - plic_complete(irqno); - return 0; // spurious or out-of-range, not an error + + if (irqno == 0) + return 0; + + if (irqno < 0 || irqno >= MAX_CALLBACK_NUM) { + if (irqno > 0) + plic_complete(irqno); + return -1; } if (callbacks[irqno] != NULL) ret = (callbacks[irqno])(irqno); diff --git a/test/tx/cmake/riscv/bsp/syscalls.c b/test/tx/cmake/riscv/bsp/syscalls.c index 802fe8af..40ca86f3 100644 --- a/test/tx/cmake/riscv/bsp/syscalls.c +++ b/test/tx/cmake/riscv/bsp/syscalls.c @@ -15,6 +15,7 @@ #include <sys/stat.h> #include <errno.h> #include <stdint.h> +#include <time.h> #define UART0_THR (*(volatile unsigned char *)0x10000000L) #define UART0_LSR (*(volatile unsigned char *)0x10000005L) @@ -25,6 +26,39 @@ #define VIRT_TEST_PASS 0x5555u #define VIRT_TEST_FAIL 0x3333u +#define QEMU_TIMEBASE_HZ 10000000ULL + +static uint64_t read_time_counter(void) +{ +#if __riscv_xlen == 64 + uint64_t value; + __asm__ volatile("rdtime %0" : "=r" (value)); + return value; +#else + uint32_t high; + uint32_t low; + uint32_t high_check; + + do + { + __asm__ volatile("rdtimeh %0" : "=r" (high)); + __asm__ volatile("rdtime %0" : "=r" (low)); + __asm__ volatile("rdtimeh %0" : "=r" (high_check)); + } while (high != high_check); + + return ((uint64_t)high << 32) | low; +#endif +} + +time_t time(time_t *timer) +{ + time_t seconds = (time_t)(read_time_counter() / QEMU_TIMEBASE_HZ); + + if (timer != NULL) + *timer = seconds; + return seconds; +} + /* Called by testcontrol.c when EXTERNAL_EXIT is defined. */ __attribute__((noreturn)) void external_exit(unsigned int code) { @@ -50,13 +84,24 @@ int _write(int fd, const char *buf, int count) return count; } -extern char _end[]; +/* Fixed 64 KiB heap window from the linker script. It keeps the newlib + heap out of the ThreadX first-unused-memory area, which starts at + _end (placed after _heap_end). */ +extern char _heap_start[]; +extern char _heap_end[]; static char *heap_ptr = 0; void *_sbrk(int incr) { if (heap_ptr == 0) - heap_ptr = _end; + heap_ptr = _heap_start; + + if ((incr > 0 && _heap_end - heap_ptr < incr) || + (incr < 0 && heap_ptr - _heap_start < -incr)) + { + errno = ENOMEM; + return (void *)-1; + } char *prev = heap_ptr; heap_ptr += incr; diff --git a/test/tx/cmake/riscv/bsp/trap.c b/test/tx/cmake/riscv/bsp/trap.c index 0ac7990a..f54ea2ff 100644 --- a/test/tx/cmake/riscv/bsp/trap.c +++ b/test/tx/cmake/riscv/bsp/trap.c @@ -36,9 +36,6 @@ extern void _exit(int code) __attribute__((noreturn)); void trap_handler(uintptr_t mcause, uintptr_t mepc, uintptr_t mtval) { - (void)mepc; - (void)mtval; - if (OS_IS_INTERUPT(mcause)) { if (OS_IS_TICK_INT(mcause)) diff --git a/test/tx/cmake/riscv/bsp/tx_initialize_low_level.S b/test/tx/cmake/riscv/bsp/tx_initialize_low_level.S index 8d474d66..d72c9b6a 100644 --- a/test/tx/cmake/riscv/bsp/tx_initialize_low_level.S +++ b/test/tx/cmake/riscv/bsp/tx_initialize_low_level.S @@ -1,5 +1,6 @@ /*************************************************************************** * Copyright (c) 2026 10xEngineers + * 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 @@ -8,10 +9,13 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Portions of this file were generated with AI assistance. + /* Trap entry and low-level init for RISC-V QEMU virt regression tests. Supports both RV32 and RV64 via __riscv_xlen conditionals. */ #include "csr.h" +#include "tx_port.h" #if __riscv_xlen == 64 #define STORE sd @@ -33,13 +37,28 @@ .extern trap_handler .extern _tx_thread_context_restore -trap_entry: +/* Take the interrupt frame size from the port that this BSP is linked + against, so the two can never disagree. A port that publishes no + contract keeps the historical layout. */ +#ifndef TX_RISCV_TRAP_FRAME_SIZE #if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) - addi sp, sp, -(65 * REGBYTES) +#define TX_RISCV_TRAP_FRAME_SIZE (65 * REGBYTES) #else - addi sp, sp, -(32 * REGBYTES) +#define TX_RISCV_TRAP_FRAME_SIZE (32 * REGBYTES) +#endif +#endif + +/* The RISC-V psABI requires sp to be 16-byte aligned at a call. */ +#ifndef TX_RISCV_TRAP_CALL_FRAME_SIZE +#define TX_RISCV_TRAP_CALL_FRAME_SIZE 16 #endif + .equ TX_TRAP_FRAME_SIZE, TX_RISCV_TRAP_FRAME_SIZE + .equ TX_TRAP_CALL_FRAME_SIZE, TX_RISCV_TRAP_CALL_FRAME_SIZE + +trap_entry: + addi sp, sp, -TX_TRAP_FRAME_SIZE + STORE x1, (28 * REGBYTES)(sp) call _tx_thread_context_save @@ -47,11 +66,11 @@ trap_entry: csrr a0, mcause csrr a1, mepc csrr a2, mtval - addi sp, sp, -REGBYTES + addi sp, sp, -TX_TRAP_CALL_FRAME_SIZE STORE ra, 0(sp) call trap_handler LOAD ra, 0(sp) - addi sp, sp, REGBYTES + addi sp, sp, TX_TRAP_CALL_FRAME_SIZE call _tx_thread_context_restore @@ -80,7 +99,7 @@ _tx_initialize_low_level: csrrc zero, mstatus, t0 li t0, (MSTATUS_MPP_M | MSTATUS_MPIE) csrrs zero, mstatus, t0 - li t0, (MIE_MTIE | MIE_MSIE | MIE_MEIE) + li t0, (MIE_MTIE | MIE_MEIE) // No MSIE: software interrupts are never dispatched here csrrs zero, mie, t0 #ifdef __riscv_flen @@ -89,11 +108,11 @@ _tx_initialize_low_level: fscsr x0 #endif - addi sp, sp, -REGBYTES + addi sp, sp, -TX_TRAP_CALL_FRAME_SIZE STORE ra, 0(sp) call board_init LOAD ra, 0(sp) - addi sp, sp, REGBYTES + addi sp, sp, TX_TRAP_CALL_FRAME_SIZE la t0, trap_entry csrw mtvec, t0 diff --git a/test/tx/cmake/riscv/regression/CMakeLists.txt b/test/tx/cmake/riscv/regression/CMakeLists.txt index 80b6e3bd..b15247ff 100644 --- a/test/tx/cmake/riscv/regression/CMakeLists.txt +++ b/test/tx/cmake/riscv/regression/CMakeLists.txt @@ -110,11 +110,17 @@ set(regression_test_cases ${SOURCE_DIR}/threadx_timer_multiple_accuracy_test.c ${SOURCE_DIR}/threadx_timer_multiple_test.c ${SOURCE_DIR}/threadx_timer_simple_test.c - # threadx_trace_basic_test.c excluded: references tx_saved_posture - # (the Linux port's interrupt save variable) which is named - # interrupt_save on RISC-V. + ${SOURCE_DIR}/threadx_trace_basic_test.c ) +# Architecture specific tests living next to this file. The RV32 stack +# builder still leaves the mstatus slot of the frame unwritten, so the new +# thread FPU state test only applies to RV64 for now. +if(THREADX_ARCH STREQUAL "risc-v64") + list(APPEND regression_test_cases + ${CMAKE_CURRENT_LIST_DIR}/threadx_riscv_new_thread_fpu_state_test.c) +endif() + # Tests that provide their own main() and must NOT link with testcontrol. set(standalone_test_cases ${SOURCE_DIR}/threadx_initialize_kernel_setup_test.c diff --git a/test/tx/cmake/riscv/regression/threadx_riscv_new_thread_fpu_state_test.c b/test/tx/cmake/riscv/regression/threadx_riscv_new_thread_fpu_state_test.c new file mode 100644 index 00000000..b388ad62 --- /dev/null +++ b/test/tx/cmake/riscv/regression/threadx_riscv_new_thread_fpu_state_test.c @@ -0,0 +1,278 @@ +/***************************************************************************/ +/* 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 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 */ +/***************************************************************************/ + +/* This test is designed to verify that a thread created after another + thread has used the floating point registers starts with a clean + floating point state. + + The interrupt stack frame reserves slot 29 for mstatus, and the context + restore path uses the FS field of that slot to decide whether the saved + floating point registers have to be recovered. When the stack builder + leaves the slot untouched, the decision is made on whatever the thread + stack happened to contain, so the new thread can silently inherit the + floating point registers of the thread that ran before it. + + Thread 0 fills every floating point register with a non-zero value and + then creates thread 1. The test checks that the stack builder wrote the + mstatus slot at all, that the slot describes a live floating point state, + and that thread 1 observes all floating point registers as zero once it + preempts thread 0. + + An unwritten slot still holds the stack fill pattern, so it is detected + by comparing the slot against a stack word the builder never touches. */ + +#include <stdio.h> +#include "tx_api.h" + + +/* The mstatus slot of the interrupt stack frame. */ + +#define FRAME_MSTATUS_INDEX 29 + + +#if defined(__riscv_float_abi_double) + +#define FP_SUPPORTED 1 +#define FP_LOAD "fld" +#define FP_STORE "fsd" +#define FP_WORD_SIZE "8" +typedef double FP_WORD; + +#elif defined(__riscv_float_abi_single) + +#define FP_SUPPORTED 1 +#define FP_LOAD "flw" +#define FP_STORE "fsw" +#define FP_WORD_SIZE "4" +typedef float FP_WORD; + +#else + +#define FP_SUPPORTED 0 + +#endif + + +#if FP_SUPPORTED + +/* Walk f0-f31 in register number order so that the index is both the + register number and the slot in the state buffers. */ + +#define FP_REG_LIST(op) \ + op(ft0, 0) op(ft1, 1) op(ft2, 2) op(ft3, 3) \ + op(ft4, 4) op(ft5, 5) op(ft6, 6) op(ft7, 7) \ + op(fs0, 8) op(fs1, 9) op(fa0, 10) op(fa1, 11) \ + op(fa2, 12) op(fa3, 13) op(fa4, 14) op(fa5, 15) \ + op(fa6, 16) op(fa7, 17) op(fs2, 18) op(fs3, 19) \ + op(fs4, 20) op(fs5, 21) op(fs6, 22) op(fs7, 23) \ + op(fs8, 24) op(fs9, 25) op(fs10, 26) op(fs11, 27) \ + op(ft8, 28) op(ft9, 29) op(ft10, 30) op(ft11, 31) + +#define FP_LOAD_ONE(reg, index) FP_LOAD " " #reg ", " #index "*" FP_WORD_SIZE "(%0)\n" +#define FP_STORE_ONE(reg, index) FP_STORE " " #reg ", " #index "*" FP_WORD_SIZE "(%0)\n" +#define FP_CLOBBER_ONE(reg, index) #reg, + +#define FP_REGISTERS 32 + +static FP_WORD dirty_pattern[FP_REGISTERS]; +static FP_WORD thread_1_fp_state[FP_REGISTERS]; +static unsigned long thread_1_fp_dirty = 0; + +#endif + + +static TX_THREAD thread_0; +static TX_THREAD thread_1; + +/* Give thread 1 a private stack so it can be cleared before the thread is + created. */ + +static unsigned long thread_1_stack[TEST_STACK_SIZE_PRINTF/sizeof(unsigned long)]; + +static unsigned long thread_1_executed = 0; + + +/* Define thread prototypes. */ + +static void thread_0_entry(ULONG thread_input); +static void thread_1_entry(ULONG thread_input); + + +/* Prototype for test control return. */ +void test_control_return(UINT status); + + +/* Define what the initial system looks like. */ + +#ifdef CTEST +void test_application_define(void *first_unused_memory) +#else +void threadx_riscv_new_thread_fpu_state_application_define(void *first_unused_memory) +#endif +{ + +UINT status; +CHAR *pointer; + + + /* Put first available memory address into a character pointer. */ + pointer = (CHAR *) first_unused_memory; + + status = tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0, + pointer, TEST_STACK_SIZE_PRINTF, + 16, 16, TX_NO_TIME_SLICE, TX_AUTO_START); + + /* Check for status. */ + if (status != TX_SUCCESS) + { + + printf("Running RISC-V New Thread FPU State Test............................ ERROR #1\n"); + test_control_return(1); + } +} + + + +/* Define the test threads. */ + +static void thread_0_entry(ULONG thread_input) +{ + +UINT status; +unsigned long *frame; +unsigned long stack_fill; +unsigned long frame_fs; +#if FP_SUPPORTED +UINT i; +#endif + + + /* Inform user. */ + printf("Running RISC-V New Thread FPU State Test............................ "); + +#if FP_SUPPORTED + + /* Build a pattern that is non-zero in every register. */ + for (i = 0; i < FP_REGISTERS; i++) + { + + dirty_pattern[i] = (FP_WORD) (i + 1); + } + + /* Make the floating point state of this thread dirty. */ + __asm__ volatile (FP_REG_LIST(FP_LOAD_ONE) + : + : "r" (dirty_pattern) + : FP_REG_LIST(FP_CLOBBER_ONE) "memory"); +#endif + + /* Create thread 1 without starting it, so the built frame can be + inspected before the thread is scheduled. */ + status = tx_thread_create(&thread_1, "thread 1", thread_1_entry, 0, + thread_1_stack, sizeof(thread_1_stack), + 15, 15, TX_NO_TIME_SLICE, TX_DONT_START); + + /* Check for status. */ + if (status != TX_SUCCESS) + { + + printf("ERROR #2\n"); + test_control_return(1); + } + + /* The bottom of the stack is never part of the frame, so it still holds + the fill pattern that thread creation left behind. */ + frame = (unsigned long *) thread_1.tx_thread_stack_ptr; + stack_fill = thread_1_stack[0]; + + /* The stack builder must have written the mstatus slot. */ + if (frame[FRAME_MSTATUS_INDEX] == stack_fill) + { + + printf("ERROR #3\n"); + test_control_return(1); + } + + /* The built frame must describe a live floating point state. */ + frame_fs = (frame[FRAME_MSTATUS_INDEX] >> 13) & 0x3UL; + + if (frame_fs == 0) + { + + printf("ERROR #4\n"); + test_control_return(1); + } + + /* Let thread 1 preempt and run to completion. */ + status = tx_thread_resume(&thread_1); + + /* Check for status. */ + if ((status != TX_SUCCESS) || (thread_1_executed != 1)) + { + + printf("ERROR #5\n"); + test_control_return(1); + } + +#if FP_SUPPORTED + + /* Thread 1 must not have inherited the floating point registers of + thread 0. */ + if (thread_1_fp_dirty) + { + + printf("ERROR #6\n"); + test_control_return(1); + } +#endif + + /* Successful test. */ + printf("SUCCESS!\n"); + test_control_return(0); +} + + +static void thread_1_entry(ULONG thread_input) +{ + +#if FP_SUPPORTED + +const unsigned char *state_bytes; +UINT i; + + + /* Capture the floating point registers before anything else can use + them. */ + __asm__ volatile (FP_REG_LIST(FP_STORE_ONE) + : + : "r" (thread_1_fp_state) + : "memory"); + + state_bytes = (const unsigned char *) thread_1_fp_state; + + for (i = 0; i < (UINT) sizeof(thread_1_fp_state); i++) + { + + if (state_bytes[i] != 0) + { + + thread_1_fp_dirty = 1; + break; + } + } +#endif + + thread_1_executed = 1; +} diff --git a/test/tx/cmake/riscv/run.sh b/test/tx/cmake/riscv/run.sh index 3b0905e7..0e4fb7a1 100755 --- a/test/tx/cmake/riscv/run.sh +++ b/test/tx/cmake/riscv/run.sh @@ -61,7 +61,11 @@ function generate() { function build() { local arch=$1 local build=$2 - cmake --build "build/${arch}_${build}" + # -k 0 keeps Ninja going after a target fails, so one broken target no + # longer decides whether the targets after it exist. ctest reports a + # missing binary as a failing test, which turned a single link error into + # a failure count that varied with build scheduling order. + cmake --build "build/${arch}_${build}" -- -k 0 } function run_test() { @@ -69,13 +73,20 @@ function run_test() { local build=$2 local parallel_jobs=$3 - pushd "build/${arch}_${build}" + # Guard the pushd: with the caller capturing this function's status, set -e + # no longer aborts here, so a missing build directory would otherwise let + # ctest run in the source tree and report "no tests" as success. + pushd "build/${arch}_${build}" || return 1 [ -z "${CTEST_PARALLEL_LEVEL}" ] && parallel="-j${parallel_jobs}" if [ -z "${CTEST_REPEAT_FAIL}" ]; then repeat_fail=2 else repeat_fail=${CTEST_REPEAT_FAIL} fi + # ctest's status is captured and returned past the popd. popd succeeds, so + # without this the function would return 0 and a failing configuration + # would be reported as a pass. + local status=0 ctest $parallel --timeout 1000 \ -O "${arch}_${build}.txt" \ -T test --no-compress-output \ @@ -83,8 +94,9 @@ function run_test() { --test-output-size-failed 4194304 \ --output-on-failure \ --repeat until-pass:${repeat_fail} \ - --output-junit "${arch}_${build}.xml" + --output-junit "${arch}_${build}.xml" || status=$? popd + return $status } # Determine repo root and script directory @@ -129,11 +141,16 @@ if [ "$command" == "build" ]; then echo "" done + # A failing configuration must not stop the ones after it: under set -e + # the loop would abort and leave them unbuilt, which then reads as a wall + # of missing-binary test failures. The status is accumulated and returned. + build_status=0 for item in $builds; do echo "Building ${arch} ${item}" - build "$arch" "$item" + build "$arch" "$item" || build_status=$? echo "" done + [ $build_status -eq 0 ] || exit $build_status elif [ "$command" == "test" ]; then cores=$(nproc) if [ -z "${CTEST_PARALLEL_LEVEL}" ]; then @@ -152,10 +169,16 @@ elif [ "$command" == "test" ]; then done exit $exit_code else + # ctest's status is accumulated rather than allowed to abort the loop. + # set -e would otherwise stop at the first failing configuration and + # leave every configuration after it untested. This mirrors what the + # parallel branch above already does with wait. + exit_code=0 for item in $builds; do echo "Testing ${arch} ${item}" - run_test "$arch" "$item" "$parallel_jobs" + run_test "$arch" "$item" "$parallel_jobs" || exit_code=$? done + exit $exit_code fi else help |
