diff options
| author | Frédéric Desbiens <[email protected]> | 2026-05-27 10:30:57 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-05-27 10:30:57 -0400 |
| commit | 7486de06c80aac7ce8b40a9fbbabd448707e1fa8 (patch) | |
| tree | 099df617af05f3d50d9111737bc9f59a826caf23 /ports/risc-v64/gnu | |
| parent | 2c16114a450056103bacee0a3254a57dad7e1abd (diff) | |
Refactored, consolidated, and cleaned up RV32/RV64 ports (#536)
risc-v: refactor, consolidate, and fix RV32/RV64 ports
Consolidates the RISC-V 32-bit and 64-bit GNU/Clang port sources, fixes two
pre-existing assembly bugs discovered during testing, and hardens the build
infrastructure for both the regression suite and the CORE-V MCU example.
--- Port consolidation (RV32 GNU + Clang) ---
- Delete ports/risc-v32/clang/src/ (8 .S files had no Clang-specific
directives; diverged from GNU only due to missing bug fixes). The Clang
port CMakeLists.txt now compiles from ../gnu/src/.
- Change .global -> .weak for _tx_initialize_low_level in gnu/src/ to allow
BSP-level override without a linker conflict (adopted from Clang port).
- Create ports/risc-v32/common/tx_port_riscv32_common.h with all definitions
shared between GNU and Clang ports. Reduce both tx_port.h files to thin
wrappers.
- Add a prominent comment in risc-v64/gnu/inc/tx_port.h explaining why
LONG/ULONG are intentionally 32-bit on RV64 (ThreadX ABI requirement,
mirrors win64/MSVC LLP64).
--- Shared CMake helper ---
- Add cmake/threadx_riscv_port.cmake with threadx_add_riscv_port(). All
three port CMakeLists.txt files are reduced to ~8 lines each. Include path
is relative to CMAKE_CURRENT_LIST_DIR so the helper works whether ports
are built standalone or as a subdirectory of the test framework.
--- Shared example-build drivers ---
- Create canonical driver files under ports/risc-v_common/:
inc/csr.h (uintptr_t-based; portable RV32 + RV64)
example_build/plic/ (plic.c, plic.h)
example_build/uart/ (uart_qemu_ns16550.c/h; static inline putc_nolock)
example_build/trap/ (trap_qemu.c; XLEN-portable mcause constants)
- Replace per-example copies with symlinks in all qemu_virt and cva6_ariane
example directories.
- Fix OS_IS_INTERRUPT typo (was OS_IS_INTERUPT) in shared trap_qemu.c.
- Gate print_hex() behind TX_RISCV_TRAP_DEBUG.
--- Bug fixes in RV32 assembly ---
tx_thread_schedule.S:
- Solicited-return FP path: reload t0 from the mepc stack slot before
csrw mepc, t0. After the FP restore block, t0 held the fcsr value (0 for
new threads), which caused mepc = 0 and an immediate instruction-address
fault on the first context switch.
- Same path: reload t0 from the mstatus stack slot before csrw mstatus, t0
to avoid writing the stale fcsr value into mstatus.
tx_thread_system_return.S:
- FP callee-saved registers were saved unconditionally before the mstatus.FS
check, causing an illegal instruction trap (mcause=0x2) when a thread with
FS=Off (lazy FPU, thread has never used FP) voluntarily yielded.
- Apply the same FS guard pattern used in tx_thread_context_save.S: read
mstatus first, isolate FS[1:0], and skip fsw/fsd if FS == Off.
Both bugs were pre-existing on origin/dev and are unrelated to the
consolidation changes.
--- RV64 64-bit pointer compatibility ---
- Add TX_TIMER_INTERNAL_EXTENSION, TX_THREAD_CREATE_TIMEOUT_SETUP, and
TX_THREAD_TIMEOUT_POINTER_SETUP to risc-v64/gnu/inc/tx_port.h to store the
thread timeout pointer in a VOID
* extension field rather than truncating it
into a 32-bit ULONG. Mirrors the win64 port pattern.
- Define TX_TIMER_EXTENSION_PTR_DEFINED as a portable sentinel.
- Update threadx_thread_basic_execution_test.c guard from #if defined(_WIN64)
to #if defined(_WIN64) || defined(TX_TIMER_EXTENSION_PTR_DEFINED).
- Disable -Wconversion for the RV64 test build: ULONG = unsigned int (32-bit)
is intentional for ThreadX ABI but triggers spurious warnings when sizeof()
(8 bytes on RV64) appears in arithmetic with ULONG in common/src/.
--- Regression suite cmake fixes ---
test/tx/cmake/riscv/regression/CMakeLists.txt:
- Build testcontrol_weak_defaults.c as a separate OBJECT library and include
it in every test executable via $<TARGET_OBJECTS:>. GNU ld does not extract
objects from a static archive to satisfy weak symbols, so bundling it in
test_utility was insufficient for the standalone
threadx_initialize_kernel_setup_test.
test/tx/cmake/regression/CMakeLists.txt,
test/smp/cmake/regression/CMakeLists.txt:
- Same fix applied to the Linux and SMP regression builds. The symbols
abort_all_threads_suspended_on_mutex, suspend_lowest_priority, and
abort_and_resume_byte_allocating_thread were introduced by the win64 merge
and left the standalone test unlinkable.
--- CORE-V MCU toolchain and build fixes ---
cmake/riscv64-gcc-rv32imc.cmake:
- Resolve riscv64-unknown-elf-gcc via PATH so the riscv-collab toolchain in
/opt/riscv/bin is preferred when it appears first.
ports/risc-v32/gnu/example_build/core_v_mcu/bsp/clz.c (new):
- The riscv-collab toolchain is built without rv32 multilib, so its libgcc
does not define __clzsi2 (the helper emitted for __builtin_clz() in fll.c).
Add a weak __clzsi2 fallback so the build is self-contained with any
riscv64-unknown-elf toolchain. The weak attribute yields to a
libgcc-provided strong symbol when the Ubuntu multilib package is used.
core_v_mcu/CMakeLists.txt:
- Add bsp/clz.c to sources.
- Reference CMAKE_TOOLCHAIN_FILE via message(STATUS) to suppress the false-
positive "Manually-specified variables were not used by the project" CMake
warning and to show the active toolchain at configure time.
--- Housekeeping ---
- Rename azrtos_test_* -> threadx_test_* (eliminate Azure RTOS branding).
- Add RV64 QEMU CI test script:
ports/risc-v64/gnu/example_build/qemu_virt/test/
threadx_test_tx_gnu_riscv64_qemu.py
- Normalize entry.s -> entry.S in all 4 example directories.
- .gitignore: exclude build_m7/ and .codex local artifacts.
- CI: comment out the riscv regression workflow job and remove it from the
deploy job's needs list (preserved in-place for easy re-enablement).
--- Verified ---
- 95/95 RV32 regression tests pass (QEMU virt)
- 95/95 RV64 regression tests pass (QEMU virt)
- All 5 Linux build configurations build cleanly (default_build_coverage,
disable_notify_callbacks_build, stack_checking_build,
stack_checking_rand_fill_build, trace_build)
- CORE-V MCU example_build links cleanly with /opt/riscv toolchain
Co-authored-by: Copilot [email protected]
Diffstat (limited to 'ports/risc-v64/gnu')
| -rw-r--r-- | ports/risc-v64/gnu/CMakeLists.txt | 21 | ||||
| -rwxr-xr-x | ports/risc-v64/gnu/example_build/qemu_virt/build_libthreadx.sh | 2 | ||||
| l---------[-rw-r--r--] | ports/risc-v64/gnu/example_build/qemu_virt/csr.h | 376 | ||||
| -rw-r--r-- | ports/risc-v64/gnu/example_build/qemu_virt/entry.S (renamed from ports/risc-v64/gnu/example_build/qemu_virt/entry.s) | 0 | ||||
| l---------[-rw-r--r--] | ports/risc-v64/gnu/example_build/qemu_virt/plic.c | 73 | ||||
| l---------[-rw-r--r--] | ports/risc-v64/gnu/example_build/qemu_virt/plic.h | 52 | ||||
| -rw-r--r-- | ports/risc-v64/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv64_qemu.py | 275 | ||||
| l---------[-rw-r--r--] | ports/risc-v64/gnu/example_build/qemu_virt/trap.c | 47 | ||||
| l---------[-rw-r--r--] | ports/risc-v64/gnu/example_build/qemu_virt/uart.c | 101 | ||||
| l---------[-rw-r--r--] | ports/risc-v64/gnu/example_build/qemu_virt/uart.h | 23 | ||||
| -rw-r--r-- | ports/risc-v64/gnu/inc/tx_port.h | 23 |
11 files changed, 309 insertions, 684 deletions
diff --git a/ports/risc-v64/gnu/CMakeLists.txt b/ports/risc-v64/gnu/CMakeLists.txt index 9357c697..e51de735 100644 --- a/ports/risc-v64/gnu/CMakeLists.txt +++ b/ports/risc-v64/gnu/CMakeLists.txt @@ -1,19 +1,6 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../../../cmake/threadx_riscv_port.cmake) -target_sources(${PROJECT_NAME} - PRIVATE - # {{BEGIN_TARGET_SOURCES}} - ${CMAKE_CURRENT_LIST_DIR}/src/tx_initialize_low_level.S - ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_context_restore.S - ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_context_save.S - ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_interrupt_control.S - ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_schedule.S - ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_stack_build.S - ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_system_return.S - ${CMAKE_CURRENT_LIST_DIR}/src/tx_timer_interrupt.S - # {{END_TARGET_SOURCES}} -) - -target_include_directories(${PROJECT_NAME} - PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/inc +threadx_add_riscv_port( + SRC_DIR ${CMAKE_CURRENT_LIST_DIR}/src + INC_DIR ${CMAKE_CURRENT_LIST_DIR}/inc ) diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/build_libthreadx.sh b/ports/risc-v64/gnu/example_build/qemu_virt/build_libthreadx.sh index 35f7ac5a..b655820e 100755 --- a/ports/risc-v64/gnu/example_build/qemu_virt/build_libthreadx.sh +++ b/ports/risc-v64/gnu/example_build/qemu_virt/build_libthreadx.sh @@ -13,7 +13,7 @@ riscv64-unknown-elf-gcc \ -ffunction-sections -fdata-sections \ -I../../../../../common/inc \ -I../../inc \ - entry.s \ + entry.S \ tx_initialize_low_level.S \ board.c uart.c hwtimer.c plic.c trap.c demo_threadx.c \ -L../../../../../build -lthreadx \ diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/csr.h b/ports/risc-v64/gnu/example_build/qemu_virt/csr.h index 83d05afd..b5df6f12 100644..120000 --- a/ports/risc-v64/gnu/example_build/qemu_virt/csr.h +++ b/ports/risc-v64/gnu/example_build/qemu_virt/csr.h @@ -1,375 +1 @@ -/*************************************************************************** - * Copyright (c) 2024 Microsoft Corporation - * Copyright (c) 2026-present Eclipse ThreadX contributors - * - * This program and the accompanying materials are made available under the - * terms of the MIT License which is available at - * https://opensource.org/licenses/MIT. - * - * SPDX-License-Identifier: MIT - **************************************************************************/ - - -#ifndef RISCV_CSR_H -#define RISCV_CSR_H - - -// Machine Status Register, mstatus -#define MSTATUS_MPP_MASK (3L << 11) // previous mode. -#define MSTATUS_MPP_M (3L << 11) -#define MSTATUS_MPP_S (1L << 11) -#define MSTATUS_MPP_U (0L << 11) -#define MSTATUS_MIE (1L << 3) // machine-mode interrupt enable. -#define MSTATUS_MPIE (1L << 7) -#define MSTATUS_FS (1L << 13) -#define MSTATUS_VS (1L << 9) - -// Machine-mode Interrupt Enable -#define MIE_MTIE (1L << 7) -#define MIE_MSIE (1L << 3) -#define MIE_MEIE (1L << 11) -#define MIE_STIE (1L << 5) // supervisor timer -#define MIE_SSIE (1L << 1) -#define MIE_SEIE (1L << 9) - -// Supervisor Status Register, sstatus -#define SSTATUS_SPP (1L << 8) // Previous mode, 1=Supervisor, 0=User -#define SSTATUS_SPIE (1L << 5) // Supervisor Previous Interrupt Enable -#define SSTATUS_UPIE (1L << 4) // User Previous Interrupt Enable -#define SSTATUS_SIE (1L << 1) // Supervisor Interrupt Enable -#define SSTATUS_UIE (1L << 0) // User Interrupt Enable -#define SSTATUS_SPIE (1L << 5) -#define SSTATUS_UPIE (1L << 4) - -// Supervisor Interrupt Enable -#define SIE_SEIE (1L << 9) // external -#define SIE_STIE (1L << 5) // timer -#define SIE_SSIE (1L << 1) // software - -#ifndef __ASSEMBLER__ - -#include <stdint.h> - -static inline uint64_t riscv_get_core() -{ - uint64_t x; - asm volatile("csrr %0, mhartid" : "=r" (x) ); - return x; -} - -static inline uint64_t riscv_get_mstatus() -{ - uint64_t x; - asm volatile("csrr %0, mstatus" : "=r" (x) ); - return x; -} - -static inline void riscv_writ_mstatus(uint64_t x) -{ - asm volatile("csrw mstatus, %0" : : "r" (x)); -} - -// machine exception program counter, holds the -// instruction address to which a return from -// exception will go. -static inline void riscv_writ_mepc(uint64_t x) -{ - asm volatile("csrw mepc, %0" : : "r" (x)); -} - -static inline uint64_t riscv_get_sstatus() -{ - uint64_t x; - asm volatile("csrr %0, sstatus" : "=r" (x) ); - return x; -} - -static inline void riscv_writ_sstatus(uint64_t x) -{ - asm volatile("csrw sstatus, %0" : : "r" (x)); -} - -// Supervisor Interrupt Pending -static inline uint64_t riscv_get_sip() -{ - uint64_t x; - asm volatile("csrr %0, sip" : "=r" (x) ); - return x; -} - -static inline void riscv_writ_sip(uint64_t x) -{ - asm volatile("csrw sip, %0" : : "r" (x)); -} - -static inline uint64_t riscv_get_sie() -{ - uint64_t x; - asm volatile("csrr %0, sie" : "=r" (x) ); - return x; -} - -static inline void riscv_writ_sie(uint64_t x) -{ - asm volatile("csrw sie, %0" : : "r" (x)); -} - -static inline uint64_t riscv_get_mie() -{ - uint64_t x; - asm volatile("csrr %0, mie" : "=r" (x) ); - return x; -} - -static inline void riscv_writ_mie(uint64_t x) -{ - asm volatile("csrw mie, %0" : : "r" (x)); -} - -// supervisor exception program counter, holds the -// instruction address to which a return from -// exception will go. -static inline void riscv_writ_sepc(uint64_t x) -{ - asm volatile("csrw sepc, %0" : : "r" (x)); -} - -static inline uint64_t riscv_get_sepc() -{ - uint64_t x; - asm volatile("csrr %0, sepc" : "=r" (x) ); - return x; -} - -// Machine Exception Delegation -static inline uint64_t riscv_get_medeleg() -{ - uint64_t x; - asm volatile("csrr %0, medeleg" : "=r" (x) ); - return x; -} - -static inline void riscv_writ_medeleg(uint64_t x) -{ - asm volatile("csrw medeleg, %0" : : "r" (x)); -} - -// Machine Interrupt Delegation -static inline uint64_t riscv_get_mideleg() -{ - uint64_t x; - asm volatile("csrr %0, mideleg" : "=r" (x) ); - return x; -} - -static inline void riscv_writ_mideleg(uint64_t x) -{ - asm volatile("csrw mideleg, %0" : : "r" (x)); -} - -// Supervisor Trap-Vector Base Address -// low two bits are mode. -static inline void riscv_writ_stvec(uint64_t x) -{ - asm volatile("csrw stvec, %0" : : "r" (x)); -} - -static inline uint64_t riscv_get_stvec() -{ - uint64_t x; - asm volatile("csrr %0, stvec" : "=r" (x) ); - return x; -} - -// Supervisor Timer Comparison Register -static inline uint64_t riscv_get_stimecmp() -{ - uint64_t x; - // asm volatile("csrr %0, stimecmp" : "=r" (x) ); - asm volatile("csrr %0, 0x14d" : "=r" (x) ); - return x; -} - -static inline void riscv_writ_stimecmp(uint64_t x) -{ - // asm volatile("csrw stimecmp, %0" : : "r" (x)); - asm volatile("csrw 0x14d, %0" : : "r" (x)); -} - -// Machine Environment Configuration Register -static inline uint64_t riscv_get_menvcfg() -{ - uint64_t x; - // asm volatile("csrr %0, menvcfg" : "=r" (x) ); - asm volatile("csrr %0, 0x30a" : "=r" (x) ); - return x; -} - -static inline void riscv_writ_menvcfg(uint64_t x) -{ - // asm volatile("csrw menvcfg, %0" : : "r" (x)); - asm volatile("csrw 0x30a, %0" : : "r" (x)); -} - -// Physical Memory Protection -static inline void riscv_writ_pmpcfg0(uint64_t x) -{ - asm volatile("csrw pmpcfg0, %0" : : "r" (x)); -} - -static inline void riscv_writ_pmpaddr0(uint64_t x) -{ - asm volatile("csrw pmpaddr0, %0" : : "r" (x)); -} - -// supervisor address translation and protection; -// holds the address of the page table. -static inline void riscv_writ_satp(uint64_t x) -{ - asm volatile("csrw satp, %0" : : "r" (x)); -} - -static inline uint64_t riscv_get_satp() -{ - uint64_t x; - asm volatile("csrr %0, satp" : "=r" (x) ); - return x; -} - -// Supervisor Trap Cause -static inline uint64_t riscv_get_scause() -{ - uint64_t x; - asm volatile("csrr %0, scause" : "=r" (x) ); - return x; -} - -// Supervisor Trap Value -static inline uint64_t riscv_get_stval() -{ - uint64_t x; - asm volatile("csrr %0, stval" : "=r" (x) ); - return x; -} - -// Machine-mode Counter-Enable -static inline void riscv_writ_mcounteren(uint64_t x) -{ - asm volatile("csrw mcounteren, %0" : : "r" (x)); -} - -static inline uint64_t riscv_get_mcounteren() -{ - uint64_t x; - asm volatile("csrr %0, mcounteren" : "=r" (x) ); - return x; -} - -// machine-mode cycle counter -static inline uint64_t riscv_get_time() -{ - uint64_t x; - asm volatile("csrr %0, time" : "=r" (x) ); - return x; -} - -// enable device interrupts -static inline void riscv_sintr_on() -{ - uint64_t sstatus = riscv_get_sstatus(); - sstatus |= SSTATUS_SIE; - riscv_writ_sstatus(sstatus); -} - -// disable device interrupts -static inline void riscv_sintr_off() -{ - uint64_t sstatus = riscv_get_sstatus(); - sstatus &= (~SSTATUS_SIE); - riscv_writ_sstatus(sstatus); -} - -// are device interrupts enabled? -static inline int riscv_sintr_get() -{ - uint64_t x = riscv_get_sstatus(); - return (x & SSTATUS_SIE) != 0; -} - -static inline void riscv_sintr_restore(int x) -{ - if(x) - riscv_sintr_on(); - else - riscv_sintr_off(); -} - -// enable device interrupts -static inline void riscv_mintr_on() -{ - uint64_t mstatus = riscv_get_mstatus(); - mstatus |= MSTATUS_MIE; - riscv_writ_mstatus(mstatus); -} - -// disable device interrupts -static inline void riscv_mintr_off() -{ - uint64_t mstatus = riscv_get_mstatus(); - mstatus &= (~MSTATUS_MIE); - riscv_writ_mstatus(mstatus); -} - -// are device interrupts enabled? -static inline int riscv_mintr_get() -{ - uint64_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(); -} - -static inline uint64_t riscv_get_sp() -{ - uint64_t x; - asm volatile("mv %0, sp" : "=r" (x) ); - return x; -} - -// read and write tp, the thread pointer, which xv6 uses to hold -// this core's hartid (core number), the index into cpus[]. -static inline uint64_t riscv_get_tp() -{ - uint64_t x; - asm volatile("mv %0, tp" : "=r" (x) ); - return x; -} - -static inline void riscv_writ_tp(uint64_t x) -{ - asm volatile("mv tp, %0" : : "r" (x)); -} - -static inline uint64_t riscv_get_ra() -{ - uint64_t x; - asm volatile("mv %0, ra" : "=r" (x) ); - return x; -} - -// flush the TLB. -static inline void sfence_vma() -{ - // the zero, zero means flush all TLB entries. - asm volatile("sfence.vma zero, zero"); -} - -#endif // __ASSEMBLER__ - -#endif +../../../../risc-v_common/inc/csr.h
\ No newline at end of file diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/entry.s b/ports/risc-v64/gnu/example_build/qemu_virt/entry.S index 791d08eb..791d08eb 100644 --- a/ports/risc-v64/gnu/example_build/qemu_virt/entry.s +++ b/ports/risc-v64/gnu/example_build/qemu_virt/entry.S diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/plic.c b/ports/risc-v64/gnu/example_build/qemu_virt/plic.c index 01e5c71a..b247f4fe 100644..120000 --- a/ports/risc-v64/gnu/example_build/qemu_virt/plic.c +++ b/ports/risc-v64/gnu/example_build/qemu_virt/plic.c @@ -1,72 +1 @@ -#include "plic.h" -#include <stddef.h> -irq_callback callbacks[MAX_CALLBACK_NUM]; - -void plic_irq_enable(int irqno) -{ - int hart = riscv_get_core(); - *(uint32_t*)PLIC_MENABLE(hart) = (*(uint32_t*)PLIC_MENABLE(hart) | (1 << irqno)); - return; -} - -void plic_irq_disable(int irqno) -{ - int hart = riscv_get_core(); - *(uint32_t*)PLIC_MENABLE(hart) = (*(uint32_t*)PLIC_MENABLE(hart) & (~(1 << irqno))); - return; -} - -void plic_prio_set(int irqno, int prio) -{ - PLIC_SET_PRIO(irqno, prio); -} - -int plic_prio_get(int irqno) -{ - return 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) -{ - int hart = riscv_get_core(); - return (*(uint32_t*)PLIC_MCLAIM(hart)); -} - -void plic_complete(int irqno) -{ - int hart = riscv_get_core(); - *(uint32_t*)(PLIC_MCOMPLETE(hart)) = (uint32_t)irqno; - return; -} - -int plic_irq_intr(void) -{ - int ret = -1; - int irqno = plic_claim(); - if(callbacks[irqno] != NULL) - ret = (callbacks[irqno])(irqno); - plic_complete(irqno); - return ret; -} +../../../../risc-v_common/example_build/plic/plic.c
\ No newline at end of file diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/plic.h b/ports/risc-v64/gnu/example_build/qemu_virt/plic.h index 42e4c925..f776a3d2 100644..120000 --- a/ports/risc-v64/gnu/example_build/qemu_virt/plic.h +++ b/ports/risc-v64/gnu/example_build/qemu_virt/plic.h @@ -1,51 +1 @@ -/*************************************************************************** - * Copyright (c) 2024 Microsoft Corporation - * Copyright (c) 2026-present Eclipse ThreadX contributors - * - * This program and the accompanying materials are made available under the - * terms of the MIT License which is available at - * https://opensource.org/licenses/MIT. - * - * 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 - +../../../../risc-v_common/example_build/plic/plic.h
\ No newline at end of file diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv64_qemu.py b/ports/risc-v64/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv64_qemu.py new file mode 100644 index 00000000..459fdf21 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv64_qemu.py @@ -0,0 +1,275 @@ +import subprocess +import sys +import os +import argparse +import socket +import select + +def print_content(content): + """Prints content using os.write to handle non-blocking stdout robustly.""" + try: + msg = f"{content}\n".encode('utf-8') + total_len = len(msg) + written = 0 + fd = sys.stdout.fileno() + while written < total_len: + try: + n = os.write(fd, msg[written:]) + written += n + except BlockingIOError: + select.select([], [fd], []) + except Exception: + pass + +def get_free_port(): + """Finds a free TCP port.""" + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(('', 0)) + return s.getsockname()[1] + +def run_qemu_test(elf_path, qemu_bin, gdb_bin): + """ + Runs a test cycle using QEMU and GDB. + """ + print(f"Testing ELF: {elf_path}") + print(f"QEMU: {qemu_bin}") + print(f"GDB: {gdb_bin}") + + # Find a free port for GDB connection + gdb_port = get_free_port() + print(f"Using GDB port: {gdb_port}") + + # 1. Start QEMU in the background + qemu_cmd = [ + qemu_bin, + "-M", "virt", + "-nographic", + "-bios", "none", # Disable default OpenSBI + "-kernel", elf_path, + "-gdb", f"tcp::{gdb_port}", "-S", + "-monitor", "none", # Disable monitor + "-serial", "stdio" # Redirect serial output to stdio + ] + + print(f"Starting QEMU: {' '.join(qemu_cmd)}") + qemu_process = subprocess.Popen( + qemu_cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + if qemu_process.poll() is not None: + print("QEMU failed to start.") + print(qemu_process.stderr.read()) + return False + + # 2. Create a GDB command file + gdb_cmds = """ +file {elf} +target remote :{port} +set pagination off +set confirm off + +# Setup Breakpoints +break tx_application_define +break thread_0_entry +break thread_6_and_7_entry +break _tx_timer_interrupt + +# Execute to Application Definition +continue + +# Inspect mstatus once thread_0 has started +continue +print/x $mstatus + +# Verify FPU Logic and Register State exercised by thread_6/7 +continue +finish +step +step +step +print/x $mstatus +info registers float +print fpu_test_val + +# Await Timer Interrupt +continue +print "Hit Timer Interrupt" + +# Verify MEPC Integrity - Save State +print/x $mepc +set $saved_pc = $mepc + +# Verify System Timer Before ISR +set $clock_before = _tx_timer_system_clock +print $clock_before + +# Configure Time-Slice Test Conditions +set _tx_timer_time_slice = 1 +set _tx_timer_expired_time_slice = 0 +set $ts_handler_called = 0 + +# Set Breakpoint at Time-Slice Handler with Auto-Continue +tbreak _tx_thread_time_slice +commands + set $ts_handler_called = 1 + continue +end + +# Set Breakpoint at ISR Return Address +set $ret_addr = $ra +tbreak *$ret_addr +continue + +# Verify Time-Slice Handler Was Called +if $ts_handler_called == 1 + print "SUCCESS: Time-slice handler called." +else + print "FAILURE: Time-slice handler NOT called." +end + +# Verify System Timer Increment (Monotonicity) +set $clock_after = _tx_timer_system_clock +print $clock_after + +if $clock_after > $clock_before + print "SUCCESS: System timer incremented." +else + print "FAILURE: System timer did not increment." +end + +# Verify Preemption Logic (Thread Priority) +set $curr_ptr = _tx_thread_current_ptr +set $exec_ptr = _tx_thread_execute_ptr +if $curr_ptr != 0 && $exec_ptr != 0 + set $curr_prio = $curr_ptr->tx_thread_priority + set $exec_prio = $exec_ptr->tx_thread_priority + printf "PREEMPT_CHECK current_prio=%d execute_prio=%d\\n", $curr_prio, $exec_prio + if $exec_prio < $curr_prio + printf "PREEMPT_VERIFIED_OK\\n" + else + printf "PREEMPT_VERIFIED_FAIL_NOT_OBSERVED\\n" + end +else + printf "PREEMPT_VERIFIED_FAIL_NULL\\n" +end + +quit +""".format(port=gdb_port, elf=elf_path) + + gdb_cmd_file = "test_cmds.gdb" + with open(gdb_cmd_file, "w") as f: + f.write(gdb_cmds) + + # 3. Run GDB + gdb_cmd = [ + gdb_bin, + "--batch", + "-x", gdb_cmd_file + ] + + print_content(f"Starting GDB: {' '.join(gdb_cmd)}") + + GDB_TIMEOUT_S = 30 + + try: + gdb_process = subprocess.run( + gdb_cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + timeout=GDB_TIMEOUT_S, + ) + + print_content("GDB Output:") + print_content(gdb_process.stdout) + if gdb_process.stderr: + print_content("GDB Error Output:") + print_content(gdb_process.stderr) + + except subprocess.TimeoutExpired as e: + print_content( + f"FAILURE: GDB session exceeded {GDB_TIMEOUT_S}s timeout; " + "likely stuck on a `continue` that never matched a breakpoint." + ) + if e.stdout: + print_content("GDB Output (partial):") + print_content(e.stdout if isinstance(e.stdout, str) + else e.stdout.decode(errors='replace')) + if e.stderr: + print_content("GDB Error Output (partial):") + print_content(e.stderr if isinstance(e.stderr, str) + else e.stderr.decode(errors='replace')) + return False + + except Exception as e: + print_content(f"An error occurred during test execution: {e}") + return False + + finally: + # 4. Clean up + print_content("Stopping QEMU...") + qemu_process.terminate() + try: + qemu_process.wait(timeout=2) + except subprocess.TimeoutExpired: + print_content("QEMU did not terminate gracefully, killing it forcefully.") + qemu_process.kill() + + # Verify results + stdout = gdb_process.stdout + timer_hit = "Breakpoint 4, _tx_timer_interrupt" in stdout + fpu_verified = False + preemption_verified = "PREEMPT_VERIFIED_OK" in stdout + + if "Breakpoint 3, thread_6_and_7_entry" in stdout: + if "1.10" in stdout or "fpu_test_val" in stdout: + print_content("SUCCESS: FPU instructions executed and registers inspected.") + fpu_verified = True + else: + print_content("FAILURE: Hit thread, but failed to inspect FPU. " + "Output does not contain expected value.") + + if timer_hit: + print_content("SUCCESS: Timer Interrupt verified! Hit _tx_timer_interrupt.") + else: + print_content("FAILURE: Did not hit timer interrupt.") + + if preemption_verified: + print_content("SUCCESS: Preemption verified (higher-priority thread " + "preempted a lower-priority one).") + else: + if "PREEMPT_VERIFIED_FAIL_INVERTED" in stdout: + print_content("FAILURE: Preemption inverted -- lower priority " + "thread scheduled over higher priority one.") + elif "PREEMPT_VERIFIED_FAIL_NULL" in stdout: + print_content("FAILURE: Preemption check saw NULL thread pointers.") + elif "PREEMPT_VERIFIED_FAIL_NOT_OBSERVED" in stdout: + print_content("FAILURE: Preemption was not observed within the " + "loop budget.") + else: + print_content("FAILURE: Preemption check did not run to completion.") + + if timer_hit and fpu_verified and preemption_verified: + return True + else: + return False + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Run ThreadX RV64 QEMU/GDB Test") + parser.add_argument("--elf", required=True, help="Path to the kernel ELF file") + parser.add_argument("--qemu", default="qemu-system-riscv64", + help="Path to QEMU binary") + parser.add_argument("--gdb", default="riscv64-unknown-elf-gdb", + help="Path to GDB binary") + + args = parser.parse_args() + + success = run_qemu_test(args.elf, args.qemu, args.gdb) + + if success: + sys.exit(0) + else: + sys.exit(1) diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/trap.c b/ports/risc-v64/gnu/example_build/qemu_virt/trap.c index 5de9f37a..bdce3305 100644..120000 --- a/ports/risc-v64/gnu/example_build/qemu_virt/trap.c +++ b/ports/risc-v64/gnu/example_build/qemu_virt/trap.c @@ -1,46 +1 @@ -#include "csr.h" -#include <stdint.h> -#include "uart.h" -#include "hwtimer.h" -#include "plic.h" -#include <tx_port.h> -#include <tx_api.h> - -#define OS_IS_INTERUPT(mcause) (mcause & 0x8000000000000000ull) -#define OS_IS_EXCEPTION(mcause) (~(OS_IS_INTERUPT)) -#define OS_IS_TICK_INT(mcause) (mcause == 0x8000000000000007ull) -#define OS_IS_SOFT_INT(mcause) (mcause == 0x8000000000000003ull) -#define OS_IS_EXT_INT(mcause) (mcause == 0x800000000000000bull) -#define OS_IS_TRAP_USER(mcause) (mcause == 0x000000000000000bull) -extern void _tx_timer_interrupt(void); - -void trap_handler(uintptr_t mcause, uintptr_t mepc, uintptr_t mtval) -{ - if(OS_IS_INTERUPT(mcause)) - { - if(OS_IS_TICK_INT(mcause)) - { - hwtimer_handler(); - _tx_timer_interrupt(); - } - else if(OS_IS_EXT_INT(mcause)) - { - int ret = plic_irq_intr(); - if(ret) - { - puts("[INTERRUPT]: handler irq error!"); - while(1) ; - } - } - else - { - puts("[INTERRUPT]: now can't deal with the interrupt!"); - while(1) ; - } - } - else - { - puts("[EXCEPTION] : Unkown Error!!"); - while(1) ; - } -} +../../../../risc-v_common/example_build/trap/trap_qemu.c
\ No newline at end of file diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/uart.c b/ports/risc-v64/gnu/example_build/qemu_virt/uart.c index eea30203..1666c8c8 100644..120000 --- a/ports/risc-v64/gnu/example_build/qemu_virt/uart.c +++ b/ports/risc-v64/gnu/example_build/qemu_virt/uart.c @@ -1,100 +1 @@ -#include "uart.h" -#include "csr.h" -#include "plic.h" -#include <stdint.h> - -// the UART control registers are memory-mapped -// at address UART0. this macro returns the -// address of one of the registers. -#define Reg(reg) ((volatile unsigned char *)(UART0 + (reg))) - -// the UART control registers. -// some have different meanings for -// read vs write. -// see http://byterunner.com/16550.html -#define RHR 0 // receive holding register (for input bytes) -#define THR 0 // transmit holding register (for output bytes) -#define IER 1 // interrupt enable register -#define IER_RX_ENABLE (1<<0) -#define IER_TX_ENABLE (1<<1) -#define FCR 2 // FIFO control register -#define FCR_FIFO_ENABLE (1<<0) -#define FCR_FIFO_CLEAR (3<<1) // clear the content of the two FIFOs -#define ISR 2 // interrupt status register -#define LCR 3 // line control register -#define LCR_EIGHT_BITS (3<<0) -#define LCR_BAUD_LATCH (1<<7) // special mode to set baud rate -#define LSR 5 // line status register -#define LSR_RX_READY (1<<0) // input is waiting to be read from RHR -#define LSR_TX_IDLE (1<<5) // THR can accept another character to send - -#define ReadReg(reg) (*(Reg(reg))) -#define WriteReg(reg, v) (*(Reg(reg)) = (v)) - -int uart_init(void) -{ - // disable interrupts. - WriteReg(IER, 0x00); - - // special mode to set baud rate. - WriteReg(LCR, LCR_BAUD_LATCH); - - // LSB for baud rate of 38.4K. - WriteReg(0, 0x03); - - // MSB for baud rate of 38.4K. - WriteReg(1, 0x00); - - // leave set-baud mode, - // and set word length to 8 bits, no parity. - WriteReg(LCR, LCR_EIGHT_BITS); - - // reset and enable FIFOs. - WriteReg(FCR, FCR_FIFO_ENABLE | FCR_FIFO_CLEAR); - - // enable transmit and receive interrupts. - // WriteReg(IER, IER_TX_ENABLE | IER_RX_ENABLE); - - //enable UART0 in PLIC - plic_irq_enable(UART0_IRQ); - - //set UART0 priority in PLIC - plic_prio_set(UART0_IRQ, 1); - - //register callback for UART0 - //plic_register_callback(UART0_IRQ, uart_intr); - puts("[UART0] : Uart Init Done, this is Test output!"); - return 0; -} - -static inline void uart_putc_nolock(int ch) -{ - // wait for Transmit Holding Empty to be set in LSR. - while((ReadReg(LSR) & LSR_TX_IDLE) == 0) - ; - WriteReg(THR, ch); - return; -} - -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; -} +../../../../risc-v_common/example_build/uart/uart_qemu_ns16550.c
\ No newline at end of file diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/uart.h b/ports/risc-v64/gnu/example_build/qemu_virt/uart.h index 824f0b56..aba5ae7b 100644..120000 --- a/ports/risc-v64/gnu/example_build/qemu_virt/uart.h +++ b/ports/risc-v64/gnu/example_build/qemu_virt/uart.h @@ -1,22 +1 @@ -/*************************************************************************** - * Copyright (c) 2024 Microsoft Corporation - * Copyright (c) 2026-present Eclipse ThreadX contributors - * - * This program and the accompanying materials are made available under the - * terms of the MIT License which is available at - * https://opensource.org/licenses/MIT. - * - * SPDX-License-Identifier: MIT - **************************************************************************/ - -#ifndef RISCV_UART_H -#define RISCV_UART_H - -#define UART0 0x10000000L -#define UART0_IRQ 10 - -#define puts uart_puts -int uart_init(void); -int uart_putc(int ch); -int uart_puts(const char* str); -#endif +../../../../risc-v_common/example_build/uart/uart_qemu_ns16550.h
\ No newline at end of file diff --git a/ports/risc-v64/gnu/inc/tx_port.h b/ports/risc-v64/gnu/inc/tx_port.h index 9bf16704..4df55a6a 100644 --- a/ports/risc-v64/gnu/inc/tx_port.h +++ b/ports/risc-v64/gnu/inc/tx_port.h @@ -72,6 +72,13 @@ #define VOID void +/* IMPORTANT: On this RV64 port LONG/ULONG are intentionally 32-bit (int / + * unsigned int), NOT 64-bit. ThreadX's internal data model requires LONG + * and ULONG to be exactly 4 bytes so that control-block layouts, queue + * message sizes, and the binary API remain identical to all other ThreadX + * ports. Do NOT change these to long/unsigned long — that mistake was + * already corrected once (see PR #534). Use ULONG64 for 64-bit values. */ + #ifndef __ASSEMBLER__ typedef char CHAR; typedef unsigned char UCHAR; @@ -88,6 +95,22 @@ typedef unsigned short USHORT; #define ALIGN_TYPE_DEFINED typedef unsigned long long ALIGN_TYPE; +/* On RV64, ULONG is 32-bit but pointers are 64-bit. Store the thread + pointer in the timer's VOID * extension field so _tx_thread_timeout + can recover it without truncation. This mirrors the win64 port. */ +#define TX_TIMER_INTERNAL_EXTENSION VOID *tx_timer_internal_extension_ptr; + +/* TX_TIMER_EXTENSION_PTR_DEFINED signals to portable code (e.g. tests) + that the timer extension pointer mechanism is in use on this port. */ +#define TX_TIMER_EXTENSION_PTR_DEFINED + +#define TX_THREAD_CREATE_TIMEOUT_SETUP(t) (t) -> tx_thread_timer.tx_timer_internal_timeout_function = &(_tx_thread_timeout); \ + (t) -> tx_thread_timer.tx_timer_internal_timeout_param = 0; \ + (t) -> tx_thread_timer.tx_timer_internal_extension_ptr = (VOID *) (t); + +#define TX_THREAD_TIMEOUT_POINTER_SETUP(t) TX_PARAMETER_NOT_USED(timeout_input); \ + (t) = (TX_THREAD *) _tx_timer_expired_timer_ptr -> tx_timer_internal_extension_ptr; + /* Define the priority levels for ThreadX. Legal values range from 32 to 1024 and MUST be evenly divisible by 32. */ |
