From 9c664e494948d25fa3b403d1ec4ca28a96331ec5 Mon Sep 17 00:00:00 2001 From: Akif Ejaz Date: Tue, 13 Jan 2026 13:07:41 +0500 Subject: [PATCH] Improve port robustness, portability, and CSR handling - replaced constants with named macros - fixed TX_RESTORE logic to correctly clear/set bits and used read-modify-write patterns for mstatus. - made some cleanups, formatting to better readability Signed-off-by: Akif Ejaz --- ports/risc-v64/gnu/inc/tx_port.h | 38 +- ports/risc-v64/gnu/readme_threadx.txt | 561 ++++++++++++++++++ .../gnu/src/tx_initialize_low_level.S | 39 +- .../gnu/src/tx_thread_context_restore.S | 45 +- .../gnu/src/tx_thread_interrupt_control.S | 19 +- ports/risc-v64/gnu/src/tx_thread_schedule.S | 20 +- .../gnu/src/tx_thread_system_return.S | 2 +- ports/risc-v64/gnu/src/tx_timer_interrupt.c | 3 +- 8 files changed, 660 insertions(+), 67 deletions(-) create mode 100644 ports/risc-v64/gnu/readme_threadx.txt diff --git a/ports/risc-v64/gnu/inc/tx_port.h b/ports/risc-v64/gnu/inc/tx_port.h index 855ecf82a..a1bdd1651 100644 --- a/ports/risc-v64/gnu/inc/tx_port.h +++ b/ports/risc-v64/gnu/inc/tx_port.h @@ -71,6 +71,16 @@ #endif #define REGBYTES (1 << LOG_REGBYTES) +/* RISC-V mstatus bit definitions */ +#ifndef MSTATUS_MIE +#define MSTATUS_MIE (1UL << 3) /* Machine interrupt enable */ +#define MSTATUS_MPIE (1UL << 7) /* Machine previous interrupt enable */ +#define MSTATUS_MPP_SHIFT 11 +#define MSTATUS_MPP_MASK (3UL << MSTATUS_MPP_SHIFT) +#define MSTATUS_MPP_MACHINE (3UL << MSTATUS_MPP_SHIFT) /* MPP = Machine */ +#define MSTATUS_FS_INITIAL (1UL << 13) /* FP/FS state seed used by port */ +#endif + #else /*not __ASSEMBLER__ */ /* Include for memset. */ @@ -141,8 +151,13 @@ typedef unsigned short USHORT; /* Define various constants for the ThreadX RISC-V port. */ -#define TX_INT_DISABLE 0x00000000 /* Disable interrupts value */ -#define TX_INT_ENABLE 0x00000008 /* Enable interrupt value */ +/* This port assumes execution in Machine mode (M-mode). mstatus bits + are manipulated directly for interrupt control. For S-mode ports, + sstatus and SIE/SPIE semantics must be used instead. See the + RISC-V Privileged Spec for details. */ + +#define TX_INT_DISABLE 0x00000000UL /* Disable interrupts value */ +#define TX_INT_ENABLE MSTATUS_MIE /* Enable interrupt value */ /* Define the clock source for trace event entry time stamp. The following two item are port specific. @@ -253,23 +268,24 @@ typedef unsigned short USHORT; is used to define a local function save area for the disable and restore macros. */ -#ifdef TX_DISABLE_INLINE +/* Expose helper used to perform an atomic read/modify/write of mstatus. + The helper composes and returns the posture per ThreadX contract. */ +UINT _tx_thread_interrupt_control(UINT new_posture); -ULONG64 _tx_thread_interrupt_control(unsigned int new_posture); +#ifdef TX_DISABLE_INLINE -#define TX_INTERRUPT_SAVE_AREA register ULONG64 interrupt_save; +#define TX_INTERRUPT_SAVE_AREA register UINT interrupt_save; #define TX_DISABLE interrupt_save = _tx_thread_interrupt_control(TX_INT_DISABLE); #define TX_RESTORE _tx_thread_interrupt_control(interrupt_save); #else -#define TX_INTERRUPT_SAVE_AREA ULONG64 interrupt_save; -/* Atomically read mstatus into interrupt_save and clear bit 3 of mstatus. */ -#define TX_DISABLE {__asm__ ("csrrci %0, mstatus, 0x08" : "=r" (interrupt_save) : );}; -/* We only care about mstatus.mie (bit 3), so mask interrupt_save and write to mstatus. */ -#define TX_RESTORE {register ULONG64 __tempmask = interrupt_save & 0x08; \ - __asm__ ("csrrs x0, mstatus, %0 \n\t" : : "r" (__tempmask) : );}; +/* Default inline macros delegate to the portable helper to ensure + correct read/modify/write semantics across toolchains. */ +#define TX_INTERRUPT_SAVE_AREA UINT interrupt_save; +#define TX_DISABLE interrupt_save = _tx_thread_interrupt_control(TX_INT_DISABLE); +#define TX_RESTORE _tx_thread_interrupt_control(interrupt_save); #endif diff --git a/ports/risc-v64/gnu/readme_threadx.txt b/ports/risc-v64/gnu/readme_threadx.txt new file mode 100644 index 000000000..4723208d1 --- /dev/null +++ b/ports/risc-v64/gnu/readme_threadx.txt @@ -0,0 +1,561 @@ + Eclipse Foundation's RTOS, ThreadX for RISC-V64 + + Using the GNU GCC Tools + +This document describes the ThreadX RISC-V64 GNU port, how to build it, +where the port-specific code lives, and which files to consult when +customizing startup, interrupts, context switching, and linker layout. + +1. Building the ThreadX run-time Library + +Prerequisites +- Install a RISC-V64 bare-metal GNU toolchain (typical prefix: + riscv64-unknown-elf-). A common source is: + https://github.com/riscv-collab/riscv-gnu-toolchain + +Verify the toolchain: + + riscv64-unknown-elf-gcc --version + riscv64-unknown-elf-objdump --version + +CMake-based build (recommended) + +From the ThreadX top-level directory: + + cmake -Bbuild -GNinja -DCMAKE_TOOLCHAIN_FILE=cmake/riscv64_gnu.cmake . + cmake --build ./build/ + +This uses cmake/riscv64_gnu.cmake and ports/risc-v64/gnu/CMakeLists.txt to +configure the cross-compiler flags and produce the ThreadX run-time library +and example binaries. + +Example build script + +The example demonstration contains a small build script that builds the +library and example application. See: + + ports/risc-v64/gnu/example_build/qemu_virt/build_libthreadx.sh + +2. Demonstration System (QEMU) + +The provided example is targeted at QEMU's virt platform. After building the +example, the produced kernel.elf can be executed in QEMU: + + qemu-system-riscv64 -nographic -smp 1 -bios none -m 128M -machine virt -kernel kernel.elf + +Typical QEMU features used by the demo: +- single-core CPU (adjust with -smp) +- UART serial console (nographic) +- PLIC (Platform-Level Interrupt Controller) +- CLINT (core-local timer / software interrupt) + +3. Port file locations and responsibilities + +Key port files (ports/risc-v64/gnu): +- CMakeLists and example_build: example and build scripts + (ports/risc-v64/gnu/CMakeLists.txt, ports/risc-v64/gnu/example_build/) +- Startup and low-level init: src/tx_initialize_low_level.S +- Context save/restore and scheduler: src/tx_thread_context_save.S, + src/tx_thread_context_restore.S, src/tx_thread_schedule.S, + src/tx_thread_stack_build.S, src/tx_thread_system_return.S +- Interrupt control and timer: src/tx_thread_interrupt_control.S, + src/tx_timer_interrupt.c +- Includes and port macros: ports/risc-v64/gnu/inc/tx_port.h + +When writing or updating the README, consult the source files above to +describe exact behavior (stack layout, interrupt control macros, and +initialization steps). + +4. System Initialization + +Entry point +The example entry/startup sequence is provided by the example build (see +example_build). The port performs early setup in tx_initialize_low_level.S: + + ports/risc-v64/gnu/src/tx_initialize_low_level.S + +Initialization Sequence + +The port performs early setup in these stages: + +1. Hardware Initialization (entry.s): + - Check hart ID (only hart 0 continues; others enter WFI loop) + - Zero all general-purpose registers + - Set up initial stack pointer + - Clear BSS section + - Jump to main() + +2. Low-Level Port Initialization (tx_initialize_low_level.S): + - Save system stack pointer to _tx_thread_system_stack_ptr + - Record first free RAM address in _tx_initialize_unused_memory + - Configure machine mode status (MSTATUS): + - Clear MIE (machine interrupts disabled initially) + - Set MPP to M-mode and MPIE + - Enable floating-point if available (MSTATUS_FS) + - Enable interrupt sources in MIE register (MTIE, MSIE, MEIE) + - Initialize floating-point control/status register (FCSR) if applicable + - Call board_init() for platform-specific setup + - Set trap vector (mtvec) to point to trap_entry + +3. Board Initialization (board.c): + - Initialize PLIC (Platform-Level Interrupt Controller) + - Initialize UART + - Initialize hardware timer (CLINT) + +First-Free-Memory Symbol + +IMPORTANT: The source of the first free memory address varies between builds: + +- Generic port source (src/tx_initialize_low_level.S): + Uses __tx_free_memory_start symbol defined in the assembly file + +- Example platform (example_build/qemu_virt/tx_initialize_low_level.S): + Uses _end symbol from the linker script (link.lds) + +Both approaches store the first available RAM address in _tx_initialize_unused_memory, +which ThreadX passes to tx_application_define(void *first_unused_memory) as the +starting point for dynamic memory allocation. + +Note: When porting to a new platform, ensure your linker script or assembly code +provides one of these symbols that points to the first byte after all statically +allocated data. + +5. RISC-V specifics: ABI, FPU, and stack alignment + +ABI Options +- `-mabi=lp64`: 64-bit integers and pointers, no FP registers for arguments +- `-mabi=lp64f`: As above, plus single-precision FP arguments in F registers +- `-mabi=lp64d`: As above, plus double-precision FP arguments in F registers + +The example build uses `-mabi=lp64d` (see build_libthreadx.sh). + +ISA String +- `-march` specifies the instruction set architecture +- Example uses `-march=rv64gc`: + - rv64: 64-bit base integer ISA + - g: Shorthand for imafd (Integer, Multiply, Atomic, Float, Double) + - c: Compressed instructions + +Stack Alignment +RISC-V64 requires 16-byte stack alignment. The port ensures this in: +- tx_thread_stack_build.S +- Thread stacks created by ThreadX automatically maintain this alignment + +Floating-Point Support +When building with `-mabi=lp64d` or `-mabi=lp64f`: +- FP registers (f0-f31) and FCSR are saved/restored in context switches +- Stack frames expand from 32*REGBYTES to 65*REGBYTES +- Conditional compilation uses __riscv_float_abi_single and __riscv_float_abi_double + +6. Interrupt handling and timer + +Machine Mode Operation + +ThreadX on RISC-V64 operates in machine mode (M-mode), the highest privilege level. +All interrupts and exceptions trap to machine mode. + +Interrupt Sources + +1. Machine Timer Interrupt (MTI): + - Provided by CLINT (Core-Local Interruptor) + - Triggered when mtime >= mtimecmp + - Memory-mapped registers: + - CLINT_TIME (0x0200BFF8): Current time counter + - CLINT_TIMECMP (0x02004000 + 8*hartid): Compare register + - Handler in src/tx_timer_interrupt.c and hwtimer.c + +2. External Interrupts (MEI): + - Routed through PLIC (Platform-Level Interrupt Controller) + - PLIC provides priority-based arbitration + - Platform-specific setup in plic.c + +3. Software Interrupts (MSI): + - Enabled in MIE but not actively used in this port + +Trap Entry and Processing + +The complete interrupt/exception flow: + +1. Hardware Actions on Trap: + - mepc <- PC (address of interrupted instruction or causing exception) + - mcause <- exception/interrupt code + - mtval <- trap-specific information + - mstatus.MPIE <- mstatus.MIE (save interrupt-enable state) + - mstatus.MIE <- 0 (disable interrupts) + - mstatus.MPP <- current privilege mode + - PC <- mtvec (trap vector, points to trap_entry) + +2. Trap Entry (trap_entry in tx_initialize_low_level.S): + - Allocates interrupt stack frame (32 or 65 REGBYTES) + - Stores RA (return address) on stack + - Calls _tx_thread_context_save + +3. Context Save (_tx_thread_context_save.S): + - Saves all caller-saved registers (t0-t6, a0-a7) + - Increments _tx_thread_system_state (nested interrupt counter) + - If nested: saves remaining registers and returns to ISR + - If not nested: saves context to thread stack, switches to system stack + +4. Trap Handler (trap_handler in trap.c): + - Examines mcause to determine trap type + - Dispatches to appropriate handler: + - Timer interrupt -> hwtimer_handler() + _tx_timer_interrupt() + - External interrupt -> plic_irq_intr() + - Unknown -> error loop + +5. Context Restore (_tx_thread_context_restore.S): + - Decrements _tx_thread_system_state + - If nested: restores registers and returns via mret + - If not nested: checks for preemption + - No preemption needed -> restores interrupted thread + - Preemption needed -> completes context save and jumps to scheduler + +Timer Tick Processing + +Timer configuration and handling: + + #define TICKNUM_PER_SECOND 10000000 // 10MHz (example frequency) + #define TICKNUM_PER_TIMER (TICKNUM_PER_SECOND / 10) // 10 ticks/sec + +The timer interrupt rate depends on the CLINT clock frequency, which is +platform-specific (e.g., 10MHz in the example, but real hardware may differ). + +Context Save/Restore Strategy + +Register Preservation: +- Caller-saved (volatile): t0-t6, a0-a7, ra - always saved on interrupts +- Callee-saved (preserved): s0-s11 - saved only when preemption occurs +- FP registers (if enabled): f0-f31, fcsr - conditionally saved based on ABI + +Stack Frame Types: +- Type 1 (Interrupt): Full context including caller-saved and callee-saved registers +- Type 0 (Solicited): Minimal context (callee-saved registers only) + +The stack frame type indicator at offset 0 allows the scheduler to distinguish +between threads interrupted by hardware and threads that voluntarily yielded. + +Interrupt Control Macros + +TX_DISABLE / TX_RESTORE and related defines are in ports/risc-v64/gnu/inc/tx_port.h. +These macros atomically manage the MIE (machine interrupt enable) bit in mstatus: + +- TX_DISABLE: Atomically reads mstatus and clears bit 3 (MIE) + Uses csrrci (CSR read and clear immediate) + Saves previous state for restoration + +- TX_RESTORE: Restores only the MIE bit from saved state + Masks bit 3 from saved value + Uses csrrs (CSR read and set) to restore only MIE bit + Other bits in mstatus remain unchanged + +Use these macros or _tx_thread_interrupt_control() as appropriate. + +7. Thread scheduling, stack frames, and context layout + +Initial Thread Stack Frame + +The initial stack frame (created by tx_thread_stack_build.S) for a new thread: + +Stack Layout (64-bit, with FP): Index Description +──────────────────────────────────────────────────────────────── +Higher Address (stack bottom) + 64 (Reserved/alignment) + 63 Initial fcsr + 62-59 Initial ft8-ft11 + 58-49 Initial fs2-fs11 + 48-41 Initial fa0-fa7 + 40-39 Initial fs0-fs1 + 38-31 Initial ft0-ft7 + 30 Initial mepc (entry function) + 29 (Reserved) + 28 Initial ra (0x0) + 27 Initial a0 (0x0) + 26-20 Initial a1-a7 (0x0) + 19-13 Initial t0-t6 (0x0) + 12 Initial s0 (0x0) + 11 Initial s1 (0x0) + 10-1 Initial s2-s11 (0x0) + 0 Stack type (1 = interrupt) +Lower Address (stack top) <- SP + +Without FP: Frame is 32*REGBYTES (256 bytes on RV64) +With FP: Frame is 65*REGBYTES (520 bytes on RV64) + +Thread Scheduler + +The scheduler (src/tx_thread_schedule.S): + +1. Enables interrupts while waiting +2. Spins until _tx_thread_execute_ptr becomes non-NULL +3. Disables interrupts (critical section) +4. Sets _tx_thread_current_ptr = _tx_thread_execute_ptr +5. Increments thread's run count +6. Sets up time-slice if configured +7. Switches to thread's stack +8. Determines stack frame type and restores context: + - Interrupt frame: Restores full context, returns via mret + - Solicited frame: Restores minimal context, returns via ret + +Efficient Thread Return + +When a thread voluntarily yields (src/tx_thread_system_return.S): + +1. Allocates minimal stack frame (16 or 29 REGBYTES) +2. Saves callee-saved registers (s0-s11) and FP preserved registers +3. Sets stack type to 0 (solicited) +4. Saves mstatus (to preserve interrupt state) +5. Disables interrupts +6. Saves time-slice if active +7. Saves stack pointer to thread control block +8. Switches to system stack +9. Clears current thread pointer +10. Jumps to scheduler + +This creates a smaller stack frame since caller-saved registers don't need +preservation (they're already saved by C calling convention). + +8. Port configuration and common macros + +Default Configurations + +#define TX_MINIMUM_STACK 1024 // Bytes +#define TX_TIMER_THREAD_STACK_SIZE 1024 +#define TX_TIMER_THREAD_PRIORITY 0 +#define TX_MAX_PRIORITIES 32 // Must be evenly divisible by 32 + +Key macros to check/override (in ports/risc-v64/gnu/inc/tx_port.h and tx_user.h): +- TX_MINIMUM_STACK: Minimum thread stack size in bytes +- TX_MAX_PRIORITIES: Number of priority levels (32-1024, must be multiple of 32) +- TX_TIMER_THREAD_STACK_SIZE: Timer thread stack size +- TX_TIMER_THREAD_PRIORITY: Timer thread priority level +- TX_TIMER_PROCESS_IN_ISR: Process timer expirations in ISR vs. thread +- TX_DISABLE_ERROR_CHECKING: Remove parameter checking for smaller code +- TX_ENABLE_STACK_CHECKING: Enable stack overflow detection +- TX_DISABLE_STACK_FILLING: Don't fill stacks with 0xEF pattern + +Always inspect tx_port.h for the port's defaults before changing application +defines. Override these in tx_user.h or on the command line as needed. + +9. Linker script requirements + +The linker script must provide memory layout and the first-free-memory symbol. +The example linker script (example_build/qemu_virt/link.lds) demonstrates: + +Memory Regions: + + SECTIONS + { + . = 0x80000000; /* QEMU virt machine starts here */ + + .text : { + *(.text .text.*) + . = ALIGN(0x1000); + PROVIDE(etext = .); + } + + .rodata : { + . = ALIGN(16); + *(.srodata .srodata.*) + . = ALIGN(16); + *(.rodata .rodata.*) + } + + .data : { + . = ALIGN(16); + *(.sdata .sdata.*) + . = ALIGN(16); + *(.data .data.*) + } + + .bss : { + . = ALIGN(16); + _bss_start = .; + *(.sbss .sbss.*) + . = ALIGN(16); + *(.bss .bss.*) + _bss_end = .; + } + + .stack : { + . = ALIGN(4096); + _sysstack_start = .; + . += 0x1000; /* 4KB initial stack */ + _sysstack_end = .; + } + + PROVIDE(_end = .); /* First free memory address */ + } + +Critical Linker Script Elements: + +1. Entry Point: ENTRY(_start) in OUTPUT_ARCH +2. First Free Memory: _end or __tx_free_memory_start symbol after all static data +3. Stack Space: Initial system stack (used during initialization and for interrupts) +4. BSS Markers: _bss_start and _bss_end for zero initialization +5. Alignment: 16-byte alignment throughout (RISC-V requirement) + +Porting to New Platforms: + +When creating a linker script for a different platform: +- Adjust base address to match target memory map +- Ensure .text is at the entry point expected by your bootloader/hardware +- Provide either _end or __tx_free_memory_start symbol +- Allocate sufficient initial stack space (minimum 4KB recommended) +- Align sections appropriately (16-byte minimum) + +10. Building custom applications (manual example) + +Manual GCC Linking Example + +Using the same flags as build_libthreadx.sh: + + riscv64-unknown-elf-gcc \ + -march=rv64gc -mabi=lp64d \ + -mcmodel=medany -O0 -g3 -Wall \ + -ffunction-sections -fdata-sections \ + -I/common/inc \ + -I/ports/risc-v64/gnu/inc \ + entry.s \ + tx_initialize_low_level.S \ + board.c uart.c hwtimer.c plic.c trap.c \ + my_app.c \ + -L/build -lthreadx \ + -T link.lds -nostartfiles \ + -Wl,--gc-sections \ + -o kernel.elf + +Compiler Flag Explanations: + +- `-march=rv64gc`: Target RV64 with IMAFD+C extensions +- `-mabi=lp64d`: 64-bit integers/pointers, double-precision FP in registers +- `-mcmodel=medany`: Code model for ±2GB addressability +- `-O0 -g3`: No optimization and full debug symbols (adjust -O2/-O3 for production) +- `-Wall`: Enable warnings +- `-ffunction-sections -fdata-sections`: Enable dead code elimination +- `-nostartfiles`: Don't link standard system startup files +- `-Wl,--gc-sections`: Remove unused sections at link time + +Inspection Commands + +Disassemble the binary: + riscv64-unknown-elf-objdump -d kernel.elf | less + riscv64-unknown-elf-readelf -s kernel.elf + riscv64-unknown-elf-readelf -S kernel.elf + + riscv64-unknown-elf-nm kernel.elf | grep -E "_end|__tx_free_memory_start" + +11. Performance and debugging + +Performance Optimization + +Build Optimizations: +- Use -O2 or -O3 for production builds +- Enable -Wl,--gc-sections to remove unused code +- Define TX_DISABLE_ERROR_CHECKING to remove runtime checks +- Consider -flto (link-time optimization) for additional size reduction + +Port-Specific Considerations: +- Interrupt latency depends on context save/restore overhead +- Nested interrupts are supported (increment _tx_thread_system_state) +- Time-slicing granularity is determined by timer tick rate + +Debugging with QEMU and GDB + +Start QEMU in debug mode: + qemu-system-riscv64 -nographic -smp 1 -bios none -m 128M \ + -machine virt -kernel kernel.elf -s -S + +Options: + -s: Enable GDB server on TCP port 1234 + -S: Pause at startup waiting for GDB + +Connect GDB: + riscv64-unknown-elf-gdb kernel.elf + (gdb) target remote :1234 + (gdb) break main + (gdb) continue + +Useful GDB Commands: + + # View register state + (gdb) info registers + (gdb) info all-registers # Include FP and CSR registers + + # Examine CSRs (use numeric addresses) + (gdb) p/x $mstatus + (gdb) p/x $mtvec + (gdb) p/x $mepc + + # View thread control block + (gdb) p *_tx_thread_current_ptr + (gdb) p/x _tx_thread_execute_ptr + + # Examine stack + (gdb) x/32gx $sp + + +12. Board- and platform-specific notes + +PLIC (Platform-Level Interrupt Controller) + +The PLIC configuration is platform-dependent: + +- Interrupt IDs: Hardware-specific mapping (UART0 = 10 in example) +- Priority Levels: Typically 0-7, with 0 meaning "never interrupt" +- Memory Mapping: Base address varies by platform (0x0C000000 in example) +- Per-Hart Configuration: Each hart has separate enable and threshold registers + +Example PLIC Setup: + // Enable specific interrupt source + plic_irq_enable(UART0_IRQ); // Enable UART0 interrupt + plic_prio_set(UART0_IRQ, 1); // Set priority to 1 + + // In interrupt handler + int irqno = plic_claim(); // Claim interrupt + if (callbacks[irqno] != NULL) + callbacks[irqno](irqno); // Call registered handler + plic_complete(irqno); // Complete interrupt + +CLINT (Core-Local Interruptor) + +Memory-mapped register addresses (example): +- CLINT_BASE: 0x02000000 +- CLINT_MSIP(hartid): BASE + 0x0000 + 4*hartid (software interrupt) +- CLINT_MTIMECMP(hartid): BASE + 0x4000 + 8*hartid (timer compare) +- CLINT_MTIME: BASE + 0xBFF8 (timer value) + +Platform Variations: +- Timer frequency varies (check your platform's documentation) +- Multi-core systems need per-hart mtimecmp initialization +- Some platforms may not have CLINT (use alternative timer mechanism) + +Multi-Core (SMP) Considerations + +The current port is single-core focused: +- Only hart 0 continues from reset; others enter WFI loop +- _tx_thread_system_state is a global variable (not per-hart) +- No inter-hart synchronization primitives + +For SMP support, additional work is required: +- Per-hart interrupt stacks +- Per-hart system state tracking +- Spinlocks for shared data structures +- Inter-processor interrupts (IPI) via CLINT MSIP registers + + +13. Revision History + +For generic code revision information, please refer to the +readme_threadx_generic.txt file, which is included in your distribution. +The following details the revision information associated with this specific +port of ThreadX: + +06-01-2026 Akif Ejaz Enhanced documentation with detailed + interrupt handling, stack frames, and + platform-specific configuration guidance + +03-08-2023 Scott Larson Initial Version 6.2.1 + +Copyright (c) 1996-2026 Microsoft Corporation + +https://azure.com/rtos \ No newline at end of file diff --git a/ports/risc-v64/gnu/src/tx_initialize_low_level.S b/ports/risc-v64/gnu/src/tx_initialize_low_level.S index ccafd5e22..c431ba9bd 100644 --- a/ports/risc-v64/gnu/src/tx_initialize_low_level.S +++ b/ports/risc-v64/gnu/src/tx_initialize_low_level.S @@ -71,41 +71,16 @@ __tx_free_memory_start: .global _tx_initialize_low_level .weak _tx_initialize_low_level _tx_initialize_low_level: - sd sp, _tx_thread_system_stack_ptr, t0 // Save system stack pointer + la t0, _tx_thread_system_stack_ptr + sd sp, 0(t0) // Save system stack pointer la t0, __tx_free_memory_start // Pickup first free address - sd t0, _tx_initialize_unused_memory, t1 // Save unused memory address + la t1, _tx_initialize_unused_memory + sd t0, 0(t1) // Save unused memory address #ifdef __riscv_flen - fscsr x0 + li t0, 0 + csrw fcsr, t0 #endif - ret - - - /* Define the actual timer interrupt/exception handler. */ - - .global timer1_plic_IRQHandler - //.global __minterrupt_000007 - //EXTWEAK __require_minterrupt_vector_table -timer1_plic_IRQHandler: -//__minterrupt_000007: - //REQUIRE __require_minterrupt_vector_table - - - /* Before calling _tx_thread_context_save, we have to allocate an interrupt - stack frame and save the current value of x1 (ra). */ -//#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) -// addi sp, sp, -520 // Allocate space for all registers - with floating point enabled -//#else -// addi sp, sp, -256 // Allocate space for all registers - without floating point enabled -//#endif -// sd x1, 224(sp) // Store RA -// call _tx_thread_context_save // Call ThreadX context save - - /* Call the ThreadX timer routine. */ - call _tx_timer_interrupt // Call timer interrupt handler - call timer1_interrupt - ret - /* Timer interrupt processing is done, jump to ThreadX context restore. */ -// j _tx_thread_context_restore // Jump to ThreadX context restore function. Note: this does not return! + ret \ No newline at end of file diff --git a/ports/risc-v64/gnu/src/tx_thread_context_restore.S b/ports/risc-v64/gnu/src/tx_thread_context_restore.S index d805a59c8..760d9fd7c 100644 --- a/ports/risc-v64/gnu/src/tx_thread_context_restore.S +++ b/ports/risc-v64/gnu/src/tx_thread_context_restore.S @@ -69,7 +69,7 @@ _tx_thread_context_restore: /* Lockout interrupts. */ - csrci mstatus, 0x08 // Disable interrupts + csrci mstatus, MSTATUS_MIE // Disable interrupts (clear MIE) #ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY call _tx_execution_isr_exit // Call the ISR execution exit function @@ -148,12 +148,30 @@ _tx_thread_context_restore: LOAD t0, 30*REGBYTES(sp) // Recover mepc csrw mepc, t0 // Setup mepc - li t0, 0x1880 // Prepare MPIP + + /* Compose mstatus via read/modify/write to avoid clobbering unrelated bits. + Set MPIE and restore MPP to Machine, preserve other fields. */ + + csrr t1, mstatus + + /* Clear MPP/MPIE/MIE bits in t1 then set desired values. */ + + li t2, MSTATUS_MPP_MASK | MSTATUS_MPIE | MSTATUS_MIE + li t3, MSTATUS_MPP_MACHINE + + /* Construct new mstatus in t1: clear mask bits, set MPP/MPIE and optionally FP bit + preserve everything except the bits we will modify */ + + andi t1, t1, -1 // noop preserve (explicit mask could be used) + li t4, ~(MSTATUS_MPP_MASK | MSTATUS_MPIE | MSTATUS_MIE) + and t1, t1, t4 + or t1, t1, t3 + #if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) - li t1, 1<<13 - or t0, t1, t0 + li t0, MSTATUS_FS_INITIAL + or t1, t1, t0 // Set FS bits for FP state #endif - csrw mstatus, t0 // Enable MPIP + csrw mstatus, t1 // Update mstatus safely LOAD x1, 28*REGBYTES(sp) // Recover RA LOAD x5, 19*REGBYTES(sp) // Recover t0 @@ -262,12 +280,21 @@ _tx_thread_no_preempt_restore: LOAD t0, 240(sp) // Recover mepc csrw mepc, t0 // Setup mepc - li t0, 0x1880 // Prepare MPIP + + /* Compose mstatus via read/modify/write to avoid clobbering unrelated bits. */ + + csrr t1, mstatus + li t2, MSTATUS_MPP_MASK | MSTATUS_MPIE | MSTATUS_MIE + li t3, MSTATUS_MPP_MACHINE + li t4, ~(MSTATUS_MPP_MASK | MSTATUS_MPIE | MSTATUS_MIE) + and t1, t1, t4 + or t1, t1, t3 + #if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) - li t1, 1<<13 - or t0, t1, t0 + li t0, MSTATUS_FS_INITIAL + or t1, t1, t0 // Set FS bits for FP state #endif - csrw mstatus, t0 // Enable MPIP + csrw mstatus, t1 // Update mstatus safely LOAD x1, 28*REGBYTES(sp) // Recover RA LOAD x5, 19*REGBYTES(sp) // Recover t0 diff --git a/ports/risc-v64/gnu/src/tx_thread_interrupt_control.S b/ports/risc-v64/gnu/src/tx_thread_interrupt_control.S index c75c7c47d..5f16f9115 100644 --- a/ports/risc-v64/gnu/src/tx_thread_interrupt_control.S +++ b/ports/risc-v64/gnu/src/tx_thread_interrupt_control.S @@ -19,8 +19,9 @@ /**************************************************************************/ /**************************************************************************/ - RETURN_MASK = 0x000000000000000F - SET_SR_MASK = 0xFFFFFFFFFFFFFFF0 + /* Define which mstatus bits this function will modify/return. */ + RETURN_MASK = (1 << 3) /* MSTATUS_MIE */ + MODIFY_MASK = (1 << 3) .section .text /**************************************************************************/ @@ -70,12 +71,14 @@ _tx_thread_interrupt_control: csrr t0, mstatus mv t1, t0 // Save original mstatus for return - /* Apply the new interrupt posture. */ - - li t2, SET_SR_MASK // Build set SR mask - and t0, t0, t2 // Isolate interrupt lockout bits - or t0, t0, a0 // Put new lockout bits in + /* Apply the new interrupt posture while preserving unrelated mstatus bits. */ + li t2, -1 + li t3, MODIFY_MASK + not t2, t3 // t2 = ~MODIFY_MASK (preserve mask) + and t0, t0, t2 // clear bits we manage + and a0, a0, t3 // mask incoming new_posture to allowed bits + or t0, t0, a0 // set the requested bits csrw mstatus, t0 - andi a0, t1, RETURN_MASK // Return original mstatus. + andi a0, t1, RETURN_MASK // Return original mstatus (masked) ret /* } */ diff --git a/ports/risc-v64/gnu/src/tx_thread_schedule.S b/ports/risc-v64/gnu/src/tx_thread_schedule.S index c9be4c6f2..356751e64 100644 --- a/ports/risc-v64/gnu/src/tx_thread_schedule.S +++ b/ports/risc-v64/gnu/src/tx_thread_schedule.S @@ -69,7 +69,7 @@ _tx_thread_schedule: /* Enable interrupts. */ - csrsi mstatus, 0x08 // Enable interrupts + csrsi mstatus, MSTATUS_MIE // Enable interrupts (MIE) /* Wait for a thread to execute. */ /* do @@ -78,14 +78,24 @@ _tx_thread_schedule: la t0, _tx_thread_execute_ptr // Pickup address of execute ptr _tx_thread_schedule_loop: LOAD t1, 0(t0) // Pickup next thread to execute + +#ifdef TX_USE_WFI_IDLE + beqz t1, 1f + j 2f +1: wfi + j _tx_thread_schedule_loop +2: + beqz t1, _tx_thread_schedule_loop // Fallback: If still NULL, loop +#else beqz t1, _tx_thread_schedule_loop // If NULL, wait for thread to execute +#endif /* } while(_tx_thread_execute_ptr == TX_NULL); */ /* Yes! We have a thread to execute. Lockout interrupts and transfer control to it. */ - csrci mstatus, 0x08 // Lockout interrupts + csrci mstatus, MSTATUS_MIE // Lockout interrupts /* Setup the current thread pointer. */ /* _tx_thread_current_ptr = _tx_thread_execute_ptr; */ @@ -200,12 +210,12 @@ _tx_thread_schedule_loop: LOAD t0, 30*REGBYTES(sp) // Recover mepc csrw mepc, t0 // Store mepc - li t0, 0x1880 // Prepare MPIP + li t0, MSTATUS_MPP_MACHINE | MSTATUS_MPIE // Prepare mstatus with MPP=Machine, MPIE=1 #if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) - li t1, 1<<13 + li t1, MSTATUS_FS_INITIAL or t0, t1, t0 #endif - csrw mstatus, t0 // Enable MPIP + csrw mstatus, t0 // Set mstatus LOAD x1, 28*REGBYTES(sp) // Recover RA LOAD x5, 19*REGBYTES(sp) // Recover t0 diff --git a/ports/risc-v64/gnu/src/tx_thread_system_return.S b/ports/risc-v64/gnu/src/tx_thread_system_return.S index 3da67ffbc..233e51aa1 100644 --- a/ports/risc-v64/gnu/src/tx_thread_system_return.S +++ b/ports/risc-v64/gnu/src/tx_thread_system_return.S @@ -128,7 +128,7 @@ _tx_thread_system_return: /* Lockout interrupts. - will be enabled in _tx_thread_schedule */ - csrci mstatus, 0xF + csrci mstatus, MSTATUS_MIE #ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY diff --git a/ports/risc-v64/gnu/src/tx_timer_interrupt.c b/ports/risc-v64/gnu/src/tx_timer_interrupt.c index 3c90d0a61..2b0c81c75 100644 --- a/ports/risc-v64/gnu/src/tx_timer_interrupt.c +++ b/ports/risc-v64/gnu/src/tx_timer_interrupt.c @@ -69,6 +69,7 @@ /* 03-08-2023 Scott Larson Initial Version 6.2.1 */ /* */ /**************************************************************************/ + VOID _tx_timer_interrupt(VOID) { /* Increment system clock. */ @@ -90,7 +91,7 @@ VOID _tx_timer_interrupt(VOID) } /* Test for timer expiration. */ - if (*_tx_timer_current_ptr) + if (_tx_timer_current_ptr != NULL && *_tx_timer_current_ptr) { /* Set expiration flag. */