mirror of
https://github.com/eclipse-threadx/threadx.git
synced 2026-10-06 06:59:08 +08:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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__
|
||||
|
||||
Executable
+15
@@ -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
|
||||
Executable
+32
@@ -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
|
||||
Executable
+19
@@ -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
|
||||
@@ -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>
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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()
|
||||
Executable
+151
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user