From 6ffd3772db9127e4e7d81b7e381d0d7daab87275 Mon Sep 17 00:00:00 2001 From: Akif Ejaz Date: Fri, 15 May 2026 15:59:10 +0500 Subject: [PATCH] Add BananaPi BPI-F3 BSP support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add RISC-V supervisor support to the rv64/gnu port and provide a complete board support package for the BananaPi BPI-F3 (SpacemiT K1 SoC, X60 cores). Port changes (risc-v64/gnu): - Guard all CSR accesses with TX_RISCV_SMODE to select S-mode registers (sstatus/sepc/sie/sret) vs M-mode (mstatus/mepc/mie/mret) in context_save, context_restore, schedule, system_return, interrupt_control, and stack_build. - Add S-mode TX_INT_ENABLE/TX_DISABLE and inline TX_RESTORE macros to tx_port.h. - Add TX_RISCV_SMODE CMake option to CMakeLists.txt. BananaPi BPI-F3 BSP (example_build/bananapi-f3): - Boot flow: FSBL → OpenSBI (M-mode) → U-Boot (S-mode) → ThreadX - S-mode trap handler with context save/restore integration - SBI legacy ecall timer at 10 Hz (24 MHz timebase) - PLIC driver with S-mode context, stale-IRQ drain, and callbacks - PXA-compatible UART0 console (115200 8N1) - Linker script at 0x200000 load address Tested on risc-v board, BananaPi BPI-F3 hardware. Signed-off-by: Akif Ejaz --- CMakeLists.txt | 8 + .../gnu/example_build/bananapi-f3/.gitignore | 4 + .../gnu/example_build/bananapi-f3/board.c | 45 +++ .../bananapi-f3/build_libthreadx.sh | 59 +++ .../gnu/example_build/bananapi-f3/csr.h | 98 +++++ .../example_build/bananapi-f3/demo_threadx.c | 371 ++++++++++++++++++ .../gnu/example_build/bananapi-f3/entry.S | 73 ++++ .../gnu/example_build/bananapi-f3/hwtimer.c | 56 +++ .../gnu/example_build/bananapi-f3/hwtimer.h | 43 ++ .../gnu/example_build/bananapi-f3/link.lds | 87 ++++ .../gnu/example_build/bananapi-f3/plic.c | 130 ++++++ .../gnu/example_build/bananapi-f3/plic.h | 108 +++++ .../gnu/example_build/bananapi-f3/trap.c | 65 +++ .../bananapi-f3/tx_initialize_low_level.S | 139 +++++++ .../gnu/example_build/bananapi-f3/uart.c | 127 ++++++ .../gnu/example_build/bananapi-f3/uart.h | 72 ++++ ports/risc-v64/gnu/inc/tx_port.h | 23 +- .../gnu/src/tx_thread_context_restore.S | 53 ++- .../risc-v64/gnu/src/tx_thread_context_save.S | 8 + .../gnu/src/tx_thread_interrupt_control.S | 19 +- ports/risc-v64/gnu/src/tx_thread_schedule.S | 47 ++- .../risc-v64/gnu/src/tx_thread_stack_build.S | 2 +- .../gnu/src/tx_thread_system_return.S | 10 + 23 files changed, 1631 insertions(+), 16 deletions(-) create mode 100644 ports/risc-v64/gnu/example_build/bananapi-f3/.gitignore create mode 100644 ports/risc-v64/gnu/example_build/bananapi-f3/board.c create mode 100755 ports/risc-v64/gnu/example_build/bananapi-f3/build_libthreadx.sh create mode 100644 ports/risc-v64/gnu/example_build/bananapi-f3/csr.h create mode 100644 ports/risc-v64/gnu/example_build/bananapi-f3/demo_threadx.c create mode 100644 ports/risc-v64/gnu/example_build/bananapi-f3/entry.S create mode 100644 ports/risc-v64/gnu/example_build/bananapi-f3/hwtimer.c create mode 100644 ports/risc-v64/gnu/example_build/bananapi-f3/hwtimer.h create mode 100644 ports/risc-v64/gnu/example_build/bananapi-f3/link.lds create mode 100644 ports/risc-v64/gnu/example_build/bananapi-f3/plic.c create mode 100644 ports/risc-v64/gnu/example_build/bananapi-f3/plic.h create mode 100644 ports/risc-v64/gnu/example_build/bananapi-f3/trap.c create mode 100644 ports/risc-v64/gnu/example_build/bananapi-f3/tx_initialize_low_level.S create mode 100644 ports/risc-v64/gnu/example_build/bananapi-f3/uart.c create mode 100644 ports/risc-v64/gnu/example_build/bananapi-f3/uart.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e988d3a60..5faef86e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,14 @@ if(THREADX_SMP) target_compile_definitions(${PROJECT_NAME} PUBLIC "TX_MPCORE" ) endif() +# Optional: build for S-mode (Supervisor mode) instead of M-mode (Machine mode). +# Required when running after OpenSBI, e.g. booted from U-Boot. +option(TX_RISCV_SMODE "Use S-mode CSRs instead of M-mode for RISC-V targets" OFF) +if(TX_RISCV_SMODE) + message(STATUS "RISC-V S-mode enabled (TX_RISCV_SMODE)") + target_compile_definitions(${PROJECT_NAME} PUBLIC "TX_RISCV_SMODE") +endif() + # Enable a build target that produces a ZIP file of all sources set(CPACK_SOURCE_GENERATOR "ZIP") set(CPACK_SOURCE_IGNORE_FILES diff --git a/ports/risc-v64/gnu/example_build/bananapi-f3/.gitignore b/ports/risc-v64/gnu/example_build/bananapi-f3/.gitignore new file mode 100644 index 000000000..9cae8f843 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/bananapi-f3/.gitignore @@ -0,0 +1,4 @@ +kernel.bin +kernel.elf +kernel.uImage + diff --git a/ports/risc-v64/gnu/example_build/bananapi-f3/board.c b/ports/risc-v64/gnu/example_build/bananapi-f3/board.c new file mode 100644 index 000000000..efdbfe6dd --- /dev/null +++ b/ports/risc-v64/gnu/example_build/bananapi-f3/board.c @@ -0,0 +1,45 @@ +/*************************************************************************** + * Copyright (c) 2026 10xEngineers + * + * 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 +#include + +void *memset(void *des, int c, size_t n) +{ + if ((des == NULL) || n == 0) + return des; + + char *t = (char *)des; + for (size_t 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; +} diff --git a/ports/risc-v64/gnu/example_build/bananapi-f3/build_libthreadx.sh b/ports/risc-v64/gnu/example_build/bananapi-f3/build_libthreadx.sh new file mode 100755 index 000000000..249ce68ce --- /dev/null +++ b/ports/risc-v64/gnu/example_build/bananapi-f3/build_libthreadx.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# Build the bananapi-f3 (SpacemiT K1) ThreadX kernel. +# +# Boot flow: FSBL -> OpenSBI (M-mode) -> U-Boot (S-mode) -> ThreadX (S-mode). +# +# OpenSBI runs in M-mode and delegates S-mode to the next stage. +# U-Boot runs in S-mode; any code launched from U-Boot also runs in S-mode. +# +# Use the TX_RISCV_SMODE CMake option to build libthreadx.a for S-mode. +# +# This libthreadx.a (S-mode), then links the BSP objects +# to produce kernel.elf / kernel.bin. + +set -e + +# Where ThreadX is loaded by U-Boot (${loadaddr}). Must match the link.lds origin. +LOAD_ADDR=0x00200000 + +# printf "y\n" | rm -rf ../../../../../build/ +rm -f kernel.elf kernel.bin kernel.uImage + +pushd ../../../../../ +cmake -Bbuild -GNinja \ + -DCMAKE_TOOLCHAIN_FILE=cmake/riscv64_gnu.cmake \ + -DTX_USER_FILE="" \ + -DTX_RISCV_SMODE=ON \ + . +cmake --build ./build/ +popd + +riscv64-unknown-elf-gcc \ + -march=rv64gc -mabi=lp64d \ + -mcmodel=medany -O0 -g3 -Wall \ + -DTX_RISCV_SMODE \ + -ffunction-sections -fdata-sections \ + -I../../../../../common/inc \ + -I../../inc \ + entry.S \ + tx_initialize_low_level.S \ + board.c uart.c hwtimer.c plic.c trap.c demo_threadx.c \ + -L../../../../../build -lthreadx \ + -T link.lds -nostartfiles \ + -o kernel.elf + +# Strip ELF metadata down to the loadable bytes. +riscv64-unknown-elf-objcopy -O binary kernel.elf kernel.bin + +echo "== Build artifacts ==" +ls -la kernel.elf kernel.bin 2>/dev/null || true +echo +riscv64-unknown-elf-size kernel.elf || true +echo + +# Run on Bananapi BPI-F3 +# Stop the boot at U-Boot (press reset button and press "s" key continuously to stop autoboot). +# at "=>" prompt, load the kernel using the following commands: +# +# " ELF: tftpboot ${loadaddr} kernel.elf && bootelf ${loadaddr}" +# " BIN: tftpboot 0x200000 kernel.bin && go 0x200000" diff --git a/ports/risc-v64/gnu/example_build/bananapi-f3/csr.h b/ports/risc-v64/gnu/example_build/bananapi-f3/csr.h new file mode 100644 index 000000000..c5dfc6308 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/bananapi-f3/csr.h @@ -0,0 +1,98 @@ +/*************************************************************************** + * Copyright (c) 2026 10xEngineers + * + * 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 + **************************************************************************/ + +/* RISC-V S-mode CSR helpers + * + * Privilege level: Supervisor (S-mode). + * Reference: RISC-V Privileged Specification + * + * Bit positions per the RISC-V Privileged Specification: + * + * sstatus: SIE(1) SPIE(5) SPP(8) FS(13:14) SUM(18) MXR(19) + * sie: SSIE(1) STIE(5) SEIE(9) + * scause: Interrupt bit = 63; Code 1=SSI, 5=STI, 9=SEI + */ + +#ifndef RISCV_CSR_H +#define RISCV_CSR_H + +#define SSTATUS_SIE (1L << 1) /* Supervisor Interrupt Enable */ +#define SSTATUS_SPIE (1L << 5) /* Previous SIE (saved on trap) */ +#define SSTATUS_SPP_MASK (1L << 8) +#define SSTATUS_SPP_S (1L << 8) /* SPP = Supervisor */ +#define SSTATUS_SPP_U (0L << 8) /* SPP = User */ +#define SSTATUS_FS (3L << 13) /* FP unit state (Off/Init/Clean/Dirty) */ + +#define SIE_SSIE (1L << 1) /* S-mode software interrupt */ +#define SIE_STIE (1L << 5) /* S-mode timer interrupt */ +#define SIE_SEIE (1L << 9) /* S-mode external interrupt */ + +#ifndef __ASSEMBLER__ + +#include + +/* + * Return the hart ID of the running core. + * + * mhartid is an M-mode CSR and cannot be read from S-mode. When booted + * from U-Boot only hart 0 is active (secondary harts remain parked in + * OpenSBI HSM), so we return 0. An SMP extension would need to pass + * the hart ID through a0 or shared memory at boot. + */ +static inline uint64_t riscv_get_core(void) +{ + return 0; +} + + +static inline uint64_t riscv_read_sstatus(void) +{ + uint64_t x; + asm volatile("csrr %0, sstatus" : "=r" (x)); + return x; +} + +static inline void riscv_write_sstatus(uint64_t x) +{ + asm volatile("csrw sstatus, %0" : : "r" (x)); +} + + +static inline void riscv_sintr_on(void) +{ + riscv_write_sstatus(riscv_read_sstatus() | SSTATUS_SIE); +} + +static inline void riscv_sintr_off(void) +{ + riscv_write_sstatus(riscv_read_sstatus() & ~SSTATUS_SIE); +} + +static inline int riscv_sintr_get(void) +{ + return (riscv_read_sstatus() & SSTATUS_SIE) != 0; +} + +static inline void riscv_sintr_restore(int enabled) +{ + if (enabled) + riscv_sintr_on(); + else + riscv_sintr_off(); +} + +/* Unified names used by BSP drivers (uart.c, etc.). */ +#define riscv_intr_on riscv_sintr_on +#define riscv_intr_off riscv_sintr_off +#define riscv_intr_get riscv_sintr_get +#define riscv_intr_restore riscv_sintr_restore + +#endif /* __ASSEMBLER__ */ +#endif /* RISCV_CSR_H */ diff --git a/ports/risc-v64/gnu/example_build/bananapi-f3/demo_threadx.c b/ports/risc-v64/gnu/example_build/bananapi-f3/demo_threadx.c new file mode 100644 index 000000000..93213c53a --- /dev/null +++ b/ports/risc-v64/gnu/example_build/bananapi-f3/demo_threadx.c @@ -0,0 +1,371 @@ +/* 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 9180 +#define DEMO_BLOCK_POOL_SIZE 100 +#define DEMO_QUEUE_SIZE 100 + + +/* 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; +UCHAR memory_area[DEMO_BYTE_POOL_SIZE]; + + +/* 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", memory_area, 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) + 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)) + 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; + } +} diff --git a/ports/risc-v64/gnu/example_build/bananapi-f3/entry.S b/ports/risc-v64/gnu/example_build/bananapi-f3/entry.S new file mode 100644 index 000000000..f1d0f03bf --- /dev/null +++ b/ports/risc-v64/gnu/example_build/bananapi-f3/entry.S @@ -0,0 +1,73 @@ +/*************************************************************************** + * Copyright (c) 2026 10xEngineers + * + * 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 + **************************************************************************/ + + .global _start + .extern main + .extern _sysstack_start + .extern _bss_start + .extern _bss_end + + .section .text.entry + .align 4 +_start: + /* Zero all general-purpose registers (x1–x31). */ + 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 + + /* Set up the initial supervisor stack (16 KiB). */ + la t0, _sysstack_start + li t1, 0x4000 + add sp, t0, t1 + + /* Zero the .bss section. */ + 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 + + /* Halt if main() ever returns. */ +_park: + wfi + j _park diff --git a/ports/risc-v64/gnu/example_build/bananapi-f3/hwtimer.c b/ports/risc-v64/gnu/example_build/bananapi-f3/hwtimer.c new file mode 100644 index 000000000..4881ba3cc --- /dev/null +++ b/ports/risc-v64/gnu/example_build/bananapi-f3/hwtimer.c @@ -0,0 +1,56 @@ +/*************************************************************************** + * Copyright (c) 2026 10xEngineers + * + * 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" + +/* + * SBI legacy set_timer ecall + * + * EID (a7) = 0 (SBI_SET_TIMER) + * a0 = absolute mtime compare value + * + * Programs mtimecmp for the current hart and clears sip.STIP. + */ +static inline void sbi_set_timer(uint64_t stime_value) +{ + register uint64_t a0 asm("a0") = stime_value; + register uint64_t a7 asm("a7") = 0; /* SBI_SET_TIMER */ + asm volatile("ecall" + : "+r"(a0) + : "r"(a7) + : "memory"); +} + +/* + * Read the free-running mtime counter via the rdtime pseudo-instruction. + * Accessible from S-mode per RISC-V Priv Spec §10.1 (Zicntr extension). + */ +static inline uint64_t read_time(void) +{ + uint64_t t; + asm volatile("rdtime %0" : "=r"(t)); + return t; +} + +int hwtimer_init(void) +{ + uint64_t now = read_time(); + sbi_set_timer(now + TICKNUM_PER_TIMER); + return 0; +} + +int hwtimer_handler(void) +{ + uint64_t now = read_time(); + sbi_set_timer(now + TICKNUM_PER_TIMER); + return 0; +} diff --git a/ports/risc-v64/gnu/example_build/bananapi-f3/hwtimer.h b/ports/risc-v64/gnu/example_build/bananapi-f3/hwtimer.h new file mode 100644 index 000000000..9265cee95 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/bananapi-f3/hwtimer.h @@ -0,0 +1,43 @@ +/*************************************************************************** + * Copyright (c) 2026 10xEngineers + * + * 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 + +/* SpacemiT K1 TIMER (S-mode via SBI ecall) + * + * In S-mode the CLINT MMIO registers (mtime / mtimecmp) are protected + * by PMP and inaccessible. Timer operations are performed through the + * SBI legacy interface. + * + * rdtime - pseudo-instruction reading the time CSR (aliased to mtime + * by the implementation; accessible from S-mode per Priv + * Spec). + * + * SBI legacy set_timer (EID = 0, FID = 0) - programs mtimecmp on + * the current hart. Argument a0 = absolute compare value. + * Clears the pending supervisor timer interrupt (sip.STIP) + * as a side effect. + * + * + * Timebase frequency (DTS cpus { timebase-frequency = <0x16e3600>; }): + * 24,000,000 Hz (24 MHz). + * + * ThreadX tick rate: 10 Hz (100 ms period). + */ +#define TICKNUM_PER_SECOND 24000000UL +#define TICKNUM_PER_TIMER (TICKNUM_PER_SECOND / 10) + +int hwtimer_init(void); +int hwtimer_handler(void); + +#endif /* RISCV_HWTIMER_H */ diff --git a/ports/risc-v64/gnu/example_build/bananapi-f3/link.lds b/ports/risc-v64/gnu/example_build/bananapi-f3/link.lds new file mode 100644 index 000000000..99d9ae498 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/bananapi-f3/link.lds @@ -0,0 +1,87 @@ +/*************************************************************************** + * Copyright (c) 2026 10xEngineers + * + * 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 + **************************************************************************/ + +/* Memory Layout of the SpacemiT K1 SoC. + * + * Memory map: + * + * Physical Address Size Usage + * ────────────────── ────────── ────────────────────────────── + * 0x0000_0000 512 KiB Reserved - M-mode / OpenSBI + * 0x0008_0000 ~2 GiB Usable DRAM Bank 0 + * 0x0020_0000 - ← ThreadX kernel load address + * 0x7F00_0000 16 MiB Reserved - framebuffer / runtime + * 0x8000_0000–0xFFFF_FFFF PCI / MMIO hole (not DRAM) + * 0x1_0000_0000 ~14 GiB Usable DRAM Bank 1 + * + * Peripheral MMIO: + * + * 0xD401_7000 256 B UART0 (serial console) + * 0xE000_0000 64 MiB PLIC (interrupt controller) + * 0xE400_0000 64 KiB CLINT (timer / IPI) + * + * see more details on K1 Spec: + * Web : https://www.spacemit.com/community/document/?k1 + * PDF : https://cdn-resource.spacemit.com/file/chip/K1/K1_User_Manual_en.pdf + */ + + +OUTPUT_ARCH( "riscv" ) +ENTRY( _start ) + +PHDRS +{ + text PT_LOAD FLAGS(5); /* PF_R | PF_X */ + data PT_LOAD FLAGS(6); /* PF_R | PF_W */ +} + +SECTIONS +{ + . = 0x00200000; + + .text : { + KEEP(*(.text.entry)) + *(.text .text.*) + . = ALIGN(0x1000); + PROVIDE(etext = .); + } :text + + .rodata : { + . = ALIGN(16); + *(.srodata .srodata.*) + . = ALIGN(16); + *(.rodata .rodata.*) + } :text + + .data : { + . = ALIGN(16); + *(.sdata .sdata.*) + . = ALIGN(16); + *(.data .data.*) + } :data + + .bss : { + . = ALIGN(16); + _bss_start = .; + *(.sbss .sbss.*) + . = ALIGN(16); + *(.bss .bss.*) + _bss_end = .; + } :data + + .stack : { + . = ALIGN(4096); + _sysstack_start = .; + . += 0x4000; + _sysstack_end = .; + } :data + + PROVIDE(_end = .); +} diff --git a/ports/risc-v64/gnu/example_build/bananapi-f3/plic.c b/ports/risc-v64/gnu/example_build/bananapi-f3/plic.c new file mode 100644 index 000000000..e3290579e --- /dev/null +++ b/ports/risc-v64/gnu/example_build/bananapi-f3/plic.c @@ -0,0 +1,130 @@ +/*************************************************************************** + * Copyright (c) 2025 10xEngineers + * + * 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 + +static irq_callback callbacks[MAX_CALLBACK_NUM]; + +#define PLIC_ENABLE(hart) PLIC_SENABLE(hart) +#define PLIC_PRIORITY_REG(hart) PLIC_SPRIORITY(hart) +#define PLIC_CLAIM_REG(hart) PLIC_SCLAIM(hart) +#define PLIC_COMPLETE_REG(hart) PLIC_SCOMPLETE(hart) + +void plic_irq_enable(int irqno) +{ + int hart = (int)riscv_get_core(); + uint32_t word = irqno / 32; + uint32_t bit = irqno % 32; + volatile uint32_t *en = (volatile uint32_t *)(PLIC_ENABLE(hart) + word * 4); + *en |= (1u << bit); +} + +void plic_irq_disable(int irqno) +{ + int hart = (int)riscv_get_core(); + uint32_t word = irqno / 32; + uint32_t bit = irqno % 32; + volatile uint32_t *en = (volatile uint32_t *)(PLIC_ENABLE(hart) + word * 4); + *en &= ~(1u << bit); +} + +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) +{ + int hart = (int)riscv_get_core(); + + for (int i = 0; i < MAX_CALLBACK_NUM; i++) + callbacks[i] = NULL; + + /* Mask everything for this hart. */ + for (int word = 0; word < (MAX_CALLBACK_NUM + 31) / 32; word++) + *(volatile uint32_t *)(PLIC_ENABLE(hart) + word * 4) = 0; + + /* Set hart threshold to 0 so any non-zero priority IRQ can fire. */ + *(volatile uint32_t *)PLIC_PRIORITY_REG(hart) = 0; + + /* + * Drain stale pending interrupts left over from a prior boot stage + * (BootROM / OpenSBI / U-Boot). We temporarily enable every source + * so claim returns the actual highest-priority pending ID, then + * complete whatever was claimed. Loop until claim returns 0 + * (no more pending). This follows the PLIC spec: claim returns 0 + * when nothing is pending for this context. + */ + for (int word = 0; word < (PLIC_NUM_SOURCES + 31) / 32; word++) + *(volatile uint32_t *)(PLIC_ENABLE(hart) + word * 4) = 0xFFFFFFFFu; + + for (;;) { + uint32_t id = *(volatile uint32_t *)PLIC_CLAIM_REG(hart); + if (id == 0) + break; + *(volatile uint32_t *)PLIC_COMPLETE_REG(hart) = id; + } + + /* Re-mask everything; individual drivers will enable their sources. */ + for (int word = 0; word < (PLIC_NUM_SOURCES + 31) / 32; word++) + *(volatile uint32_t *)(PLIC_ENABLE(hart) + word * 4) = 0; + + /* + * Set default priority for every source to PLIC_DEFAULT_PRIORITY (2), + * Priority 0 means "never pending" per the SiFive PLIC spec, so any + * source that should be active must have priority >= 1. Individual + * drivers may override this with plic_prio_set() later. + */ + for (int i = 1; i <= PLIC_NUM_SOURCES; i++) + PLIC_SET_PRIO(i, PLIC_DEFAULT_PRIORITY); + + return 0; +} + +int plic_claim(void) +{ + int hart = (int)riscv_get_core(); + return (int)*(volatile uint32_t *)PLIC_CLAIM_REG(hart); +} + +void plic_complete(int irqno) +{ + int hart = (int)riscv_get_core(); + *(volatile uint32_t *)PLIC_COMPLETE_REG(hart) = (uint32_t)irqno; +} + +int plic_irq_intr(void) +{ + int ret = -1; + int irqno = plic_claim(); + if (irqno > 0 && irqno < MAX_CALLBACK_NUM && callbacks[irqno] != NULL) + ret = (callbacks[irqno])(irqno); + plic_complete(irqno); + return ret; +} diff --git a/ports/risc-v64/gnu/example_build/bananapi-f3/plic.h b/ports/risc-v64/gnu/example_build/bananapi-f3/plic.h new file mode 100644 index 000000000..fa755f896 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/bananapi-f3/plic.h @@ -0,0 +1,108 @@ +/*************************************************************************** + * Copyright (c) 2025 10xEngineers + * + * 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 + +/* + * SpacemiT K1 PLIC - SiFive-compatible Platform-Level Interrupt Controller. + * + * From bananapi-f3.dts (linux main tree): + * interrupt-controller@e0000000 { + * reg-names = "control"; + * interrupts-extended = <0x10 0x0b 0x10 0x09 0x11 0x0b 0x11 0x09 0x12 0x0b 0x12 0x09 0x13 0x0b 0x13 0x09 0x14 0x0b 0x14 0x09 0x15 0x0b 0x15 0x09 0x16 0x0b 0x16 0x09 0x17 0x0b 0x17 0x09>; + * compatible = "riscv,plic0"; + * #interrupt-cells = <0x01>; + * reg = <0x00 0xe0000000 0x00 0x4000000>; + * phandle = <0x1e>; + * riscv,ndev = <0x9f>; // (159 external interrupt sources) + * riscv,max-priority = <0x07>; // (priority levels 1..7; 0 = disabled) + * interrupt-controller; + * }; + + * The RISCV_APB block is at 0xE000_0000 with size 0x1000_0000. + * Within that block the PLIC occupies 0xE000_0000..0xE3FF_FFFF + * (64 MiB) and the CLINT lives at 0xE400_0000. + * + * The K1 main CPU has up to 8 X60 cores (harts 0..7). Each hart + * exposes both an M-mode and S-mode interrupt context to the PLIC. + * + * PLIC register layout: + * + * Offset Size Description + * ────────────── ────── ──────────────────────────────────────────── + * 0x000000 4/src Source priority (src 0 reserved, 1..159) + * 0x001000 20B Pending bits (5 × 32-bit words) + * 0x002000 0x100/h Enable bits per hart (M-mode at ctx 0) + * 0x002080 0x100/h Enable bits per hart (S-mode at ctx 1) + * 0x200000 0x2000/h Threshold + Claim/Complete (M-mode ctx 0) + * 0x201000 0x2000/h Threshold + Claim/Complete (S-mode ctx 1) + * + * Context mapping: + * EN_PER_HART = 0x100 (two contexts × 0x80 each) + * EN_PER_CONTEXT = 0x80 + * THRES_PER_HART = 0x2000 (two contexts × 0x1000 each) + * THRES_PER_CTX = 0x1000 + * THRES_CLAIM_OFF = 0x4 (claim/complete at threshold + 4) + * + */ + +#define PLIC 0xE0000000UL +#define PLIC_SIZE 0x04000000UL /* 64 MiB, from DTS reg */ + +#define PLIC_PRIORITY (PLIC + 0x0) +#define PLIC_PRIO_PER_ID 4 /* stride: 4 bytes/source */ + +#define PLIC_EN (PLIC + 0x2000) +#define PLIC_EN_PER_HART 0x100 +#define PLIC_EN_PER_CONTEXT 0x80 + + +#define PLIC_SENABLE(hart) (PLIC_EN + (hart) * PLIC_EN_PER_HART + PLIC_EN_PER_CONTEXT) + +#define PLIC_THRES (PLIC + 0x200000) +#define PLIC_THRES_PER_HART 0x2000 +#define PLIC_THRES_PER_CONTEXT 0x1000 +#define PLIC_THRES_CLAIM_OFF 0x4 + +/* + * S-mode threshold/claim = M-mode base + one context offset (0x1000). + */ +#define PLIC_SPRIORITY(hart) (PLIC_THRES + (hart) * PLIC_THRES_PER_HART + PLIC_THRES_PER_CONTEXT) +#define PLIC_SCLAIM(hart) (PLIC_SPRIORITY(hart) + PLIC_THRES_CLAIM_OFF) +#define PLIC_SCOMPLETE(hart) PLIC_SCLAIM(hart) + +#define PLIC_GET_PRIO(irqno) (*(volatile uint32_t *)(PLIC_PRIORITY + (irqno) * PLIC_PRIO_PER_ID)) +#define PLIC_SET_PRIO(irqno, prio) (*(volatile uint32_t *)(PLIC_PRIORITY + (irqno) * PLIC_PRIO_PER_ID) = (prio)) + + +#define PLIC_NUM_SOURCES 159 +#define PLIC_MAX_PRIORITY 7 +#define PLIC_DEFAULT_PRIORITY 2 +#define MAX_CALLBACK_NUM (PLIC_NUM_SOURCES + 1) + +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 /* RISCV_PLIC_H */ diff --git a/ports/risc-v64/gnu/example_build/bananapi-f3/trap.c b/ports/risc-v64/gnu/example_build/bananapi-f3/trap.c new file mode 100644 index 000000000..6418b02c3 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/bananapi-f3/trap.c @@ -0,0 +1,65 @@ +/*************************************************************************** + * Copyright (c) 2025 10xEngineers + * + * 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 + **************************************************************************/ + +/* + * S-mode trap handler + */ + +#include "csr.h" +#include "uart.h" +#include "hwtimer.h" +#include "plic.h" +#include +#include +#include + +/* scause bit 63: interrupt flag. */ +#define SCAUSE_INTERRUPT 0x8000000000000000ull + +#define SCAUSE_S_TIMER_INT (SCAUSE_INTERRUPT | 5u) +#define SCAUSE_S_EXTERNAL_INT (SCAUSE_INTERRUPT | 9u) + +extern void _tx_timer_interrupt(void); + +void trap_handler(uintptr_t cause, uintptr_t epc, uintptr_t tval) +{ + (void)epc; + (void)tval; + + if (cause & SCAUSE_INTERRUPT) + { + if (cause == SCAUSE_S_TIMER_INT) + { + hwtimer_handler(); + _tx_timer_interrupt(); + } + else if (cause == SCAUSE_S_EXTERNAL_INT) + { + if (plic_irq_intr() != 0) + { + puts("[trap] PLIC dispatch failed"); + while (1) + ; + } + } + else + { + puts("[trap] unhandled S-mode interrupt"); + while (1) + ; + } + } + else + { + puts("[trap] unhandled synchronous exception"); + while (1) + ; + } +} diff --git a/ports/risc-v64/gnu/example_build/bananapi-f3/tx_initialize_low_level.S b/ports/risc-v64/gnu/example_build/bananapi-f3/tx_initialize_low_level.S new file mode 100644 index 000000000..3cfa83c39 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/bananapi-f3/tx_initialize_low_level.S @@ -0,0 +1,139 @@ +/*************************************************************************** + * Copyright (c) 2025 10xEngineers + * + * 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" + + .section .text + .align 4 +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* trap_entry RISC-V64/GNU */ +/* 6.2.1 */ +/* AUTHOR */ +/* */ +/* Akif Ejaz, 10xEngineers */ +/* */ +/* 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 */ +/* */ +/**************************************************************************/ + + .global trap_entry + .extern trap_handler + .extern _tx_thread_context_save + .extern _tx_thread_context_restore + +trap_entry: +#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) + addi sp, sp, -520 /* 65 * 8: integer + FP */ +#else + addi sp, sp, -256 /* 32 * 8: integer only */ +#endif + + sd x1, 224(sp) /* Save RA at offset 28*8 */ + + call _tx_thread_context_save + + csrr a0, scause + csrr a1, sepc + csrr a2, stval + addi sp, sp, -8 + sd ra, 0(sp) + call trap_handler + ld ra, 0(sp) + addi sp, sp, 8 + + call _tx_thread_context_restore + /* never returns */ +_trap_err: + wfi + j _trap_err + + .section .text +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _tx_initialize_low_level RISC-V64/GNU */ +/* */ +/* 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. */ +/* */ +/**************************************************************************/ + + .global _tx_initialize_low_level + .weak _tx_initialize_low_level + .extern _end + .extern board_init + .extern trap_entry + .extern _tx_thread_system_stack_ptr + .extern _tx_initialize_unused_memory + +_tx_initialize_low_level: + la t0, _tx_thread_system_stack_ptr + sd sp, 0(t0) /* Save system stack */ + + la t0, _end + la t1, _tx_initialize_unused_memory + sd t0, 0(t1) /* First free address */ + + /* Disable global S-mode interrupts during early init. */ + li t0, SSTATUS_SIE + csrrc zero, sstatus, t0 + + /* Set SPIE = 1 so SRET re-enables interrupts. */ + li t0, SSTATUS_SPIE + csrrs zero, sstatus, t0 + + /* Enable S-mode timer and external interrupts. */ + li t0, (SIE_STIE | SIE_SEIE) + csrrs zero, sie, t0 + +#ifdef __riscv_flen + li t0, SSTATUS_FS + csrrs zero, sstatus, t0 + fscsr x0 +#endif + + addi sp, sp, -8 + sd ra, 0(sp) + call board_init + ld ra, 0(sp) + addi sp, sp, 8 + + la t0, trap_entry + csrw stvec, t0 + ret diff --git a/ports/risc-v64/gnu/example_build/bananapi-f3/uart.c b/ports/risc-v64/gnu/example_build/bananapi-f3/uart.c new file mode 100644 index 000000000..3ef89d094 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/bananapi-f3/uart.c @@ -0,0 +1,127 @@ +/*************************************************************************** + * Copyright (c) 2025 10xEngineers + * + * 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 "uart.h" +#include "csr.h" +#include "plic.h" +#include + +/* + * The K1 UART Register layout + * + * Offset Name Access Description + * ────── ────── ────── ────────────────────────────── + * 0x00 RBR/THR R/W Receive Buffer / Transmit Hold + * 0x00 DLL R/W (DLAB=1) Divisor Latch Low + * 0x04 IER R/W Interrupt Enable + * 0x04 DLH R/W (DLAB=1) Divisor Latch High + * 0x08 IIR R Interrupt Identification + * 0x08 FCR W FIFO Control + * 0x0C LCR R/W Line Control + * 0x10 MCR R/W Modem Control + * 0x14 LSR R Line Status + * 0x18 MSR R Modem Status + * 0x1C SPR R/W Scratchpad + */ + +#define REG(off) (*(volatile uint32_t *)(UART0 + (off))) + +#define RHR 0x00 +#define THR 0x00 +#define DLL 0x00 +#define IER 0x04 +#define DLH 0x04 +#define IIR 0x08 +#define FCR 0x08 +#define LCR 0x0C +#define MCR 0x10 +#define LSR 0x14 + + +#define IER_RAVIE (1u << 0) /* Receiver data available IRQ enable */ +#define IER_TIE (1u << 1) /* Transmit data request IRQ enable */ +#define IER_RLSE (1u << 2) /* Receiver line status IRQ enable */ +#define IER_UUE (1u << 6) /* UART unit enable */ + +#define FCR_TRFIFOE (1u << 0) /* Transmit/Receive FIFO enable */ +#define FCR_RESETRF (1u << 1) /* Reset receive FIFO */ +#define FCR_RESETTF (1u << 2) /* Reset transmit FIFO */ +#define FCR_FIFO_CLEAR (FCR_RESETRF | FCR_RESETTF) + +#define LCR_WLS_8 (3u << 0) /* 8-bit word */ +#define LCR_DLAB (1u << 7) /* Divisor latch access bit */ + +#define LSR_DR (1u << 0) /* Data ready in receive FIFO */ +#define LSR_TDRQ (1u << 5) /* Transmit holding/FIFO ready */ +#define LSR_TEMT (1u << 6) /* Transmitter empty */ + +#define ReadReg(off) (REG(off) & 0xFF) +#define WriteReg(off, v) (REG(off) = (uint32_t)((v) & 0xFF)) + +int uart_init(void) +{ + /* + * If a prior boot stage (BootROM / OpenSBI / U-Boot) already + * configured the UART, keep its baud-rate divisor so we keep + * a working console. Just make sure interrupts are masked, + * the FIFOs are clean and the unit-enable bit is set. + */ + WriteReg(IER, 0x00); + + /* + * Program the baud-rate divisor for 115200 baud. + * The PXA UART functional clock on K1 is 14.7456 MHz: + * 14,745,600 / (16 × 8) = 115,200 baud → DLL = 8, DLH = 0. + */ + WriteReg(LCR, LCR_DLAB); + WriteReg(DLL, 0x08); + WriteReg(DLH, 0x00); + + WriteReg(LCR, LCR_WLS_8); + WriteReg(FCR, FCR_TRFIFOE | FCR_FIFO_CLEAR); + + /* Enable the UART transmit/receive engines (K1-specific). */ + WriteReg(IER, IER_UUE); + + plic_irq_enable(UART0_IRQ); + plic_prio_set(UART0_IRQ, 1); + + puts("[uart] UART0 initialized"); + return 0; +} + +static inline void uart_putc_nolock(int ch) +{ + while ((ReadReg(LSR) & LSR_TDRQ) == 0) + ; + WriteReg(THR, ch); +} + +int uart_putc(int ch) +{ + int intr_enable = riscv_intr_get(); + riscv_intr_off(); + uart_putc_nolock(ch); + riscv_intr_restore(intr_enable); + return 1; +} + +int uart_puts(const char *str) +{ + int i; + int intr_enable = riscv_intr_get(); + riscv_intr_off(); + for (i = 0; str[i] != 0; i++) + uart_putc_nolock(str[i]); + uart_putc_nolock('\r'); + uart_putc_nolock('\n'); + riscv_intr_restore(intr_enable); + return i; +} diff --git a/ports/risc-v64/gnu/example_build/bananapi-f3/uart.h b/ports/risc-v64/gnu/example_build/bananapi-f3/uart.h new file mode 100644 index 000000000..b03d0dd4a --- /dev/null +++ b/ports/risc-v64/gnu/example_build/bananapi-f3/uart.h @@ -0,0 +1,72 @@ +/*************************************************************************** + * Copyright (c) 2025 10xEngineers + * + * 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 + +/* SpacemiT K1 UART0 + * + * DTS node: /soc/serial@d4017000 + * serial@d4017000 { + * power-domains = <0x20 0x00>; + * reg-io-width = <0x04>; + * clk-fpga = <0xe11130>; + * pinctrl-names = "default"; + * interconnect-names = "dma-mem"; + * pinctrl-0 = <0x23>; + * interconnects = <0x22>; + * resets = <0x1d 0x01>; + * interrupts = <0x2a>; + * clocks = <0x03 0x3a 0x03 0xb4>; + * interrupt-parent = <0x1e>; + * dma-names = "rx", "tx"; + * cpuidle,pm-runtime,sleep; + * compatible = "spacemit,pxa-uart"; + * status = "okay"; + * reg = <0x00 0xd4017000 0x00 0x100>; + * dmas = <0x21 0x04 0x01 0x21 0x03 0x01>; + * reg-shift = <0x02>; + * }; + + + * K1 User Manual Section 16.3.4 defines the register layout. Because + * reg-shift = 2, the logical 16550A register indices (0..7) map to + * byte offsets 0x00, 0x04, 0x08, ..., 0x1C (stride of 4). + * + * Register map (at UART0 + offset): + * + * Offset Name R/W Description + * ────── ────────── ─── ──────────────────────────────────── + * 0x00 RBR/THR/DLL R/W Receive Buffer / Transmit Holding / + * Divisor Latch Low (when LCR.DLAB=1) + * 0x04 IER/DLH R/W Interrupt Enable / + * Divisor Latch High (when LCR.DLAB=1) + * 0x08 IIR/FCR R/W Interrupt Identification (R) / + * FIFO Control (W) + * 0x0C LCR R/W Line Control + * 0x10 MCR R/W Modem Control + * 0x14 LSR R Line Status + * 0x18 MSR R Modem Status + * 0x1C SPR R/W Scratchpad + * + */ + +#define UART0 0xD4017000UL +#define UART0_SIZE 0x100UL /* 256 bytes, from DTS reg */ +#define UART0_IRQ 42 /* DTS: interrupts = <0x2a> */ +#define UART0_REG_SHIFT 2 /* DTS: reg-shift = <0x02> */ + +#define puts uart_puts + +int uart_init(void); +int uart_putc(int ch); +int uart_puts(const char *str); + +#endif /* RISCV_UART_H */ diff --git a/ports/risc-v64/gnu/inc/tx_port.h b/ports/risc-v64/gnu/inc/tx_port.h index c8f5b3124..3c25e9fea 100644 --- a/ports/risc-v64/gnu/inc/tx_port.h +++ b/ports/risc-v64/gnu/inc/tx_port.h @@ -126,8 +126,13 @@ typedef unsigned short USHORT; /* Define various constants for the ThreadX RISC-V port. */ -#define TX_INT_DISABLE 0x00000000 /* Disable interrupts value */ -#define TX_INT_ENABLE 0x00000008 /* Enable interrupt value */ +#ifdef TX_RISCV_SMODE +#define TX_INT_DISABLE 0x00000000 /* Disable interrupts value */ +#define TX_INT_ENABLE 0x00000002 /* Enable interrupt value (SIE bit 1 of sstatus) */ +#else +#define TX_INT_DISABLE 0x00000000 /* Disable interrupts value */ +#define TX_INT_ENABLE 0x00000008 /* Enable interrupt value (MIE bit 3 of mstatus) */ +#endif /* Define the clock source for trace event entry time stamp. The following two item are port specific. @@ -248,6 +253,19 @@ UINT _tx_thread_interrupt_control(UIN #define TX_INTERRUPT_SAVE_AREA register UINT interrupt_save; +#ifdef TX_RISCV_SMODE +#define TX_DISABLE __asm__ volatile("csrrci %0, sstatus, 2" : "=r" (interrupt_save) :: "memory"); +#define TX_RESTORE { \ + unsigned long _temp_sstatus; \ + __asm__ volatile( \ + "csrc sstatus, 2\n" \ + "andi %0, %1, 2\n" \ + "csrs sstatus, %0" \ + : "=&r" (_temp_sstatus) \ + : "r" (interrupt_save) \ + : "memory"); \ + } +#else #define TX_DISABLE __asm__ volatile("csrrci %0, mstatus, 8" : "=r" (interrupt_save) :: "memory"); #define TX_RESTORE { \ unsigned long _temp_mstatus; \ @@ -259,6 +277,7 @@ UINT _tx_thread_interrupt_control(UIN : "r" (interrupt_save) \ : "memory"); \ } +#endif /* TX_RISCV_SMODE */ #else diff --git a/ports/risc-v64/gnu/src/tx_thread_context_restore.S b/ports/risc-v64/gnu/src/tx_thread_context_restore.S index b73b485d4..50646cfb2 100644 --- a/ports/risc-v64/gnu/src/tx_thread_context_restore.S +++ b/ports/risc-v64/gnu/src/tx_thread_context_restore.S @@ -63,7 +63,11 @@ _tx_thread_context_restore: /* Lockout interrupts. */ +#ifdef TX_RISCV_SMODE + csrci sstatus, 0x02 // Disable interrupts (SIE bit 1) +#else csrci mstatus, 0x08 // Disable interrupts (MIE bit 3) +#endif #ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY call _tx_execution_isr_exit // Call the ISR execution exit function @@ -168,7 +172,25 @@ _tx_thread_context_restore: Also skip the saved registers since they have been restored by any function we called, except s0 since we use it ourselves. */ - ld t0, 30*8(sp) // Recover mepc + ld t0, 30*8(sp) // Recover exception PC +#ifdef TX_RISCV_SMODE + csrw sepc, t0 // Setup sepc + + /* Compose sstatus via read/modify/write to avoid clobbering unrelated bits. + Set SPIE and restore SPP to Supervisor, preserve other fields. */ + + csrr t1, sstatus + li t4, ~0x122 // Clear mask for SPP/SPIE/SIE + and t1, t1, t4 + li t3, 0x100 // Set SPP to Supervisor mode (bit 8) + 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 sstatus, t1 // Update sstatus safely +#else csrw mepc, t0 // Setup mepc /* Compose mstatus via read/modify/write to avoid clobbering unrelated bits. @@ -189,6 +211,7 @@ _tx_thread_context_restore: or t1, t1, t0 #endif csrw mstatus, t1 // Update mstatus safely +#endif ld ra, 28*8(sp) // Recover return address ld t0, 19*8(sp) // Recover t0 @@ -228,7 +251,11 @@ _tx_thread_context_restore: ld t1, 18*8(t0) // Recover t1 ld t0, 19*8(t0) // Recover t0 #endif +#ifdef TX_RISCV_SMODE + sret // Return to point of interrupt +#else mret // Return to point of interrupt +#endif /* } */ _tx_thread_not_nested_restore: @@ -341,7 +368,24 @@ _tx_thread_no_preempt_restore: /* Restore registers, Skip global pointer because that does not change */ - ld t0, 30*8(sp) // Recover mepc + ld t0, 30*8(sp) // Recover exception PC +#ifdef TX_RISCV_SMODE + csrw sepc, t0 // Setup sepc + + /* Compose sstatus via read/modify/write to avoid clobbering unrelated bits. */ + + csrr t1, sstatus + li t4, ~0x122 // Clear mask for SPP/SPIE/SIE + and t1, t1, t4 + li t3, 0x120 // Set SPP=Supervisor(0x100) + SPIE(0x20) so sret re-enables SIE + 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 sstatus, t1 // Update sstatus safely +#else csrw mepc, t0 // Setup mepc @@ -363,6 +407,7 @@ _tx_thread_no_preempt_restore: or t1, t1, t0 #endif csrw mstatus, t1 // Update mstatus safely +#endif ld ra, 28*8(sp) // Recover return address ld t0, 19*8(sp) // Recover t0 @@ -402,7 +447,11 @@ _tx_thread_no_preempt_restore: ld t1, 18*8(t0) // Recover t1 ld t0, 19*8(t0) // Recover t0 #endif +#ifdef TX_RISCV_SMODE + sret // Return to point of interrupt +#else mret // Return to point of interrupt +#endif /* } else diff --git a/ports/risc-v64/gnu/src/tx_thread_context_save.S b/ports/risc-v64/gnu/src/tx_thread_context_save.S index 1a6389907..3ffcb6b68 100644 --- a/ports/risc-v64/gnu/src/tx_thread_context_save.S +++ b/ports/risc-v64/gnu/src/tx_thread_context_save.S @@ -94,7 +94,11 @@ _tx_thread_context_save: sd t4, 15*8(sp) // Store t4 sd t5, 14*8(sp) // Store t5 sd t6, 13*8(sp) // Store t6 +#ifdef TX_RISCV_SMODE + csrr t0, sepc // Load exception program counter +#else csrr t0, mepc // Load exception program counter +#endif sd t0, 30*8(sp) // Save it on the stack /* Save floating point scratch registers if floating point is enabled. */ @@ -214,7 +218,11 @@ _tx_thread_not_nested_save: sd t5, 14*8(sp) // Store t5 sd t6, 13*8(sp) // Store t6 +#ifdef TX_RISCV_SMODE + csrr t1, sepc // Load exception program counter +#else csrr t1, mepc // Load exception program counter +#endif sd t1, 30*8(sp) // Save it on the stack /* Save floating point scratch registers if floating point is enabled. */ diff --git a/ports/risc-v64/gnu/src/tx_thread_interrupt_control.S b/ports/risc-v64/gnu/src/tx_thread_interrupt_control.S index dfcb6b47f..b43d95589 100644 --- a/ports/risc-v64/gnu/src/tx_thread_interrupt_control.S +++ b/ports/risc-v64/gnu/src/tx_thread_interrupt_control.S @@ -59,20 +59,25 @@ .global _tx_thread_interrupt_control _tx_thread_interrupt_control: /* Pickup current interrupt lockout posture. */ - /* old_mstatus = mstatus; */ +#ifdef TX_RISCV_SMODE + csrr t0, sstatus + mv t1, t0 // Save original sstatus for return + li t2, ~0x02 // Build mask to clear SIE (bit 1) + and t0, t0, t2 // Clear SIE bit + andi a0, a0, 0x02 // Mask incoming to only SIE bit + or t0, t0, a0 // Set requested SIE state + csrw sstatus, t0 + andi a0, t1, 0x02 // Return original SIE bit +#else csrr t0, mstatus mv t1, t0 // Save original mstatus for return - - /* Apply the new interrupt posture while preserving unrelated mstatus bits. */ - /* Only modify the MIE bit (bit 3) */ - /* mstatus = (mstatus & ~MIE) | (new_posture & MIE); */ - li t2, ~0x08 // Build mask to clear MIE and t0, t0, t2 // Clear MIE bit - and a0, a0, 0x08 // Mask incoming to only MIE bit + andi a0, a0, 0x08 // Mask incoming to only MIE bit or t0, t0, a0 // Set requested MIE state csrw mstatus, t0 andi a0, t1, 0x08 // Return original MIE bit +#endif ret /* } */ diff --git a/ports/risc-v64/gnu/src/tx_thread_schedule.S b/ports/risc-v64/gnu/src/tx_thread_schedule.S index 85fc9488f..401c7b1a0 100644 --- a/ports/risc-v64/gnu/src/tx_thread_schedule.S +++ b/ports/risc-v64/gnu/src/tx_thread_schedule.S @@ -62,7 +62,11 @@ _tx_thread_schedule: /* Enable interrupts. */ +#ifdef TX_RISCV_SMODE + csrsi sstatus, 0x02 // Enable interrupts (SIE bit 1) +#else csrsi mstatus, 0x08 // Enable interrupts (MIE bit 3) +#endif /* Wait for a thread to execute. */ /* do @@ -94,7 +98,11 @@ _tx_thread_schedule_loop: /* Yes! We have a thread to execute. Lockout interrupts and transfer control to it. */ +#ifdef TX_RISCV_SMODE + csrci sstatus, 0x02 // Lockout interrupts +#else csrci mstatus, 0x08 // Lockout interrupts +#endif /* Setup the current thread pointer. */ /* _tx_thread_current_ptr = _tx_thread_execute_ptr; */ @@ -236,11 +244,33 @@ _tx_thread_schedule_loop: /* Recover standard registers. */ - ld t0, 30*8(sp) // Recover mepc + ld t0, 30*8(sp) // Recover mepc/sepc +#ifdef TX_RISCV_SMODE + csrw sepc, t0 // Store sepc + + /* Read/modify/write sstatus to preserve SUM, MXR, UXL, FS, etc. + Only touch SPP, SPIE, SIE — the bits SRET consumes. */ + csrr t0, sstatus + li t1, ~0x122 // Clear mask: SIE(1) | SPIE(5) | SPP(8) + and t0, t0, t1 + li t1, 0x120 // Set SPP=Supervisor(0x100) | SPIE(0x20) + or t0, t0, t1 +#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) + li t1, 0x6000 // Set FS=Dirty (bits 14:13) + or t0, t0, t1 +#endif + csrw sstatus, t0 // Update sstatus safely +#else csrw mepc, t0 // Store mepc - li t0, 0x1880 // Prepare mstatus: MPP=Machine(0x1800) | MPIE(0x80) + + /* Read/modify/write mstatus — same principle as S-mode path. */ + csrr t0, mstatus + li t1, ~0x1888 // Clear mask: MIE(3) | MPIE(7) | MPP(11:12) + and t0, t0, t1 + li t1, 0x1880 // Set MPP=Machine(0x1800) | MPIE(0x80) + or t0, t0, t1 #if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) - li t1, 0x2000 // Set FS bits for FP state + li t1, 0x6000 // Set FS=Dirty (bits 14:13) or t0, t0, t1 #endif #if defined(__riscv_vector) @@ -248,6 +278,7 @@ _tx_thread_schedule_loop: or t0, t0, t1 #endif csrw mstatus, t0 // Set mstatus +#endif ld ra, 28*8(sp) // Recover return address ld t0, 19*8(sp) // Recover t0 @@ -297,7 +328,11 @@ _tx_thread_schedule_loop: ld t1, 18*8(t0) // Recover t1 ld t0, 19*8(t0) // Recover t0 #endif +#ifdef TX_RISCV_SMODE + sret // Return to point of interrupt +#else mret // Return to point of interrupt +#endif _tx_thread_synch_return: @@ -377,8 +412,12 @@ _tx_thread_synch_return: ld s9, 3*8(sp) // Recover s9 ld s10, 2*8(sp) // Recover s10 ld s11, 1*8(sp) // Recover s11 - ld t0, 14*8(sp) // Recover mstatus + ld t0, 14*8(sp) // Recover status register +#ifdef TX_RISCV_SMODE + csrw sstatus, t0 // Store sstatus, enables interrupt +#else csrw mstatus, t0 // Store mstatus, enables interrupt +#endif #if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) addi sp, sp, 29*8 // Recover stack frame #else diff --git a/ports/risc-v64/gnu/src/tx_thread_stack_build.S b/ports/risc-v64/gnu/src/tx_thread_stack_build.S index 410f6cc84..cc1cf2a36 100644 --- a/ports/risc-v64/gnu/src/tx_thread_stack_build.S +++ b/ports/risc-v64/gnu/src/tx_thread_stack_build.S @@ -192,7 +192,7 @@ If vector extension support: sd zero, 26*8(t0) // Initial a1 sd zero, 27*8(t0) // Initial a0 sd zero, 28*8(t0) // Initial ra - sd a1, 30*8(t0) // Initial mepc (thread entry point) + sd a1, 30*8(t0) // Initial mepc/sepc (thread entry point) #if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) sd zero, 31*8(t0) // Initial ft0 sd zero, 32*8(t0) // Initial ft1 diff --git a/ports/risc-v64/gnu/src/tx_thread_system_return.S b/ports/risc-v64/gnu/src/tx_thread_system_return.S index 61babae35..8becb54b0 100644 --- a/ports/risc-v64/gnu/src/tx_thread_system_return.S +++ b/ports/risc-v64/gnu/src/tx_thread_system_return.S @@ -152,6 +152,15 @@ _tx_thread_system_return: sd s9, 3*8(sp) // Save s9 sd s10, 2*8(sp) // Save s10 sd s11, 1*8(sp) // Save s11 +#ifdef TX_RISCV_SMODE + csrr t0, sstatus // Pickup sstatus + sd t0, 14*8(sp) // Save sstatus + + + /* Lockout interrupts. will be enabled in _tx_thread_schedule */ + + csrci sstatus, 0x02 // Disable interrupts (SIE bit 1) +#else csrr t0, mstatus // Pickup mstatus sd t0, 14*8(sp) // Save mstatus @@ -159,6 +168,7 @@ _tx_thread_system_return: /* Lockout interrupts. will be enabled in _tx_thread_schedule */ csrci mstatus, 0x08 // Disable interrupts (MIE bit 3) +#endif #ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY