Add QEMU based CI regression test infra for RV32 and RV64 (#526)

Added a QEMU virt-machine BSP and CTest infrastructure to run the
ThreadX regression suite on both RISC-V 32-bit and 64-bit targets
in CI.

New components:
- BSP (entry, trap, PLIC, CLINT timer, UART, linker script) targeting
  QEMU virt machine for RV32 and RV64
- CMake build system with Ninja, supporting multiple build configs
- CI scripts: install_riscv.sh (toolchain + QEMU), build_tx_riscv.sh,
  test_tx_riscv.sh
- GitHub Actions workflow job for RISC-V regression gating

Port fixes:
- RV32 tx_thread_context_restore.S: set MPIE alongside MPP (0x1800 →
  0x1880) so mret re-enables interrupts
- RV32/RV64 tx_port.h: add TX_REGRESSION_TEST extension macros needed
  by the test harness
- RV32/RV64 example_build scripts: add compile and QEMU launch steps

Regression test portability fixes:
- Block memory tests: increase pool sizes (320 → 340) to accommodate
  larger RISC-V block-header alignment
- Byte memory test: replace hardcoded offsets with BYTE_POOL_OVERHEAD
  macro for portable pool-size computation
- Event flag timeout test: make counter tolerance unconditional,
  removing linux-only guard

Signed-off-by: Akif Ejaz <akif.ejaz@10xengineers.ai>
This commit is contained in:
Akif Ejaz
2026-05-19 11:03:23 -04:00
committed by GitHub
parent 0dd6d28afb
commit c1e3678797
36 changed files with 1796 additions and 38 deletions
+18 -1
View File
@@ -41,6 +41,23 @@ jobs:
cmake_path: ./test/smp/cmake
result_affix: SMP
skip_deploy: true
riscv:
permissions:
contents: read
issues: read
checks: write
pull-requests: write
pages: write
id-token: write
uses: ./.github/workflows/regression_template.yml
with:
install_script: ./scripts/install_riscv.sh
build_script: ./scripts/build_tx_riscv.sh
test_script: ./scripts/test_tx_riscv.sh
cmake_path: ./test/tx/cmake/riscv
result_affix: RISC-V
skip_deploy: true
skip_coverage: true
deploy:
permissions:
contents: read
@@ -49,7 +66,7 @@ jobs:
pull-requests: write
pages: write
id-token: write
needs: [tx, smp]
needs: [tx, smp, riscv]
uses: ./.github/workflows/regression_template.yml
with:
skip_test: true
+2 -2
View File
@@ -85,7 +85,7 @@ TX_TRACE_OBJECT_ENTRY *entry_ptr;
/* Trace buffer is enabled, proceed. */
/* Pickup the total entries. */
entries = _tx_trace_total_registry_entries;
entries = (UINT) _tx_trace_total_registry_entries;
/* Determine if there are available entries in the registry. */
if (_tx_trace_available_registry_entries != ((ULONG) 0))
@@ -98,7 +98,7 @@ TX_TRACE_OBJECT_ENTRY *entry_ptr;
loop_break = TX_FALSE;
/* Loop to find available entry. */
i = _tx_trace_registry_search_start;
i = (UINT) _tx_trace_registry_search_start;
do
{
+1 -1
View File
@@ -78,7 +78,7 @@ TX_TRACE_OBJECT_ENTRY *entry_ptr;
/* Registry is setup, proceed. */
/* Pickup the total entries. */
entries = _tx_trace_total_registry_entries;
entries = (UINT) _tx_trace_total_registry_entries;
/* Loop to find available entry. */
for (i = ((ULONG) 0); i < entries; i++)
@@ -1,5 +1,24 @@
#!/bin/bash
printf "y\n" | rm -rf ../../../../../build/
rm -f kernel.elf
pushd ../../../../../
cmake -Bbuild -GNinja -DCMAKE_TOOLCHAIN_FILE=cmake/riscv32_gnu.cmake .
cmake --build ./build/
popd
popd
riscv32-unknown-elf-gcc \
-march=rv32gc -mabi=ilp32d \
-mcmodel=medany -O0 -g3 -Wall \
-ffunction-sections -fdata-sections \
-I../../../../../common/inc \
-I../../inc \
entry.s \
tx_initialize_low_level.S \
board.c uart.c hwtimer.c plic.c trap.c demo_threadx.c \
-L../../../../../build -lthreadx \
-T link.lds -nostartfiles \
-o kernel.elf
qemu-system-riscv32 -nographic -smp 1 -bios none -m 128M -machine virt -kernel kernel.elf
+98
View File
@@ -271,6 +271,104 @@ UINT _tx_thread_interrupt_control(UIN
#define TX_SEMAPHORE_DISABLE TX_DISABLE
/* Define automated coverage test extensions for the ThreadX regression test. */
#ifndef __ASSEMBLER__
typedef unsigned int TEST_FLAG;
extern TEST_FLAG threadx_byte_allocate_loop_test;
extern TEST_FLAG threadx_byte_release_loop_test;
extern TEST_FLAG threadx_mutex_suspension_put_test;
extern TEST_FLAG threadx_mutex_suspension_priority_test;
#ifndef TX_TIMER_PROCESS_IN_ISR
extern TEST_FLAG threadx_delete_timer_thread;
#endif
extern void abort_and_resume_byte_allocating_thread(void);
extern void abort_all_threads_suspended_on_mutex(void);
extern void suspend_lowest_priority(void);
#ifndef TX_TIMER_PROCESS_IN_ISR
extern void delete_timer_thread(void);
#endif
extern TEST_FLAG test_stack_analyze_flag;
extern TEST_FLAG test_initialize_flag;
extern TEST_FLAG test_forced_mutex_timeout;
#ifdef TX_REGRESSION_TEST
#define TX_BYTE_ALLOCATE_EXTENSION if (threadx_byte_allocate_loop_test == ((TEST_FLAG) 1)) \
{ \
pool_ptr -> tx_byte_pool_owner = TX_NULL; \
threadx_byte_allocate_loop_test = ((TEST_FLAG) 0); \
}
#define TX_BYTE_RELEASE_EXTENSION if (threadx_byte_release_loop_test == ((TEST_FLAG) 1)) \
{ \
threadx_byte_release_loop_test = ((TEST_FLAG) 0); \
abort_and_resume_byte_allocating_thread(); \
}
#define TX_MUTEX_PUT_EXTENSION_1 if (threadx_mutex_suspension_put_test == ((TEST_FLAG) 1)) \
{ \
threadx_mutex_suspension_put_test = ((TEST_FLAG) 0); \
abort_all_threads_suspended_on_mutex(); \
}
#define TX_MUTEX_PUT_EXTENSION_2 if (test_forced_mutex_timeout == ((TEST_FLAG) 1)) \
{ \
test_forced_mutex_timeout = ((TEST_FLAG) 0); \
_tx_thread_wait_abort(mutex_ptr -> tx_mutex_suspension_list); \
}
#define TX_MUTEX_PRIORITY_CHANGE_EXTENSION if (threadx_mutex_suspension_priority_test == ((TEST_FLAG) 1)) \
{ \
threadx_mutex_suspension_priority_test = ((TEST_FLAG) 0); \
suspend_lowest_priority(); \
}
#ifndef TX_TIMER_PROCESS_IN_ISR
#define TX_TIMER_INITIALIZE_EXTENSION(a) if (threadx_delete_timer_thread == ((TEST_FLAG) 1)) \
{ \
threadx_delete_timer_thread = ((TEST_FLAG) 0); \
delete_timer_thread(); \
(a) = ((UINT) 1); \
}
#endif
#define TX_THREAD_STACK_ANALYZE_EXTENSION if (test_stack_analyze_flag == ((TEST_FLAG) 1)) \
{ \
thread_ptr -> tx_thread_id = ((TEST_FLAG) 0); \
test_stack_analyze_flag = ((TEST_FLAG) 0); \
} \
else if (test_stack_analyze_flag == ((TEST_FLAG) 2)) \
{ \
stack_ptr = thread_ptr -> tx_thread_stack_start; \
test_stack_analyze_flag = ((TEST_FLAG) 0); \
} \
else if (test_stack_analyze_flag == ((TEST_FLAG) 3)) \
{ \
*stack_ptr = TX_STACK_FILL; \
test_stack_analyze_flag = ((TEST_FLAG) 0); \
} \
else \
{ \
test_stack_analyze_flag = ((TEST_FLAG) 0); \
}
#define TX_INITIALIZE_KERNEL_ENTER_EXTENSION if (test_initialize_flag == ((TEST_FLAG) 1)) \
{ \
test_initialize_flag = ((TEST_FLAG) 0); \
return; \
}
#endif /* TX_REGRESSION_TEST */
#endif /* __ASSEMBLER__ */
/* Define the version ID of ThreadX. This may be utilized by the application. */
#ifndef __ASSEMBLER__
@@ -284,7 +284,7 @@ _tx_thread_no_preempt_restore:
csrr t1, mstatus
li t2, 0x1888 // MPP(0x1800) | MPIE(0x80) | MIE(0x08)
li t3, 0x1800 // Set MPP to Machine mode
li t3, 0x1880 // Set MPP=Machine(0x1800) + MPIE(0x80) so mret re-enables MIE
li t4, ~0x1888 // Clear mask for MPP/MPIE/MIE
and t1, t1, t4
or t1, t1, t3
@@ -1,6 +1,24 @@
#!/bin/bash
printf "y\n" | rm -rf ../../../../../build/
rm -f kernel.elf
pushd ../../../../../
cmake -Bbuild -GNinja -DCMAKE_TOOLCHAIN_FILE=cmake/riscv64_gnu.cmake .
cmake --build ./build/
popd
riscv64-unknown-elf-gcc \
-march=rv64gc -mabi=lp64d \
-mcmodel=medany -O0 -g3 -Wall \
-ffunction-sections -fdata-sections \
-I../../../../../common/inc \
-I../../inc \
entry.s \
tx_initialize_low_level.S \
board.c uart.c hwtimer.c plic.c trap.c demo_threadx.c \
-L../../../../../build -lthreadx \
-T link.lds -nostartfiles \
-o kernel.elf
qemu-system-riscv64 -nographic -smp 1 -bios none -m 128M -machine virt -kernel kernel.elf
+98
View File
@@ -280,6 +280,104 @@ UINT _tx_thread_interrupt_control(UIN
#define TX_SEMAPHORE_DISABLE TX_DISABLE
/* Define automated coverage test extensions for the ThreadX regression test. */
#ifndef __ASSEMBLER__
typedef unsigned int TEST_FLAG;
extern TEST_FLAG threadx_byte_allocate_loop_test;
extern TEST_FLAG threadx_byte_release_loop_test;
extern TEST_FLAG threadx_mutex_suspension_put_test;
extern TEST_FLAG threadx_mutex_suspension_priority_test;
#ifndef TX_TIMER_PROCESS_IN_ISR
extern TEST_FLAG threadx_delete_timer_thread;
#endif
extern void abort_and_resume_byte_allocating_thread(void);
extern void abort_all_threads_suspended_on_mutex(void);
extern void suspend_lowest_priority(void);
#ifndef TX_TIMER_PROCESS_IN_ISR
extern void delete_timer_thread(void);
#endif
extern TEST_FLAG test_stack_analyze_flag;
extern TEST_FLAG test_initialize_flag;
extern TEST_FLAG test_forced_mutex_timeout;
#ifdef TX_REGRESSION_TEST
#define TX_BYTE_ALLOCATE_EXTENSION if (threadx_byte_allocate_loop_test == ((TEST_FLAG) 1)) \
{ \
pool_ptr -> tx_byte_pool_owner = TX_NULL; \
threadx_byte_allocate_loop_test = ((TEST_FLAG) 0); \
}
#define TX_BYTE_RELEASE_EXTENSION if (threadx_byte_release_loop_test == ((TEST_FLAG) 1)) \
{ \
threadx_byte_release_loop_test = ((TEST_FLAG) 0); \
abort_and_resume_byte_allocating_thread(); \
}
#define TX_MUTEX_PUT_EXTENSION_1 if (threadx_mutex_suspension_put_test == ((TEST_FLAG) 1)) \
{ \
threadx_mutex_suspension_put_test = ((TEST_FLAG) 0); \
abort_all_threads_suspended_on_mutex(); \
}
#define TX_MUTEX_PUT_EXTENSION_2 if (test_forced_mutex_timeout == ((TEST_FLAG) 1)) \
{ \
test_forced_mutex_timeout = ((TEST_FLAG) 0); \
_tx_thread_wait_abort(mutex_ptr -> tx_mutex_suspension_list); \
}
#define TX_MUTEX_PRIORITY_CHANGE_EXTENSION if (threadx_mutex_suspension_priority_test == ((TEST_FLAG) 1)) \
{ \
threadx_mutex_suspension_priority_test = ((TEST_FLAG) 0); \
suspend_lowest_priority(); \
}
#ifndef TX_TIMER_PROCESS_IN_ISR
#define TX_TIMER_INITIALIZE_EXTENSION(a) if (threadx_delete_timer_thread == ((TEST_FLAG) 1)) \
{ \
threadx_delete_timer_thread = ((TEST_FLAG) 0); \
delete_timer_thread(); \
(a) = ((UINT) 1); \
}
#endif
#define TX_THREAD_STACK_ANALYZE_EXTENSION if (test_stack_analyze_flag == ((TEST_FLAG) 1)) \
{ \
thread_ptr -> tx_thread_id = ((TEST_FLAG) 0); \
test_stack_analyze_flag = ((TEST_FLAG) 0); \
} \
else if (test_stack_analyze_flag == ((TEST_FLAG) 2)) \
{ \
stack_ptr = thread_ptr -> tx_thread_stack_start; \
test_stack_analyze_flag = ((TEST_FLAG) 0); \
} \
else if (test_stack_analyze_flag == ((TEST_FLAG) 3)) \
{ \
*stack_ptr = TX_STACK_FILL; \
test_stack_analyze_flag = ((TEST_FLAG) 0); \
} \
else \
{ \
test_stack_analyze_flag = ((TEST_FLAG) 0); \
}
#define TX_INITIALIZE_KERNEL_ENTER_EXTENSION if (test_initialize_flag == ((TEST_FLAG) 1)) \
{ \
test_initialize_flag = ((TEST_FLAG) 0); \
return; \
}
#endif /* TX_REGRESSION_TEST */
#endif /* __ASSEMBLER__ */
/* Define the version ID of ThreadX. This may be utilized by the application. */
#ifndef __ASSEMBLER__
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
# Build RISC-V regression tests for both RV32 and RV64.
# Usage: build_tx_riscv.sh [all|<config>]
SCRIPT_DIR="$(dirname "$(realpath "$0")")"
RUN_SH="${SCRIPT_DIR}/../test/tx/cmake/riscv/run.sh"
ARGS="${@:-all}"
echo "=== Building RISC-V32 ==="
"$RUN_SH" riscv32 build $ARGS
echo ""
echo "=== Building RISC-V64 ==="
"$RUN_SH" riscv64 build $ARGS
+32
View File
@@ -0,0 +1,32 @@
#!/bin/bash
# Install RISC-V bare-metal cross-compiler toolchain and QEMU for CI.
set -e
RELEASE_TAG="2026.04.26"
BASE_URL="https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/${RELEASE_TAG}"
# Use ubuntu-24.04 binaries to match ubuntu-latest runners.
RV32_TARBALL="riscv32-elf-ubuntu-24.04-gcc.tar.xz"
RV64_TARBALL="riscv64-elf-ubuntu-24.04-gcc.tar.xz"
echo "=== Installing QEMU and build tools ==="
sudo apt-get update -qq
sudo apt-get install -y -qq qemu-system-misc ninja-build cmake
echo "=== Downloading RISC-V GCC toolchain (${RELEASE_TAG}) ==="
# Both tarballs extract into riscv/ with non-overlapping prefixes
# (riscv32-unknown-elf-* and riscv64-unknown-elf-*).
for tarball in "$RV32_TARBALL" "$RV64_TARBALL"; do
echo "Downloading ${tarball} ..."
wget --no-verbose "${BASE_URL}/${tarball}" -O "/tmp/${tarball}"
sudo tar xJf "/tmp/${tarball}" -C /opt
rm "/tmp/${tarball}"
done
TOOLCHAIN_BIN=/opt/riscv/bin
echo "$TOOLCHAIN_BIN" >> "$GITHUB_PATH"
echo "=== Verifying installation ==="
"$TOOLCHAIN_BIN/riscv32-unknown-elf-gcc" --version | head -1
"$TOOLCHAIN_BIN/riscv64-unknown-elf-gcc" --version | head -1
qemu-system-riscv64 --version | head -1
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
# Run RISC-V regression tests for both RV32 and RV64 on QEMU.
# Usage: test_tx_riscv.sh [all|<config>]
SCRIPT_DIR="$(dirname "$(realpath "$0")")"
RUN_SH="${SCRIPT_DIR}/../test/tx/cmake/riscv/run.sh"
ARGS="${@:-all}"
exit_code=0
echo "=== Testing RISC-V32 ==="
CTEST_PARALLEL_LEVEL=4 "$RUN_SH" riscv32 test $ARGS || exit_code=$?
echo ""
echo "=== Testing RISC-V64 ==="
CTEST_PARALLEL_LEVEL=4 "$RUN_SH" riscv64 test $ARGS || exit_code=$?
exit $exit_code
+79
View File
@@ -0,0 +1,79 @@
cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
cmake_policy(SET CMP0054 NEW)
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
stack_checking_build stack_checking_rand_fill_build trace_build)
set(CMAKE_CONFIGURATION_TYPES
${BUILD_CONFIGURATIONS}
CACHE STRING "list of supported configuration types" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
${CMAKE_CONFIGURATION_TYPES})
list(GET CMAKE_CONFIGURATION_TYPES 0 BUILD_TYPE)
if((NOT CMAKE_BUILD_TYPE) OR (NOT ("${CMAKE_BUILD_TYPE}" IN_LIST
CMAKE_CONFIGURATION_TYPES)))
set(CMAKE_BUILD_TYPE
"${BUILD_TYPE}"
CACHE STRING "Build Type of the project" FORCE)
endif()
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
message(STATUS "Using toolchain file: ${CMAKE_TOOLCHAIN_FILE}.")
message(STATUS "THREADX_ARCH: ${THREADX_ARCH}")
# Per-configuration compile definitions
set(default_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)
add_compile_options(
-std=c99
-O0
-g3
-fdiagnostics-color
-Werror
-ffunction-sections
-fdata-sections
-DTX_REGRESSION_TEST
-DTEST_STACK_SIZE_PRINTF=4096
-DEXTERNAL_EXIT
${${CMAKE_BUILD_TYPE}}
)
enable_testing()
# Add ThreadX library (uses the RISC-V toolchain set by CMAKE_TOOLCHAIN_FILE)
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../../../.. threadx)
# Add BSP
add_subdirectory(bsp)
# Add regression tests
add_subdirectory(regression)
# Strict warning flags for the ThreadX library build
target_compile_options(
threadx
PRIVATE -Werror
-Wall
-Wextra
-pedantic
-fmessage-length=0
-fsigned-char
-ffunction-sections
-fdata-sections
-Wunused
-Wuninitialized
-Wmissing-declarations
-Wconversion
-Wpointer-arith
-Wlogical-op
-Waggregate-return
-Wfloat-equal
$<$<COMPILE_LANGUAGE:C>:-include$<SEMICOLON>stdlib.h>
)
+25
View File
@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
# BSP library for RISC-V QEMU virt regression tests.
# Provides board init, UART, CLINT timer, PLIC, trap handler,
# newlib syscall stubs, and low-level ThreadX initialization.
project(riscv_bsp LANGUAGES C ASM)
set(BSP_DIR ${CMAKE_CURRENT_LIST_DIR})
add_library(riscv_bsp
${BSP_DIR}/entry.S
${BSP_DIR}/tx_initialize_low_level.S
${BSP_DIR}/board.c
${BSP_DIR}/trap.c
${BSP_DIR}/hwtimer.c
${BSP_DIR}/plic.c
${BSP_DIR}/uart.c
${BSP_DIR}/syscalls.c
${BSP_DIR}/printf.c
)
target_include_directories(riscv_bsp PUBLIC ${BSP_DIR})
target_link_libraries(riscv_bsp PUBLIC azrtos::threadx)
target_compile_definitions(riscv_bsp PUBLIC EXTERNAL_EXIT)
+30
View File
@@ -0,0 +1,30 @@
/***************************************************************************
* Copyright (c) 2026 10xEngineers
*
* 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
**************************************************************************/
#include "plic.h"
#include "hwtimer.h"
#include "uart.h"
#include <stdint.h>
#include <stddef.h>
int board_init(void)
{
int ret;
ret = plic_init();
if (ret)
return ret;
ret = uart_init();
if (ret)
return ret;
ret = hwtimer_init();
if (ret)
return ret;
return 0;
}
+84
View File
@@ -0,0 +1,84 @@
/***************************************************************************
* Copyright (c) 2026 10xEngineers
*
* 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
**************************************************************************/
#ifndef RISCV_CSR_H
#define RISCV_CSR_H
/* Machine Status Register, mstatus */
#define MSTATUS_MPP_MASK (3L << 11)
#define MSTATUS_MPP_M (3L << 11)
#define MSTATUS_MPP_S (1L << 11)
#define MSTATUS_MPP_U (0L << 11)
#define MSTATUS_MIE (1L << 3)
#define MSTATUS_MPIE (1L << 7)
#define MSTATUS_FS (1L << 13)
/* Machine-mode Interrupt Enable */
#define MIE_MTIE (1L << 7)
#define MIE_MSIE (1L << 3)
#define MIE_MEIE (1L << 11)
#define MIE_STIE (1L << 5)
#define MIE_SSIE (1L << 1)
#define MIE_SEIE (1L << 9)
#ifndef __ASSEMBLER__
#include <stdint.h>
static inline uintptr_t riscv_get_core(void)
{
uintptr_t x;
__asm__ volatile("csrr %0, mhartid" : "=r" (x));
return x;
}
static inline uintptr_t riscv_get_mstatus(void)
{
uintptr_t x;
__asm__ volatile("csrr %0, mstatus" : "=r" (x));
return x;
}
static inline void riscv_writ_mstatus(uintptr_t x)
{
__asm__ volatile("csrw mstatus, %0" : : "r" (x));
}
static inline void riscv_mintr_on(void)
{
uintptr_t mstatus = riscv_get_mstatus();
mstatus |= MSTATUS_MIE;
riscv_writ_mstatus(mstatus);
}
static inline void riscv_mintr_off(void)
{
uintptr_t mstatus = riscv_get_mstatus();
mstatus &= (~MSTATUS_MIE);
riscv_writ_mstatus(mstatus);
}
static inline int riscv_mintr_get(void)
{
uintptr_t x = riscv_get_mstatus();
return (x & MSTATUS_MIE) != 0;
}
static inline void riscv_mintr_restore(int x)
{
if (x)
riscv_mintr_on();
else
riscv_mintr_off();
}
#endif /* __ASSEMBLER__ */
#endif /* RISCV_CSR_H */
+75
View File
@@ -0,0 +1,75 @@
/***************************************************************************
* Copyright (c) 2026 10xEngineers
*
* 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
**************************************************************************/
/* Boot code for RISC-V QEMU virt regression tests.
Works for both RV32 and RV64 (instructions are width-agnostic). */
.section .text.init
.align 4
.global _start
.extern main
.extern _sysstack_end
.extern _bss_start
.extern _bss_end
_start:
csrr t0, mhartid
bne t0, zero, _secondary_halt
/* Zero all general-purpose registers. */
li x1, 0
li x2, 0
li x3, 0
li x4, 0
li x5, 0
li x6, 0
li x7, 0
li x8, 0
li x9, 0
li x10, 0
li x11, 0
li x12, 0
li x13, 0
li x14, 0
li x15, 0
li x16, 0
li x17, 0
li x18, 0
li x19, 0
li x20, 0
li x21, 0
li x22, 0
li x23, 0
li x24, 0
li x25, 0
li x26, 0
li x27, 0
li x28, 0
li x29, 0
li x30, 0
li x31, 0
/* Set up stack pointer from linker symbol. */
la sp, _sysstack_end
/* Clear BSS. */
la t0, _bss_start
la t1, _bss_end
_bss_clean:
bgeu t0, t1, _bss_done
sb zero, 0(t0)
addi t0, t0, 1
j _bss_clean
_bss_done:
call main
_secondary_halt:
wfi
j _secondary_halt
+33
View File
@@ -0,0 +1,33 @@
/***************************************************************************
* Copyright (c) 2026 10xEngineers
*
* 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
**************************************************************************/
#include "csr.h"
#include "hwtimer.h"
#include <tx_port.h>
#define CLINT (0x02000000L)
#define CLINT_TIME (CLINT + 0xBFF8)
#define CLINT_TIMECMP(id) (CLINT + 0x4000 + 8 * (id))
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;
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;
return 0;
}
+22
View File
@@ -0,0 +1,22 @@
/***************************************************************************
* Copyright (c) 2026 10xEngineers
*
* 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
**************************************************************************/
#ifndef RISCV_HWTIMER_H
#define RISCV_HWTIMER_H
#include <stdint.h>
#define TICKNUM_PER_SECOND 10000000
#define TICKNUM_PER_TIMER (TICKNUM_PER_SECOND / 10)
int hwtimer_init(void);
int hwtimer_handler(void);
#endif /* RISCV_HWTIMER_H */
+60
View File
@@ -0,0 +1,60 @@
/***************************************************************************
* Linker script for RISC-V QEMU virt regression tests.
* Provides 128 MB RAM starting at 0x80000000 (QEMU virt default).
* System stack is 16 KB to accommodate test framework overhead.
**************************************************************************/
OUTPUT_ARCH( "riscv" )
ENTRY( _start )
MEMORY
{
RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 128M
}
SECTIONS
{
. = 0x80000000;
.text : {
KEEP(*(.text.init))
*(.text .text.*)
. = ALIGN(0x1000);
PROVIDE(etext = .);
} > RAM
.rodata : {
. = ALIGN(16);
*(.srodata .srodata.*)
. = ALIGN(16);
*(.rodata .rodata.*)
} > RAM
.data : {
. = ALIGN(16);
*(.sdata .sdata.*)
. = ALIGN(16);
*(.data .data.*)
} > RAM
.bss : {
. = ALIGN(16);
_bss_start = .;
*(.sbss .sbss.*)
. = ALIGN(16);
*(.bss .bss.*)
*(COMMON)
_bss_end = .;
} > RAM
.stack : {
. = ALIGN(4096);
_sysstack_start = .;
. += 0x4000;
_sysstack_end = .;
} > RAM
PROVIDE(_end = .);
ASSERT(. <= ORIGIN(RAM) + LENGTH(RAM), "Image exceeds available RAM")
}
+87
View File
@@ -0,0 +1,87 @@
/***************************************************************************
* Copyright (c) 2026 10xEngineers
*
* 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
**************************************************************************/
#include "plic.h"
#include <stddef.h>
irq_callback callbacks[MAX_CALLBACK_NUM];
void plic_irq_enable(int irqno)
{
uintptr_t hart = riscv_get_core();
volatile uint32_t *reg = (volatile uint32_t *)(PLIC_MENABLE(hart) + (irqno / 32) * 4);
*reg = *reg | (1U << (irqno % 32));
}
void plic_irq_disable(int irqno)
{
uintptr_t hart = riscv_get_core();
volatile uint32_t *reg = (volatile uint32_t *)(PLIC_MENABLE(hart) + (irqno / 32) * 4);
*reg = *reg & ~(1U << (irqno % 32));
}
void plic_prio_set(int irqno, int prio)
{
PLIC_SET_PRIO(irqno, prio);
}
int plic_prio_get(int irqno)
{
return (int)PLIC_GET_PRIO(irqno);
}
int plic_register_callback(int irqno, irq_callback callback)
{
if (!(irqno >= 0 && irqno < MAX_CALLBACK_NUM))
return -1;
callbacks[irqno] = callback;
return 0;
}
int plic_unregister_callback(int irqno)
{
return plic_register_callback(irqno, NULL);
}
int plic_init(void)
{
for (int i = 0; i < MAX_CALLBACK_NUM; i++)
{
callbacks[i] = NULL;
}
return 0;
}
int plic_claim(void)
{
uintptr_t hart = riscv_get_core();
return (int)(*(uint32_t *)PLIC_MCLAIM(hart));
}
void plic_complete(int irqno)
{
uintptr_t hart = riscv_get_core();
*(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 (callbacks[irqno] != NULL)
ret = (callbacks[irqno])(irqno);
plic_complete(irqno);
return ret;
}
+46
View File
@@ -0,0 +1,46 @@
/***************************************************************************
* Copyright (c) 2026 10xEngineers
*
* 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
**************************************************************************/
#ifndef RISCV_PLIC_H
#define RISCV_PLIC_H
#include "csr.h"
#include <stdint.h>
#define PLIC 0x0c000000L
#define PLIC_PRIORITY (PLIC + 0x0)
#define PLIC_PENDING (PLIC + 0x1000)
#define PLIC_MENABLE(hart) (PLIC + 0x2000 + (hart) * 0x100)
#define PLIC_SENABLE(hart) (PLIC + 0x2080 + (hart) * 0x100)
#define PLIC_MPRIORITY(hart) (PLIC + 0x200000 + (hart) * 0x2000)
#define PLIC_SPRIORITY(hart) (PLIC + 0x201000 + (hart) * 0x2000)
#define PLIC_MCLAIM(hart) (PLIC + 0x200004 + (hart) * 0x2000)
#define PLIC_SCLAIM(hart) (PLIC + 0x201004 + (hart) * 0x2000)
#define PLIC_MCOMPLETE(hart) (PLIC + 0x200004 + (hart) * 0x2000)
#define PLIC_SCOMPLETE(hart) (PLIC + 0x201004 + (hart) * 0x2000)
#define PLIC_GET_PRIO(irqno) (*(uint32_t *)(PLIC_PRIORITY + (irqno) * 4))
#define PLIC_SET_PRIO(irqno, prio) (*(uint32_t *)(PLIC_PRIORITY + (irqno) * 4) = (prio))
#define MAX_CALLBACK_NUM 128
typedef int (*irq_callback)(int irqno);
void plic_irq_enable(int irqno);
void plic_irq_disable(int irqno);
int plic_prio_get(int irqno);
void plic_prio_set(int irqno, int prio);
int plic_register_callback(int irqno, irq_callback callback);
int plic_unregister_callback(int irqno);
int plic_init(void);
int plic_claim(void);
void plic_complete(int irqno);
int plic_irq_intr(void);
#endif /* RISCV_PLIC_H */
+168
View File
@@ -0,0 +1,168 @@
/***************************************************************************
* Copyright (c) 2026 10xEngineers
*
* 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
**************************************************************************/
/* Minimal printf/puts for RISC-V QEMU virt regression tests.
Avoids linking newlib's stdio which is compiled with mcmodel=medlow
and causes relocation overflow on RV64 (DRAM at 0x80000000).
Only the format specifiers used by the test suite are supported:
%s, %d, %u, %lu, %ld, %%, and plain strings. */
#include <stdarg.h>
#include <stddef.h>
extern int _write(int fd, const char *buf, int count);
static void print_str(const char *s)
{
const char *p = s;
while (*p)
p++;
_write(1, s, (int)(p - s));
}
static void print_unsigned(unsigned long val)
{
char buf[21];
int i = 0;
if (val == 0)
{
_write(1, "0", 1);
return;
}
while (val > 0)
{
buf[i++] = (char)('0' + (int)(val % 10u));
val /= 10u;
}
/* Reverse. */
for (int j = i - 1; j >= 0; j--)
_write(1, &buf[j], 1);
}
static void print_hex(unsigned long val)
{
static const char hex[] = "0123456789abcdef";
char buf[17];
int i = 0;
if (val == 0)
{
_write(1, "0", 1);
return;
}
while (val > 0)
{
buf[i++] = hex[val & 0xfu];
val >>= 4;
}
for (int j = i - 1; j >= 0; j--)
_write(1, &buf[j], 1);
}
static void print_signed(long val)
{
if (val < 0)
{
_write(1, "-", 1);
print_unsigned((unsigned long)(-val));
}
else
{
print_unsigned((unsigned long)val);
}
}
int printf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
const char *p = fmt;
const char *seg = p;
while (*p)
{
if (*p == '%')
{
/* Flush preceding literal segment. */
if (p > seg)
_write(1, seg, (int)(p - seg));
p++;
/* Check for 'l' length modifier. */
int is_long = 0;
if (*p == 'l')
{
is_long = 1;
p++;
}
switch (*p)
{
case 's':
print_str(va_arg(ap, const char *));
break;
case 'd':
if (is_long)
print_signed(va_arg(ap, long));
else
print_signed((long)va_arg(ap, int));
break;
case 'u':
if (is_long)
print_unsigned(va_arg(ap, unsigned long));
else
print_unsigned((unsigned long)va_arg(ap, unsigned int));
break;
case 'x':
if (is_long)
print_hex(va_arg(ap, unsigned long));
else
print_hex((unsigned long)va_arg(ap, unsigned int));
break;
case '%':
_write(1, "%", 1);
break;
default:
/* Unknown specifier — print as-is. */
_write(1, "%", 1);
if (is_long)
_write(1, "l", 1);
_write(1, p, 1);
break;
}
p++;
seg = p;
}
else
{
p++;
}
}
/* Flush remaining literal segment. */
if (p > seg)
_write(1, seg, (int)(p - seg));
va_end(ap);
return 0;
}
int puts(const char *s)
{
print_str(s);
_write(1, "\n", 1);
return 0;
}
+128
View File
@@ -0,0 +1,128 @@
/***************************************************************************
* Copyright (c) 2026 10xEngineers
*
* 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
**************************************************************************/
/* Newlib system call stubs for RISC-V QEMU virt regression tests.
Provides _write (UART output for printf), _exit (QEMU test finisher),
and other minimal stubs needed by newlib. */
#include <sys/stat.h>
#include <errno.h>
#include <stdint.h>
#define UART0_THR (*(volatile unsigned char *)0x10000000L)
#define UART0_LSR (*(volatile unsigned char *)0x10000005L)
#define LSR_TX_IDLE (1 << 5)
/* QEMU virt sifive_test device for clean exit. */
#define VIRT_TEST (*(volatile uint32_t *)0x100000L)
#define VIRT_TEST_PASS 0x5555u
#define VIRT_TEST_FAIL 0x3333u
/* Called by testcontrol.c when EXTERNAL_EXIT is defined. */
__attribute__((noreturn)) void external_exit(unsigned int code)
{
if (code == 0)
VIRT_TEST = VIRT_TEST_PASS;
else
VIRT_TEST = (code << 16) | VIRT_TEST_FAIL;
/* Should not reach here, but halt if it does. */
while (1)
;
}
int _write(int fd, const char *buf, int count)
{
(void)fd;
for (int i = 0; i < count; i++)
{
while ((UART0_LSR & LSR_TX_IDLE) == 0)
;
UART0_THR = (unsigned char)buf[i];
}
return count;
}
extern char _end[];
static char *heap_ptr = 0;
void *_sbrk(int incr)
{
if (heap_ptr == 0)
heap_ptr = _end;
char *prev = heap_ptr;
heap_ptr += incr;
return prev;
}
int _close(int fd)
{
(void)fd;
errno = EBADF;
return -1;
}
int _fstat(int fd, struct stat *st)
{
(void)fd;
st->st_mode = S_IFCHR;
return 0;
}
int _isatty(int fd)
{
(void)fd;
return 1;
}
int _lseek(int fd, int offset, int whence)
{
(void)fd;
(void)offset;
(void)whence;
errno = ESPIPE;
return -1;
}
int _read(int fd, char *buf, int count)
{
(void)fd;
(void)buf;
(void)count;
return 0;
}
__attribute__((noreturn)) void _exit(int code)
{
external_exit((unsigned int)code);
}
/* Override newlib's exit() to avoid pulling in __call_exitprocs and
__stdio_exit_handler (compiled with mcmodel=medlow). */
__attribute__((noreturn)) void exit(int code)
{
external_exit((unsigned int)code);
}
/* Minimal rand/srand to avoid pulling newlib's version which uses
_impure_ptr (compiled with mcmodel=medlow, unreachable on RV64). */
static unsigned long rand_seed = 1;
int rand(void)
{
rand_seed = rand_seed * 1103515245UL + 12345UL;
return (int)((rand_seed >> 16) & 0x7fff);
}
void srand(unsigned int seed)
{
rand_seed = seed;
}
+84
View File
@@ -0,0 +1,84 @@
/***************************************************************************
* Copyright (c) 2026 10xEngineers
*
* 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
**************************************************************************/
/* Trap handler for RISC-V QEMU virt regression tests.
Calls test_interrupt_dispatch() BEFORE _tx_timer_interrupt() to match
the Linux port's behavior (generate_test_file.sh injection order). */
#include "csr.h"
#include <stdint.h>
#include <stdio.h>
#include "uart.h"
#include "hwtimer.h"
#include "plic.h"
#include <tx_port.h>
#include <tx_api.h>
#include <tx_trace.h>
/* Interrupt bit is the MSB of mcause */
#define MCAUSE_INT_BIT ((uintptr_t)1 << (sizeof(uintptr_t) * 8 - 1))
#define OS_IS_INTERUPT(mcause) ((mcause) & MCAUSE_INT_BIT)
#define OS_IS_TICK_INT(mcause) ((mcause) == (MCAUSE_INT_BIT | 7u))
#define OS_IS_SOFT_INT(mcause) ((mcause) == (MCAUSE_INT_BIT | 3u))
#define OS_IS_EXT_INT(mcause) ((mcause) == (MCAUSE_INT_BIT | 11u))
extern void _tx_timer_interrupt(void);
extern void test_interrupt_dispatch(void) __attribute__((weak));
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))
{
hwtimer_handler();
/* Trace ISR enter event (matches Linux port behavior). */
_tx_trace_isr_enter_insert(0);
/* Call test ISR dispatch BEFORE timer interrupt (matches Linux). */
if (test_interrupt_dispatch)
test_interrupt_dispatch();
_tx_timer_interrupt();
/* Trace ISR exit event. */
_tx_trace_isr_exit_insert(0);
}
else if (OS_IS_EXT_INT(mcause))
{
int ret = plic_irq_intr();
if (ret)
{
uart_puts("[INTERRUPT]: handler irq error!");
while (1)
;
}
}
else
{
uart_puts("[INTERRUPT]: unhandled interrupt!");
while (1)
;
}
}
else
{
uart_puts("[EXCEPTION]: unhandled exception!");
printf(" mcause=0x%lx mepc=0x%lx mtval=0x%lx\n",
(unsigned long)mcause, (unsigned long)mepc, (unsigned long)mtval);
_exit(1);
}
}
@@ -0,0 +1,100 @@
/***************************************************************************
* Copyright (c) 2026 10xEngineers
*
* 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
**************************************************************************/
/* 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"
#if __riscv_xlen == 64
#define STORE sd
#define LOAD ld
#define REGBYTES 8
#else
#define STORE sw
#define LOAD lw
#define REGBYTES 4
#endif
.section .text
.align 4
/**************************************************************************/
/* trap_entry — saves context, calls C trap_handler, restores context. */
/**************************************************************************/
.global trap_entry
.extern trap_handler
.extern _tx_thread_context_restore
trap_entry:
#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
addi sp, sp, -(65 * REGBYTES)
#else
addi sp, sp, -(32 * REGBYTES)
#endif
STORE x1, (28 * REGBYTES)(sp)
call _tx_thread_context_save
csrr a0, mcause
csrr a1, mepc
csrr a2, mtval
addi sp, sp, -REGBYTES
STORE ra, 0(sp)
call trap_handler
LOAD ra, 0(sp)
addi sp, sp, REGBYTES
call _tx_thread_context_restore
_trap_err:
wfi
j _trap_err
/**************************************************************************/
/* _tx_initialize_low_level — hardware init for QEMU virt. */
/**************************************************************************/
.section .text
.global _tx_initialize_low_level
.extern _end
.extern board_init
_tx_initialize_low_level:
la t0, _tx_thread_system_stack_ptr
STORE sp, 0(t0)
la t0, _end
la t1, _tx_initialize_unused_memory
STORE t0, 0(t1)
li t0, MSTATUS_MIE
csrrc zero, mstatus, t0
li t0, (MSTATUS_MPP_M | MSTATUS_MPIE)
csrrs zero, mstatus, t0
li t0, (MIE_MTIE | MIE_MSIE | MIE_MEIE)
csrrs zero, mie, t0
#ifdef __riscv_flen
li t0, MSTATUS_FS
csrrs zero, mstatus, t0
fscsr x0
#endif
addi sp, sp, -REGBYTES
STORE ra, 0(sp)
call board_init
LOAD ra, 0(sp)
addi sp, sp, REGBYTES
la t0, trap_entry
csrw mtvec, t0
ret
+79
View File
@@ -0,0 +1,79 @@
/***************************************************************************
* Copyright (c) 2026 10xEngineers
*
* 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
**************************************************************************/
#include "uart.h"
#include "csr.h"
#include "plic.h"
#include <stdint.h>
#define Reg(reg) ((volatile unsigned char *)(UART0 + (reg)))
#define RHR 0
#define THR 0
#define IER 1
#define IER_RX_ENABLE (1<<0)
#define IER_TX_ENABLE (1<<1)
#define FCR 2
#define FCR_FIFO_ENABLE (1<<0)
#define FCR_FIFO_CLEAR (3<<1)
#define LCR 3
#define LCR_EIGHT_BITS (3<<0)
#define LCR_BAUD_LATCH (1<<7)
#define LSR 5
#define LSR_RX_READY (1<<0)
#define LSR_TX_IDLE (1<<5)
#define ReadReg(reg) (*(Reg(reg)))
#define WriteReg(reg, v) (*(Reg(reg)) = (v))
int uart_init(void)
{
WriteReg(IER, 0x00);
WriteReg(LCR, LCR_BAUD_LATCH);
WriteReg(0, 0x03);
WriteReg(1, 0x00);
WriteReg(LCR, LCR_EIGHT_BITS);
WriteReg(FCR, FCR_FIFO_ENABLE | FCR_FIFO_CLEAR);
plic_irq_enable(UART0_IRQ);
plic_prio_set(UART0_IRQ, 1);
return 0;
}
void uart_putc_nolock(int ch)
{
while ((ReadReg(LSR) & LSR_TX_IDLE) == 0)
;
WriteReg(THR, ch);
}
int uart_putc(int ch)
{
int intr_enable = riscv_mintr_get();
riscv_mintr_off();
uart_putc_nolock(ch);
riscv_mintr_restore(intr_enable);
return 1;
}
int uart_puts(const char *str)
{
int i;
int intr_enable = riscv_mintr_get();
riscv_mintr_off();
for (i = 0; str[i] != 0; i++)
{
uart_putc_nolock(str[i]);
}
uart_putc_nolock('\n');
riscv_mintr_restore(intr_enable);
return i;
}
+22
View File
@@ -0,0 +1,22 @@
/***************************************************************************
* Copyright (c) 2026 10xEngineers
*
* 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
**************************************************************************/
#ifndef RISCV_UART_H
#define RISCV_UART_H
#define UART0 0x10000000L
#define UART0_IRQ 10
int uart_init(void);
int uart_putc(int ch);
void uart_putc_nolock(int ch);
int uart_puts(const char *str);
#endif /* RISCV_UART_H */
@@ -0,0 +1,165 @@
cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
cmake_policy(SET CMP0057 NEW)
project(riscv_regression_test LANGUAGES C ASM)
set(SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../regression)
set(BSP_DIR ${CMAKE_CURRENT_LIST_DIR}/../bsp)
# Determine QEMU arch from THREADX_ARCH
if(THREADX_ARCH STREQUAL "risc-v64")
set(QEMU_SYSTEM "qemu-system-riscv64")
set(XLEN_TAG "rv64")
else()
set(QEMU_SYSTEM "qemu-system-riscv32")
set(XLEN_TAG "rv32")
endif()
set(regression_test_cases
${SOURCE_DIR}/threadx_block_memory_basic_test.c
${SOURCE_DIR}/threadx_block_memory_error_detection_test.c
${SOURCE_DIR}/threadx_block_memory_information_test.c
${SOURCE_DIR}/threadx_block_memory_prioritize_test.c
${SOURCE_DIR}/threadx_block_memory_suspension_test.c
${SOURCE_DIR}/threadx_block_memory_suspension_timeout_test.c
${SOURCE_DIR}/threadx_block_memory_thread_terminate_test.c
${SOURCE_DIR}/threadx_byte_memory_basic_test.c
${SOURCE_DIR}/threadx_byte_memory_information_test.c
${SOURCE_DIR}/threadx_byte_memory_prioritize_test.c
${SOURCE_DIR}/threadx_byte_memory_suspension_test.c
${SOURCE_DIR}/threadx_byte_memory_suspension_timeout_test.c
${SOURCE_DIR}/threadx_byte_memory_thread_contention_test.c
${SOURCE_DIR}/threadx_byte_memory_thread_terminate_test.c
${SOURCE_DIR}/threadx_event_flag_basic_test.c
${SOURCE_DIR}/threadx_event_flag_information_test.c
${SOURCE_DIR}/threadx_event_flag_isr_set_clear_test.c
${SOURCE_DIR}/threadx_event_flag_isr_wait_abort_test.c
${SOURCE_DIR}/threadx_event_flag_single_thread_terminate_test.c
${SOURCE_DIR}/threadx_event_flag_suspension_consume_test.c
${SOURCE_DIR}/threadx_event_flag_suspension_different_bits_consume_test.c
${SOURCE_DIR}/threadx_event_flag_suspension_different_bits_test.c
${SOURCE_DIR}/threadx_event_flag_suspension_test.c
${SOURCE_DIR}/threadx_event_flag_suspension_timeout_test.c
${SOURCE_DIR}/threadx_event_flag_thread_terminate_test.c
${SOURCE_DIR}/threadx_interrupt_control_test.c
${SOURCE_DIR}/threadx_mutex_basic_test.c
${SOURCE_DIR}/threadx_mutex_delete_test.c
${SOURCE_DIR}/threadx_mutex_information_test.c
${SOURCE_DIR}/threadx_mutex_nested_priority_inheritance_test.c
${SOURCE_DIR}/threadx_mutex_no_preemption_test.c
${SOURCE_DIR}/threadx_mutex_preemption_test.c
${SOURCE_DIR}/threadx_mutex_priority_inheritance_test.c
${SOURCE_DIR}/threadx_mutex_proritize_test.c
${SOURCE_DIR}/threadx_mutex_suspension_timeout_test.c
${SOURCE_DIR}/threadx_mutex_thread_terminate_test.c
${SOURCE_DIR}/threadx_queue_basic_eight_word_test.c
${SOURCE_DIR}/threadx_queue_basic_four_word_test.c
${SOURCE_DIR}/threadx_queue_basic_one_word_test.c
${SOURCE_DIR}/threadx_queue_basic_sixteen_word_test.c
${SOURCE_DIR}/threadx_queue_basic_two_word_test.c
${SOURCE_DIR}/threadx_queue_basic_max_message_size_test.c
${SOURCE_DIR}/threadx_queue_empty_suspension_test.c
${SOURCE_DIR}/threadx_queue_flush_no_suspension_test.c
${SOURCE_DIR}/threadx_queue_flush_test.c
${SOURCE_DIR}/threadx_queue_front_send_test.c
${SOURCE_DIR}/threadx_queue_full_suspension_test.c
${SOURCE_DIR}/threadx_queue_information_test.c
${SOURCE_DIR}/threadx_queue_prioritize.c
${SOURCE_DIR}/threadx_queue_suspension_timeout_test.c
${SOURCE_DIR}/threadx_queue_thread_terminate_test.c
${SOURCE_DIR}/threadx_semaphore_basic_test.c
${SOURCE_DIR}/threadx_semaphore_ceiling_put_test.c
${SOURCE_DIR}/threadx_semaphore_delete_test.c
${SOURCE_DIR}/threadx_semaphore_information_test.c
${SOURCE_DIR}/threadx_semaphore_non_preemption_test.c
${SOURCE_DIR}/threadx_semaphore_preemption_test.c
${SOURCE_DIR}/threadx_semaphore_prioritize.c
${SOURCE_DIR}/threadx_semaphore_thread_terminate_test.c
${SOURCE_DIR}/threadx_semaphore_timeout_test.c
${SOURCE_DIR}/threadx_thread_basic_execution_test.c
${SOURCE_DIR}/threadx_thread_basic_time_slice_test.c
${SOURCE_DIR}/threadx_thread_completed_test.c
${SOURCE_DIR}/threadx_thread_create_preemption_threshold_test.c
${SOURCE_DIR}/threadx_thread_delayed_suspension_test.c
${SOURCE_DIR}/threadx_thread_information_test.c
${SOURCE_DIR}/threadx_thread_multi_level_preemption_threshold_test.c
${SOURCE_DIR}/threadx_thread_multiple_non_current_test.c
${SOURCE_DIR}/threadx_thread_multiple_sleep_test.c
${SOURCE_DIR}/threadx_thread_multiple_suspension_test.c
${SOURCE_DIR}/threadx_thread_multiple_time_slice_test.c
${SOURCE_DIR}/threadx_thread_preemptable_suspension_test.c
${SOURCE_DIR}/threadx_thread_preemption_change_test.c
${SOURCE_DIR}/threadx_thread_priority_change.c
${SOURCE_DIR}/threadx_thread_relinquish_test.c
${SOURCE_DIR}/threadx_thread_reset_test.c
${SOURCE_DIR}/threadx_thread_simple_sleep_non_clear_test.c
${SOURCE_DIR}/threadx_thread_simple_sleep_test.c
${SOURCE_DIR}/threadx_thread_simple_suspend_test.c
${SOURCE_DIR}/threadx_thread_sleep_for_100ticks_test.c
${SOURCE_DIR}/threadx_thread_sleep_terminate_test.c
${SOURCE_DIR}/threadx_thread_stack_checking_test.c
${SOURCE_DIR}/threadx_thread_terminate_delete_test.c
${SOURCE_DIR}/threadx_thread_time_slice_change_test.c
${SOURCE_DIR}/threadx_thread_wait_abort_and_isr_test.c
${SOURCE_DIR}/threadx_thread_wait_abort_test.c
${SOURCE_DIR}/threadx_time_get_set_test.c
${SOURCE_DIR}/threadx_timer_activate_deactivate_test.c
${SOURCE_DIR}/threadx_timer_deactivate_accuracy_test.c
${SOURCE_DIR}/threadx_timer_information_test.c
${SOURCE_DIR}/threadx_timer_large_timer_accuracy_test.c
${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.
)
# Tests that provide their own main() and must NOT link with testcontrol.
set(standalone_test_cases
${SOURCE_DIR}/threadx_initialize_kernel_setup_test.c
)
# Build testcontrol.c as a static library (no patched tx_initialize_low_level
# needed — the BSP provides it directly with test_interrupt_dispatch hook).
add_library(riscv_test_utility ${SOURCE_DIR}/testcontrol.c)
target_link_libraries(riscv_test_utility PUBLIC riscv_bsp azrtos::threadx)
target_compile_definitions(riscv_test_utility PUBLIC
CTEST BATCH_TEST EXTERNAL_EXIT TEST_STACK_SIZE_PRINTF=4096)
# Bare-metal newlib needs explicit stdlib.h for rand() used in some tests.
target_compile_options(riscv_test_utility PUBLIC -include stdlib.h)
# Helper function to register a test executable with QEMU + CTest.
function(register_riscv_test test_name)
target_link_options(${test_name} PRIVATE
-T ${BSP_DIR}/link.lds -nostartfiles
-Wl,--gc-sections)
add_test(
NAME ${CMAKE_BUILD_TYPE}::${XLEN_TAG}::${test_name}
COMMAND ${QEMU_SYSTEM} -nographic -smp 1 -bios none -m 128M
-machine virt -kernel $<TARGET_FILE:${test_name}>
)
set_tests_properties(${CMAKE_BUILD_TYPE}::${XLEN_TAG}::${test_name}
PROPERTIES TIMEOUT 120)
endfunction()
# Regular tests (linked with testcontrol).
foreach(test_case ${regression_test_cases})
get_filename_component(test_name ${test_case} NAME_WE)
add_executable(${test_name} ${test_case})
target_link_libraries(${test_name} PRIVATE
-Wl,--whole-archive riscv_bsp -Wl,--no-whole-archive
riscv_test_utility)
register_riscv_test(${test_name})
endforeach()
# Standalone tests (provide their own main, no testcontrol).
foreach(test_case ${standalone_test_cases})
get_filename_component(test_name ${test_case} NAME_WE)
add_executable(${test_name} ${test_case})
target_link_libraries(${test_name} PRIVATE
-Wl,--whole-archive riscv_bsp -Wl,--no-whole-archive
azrtos::threadx)
target_compile_options(${test_name} PRIVATE -include stdlib.h)
register_riscv_test(${test_name})
endforeach()
+151
View File
@@ -0,0 +1,151 @@
#!/bin/bash
set -e
# RISC-V regression test build/test runner.
# Usage: run.sh <arch> <build|test> [all|<config> ...]
# arch: riscv32 or riscv64
function help() {
echo "Usage: $0 <arch> <command> [all|<build_configuration> ...]"
echo ""
echo " arch: riscv32 | riscv64"
echo " command: build | test"
echo ""
echo "Available build configurations:"
for build in ${build_configurations[*]}; do
echo " $build"
done
exit 1
}
function validate() {
for build in ${build_configurations[*]}; do
if [ "$1" == "$build" ]; then
return
fi
done
help
}
function generate() {
local arch=$1
local build=$2
local toolchain_file
local build_dir
if [ "$arch" == "riscv32" ]; then
toolchain_file="${REPO_ROOT}/cmake/riscv32_gnu.cmake"
else
toolchain_file="${REPO_ROOT}/cmake/riscv64_gnu.cmake"
fi
build_dir="build/${arch}_${build}"
cmake -B"$build_dir" -GNinja \
-DCMAKE_TOOLCHAIN_FILE="$toolchain_file" \
-DCMAKE_BUILD_TYPE="$build" \
.
}
function build() {
local arch=$1
local build=$2
cmake --build "build/${arch}_${build}"
}
function run_test() {
local arch=$1
local build=$2
local parallel_jobs=$3
pushd "build/${arch}_${build}"
[ -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 $parallel --timeout 1000 \
-O "${arch}_${build}.txt" \
-T test --no-compress-output \
--test-output-size-passed 4194304 \
--test-output-size-failed 4194304 \
--output-on-failure \
--repeat until-pass:${repeat_fail} \
--output-junit "${arch}_${build}.xml"
popd
}
# Determine repo root and script directory
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../../../.." && pwd)"
cd "$SCRIPT_DIR"
# Parse build configurations from CMakeLists.txt
result=$(sed -n "/(BUILD_CONFIGURATIONS/,/)/p" CMakeLists.txt | sed ':label;N;s/\n/ /;b label' | grep -Pzo "[a-zA-Z0-9_]*build[a-zA-Z0-9_]*\s*" | tr -d '\0')
IFS=' '
read -ra build_configurations <<< "$result"
if [ $# -lt 2 ]; then
help
fi
arch=$1
shift
if [ "$arch" != "riscv32" ] && [ "$arch" != "riscv64" ]; then
echo "Error: arch must be 'riscv32' or 'riscv64'"
help
fi
command=$1
shift
if [ "$#" == "0" ]; then
builds=${build_configurations[0]}
elif [ "$*" == "all" ]; then
builds=${build_configurations[@]}
else
for item in $*; do
validate "$item"
done
builds=$*
fi
if [ "$command" == "build" ]; then
for item in $builds; do
generate "$arch" "$item"
echo ""
done
for item in $builds; do
echo "Building ${arch} ${item}"
build "$arch" "$item"
echo ""
done
elif [ "$command" == "test" ]; then
cores=$(nproc)
if [ -z "${CTEST_PARALLEL_LEVEL}" ]; then
build_counts=$(echo $builds | wc -w)
parallel_jobs=$(($cores / $build_counts))
parallel_jobs=$(($parallel_jobs + 2))
pids=""
for item in $builds; do
echo "Testing ${arch} ${item}"
run_test "$arch" "$item" "$parallel_jobs" &
pids+=" $!"
done
exit_code=0
for p in $pids; do
wait $p || exit_code=$?
done
exit $exit_code
else
for item in $builds; do
echo "Testing ${arch} ${item}"
run_test "$arch" "$item" "$parallel_jobs"
done
fi
else
help
fi
@@ -217,8 +217,8 @@ CHAR *pointer;
}
/* Create block pools 0 and 1. */
status = tx_block_pool_create(&pool_0, "pool 0", 100, pointer, 320);
pointer = pointer + 320;
status = tx_block_pool_create(&pool_0, "pool 0", 100, pointer, 340);
pointer = pointer + 340;
/* Check status. */
if (status != TX_SUCCESS)
@@ -228,8 +228,8 @@ CHAR *pointer;
test_control_return(1);
}
status = tx_block_pool_create(&pool_1, "pool 1", 100, pointer, 320);
pointer = pointer + 320;
status = tx_block_pool_create(&pool_1, "pool 1", 100, pointer, 340);
pointer = pointer + 340;
/* Check status. */
if (status != TX_SUCCESS)
@@ -49,8 +49,8 @@ INT status;
pointer = pointer + TEST_STACK_SIZE_PRINTF;
/* Create block pool 0. */
status = tx_block_pool_create(&pool_0, "pool 0", 100, pointer, 320);
pointer = pointer + 320;
status = tx_block_pool_create(&pool_0, "pool 0", 100, pointer, 340);
pointer = pointer + 340;
#ifndef TX_DISABLE_ERROR_CHECKING /* skip this test and pretend it passed */
@@ -128,8 +128,8 @@ INT i;
#ifndef TX_DISABLE_ERROR_CHECKING /* skip this test and pretend it passed */
status = tx_block_pool_create(&pool_1, "pool 1", 100, pointer, 320);
pointer = pointer + 320;
status = tx_block_pool_create(&pool_1, "pool 1", 100, pointer, 340);
pointer = pointer + 340;
/* Attempt to create a pool with an invalid size. */
status = _txe_block_pool_create(&pool_2, "pool 2", 100, pointer, 320, 777777);
@@ -83,8 +83,8 @@ CHAR *pointer;
}
/* Create block pool. */
status = tx_block_pool_create(&pool_0, "pool 0", 100, pointer, 320);
pointer = pointer + 320;
status = tx_block_pool_create(&pool_0, "pool 0", 100, pointer, 340);
pointer = pointer + 340;
/* Check status. */
if (status != TX_SUCCESS)
@@ -84,8 +84,8 @@ CHAR *pointer;
}
/* Create block pool. */
status = tx_block_pool_create(&pool_0, "pool 0", 100, pointer, 320);
pointer = pointer + 320;
status = tx_block_pool_create(&pool_0, "pool 0", 100, pointer, 340);
pointer = pointer + 340;
/* Check status. */
if (status != TX_SUCCESS)
@@ -69,8 +69,8 @@ CHAR *pointer;
}
/* Create block pool. */
status = tx_block_pool_create(&pool_0, "pool 0", 100, pointer, 320);
pointer = pointer + 320;
status = tx_block_pool_create(&pool_0, "pool 0", 100, pointer, 340);
pointer = pointer + 340;
/* Check status. */
if (status != TX_SUCCESS)
@@ -4,6 +4,15 @@
#include <stdio.h>
#include "tx_api.h"
/* Per-allocation overhead in byte pools: next pointer + ALIGN_TYPE alignment marker. */
#define BYTE_POOL_OVERHEAD (sizeof(UCHAR *) + sizeof(ALIGN_TYPE))
/* Pool size for pool_0: exactly fits 3 allocations of 24 bytes with no room for a 4th. */
#define POOL_0_SIZE (ULONG)(3u * (24u + BYTE_POOL_OVERHEAD) + BYTE_POOL_OVERHEAD)
/* Pool size for pool_4: must accommodate 3 allocations of 84 bytes (tightest pattern). */
#define POOL_4_SIZE (ULONG)(3u * (84u + BYTE_POOL_OVERHEAD) + BYTE_POOL_OVERHEAD + 16u)
typedef struct BYTE_MEMORY_TEST_STRUCT
{
ULONG first;
@@ -256,8 +265,8 @@ CHAR *pointer;
}
/* Create byte pools 0 and 1. */
status = tx_byte_pool_create(&pool_0, "pool 0", pointer, 108);
pointer = pointer + 108;
status = tx_byte_pool_create(&pool_0, "pool 0", pointer, POOL_0_SIZE);
pointer = pointer + POOL_0_SIZE;
/* Check status. */
if (status != TX_SUCCESS)
@@ -279,9 +288,9 @@ CHAR *pointer;
}
/* Test for search pointer issue on wrapped seach with prior block to search pointer merged. */
status = tx_byte_pool_create(&pool_4, "pool 4", pointer, 300);
status = tx_byte_pool_create(&pool_4, "pool 4", pointer, POOL_4_SIZE);
pool_4_memory = pointer;
pointer = pointer + 300;
pointer = pointer + POOL_4_SIZE;
/* Check status. */
if (status != TX_SUCCESS)
@@ -865,7 +874,7 @@ UCHAR *save_search;
}
/* Move the search pointer to the third block to exercise that code in the byte search algorithm. */
pool_0.tx_byte_pool_search = (UCHAR *) pointer_3-8;
pool_0.tx_byte_pool_search = (UCHAR *) pointer_3 - BYTE_POOL_OVERHEAD;
/* Now allocate the block again. */
status = tx_byte_allocate(&pool_0, (VOID **) &pointer_2, 24, TX_NO_WAIT);
@@ -885,7 +894,7 @@ UCHAR *save_search;
status += tx_byte_release(pointer_1);
/* Move the search pointer to the third block to exercise that code in the byte search algorithm. */
pool_0.tx_byte_pool_search = (UCHAR *) pointer_3-8;
pool_0.tx_byte_pool_search = (UCHAR *) pointer_3 - BYTE_POOL_OVERHEAD;
/* Allocate a large block to force the search pointer update. */
status = tx_byte_allocate(&pool_0, (VOID **) &pointer_3, 88, TX_NO_WAIT);
@@ -961,7 +970,7 @@ UCHAR *save_search;
}
/* Create pool 4. */
status = tx_byte_pool_create(&pool_4, "pool 4", pool_4_memory, 300);
status = tx_byte_pool_create(&pool_4, "pool 4", pool_4_memory, POOL_4_SIZE);
/* Check status. */
if (status != TX_SUCCESS)
@@ -178,17 +178,12 @@ UINT status;
/* Sleep for 63 ticks. */
tx_thread_sleep(63);
/* Check the run counters. */
if (((thread_1_counter != 32)
#ifdef __linux__
&& (thread_1_counter != 33) /* Depending on the starting time, thread 1 can run either 32 or 33 rounds. */
#endif
) ||
((thread_2_counter != 13)
#ifdef __linux__
&& (thread_2_counter != 14) /* When CPU starves, the thread 2 can run 14 ronuds. */
#endif
))
/* Check the run counters. Depending on the starting time relative to
the tick boundary, thread 1 can run either 32 or 33 rounds and
thread 2 can run 13 or 14 rounds. This variance applies to any
platform with non-deterministic tick alignment (Linux, QEMU, etc.). */
if (((thread_1_counter != 32) && (thread_1_counter != 33)) ||
((thread_2_counter != 13) && (thread_2_counter != 14)))
{
/* Event flag error. */