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
+29
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
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)
+19
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
)
+58
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.
@@ -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;
}
@@ -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
@@ -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
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -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
@@ -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;
}
@@ -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
@@ -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 = .);
}
@@ -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;
}
@@ -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
@@ -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) ;
}
}
@@ -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
@@ -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;
}
@@ -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
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -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. */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -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
/* } */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -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
/* } */
File diff suppressed because it is too large Load Diff