summaryrefslogtreecommitdiff
path: root/test/tx/cmake/riscv
diff options
context:
space:
mode:
authorFrédéric Desbiens <[email protected]>2026-10-02 10:42:44 -0400
committerGitHub <[email protected]>2026-10-02 10:42:44 -0400
commit93387b0a60382a29579bbad5045c68328e3751df (patch)
tree42be790d602cb76f55090e5a7c418df7ea2b3428 /test/tx/cmake/riscv
parent44d7c95c582d415c4ad84527180b29c93c3bf664 (diff)
parent28281cd5647d4671236ca08d19bd18ed15b2265a (diff)
Merge pull request #783 from eclipse-threadx/devv6.5.2.202603_rel
Merged changes ahead of the v6.5.2.202603 release
Diffstat (limited to 'test/tx/cmake/riscv')
-rw-r--r--test/tx/cmake/riscv/CMakeLists.txt11
-rw-r--r--test/tx/cmake/riscv/bsp/entry.S6
-rw-r--r--test/tx/cmake/riscv/bsp/hwtimer.c84
-rw-r--r--test/tx/cmake/riscv/bsp/hwtimer.h4
-rw-r--r--test/tx/cmake/riscv/bsp/link.lds32
-rw-r--r--test/tx/cmake/riscv/bsp/plic.c25
-rw-r--r--test/tx/cmake/riscv/bsp/syscalls.c49
-rw-r--r--test/tx/cmake/riscv/bsp/trap.c3
-rw-r--r--test/tx/cmake/riscv/bsp/tx_initialize_low_level.S35
-rw-r--r--test/tx/cmake/riscv/regression/CMakeLists.txt12
-rw-r--r--test/tx/cmake/riscv/regression/threadx_riscv_new_thread_fpu_state_test.c278
-rwxr-xr-xtest/tx/cmake/riscv/run.sh33
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