Added a RISC-V32 architecture port layer for Clang.

This commit is contained in:
Francisco Manuel Merino Torres
2026-02-26 10:49:23 +01:00
parent 8f126ee414
commit 888f7a42b0
29 changed files with 4167 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
# Toolchain settings
set(CMAKE_C_COMPILER clang-18)
set(CMAKE_CXX_COMPILER clang++-18)
#set(AS llvm-as)
#set(AR llvm-ar)
#set(OBJCOPY llvm-objcopy)
#set(OBJDUMP llvm-objdump-18)
#set(SIZE llvm-size)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
# this makes the test compiles use static library option so that we don't need to pre-set linker flags and scripts
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
set(CMAKE_C_FLAGS "${CFLAGS}" CACHE INTERNAL "c compiler flags")
set(CMAKE_CXX_FLAGS "${CXXFLAGS}" CACHE INTERNAL "cxx compiler flags")
set(CMAKE_ASM_FLAGS "${ASFLAGS} -D__ASSEMBLER__" CACHE INTERNAL "asm compiler flags")
set(CMAKE_EXE_LINKER_FLAGS "${LDFLAGS}" CACHE INTERNAL "exe link flags")
SET(CMAKE_C_FLAGS_DEBUG "--target=riscv32 -march=rv32im_zicsr_zicntr -mabi=ilp32 -g" CACHE INTERNAL "c debug compiler flags")
SET(CMAKE_CXX_FLAGS_DEBUG "--target=riscv32 -march=rv32im_zicsr_zicntr -mabi=ilp32 -g" CACHE INTERNAL "cxx debug compiler flags")
SET(CMAKE_ASM_FLAGS_DEBUG "--target=riscv32 -march=rv32im_zicsr_zicntr -mabi=ilp32 -g" CACHE INTERNAL "asm debug compiler flags")
SET(CMAKE_C_FLAGS_RELEASE "--target=riscv32 -march=rv32im_zicsr_zicntr -mabi=ilp32 -O3" CACHE INTERNAL "c release compiler flags")
SET(CMAKE_CXX_FLAGS_RELEASE "--target=riscv32 -march=rv32im_zicsr_zicntr -mabi=ilp32 -O3" CACHE INTERNAL "cxx release compiler flags")
SET(CMAKE_ASM_FLAGS_RELEASE "--target=riscv32 -march=rv32im_zicsr_zicntr -mabi=ilp32" CACHE INTERNAL "asm release compiler flags")

18
cmake/riscv32_clang.cmake Normal file
View File

@@ -0,0 +1,18 @@
# Name of the target
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR risc-v32)
IF(DEFINED $ENV{GCC_INSTALL_PREFIX})
SET(GCC_INSTALL_PREFIX "$ENV{GCC_INSTALL_PREFIX}" CACHE INTERNAL "" FORCE)
ELSE()
SET(GCC_INSTALL_PREFIX "/opt/riscv_rv32ima" CACHE INTERNAL "" FORCE)
ENDIF()
set(THREADX_ARCH "risc-v32")
set(THREADX_TOOLCHAIN "clang")
set(ARCH_FLAGS "--sysroot=${GCC_INSTALL_PREFIX}/riscv32-unknown-elf --target=riscv32 -g -march=rv32ima_zicsr -mabi=ilp32")
set(CFLAGS "${ARCH_FLAGS}")
set(ASFLAGS "${ARCH_FLAGS}")
set(LDFLAGS "--no-dynamic-linker -m elf32lriscv -static -nostdlib")
include(${CMAKE_CURRENT_LIST_DIR}/riscv32-clang-unknown-elf.cmake)

View File

@@ -0,0 +1,19 @@
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
)

View File

@@ -0,0 +1,58 @@
# RISCV32 clang port
This is basically a copy of the RISC64 gnu port.
The only major modification was changing the load double word (ld)
and store double double (sd) word with load word (ld) and store word (sd).
I also added support for semihosting so the example can be executed on QEMU.
## How to build
cd to the folder where this repo is cloned and run the following commands:
```
cd /threadx/ports/risc-v32/clang/example_build/qemu_virt
./build_libthreadx.sh
./build_threadx_sample.sh
```
The first script will build the ThreadX libraries.
You can find the library in <threadx repo>/build/libthreadx.a.
The second script will build the demo application.
You can find the demo application in <threadx repo>/ports/risc-v32/clang/example_build/qemu_virt/build/demo_threadx.elf
## How to run using QEMU
cd to the folder where this repo is cloned and run the following command:
```
docker run --rm -it -p 1234:1234 -v $(pwd):/threadx -w /threadx ghcr.io/quintauris-tech/qemu-system-riscv32-v10:latest bash
```
The commands assumes that this repo is clone into a folder named "threadx"
```
cd /threadx/ports/risc-v32/clang/example_build/qemu_virt
qemu-system-riscv32 -machine virt -m 16M -bios ./build/demo_threadx.elf -display none -chardev stdio,id=stdio0 -semihosting-config enable=on,userspace=on,chardev=stdio0 -gdb tcp::1234
```
This should print output from different threads. In the QEMU output you should see output like the following:
```
[Thread] : thread_xxxx_entry is here!
```
You can use option -S with qemu-system-riscv32 to debug.
In this case run debugger as /opt/riscv_rv32ima_zicsr/bin/riscv32-unknown-elf-gdb <repo>/ports/risc-v32/gnu/example_build/qemu_virt/build/demo_threadx.elf
```
target remote :1234
```
to connect to the target. Enter 'c' to continue execution.

View File

@@ -0,0 +1,42 @@
/***************************************************************************
* Copyright (c) 2026 Quintauris
*
* This program and the accompanying materials are made available under the
* terms of the MIT License which is available at
* https://opensource.org/licenses/MIT.
*
* SPDX-License-Identifier: MIT
**************************************************************************/
#include "plic.h"
#include "hwtimer.h"
#include "uart.h"
#include <stdint.h>
#include <stddef.h>
void *memset(void *des, int c,size_t n)
{
if((des == NULL) || n <=0)
return (void*)des;
char* t = (char*)des;
int i;
for(i=0;i<n;i++)
t[i]=c;
return t;
}
int board_init(void)
{
int ret;
ret = plic_init();
if(ret)
return ret;
ret = uart_init();
if(ret)
return ret;
ret = hwtimer_init();
if(ret)
return ret;
return 0;
}

View File

@@ -0,0 +1,7 @@
#!/bin/bash
pushd ../../../../../
#cmake -Bbuild -GNinja -DCMAKE_TOOLCHAIN_FILE=cmake/riscv32_clang.cmake .
cmake -Bbuild -DCMAKE_TOOLCHAIN_FILE=cmake/riscv32_clang.cmake .
cmake --build ./build/
popd

View File

@@ -0,0 +1,19 @@
#!/bin/bash
DIRNAME=$(dirname "$0")
BASEDIR=$DIRNAME/../../../../..
mkdir -p build
CC=clang-18
LD=ld.lld-18
$CC -I $BASEDIR/ports/risc-v32/clang/inc -I $BASEDIR/build/custom_inc -g --sysroot=/opt/riscv_rv32ima/riscv32-unknown-elf --target=riscv32 -march=rv32ima_zicsr -mabi=ilp32 -o build/entry.obj -c entry.s
$CC -DTX_INCLUDE_USER_DEFINE_FILE -I $BASEDIR/ports/risc-v32/clang/inc -I $BASEDIR/build/custom_inc -isystem $BASEDIR/common/inc -g --sysroot=/opt/riscv_rv32ima/riscv32-unknown-elf --target=riscv32 -march=rv32ima_zicsr -mabi=ilp32 -D__ASSEMBLER__ -o build/tx_initialize_low_level.obj -c tx_initialize_low_level.S
$CC -DTX_INCLUDE_USER_DEFINE_FILE -I $BASEDIR/ports/risc-v32/clang/inc -I $BASEDIR/build/custom_inc -isystem $BASEDIR/common/inc -g --sysroot=/opt/riscv_rv32ima/riscv32-unknown-elf --target=riscv32 -march=rv32ima_zicsr -mabi=ilp32 -o build/board.obj -c board.c
$CC -DTX_INCLUDE_USER_DEFINE_FILE -I $BASEDIR/ports/risc-v32/clang/inc -I $BASEDIR/build/custom_inc -isystem $BASEDIR/common/inc -g --sysroot=/opt/riscv_rv32ima/riscv32-unknown-elf --target=riscv32 -march=rv32ima_zicsr -mabi=ilp32 -o build/hwtimer.obj -c hwtimer.c
$CC -DTX_INCLUDE_USER_DEFINE_FILE -I $BASEDIR/ports/risc-v32/clang/inc -I $BASEDIR/build/custom_inc -isystem $BASEDIR/common/inc -g --sysroot=/opt/riscv_rv32ima/riscv32-unknown-elf --target=riscv32 -march=rv32ima_zicsr -mabi=ilp32 -o build/plic.obj -c plic.c
$CC -DTX_INCLUDE_USER_DEFINE_FILE -I $BASEDIR/ports/risc-v32/clang/inc -I $BASEDIR/build/custom_inc -isystem $BASEDIR/common/inc -g --sysroot=/opt/riscv_rv32ima/riscv32-unknown-elf --target=riscv32 -march=rv32ima_zicsr -mabi=ilp32 -o build/trap.obj -c trap.c
$CC -DTX_INCLUDE_USER_DEFINE_FILE -I $BASEDIR/ports/risc-v32/clang/inc -I $BASEDIR/build/custom_inc -isystem $BASEDIR/common/inc -g --sysroot=/opt/riscv_rv32ima/riscv32-unknown-elf --target=riscv32 -march=rv32ima_zicsr -mabi=ilp32 -o build/uart.obj -c uart.c
$CC -DTX_INCLUDE_USER_DEFINE_FILE -I $BASEDIR/ports/risc-v32/clang/inc -I $BASEDIR/build/custom_inc -isystem $BASEDIR/common/inc -g --sysroot=/opt/riscv_rv32ima/riscv32-unknown-elf --target=riscv32 -march=rv32ima_zicsr -mabi=ilp32 -o build/demo_threadx.obj -c demo_threadx.c
$LD -Tlink.lds --no-dynamic-linker -m elf32lriscv -static -nostdlib -o build/demo_threadx.elf --Map=build/demo_threadx.map build/entry.obj build/tx_initialize_low_level.obj build/board.obj build/hwtimer.obj build/plic.obj build/trap.obj build/uart.obj build/demo_threadx.obj $BASEDIR/build/libthreadx.a

View File

@@ -0,0 +1,343 @@
/***************************************************************************
* Copyright (c) 2026 Quintauris
*
* 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)
// 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 uint32_t riscv_get_core()
{
uint32_t x;
asm volatile("csrr %0, mhartid" : "=r" (x) );
return x;
}
static inline uint32_t riscv_get_mstatus()
{
uint32_t x;
asm volatile("csrr %0, mstatus" : "=r" (x) );
return x;
}
static inline void riscv_writ_mstatus(uint32_t x)
{
asm volatile("csrw mstatus, %0" : : "r" (x));
}
static inline void riscv_writ_mepc(uint32_t x)
{
asm volatile("csrw mepc, %0" : : "r" (x));
}
static inline uint32_t riscv_get_sstatus()
{
uint32_t x;
asm volatile("csrr %0, sstatus" : "=r" (x) );
return x;
}
static inline void riscv_writ_sstatus(uint32_t x)
{
asm volatile("csrw sstatus, %0" : : "r" (x));
}
static inline uint32_t riscv_get_sip()
{
uint32_t x;
asm volatile("csrr %0, sip" : "=r" (x) );
return x;
}
static inline void riscv_writ_sip(uint32_t x)
{
asm volatile("csrw sip, %0" : : "r" (x));
}
static inline uint32_t riscv_get_sie()
{
uint32_t x;
asm volatile("csrr %0, sie" : "=r" (x) );
return x;
}
static inline void riscv_writ_sie(uint32_t x)
{
asm volatile("csrw sie, %0" : : "r" (x));
}
static inline uint32_t riscv_get_mie()
{
uint32_t x;
asm volatile("csrr %0, mie" : "=r" (x) );
return x;
}
static inline void riscv_writ_mie(uint32_t x)
{
asm volatile("csrw mie, %0" : : "r" (x));
}
static inline void riscv_writ_sepc(uint32_t x)
{
asm volatile("csrw sepc, %0" : : "r" (x));
}
static inline uint32_t riscv_get_sepc()
{
uint32_t x;
asm volatile("csrr %0, sepc" : "=r" (x) );
return x;
}
static inline uint32_t riscv_get_medeleg()
{
uint32_t x;
asm volatile("csrr %0, medeleg" : "=r" (x) );
return x;
}
static inline void riscv_writ_medeleg(uint32_t x)
{
asm volatile("csrw medeleg, %0" : : "r" (x));
}
static inline uint32_t riscv_get_mideleg()
{
uint32_t x;
asm volatile("csrr %0, mideleg" : "=r" (x) );
return x;
}
static inline void riscv_writ_mideleg(uint32_t x)
{
asm volatile("csrw mideleg, %0" : : "r" (x));
}
static inline void riscv_writ_stvec(uint32_t x)
{
asm volatile("csrw stvec, %0" : : "r" (x));
}
static inline uint32_t riscv_get_stvec()
{
uint32_t x;
asm volatile("csrr %0, stvec" : "=r" (x) );
return x;
}
static inline uint32_t riscv_get_stimecmp()
{
uint32_t x;
asm volatile("csrr %0, 0x14d" : "=r" (x) );
return x;
}
static inline void riscv_writ_stimecmp(uint32_t x)
{
asm volatile("csrw 0x14d, %0" : : "r" (x));
}
static inline uint32_t riscv_get_menvcfg()
{
uint32_t x;
asm volatile("csrr %0, 0x30a" : "=r" (x) );
return x;
}
static inline void riscv_writ_menvcfg(uint32_t x)
{
asm volatile("csrw 0x30a, %0" : : "r" (x));
}
static inline void riscv_writ_pmpcfg0(uint32_t x)
{
asm volatile("csrw pmpcfg0, %0" : : "r" (x));
}
static inline void riscv_writ_pmpaddr0(uint32_t x)
{
asm volatile("csrw pmpaddr0, %0" : : "r" (x));
}
static inline void riscv_writ_satp(uint32_t x)
{
asm volatile("csrw satp, %0" : : "r" (x));
}
static inline uint32_t riscv_get_satp()
{
uint32_t x;
asm volatile("csrr %0, satp" : "=r" (x) );
return x;
}
static inline uint32_t riscv_get_scause()
{
uint32_t x;
asm volatile("csrr %0, scause" : "=r" (x) );
return x;
}
static inline uint32_t riscv_get_stval()
{
uint32_t x;
asm volatile("csrr %0, stval" : "=r" (x) );
return x;
}
static inline void riscv_writ_mcounteren(uint32_t x)
{
asm volatile("csrw mcounteren, %0" : : "r" (x));
}
static inline uint32_t riscv_get_mcounteren()
{
uint32_t x;
asm volatile("csrr %0, mcounteren" : "=r" (x) );
return x;
}
static inline uint32_t riscv_get_time()
{
uint32_t x;
asm volatile("csrr %0, time" : "=r" (x) );
return x;
}
static inline void riscv_sintr_on()
{
uint32_t sstatus = riscv_get_sstatus();
sstatus |= SSTATUS_SIE;
riscv_writ_sstatus(sstatus);
}
static inline void riscv_sintr_off()
{
uint32_t sstatus = riscv_get_sstatus();
sstatus &= (~SSTATUS_SIE);
riscv_writ_sstatus(sstatus);
}
static inline int riscv_sintr_get()
{
uint32_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();
}
static inline void riscv_mintr_on()
{
uint32_t mstatus = riscv_get_mstatus();
mstatus |= MSTATUS_MIE;
riscv_writ_mstatus(mstatus);
}
static inline void riscv_mintr_off()
{
uint32_t mstatus = riscv_get_mstatus();
mstatus &= (~MSTATUS_MIE);
riscv_writ_mstatus(mstatus);
}
static inline int riscv_mintr_get()
{
uint32_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 uint32_t riscv_get_sp()
{
uint32_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 uint32_t riscv_get_tp()
{
uint32_t x;
asm volatile("mv %0, tp" : "=r" (x) );
return x;
}
static inline void riscv_writ_tp(uint32_t x)
{
asm volatile("mv tp, %0" : : "r" (x));
}
static inline uint32_t riscv_get_ra()
{
uint32_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

View File

@@ -0,0 +1,393 @@
/* This is a small demo of the high-performance ThreadX kernel. It includes examples of eight
threads of different priorities, using a message queue, semaphore, mutex, event flags group,
byte pool, and block pool. */
#include "tx_api.h"
#include "uart.h"
#define DEMO_STACK_SIZE 1024
#define DEMO_BYTE_POOL_SIZE 9120
#define DEMO_BLOCK_POOL_SIZE 100
#define DEMO_QUEUE_SIZE 100
char *_to_str(ULONG val)
{
static char buf[11]; /* 10 digits max + '\0' */
char *p = buf + sizeof(buf) - 1;
*p = '\0';
do {
*--p = '0' + (val % 10);
val /= 10;
} while (val);
return p;
}
/* Define the ThreadX object control blocks... */
TX_THREAD thread_0;
TX_THREAD thread_1;
TX_THREAD thread_2;
TX_THREAD thread_3;
TX_THREAD thread_4;
TX_THREAD thread_5;
TX_THREAD thread_6;
TX_THREAD thread_7;
TX_QUEUE queue_0;
TX_SEMAPHORE semaphore_0;
TX_MUTEX mutex_0;
TX_EVENT_FLAGS_GROUP event_flags_0;
TX_BYTE_POOL byte_pool_0;
TX_BLOCK_POOL block_pool_0;
/* Define the counters used in the demo application... */
ULONG thread_0_counter;
ULONG thread_1_counter;
ULONG thread_1_messages_sent;
ULONG thread_2_counter;
ULONG thread_2_messages_received;
ULONG thread_3_counter;
ULONG thread_4_counter;
ULONG thread_5_counter;
ULONG thread_6_counter;
ULONG thread_7_counter;
/* Define thread prototypes. */
void thread_0_entry(ULONG thread_input);
void thread_1_entry(ULONG thread_input);
void thread_2_entry(ULONG thread_input);
void thread_3_and_4_entry(ULONG thread_input);
void thread_5_entry(ULONG thread_input);
void thread_6_and_7_entry(ULONG thread_input);
/* Define main entry point. */
int main()
{
/* Enter the ThreadX kernel. */
tx_kernel_enter();
}
/* Define what the initial system looks like. */
void tx_application_define(void *first_unused_memory)
{
CHAR *pointer = TX_NULL;
/* Create a byte memory pool from which to allocate the thread stacks. */
tx_byte_pool_create(&byte_pool_0, "byte pool 0", first_unused_memory, DEMO_BYTE_POOL_SIZE);
/* Put system definition stuff in here, e.g. thread creates and other assorted
create information. */
/* Allocate the stack for thread 0. */
tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
/* Create the main thread. */
tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,
pointer, DEMO_STACK_SIZE,
1, 1, TX_NO_TIME_SLICE, TX_AUTO_START);
/* Allocate the stack for thread 1. */
tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
/* Create threads 1 and 2. These threads pass information through a ThreadX
message queue. It is also interesting to note that these threads have a time
slice. */
tx_thread_create(&thread_1, "thread 1", thread_1_entry, 1,
pointer, DEMO_STACK_SIZE,
16, 16, 4, TX_AUTO_START);
/* Allocate the stack for thread 2. */
tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
tx_thread_create(&thread_2, "thread 2", thread_2_entry, 2,
pointer, DEMO_STACK_SIZE,
16, 16, 4, TX_AUTO_START);
/* Allocate the stack for thread 3. */
tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
/* Create threads 3 and 4. These threads compete for a ThreadX counting semaphore.
An interesting thing here is that both threads share the same instruction area. */
tx_thread_create(&thread_3, "thread 3", thread_3_and_4_entry, 3,
pointer, DEMO_STACK_SIZE,
8, 8, TX_NO_TIME_SLICE, TX_AUTO_START);
/* Allocate the stack for thread 4. */
tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
tx_thread_create(&thread_4, "thread 4", thread_3_and_4_entry, 4,
pointer, DEMO_STACK_SIZE,
8, 8, TX_NO_TIME_SLICE, TX_AUTO_START);
/* Allocate the stack for thread 5. */
tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
/* Create thread 5. This thread simply pends on an event flag which will be set
by thread_0. */
tx_thread_create(&thread_5, "thread 5", thread_5_entry, 5,
pointer, DEMO_STACK_SIZE,
4, 4, TX_NO_TIME_SLICE, TX_AUTO_START);
/* Allocate the stack for thread 6. */
tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
/* Create threads 6 and 7. These threads compete for a ThreadX mutex. */
tx_thread_create(&thread_6, "thread 6", thread_6_and_7_entry, 6,
pointer, DEMO_STACK_SIZE,
8, 8, TX_NO_TIME_SLICE, TX_AUTO_START);
/* Allocate the stack for thread 7. */
tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
tx_thread_create(&thread_7, "thread 7", thread_6_and_7_entry, 7,
pointer, DEMO_STACK_SIZE,
8, 8, TX_NO_TIME_SLICE, TX_AUTO_START);
/* Allocate the message queue. */
tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_QUEUE_SIZE*sizeof(ULONG), TX_NO_WAIT);
/* Create the message queue shared by threads 1 and 2. */
tx_queue_create(&queue_0, "queue 0", TX_1_ULONG, pointer, DEMO_QUEUE_SIZE*sizeof(ULONG));
/* Create the semaphore used by threads 3 and 4. */
tx_semaphore_create(&semaphore_0, "semaphore 0", 1);
/* Create the event flags group used by threads 1 and 5. */
tx_event_flags_create(&event_flags_0, "event flags 0");
/* Create the mutex used by thread 6 and 7 without priority inheritance. */
tx_mutex_create(&mutex_0, "mutex 0", TX_NO_INHERIT);
/* Allocate the memory for a small block pool. */
tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_BLOCK_POOL_SIZE, TX_NO_WAIT);
/* Create a block memory pool to allocate a message buffer from. */
tx_block_pool_create(&block_pool_0, "block pool 0", sizeof(ULONG), pointer, DEMO_BLOCK_POOL_SIZE);
/* Allocate a block and release the block memory. */
tx_block_allocate(&block_pool_0, (VOID **) &pointer, TX_NO_WAIT);
/* Release the block back to the pool. */
tx_block_release(pointer);
}
/* Define the test threads. */
void thread_0_entry(ULONG thread_input)
{
UINT status;
/* This thread simply sits in while-forever-sleep loop. */
while(1)
{
puts("[Thread] : thread_0_entry is here!");
/* Increment the thread counter. */
thread_0_counter++;
/* Sleep for 10 ticks. */
tx_thread_sleep(10);
/* Set event flag 0 to wakeup thread 5. */
status = tx_event_flags_set(&event_flags_0, 0x1, TX_OR);
/* Check status. */
if (status != TX_SUCCESS)
break;
}
}
void thread_1_entry(ULONG thread_input)
{
UINT status;
/* This thread simply sends messages to a queue shared by thread 2. */
while(1)
{
puts("[Thread] : thread_1_entry is here!");
/* Increment the thread counter. */
thread_1_counter++;
/* Send message to queue 0. */
status = tx_queue_send(&queue_0, &thread_1_messages_sent, TX_WAIT_FOREVER);
/* Check completion status. */
if (status != TX_SUCCESS) {
puts("[Thread 1] ERROR: Failed to send message!");
break;
}
/* Increment the message sent. */
thread_1_messages_sent++;
}
}
void thread_2_entry(ULONG thread_input)
{
ULONG received_message;
UINT status;
/* This thread retrieves messages placed on the queue by thread 1. */
while(1)
{
puts("[Thread] : thread_2_entry is here!");
/* Increment the thread counter. */
thread_2_counter++;
/* Retrieve a message from the queue. */
status = tx_queue_receive(&queue_0, &received_message, TX_WAIT_FOREVER);
/* Check completion status and make sure the message is what we
expected. */
if ((status != TX_SUCCESS) || (received_message != thread_2_messages_received)){
puts("[Thread 2] ERROR: Failed to receive message ! Expected # ");
uart_puts(_to_str(thread_2_messages_received));
puts(", but got # ");
uart_puts(_to_str(received_message));
break;
}
/* Otherwise, all is okay. Increment the received message count. */
thread_2_messages_received++;
}
}
void thread_3_and_4_entry(ULONG thread_input)
{
UINT status;
/* This function is executed from thread 3 and thread 4. As the loop
below shows, these function compete for ownership of semaphore_0. */
while(1)
{
puts("[Thread] : thread_3_and_4_entry is here!");
/* Increment the thread counter. */
if (thread_input == 3)
thread_3_counter++;
else
thread_4_counter++;
/* Get the semaphore with suspension. */
status = tx_semaphore_get(&semaphore_0, TX_WAIT_FOREVER);
/* Check status. */
if (status != TX_SUCCESS)
break;
/* Sleep for 2 ticks to hold the semaphore. */
tx_thread_sleep(2);
/* Release the semaphore. */
status = tx_semaphore_put(&semaphore_0);
/* Check status. */
if (status != TX_SUCCESS)
break;
}
}
void thread_5_entry(ULONG thread_input)
{
UINT status;
ULONG actual_flags;
/* This thread simply waits for an event in a forever loop. */
while(1)
{
puts("[Thread] : thread_5_entry is here!");
/* Increment the thread counter. */
thread_5_counter++;
/* Wait for event flag 0. */
status = tx_event_flags_get(&event_flags_0, 0x1, TX_OR_CLEAR,
&actual_flags, TX_WAIT_FOREVER);
/* Check status. */
if ((status != TX_SUCCESS) || (actual_flags != 0x1))
break;
}
}
void thread_6_and_7_entry(ULONG thread_input)
{
UINT status;
/* This function is executed from thread 6 and thread 7. As the loop
below shows, these function compete for ownership of mutex_0. */
while(1)
{
puts("[Thread] : thread_6_and_7_entry is here!");
/* Increment the thread counter. */
if (thread_input == 6)
thread_6_counter++;
else
thread_7_counter++;
/* Get the mutex with suspension. */
status = tx_mutex_get(&mutex_0, TX_WAIT_FOREVER);
/* Check status. */
if (status != TX_SUCCESS)
break;
/* Get the mutex again with suspension. This shows
that an owning thread may retrieve the mutex it
owns multiple times. */
status = tx_mutex_get(&mutex_0, TX_WAIT_FOREVER);
/* Check status. */
if (status != TX_SUCCESS)
break;
/* Sleep for 2 ticks to hold the mutex. */
tx_thread_sleep(2);
/* Release the mutex. */
status = tx_mutex_put(&mutex_0);
/* Check status. */
if (status != TX_SUCCESS)
break;
/* Release the mutex again. This will actually
release ownership since it was obtained twice. */
status = tx_mutex_put(&mutex_0);
/* Check status. */
if (status != TX_SUCCESS)
break;
}
}

View File

@@ -0,0 +1,58 @@
.section .text
.align 4
.global _start
.extern main
.extern _sysstack_start
.extern _bss_start
.extern _bss_end
_start:
csrr t0, mhartid
bne t0, zero, 1f
li x1, 0
li x2, 0
li x3, 0
li x4, 0
li x5, 0
li x6, 0
li x7, 0
li x8, 0
li x9, 0
li x10, 0
li x11, 0
li x12, 0
li x13, 0
li x14, 0
li x15, 0
li x16, 0
li x17, 0
li x18, 0
li x19, 0
li x20, 0
li x21, 0
li x22, 0
li x23, 0
li x24, 0
li x25, 0
li x26, 0
li x27, 0
li x28, 0
li x29, 0
li x30, 0
li x31, 0
la t0, _sysstack_start
li t1, 0x1000
add sp, t0, t1
la t0, _bss_start
la t1, _bss_end
_bss_clean_start:
bgeu t0, t1, _bss_clean_end
sb zero, 0(t0)
addi t0, t0, 1
j _bss_clean_start
_bss_clean_end:
call main
1:
/* todo smp */
wfi
j 1b

View File

@@ -0,0 +1,35 @@
/***************************************************************************
* Copyright (c) 2026 Quintauris
*
* This program and the accompanying materials are made available under the
* terms of the MIT License which is available at
* https://opensource.org/licenses/MIT.
*
* SPDX-License-Identifier: MIT
**************************************************************************/
#include "tx_port.h"
#include "csr.h"
#include "hwtimer.h"
#define CLINT (0x02000000L)
#define CLINT_TIME (CLINT+0xBFF8)
#define CLINT_TIMECMP(hart_id) (CLINT+0x4000+8*(hart_id))
int hwtimer_init(void)
{
int hart = riscv_get_core();
uint64_t time = *((uint64_t*)CLINT_TIME);
*((uint64_t*)CLINT_TIMECMP(hart)) = time + TICKNUM_PER_TIMER;
return 0;
}
int hwtimer_handler(void)
{
int hart = riscv_get_core();
uint64_t time = *((uint64_t*)CLINT_TIME);
*((uint64_t*)CLINT_TIMECMP(hart)) = time + TICKNUM_PER_TIMER;
return 0;
}

View File

@@ -0,0 +1,23 @@
/***************************************************************************
* Copyright (c) 2026 Quintauris
*
* This program and the accompanying materials are made available under the
* terms of the MIT License which is available at
* https://opensource.org/licenses/MIT.
*
* SPDX-License-Identifier: MIT
**************************************************************************/
#ifndef RISCV_HWTIMER_H
#define RISCV_HWTIMER_H
#include <stdint.h>
#define TICKNUM_PER_SECOND 10000000
#define TICKNUM_PER_TIMER (TICKNUM_PER_SECOND / 10)
int hwtimer_init(void);
int hwtimer_handler(void);
#endif

View File

@@ -0,0 +1,49 @@
OUTPUT_ARCH( "riscv" )
ENTRY( _start )
SECTIONS
{
/*
* ensure that entry.S / _entry is at 0x80000000,
* where qemu's -kernel jumps.
*/
. = 0x80000000;
.text : {
*(.text .text.*)
. = ALIGN(0x1000);
PROVIDE(etext = .);
}
.rodata : {
. = ALIGN(16);
*(.srodata .srodata.*) /* do not need to distinguish this from .rodata */
. = ALIGN(16);
*(.rodata .rodata.*)
}
.data : {
. = ALIGN(16);
*(.sdata .sdata.*) /* do not need to distinguish this from .data */
. = ALIGN(16);
*(.data .data.*)
}
.bss : {
. = ALIGN(16);
_bss_start = .;
*(.sbss .sbss.*) /* do not need to distinguish this from .bss */
. = ALIGN(16);
*(.bss .bss.*)
_bss_end = .;
}
.stack : {
. = ALIGN(4096);
_sysstack_start = .;
. += 0x1000;
_sysstack_end = .;
}
PROVIDE(_end = .);
}

View File

@@ -0,0 +1,72 @@
#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;
}

View File

@@ -0,0 +1,49 @@
/***************************************************************************
* Copyright (c) 2026 Quintauris
*
* 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

View File

@@ -0,0 +1,67 @@
#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 & 0x80000000u)
#define OS_IS_EXCEPTION(mcause) (~(OS_IS_INTERUPT))
#define OS_IS_TICK_INT(mcause) (mcause == 0x80000007u)
#define OS_IS_SOFT_INT(mcause) (mcause == 0x80000003u)
#define OS_IS_EXT_INT(mcause) (mcause == 0x8000000bu)
#define OS_IS_TRAP_USER(mcause) (mcause == 0x0000000bu)
extern void _tx_timer_interrupt(void);
extern int uart_putc(int ch);
static void print_hex(uintptr_t val)
{
char digits[] = "0123456789ABCDEF";
uart_putc('0');
uart_putc('x');
for(int i = (sizeof(uintptr_t)*2) - 1; i >= 0; i--) {
int d = (val >> (i*4)) & 0xF;
uart_putc(digits[d]);
}
uart_putc('\n');
}
void trap_handler(uintptr_t mcause, uintptr_t mepc, uintptr_t mtval)
{
// uart_puts("DEBUG : threadx/ports/risc-v32/gnu/example_build/qemu_virt/trap.c, trap_handler\n");
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!!");
puts("mcause:");
print_hex(mcause);
puts("mepc:");
print_hex(mepc);
puts("mtval:");
print_hex(mtval);
while(1) ;
}
}

View File

@@ -0,0 +1,177 @@
/***************************************************************************
* Copyright (c) 2026 Quintauris
*
* This program and the accompanying materials are made available under the
* terms of the MIT License which is available at
* https://opensource.org/licenses/MIT.
*
* SPDX-License-Identifier: MIT
**************************************************************************/
#include "csr.h"
#include "tx_port.h"
.section .text
.align 4
/**************************************************************************/
/* */
/* FUNCTION RELEASE */
/* */
/* trap_entry RISC-V32/GNU */
/* 6.4.x */
/* AUTHOR */
/* */
/* Francisco Merino, Quintauris */
/* */
/* DESCRIPTION */
/* */
/* This function is responsible for riscv processor trap handle */
/* It will do the contex save and call c trap_handler and do contex */
/* load */
/* */
/* INPUT */
/* */
/* None */
/* */
/* OUTPUT */
/* */
/* None */
/* */
/* CALLS */
/* */
/* trap_handler */
/* */
/* CALLED BY */
/* */
/* hardware exception */
/* RELEASE HISTORY */
/* */
/* DATE NAME DESCRIPTION */
/* */
/* 02-02-2026 Francisco Merino Adapted for RV32 Clang */
/* */
/**************************************************************************/
/**************************************************************************/
/**************************************************************************/
/** */
/** ThreadX Component */
/** */
/** Initialize */
/** */
/**************************************************************************/
/**************************************************************************/
.global trap_entry
.extern trap_handler
.extern _tx_thread_context_restore
trap_entry:
#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
addi sp, sp, -65*REGBYTES // Allocate space for all registers - with floating point enabled
#else
addi sp, sp, -32*REGBYTES // Allocate space for all registers - without floating point enabled
#endif
STORE x1, 28*REGBYTES(sp) // Store RA, 28*REGBYTES(because call will override ra [ra is a calle register in riscv])
call _tx_thread_context_save
csrr a0, mcause
csrr a1, mepc
csrr a2, mtval
addi sp, sp, -4
sw ra, 0(sp)
call trap_handler
lw ra, 0(sp)
addi sp, sp, 4
call _tx_thread_context_restore
// it will nerver return
_err:
wfi
j _err
.section .text
/**************************************************************************/
/* */
/* FUNCTION RELEASE */
/* */
/* _tx_initialize_low_level RISC-V32/GNU */
/* 6.4.x */
/* AUTHOR */
/* */
/* Francisco Merino, Quintauris */
/* */
/* DESCRIPTION */
/* */
/* This function is responsible for any low-level processor */
/* initialization, including setting up interrupt vectors, setting */
/* up a periodic timer interrupt source, saving the system stack */
/* pointer for use in ISR processing later, and finding the first */
/* available RAM memory address for tx_application_define. */
/* */
/* INPUT */
/* */
/* None */
/* */
/* OUTPUT */
/* */
/* None */
/* */
/* CALLS */
/* */
/* None */
/* */
/* CALLED BY */
/* */
/* _tx_initialize_kernel_enter ThreadX entry function */
/* */
/* RELEASE HISTORY */
/* */
/* DATE NAME DESCRIPTION */
/* */
/* 26-02-2026 Francisco Merino Adapted for RV32 Clang */
/* */
/**************************************************************************/
/* VOID _tx_initialize_low_level(VOID)
{ */
// .global _tx_initialize_low_level
.weak _tx_initialize_low_level
.extern _end
.extern board_init
_tx_initialize_low_level:
/* debug print
.section .rodata
debug_str_init:
.string "DEBUG : threadx/ports/risc-v32/gnu/example_build/qemu_virt/tx_initialize_low_level.S, _tx_initialize_low_level\n"
*/
.section .text
la t0, _tx_thread_system_stack_ptr
sw sp, 0(t0) // Save system stack pointer
la t0, _end // Pickup first free address
la t1, _tx_initialize_unused_memory
sw t0, 0(t1) // Save unused memory address
li t0, MSTATUS_MIE
csrrc zero, mstatus, t0 // clear MSTATUS_MIE bit
li t0, (MSTATUS_MPP_M | MSTATUS_MPIE )
csrrs zero, mstatus, t0 // set MSTATUS_MPP, MPIE bit
li t0, (MIE_MTIE | MIE_MSIE | MIE_MEIE)
csrrs zero, mie, t0 // set mie
#ifdef __riscv_flen
li t0, MSTATUS_FS
csrrs zero, mstatus, t0 // set MSTATUS_FS bit to open f/d isa in riscv
fscsr x0
#endif
addi sp, sp, -4
sw ra, 0(sp)
call board_init
/* debug print
la a0, debug_str_init
call uart_puts
*/
lw ra, 0(sp)
addi sp, sp, 4
la t0, trap_entry
csrw mtvec, t0
ret

View File

@@ -0,0 +1,102 @@
#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;
}
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;
}

View File

@@ -0,0 +1,22 @@
/***************************************************************************
* Copyright (c) 2024 Microsoft Corporation
*
* 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);
void uart_putc_nolock(int ch);
int uart_puts(const char* str);
#endif

View File

@@ -0,0 +1,309 @@
/***************************************************************************
* Copyright (c) 2025 Quintauris
*
* 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
**************************************************************************/
/**************************************************************************/
/**************************************************************************/
/** */
/** ThreadX Component */
/** */
/** Port Specific */
/** */
/**************************************************************************/
/**************************************************************************/
/**************************************************************************/
/* */
/* PORT SPECIFIC C INFORMATION RELEASE */
/* */
/* tx_port.h RISC-V32/GNU */
/* 6.4.x */
/* */
/* AUTHOR */
/* */
/* Francisco Merino, Quintauris */
/* */
/* DESCRIPTION */
/* */
/* This file contains data type definitions that make the ThreadX */
/* real-time kernel function identically on a variety of different */
/* processor architectures. For example, the size or number of bits */
/* in an "int" data type vary between microprocessor architectures and */
/* even C compilers for the same microprocessor. ThreadX does not */
/* directly use native C data types. Instead, ThreadX creates its */
/* own special types that can be mapped to actual data types by this */
/* file to guarantee consistency in the interface and functionality. */
/* */
/* RELEASE HISTORY */
/* */
/* DATE NAME DESCRIPTION */
/* */
/* 02-26-2026 Francisco Merino Initial Version 6.4.x */
/* */
/**************************************************************************/
#ifndef TX_PORT_H
#define TX_PORT_H
#ifdef __ASSEMBLER__
#if __riscv_xlen == 64
# define SLL32 sllw
# define STORE sd
# define LOAD ld
# define LWU lwu
# define LOG_REGBYTES 3
#else
# define SLL32 sll
# define STORE sw
# define LOAD lw
# define LWU lw
# define LOG_REGBYTES 2
#endif
#define REGBYTES (1 << LOG_REGBYTES)
#else /*not __ASSEMBLER__ */
/* Include for memset. */
#include <string.h>
/* Determine if the optional ThreadX user define file should be used. */
#ifdef TX_INCLUDE_USER_DEFINE_FILE
/* Yes, include the user defines in tx_user.h. The defines in this file may
alternately be defined on the command line. */
#include "tx_user.h"
#endif /* TX_INCLUDE_USER_DEFINE_FILE */
#endif /* __ASSEMBLER__ */
/* Define ThreadX basic types for this port. */
#define VOID void
#ifndef __ASSEMBLER__
typedef char CHAR;
typedef unsigned char UCHAR;
typedef int INT;
typedef unsigned int UINT;
typedef long LONG;
typedef unsigned long ULONG;
typedef unsigned long long ULONG64;
typedef short SHORT;
typedef unsigned short USHORT;
#define ULONG64_DEFINED
#endif /* __ASSEMBLER__ */
/* Define the priority levels for ThreadX. Legal values range
from 32 to 1024 and MUST be evenly divisible by 32. */
#ifndef TX_MAX_PRIORITIES
#define TX_MAX_PRIORITIES 32
#endif
/* Define the minimum stack for a ThreadX thread on this processor. If the size supplied during
thread creation is less than this value, the thread create call will return an error. */
#ifndef TX_MINIMUM_STACK
#define TX_MINIMUM_STACK 1024 /* Minimum stack size for this port */
#endif
/* Define the system timer thread's default stack size and priority. These are only applicable
if TX_TIMER_PROCESS_IN_ISR is not defined. */
#ifndef TX_TIMER_THREAD_STACK_SIZE
#define TX_TIMER_THREAD_STACK_SIZE 1024 /* Default timer thread stack size */
#endif
#ifndef TX_TIMER_THREAD_PRIORITY
#define TX_TIMER_THREAD_PRIORITY 0 /* Default timer thread priority */
#endif
/* 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 */
/* Define the clock source for trace event entry time stamp. The following two item are port specific.
For example, if the time source is at the address 0x0a800024 and is 16-bits in size, the clock
source constants would be:
#define TX_TRACE_TIME_SOURCE *((ULONG *) 0x0a800024)
#define TX_TRACE_TIME_MASK 0x0000FFFFUL
*/
#ifndef TX_TRACE_TIME_SOURCE
#define TX_TRACE_TIME_SOURCE ++_tx_trace_simulated_time
#endif
#ifndef TX_TRACE_TIME_MASK
#define TX_TRACE_TIME_MASK 0xFFFFFFFFUL
#endif
/* Define the port specific options for the _tx_build_options variable. This variable indicates
how the ThreadX library was built. */
#define TX_PORT_SPECIFIC_BUILD_OPTIONS 0
/* Define the in-line initialization constant so that modules with in-line
initialization capabilities can prevent their initialization from being
a function call. */
#define TX_INLINE_INITIALIZATION
/* Determine whether or not stack checking is enabled. By default, ThreadX stack checking is
disabled. When the following is defined, ThreadX thread stack checking is enabled. If stack
checking is enabled (TX_ENABLE_STACK_CHECKING is defined), the TX_DISABLE_STACK_FILLING
define is negated, thereby forcing the stack fill which is necessary for the stack checking
logic. */
#ifdef TX_ENABLE_STACK_CHECKING
#undef TX_DISABLE_STACK_FILLING
#endif
/* Define the TX_THREAD control block extensions for this port. The main reason
for the multiple macros is so that backward compatibility can be maintained with
existing ThreadX kernel awareness modules. */
#define TX_THREAD_EXTENSION_0
#define TX_THREAD_EXTENSION_1
#define TX_THREAD_EXTENSION_2
#define TX_THREAD_EXTENSION_3
/* Define the port extensions of the remaining ThreadX objects. */
#define TX_BLOCK_POOL_EXTENSION
#define TX_BYTE_POOL_EXTENSION
#define TX_EVENT_FLAGS_GROUP_EXTENSION
#define TX_MUTEX_EXTENSION
#define TX_QUEUE_EXTENSION
#define TX_SEMAPHORE_EXTENSION
#define TX_TIMER_EXTENSION
/* Define the user extension field of the thread control block. Nothing
additional is needed for this port so it is defined as white space. */
#ifndef TX_THREAD_USER_EXTENSION
#define TX_THREAD_USER_EXTENSION
#endif
/* Define the macros for processing extensions in tx_thread_create, tx_thread_delete,
tx_thread_shell_entry, and tx_thread_terminate. */
#define TX_THREAD_CREATE_EXTENSION(thread_ptr)
#define TX_THREAD_DELETE_EXTENSION(thread_ptr)
#define TX_THREAD_COMPLETED_EXTENSION(thread_ptr)
#define TX_THREAD_TERMINATED_EXTENSION(thread_ptr)
/* Define the ThreadX object creation extensions for the remaining objects. */
#define TX_BLOCK_POOL_CREATE_EXTENSION(pool_ptr)
#define TX_BYTE_POOL_CREATE_EXTENSION(pool_ptr)
#define TX_EVENT_FLAGS_GROUP_CREATE_EXTENSION(group_ptr)
#define TX_MUTEX_CREATE_EXTENSION(mutex_ptr)
#define TX_QUEUE_CREATE_EXTENSION(queue_ptr)
#define TX_SEMAPHORE_CREATE_EXTENSION(semaphore_ptr)
#define TX_TIMER_CREATE_EXTENSION(timer_ptr)
/* Define the ThreadX object deletion extensions for the remaining objects. */
#define TX_BLOCK_POOL_DELETE_EXTENSION(pool_ptr)
#define TX_BYTE_POOL_DELETE_EXTENSION(pool_ptr)
#define TX_EVENT_FLAGS_GROUP_DELETE_EXTENSION(group_ptr)
#define TX_MUTEX_DELETE_EXTENSION(mutex_ptr)
#define TX_QUEUE_DELETE_EXTENSION(queue_ptr)
#define TX_SEMAPHORE_DELETE_EXTENSION(semaphore_ptr)
#define TX_TIMER_DELETE_EXTENSION(timer_ptr)
/* Define ThreadX interrupt lockout and restore macros for protection on
access of critical kernel information. The restore interrupt macro must
restore the interrupt posture of the running thread prior to the value
present prior to the disable macro. In most cases, the save area macro
is used to define a local function save area for the disable and restore
macros. */
/* Expose helper used to perform an atomic read/modify/write of mstatus.
The helper composes and returns the posture per ThreadX contract. */
#ifndef __ASSEMBLER__
UINT _tx_thread_interrupt_control(UINT new_posture);
#endif
#ifdef TX_DISABLE_INLINE
#define TX_INTERRUPT_SAVE_AREA register UINT interrupt_save;
#define TX_DISABLE __asm__ volatile("csrrci %0, mstatus, 8" : "=r" (interrupt_save) :: "memory");
#define TX_RESTORE { \
unsigned long _temp_mstatus; \
__asm__ volatile( \
"csrc mstatus, 8\n" \
"andi %0, %1, 8\n" \
"csrs mstatus, %0" \
: "=&r" (_temp_mstatus) \
: "r" (interrupt_save) \
: "memory"); \
}
#else
#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);
#endif /* TX_DISABLE_INLINE */
/* Define the interrupt lockout macros for each ThreadX object. */
#define TX_BLOCK_POOL_DISABLE TX_DISABLE
#define TX_BYTE_POOL_DISABLE TX_DISABLE
#define TX_EVENT_FLAGS_GROUP_DISABLE TX_DISABLE
#define TX_MUTEX_DISABLE TX_DISABLE
#define TX_QUEUE_DISABLE TX_DISABLE
#define TX_SEMAPHORE_DISABLE TX_DISABLE
/* Define the version ID of ThreadX. This may be utilized by the application. */
#ifndef __ASSEMBLER__
#ifdef TX_THREAD_INIT
CHAR _tx_version_id[] =
"Copyright (c) 2024 Microsoft Corporation. * ThreadX RISC-V32/GNU Version 6.4.2 *";
#else
extern CHAR _tx_version_id[];
#endif /* TX_THREAD_INIT */
#endif /* __ASSEMBLER__ */
#endif /* TX_PORT_H */

View File

@@ -0,0 +1,436 @@
Eclipse Foundation's RTOS, ThreadX for RISC-V32
Using the Clang Tools
1. Building the ThreadX run-time Library
Prerequisites
- Install a RISC-V32 bare-metal Clang toolchain
- Install a RISC-V32 bare-metal GNU toolchain with riscv32-unknown-elf prefix
- Common source: https://github.com/riscv-collab/riscv-gnu-toolchain
Verify the Clang toolchaing:
clang --version
Verify the GCC toolchain:
riscv32-unknown-elf-gcc --version
riscv32-unknown-elf-objdump --version
CMake-based build (recommended)
From the ThreadX top-level directory:
cmake -Bbuild -GNinja -DCMAKE_TOOLCHAIN_FILE=cmake/riscv32_clang.cmake .
cmake --build ./build/
This uses cmake/riscv32_clang.cmake and ports/risc-v32/clang/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 build script. See:
ports/risc-v32/clang/example_build/qemu_virt/build_libthreadx.sh
This script builds the library and the demo application kernel.elf.
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-riscv32 -nographic -smp 1 -bios none -m 128M -machine virt -kernel kernel.elf
Typical QEMU features used:
- Single-core CPU
- UART serial console
- PLIC (Platform-Level Interrupt Controller)
- CLINT (Core-Local Interruptor) for timer
3. System Initialization
Entry Point
The example startup code begins at the _start label in entry.s. This startup
code performs hardware initialization including:
- Check hart ID (only hart 0 continues; others enter WFI loop)
- Zero general-purpose registers
- Set up initial stack pointer
- Clear BSS section
- Jump to main()
Low-Level Port Initialization (tx_initialize_low_level.S)
The _tx_initialize_low_level function:
- Saves the system stack pointer to _tx_thread_system_stack_ptr
- Records first free RAM address from __tx_free_memory_start symbol
- Initializes floating-point control/status register (FCSR) if floating point enabled
Board Initialization (board.c)
After tx_initialize_low_level returns, main() calls board_init() to:
- Initialize PLIC (Platform-Level Interrupt Controller)
- Initialize UART
- Initialize hardware timer (CLINT)
- Set trap vector (mtvec) to point to trap handler
4. Register Usage and Stack Frames
The RISC-V32 ABI defines t0-t6 and a0-a7 as caller-saved (scratch) registers.
All other registers used by a function must be preserved by the function.
ThreadX takes advantage of this: when a context switch happens during a
function call, only the non-scratch registers need to be saved.
Stack Frame Types
Two types of stack frames exist:
A. Interrupt Frame (stack type = 1)
Created when an interrupt occurs during thread execution.
Saves all registers including caller-saved registers.
Size: 65*4 = 260 bytes (with FP), or 32*4 = 128 bytes (without FP)
B. Solicited Frame (stack type = 0)
Created when a thread voluntarily yields via ThreadX service calls.
Saves only callee-saved registers (s0-s11) and mstatus.
Size: 29*4 = 116 bytes (with FP), or 16*4 = 64 bytes (without FP)
Stack Layout for Interrupt Frame (with FP enabled):
Index Offset Register Description
─────────────────────────────────────────────────
0 0x00 -- Stack type (1 = interrupt)
1 0x04 s11 Preserved register
2 0x08 s10 Preserved register
3 0x0C s9 Preserved register
4 0x10 s8 Preserved register
5 0x14 s7 Preserved register
6 0x18 s6 Preserved register
7 0x1C s5 Preserved register
8 0x20 s4 Preserved register
9 0x24 s3 Preserved register
10 0x28 s2 Preserved register
11 0x2C s1 Preserved register
12 0x30 s0 Preserved register
13 0x34 t6 Scratch register
14 0x38 t5 Scratch register
15 0x3C t4 Scratch register
16 0x40 t3 Scratch register
17 0x44 t2 Scratch register
18 0x48 t1 Scratch register
19 0x4C t0 Scratch register
20 0x50 a7 Argument register
21 0x54 a6 Argument register
22 0x58 a5 Argument register
23 0x5C a4 Argument register
24 0x60 a3 Argument register
25 0x64 a2 Argument register
26 0x68 a1 Argument register
27 0x6C a0 Argument register
28 0x70 ra Return address
29 0x74 -- Reserved
30 0x78 mepc Machine exception PC
31-46 0x7C-0xB8 fs0-fs7 Preserved FP registers*
47-62 0xBC-0xF8 ft0-ft11 Scratch FP registers*
63 0xFC fcsr FP control/status register
─────────────────────────────────────────────────
*Note: In ilp32d ABI, FP registers are 8 bytes each, but current
port implementation uses 4-byte indexing which may cause
overlap if fsd/fld are used.
5. Interrupt Handling
Machine Mode Operation
ThreadX 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):
- Triggered by CLINT when mtime >= mtimecmp
- Handled by _tx_timer_interrupt (src/tx_timer_interrupt.S)
- Called from trap handler in trap.c
2. External Interrupts (MEI):
- Routed through PLIC
- Handler in trap.c calls registered ISR callbacks
3. Software Interrupts (MSI):
- Supported but not actively used in this port
Interrupt Flow
1. Hardware trap entry (automatic):
- mepc <- PC (address of interrupted instruction)
- mcause <- exception/interrupt code
- mstatus.MPIE <- mstatus.MIE (save interrupt-enable state)
- mstatus.MIE <- 0 (disable interrupts)
- mstatus.MPP <- Machine mode
- PC <- mtvec (points to trap_entry in entry.s)
2. Trap entry (entry.s):
- Allocates interrupt stack frame (32*4 or 65*4 bytes depending on FP)
- Saves RA (x1) on stack
- Calls _tx_thread_context_save
3. Context save (_tx_thread_context_save.S):
- Increments _tx_thread_system_state (nested interrupt counter)
- If nested interrupt: saves remaining registers and returns to ISR
- If first interrupt: saves full context, switches to system stack
4. Trap handler (trap.c):
- Examines mcause to determine interrupt type
- Dispatches to appropriate handler (_tx_timer_interrupt or PLIC handler)
- Returns to context restore
5. Context restore (_tx_thread_context_restore.S):
- Decrements _tx_thread_system_state
- Checks if preemption needed
- Restores thread context or switches to next ready thread via scheduler
- Returns to interrupted thread or executes new thread
Interrupt Control Macros
TX_DISABLE and TX_RESTORE macros atomically manage the MIE bit in mstatus:
TX_DISABLE: Saves and clears MIE bit via csrrci (CSR read-clear immediate)
TX_RESTORE: Restores only MIE bit via csrrs (CSR read-set)
Other mstatus bits remain unchanged
These are defined in ports/risc-v32/gnu/inc/tx_port.h and use the
_tx_thread_interrupt_control() function.
6. Thread Scheduling and Context Switching
Thread Scheduler (src/tx_thread_schedule.S)
The scheduler:
1. Enables interrupts while waiting for next thread
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. Switches to thread's stack
7. Determines stack frame type and restores context:
- Interrupt frame: full context restored, returns via mret
- Solicited frame: minimal context restored, returns via ret
Initial Thread Stack Frame (src/tx_thread_stack_build.S)
New threads start with a fake interrupt frame containing:
- All registers initialized to 0
- ra (x1) = 0
- mepc = entry function pointer
- Stack type = 1 (interrupt frame)
- Floating-point registers initialized based on ABI
7. Port Configuration and Macros
Default Configurations (in ports/risc-v32/gnu/inc/tx_port.h):
TX_MINIMUM_STACK 1024 /* Minimum thread stack size */
TX_TIMER_THREAD_STACK_SIZE 1024 /* Timer thread stack size */
TX_TIMER_THREAD_PRIORITY 0 /* Timer thread priority */
TX_MAX_PRIORITIES 32 /* Must be multiple of 32 */
These can be overridden in tx_user.h or on the compiler command line.
8. Build Configuration
CMake Toolchain File: cmake/riscv32_gnu.cmake
Compiler Flags:
-march=rv32gc RV32 with IMAFD+C extensions
-mabi=ilp32d 32-bit integers/pointers, double-precision FP in registers
-mcmodel=medany ±2GB addressability
-D__ASSEMBLER__ For assembly files
ABI Selection
The port uses ilp32d ABI which includes:
- 32-bit integers and pointers
- Double-precision floating-point arguments in registers
- Floating-point registers f0-f31
When building with floating-point ABI:
- FP registers and FCSR are saved/restored in context switches
- Stack frames expand from 32*REGBYTES to 65*REGBYTES
- Conditional compilation uses __riscv_float_abi_double / __riscv_float_abi_single
9. File Organization
Port-specific files (ports/risc-v32/gnu/):
Core assembly files (src/):
- tx_initialize_low_level.S Initial setup and system state
- tx_thread_context_save.S Save context on interrupt entry
- tx_thread_context_restore.S Restore context on interrupt exit
- tx_thread_schedule.S Thread scheduler
- tx_thread_system_return.S Solicited context save for voluntary yield
- tx_thread_stack_build.S Build initial stack frame for new thread
- tx_thread_interrupt_control.S Interrupt enable/disable control
- tx_timer_interrupt.S Timer interrupt handler
Header file (inc/):
- tx_port.h Port-specific defines and macros
Example files (example_build/qemu_virt/):
- entry.s Startup code, trap entry point
- board.c, uart.c, hwtimer.c Platform-specific initialization
- plic.c PLIC interrupt controller driver
- trap.c Trap/exception dispatcher
- link.lds Linker script for QEMU virt
- build_libthreadx.sh Build script
10. Linker Script Requirements
The linker script must provide:
1. Entry point:
ENTRY(_start)
2. Memory layout:
- .text section (code)
- .rodata section (read-only data)
- .data section (initialized data)
- .bss section (uninitialized data)
3. Symbols:
- _end: First free memory address (used by ThreadX allocation)
- _bss_start, _bss_end: For zero initialization
- Initial stack space (example: 4KB)
4. Alignment:
- 16-byte alignment throughout (RISC-V requirement)
Example from QEMU virt build:
SECTIONS
{
. = 0x80000000; /* QEMU virt base address */
.text : { *(.text .text.*) }
.rodata : { *(.rodata .rodata.*) }
.data : { *(.data .data.*) }
.bss : { *(.bss .bss.*) }
.stack : {
. = ALIGN(4096);
_sysstack_start = .;
. += 0x1000; /* 4KB initial stack */
_sysstack_end = .;
}
PROVIDE(_end = .);
}
11. Floating-Point Support
When building with ilp32d ABI and FP enabled:
- FP registers f0-f31 and FCSR are saved/restored during context switches
- Stack frames increase from 32*REGBYTES to 65*REGBYTES (128 to 260 bytes)
- MSTATUS.FS (floating-point state) field is set to indicate dirty FP state
Stack frame differences:
- Without FP: 32*4 = 128 bytes (interrupt), 16*4 = 64 bytes (solicited)
- With FP: 65*4 = 260 bytes (interrupt), 29*4 = 116 bytes (solicited)
12. Performance and Debugging
Performance Optimization
Build optimizations:
- Use -O2 or -O3 for production (example uses -O0 for debugging)
- Enable -Wl,--gc-sections to remove unused code
- Define TX_DISABLE_ERROR_CHECKING to remove parameter checks
- Consider -flto for link-time optimization
Debugging with QEMU and GDB
Start QEMU in debug mode:
qemu-system-riscv32 -nographic -smp 1 -bios none -m 128M \
-machine virt -kernel kernel.elf -s -S
-s: Enable GDB server on TCP port 1234
-S: Pause at startup waiting for GDB
Connect GDB:
riscv32-unknown-elf-gdb kernel.elf
(gdb) target remote :1234
(gdb) break main
(gdb) continue
Useful GDB commands:
(gdb) info registers # View general registers
(gdb) info all-registers # Include CSR and FP registers
(gdb) p/x $mstatus # View machine status register
(gdb) x/32xw $sp # Examine stack memory
(gdb) p *_tx_thread_current_ptr # View current thread control block
13. Platform-Specific Notes (QEMU virt)
PLIC Configuration
The PLIC (Platform-Level Interrupt Controller) is memory-mapped at 0x0C000000:
- Enables up to 1024 interrupt sources
- Supports priority levels 0-7 (0 = disabled)
- Requires per-hart priority threshold and enable register configuration
Example PLIC usage (from plic.c):
plic_irq_enable(irq_number); # Enable specific interrupt
plic_prio_set(irq_number, priority);# Set priority level
CLINT Configuration
The CLINT (Core-Local Interruptor) is memory-mapped at 0x02000000:
- CLINT_MSIP(hartid): 0x0000 + 4*hartid (software interrupt)
- CLINT_MTIMECMP(hartid): 0x4000 + 8*hartid (timer compare)
- CLINT_MTIME: 0xBFF8 (timer value, read-only)
Timer frequency is platform-dependent (example uses 10MHz).
Multi-Core 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
- No per-hart data structures
14. Revision History
For generic code revision information, refer to readme_threadx_generic.txt.
The following details the revision history for this RISC-V32 GNU port:
01-26-2026 Akif Ejaz Brief rewrite with accurate
technical details matching implementation,
register naming per RISC-V ABI, and
complete interrupt flow documentation
(Adapted from RISC-V64 port)
Copyright (c) 1996-2026 Microsoft Corporation
https://azure.com/rtos

View File

@@ -0,0 +1,118 @@
/***************************************************************************
* Copyright (c) 2026 Quintauris
*
* 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
**************************************************************************/
/**************************************************************************/
/**************************************************************************/
/** */
/** ThreadX Component */
/** */
/** Initialize */
/** */
/**************************************************************************/
/**************************************************************************/
.section .data
.global __tx_free_memory_start
__tx_free_memory_start:
.section .text
/**************************************************************************/
/* */
/* FUNCTION RELEASE */
/* */
/* _tx_initialize_low_level RISC-V32/GNU */
/* 6.4.x */
/* AUTHOR */
/* */
/* Francisco Merino, Quintauris */
/* */
/* DESCRIPTION */
/* */
/* This function is responsible for any low-level processor */
/* initialization, including setting up interrupt vectors, setting */
/* up a periodic timer interrupt source, saving the system stack */
/* pointer for use in ISR processing later, and finding the first */
/* available RAM memory address for tx_application_define. */
/* */
/* INPUT */
/* */
/* None */
/* */
/* OUTPUT */
/* */
/* None */
/* */
/* CALLS */
/* */
/* None */
/* */
/* CALLED BY */
/* */
/* _tx_initialize_kernel_enter ThreadX entry function */
/* */
/* RELEASE HISTORY */
/* */
/* DATE NAME DESCRIPTION */
/* */
/* 26-02-2026 Francisco Merino Initial Version 6.4.x */
/* */
/**************************************************************************/
/* VOID _tx_initialize_low_level(VOID)
{ */
// .global _tx_initialize_low_level
.weak _tx_initialize_low_level
_tx_initialize_low_level:
/* Save the system stack pointer. */
/* _tx_thread_system_stack_ptr = sp; */
la t0, _tx_thread_system_stack_ptr // Pickup address of system stack ptr
sw sp, 0(t0) // Save system stack pointer
/* Pickup first free address. */
/* _tx_initialize_unused_memory(__tx_free_memory_start); */
la t0, __tx_free_memory_start // Pickup first free address
la t1, _tx_initialize_unused_memory // Pickup address of unused memory
sw t0, 0(t1) // Save unused memory address
/* Initialize floating point control/status register if floating point is enabled. */
#ifdef __riscv_flen
li t0, 0
csrw fcsr, t0 // Clear FP control/status register
#endif
ret
/* Timer Interrupt Handler Note:
Platform-specific implementations must provide their own timer ISR.
The timer interrupt handler should follow this execution flow:
1. Disable interrupts (if not done by hardware exception entry)
2. Allocate interrupt stack frame (65*4 bytes with FP, 32*4 bytes without)
3. Save RA (x1) on the stack at offset 28*4
4. Call _tx_thread_context_save to save thread context
5. Call _tx_timer_interrupt to process the timer tick
6. Call _tx_thread_context_restore to resume execution (does not return)
Example (for CLINT timer):
_tx_timer_interrupt_handler:
addi sp, sp, -32*4
sw ra, 28*4(sp)
call _tx_thread_context_save
call _tx_timer_interrupt
j _tx_thread_context_restore
The port assumes Machine mode (M-mode) execution.
For Supervisor mode (S-mode), use sstatus and SIE/SPIE instead of mstatus.
See the RISC-V Privileged Specification for more details. */

View File

@@ -0,0 +1,416 @@
/***************************************************************************
* Copyright (c) 2025 Quintauris
*
* 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
**************************************************************************/
/**************************************************************************/
/**************************************************************************/
/** */
/** ThreadX Component */
/** */
/** Thread */
/** */
/**************************************************************************/
/**************************************************************************/
.section .text
/**************************************************************************/
/* */
/* FUNCTION RELEASE */
/* */
/* _tx_thread_context_restore RISC-V32/GNU */
/* 6.4.x */
/* AUTHOR */
/* */
/* Francisco Merino, Quintauris */
/* */
/* DESCRIPTION */
/* */
/* This function restores the interrupt context if it is processing a */
/* nested interrupt. If not, it returns to the interrupt thread if no */
/* preemption is necessary. Otherwise, if preemption is necessary or */
/* if no thread was running, the function returns to the scheduler. */
/* */
/* INPUT */
/* */
/* None */
/* */
/* OUTPUT */
/* */
/* None */
/* */
/* CALLS */
/* */
/* _tx_thread_schedule Thread scheduling routine */
/* */
/* CALLED BY */
/* */
/* ISRs Interrupt Service Routines */
/* */
/* RELEASE HISTORY */
/* */
/* DATE NAME DESCRIPTION */
/* */
/* 26-02-2026 Francisco Merino Initial Version 6.4.x */
/* */
/**************************************************************************/
/* VOID _tx_thread_context_restore(VOID)
{ */
.global _tx_thread_context_restore
_tx_thread_context_restore:
/* Lockout interrupts. */
csrci mstatus, 0x08 // Disable interrupts (MIE bit 3)
#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY
call _tx_execution_isr_exit // Call the ISR execution exit function
#endif
/* Determine if interrupts are nested. */
/* if (--_tx_thread_system_state)
{ */
la t0, _tx_thread_system_state // Pickup addr of nested interrupt count
lw t1, 0(t0) // Pickup nested interrupt count
addi t1, t1, -1 // Decrement the nested interrupt counter
sw t1, 0(t0) // Store new nested count
beqz t1, _tx_thread_not_nested_restore // If 0, not nested restore
/* Interrupts are nested. */
/* Just recover the saved registers and return to the point of
interrupt. */
/* Recover floating point registers. */
#if defined(__riscv_float_abi_single)
flw f0, 31*4(sp) // Recover ft0
flw f1, 32*4(sp) // Recover ft1
flw f2, 33*4(sp) // Recover ft2
flw f3, 34*4(sp) // Recover ft3
flw f4, 35*4(sp) // Recover ft4
flw f5, 36*4(sp) // Recover ft5
flw f6, 37*4(sp) // Recover ft6
flw f7, 38*4(sp) // Recover ft7
flw f10, 41*4(sp) // Recover fa0
flw f11, 42*4(sp) // Recover fa1
flw f12, 43*4(sp) // Recover fa2
flw f13, 44*4(sp) // Recover fa3
flw f14, 45*4(sp) // Recover fa4
flw f15, 46*4(sp) // Recover fa5
flw f16, 47*4(sp) // Recover fa6
flw f17, 48*4(sp) // Recover fa7
flw f28, 59*4(sp) // Recover ft8
flw f29, 60*4(sp) // Recover ft9
flw f30, 61*4(sp) // Recover ft10
flw f31, 62*4(sp) // Recover ft11
lw t0, 63*4(sp) // Recover fcsr
csrw fcsr, t0 // Restore fcsr
#elif defined(__riscv_float_abi_double)
fld f0, 31*4(sp) // Recover ft0
fld f1, 32*4(sp) // Recover ft1
fld f2, 33*4(sp) // Recover ft2
fld f3, 34*4(sp) // Recover ft3
fld f4, 35*4(sp) // Recover ft4
fld f5, 36*4(sp) // Recover ft5
fld f6, 37*4(sp) // Recover ft6
fld f7, 38*4(sp) // Recover ft7
fld f10, 41*4(sp) // Recover fa0
fld f11, 42*4(sp) // Recover fa1
fld f12, 43*4(sp) // Recover fa2
fld f13, 44*4(sp) // Recover fa3
fld f14, 45*4(sp) // Recover fa4
fld f15, 46*4(sp) // Recover fa5
fld f16, 47*4(sp) // Recover fa6
fld f17, 48*4(sp) // Recover fa7
fld f28, 59*4(sp) // Recover ft8
fld f29, 60*4(sp) // Recover ft9
fld f30, 61*4(sp) // Recover ft10
fld f31, 62*4(sp) // Recover ft11
lw t0, 63*4(sp) // Recover fcsr
csrw fcsr, t0 // Restore fcsr
#endif
/* Recover standard registers. */
/* Restore registers,
Skip global pointer because that does not change.
Also skip the saved registers since they have been restored by any function we called,
except s0 since we use it ourselves. */
lw t0, 30*4(sp) // Recover mepc
csrw mepc, t0 // Setup mepc
/* 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, 0x1888 // MPP(0x1800) | MPIE(0x80) | MIE(0x08)
li t3, 0x1800 // Set MPP to Machine mode (bits 12:11)
/* Construct new mstatus in t1: clear mask bits, set MPP/MPIE and optionally FP bit,
preserve everything except the bits we will modify. */
li t4, ~0x1888 // Clear mask for MPP/MPIE/MIE
and t1, t1, t4
or t1, t1, t3
#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
li t0, 0x2000 // Set FS bits (bits 14:13 to 01) for FP state
or t1, t1, t0
#endif
csrw mstatus, t1 // Update mstatus safely
lw ra, 28*4(sp) // Recover return address
lw t0, 19*4(sp) // Recover t0
lw t1, 18*4(sp) // Recover t1
lw t2, 17*4(sp) // Recover t2
lw s0, 12*4(sp) // Recover s0
lw a0, 27*4(sp) // Recover a0
lw a1, 26*4(sp) // Recover a1
lw a2, 25*4(sp) // Recover a2
lw a3, 24*4(sp) // Recover a3
lw a4, 23*4(sp) // Recover a4
lw a5, 22*4(sp) // Recover a5
lw a6, 21*4(sp) // Recover a6
lw a7, 20*4(sp) // Recover a7
lw t3, 16*4(sp) // Recover t3
lw t4, 15*4(sp) // Recover t4
lw t5, 14*4(sp) // Recover t5
lw t6, 13*4(sp) // Recover t6
#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
addi sp, sp, 65*4 // Recover stack frame - with floating point enabled
#else
addi sp, sp, 32*4 // Recover stack frame - without floating point enabled
#endif
mret // Return to point of interrupt
/* } */
_tx_thread_not_nested_restore:
/* Determine if a thread was interrupted and no preemption is required. */
/* else if (((_tx_thread_current_ptr) && (_tx_thread_current_ptr == _tx_thread_execute_ptr)
|| (_tx_thread_preempt_disable))
{ */
la t0, _tx_thread_current_ptr // Pickup current thread pointer address
lw t1, 0(t0) // Pickup current thread pointer
beqz t1, _tx_thread_idle_system_restore // If NULL, idle system restore
la t0, _tx_thread_preempt_disable // Pickup preempt disable flag address
lw t2, 0(t0) // Pickup preempt disable flag (UINT)
bgtz t2, _tx_thread_no_preempt_restore // If set, restore interrupted thread
la t0, _tx_thread_execute_ptr // Pickup thread execute pointer address
lw t2, 0(t0) // Pickup thread execute pointer
bne t1, t2, _tx_thread_preempt_restore // If higher-priority thread is ready, preempt
_tx_thread_no_preempt_restore:
/* Restore interrupted thread or ISR. */
/* Pickup the saved stack pointer. */
/* sp = _tx_thread_current_ptr -> tx_thread_stack_ptr; */
lw sp, 8(t1) // Switch back to thread's stack
/* Recover floating point registers. */
#if defined(__riscv_float_abi_single)
flw f0, 31*4(sp) // Recover ft0
flw f1, 32*4(sp) // Recover ft1
flw f2, 33*4(sp) // Recover ft2
flw f3, 34*4(sp) // Recover ft3
flw f4, 35*4(sp) // Recover ft4
flw f5, 36*4(sp) // Recover ft5
flw f6, 37*4(sp) // Recover ft6
flw f7, 38*4(sp) // Recover ft7
flw f10, 41*4(sp) // Recover fa0
flw f11, 42*4(sp) // Recover fa1
flw f12, 43*4(sp) // Recover fa2
flw f13, 44*4(sp) // Recover fa3
flw f14, 45*4(sp) // Recover fa4
flw f15, 46*4(sp) // Recover fa5
flw f16, 47*4(sp) // Recover fa6
flw f17, 48*4(sp) // Recover fa7
flw f28, 59*4(sp) // Recover ft8
flw f29, 60*4(sp) // Recover ft9
flw f30, 61*4(sp) // Recover ft10
flw f31, 62*4(sp) // Recover ft11
lw t0, 63*4(sp) // Recover fcsr
csrw fcsr, t0 // Restore fcsr
#elif defined(__riscv_float_abi_double)
fld f0, 31*4(sp) // Recover ft0
fld f1, 32*4(sp) // Recover ft1
fld f2, 33*4(sp) // Recover ft2
fld f3, 34*4(sp) // Recover ft3
fld f4, 35*4(sp) // Recover ft4
fld f5, 36*4(sp) // Recover ft5
fld f6, 37*4(sp) // Recover ft6
fld f7, 38*4(sp) // Recover ft7
fld f10, 41*4(sp) // Recover fa0
fld f11, 42*4(sp) // Recover fa1
fld f12, 43*4(sp) // Recover fa2
fld f13, 44*4(sp) // Recover fa3
fld f14, 45*4(sp) // Recover fa4
fld f15, 46*4(sp) // Recover fa5
fld f16, 47*4(sp) // Recover fa6
fld f17, 48*4(sp) // Recover fa7
fld f28, 59*4(sp) // Recover ft8
fld f29, 60*4(sp) // Recover ft9
fld f30, 61*4(sp) // Recover ft10
fld f31, 62*4(sp) // Recover ft11
lw t0, 63*4(sp) // Recover fcsr
csrw fcsr, t0 // Restore fcsr
#endif
/* Recover the saved context and return to the point of interrupt. */
/* Recover standard registers. */
/* Restore registers,
Skip global pointer because that does not change */
lw t0, 30*4(sp) // Recover mepc
csrw mepc, t0 // Setup mepc
/* Compose mstatus via read/modify/write to avoid clobbering unrelated bits. */
csrr t1, mstatus
li t2, 0x1888 // MPP(0x1800) | MPIE(0x80) | MIE(0x08)
li t3, 0x1800 // Set MPP to Machine mode
li t4, ~0x1888 // Clear mask for MPP/MPIE/MIE
and t1, t1, t4
or t1, t1, t3
#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
li t0, 0x2000 // Set FS bits for FP state
or t1, t1, t0
#endif
csrw mstatus, t1 // Update mstatus safely
lw ra, 28*4(sp) // Recover return address
lw t0, 19*4(sp) // Recover t0
lw t1, 18*4(sp) // Recover t1
lw t2, 17*4(sp) // Recover t2
lw s0, 12*4(sp) // Recover s0
lw a0, 27*4(sp) // Recover a0
lw a1, 26*4(sp) // Recover a1
lw a2, 25*4(sp) // Recover a2
lw a3, 24*4(sp) // Recover a3
lw a4, 23*4(sp) // Recover a4
lw a5, 22*4(sp) // Recover a5
lw a6, 21*4(sp) // Recover a6
lw a7, 20*4(sp) // Recover a7
lw t3, 16*4(sp) // Recover t3
lw t4, 15*4(sp) // Recover t4
lw t5, 14*4(sp) // Recover t5
lw t6, 13*4(sp) // Recover t6
#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
addi sp, sp, 65*4 // Recover stack frame - with floating point enabled
#else
addi sp, sp, 32*4 // Recover stack frame - without floating point enabled
#endif
mret // Return to point of interrupt
/* }
else
{ */
_tx_thread_preempt_restore:
/* Instead of directly activating the thread again, ensure we save the
entire stack frame by saving the remaining registers. */
lw t0, 8(t1) // Pickup thread's stack pointer
ori t3, zero, 1 // Build interrupt stack type
sw t3, 0(t0) // Store stack type
/* Store floating point preserved registers. */
#ifdef __riscv_float_abi_single
fsw f8, 39*4(t0) // Store fs0
fsw f9, 40*4(t0) // Store fs1
fsw f18, 49*4(t0) // Store fs2
fsw f19, 50*4(t0) // Store fs3
fsw f20, 51*4(t0) // Store fs4
fsw f21, 52*4(t0) // Store fs5
fsw f22, 53*4(t0) // Store fs6
fsw f23, 54*4(t0) // Store fs7
fsw f24, 55*4(t0) // Store fs8
fsw f25, 56*4(t0) // Store fs9
fsw f26, 57*4(t0) // Store fs10
fsw f27, 58*4(t0) // Store fs11
#elif defined(__riscv_float_abi_double)
fsd f8, 39*4(t0) // Store fs0
fsd f9, 40*4(t0) // Store fs1
fsd f18, 49*4(t0) // Store fs2
fsd f19, 50*4(t0) // Store fs3
fsd f20, 51*4(t0) // Store fs4
fsd f21, 52*4(t0) // Store fs5
fsd f22, 53*4(t0) // Store fs6
fsd f23, 54*4(t0) // Store fs7
fsd f24, 55*4(t0) // Store fs8
fsd f25, 56*4(t0) // Store fs9
fsd f26, 57*4(t0) // Store fs10
fsd f27, 58*4(t0) // Store fs11
#endif
/* Store standard preserved registers. */
sw x9, 11*4(t0) // Store s1
sw x18, 10*4(t0) // Store s2
sw x19, 9*4(t0) // Store s3
sw x20, 8*4(t0) // Store s4
sw x21, 7*4(t0) // Store s5
sw x22, 6*4(t0) // Store s6
sw x23, 5*4(t0) // Store s7
sw x24, 4*4(t0) // Store s8
sw x25, 3*4(t0) // Store s9
sw x26, 2*4(t0) // Store s10
sw x27, 1*4(t0) // Store s11
// Note: s0 is already stored!
/* Save the remaining time-slice and disable it. */
/* if (_tx_timer_time_slice)
{ */
la t0, _tx_timer_time_slice // Pickup time slice variable address
lw t2, 0(t0) // Pickup time slice
beqz t2, _tx_thread_dont_save_ts // If 0, skip time slice processing
/* _tx_thread_current_ptr -> tx_thread_time_slice = _tx_timer_time_slice
_tx_timer_time_slice = 0; */
sw t2, 24(t1) // Save current time slice
sw x0, 0(t0) // Clear global time slice
/* } */
_tx_thread_dont_save_ts:
/* Clear the current task pointer. */
/* _tx_thread_current_ptr = TX_NULL; */
/* Return to the scheduler. */
/* _tx_thread_schedule(); */
la t0, _tx_thread_current_ptr // Pickup current thread pointer address
sw x0, 0(t0) // Clear current thread pointer
/* } */
_tx_thread_idle_system_restore:
/* Just return back to the scheduler! */
j _tx_thread_schedule // Return to scheduler
/* } */

View File

@@ -0,0 +1,277 @@
/***************************************************************************
* Copyright (c) 2026 Quintauris
*
* 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
**************************************************************************/
/**************************************************************************/
/**************************************************************************/
/** */
/** ThreadX Component */
/** */
/** Thread */
/** */
/**************************************************************************/
/**************************************************************************/
.section .text
/**************************************************************************/
/* */
/* FUNCTION RELEASE */
/* */
/* _tx_thread_context_save RISC-V32/GNU */
/* 6.4.x */
/* AUTHOR */
/* */
/* Francisco Merino, Quintauris */
/* */
/* DESCRIPTION */
/* */
/* This function saves the context of an executing thread in the */
/* beginning of interrupt processing. The function also ensures that */
/* the system stack is used upon return to the calling ISR. */
/* */
/* INPUT */
/* */
/* None */
/* */
/* OUTPUT */
/* */
/* None */
/* */
/* CALLS */
/* */
/* None */
/* */
/* CALLED BY */
/* */
/* ISRs */
/* */
/* RELEASE HISTORY */
/* */
/* DATE NAME DESCRIPTION */
/* */
/* 26-02-2026 Francisco Merino Initial Version 6.4.x */
/* */
/**************************************************************************/
/* VOID _tx_thread_context_save(VOID)
{ */
.global _tx_thread_context_save
_tx_thread_context_save:
/* Upon entry to this routine, RA/x1 has been saved on the stack
and the stack has been already allocated for the entire context:
addi sp, sp, -32*4 (or -65*4)
sw ra, 28*4(sp)
*/
sw t0, 19*4(sp) // Store t0
sw t1, 18*4(sp) // Store t1
/* Check for a nested interrupt. */
/* if (_tx_thread_system_state++)
{ */
la t0, _tx_thread_system_state // Pickup addr of system state var
lw t1, 0(t0) // Pickup system state
addi t1, t1, 1 // Increment system state
sw t1, 0(t0) // Store system state
li t0, 1
bgt t1, t0, _tx_thread_nested_save // If it's more than 1, nested interrupt
/* First level interrupt, save the rest of the scratch registers and
check for a thread to preempt. */
sw t2, 17*4(sp) // Store t2
sw s0, 12*4(sp) // Store s0
sw a0, 27*4(sp) // Store a0
sw a1, 26*4(sp) // Store a1
sw a2, 25*4(sp) // Store a2
sw a3, 24*4(sp) // Store a3
sw a4, 23*4(sp) // Store a4
sw a5, 22*4(sp) // Store a5
sw a6, 21*4(sp) // Store a6
sw a7, 20*4(sp) // Store a7
sw t3, 16*4(sp) // Store t3
sw t4, 15*4(sp) // Store t4
sw t5, 14*4(sp) // Store t5
sw t6, 13*4(sp) // Store t6
/* Save floating point registers. */
#if defined(__riscv_float_abi_single)
fsw f0, 31*4(sp) // Store ft0
fsw f1, 32*4(sp) // Store ft1
fsw f2, 33*4(sp) // Store ft2
fsw f3, 34*4(sp) // Store ft3
fsw f4, 35*4(sp) // Store ft4
fsw f5, 36*4(sp) // Store ft5
fsw f6, 37*4(sp) // Store ft6
fsw f7, 38*4(sp) // Store ft7
fsw f10, 41*4(sp) // Store fa0
fsw f11, 42*4(sp) // Store fa1
fsw f12, 43*4(sp) // Store fa2
fsw f13, 44*4(sp) // Store fa3
fsw f14, 45*4(sp) // Store fa4
fsw f15, 46*4(sp) // Store fa5
fsw f16, 47*4(sp) // Store fa6
fsw f17, 48*4(sp) // Store fa7
fsw f28, 59*4(sp) // Store ft8
fsw f29, 60*4(sp) // Store ft9
fsw f30, 61*4(sp) // Store ft10
fsw f31, 62*4(sp) // Store ft11
csrr t0, fcsr
sw t0, 63*4(sp) // Store fcsr
#elif defined(__riscv_float_abi_double)
fsd f0, 31*4(sp) // Store ft0
fsd f1, 32*4(sp) // Store ft1
fsd f2, 33*4(sp) // Store ft2
fsd f3, 34*4(sp) // Store ft3
fsd f4, 35*4(sp) // Store ft4
fsd f5, 36*4(sp) // Store ft5
fsd f6, 37*4(sp) // Store ft6
fsd f7, 38*4(sp) // Store ft7
fsd f10, 41*4(sp) // Store fa0
fsd f11, 42*4(sp) // Store fa1
fsd f12, 43*4(sp) // Store fa2
fsd f13, 44*4(sp) // Store fa3
fsd f14, 45*4(sp) // Store fa4
fsd f15, 46*4(sp) // Store fa5
fsd f16, 47*4(sp) // Store fa6
fsd f17, 48*4(sp) // Store fa7
fsd f28, 59*4(sp) // Store ft8
fsd f29, 60*4(sp) // Store ft9
fsd f30, 61*4(sp) // Store ft10
fsd f31, 62*4(sp) // Store ft11
csrr t0, fcsr
sw t0, 63*4(sp) // Store fcsr
#endif
csrr t0, mepc
sw t0, 30*4(sp) // Save it on the stack
/* Save mstatus. */
csrr t0, mstatus
sw t0, 29*4(sp)
la t1, _tx_thread_current_ptr // Pickup address of current thread ptr
lw t2, 0(t1) // Pickup current thread pointer
beqz t2, _tx_thread_idle_system_save // If NULL, idle system was interrupted
/* Save the current thread's stack pointer and switch to the system stack. */
/* _tx_thread_current_ptr -> tx_thread_stack_ptr = sp;
sp = _tx_thread_system_stack_ptr; */
sw sp, 8(t2) // Save stack pointer
la t0, _tx_thread_system_stack_ptr
lw sp, 0(t0) // Switch to system stack
/* Call the ISR execution exit function if enabled. */
#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY
call _tx_execution_isr_enter // Call the ISR execution enter function
#endif
ret // Return to ISR
_tx_thread_nested_save:
/* Nested interrupt! Just save the scratch registers and return to the ISR. */
sw t2, 17*4(sp) // Store t2
sw s0, 12*4(sp) // Store s0
sw a0, 27*4(sp) // Store a0
sw a1, 26*4(sp) // Store a1
sw a2, 25*4(sp) // Store a2
sw a3, 24*4(sp) // Store a3
sw a4, 23*4(sp) // Store a4
sw a5, 22*4(sp) // Store a5
sw a6, 21*4(sp) // Store a6
sw a7, 20*4(sp) // Store a7
sw t3, 16*4(sp) // Store t3
sw t4, 15*4(sp) // Store t4
sw t5, 14*4(sp) // Store t5
sw t6, 13*4(sp) // Store t6
/* Save floating point registers. */
#if defined(__riscv_float_abi_single)
fsw f0, 31*4(sp) // Store ft0
fsw f1, 32*4(sp) // Store ft1
fsw f2, 33*4(sp) // Store ft2
fsw f3, 34*4(sp) // Store ft3
fsw f4, 35*4(sp) // Store ft4
fsw f5, 36*4(sp) // Store ft5
fsw f6, 37*4(sp) // Store ft6
fsw f7, 38*4(sp) // Store ft7
fsw f10, 41*4(sp) // Store fa0
fsw f11, 42*4(sp) // Store fa1
fsw f12, 43*4(sp) // Store fa2
fsw f13, 44*4(sp) // Store fa3
fsw f14, 45*4(sp) // Store fa4
fsw f15, 46*4(sp) // Store fa5
fsw f16, 47*4(sp) // Store fa6
fsw f17, 48*4(sp) // Store fa7
fsw f28, 59*4(sp) // Store ft8
fsw f29, 60*4(sp) // Store ft9
fsw f30, 61*4(sp) // Store ft10
fsw f31, 62*4(sp) // Store ft11
csrr t0, fcsr
sw t0, 63*4(sp) // Store fcsr
#elif defined(__riscv_float_abi_double)
fsd f0, 31*4(sp) // Store ft0
fsd f1, 32*4(sp) // Store ft1
fsd f2, 33*4(sp) // Store ft2
fsd f3, 34*4(sp) // Store ft3
fsd f4, 35*4(sp) // Store ft4
fsd f5, 36*4(sp) // Store ft5
fsd f6, 37*4(sp) // Store ft6
fsd f7, 38*4(sp) // Store ft7
fsd f10, 41*4(sp) // Store fa0
fsd f11, 42*4(sp) // Store fa1
fsd f12, 43*4(sp) // Store fa2
fsd f13, 44*4(sp) // Store fa3
fsd f14, 45*4(sp) // Store fa4
fsd f15, 46*4(sp) // Store fa5
fsd f16, 47*4(sp) // Store fa6
fsd f17, 48*4(sp) // Store fa7
fsd f28, 59*4(sp) // Store ft8
fsd f29, 60*4(sp) // Store ft9
fsd f30, 61*4(sp) // Store ft10
fsd f31, 62*4(sp) // Store ft11
csrr t0, fcsr
sw t0, 63*4(sp) // Store fcsr
#endif
csrr t0, mepc
sw t0, 30*4(sp) // Save it on stack
csrr t0, mstatus
sw t0, 29*4(sp)
/* Call the ISR execution exit function if enabled. */
#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY
call _tx_execution_isr_enter // Call the ISR execution enter function
#endif
ret // Return to ISR
_tx_thread_idle_system_save:
#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY
call _tx_execution_isr_enter // Call the ISR execution enter function
#endif
/* Interrupt occurred in the scheduling loop. */
/* }
} */
#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
addi sp, sp, 65*4 // Recover stack frame - with floating point enabled
#else
addi sp, sp, 32*4 // Recover the reserved stack space
#endif
ret // Return to calling ISR

View File

@@ -0,0 +1,94 @@
/***************************************************************************
* Copyright (c) 2026 Quintauris
*
* 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
**************************************************************************/
/**************************************************************************/
/**************************************************************************/
/** */
/** ThreadX Component */
/** */
/** Thread */
/** */
/**************************************************************************/
/**************************************************************************/
.section .text
/**************************************************************************/
/* */
/* FUNCTION RELEASE */
/* */
/* _tx_thread_interrupt_control RISC-V32/GNU */
/* 6.4.x */
/* AUTHOR */
/* */
/* Francisco Merino, Quintauris */
/* */
/* DESCRIPTION */
/* */
/* This function is responsible for changing the interrupt lockout */
/* posture of the system. */
/* */
/* INPUT */
/* */
/* new_posture New interrupt lockout posture */
/* */
/* OUTPUT */
/* */
/* old_posture Old interrupt lockout posture */
/* */
/* CALLS */
/* */
/* None */
/* */
/* CALLED BY */
/* */
/* Application Code */
/* */
/* RELEASE HISTORY */
/* */
/* DATE NAME DESCRIPTION */
/* */
/* 26-02-2026 Francisco Merino Initial Version 6.4.x */
/* */
/**************************************************************************/
/* UINT _tx_thread_interrupt_control(UINT new_posture)
{ */
.global _tx_thread_interrupt_control
_tx_thread_interrupt_control:
/* Pickup current interrupt posture. */
csrr a1, mstatus // Pickup mstatus
andi a1, a1, 0x08 // Mask out all but MIE
/* Check for the new posture. */
beqz a0, _tx_thread_interrupt_disable // If 0, disable interrupts
/* Enable interrupts. */
csrsi mstatus, 0x08 // Enable interrupts (MIE bit 3)
j _tx_thread_interrupt_control_exit // Return to caller
_tx_thread_interrupt_disable:
/* Disable interrupts. */
csrci mstatus, 0x08 // Disable interrupts (MIE bit 3)
_tx_thread_interrupt_control_exit:
/* Return the old interrupt posture. */
mv a0, a1 // Setup return value
ret // Return to caller
/* } */

View File

@@ -0,0 +1,324 @@
/***************************************************************************
* Copyright (c) 2026 Quintauris
*
* 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
**************************************************************************/
/**************************************************************************/
/**************************************************************************/
/** */
/** ThreadX Component */
/** */
/** Thread */
/** */
/**************************************************************************/
/**************************************************************************/
.section .text
/**************************************************************************/
/* */
/* FUNCTION RELEASE */
/* */
/* _tx_thread_schedule RISC-V32/GNU */
/* 6.4.x */
/* AUTHOR */
/* */
/* Francisco Merino, Quintauris */
/* */
/* DESCRIPTION */
/* */
/* This function waits for a thread control block pointer to appear in */
/* the _tx_thread_execute_ptr variable. Once a thread pointer appears */
/* in the variable, the corresponding thread is resumed. */
/* */
/* INPUT */
/* */
/* None */
/* */
/* OUTPUT */
/* */
/* None */
/* */
/* CALLS */
/* */
/* None */
/* */
/* CALLED BY */
/* */
/* _tx_initialize_kernel_enter ThreadX entry function */
/* _tx_thread_system_return Return to system from thread */
/* _tx_thread_context_restore Restore thread's context */
/* */
/* RELEASE HISTORY */
/* */
/* DATE NAME DESCRIPTION */
/* */
/* 23-12-2025 Akif Ejaz Initial Version 6.4.x */
/* */
/**************************************************************************/
/* VOID _tx_thread_schedule(VOID)
{ */
.global _tx_thread_schedule
_tx_thread_schedule:
/* Enable interrupts. */
csrsi mstatus, 0x08 // Enable interrupts (MIE bit 3)
/* Wait for a thread to execute. */
/* do
{ */
_tx_thread_schedule_loop:
la t0, _tx_thread_execute_ptr // Pickup address of execute ptr
lw t1, 0(t0) // Pickup execute pointer
bnez t1, _tx_thread_ready_to_run // If non-NULL, a thread is ready to run
#ifndef TX_NO_WFI
wfi // Wait for interrupt
#endif
j _tx_thread_schedule_loop // Check again
/* }
while (_tx_thread_execute_ptr == TX_NULL); */
_tx_thread_ready_to_run:
/* At this point, t1 contains the pointer to the thread to execute.
Lockout interrupts. */
csrci mstatus, 0x08 // Disable interrupts (MIE bit 3)
/* Check _tx_thread_execute_ptr again, in case an interrupt occurred
between the check and the disable. */
lw t1, 0(t0) // Pickup execute pointer
beqz t1, _tx_thread_schedule_loop // If NULL, go back to wait loop
/* Yes! We have a thread to execute. */
/* _tx_thread_current_ptr = _tx_thread_execute_ptr; */
la t0, _tx_thread_current_ptr // Pickup address of current thread
sw t1, 0(t0) // Setup current thread pointer
/* Increment the run count for this thread. */
/* _tx_thread_current_ptr -> tx_thread_run_count++; */
lw t2, 4(t1) // Pickup run count
addi t2, t2, 1 // Increment run count
sw t2, 4(t1) // Store run count
/* Setup time-slice values. */
/* _tx_timer_time_slice = _tx_thread_current_ptr -> tx_thread_time_slice; */
lw t2, 24(t1) // Pickup thread time-slice
la t3, _tx_timer_time_slice // Pickup address of time-slice
sw t2, 0(t3) // Setup time-slice
/* Call the thread execution enter function if enabled. */
#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY
call _tx_execution_thread_enter // Call the thread execution enter function
#endif
/* Switch to the thread's stack. */
/* sp = _tx_thread_current_ptr -> tx_thread_stack_ptr; */
lw sp, 8(t1) // Switch to thread stack
/* Determine the type of stack frame. */
/* if (*sp)
{ */
lw t0, 0(sp) // Pickup stack type
beqz t0, _tx_thread_solicited_return // If 0, solicited return
/* Recover floating point registers. */
#if defined(__riscv_float_abi_single)
flw f0, 31*4(sp) // Recover ft0
flw f1, 32*4(sp) // Recover ft1
flw f2, 33*4(sp) // Recover ft2
flw f3, 34*4(sp) // Recover ft3
flw f4, 35*4(sp) // Recover ft4
flw f5, 36*4(sp) // Recover ft5
flw f6, 37*4(sp) // Recover ft6
flw f7, 38*4(sp) // Recover ft7
flw f8, 39*4(sp) // Recover fs0
flw f9, 40*4(sp) // Recover fs1
flw f10, 41*4(sp) // Recover fa0
flw f11, 42*4(sp) // Recover fa1
flw f12, 43*4(sp) // Recover fa2
flw f13, 44*4(sp) // Recover fa3
flw f14, 45*4(sp) // Recover fa4
flw f15, 46*4(sp) // Recover fa5
flw f16, 47*4(sp) // Recover fa6
flw f17, 48*4(sp) // Recover fa7
flw f18, 49*4(sp) // Recover fs2
flw f19, 50*4(sp) // Recover fs3
flw f20, 51*4(sp) // Recover fs4
flw f21, 52*4(sp) // Recover fs5
flw f22, 53*4(sp) // Recover fs6
flw f23, 54*4(sp) // Recover fs7
flw f24, 55*4(sp) // Recover fs8
flw f25, 56*4(sp) // Recover fs9
flw f26, 57*4(sp) // Recover fs10
flw f27, 58*4(sp) // Recover fs11
flw f28, 59*4(sp) // Recover ft8
flw f29, 60*4(sp) // Recover ft9
flw f30, 61*4(sp) // Recover ft10
flw f31, 62*4(sp) // Recover ft11
lw t0, 63*4(sp) // Recover fcsr
csrw fcsr, t0 // Restore fcsr
#elif defined(__riscv_float_abi_double)
fld f0, 31*4(sp) // Recover ft0
fld f1, 32*4(sp) // Recover ft1
fld f2, 33*4(sp) // Recover ft2
fld f3, 34*4(sp) // Recover ft3
fld f4, 35*4(sp) // Recover ft4
fld f5, 36*4(sp) // Recover ft5
fld f6, 37*4(sp) // Recover ft6
fld f7, 38*4(sp) // Recover ft7
fld f8, 39*4(sp) // Recover fs0
fld f9, 40*4(sp) // Recover fs1
fld f10, 41*4(sp) // Recover fa0
fld f11, 42*4(sp) // Recover fa1
fld f12, 43*4(sp) // Recover fa2
fld f13, 44*4(sp) // Recover fa3
fld f14, 45*4(sp) // Recover fa4
fld f15, 46*4(sp) // Recover fa5
fld f16, 47*4(sp) // Recover fa6
fld f17, 48*4(sp) // Recover fa7
fld f18, 49*4(sp) // Recover fs2
fld f19, 50*4(sp) // Recover fs3
fld f20, 51*4(sp) // Recover fs4
fld f21, 52*4(sp) // Recover fs5
fld f22, 53*4(sp) // Recover fs6
fld f23, 54*4(sp) // Recover fs7
fld f24, 55*4(sp) // Recover fs8
fld f25, 56*4(sp) // Recover fs9
fld f26, 57*4(sp) // Recover fs10
fld f27, 58*4(sp) // Recover fs11
fld f28, 59*4(sp) // Recover ft8
fld f29, 60*4(sp) // Recover ft9
fld f30, 61*4(sp) // Recover ft10
fld f31, 62*4(sp) // Recover ft11
lw t0, 63*4(sp) // Recover fcsr
csrw fcsr, t0 // Restore fcsr
#endif
/* Recover standard registers. */
lw t0, 30*4(sp) // Recover mepc
csrw mepc, t0 // Setup mepc
li t0, 0x1880 // Prepare mstatus: MPP=Machine(0x1800) | MPIE(0x80)
#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
li t1, 0x2000 // Set FS bits for FP state
or t0, t0, t1
#endif
csrw mstatus, t0 // Set mstatus
lw ra, 28*4(sp) // Recover return address
lw t0, 19*4(sp) // Recover t0
lw t1, 18*4(sp) // Recover t1
lw t2, 17*4(sp) // Recover t2
lw s0, 12*4(sp) // Recover s0
lw x9, 11*4(sp) // Recover s1
lw a0, 27*4(sp) // Recover a0
lw a1, 26*4(sp) // Recover a1
lw a2, 25*4(sp) // Recover a2
lw a3, 24*4(sp) // Recover a3
lw a4, 23*4(sp) // Recover a4
lw a5, 22*4(sp) // Recover a5
lw a6, 21*4(sp) // Recover a6
lw a7, 20*4(sp) // Recover a7
lw t3, 16*4(sp) // Recover t3
lw t4, 15*4(sp) // Recover t4
lw t5, 14*4(sp) // Recover t5
lw t6, 13*4(sp) // Recover t6
lw x18, 10*4(sp) // Recover s2
lw x19, 9*4(sp) // Recover s3
lw x20, 8*4(sp) // Recover s4
lw x21, 7*4(sp) // Recover s5
lw x22, 6*4(sp) // Recover s6
lw x23, 5*4(sp) // Recover s7
lw x24, 4*4(sp) // Recover s8
lw x25, 3*4(sp) // Recover s9
lw x26, 2*4(sp) // Recover s10
lw x27, 1*4(sp) // Recover s11
#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
addi sp, sp, 65*4 // Recover stack frame - with floating point enabled
#else
addi sp, sp, 32*4 // Recover stack frame - without floating point enabled
#endif
mret // Return to thread
_tx_thread_solicited_return:
/* Recover floating point registers. */
#if defined(__riscv_float_abi_single)
flw f8, 15*4(sp) // Recover fs0
flw f9, 16*4(sp) // Recover fs1
flw f18, 17*4(sp) // Recover fs2
flw f19, 18*4(sp) // Recover fs3
flw f20, 19*4(sp) // Recover fs4
flw f21, 20*4(sp) // Recover fs5
flw f22, 21*4(sp) // Recover fs6
flw f23, 22*4(sp) // Recover fs7
flw f24, 23*4(sp) // Recover fs8
flw f25, 24*4(sp) // Recover fs9
flw f26, 25*4(sp) // Recover fs10
flw f27, 26*4(sp) // Recover fs11
lw t0, 27*4(sp) // Recover fcsr
csrw fcsr, t0 // Restore fcsr
#elif defined(__riscv_float_abi_double)
fld f8, 15*4(sp) // Recover fs0
fld f9, 16*4(sp) // Recover fs1
fld f18, 17*4(sp) // Recover fs2
fld f19, 18*4(sp) // Recover fs3
fld f20, 19*4(sp) // Recover fs4
fld f21, 20*4(sp) // Recover fs5
fld f22, 21*4(sp) // Recover fs6
fld f23, 22*4(sp) // Recover fs7
fld f24, 23*4(sp) // Recover fs8
fld f25, 24*4(sp) // Recover fs9
fld f26, 25*4(sp) // Recover fs10
fld f27, 26*4(sp) // Recover fs11
lw t0, 27*4(sp) // Recover fcsr
csrw fcsr, t0 // Restore fcsr
#endif
/* Recover standard registers. */
lw t0, 14*4(sp) // Recover mstatus
csrw mstatus, t0 // Restore mstatus
lw ra, 13*4(sp) // Recover return address
lw s0, 12*4(sp) // Recover s0
lw s1, 11*4(sp) // Recover s1
lw x18, 10*4(sp) // Recover s2
lw x19, 9*4(sp) // Recover s3
lw x20, 8*4(sp) // Recover s4
lw x21, 7*4(sp) // Recover s5
lw x22, 6*4(sp) // Recover s6
lw x23, 5*4(sp) // Recover s7
lw x24, 4*4(sp) // Recover s8
lw x25, 3*4(sp) // Recover s9
lw x26, 2*4(sp) // Recover s10
lw x27, 1*4(sp) // Recover s11
#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
addi sp, sp, 29*4 // Recover stack frame - with floating point enabled
#else
addi sp, sp, 16*4 // Recover stack frame - without floating point enabled
#endif
ret // Return to thread
/* } */

View File

@@ -0,0 +1,227 @@
/***************************************************************************
* Copyright (c) 2026 Quintauris
*
* 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
**************************************************************************/
/**************************************************************************/
/**************************************************************************/
/** */
/** ThreadX Component */
/** */
/** Thread */
/** */
/**************************************************************************/
/**************************************************************************/
.section .text
/**************************************************************************/
/* */
/* FUNCTION RELEASE */
/* */
/* _tx_thread_stack_build RISC-V32/GNU */
/* 6.4.x */
/* AUTHOR */
/* */
/* Francisco Merino, Quintauris */
/* */
/* DESCRIPTION */
/* */
/* This function builds a stack frame on the supplied thread's stack. */
/* The stack frame results in a fake interrupt return to the supplied */
/* function pointer. */
/* */
/* INPUT */
/* */
/* thread_ptr Pointer to thread control blk */
/* function_ptr Pointer to return function */
/* */
/* OUTPUT */
/* */
/* None */
/* */
/* CALLS */
/* */
/* None */
/* */
/* CALLED BY */
/* */
/* _tx_thread_create Create thread service */
/* */
/* RELEASE HISTORY */
/* */
/* DATE NAME DESCRIPTION */
/* */
/* 26-02-2026 Francisco Merino Initial Version 6.4.x */
/* */
/**************************************************************************/
/* VOID _tx_thread_stack_build(TX_THREAD *thread_ptr, VOID (*function_ptr)(VOID))
{ */
.global _tx_thread_stack_build
_tx_thread_stack_build:
/* Build a fake interrupt frame. The form of the fake interrupt stack
on the RISC-V should look like the following after it is built:
Reg Index
Stack Top: 1 0 Interrupt stack frame type
x27 1 Initial s11
x26 2 Initial s10
x25 3 Initial s9
x24 4 Initial s8
x23 5 Initial s7
x22 6 Initial s6
x21 7 Initial s5
x20 8 Initial s4
x19 9 Initial s3
x18 10 Initial s2
x9 11 Initial s1
x8 12 Initial s0
x31 13 Initial t6
x30 14 Initial t5
x29 15 Initial t4
x28 16 Initial t3
x7 17 Initial t2
x6 18 Initial t1
x5 19 Initial t0
x17 20 Initial a7
x16 21 Initial a6
x15 22 Initial a5
x14 23 Initial a4
x13 24 Initial a3
x12 25 Initial a2
x11 26 Initial a1
x10 27 Initial a0
x1 28 Initial ra
-- 29 reserved
mepc 30 Initial mepc
If floating point support:
f0 31 Initial ft0
f1 32 Initial ft1
f2 33 Initial ft2
f3 34 Initial ft3
f4 35 Initial ft4
f5 36 Initial ft5
f6 37 Initial ft6
f7 38 Initial ft7
f8 39 Initial fs0
f9 40 Initial fs1
f10 41 Initial fa0
f11 42 Initial fa1
f12 43 Initial fa2
f13 44 Initial fa3
f14 45 Initial fa4
f15 46 Initial fa5
f16 47 Initial fa6
f17 48 Initial fa7
f18 49 Initial fs2
f19 50 Initial fs3
f20 51 Initial fs4
f21 52 Initial fs5
f22 53 Initial fs6
f23 54 Initial fs7
f24 55 Initial fs8
f25 56 Initial fs9
f26 57 Initial fs10
f27 58 Initial fs11
f28 59 Initial ft8
f29 60 Initial ft9
f30 61 Initial ft10
f31 62 Initial ft11
fscr 63 Initial fscr
Stack Bottom: (higher memory address) */
lw t0, 16(a0) // Pickup end of stack area
li t1, ~15 // Build 16-byte alignment mask
and t0, t0, t1 // Make sure 16-byte alignment
/* Actually build the stack frame. */
#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
addi t0, t0, -65*4
#else
addi t0, t0, -32*4 // Allocate space for the stack frame
#endif
li t1, 1 // Build stack type
sw t1, 0*4(t0) // Place stack type on the top
sw zero, 1*4(t0) // Initial s11
sw zero, 2*4(t0) // Initial s10
sw zero, 3*4(t0) // Initial s9
sw zero, 4*4(t0) // Initial s8
sw zero, 5*4(t0) // Initial s7
sw zero, 6*4(t0) // Initial s6
sw zero, 7*4(t0) // Initial s5
sw zero, 8*4(t0) // Initial s4
sw zero, 9*4(t0) // Initial s3
sw zero, 10*4(t0) // Initial s2
sw zero, 11*4(t0) // Initial s1
sw zero, 12*4(t0) // Initial s0
sw zero, 13*4(t0) // Initial t6
sw zero, 14*4(t0) // Initial t5
sw zero, 15*4(t0) // Initial t4
sw zero, 16*4(t0) // Initial t3
sw zero, 17*4(t0) // Initial t2
sw zero, 18*4(t0) // Initial t1
sw zero, 19*4(t0) // Initial t0
sw zero, 20*4(t0) // Initial a7
sw zero, 21*4(t0) // Initial a6
sw zero, 22*4(t0) // Initial a5
sw zero, 23*4(t0) // Initial a4
sw zero, 24*4(t0) // Initial a3
sw zero, 25*4(t0) // Initial a2
sw zero, 26*4(t0) // Initial a1
sw zero, 27*4(t0) // Initial a0
sw zero, 28*4(t0) // Initial ra
sw a1, 30*4(t0) // Initial mepc (thread entry point)
#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
sw zero, 31*4(t0) // Initial ft0
sw zero, 32*4(t0) // Initial ft1
sw zero, 33*4(t0) // Initial ft2
sw zero, 34*4(t0) // Initial ft3
sw zero, 35*4(t0) // Initial ft4
sw zero, 36*4(t0) // Initial ft5
sw zero, 37*4(t0) // Initial ft6
sw zero, 38*4(t0) // Initial ft7
sw zero, 39*4(t0) // Initial fs0
sw zero, 40*4(t0) // Initial fs1
sw zero, 41*4(t0) // Initial fa0
sw zero, 42*4(t0) // Initial fa1
sw zero, 43*4(t0) // Initial fa2
sw zero, 44*4(t0) // Initial fa3
sw zero, 45*4(t0) // Initial fa4
sw zero, 46*4(t0) // Initial fa5
sw zero, 47*4(t0) // Initial fa6
sw zero, 48*4(t0) // Initial fa7
sw zero, 49*4(t0) // Initial fs2
sw zero, 50*4(t0) // Initial fs3
sw zero, 51*4(t0) // Initial fs4
sw zero, 52*4(t0) // Initial fs5
sw zero, 53*4(t0) // Initial fs6
sw zero, 54*4(t0) // Initial fs7
sw zero, 55*4(t0) // Initial fs8
sw zero, 56*4(t0) // Initial fs9
sw zero, 57*4(t0) // Initial fs10
sw zero, 58*4(t0) // Initial fs11
sw zero, 59*4(t0) // Initial ft8
sw zero, 60*4(t0) // Initial ft9
sw zero, 61*4(t0) // Initial ft10
sw zero, 62*4(t0) // Initial ft11
csrr a1, fcsr // Read fcsr for initial value
sw a1, 63*4(t0) // Initial fcsr
sw zero, 64*4(t0) // Reserved word (0)
#else
sw zero, 31*4(t0) // Reserved word (0)
#endif
/* Setup stack pointer. */
/* thread_ptr -> tx_thread_stack_ptr = t0; */
sw t0, 8(a0) // Save stack pointer in thread's
ret // control block and return
/* } */

View File

@@ -0,0 +1,174 @@
/***************************************************************************
* Copyright (c) 2026 Quintauris
*
* 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
**************************************************************************/
/**************************************************************************/
/**************************************************************************/
/** */
/** ThreadX Component */
/** */
/** Thread */
/** */
/**************************************************************************/
/**************************************************************************/
.section .text
/**************************************************************************/
/* */
/* FUNCTION RELEASE */
/* */
/* _tx_thread_system_return RISC-V32/GNU */
/* 6.4.x */
/* AUTHOR */
/* */
/* Francisco Merino, Quintauris */
/* */
/* DESCRIPTION */
/* */
/* This function is target processor specific. It is used to transfer */
/* control from a thread back to the system. Only a minimal context */
/* is saved since the compiler assumes temp registers are going to get */
/* slicked by a function call anyway. */
/* */
/* INPUT */
/* */
/* None */
/* */
/* OUTPUT */
/* */
/* None */
/* */
/* CALLS */
/* */
/* _tx_thread_schedule Thread scheduling loop */
/* */
/* CALLED BY */
/* */
/* ThreadX components */
/* */
/* RELEASE HISTORY */
/* */
/* DATE NAME DESCRIPTION */
/* */
/* 26-02-2026 Francisco Merino Initial Version 6.4.x */
/* */
/**************************************************************************/
/* VOID _tx_thread_system_return(VOID)
{ */
.global _tx_thread_system_return
_tx_thread_system_return:
/* Save minimal context on the stack. */
/* sp -= sizeof(stack_frame); */
#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
addi sp, sp, -29*4 // Allocate space on the stack - with floating point enabled
#else
addi sp, sp, -16*4 // Allocate space on the stack - without floating point enabled
#endif
/* Store floating point preserved registers. */
#if defined(__riscv_float_abi_single)
fsw f8, 15*4(sp) // Store fs0
fsw f9, 16*4(sp) // Store fs1
fsw f18, 17*4(sp) // Store fs2
fsw f19, 18*4(sp) // Store fs3
fsw f20, 19*4(sp) // Store fs4
fsw f21, 20*4(sp) // Store fs5
fsw f22, 21*4(sp) // Store fs6
fsw f23, 22*4(sp) // Store fs7
fsw f24, 23*4(sp) // Store fs8
fsw f25, 24*4(sp) // Store fs9
fsw f26, 25*4(sp) // Store fs10
fsw f27, 26*4(sp) // Store fs11
csrr t0, fcsr
sw t0, 27*4(sp) // Store fcsr
#elif defined(__riscv_float_abi_double)
fsd f8, 15*4(sp) // Store fs0
fsd f9, 16*4(sp) // Store fs1
fsd f18, 17*4(sp) // Store fs2
fsd f19, 18*4(sp) // Store fs3
fsd f20, 19*4(sp) // Store fs4
fsd f21, 20*4(sp) // Store fs5
fsd f22, 21*4(sp) // Store fs6
fsd f23, 22*4(sp) // Store fs7
fsd f24, 23*4(sp) // Store fs8
fsd f25, 24*4(sp) // Store fs9
fsd f26, 25*4(sp) // Store fs10
fsd f27, 26*4(sp) // Store fs11
csrr t0, fcsr
sw t0, 27*4(sp) // Store fcsr
#endif
sw zero, 0(sp) // Solicited stack type
sw ra, 13*4(sp) // Save return address
sw s0, 12*4(sp) // Save s0
sw s1, 11*4(sp) // Save s1
sw s2, 10*4(sp) // Save s2
sw s3, 9*4(sp) // Save s3
sw s4, 8*4(sp) // Save s4
sw s5, 7*4(sp) // Save s5
sw s6, 6*4(sp) // Save s6
sw s7, 5*4(sp) // Save s7
sw s8, 4*4(sp) // Save s8
sw s9, 3*4(sp) // Save s9
sw s10, 2*4(sp) // Save s10
sw s11, 1*4(sp) // Save s11
csrr t0, mstatus // Pickup mstatus
sw t0, 14*4(sp) // Save mstatus
/* Lockout interrupts. will be enabled in _tx_thread_schedule */
csrci mstatus, 0x08 // Disable interrupts (MIE bit 3)
#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY
call _tx_execution_thread_exit // Call the thread execution exit function
#endif
la t0, _tx_thread_current_ptr // Pickup address of pointer
lw t1, 0(t0) // Pickup current thread pointer
la t2, _tx_thread_system_stack_ptr // Pickup stack pointer address
/* Save current stack and switch to system stack. */
/* _tx_thread_current_ptr -> tx_thread_stack_ptr = SP;
SP = _tx_thread_system_stack_ptr; */
sw sp, 8(t1) // Save stack pointer
lw sp, 0(t2) // Switch to system stack
/* Determine if the time-slice is active. */
/* if (_tx_timer_time_slice)
{ */
la t4, _tx_timer_time_slice // Pickup time slice variable addr
lw t3, 0(t4) // Pickup time slice value
la t2, _tx_thread_schedule // Pickup address of scheduling loop
beqz t3, _tx_thread_dont_save_ts // If no time-slice, don't save it
/* Save time-slice for the thread and clear the current time-slice. */
/* _tx_thread_current_ptr -> tx_thread_time_slice = _tx_timer_time_slice;
_tx_timer_time_slice = 0; */
sw t3, 24(t1) // Save current time-slice for thread
sw zero, 0(t4) // Clear time-slice variable
/* } */
_tx_thread_dont_save_ts:
/* Clear the current thread pointer. */
/* _tx_thread_current_ptr = TX_NULL; */
sw x0, 0(t0) // Clear current thread pointer
jr t2 // Return to thread scheduler
/* } */

View File

@@ -0,0 +1,210 @@
/***************************************************************************
* Copyright (c) 2026 Quintauris
*
* 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
**************************************************************************/
/**************************************************************************/
/**************************************************************************/
/** */
/** ThreadX Component */
/** */
/** Timer */
/** */
/**************************************************************************/
/**************************************************************************/
.section .text
.align 4
/**************************************************************************/
/* */
/* FUNCTION RELEASE */
/* */
/* _tx_timer_interrupt RISC-V32/GNU */
/* 6.2.1 */
/* AUTHOR */
/* */
/* Francisco Merino, Quintauris */
/* */
/* DESCRIPTION */
/* */
/* This function processes the hardware timer interrupt. This */
/* processing includes incrementing the system clock and checking for */
/* time slice and/or timer expiration. If either is found, the */
/* interrupt context save/restore functions are called along with the */
/* expiration functions. */
/* */
/* INPUT */
/* */
/* None */
/* */
/* OUTPUT */
/* */
/* None */
/* */
/* CALLS */
/* */
/* _tx_timer_expiration_process Timer expiration processing */
/* _tx_thread_time_slice Time slice interrupted thread */
/* */
/* CALLED BY */
/* */
/* interrupt vector */
/* */
/* RELEASE HISTORY */
/* */
/* DATE NAME DESCRIPTION */
/* */
/* 01-20-2023 Akif Ejaz Initial Version 6.2.1 */
/* */
/**************************************************************************/
/* VOID _tx_timer_interrupt(VOID)
{ */
.global _tx_timer_interrupt
_tx_timer_interrupt:
/* Increment the system clock. */
/* _tx_timer_system_clock++; */
la t0, _tx_timer_system_clock // Pickup address of system clock
lw t1, 0(t0) // Pickup system clock
la t2, _tx_timer_time_slice // Pickup address of time slice
lw t3, 0(t2) // Pickup time slice
addi t1, t1, 1 // Increment system clock
sw t1, 0(t0) // Store new system clock
li t6, 0 // Clear local expired flag
/* Test for time-slice expiration. */
/* if (_tx_timer_time_slice)
{ */
beqz t3, _tx_timer_no_time_slice // If 0, skip time slice processing
addi t3, t3, -1 // Decrement the time slice
/* Decrement the time_slice. */
/* _tx_timer_time_slice--; */
sw t3, 0(t2) // Store new time slice
/* Check for expiration. */
/* if (_tx_timer_time_slice == 0) */
bgtz t3, _tx_timer_no_time_slice // If not 0, has not expired yet
li t1, 1 // Build expired flag
/* Set the time-slice expired flag. */
/* _tx_timer_expired_time_slice = TX_TRUE; */
la t4, _tx_timer_expired_time_slice // Get address of expired flag
sw t1, 0(t4) // Set expired flag (UINT)
ori t6, t6, 1 // Set local expired flag
/* } */
_tx_timer_no_time_slice:
/* Test for timer expiration. */
/* if (*_tx_timer_current_ptr)
{ */
la t0, _tx_timer_current_ptr // Pickup address of current ptr
lw t1, 0(t0) // Pickup current pointer (word)
lw t3, 0(t1) // Pickup the current timer entry (word)
la t2, _tx_timer_expired // Pickup address of timer expired flag
li t4, 1 // Build TX_TRUE flag
beqz t3, _tx_timer_no_timer // If NULL, no timer has expired
/* Set expiration flag. */
/* _tx_timer_expired = TX_TRUE; */
ori t6, t6, 2 // Set local expired flag
sw t4, 0(t2) // Set expired flag in memory (UINT)
j _tx_timer_done // Finished timer processing
/* }
else
{ */
_tx_timer_no_timer:
/* No timer expired, increment the timer pointer. */
/* _tx_timer_current_ptr++; */
/* Check for wrap-around. */
/* if (_tx_timer_current_ptr == _tx_timer_list_end) */
la t2, _tx_timer_list_end // Pickup address of list end pointer
lw t3, 0(t2) // Pickup actual list end
addi t1, t1, 4 // Point to next timer entry
sw t1, 0(t0) // Store new timer pointer
bne t1, t3, _tx_timer_skip_wrap // If not same, good pointer
/* Wrap to beginning of list. */
/* _tx_timer_current_ptr = _tx_timer_list_start; */
la t2, _tx_timer_list_start // Pickup address of list start pointer
lw t4, 0(t2) // Pickup start of the list
sw t4, 0(t0) // Store new timer pointer
_tx_timer_skip_wrap:
/* } */
_tx_timer_done:
/* See if anything has expired. */
/* if ((_tx_timer_expired_time_slice) || (_tx_timer_expired))
{ */
beqz t6, _tx_timer_nothing_expired // If nothing expired skip the rest
addi sp, sp, -16 // Allocate some storage on the stack
sw t6, 0(sp) // Save local expired flag
sw ra, 4(sp) // Save ra
/* Did a timer expire? */
/* if (_tx_timer_expired)
{ */
andi t2, t6, 2 // Isolate the timer expired bit
beqz t2, _tx_timer_dont_activate // No, timer not expired
/* Call the timer expiration processing. */
/* _tx_timer_expiration_process(void); */
call _tx_timer_expiration_process // Call _tx_timer_expiration_process
lw t6, 0(sp) // Recover local expired flag
/* } */
_tx_timer_dont_activate:
/* Did time slice expire? */
/* if (_tx_timer_expired_time_slice)
{ */
andi t2, t6, 1 // Is the timer expired bit set?
beqz t2, _tx_timer_not_ts_expiration // If not, skip time slice processing
/* Time slice interrupted thread. */
/* _tx_thread_time_slice(); */
call _tx_thread_time_slice // Call time slice
/* } */
_tx_timer_not_ts_expiration:
lw ra, 4(sp) // Recover ra
addi sp, sp, 16 // Recover stack space
/* } */
_tx_timer_nothing_expired:
ret
/* } */