summaryrefslogtreecommitdiff
path: root/ports/risc-v64/gnu
diff options
context:
space:
mode:
authorFrédéric Desbiens <[email protected]>2026-05-27 10:30:57 -0400
committerGitHub <[email protected]>2026-05-27 10:30:57 -0400
commit7486de06c80aac7ce8b40a9fbbabd448707e1fa8 (patch)
tree099df617af05f3d50d9111737bc9f59a826caf23 /ports/risc-v64/gnu
parent2c16114a450056103bacee0a3254a57dad7e1abd (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.txt21
-rwxr-xr-xports/risc-v64/gnu/example_build/qemu_virt/build_libthreadx.sh2
l---------[-rw-r--r--]ports/risc-v64/gnu/example_build/qemu_virt/csr.h376
-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.c73
l---------[-rw-r--r--]ports/risc-v64/gnu/example_build/qemu_virt/plic.h52
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv64_qemu.py275
l---------[-rw-r--r--]ports/risc-v64/gnu/example_build/qemu_virt/trap.c47
l---------[-rw-r--r--]ports/risc-v64/gnu/example_build/qemu_virt/uart.c101
l---------[-rw-r--r--]ports/risc-v64/gnu/example_build/qemu_virt/uart.h23
-rw-r--r--ports/risc-v64/gnu/inc/tx_port.h23
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. */