/***************************************************************************/ /* 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 #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; }