diff options
| author | Frédéric Desbiens <[email protected]> | 2026-10-02 10:42:44 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-10-02 10:42:44 -0400 |
| commit | 93387b0a60382a29579bbad5045c68328e3751df (patch) | |
| tree | 42be790d602cb76f55090e5a7c418df7ea2b3428 /ports/risc-v64/gnu | |
| parent | 44d7c95c582d415c4ad84527180b29c93c3bf664 (diff) | |
| parent | 28281cd5647d4671236ca08d19bd18ed15b2265a (diff) | |
Merge pull request #783 from eclipse-threadx/devv6.5.2.202603_rel
Merged changes ahead of the v6.5.2.202603 release
Diffstat (limited to 'ports/risc-v64/gnu')
43 files changed, 2431 insertions, 245 deletions
diff --git a/ports/risc-v64/gnu/CMakeLists.txt b/ports/risc-v64/gnu/CMakeLists.txt index e51de735..05000757 100644 --- a/ports/risc-v64/gnu/CMakeLists.txt +++ b/ports/risc-v64/gnu/CMakeLists.txt @@ -3,4 +3,5 @@ include(${CMAKE_CURRENT_LIST_DIR}/../../../cmake/threadx_riscv_port.cmake) threadx_add_riscv_port( SRC_DIR ${CMAKE_CURRENT_LIST_DIR}/src INC_DIR ${CMAKE_CURRENT_LIST_DIR}/inc + EXAMPLE_DIR ${CMAKE_CURRENT_LIST_DIR}/example_build/qemu_virt ) diff --git a/ports/risc-v64/gnu/example_build/erbium/CMakeLists.txt b/ports/risc-v64/gnu/example_build/erbium/CMakeLists.txt new file mode 100644 index 00000000..51b07714 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/CMakeLists.txt @@ -0,0 +1,68 @@ +cmake_minimum_required(VERSION 3.13 FATAL_ERROR) + +project(demo_threadx_erbium LANGUAGES C ASM) +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) + +set(ERBIUM_DIR ${CMAKE_CURRENT_LIST_DIR}) +set(THREADX_ROOT ${CMAKE_CURRENT_LIST_DIR}/../../../../..) + +add_subdirectory(${THREADX_ROOT} threadx) + +add_executable(demo_threadx + ${ERBIUM_DIR}/entry.S + ${ERBIUM_DIR}/tx_initialize_low_level.S + ${ERBIUM_DIR}/board.c + ${ERBIUM_DIR}/hwtimer.c + ${ERBIUM_DIR}/plic.c + ${ERBIUM_DIR}/trap.c + ${ERBIUM_DIR}/uart.c + ${ERBIUM_DIR}/demo_threadx.c +) +set_target_properties(demo_threadx PROPERTIES + OUTPUT_NAME "demo_threadx.elf" + LINK_DEPENDS ${ERBIUM_DIR}/link.lds +) + +target_include_directories(demo_threadx PRIVATE ${ERBIUM_DIR}) + +target_link_libraries(demo_threadx PRIVATE threadx) + +target_compile_options(demo_threadx PRIVATE + -Wall + -ffreestanding + -ffunction-sections + -fdata-sections +) + +target_link_options(demo_threadx PRIVATE + -T${ERBIUM_DIR}/link.lds + -nostdlib + -Wl,--gc-sections + -Wl,-Map=${CMAKE_CURRENT_BINARY_DIR}/demo_threadx.map +) + +function(add_erbium_test name source handler) + add_executable(${name} + ${ERBIUM_DIR}/entry.S + ${ERBIUM_DIR}/tx_initialize_low_level.S + ${ERBIUM_DIR}/board.c + ${ERBIUM_DIR}/hwtimer.c + ${ERBIUM_DIR}/plic.c + ${ERBIUM_DIR}/uart.c + ${source} + ${handler} + ) + target_include_directories(${name} PRIVATE ${ERBIUM_DIR}) + target_link_libraries(${name} PRIVATE threadx) + target_compile_options(${name} PRIVATE -ffreestanding) + target_link_options(${name} PRIVATE + -T${ERBIUM_DIR}/link.lds + -nostdlib + -Wl,--gc-sections + ) + set_target_properties(${name} PROPERTIES LINK_DEPENDS ${ERBIUM_DIR}/link.lds) +endfunction() + +add_erbium_test(test_uart_ticks ${ERBIUM_DIR}/test/test_uart_ticks.c ${ERBIUM_DIR}/trap.c) +add_erbium_test(test_plic_race ${ERBIUM_DIR}/test/test_plic_race.c ${ERBIUM_DIR}/test/test_plic_trap.c) diff --git a/ports/risc-v64/gnu/example_build/erbium/README.md b/ports/risc-v64/gnu/example_build/erbium/README.md new file mode 100644 index 00000000..8cfa2828 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/README.md @@ -0,0 +1,140 @@ +# ThreadX example for Erbium + +This directory runs the standard ThreadX demo on one hart of Erbium, the +16-hart RISC-V platform of the OpenHW Foundation CORE-ET project, using the +RISC-V64 GNU port in machine mode. The public `et-platform` functional +simulator, `erbium_emu`, is the reference target. + +## Hardware used + +| Item | Address | Notes | +|------|---------|-------| +| Hart 0 | - | RV64IMC, machine mode; other harts are parked | +| MRAM | `0x4000_0200` | 16 MiB window; the first 512 bytes belong to the boot flow | +| System registers | `0x0200_0000` | `SystemConfig` bit 6 enables the UART | +| UART0 | `0x0200_4000` | Original Shakti integration, polling, 115200 8N1 | +| Machine timer | `0x80F4_0200` | `mtime`, with `mtimecmp` at `+0x08` | +| PLIC | `0xA000_0000` | Machine-mode context of hart 0; sources 1 to 6 | + +## Port notes + +* **Soft float.** Erbium implements only part of the F extension: divide, + square root and conversions between single precision and 64-bit integers + raise an emulation exception. The example and the ThreadX library are + built for `rv64imc_zicsr_zifencei` with the `lp64` ABI, so the port saves + no FP state. `csr.h` rejects a build with F enabled. +* **No C library.** The `riscv64-unknown-elf` toolchain installed by + `scripts/install_riscv.sh` ships `lp64d` libraries only, so the example + links with `-nostdlib` and `board.c` provides `memset`. A missing runtime + helper shows up as a link error, not as a mix of ABIs. +* **No WFI.** The example never executes `wfi`, like the Zephyr Erbium + port. `TX_USE_WFI_IDLE` stays undefined, so the scheduler spins while + idle, and parked harts and fatal-trap halts spin as well. +* **Trap vector.** Erbium ignores `mtvec` base bits 11:1, so the trap + vector must be 4 KiB aligned. `trap_entry` is placed in its own 4 KiB + aligned section and used in direct mode. +* **PLIC.** The silicon hardwires source priorities to 1 and thresholds to + 0. The simulator models them as writable registers that reset to 0, so + `plic_init()` writes the hardwired values. Drivers register a callback and + enable their source with `plic_irq_enable()`. +* **Clocks.** The UART divisor assumes a 400 MHz input clock and the tick + assumes a 2 MHz `mtime` rate, the values used by the Zephyr and NuttX + Erbium ports. If the boot firmware programs other rates, define + `ERBIUM_UART_CLOCK_HZ` and `ERBIUM_MTIME_FREQ_HZ` when you build. +* **UART.** The console only polls. `uart_init()` sets the UART enable bit + in `SystemConfig` with a read-modify-write, which leaves the watchdog + setting as the boot flow left it. + +## Building + +Install the toolchain with `scripts/install_riscv.sh`, or put another +`riscv64-unknown-elf` GCC on `PATH`, then run: + +```bash +./build.sh +``` + +This configures `CMakeLists.txt` with `erbium_gnu.cmake`, builds +`libthreadx.a` for the soft-float ABI and links `build/demo_threadx.elf`. +Set `BUILD_DIR` to build elsewhere. + +## Running on the simulator + +Build `erbium_emu` from +[et-platform](https://github.com/aifoundry-org/et-platform). The example was +tested at revision `836a4ab600e93c3059bb58c898edbc37744cd8d0`: + +```bash +git clone https://github.com/aifoundry-org/et-platform.git +cd et-platform +git checkout 836a4ab600e93c3059bb58c898edbc37744cd8d0 +cmake -S erbium-hal -B build-hal -DCMAKE_INSTALL_PREFIX="$PWD/install" +cmake --build build-hal --target install +cmake -S sw-sysemu -B build-emu -GNinja -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_FLAGS=-Wno-error=unused-result \ + -DCMAKE_PREFIX_PATH="$PWD/install" +cmake --build build-emu --target erbium_emu +``` + +At that revision a release build with a distribution GCC that enables +`_FORTIFY_SOURCE` stops on an unused `write()` result in the UART model; +the `CMAKE_CXX_FLAGS` setting above keeps that warning from being an error. + +Load the ELF and start hart 0 at the MRAM load address, bypassing the boot +ROM: + +```bash +erbium_emu -elf_load build/demo_threadx.elf -reset_pc 0x40000200 \ + -minions 1 -single_thread -max_cycles 100000000 \ + -uart_rx_file /dev/null -uart_tx_file uart.log +cat uart.log +``` + +The log starts with `[UART0] : Erbium UART initialized`, followed by lines +from all eight demo threads. The simulator ends with a `max cycles reached` +error once the cycle budget is spent; judge the run by the UART log, not by +the exit status. With the default clocks one ThreadX tick is 2,000,000 +simulated cycles, so the 100,000,000-cycle run shows `thread_0` five times. + +The simulator verifies register accesses, the trap flow and the console. +It does not verify baud timing or the timer rate of real silicon. + +## Simulator regression tests + +With `erbium_emu` on `PATH`, run: + +```bash +ERBIUM_EMU=erbium_emu ./test/run_simulator_tests.sh +``` + +The UART test checks that a long polling write does not lose ThreadX ticks. +The PLIC test changes one source in timer context while a thread changes +another, and checks that neither update is lost. The runner builds both +images with CMake and Ninja and reports a failure if either check fails. +These tests use the simulator's UART drain rate, not physical baud timing. + +## Files + +| File | Purpose | +|------|---------| +| `entry.S` | Reset entry: parks other harts, sets `gp`, stack and `.bss` | +| `tx_initialize_low_level.S` | Trap vector and `_tx_initialize_low_level` | +| `trap.c` | Dispatches the timer, the PLIC and fatal exceptions | +| `board.c` | `board_init()` and `memset` | +| `plic.c`, `plic.h` | PLIC driver for the hart's machine-mode context | +| `hwtimer.c`, `hwtimer.h` | Periodic tick from `mtime` and `mtimecmp` | +| `uart.c`, `uart.h` | Polling console | +| `demo_threadx.c` | The standard ThreadX demo | +| `link.lds` | MRAM layout | +| `erbium_gnu.cmake` | Toolchain file: `rv64imc_zicsr_zifencei`, `lp64` | +| `CMakeLists.txt`, `build.sh` | Build of the library and the demo | +| `test/` | Simulator tests for UART timekeeping and PLIC updates | + +## References + +* [Erbium processor](https://github.com/openhwfoundation/core-et-erbium) +* [UART registers](https://github.com/openhwfoundation/core-et-erbium/blob/325b32b7efaa2c2ab9c91001c89d3f49a4740826/doc/uart.md) +* [CPU memory map](https://github.com/openhwfoundation/core-et-erbium/blob/325b32b7efaa2c2ab9c91001c89d3f49a4740826/doc/cpu_mm.md) +* [Platform interrupts](https://github.com/openhwfoundation/core-et-erbium/blob/325b32b7efaa2c2ab9c91001c89d3f49a4740826/doc/interrupts.md) +* [CPU subsystem](https://github.com/openhwfoundation/core-et-erbium/blob/325b32b7efaa2c2ab9c91001c89d3f49a4740826/doc/cpu_subsystem.md) +* [et-platform simulator](https://github.com/aifoundry-org/et-platform) diff --git a/ports/risc-v64/gnu/example_build/erbium/board.c b/ports/risc-v64/gnu/example_build/erbium/board.c new file mode 100644 index 00000000..b801b55d --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/board.c @@ -0,0 +1,49 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5.5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +#include "plic.h" +#include "hwtimer.h" +#include "uart.h" +#include <stdint.h> +#include <stddef.h> + +/* The example links without a C library; ThreadX needs memset. */ +void *memset(void *des, int c, size_t n) +{ + unsigned char *target = des; + + for (size_t i = 0; i < n; i++) + target[i] = (unsigned char)c; + return des; +} + +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/erbium/build.sh b/ports/risc-v64/gnu/example_build/erbium/build.sh new file mode 100755 index 00000000..effaed0a --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/build.sh @@ -0,0 +1,28 @@ +#!/bin/bash +############################################################################## +# Copyright (c) 2026 Eclipse ThreadX contributors +# +# This program and the accompanying materials are made available under the +# terms of the MIT License which is available at +# https://opensource.org/licenses/MIT. +# +# AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5.5). +# The AI-generated portions may be considered public domain (CC0-1.0) +# and not subject to the project's licence. The human contributor has +# reviewed and verified that the code is correct. +# +# SPDX-License-Identifier: MIT and CC0-1.0 +############################################################################## + +# Build the ThreadX library and the Erbium demo, build/demo_threadx.elf. + +set -eu + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +BUILD_DIR=${BUILD_DIR:-"${SCRIPT_DIR}/build"} + +cmake -S "${SCRIPT_DIR}" -B "${BUILD_DIR}" -GNinja \ + -DCMAKE_TOOLCHAIN_FILE="${SCRIPT_DIR}/erbium_gnu.cmake" +cmake --build "${BUILD_DIR}" --target demo_threadx + +printf 'Built %s\n' "${BUILD_DIR}/demo_threadx.elf" diff --git a/ports/risc-v64/gnu/example_build/erbium/csr.h b/ports/risc-v64/gnu/example_build/erbium/csr.h new file mode 100644 index 00000000..e2aa4e18 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/csr.h @@ -0,0 +1,66 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5.5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +/* Machine-mode CSR definitions and helpers for the Erbium example. */ + +#ifndef RISCV_CSR_H +#define RISCV_CSR_H + +/* The Erbium FPU implements only part of the F extension: single-precision + divide, square root and conversions to and from 64-bit integers raise an + emulation exception that this example does not handle. Build for the + integer ISA with the soft-float lp64 ABI. */ +#ifdef __riscv_flen +#error "The Erbium example requires a soft-float build: use -march=rv64imc_zicsr_zifencei -mabi=lp64." +#endif + +/* Machine Status Register, mstatus */ +#define MSTATUS_MIE (1L << 3) /* machine-mode interrupt enable */ +#define MSTATUS_MPIE (1L << 7) /* previous MIE */ +#define MSTATUS_MPP_M (3L << 11) /* previous mode: machine */ + +/* Machine Interrupt Enable, mie */ +#define MIE_MTIE (1L << 7) /* machine timer */ +#define MIE_MEIE (1L << 11) /* machine external */ + +#ifndef __ASSEMBLER__ + +#include <stdint.h> + +static inline uintptr_t riscv_get_core(void) +{ + uintptr_t x; + __asm__ volatile("csrr %0, mhartid" : "=r" (x)); + return x; +} + +/* Clear mstatus.MIE and return whether it was set. */ +static inline int riscv_mintr_save(void) +{ + uintptr_t x; + __asm__ volatile("csrrci %0, mstatus, %1" : "=r" (x) : "i" (MSTATUS_MIE) : "memory"); + return (x & MSTATUS_MIE) != 0; +} + +/* Set mstatus.MIE again if riscv_mintr_save found it set. */ +static inline void riscv_mintr_restore(int enabled) +{ + if (enabled) + __asm__ volatile("csrsi mstatus, %0" : : "i" (MSTATUS_MIE) : "memory"); +} + +#endif /* __ASSEMBLER__ */ + +#endif /* RISCV_CSR_H */ diff --git a/ports/risc-v64/gnu/example_build/erbium/demo_threadx.c b/ports/risc-v64/gnu/example_build/erbium/demo_threadx.c new file mode 100644 index 00000000..2023df12 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/demo_threadx.c @@ -0,0 +1,382 @@ +/***************************************************************************/ +/* Copyright (c) 2024 Microsoft Corporation */ +/* Copyright (c) 2026 Eclipse ThreadX contributors */ +/* */ +/* This program and the accompanying materials are made available under */ +/* the terms of the MIT License which is available at */ +/* https://opensource.org/licenses/MIT. */ +/* */ +/* SPDX-License-Identifier: MIT */ +/***************************************************************************/ + +/* 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/erbium/entry.S b/ports/risc-v64/gnu/example_build/erbium/entry.S new file mode 100644 index 00000000..201f986e --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/entry.S @@ -0,0 +1,50 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5.5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +/* Reset entry for the Erbium example. Hart 0 sets up the global pointer, + the system stack and .bss, then calls main(). Any other hart is parked. */ + + .section .text.entry, "ax", @progbits + .align 2 + .global _start + .extern main + .extern _sysstack_end + .extern _bss_start + .extern _bss_end +_start: + csrr t0, mhartid + bnez t0, _park + +.option push +.option norelax + la gp, __global_pointer$ // norelax keeps this load absolute +.option pop + + la sp, _sysstack_end + + la t0, _bss_start // .bss is 16-byte aligned in link.lds + la t1, _bss_end +_bss_clear: + bgeu t0, t1, _bss_done + sd zero, 0(t0) + addi t0, t0, 8 + j _bss_clear +_bss_done: + + call main + + /* The example never executes WFI; a parked hart spins. */ +_park: + j _park diff --git a/ports/risc-v64/gnu/example_build/erbium/erbium_gnu.cmake b/ports/risc-v64/gnu/example_build/erbium/erbium_gnu.cmake new file mode 100644 index 00000000..0d55dd89 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/erbium_gnu.cmake @@ -0,0 +1,39 @@ +# /*************************************************************************** +# * Copyright (c) 2026 Eclipse ThreadX contributors +# * +# * This program and the accompanying materials are made available under the +# * terms of the MIT License which is available at +# * https://opensource.org/licenses/MIT. +# * +# * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5.5). +# * The AI-generated portions may be considered public domain (CC0-1.0) +# * and not subject to the project's licence. The human contributor has +# * reviewed and verified that the code is correct. +# * +# * SPDX-License-Identifier: MIT and CC0-1.0 +# ***************************************************************************/ + +# CMake toolchain file for the Erbium example. +# +# Erbium implements only part of the F extension, so the example and the +# ThreadX library are built for the integer ISA with the soft-float lp64 ABI. +# +# Target ISA : rv64imc_zicsr_zifencei +# ABI : lp64 (64-bit long/ptr, soft-float) +# Code model : medany (MRAM at 0x40000000, peripherals above 2 GiB) +# +# The riscv64-unknown-elf toolchain installed by scripts/install_riscv.sh is +# built for lp64d only, so the example links without libc and libgcc +# (-nostdlib); a missing helper is a link error rather than an ABI mix. + +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR risc-v64) + +set(THREADX_ARCH "risc-v64") +set(THREADX_TOOLCHAIN "gnu") +set(ARCH_FLAGS "-g -march=rv64imc_zicsr_zifencei -mabi=lp64 -mcmodel=medany") +set(CFLAGS "${ARCH_FLAGS}") +set(ASFLAGS "${ARCH_FLAGS}") +set(LDFLAGS "${ARCH_FLAGS}") + +include(${CMAKE_CURRENT_LIST_DIR}/../../../../../cmake/riscv64-unknown-elf.cmake) diff --git a/ports/risc-v64/gnu/example_build/erbium/hwtimer.c b/ports/risc-v64/gnu/example_build/erbium/hwtimer.c new file mode 100644 index 00000000..511bea59 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/hwtimer.c @@ -0,0 +1,42 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5.5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +#include "tx_api.h" +#include "hwtimer.h" + +/* Naturally aligned 64-bit MMIO accesses are single loads and stores on + RV64; volatile keeps the compiler from caching or reordering them. */ +#define MTIME (*(volatile uint64_t *)ERBIUM_MTIME) +#define MTIMECMP (*(volatile uint64_t *)ERBIUM_MTIMECMP) + +int hwtimer_init(void) +{ + MTIMECMP = MTIME + TICKNUM_PER_TIMER; + return 0; +} + +int hwtimer_handler(void) +{ + /* Advance from the previous compare value, so trap latency does not + accumulate as tick drift. */ + uint64_t next = MTIMECMP + TICKNUM_PER_TIMER; + uint64_t now = MTIME; + + if (next <= now) + next = now + TICKNUM_PER_TIMER; + + MTIMECMP = next; + return 0; +} diff --git a/ports/risc-v64/gnu/example_build/erbium/hwtimer.h b/ports/risc-v64/gnu/example_build/erbium/hwtimer.h new file mode 100644 index 00000000..3b9bf4e3 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/hwtimer.h @@ -0,0 +1,41 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5.5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +#ifndef RISCV_HWTIMER_H +#define RISCV_HWTIMER_H + +#include <stdint.h> + +/* Erbium machine timer. mtime and a single mtimecmp live in the CPU + register block; mtime_local_target selects which harts see the timer + interrupt and resets to all harts. + + The mtime rate depends on the reference clock and time_config, which the + boot firmware programs. The default is the 2 MHz timebase used by the + Zephyr and NuttX Erbium ports; define ERBIUM_MTIME_FREQ_HZ when the + firmware selects another rate. */ +#define ERBIUM_MTIME 0x80F40200UL +#define ERBIUM_MTIMECMP 0x80F40208UL + +#ifndef ERBIUM_MTIME_FREQ_HZ +#define ERBIUM_MTIME_FREQ_HZ 2000000UL +#endif + +#define TICKNUM_PER_TIMER (ERBIUM_MTIME_FREQ_HZ / TX_TIMER_TICKS_PER_SECOND) + +int hwtimer_init(void); +int hwtimer_handler(void); + +#endif /* RISCV_HWTIMER_H */ diff --git a/ports/risc-v64/gnu/example_build/erbium/link.lds b/ports/risc-v64/gnu/example_build/erbium/link.lds new file mode 100644 index 00000000..25ba9219 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/link.lds @@ -0,0 +1,92 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5.5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +/* Erbium memory layout. + * + * Address Size Usage + * ───────────── ──────── ────────────────────────────────────── + * 0x0200_0000 4 KiB System registers (SystemConfig at +0x08) + * 0x0200_4000 4 KiB UART0 + * 0x4000_0000 512 B MRAM, reserved for the boot flow + * 0x4000_0200 ~16 MiB MRAM: code, data, stacks and free memory + * 0x80F4_0200 16 B mtime, mtimecmp + * 0xA000_0000 64 MiB PLIC + * + * MRAM is directly readable, writable and executable, so .data runs in + * place and needs no copy from a load address. + */ + +OUTPUT_ARCH( "riscv" ) +ENTRY( _start ) + +MEMORY +{ + MRAM (rwx) : ORIGIN = 0x40000200, LENGTH = 0x00FFFE00 +} + +PHDRS +{ + text PT_LOAD FLAGS(5); /* PF_R | PF_X */ + data PT_LOAD FLAGS(6); /* PF_R | PF_W */ +} + +SECTIONS +{ + /* _start must stay at the load address, so the 4 KiB aligned trap + vector goes in the next output section rather than this one. */ + .text.entry : { + KEEP(*(.text.entry)) + } > MRAM :text + + .text : { + KEEP(*(.text.trap)) + *(.text .text.*) + } > MRAM :text + + .rodata : { + . = ALIGN(16); + *(.srodata .srodata.*) + . = ALIGN(16); + *(.rodata .rodata.*) + } > MRAM :text + + .data : { + . = ALIGN(16); + PROVIDE( __global_pointer$ = . + 0x800 ); + *(.sdata .sdata.*) + . = ALIGN(16); + *(.data .data.*) + } > MRAM :data + + .bss (NOLOAD) : { + . = ALIGN(16); + _bss_start = .; + *(.sbss .sbss.*) + . = ALIGN(16); + *(.bss .bss.*) + *(COMMON) + . = ALIGN(16); + _bss_end = .; + } > MRAM :data + + .stack (NOLOAD) : { + . = ALIGN(16); + _sysstack_start = .; + . += 0x1000; + _sysstack_end = .; + } > MRAM :data + + PROVIDE(_end = .); +} diff --git a/ports/risc-v64/gnu/example_build/erbium/plic.c b/ports/risc-v64/gnu/example_build/erbium/plic.c new file mode 100644 index 00000000..9ca25f1b --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/plic.c @@ -0,0 +1,118 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5.5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +/* PLIC driver for Erbium, machine-mode context of the current hart. */ + +#include "plic.h" +#include <stddef.h> + +static irq_callback callbacks[MAX_CALLBACK_NUM]; + +/* Source 0 is reserved and the six sources fit in the first enable word. */ +static int plic_source_valid(int irqno) +{ + return (irqno > 0) && (irqno < MAX_CALLBACK_NUM); +} + +void plic_irq_enable(int irqno) +{ + volatile uint32_t *reg = (volatile uint32_t *)PLIC_MENABLE(riscv_get_core()); + int intr_enable; + + if (!plic_source_valid(irqno)) + return; + + intr_enable = riscv_mintr_save(); + *reg = *reg | (1u << (unsigned int)irqno); + riscv_mintr_restore(intr_enable); +} + +void plic_irq_disable(int irqno) +{ + volatile uint32_t *reg = (volatile uint32_t *)PLIC_MENABLE(riscv_get_core()); + int intr_enable; + + if (!plic_source_valid(irqno)) + return; + + intr_enable = riscv_mintr_save(); + *reg = *reg & ~(1u << (unsigned int)irqno); + riscv_mintr_restore(intr_enable); +} + +int plic_register_callback(int irqno, irq_callback callback) +{ + if (!plic_source_valid(irqno)) + return -1; + callbacks[irqno] = callback; + return 0; +} + +int plic_unregister_callback(int irqno) +{ + return plic_register_callback(irqno, NULL); +} + +int plic_init(void) +{ + uintptr_t hart = riscv_get_core(); + + for (int i = 0; i < MAX_CALLBACK_NUM; i++) + callbacks[i] = NULL; + + /* Do not depend on the reset state or on a prior boot stage: start + with every source disabled for this context. Drivers enable their + own sources. */ + *(volatile uint32_t *)PLIC_MENABLE(hart) = 0; + + /* The values the silicon hardwires; see plic.h. */ + *(volatile uint32_t *)PLIC_MTHRESHOLD(hart) = 0; + for (int i = 1; i <= PLIC_NUM_SOURCES; i++) + *(volatile uint32_t *)PLIC_PRIORITY(i) = 1; + + return 0; +} + +int plic_claim(void) +{ + return (int)(*(volatile uint32_t *)PLIC_MCLAIM(riscv_get_core())); +} + +void plic_complete(int irqno) +{ + *(volatile uint32_t *)PLIC_MCOMPLETE(riscv_get_core()) = (uint32_t)irqno; +} + +int plic_irq_intr(void) +{ + int ret = -1; + int irqno = plic_claim(); + + /* Nothing pending: another context claimed it first. */ + if (irqno == 0) + return 0; + + if (!plic_source_valid(irqno)) + { + plic_complete(irqno); + return -1; + } + + if (callbacks[irqno] != NULL) + ret = (callbacks[irqno])(irqno); + + plic_complete(irqno); + return ret; +} diff --git a/ports/risc-v64/gnu/example_build/erbium/plic.h b/ports/risc-v64/gnu/example_build/erbium/plic.h new file mode 100644 index 00000000..ffc3a539 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/plic.h @@ -0,0 +1,54 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5.5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +#ifndef RISCV_PLIC_H +#define RISCV_PLIC_H + +#include "csr.h" +#include <stdint.h> + +/* Erbium PLIC, RISC-V PLIC 1.0. Each hart has two contexts, machine mode + first, so hart h uses context 2h. + + The silicon hardwires source priorities to 1 and context thresholds to + 0, so an enabled, pending source always interrupts. The et-platform + simulator implements them as writable registers that reset to 0, which + masks every source, so plic_init() programs the silicon values. There is + no priority control beyond that. + + Sources: 1 MRAM, 2 QSPI, 3 UART, 4 system register, 5 xSPI, 6 GPIO. */ +#define PLIC 0xA0000000UL +#define PLIC_PRIORITY(irqno) (PLIC + (unsigned long)(irqno) * 4UL) +#define PLIC_MENABLE(hart) (PLIC + 0x2000UL + (hart) * 0x100UL) +#define PLIC_MTHRESHOLD(hart) (PLIC + 0x200000UL + (hart) * 0x2000UL) +#define PLIC_MCLAIM(hart) (PLIC + 0x200004UL + (hart) * 0x2000UL) +#define PLIC_MCOMPLETE(hart) PLIC_MCLAIM(hart) + +#define PLIC_NUM_SOURCES 6 +#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_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/erbium/test/run_simulator_tests.sh b/ports/risc-v64/gnu/example_build/erbium/test/run_simulator_tests.sh new file mode 100755 index 00000000..7d54a5f8 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/test/run_simulator_tests.sh @@ -0,0 +1,55 @@ +#!/bin/sh +############################################################################## +# Copyright (c) 2026 Eclipse ThreadX contributors +# +# This program and the accompanying materials are made available under the +# terms of the MIT License which is available at +# https://opensource.org/licenses/MIT. +# +# AI Disclosure: This file was largely AI-generated by Codex (GPT-6-Sol). +# The AI-generated portions may be considered public domain (CC0-1.0) +# and not subject to the project's licence. The human contributor has +# reviewed and verified that the code is correct. +# +# SPDX-License-Identifier: MIT and CC0-1.0 +############################################################################## + +set -eu + +ERBIUM_EMU=${ERBIUM_EMU:?Set ERBIUM_EMU to the erbium_emu executable} +TEST_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +BUILD_DIR=${BUILD_DIR:-"${TEST_DIR}/build-tests"} +RUN_DIR=$(mktemp -d) +trap 'rm -rf "$RUN_DIR"' EXIT HUP INT TERM +failed=0 + +cmake -S "$TEST_DIR" -B "$BUILD_DIR" -GNinja \ + -DCMAKE_TOOLCHAIN_FILE="$TEST_DIR/erbium_gnu.cmake" +cmake --build "$BUILD_DIR" --target test_uart_ticks test_plic_race + +for name in uart_ticks plic_race; do + case "$name" in + uart_ticks) + cycles=20000000 + expected=TEST_UART_NO_LOST_TICKS + ;; + plic_race) + cycles=50000000 + expected=TEST_PLIC_NO_LOST_UPDATE + ;; + esac + "$ERBIUM_EMU" -elf_load "$BUILD_DIR/test_$name" \ + -reset_pc 0x40000200 -minions 1 -single_thread \ + -max_cycles "$cycles" -uart_rx_file /dev/null \ + -uart_tx_file "$RUN_DIR/$name.uart" > "$RUN_DIR/$name.run" 2>&1 || true + if grep -q -F "$expected" "$RUN_DIR/$name.uart" && + grep -q -F 'max cycles reached' "$RUN_DIR/$name.run"; then + printf '%s: PASS\n' "$name" + else + printf '%s: FAIL: ' "$name" + grep -Eo 'TEST_[A-Z_]+' "$RUN_DIR/$name.uart" | tail -1 || true + failed=1 + fi +done + +exit "$failed" diff --git a/ports/risc-v64/gnu/example_build/erbium/test/test_plic_race.c b/ports/risc-v64/gnu/example_build/erbium/test/test_plic_race.c new file mode 100644 index 00000000..d6f2a3b5 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/test/test_plic_race.c @@ -0,0 +1,90 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Codex (GPT-6-Sol). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + ***************************************************************************/ + +#include "tx_api.h" +#include "csr.h" +#include "hwtimer.h" +#include "plic.h" +#include "uart.h" + +#define TEST_TIMER_INTERVAL 8u +#define TEST_REQUIRED_INTERRUPTS 10000u + +static TX_THREAD test_thread; +static UCHAR test_stack[2048]; +volatile unsigned int test_irq_count; +volatile unsigned int test_expected_bit; + +/* Run the kernel with the board's normal interrupt setup. */ +int main(void) +{ + tx_kernel_enter(); + return 0; +} + +/* Compare the PLIC word and the ISR's expected bit atomically. */ +static int test_bit_matches(void) +{ + int enabled = riscv_mintr_save(); + /* MISRA C:2012 Rule 11.4 deviation: the PLIC has a fixed MMIO address. */ + uint32_t actual = *(volatile uint32_t *)PLIC_MENABLE(0u) & (1u << 2); + unsigned int expected = test_expected_bit; + + riscv_mintr_restore(enabled); + return actual == (expected != 0u ? (1u << 2) : 0u); +} + +/* Change source 1 while the timer ISR changes source 2. */ +static void test_entry(ULONG input) +{ + (void)input; + /* MISRA C:2012 Rule 11.4 deviation: the timer has a fixed MMIO address. */ + *(volatile uint64_t *)ERBIUM_MTIMECMP = + *(volatile uint64_t *)ERBIUM_MTIME + TEST_TIMER_INTERVAL; + + while (test_irq_count < TEST_REQUIRED_INTERRUPTS) + { + plic_irq_enable(1); + if (!test_bit_matches()) + { + uart_puts("TEST_PLIC_LOST_UPDATE"); + for (;;) + { + } + } + plic_irq_disable(1); + if (!test_bit_matches()) + { + uart_puts("TEST_PLIC_LOST_UPDATE"); + for (;;) + { + } + } + } + + uart_puts("TEST_PLIC_NO_LOST_UPDATE"); + for (;;) + { + } +} + +/* Start one test thread. */ +void tx_application_define(void *first_unused_memory) +{ + (void)first_unused_memory; + (void)tx_thread_create(&test_thread, "plic test", test_entry, 0u, + test_stack, sizeof(test_stack), 1u, 1u, + TX_NO_TIME_SLICE, TX_AUTO_START); +} diff --git a/ports/risc-v64/gnu/example_build/erbium/test/test_plic_trap.c b/ports/risc-v64/gnu/example_build/erbium/test/test_plic_trap.c new file mode 100644 index 00000000..8dabb42b --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/test/test_plic_trap.c @@ -0,0 +1,55 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Codex (GPT-6-Sol). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + ***************************************************************************/ + +#include "tx_api.h" +#include "hwtimer.h" +#include "plic.h" +#include "uart.h" +#include <stdint.h> + +#define TEST_TIMER_INTERVAL 8u +#define TEST_MCAUSE_TIMER (((uintptr_t)1u << 63) | 7u) + +extern volatile unsigned int test_irq_count; +extern volatile unsigned int test_expected_bit; + +/* Toggle source 2 in timer context to preempt source 1 updates. */ +void trap_handler(uintptr_t mcause, uintptr_t mepc, uintptr_t mtval) +{ + (void)mepc; + (void)mtval; + if (mcause != TEST_MCAUSE_TIMER) + { + uart_puts("TEST_PLIC_UNEXPECTED_TRAP"); + for (;;) + { + } + } + + /* MISRA C:2012 Rule 11.4 deviation: the timer has a fixed MMIO address. */ + *(volatile uint64_t *)ERBIUM_MTIMECMP = + *(volatile uint64_t *)ERBIUM_MTIME + TEST_TIMER_INTERVAL; + if (test_expected_bit == 0u) + { + plic_irq_enable(2); + test_expected_bit = 1u; + } + else + { + plic_irq_disable(2); + test_expected_bit = 0u; + } + test_irq_count++; +} diff --git a/ports/risc-v64/gnu/example_build/erbium/test/test_uart_ticks.c b/ports/risc-v64/gnu/example_build/erbium/test/test_uart_ticks.c new file mode 100644 index 00000000..188af2e7 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/test/test_uart_ticks.c @@ -0,0 +1,81 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Codex (GPT-6-Sol). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + ***************************************************************************/ + +#include "tx_api.h" +#include "uart.h" +#include "hwtimer.h" + +#define TEST_TEXT_SIZE 160000u + +static TX_THREAD test_thread; +static UCHAR test_stack[2048]; +static char test_text[TEST_TEXT_SIZE + 1u]; + +/* Run the kernel with the board's normal interrupt setup. */ +int main(void) +{ + tx_kernel_enter(); + return 0; +} + +/* Print long enough to span several hardware timer intervals. */ +static void test_entry(ULONG input) +{ + uint64_t start; + uint64_t elapsed; + ULONG ticks_before; + ULONG ticks_after; + size_t i; + + (void)input; + for (i = 0u; i < TEST_TEXT_SIZE; i++) + { + test_text[i] = 'X'; + } + test_text[TEST_TEXT_SIZE] = '\0'; + + /* MISRA C:2012 Rule 11.4 deviation: the timer has a fixed MMIO address. */ + start = *(volatile uint64_t *)ERBIUM_MTIME; + ticks_before = tx_time_get(); + (void)uart_puts(test_text); + elapsed = *(volatile uint64_t *)ERBIUM_MTIME - start; + ticks_after = tx_time_get(); + + if (elapsed <= (2u * TICKNUM_PER_TIMER)) + { + uart_puts("TEST_UART_TOO_SHORT"); + } + else if ((ticks_after - ticks_before) < 2u) + { + uart_puts("TEST_UART_LOST_TICKS"); + } + else + { + uart_puts("TEST_UART_NO_LOST_TICKS"); + } + + for (;;) + { + } +} + +/* Start one test thread. */ +void tx_application_define(void *first_unused_memory) +{ + (void)first_unused_memory; + (void)tx_thread_create(&test_thread, "uart test", test_entry, 0u, + test_stack, sizeof(test_stack), 1u, 1u, + TX_NO_TIME_SLICE, TX_AUTO_START); +} diff --git a/ports/risc-v64/gnu/example_build/erbium/trap.c b/ports/risc-v64/gnu/example_build/erbium/trap.c new file mode 100644 index 00000000..d839e6ea --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/trap.c @@ -0,0 +1,78 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5.5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +/* Machine-mode trap dispatcher for the Erbium example. */ + +#include "csr.h" +#include "uart.h" +#include "hwtimer.h" +#include "plic.h" +#include <stdint.h> +#include <tx_api.h> + +#define MCAUSE_INT_BIT ((uintptr_t)1 << (__riscv_xlen - 1)) +#define MCAUSE_M_TIMER (MCAUSE_INT_BIT | 7u) +#define MCAUSE_M_EXTERNAL (MCAUSE_INT_BIT | 11u) + +extern void _tx_timer_interrupt(void); + +static void print_hex(uintptr_t val) +{ + const char digits[] = "0123456789ABCDEF"; + + uart_putc('0'); + uart_putc('x'); + for (int i = (int)(sizeof(uintptr_t) * 2) - 1; i >= 0; i--) + uart_putc(digits[(val >> (i * 4)) & 0xFu]); + uart_putc('\r'); + uart_putc('\n'); +} + +static void trap_halt(const char *reason, uintptr_t mcause, uintptr_t mepc, uintptr_t mtval) +{ + uart_puts(reason); + uart_puts("mcause:"); + print_hex(mcause); + uart_puts("mepc:"); + print_hex(mepc); + uart_puts("mtval:"); + print_hex(mtval); + + /* Spin rather than WFI, as everywhere else in this example. */ + while (1) + ; +} + +void trap_handler(uintptr_t mcause, uintptr_t mepc, uintptr_t mtval) +{ + if (mcause == MCAUSE_M_TIMER) + { + hwtimer_handler(); + _tx_timer_interrupt(); + } + else if (mcause == MCAUSE_M_EXTERNAL) + { + if (plic_irq_intr() != 0) + trap_halt("[INTERRUPT]: external interrupt not handled, halting", mcause, mepc, mtval); + } + else if ((mcause & MCAUSE_INT_BIT) != 0u) + { + trap_halt("[INTERRUPT]: unexpected interrupt, halting", mcause, mepc, mtval); + } + else + { + trap_halt("[EXCEPTION]: unhandled exception, halting", mcause, mepc, mtval); + } +} diff --git a/ports/risc-v64/gnu/example_build/erbium/tx_initialize_low_level.S b/ports/risc-v64/gnu/example_build/erbium/tx_initialize_low_level.S new file mode 100644 index 00000000..fe0653ae --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/tx_initialize_low_level.S @@ -0,0 +1,142 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5.5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +#include "csr.h" +#include "tx_port.h" + +/* Erbium ignores mtvec base bits 11:1, so the trap vector must be 4 KiB + aligned. */ + .section .text.trap, "ax", @progbits + .balign 4096 +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* trap_entry RISC-V64/GNU */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function is the machine-mode trap vector (direct mode). It */ +/* saves the interrupted context, calls trap_handler with mcause, */ +/* mepc and mtval, and leaves through the ThreadX context restore. */ +/* */ +/* INPUT */ +/* */ +/* None */ +/* */ +/* OUTPUT */ +/* */ +/* None */ +/* */ +/* CALLS */ +/* */ +/* _tx_thread_context_save Save interrupted context */ +/* trap_handler Dispatch the trap */ +/* _tx_thread_context_restore Restore or schedule a thread */ +/* */ +/* CALLED BY */ +/* */ +/* Hardware trap */ +/* */ +/**************************************************************************/ + .global trap_entry + .extern trap_handler + .extern _tx_thread_context_save + .extern _tx_thread_context_restore +trap_entry: + addi sp, sp, -TX_RISCV_TRAP_FRAME_SIZE // Interrupt frame from the port contract + sd x1, 28*8(sp) // Save RA before the call below overwrites it + + call _tx_thread_context_save + + csrr a0, mcause + csrr a1, mepc + csrr a2, mtval + addi sp, sp, -16 // 16 bytes keep sp 16-byte aligned at the call + sd ra, 0(sp) + call trap_handler + ld ra, 0(sp) + addi sp, sp, 16 + + call _tx_thread_context_restore + /* _tx_thread_context_restore does not return. */ +_trap_entry_hang: + j _trap_entry_hang + + + .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. */ +/* */ +/* INPUT */ +/* */ +/* None */ +/* */ +/* OUTPUT */ +/* */ +/* None */ +/* */ +/* CALLS */ +/* */ +/* board_init Initialize the peripherals */ +/* */ +/* CALLED BY */ +/* */ +/* _tx_initialize_kernel_enter ThreadX entry function */ +/* */ +/**************************************************************************/ +/* VOID _tx_initialize_low_level(VOID) +{ */ + .global _tx_initialize_low_level // Strong definition: must beat the port's weak default + .extern _end + .extern board_init + .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 pointer + + la t0, _end // Pickup first free address + la t1, _tx_initialize_unused_memory + sd t0, 0(t1) // Save unused memory address + + li t0, MSTATUS_MIE + csrrc zero, mstatus, t0 // Keep interrupts off until the scheduler runs + li t0, (MSTATUS_MPP_M | MSTATUS_MPIE) + csrrs zero, mstatus, t0 // mret stays in M-mode with interrupts enabled + li t0, (MIE_MTIE | MIE_MEIE) + csrrs zero, mie, t0 // Enable timer and external interrupts + + addi sp, sp, -16 // 16 bytes keep sp 16-byte aligned at the call + sd ra, 0(sp) + call board_init + ld ra, 0(sp) + addi sp, sp, 16 + + la t0, trap_entry + csrw mtvec, t0 // Direct mode: trap_entry is 4 KiB aligned + ret +/* } */ diff --git a/ports/risc-v64/gnu/example_build/erbium/uart.c b/ports/risc-v64/gnu/example_build/erbium/uart.c new file mode 100644 index 00000000..1b8f2dfc --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/uart.c @@ -0,0 +1,109 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5.5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +/* Polling console driver for the Erbium UART (original Shakti integration). + * + * Offset Name Access Description + * ────── ──────────── ────── ───────────────────────────────────── + * 0x00 BaudReg R/W Divisor: clock / (16 * baud) + * 0x08 TxReg W Transmit data + * 0x10 RxReg R Receive data + * 0x18 StatusReg R FIFO and error status + * 0x20 DelayReg R/W Transmit delay + * 0x28 ControlReg R/W Character size, parity, stop bits + * 0x30 InterruptEn R/W Interrupt enables + * 0x40 Rx_Threshold R/W Receive FIFO threshold + * + * All registers are accessed as 32-bit words. The transmit and receive + * FIFOs hold 16 entries each. + */ + +#include "uart.h" +#include "csr.h" +#include <stdint.h> + +#define UART_DIVISOR (ERBIUM_UART_CLOCK_HZ / (16UL * ERBIUM_UART_BAUD)) + +#if (UART_DIVISOR == 0) || (UART_DIVISOR > 0xFFFF) +#error "The Erbium UART baud divisor must be in the range 1..65535." +#endif + +/* SystemConfig in the system register block gates the UART. */ +#define SYSREG_SYSTEM_CONFIG 0x02000008UL +#define SYSTEM_CONFIG_UART_EN (1u << 6) + +#define UART_BAUD 0x00 +#define UART_TX 0x08 +#define UART_STATUS 0x18 +#define UART_DELAY 0x20 +#define UART_CONTROL 0x28 +#define UART_IEN 0x30 +#define UART_RX_THRESHOLD 0x40 + +#define STATUS_TX_FULL (1u << 1) + +#define CONTROL_CHARSIZE_SHIFT 5 /* bits 10:5; stop bits and parity 0 */ + +#define REG32(addr) (*(volatile uint32_t *)(addr)) +#define UART_REG(off) REG32(UART0 + (off)) + +int uart_init(void) +{ + /* Ungate the UART. Read-modify-write keeps the watchdog and the + other peripheral enables as the boot flow left them. */ + REG32(SYSREG_SYSTEM_CONFIG) = REG32(SYSREG_SYSTEM_CONFIG) | SYSTEM_CONFIG_UART_EN; + + /* Polling only: no UART interrupts. */ + UART_REG(UART_IEN) = 0; + + /* 8 data bits, no parity, one stop bit. */ + UART_REG(UART_BAUD) = (uint32_t)UART_DIVISOR; + UART_REG(UART_CONTROL) = 8u << CONTROL_CHARSIZE_SHIFT; + UART_REG(UART_DELAY) = 0; + UART_REG(UART_RX_THRESHOLD) = 0; + + puts("[UART0] : Erbium UART initialized"); + return 0; +} + +int uart_putc(int ch) +{ + int intr_enable; + + for (;;) + { + while ((UART_REG(UART_STATUS) & STATUS_TX_FULL) != 0u) + ; + + intr_enable = riscv_mintr_save(); + if ((UART_REG(UART_STATUS) & STATUS_TX_FULL) == 0u) + { + UART_REG(UART_TX) = (uint32_t)(ch & 0xFF); + riscv_mintr_restore(intr_enable); + return 1; + } + riscv_mintr_restore(intr_enable); + } +} + +int uart_puts(const char *str) +{ + int i; + for (i = 0; str[i] != 0; i++) + uart_putc(str[i]); + uart_putc('\r'); + uart_putc('\n'); + return i; +} diff --git a/ports/risc-v64/gnu/example_build/erbium/uart.h b/ports/risc-v64/gnu/example_build/erbium/uart.h new file mode 100644 index 00000000..74a6c1d0 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/uart.h @@ -0,0 +1,40 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5.5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +#ifndef RISCV_UART_H +#define RISCV_UART_H + +/* Erbium UART0: the original Shakti UART integration, with 32-bit registers + at eight-byte intervals. It is PLIC source 3. */ +#define UART0 0x02004000UL +#define UART0_IRQ 3 + +/* The UART input clock and the console rate. The defaults match the + public et-platform simulator; define these on the command line when the + boot firmware programs a different peripheral clock. */ +#ifndef ERBIUM_UART_CLOCK_HZ +#define ERBIUM_UART_CLOCK_HZ 400000000UL +#endif +#ifndef ERBIUM_UART_BAUD +#define ERBIUM_UART_BAUD 115200UL +#endif + +#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/example_build/qemu_virt/.gitignore b/ports/risc-v64/gnu/example_build/qemu_virt/.gitignore new file mode 100644 index 00000000..d90c0dfd --- /dev/null +++ b/ports/risc-v64/gnu/example_build/qemu_virt/.gitignore @@ -0,0 +1,4 @@ +# Ignore generated QEMU test files. +kernel.elf +qemu-riscv64.log +test_cmds.gdb diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/CMakeLists.txt b/ports/risc-v64/gnu/example_build/qemu_virt/CMakeLists.txt new file mode 100644 index 00000000..1928fbbd --- /dev/null +++ b/ports/risc-v64/gnu/example_build/qemu_virt/CMakeLists.txt @@ -0,0 +1,51 @@ +set(QEMU_DEMO_DIR ${CMAKE_CURRENT_LIST_DIR}) + +add_executable(kernel.elf EXCLUDE_FROM_ALL + ${QEMU_DEMO_DIR}/demo_threadx.c + ${QEMU_DEMO_DIR}/entry.S + ${QEMU_DEMO_DIR}/uart.c + ${QEMU_DEMO_DIR}/plic.c + ${QEMU_DEMO_DIR}/hwtimer.c + ${QEMU_DEMO_DIR}/trap.c + ${QEMU_DEMO_DIR}/board.c + ${QEMU_DEMO_DIR}/tx_initialize_low_level.S +) + +target_link_libraries(kernel.elf PRIVATE threadx gcc) + +target_include_directories(kernel.elf PRIVATE + ${CMAKE_SOURCE_DIR}/common/inc + ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc + ${QEMU_DEMO_DIR} +) + +target_link_options(kernel.elf PRIVATE + -T${QEMU_DEMO_DIR}/link.lds + -nostartfiles + -Wl,-Map=kernel.map +) + +find_package(Python3 COMPONENTS Interpreter) +find_program(RISCV64_GDB NAMES riscv64-unknown-elf-gdb gdb-multiarch) +find_program(RISCV64_QEMU NAMES qemu-system-riscv64) +if(Python3_FOUND AND RISCV64_GDB AND RISCV64_QEMU) + add_custom_target(check-functional-riscv64 + COMMAND ${Python3_EXECUTABLE} + ${QEMU_DEMO_DIR}/test/threadx_test_tx_gnu_riscv64_qemu.py + --elf $<TARGET_FILE:kernel.elf> + --qemu ${RISCV64_QEMU} + --gdb ${RISCV64_GDB} + DEPENDS kernel.elf + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Running RISC-V64 QEMU/GDB functional test runner..." + ) +elseif(NOT Python3_FOUND) + message(STATUS + "Python3 not found; check-functional-riscv64 target unavailable.") +elseif(NOT RISCV64_GDB) + message(STATUS + "RISC-V GDB not found; check-functional-riscv64 target unavailable.") +else() + message(STATUS + "qemu-system-riscv64 not found; check-functional-riscv64 target unavailable.") +endif() diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/board.c b/ports/risc-v64/gnu/example_build/qemu_virt/board.c index 805a1ac8..160eb592 100644 --- a/ports/risc-v64/gnu/example_build/qemu_virt/board.c +++ b/ports/risc-v64/gnu/example_build/qemu_virt/board.c @@ -15,15 +15,13 @@ #include <stdint.h> #include <stddef.h> -void *memset(const void *des, int c,size_t n) +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; + unsigned char *target = des; + + for (size_t i = 0; i < n; i++) + target[i] = (unsigned char)c; + return des; } diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/build_libthreadx.sh b/ports/risc-v64/gnu/example_build/qemu_virt/build_libthreadx.sh index 0568e7a8..f4a301d7 100755 --- a/ports/risc-v64/gnu/example_build/qemu_virt/build_libthreadx.sh +++ b/ports/risc-v64/gnu/example_build/qemu_virt/build_libthreadx.sh @@ -10,26 +10,14 @@ # SPDX-License-Identifier: MIT ############################################################################## -printf "y\n" | rm -rf ../../../../../build/ -rm -f kernel.elf +set -eu -pushd ../../../../../ -cmake -Bbuild -GNinja -DCMAKE_TOOLCHAIN_FILE=cmake/riscv64_gnu.cmake . -cmake --build ./build/ -popd +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +REPO_ROOT=$(CDPATH= cd -- "${SCRIPT_DIR}/../../../../../" && pwd) +BUILD_DIR=${BUILD_DIR:-"${REPO_ROOT}/build/riscv64-qemu"} -riscv64-unknown-elf-gcc \ - -march=rv64gc -mabi=lp64d \ - -mcmodel=medany -O0 -g3 -Wall \ - -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 +cmake -S "${REPO_ROOT}" -B "${BUILD_DIR}" -GNinja \ + -DCMAKE_TOOLCHAIN_FILE="${REPO_ROOT}/cmake/riscv64_gnu.cmake" +cmake --build "${BUILD_DIR}" --target kernel.elf - -qemu-system-riscv64 -nographic -smp 1 -bios none -m 128M -machine virt -kernel kernel.elf +printf 'Built %s\n' "${BUILD_DIR}/ports/risc-v64/gnu/example_build/qemu_virt/kernel.elf" diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/demo_threadx.c b/ports/risc-v64/gnu/example_build/qemu_virt/demo_threadx.c index d3195019..82992d25 100644 --- a/ports/risc-v64/gnu/example_build/qemu_virt/demo_threadx.c +++ b/ports/risc-v64/gnu/example_build/qemu_virt/demo_threadx.c @@ -25,6 +25,7 @@ #define DEMO_BLOCK_POOL_SIZE 100 #define DEMO_QUEUE_SIZE 100 +float fpu_test_val = 0.0f; /* Define the ThreadX object control blocks... */ @@ -357,6 +358,8 @@ UINT status; if (status != TX_SUCCESS) break; + /* FPU Test */ + fpu_test_val += 1.1f; /* Get the mutex again with suspension. This shows that an owning thread may retrieve the mutex it owns multiple times. */ diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/entry.S b/ports/risc-v64/gnu/example_build/qemu_virt/entry.S index 2b68310d..5268d68e 100644 --- a/ports/risc-v64/gnu/example_build/qemu_virt/entry.S +++ b/ports/risc-v64/gnu/example_build/qemu_virt/entry.S @@ -10,7 +10,7 @@ /***************************************************************************/ -.section .text +.section .text.entry .align 4 .global _start .extern main @@ -22,7 +22,10 @@ _start: bne t0, zero, 1f li x1, 0 li x2, 0 - li x3, 0 +.option push +.option norelax + la gp, __global_pointer$ /* x3 = gp; norelax keeps this load absolute */ +.option pop li x4, 0 li x5, 0 li x6, 0 diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/hwtimer.c b/ports/risc-v64/gnu/example_build/qemu_virt/hwtimer.c index 122e71a1..5cd21f1b 100644 --- a/ports/risc-v64/gnu/example_build/qemu_virt/hwtimer.c +++ b/ports/risc-v64/gnu/example_build/qemu_virt/hwtimer.c @@ -9,7 +9,7 @@ /* SPDX-License-Identifier: MIT */ /***************************************************************************/ -#include "tx_port.h" +#include "tx_api.h" #include "csr.h" #include "hwtimer.h" @@ -17,20 +17,31 @@ #define CLINT_TIME (CLINT+0xBFF8) #define CLINT_TIMECMP(hart_id) (CLINT+0x4000+8*(hart_id)) +/* RV64: naturally aligned 64-bit MMIO accesses are single loads/stores; + volatile keeps the compiler from caching or reordering them. */ +#define MTIME (*(volatile uint64_t *)CLINT_TIME) +#define MTIMECMP(hart) (*(volatile uint64_t *)CLINT_TIMECMP(hart)) 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 hart = riscv_get_core(); + + MTIMECMP(hart) = MTIME + 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; -} + int hart = riscv_get_core(); + + /* Advance from the previous compare value, so trap latency + does not accumulate as tick drift. */ + uint64_t next = MTIMECMP(hart) + TICKNUM_PER_TIMER; + uint64_t now = MTIME; + if (next <= now) + next = now + TICKNUM_PER_TIMER; + + MTIMECMP(hart) = next; + return 0; +} diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/hwtimer.h b/ports/risc-v64/gnu/example_build/qemu_virt/hwtimer.h index f65c75e1..5cdc4d66 100644 --- a/ports/risc-v64/gnu/example_build/qemu_virt/hwtimer.h +++ b/ports/risc-v64/gnu/example_build/qemu_virt/hwtimer.h @@ -14,8 +14,8 @@ #include <stdint.h> -#define TICKNUM_PER_SECOND 10000000 -#define TICKNUM_PER_TIMER (TICKNUM_PER_SECOND / 10) +#define TICKNUM_PER_SECOND 10000000UL +#define TICKNUM_PER_TIMER (TICKNUM_PER_SECOND / TX_TIMER_TICKS_PER_SECOND) int hwtimer_init(void); diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/link.lds b/ports/risc-v64/gnu/example_build/qemu_virt/link.lds index d0d8a6bc..63803acb 100644 --- a/ports/risc-v64/gnu/example_build/qemu_virt/link.lds +++ b/ports/risc-v64/gnu/example_build/qemu_virt/link.lds @@ -1,6 +1,12 @@ OUTPUT_ARCH( "riscv" ) ENTRY( _start ) +PHDRS +{ + text PT_LOAD FLAGS(5); + data PT_LOAD FLAGS(6); +} + SECTIONS { /* @@ -10,43 +16,44 @@ SECTIONS . = 0x80000000; .text : { + KEEP(*(.text.entry)) *(.text .text.*) . = ALIGN(0x1000); PROVIDE(etext = .); - } + } :text .rodata : { . = ALIGN(16); *(.srodata .srodata.*) /* do not need to distinguish this from .rodata */ . = ALIGN(16); *(.rodata .rodata.*) - } + } :text + . = ALIGN(0x1000); .data : { . = ALIGN(16); + PROVIDE( __global_pointer$ = . + 0x800 ); *(.sdata .sdata.*) /* do not need to distinguish this from .data */ . = ALIGN(16); *(.data .data.*) - } + } :data - .bss : { + .bss (NOLOAD) : { . = ALIGN(16); _bss_start = .; *(.sbss .sbss.*) /* do not need to distinguish this from .bss */ . = ALIGN(16); *(.bss .bss.*) _bss_end = .; - } + } :data - .stack : { + .stack (NOLOAD) : { . = ALIGN(4096); _sysstack_start = .; . += 0x1000; -#ifdef __riscv_vector - . += 0x4000; -#endif + . += 0x4000; /* Vector headroom */ _sysstack_end = .; - } + } :data PROVIDE(_end = .); } diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv64_qemu.py b/ports/risc-v64/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv64_qemu.py index 861f50e6..e86d2e3f 100644 --- a/ports/risc-v64/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv64_qemu.py +++ b/ports/risc-v64/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv64_qemu.py @@ -18,19 +18,16 @@ import select def print_content(content): """Prints content using os.write to handle non-blocking stdout robustly.""" - try: - msg = f"{content}\n".encode('utf-8') - total_len = len(msg) - written = 0 - fd = sys.stdout.fileno() - while written < total_len: - try: - n = os.write(fd, msg[written:]) - written += n - except BlockingIOError: - select.select([], [fd], []) - except Exception: - pass + msg = f"{content}\n".encode('utf-8') + total_len = len(msg) + written = 0 + fd = sys.stdout.fileno() + while written < total_len: + try: + n = os.write(fd, msg[written:]) + written += n + except BlockingIOError: + select.select([], [fd], []) def get_free_port(): """Finds a free TCP port.""" @@ -38,7 +35,7 @@ def get_free_port(): s.bind(('', 0)) return s.getsockname()[1] -def run_qemu_test(elf_path, qemu_bin, gdb_bin): +def run_qemu_test(elf_path, qemu_bin, gdb_bin, cpu): """ Runs a test cycle using QEMU and GDB. """ @@ -51,6 +48,8 @@ def run_qemu_test(elf_path, qemu_bin, gdb_bin): print(f"Using GDB port: {gdb_port}") # 1. Start QEMU in the background + GDB_TIMEOUT_S = 120 + qemu_log_path = os.path.abspath("qemu-riscv64.log") qemu_cmd = [ qemu_bin, "-M", "virt", @@ -61,18 +60,33 @@ def run_qemu_test(elf_path, qemu_bin, gdb_bin): "-monitor", "none", # Disable monitor "-serial", "stdio" # Redirect serial output to stdio ] + if cpu: + qemu_cmd.extend(["-cpu", cpu]) print(f"Starting QEMU: {' '.join(qemu_cmd)}") - qemu_process = subprocess.Popen( - qemu_cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True + qemu_log = open(qemu_log_path, "w", encoding="utf-8") + try: + qemu_process = subprocess.Popen( + qemu_cmd, + stdout=qemu_log, + stderr=subprocess.STDOUT, + text=True + ) + except OSError as e: + qemu_log.close() + print_content(f"FAILURE: QEMU did not start: {e}") + return False + + print_content( + f"QEMU PID {qemu_process.pid}; expected runtime under 30s; " + f"wall timeout {GDB_TIMEOUT_S}s; log {qemu_log_path}" ) if qemu_process.poll() is not None: - print("QEMU failed to start.") - print(qemu_process.stderr.read()) + qemu_log.close() + print_content("FAILURE: QEMU exited during startup.") + with open(qemu_log_path, encoding="utf-8") as failed_log: + print_content(failed_log.read()) return False # 2. Create a GDB command file @@ -88,6 +102,13 @@ break thread_0_entry break thread_6_and_7_entry break _tx_timer_interrupt +# The timer interrupt fires at TX_TIMER_TICKS_PER_SECOND Hz. With every +# breakpoint armed at once a bare `continue` is a race: a tick can steal +# the stop that the script expects on an application breakpoint. Keep the +# timer breakpoint disabled until the timer phase, so every stop below is +# unambiguous at any tick rate. +disable 4 + # Execute to Application Definition continue @@ -95,17 +116,54 @@ continue continue print/x $mstatus -# Verify FPU Logic and Register State exercised by thread_6/7 +# Verify FPU Logic and Register State exercised by thread_6/7. +# Only breakpoint 3 can stop us here, so this is thread_6_and_7_entry on +# its first pass, before the thread has touched fpu_test_val. continue -finish -step -step -step + +# fpu_test_val exists only in a demo that builds the FPU exercise. GDB +# aborts a sourced command file on an unknown symbol, which would skip +# every later check without saying so, so probe for the symbol first. A +# missing symbol still FAILS the FPU check below; it is not tolerated. +python gdb.execute("set $fpu_sym = %d" % (1 if gdb.lookup_global_symbol("fpu_test_val") else 0)) + +set $fpu_iter = 0 +if $fpu_sym != 0 + set $fpu_before = fpu_test_val + # Wait for the FPU add (fpu_test_val += 1.1f) to retire, instead of + # stepping a fixed number of instructions past an assumed stop. Each + # further stop is another pass through thread_6_and_7_entry, so the cap + # bounds thread passes rather than ticks, and exhausting it fails below. + while fpu_test_val == $fpu_before && $fpu_iter < 64 + set $fpu_iter = $fpu_iter + 1 + continue + end +end + print/x $mstatus info registers float -print fpu_test_val -# Await Timer Interrupt +if $fpu_sym == 0 + printf "FPU_VERIFIED_FAIL_NO_SYMBOL\\n" +else + print fpu_test_val + # Assert the float unit produced an exact multiple of 1.1f. This does + # not assume how many passes ran, only that every pass added 1.1f. + set $fpu_n = (int)((fpu_test_val / 1.1) + 0.5) + set $fpu_err = fpu_test_val - ($fpu_n * 1.1) + if $fpu_n >= 1 && $fpu_err > -0.01 && $fpu_err < 0.01 + printf "FPU_VERIFIED_OK value=%f adds=%d\\n", fpu_test_val, $fpu_n + else + printf "FPU_VERIFIED_FAIL value=%f adds=%d passes=%d\\n", fpu_test_val, $fpu_n, $fpu_iter + end +end + +# Await Timer Interrupt. Arm only the timer breakpoint, so this stop is +# _tx_timer_interrupt and $ra below really is the ISR return address. +disable 1 +disable 2 +disable 3 +enable 4 continue print "Hit Timer Interrupt" @@ -129,11 +187,21 @@ commands continue end -# Set Breakpoint at ISR Return Address +# Set Breakpoint at ISR Return Address. Disable the timer breakpoint for +# this window, so the next tick cannot stop us short of the return. set $ret_addr = $ra +disable 4 tbreak *$ret_addr continue +# Verify that the live CSR and saved frame still contain the interrupted PC. +set $frame_pc = ((unsigned long *)_tx_thread_current_ptr->tx_thread_stack_ptr)[30] +if $mepc == $saved_pc && $frame_pc == $saved_pc + printf "MEPC_VERIFIED_OK mepc=0x%lx frame_pc=0x%lx\\n", $mepc, $frame_pc +else + printf "MEPC_VERIFIED_FAIL saved=0x%lx mepc=0x%lx frame_pc=0x%lx\\n", $saved_pc, $mepc, $frame_pc +end + # Verify Time-Slice Handler Was Called if $ts_handler_called == 1 print "SUCCESS: Time-slice handler called." @@ -152,19 +220,55 @@ else end # Verify Preemption Logic (Thread Priority) -set $curr_ptr = _tx_thread_current_ptr -set $exec_ptr = _tx_thread_execute_ptr -if $curr_ptr != 0 && $exec_ptr != 0 - set $curr_prio = $curr_ptr->tx_thread_priority - set $exec_prio = $exec_ptr->tx_thread_priority - printf "PREEMPT_CHECK current_prio=%d execute_prio=%d\\n", $curr_prio, $exec_prio - if $exec_prio < $curr_prio - printf "PREEMPT_VERIFIED_OK\\n" +# +# We are now stopped at the return address from _tx_timer_interrupt, +# after _tx_thread_time_slice and the timer expiration processing have had +# a chance to update _tx_thread_execute_ptr but before trap_handler +# returns into _tx_thread_context_restore. +# +# Whether any ONE tick lands inside a preemption window depends on +# scheduling phase, not on the tick rate, so poll consecutive ticks at +# this same sample point until the priority relation holds. Each retry +# re-arms the time slice and re-derives the return address from $ra, so +# the sample point is identical every iteration. The cap bounds ticks and +# fails loudly on exhaustion; it never passes the check. +set $preempt_ok = 0 +set $preempt_null = 0 +set $preempt_iter = 0 +while $preempt_iter < 250 && $preempt_ok == 0 + set $curr_ptr = _tx_thread_current_ptr + set $exec_ptr = _tx_thread_execute_ptr + if $curr_ptr == 0 || $exec_ptr == 0 + set $preempt_null = $preempt_null + 1 else - printf "PREEMPT_VERIFIED_FAIL_NOT_OBSERVED\\n" + set $curr_prio = $curr_ptr->tx_thread_priority + set $exec_prio = $exec_ptr->tx_thread_priority + if $exec_prio < $curr_prio + printf "PREEMPT_CHECK current_prio=%d execute_prio=%d\\n", $curr_prio, $exec_prio + set $preempt_ok = 1 + end + end + if $preempt_ok == 0 + set $preempt_iter = $preempt_iter + 1 + enable 4 + continue + set _tx_timer_time_slice = 1 + set _tx_timer_expired_time_slice = 0 + set $ret_addr = $ra + disable 4 + tbreak *$ret_addr + continue end +end + +if $preempt_ok == 1 + printf "PREEMPT_VERIFIED_OK ticks=%d\\n", $preempt_iter else - printf "PREEMPT_VERIFIED_FAIL_NULL\\n" + if $preempt_null == $preempt_iter + printf "PREEMPT_VERIFIED_FAIL_NULL\\n" + else + printf "PREEMPT_VERIFIED_FAIL_NOT_OBSERVED ticks=%d\\n", $preempt_iter + end end quit @@ -183,8 +287,11 @@ quit print_content(f"Starting GDB: {' '.join(gdb_cmd)}") - GDB_TIMEOUT_S = 30 - + # Cap the GDB session so a wedged batch script (e.g. a `continue` that + # never hits its breakpoint) cannot hang CI. The preemption poll may + # step through up to 250 ticks; at the port's 100 Hz tick that is 2.5 s + # of guest time, but each tick costs two gdbstub round trips, so allow + # 120 s of wall clock before declaring the session wedged. try: gdb_process = subprocess.run( gdb_cmd, @@ -228,17 +335,32 @@ quit except subprocess.TimeoutExpired: print_content("QEMU did not terminate gracefully, killing it forcefully.") qemu_process.kill() + qemu_process.wait() + qemu_log.close() # Verify results stdout = gdb_process.stdout + gdb_succeeded = gdb_process.returncode == 0 timer_hit = "Breakpoint 4, _tx_timer_interrupt" in stdout fpu_verified = False + mepc_verified = "MEPC_VERIFIED_OK" in stdout preemption_verified = "PREEMPT_VERIFIED_OK" in stdout + time_slice_verified = "SUCCESS: Time-slice handler called." in stdout + system_timer_verified = "SUCCESS: System timer incremented." in stdout + + if not gdb_succeeded: + print_content(f"FAILURE: GDB exited with status {gdb_process.returncode}.") if "Breakpoint 3, thread_6_and_7_entry" in stdout: - if "1.10" in stdout or "fpu_test_val" in stdout: + if "FPU_VERIFIED_OK" in stdout: print_content("SUCCESS: FPU instructions executed and registers inspected.") fpu_verified = True + elif "FPU_VERIFIED_FAIL_NO_SYMBOL" in stdout: + print_content("FAILURE: Hit thread, but this demo defines no " + "fpu_test_val, so the FPU result is unverifiable.") + elif "FPU_VERIFIED_FAIL" in stdout: + print_content("FAILURE: Hit thread, but fpu_test_val is not an exact " + "multiple of 1.1f. Output does not contain expected value.") else: print_content("FAILURE: Hit thread, but failed to inspect FPU. " "Output does not contain expected value.") @@ -248,6 +370,21 @@ quit else: print_content("FAILURE: Did not hit timer interrupt.") + if mepc_verified: + print_content("SUCCESS: MEPC and the saved frame PC are unchanged.") + else: + print_content("FAILURE: MEPC or the saved frame PC changed in the ISR.") + + if time_slice_verified: + print_content("SUCCESS: Time-slice handler verified.") + else: + print_content("FAILURE: Time-slice handler was not called.") + + if system_timer_verified: + print_content("SUCCESS: System timer increment verified.") + else: + print_content("FAILURE: System timer did not increment.") + if preemption_verified: print_content("SUCCESS: Preemption verified (higher-priority thread " "preempted a lower-priority one).") @@ -263,7 +400,9 @@ quit else: print_content("FAILURE: Preemption check did not run to completion.") - if timer_hit and fpu_verified and preemption_verified: + if (gdb_succeeded and timer_hit and fpu_verified and mepc_verified and + time_slice_verified and system_timer_verified and + preemption_verified): return True else: return False @@ -275,10 +414,11 @@ if __name__ == "__main__": help="Path to QEMU binary") parser.add_argument("--gdb", default="riscv64-unknown-elf-gdb", help="Path to GDB binary") + parser.add_argument("--cpu", help="QEMU CPU model and properties") args = parser.parse_args() - success = run_qemu_test(args.elf, args.qemu, args.gdb) + success = run_qemu_test(args.elf, args.qemu, args.gdb, args.cpu) if success: sys.exit(0) diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/tx_initialize_low_level.S b/ports/risc-v64/gnu/example_build/qemu_virt/tx_initialize_low_level.S index a9b2fda7..9b1d8758 100644 --- a/ports/risc-v64/gnu/example_build/qemu_virt/tx_initialize_low_level.S +++ b/ports/risc-v64/gnu/example_build/qemu_virt/tx_initialize_low_level.S @@ -10,6 +10,7 @@ **************************************************************************/ #include "csr.h" +#include "tx_port.h" .section .text .align 4 @@ -61,17 +62,18 @@ .extern trap_handler .extern _tx_thread_context_restore trap_entry: -#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) - addi sp, sp, -520 // Allocate space for all registers - with floating point enabled (65*8) -#else - addi sp, sp, -256 // Allocate space for all registers - without floating point enabled (32*8) -#endif + addi sp, sp, -TX_RISCV_TRAP_FRAME_SIZE // Interrupt frame from the port contract (528 with FP, 256 without) #if defined(__riscv_vector) - /* Allocate space for vector registers */ + /* Allocate space for vector registers. No register is saved yet, so + park t4 in the frame first and recover it after the allocation: + _tx_thread_context_save must still see the interrupted thread's t4. */ + sd t4, 15*8(sp) // Park t4 (pre-vector sp) csrr t4, vlenb slli t4, t4, 5 addi t4, t4, 4*8 sub sp, sp, t4 + add t4, sp, t4 // Rebuild the pre-vector sp + ld t4, 15*8(t4) // Recover the parked t4 #endif sd x1, 224(sp) // Store RA (28*8 = 224, because call will override ra [ra is a callee register in riscv]) @@ -81,11 +83,11 @@ csrr a0, mcause csrr a1, mepc csrr a2, mtval - addi sp, sp, -8 + addi sp, sp, -16 // 16 bytes keep sp 16-byte aligned at the call sd ra, 0(sp) call trap_handler ld ra, 0(sp) - addi sp, sp, 8 + addi sp, sp, 16 call _tx_thread_context_restore // it will nerver return _err: @@ -135,8 +137,7 @@ _err: /**************************************************************************/ /* VOID _tx_initialize_low_level(VOID) { */ - .global _tx_initialize_low_level - .weak _tx_initialize_low_level + .global _tx_initialize_low_level // Strong definition: must beat the port's weak default in any link order .extern _end .extern board_init _tx_initialize_low_level: @@ -150,9 +151,9 @@ _tx_initialize_low_level: 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, (MIE_MTIE | MIE_MEIE) + csrrs zero, mie, t0 // set mie (no MIE_MSIE: cause 3 has no dispatch path and msip is never written) +#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) li t0, MSTATUS_FS csrrs zero, mstatus, t0 // set MSTATUS_FS bit to open f/d isa in riscv fscsr x0 @@ -161,11 +162,11 @@ _tx_initialize_low_level: li t0, MSTATUS_VS csrrs zero, mstatus, t0 // set MSTATUS_VS bit to open vector isa in riscv #endif - addi sp, sp, -8 + addi sp, sp, -16 // 16 bytes keep sp 16-byte aligned at the call sd ra, 0(sp) call board_init ld ra, 0(sp) - addi sp, sp, 8 + addi sp, sp, 16 la t0, trap_entry csrw mtvec, t0 ret diff --git a/ports/risc-v64/gnu/inc/tx_port.h b/ports/risc-v64/gnu/inc/tx_port.h index 115178fb..5c29404c 100644 --- a/ports/risc-v64/gnu/inc/tx_port.h +++ b/ports/risc-v64/gnu/inc/tx_port.h @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -26,7 +28,7 @@ /* PORT SPECIFIC C INFORMATION RELEASE */ /* */ /* tx_port.h RISC-V64/GNU */ -/* 6.2.1 */ +/* 6.5.1.202602a */ /* */ /* AUTHOR */ /* */ @@ -48,6 +50,53 @@ #ifndef TX_PORT_H #define TX_PORT_H +#ifdef __riscv_float_abi_quad +#error "The ThreadX RISC-V64 port does not support the LP64Q ABI." +#endif + +#if defined(__riscv_flen) && !defined(__riscv_float_abi_single) && !defined(__riscv_float_abi_double) +#error "The ThreadX RISC-V64 port does not preserve FP state for a soft-float ABI. Remove F and D ISA extensions or use LP64F or LP64D." +#endif + +#if defined(__riscv_float_abi_single) && (!defined(__riscv_flen) || (__riscv_flen < 32)) +#error "The ThreadX RISC-V64 LP64F port requires FLEN>=32." +#endif + +#if defined(__riscv_float_abi_double) && (!defined(__riscv_flen) || (__riscv_flen < 64)) +#error "The ThreadX RISC-V64 LP64D port requires FLEN>=64." +#endif + +#if defined(__riscv_flen) && (__riscv_flen != 32) && (__riscv_flen != 64) +#error "The ThreadX RISC-V64 port supports only FLEN=32 or FLEN=64." +#endif + +/* Every port .S file uses CSR instructions, so the ISA string must include + Zicsr. Catch its absence here rather than in a wall of assembler + "unrecognized opcode" errors. */ +#if !defined(__riscv_zicsr) +#error "The ThreadX RISC-V64 port requires the Zicsr extension. Add it to the ISA string, for example rv64imac_zicsr." +#endif + +/* Publish the interrupt frame contract to GNU BSP assembly files. */ +#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) +#define TX_RISCV_TRAP_FRAME_SIZE 528 +#else +#define TX_RISCV_TRAP_FRAME_SIZE 256 +#endif + +/* Bytes a trap entry reserves around a call into C. The RISC-V psABI + requires sp to stay 16-byte aligned at a call boundary, so this is 16 + rather than one register slot. */ +#define TX_RISCV_TRAP_CALL_FRAME_SIZE 16 + + +#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) +#define TX_RISCV_SOL_FRAME_SIZE 240 +#else +#define TX_RISCV_SOL_FRAME_SIZE 128 +#endif + + #ifndef __ASSEMBLER__ /* Include for memset. */ @@ -62,6 +111,7 @@ /* Yes, include the user defines in tx_user.h. The defines in this file may alternately be defined on the command line. */ + #include "tx_user.h" #endif /* TX_INCLUDE_USER_DEFINE_FILE */ @@ -90,10 +140,9 @@ typedef unsigned long long ULONG64; typedef short SHORT; typedef unsigned short USHORT; #define ULONG64_DEFINED -#endif /* __ASSEMBLER__ */ - #define ALIGN_TYPE_DEFINED typedef unsigned long long ALIGN_TYPE; +#endif /* __ASSEMBLER__ */ /* On RV64, ULONG is 32-bit but pointers are 64-bit. Store the thread pointer in the timer's VOID * extension field so _tx_thread_timeout @@ -426,7 +475,7 @@ extern TEST_FLAG test_forced_mutex_timeout; #ifndef __ASSEMBLER__ #ifdef TX_THREAD_INIT CHAR _tx_version_id[] = - "(c) 2024 Microsoft Corp. (c) 2026-present Eclipse ThreadX contributors. * ThreadX RISC-V64/GNU Version 6.5.1.202602a *"; + "(c) 2024 Microsoft Corp. (c) 2026-present Eclipse ThreadX contributors. * ThreadX RISC-V64/GNU Version 6.5.2.202603 *"; #else extern CHAR _tx_version_id[]; #endif /* TX_THREAD_INIT */ diff --git a/ports/risc-v64/gnu/readme_threadx.txt b/ports/risc-v64/gnu/readme_threadx.txt index 83e666c7..a83da842 100644 --- a/ports/risc-v64/gnu/readme_threadx.txt +++ b/ports/risc-v64/gnu/readme_threadx.txt @@ -13,6 +13,15 @@ Verify the toolchain: riscv64-unknown-elf-gcc --version riscv64-unknown-elf-objdump --version +ISA and ABI requirements +- The ISA string must include Zicsr. rv64gc and rv64imafdc imply it; + rv64imac does not, so write rv64imac_zicsr for an integer-only build. +- Supported ABIs: lp64d (FLEN=64), lp64f (FLEN=32 or FLEN=64), and lp64 + only when the ISA has no F or D extension. lp64q is not supported. +- The vector extension (V) is optional; the port saves the full vector + state and sizes the frame from vlenb. +- tx_port.h rejects every other combination at compile time. + CMake-based build (recommended) From the ThreadX top-level directory: diff --git a/ports/risc-v64/gnu/src/tx_initialize_low_level.S b/ports/risc-v64/gnu/src/tx_initialize_low_level.S index 6a35272f..952be957 100644 --- a/ports/risc-v64/gnu/src/tx_initialize_low_level.S +++ b/ports/risc-v64/gnu/src/tx_initialize_low_level.S @@ -79,10 +79,26 @@ _tx_initialize_low_level: /* Initialize floating point control/status register if floating point is enabled. */ #ifdef __riscv_flen +#ifdef TX_RISCV_SMODE + li t0, 0x2000 // Set sstatus.FS to Initial (bits 14:13 = 01) so the fcsr write cannot trap + csrrs zero, sstatus, t0 +#else + li t0, 0x2000 // Set mstatus.FS to Initial (bits 14:13 = 01) so the fcsr write cannot trap + csrrs zero, mstatus, t0 +#endif li t0, 0 csrw fcsr, t0 // Clear FP control/status register #endif +#ifdef __riscv_vector + li t0, 0x200 // Set VS=Initial before a vector restore +#ifdef TX_RISCV_SMODE + csrrs zero, sstatus, t0 +#else + csrrs zero, mstatus, t0 +#endif +#endif + ret /* Timer Interrupt Handler Note: 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 cebc8a06..f5a68d2e 100644 --- a/ports/risc-v64/gnu/src/tx_thread_context_restore.S +++ b/ports/risc-v64/gnu/src/tx_thread_context_restore.S @@ -58,6 +58,8 @@ /**************************************************************************/ /* VOID _tx_thread_context_restore(VOID) { */ +#include "tx_port.h" + .equ TX_TRAP_FRAME_SIZE, TX_RISCV_TRAP_FRAME_SIZE .global _tx_thread_context_restore _tx_thread_context_restore: @@ -88,8 +90,18 @@ _tx_thread_context_restore: /* Just recover the saved registers and return to the point of interrupt. */ + /* Recover the FP state only when the saved mstatus.FS was not Off. The + save side skips the FP stores for a thread that has not touched the + unit, so those slots hold nothing worth loading back. */ +#if (__riscv_flen == 32) || (__riscv_flen == 64) + ld t1, 29*8(sp) // Pickup saved mstatus + srli t1, t1, 13 + andi t1, t1, 0x3 + beqz t1, _tx_thread_skip_fp_restore // Skip if FS was Off +#endif + /* Recover floating point registers. */ -#if defined(__riscv_float_abi_single) +#if (__riscv_flen == 32) flw f0, 31*8(sp) // Recover ft0 flw f1, 32*8(sp) // Recover ft1 flw f2, 33*8(sp) // Recover ft2 @@ -112,7 +124,7 @@ _tx_thread_context_restore: flw f31,62*8(sp) // Recover ft11 ld t0, 63*8(sp) // Recover fcsr csrw fcsr, t0 // Restore fcsr -#elif defined(__riscv_float_abi_double) +#elif (__riscv_flen == 64) fld f0, 31*8(sp) // Recover ft0 fld f1, 32*8(sp) // Recover ft1 fld f2, 33*8(sp) // Recover ft2 @@ -121,6 +133,20 @@ _tx_thread_context_restore: fld f5, 36*8(sp) // Recover ft5 fld f6, 37*8(sp) // Recover ft6 fld f7, 38*8(sp) // Recover ft7 +#if defined(__riscv_float_abi_single) + fld f8, 39*8(sp) // Recover fs0 + fld f9, 40*8(sp) // Recover fs1 + fld f18, 49*8(sp) // Recover fs2 + fld f19, 50*8(sp) // Recover fs3 + fld f20, 51*8(sp) // Recover fs4 + fld f21, 52*8(sp) // Recover fs5 + fld f22, 53*8(sp) // Recover fs6 + fld f23, 54*8(sp) // Recover fs7 + fld f24, 55*8(sp) // Recover fs8 + fld f25, 56*8(sp) // Recover fs9 + fld f26, 57*8(sp) // Recover fs10 + fld f27, 58*8(sp) // Recover fs11 +#endif fld f10,41*8(sp) // Recover fa0 fld f11,42*8(sp) // Recover fa1 fld f12,43*8(sp) // Recover fa2 @@ -136,6 +162,7 @@ _tx_thread_context_restore: ld t0, 63*8(sp) // Recover fcsr csrw fcsr, t0 #endif +_tx_thread_skip_fp_restore: #if defined(__riscv_vector) /* Recover vector registers v0-v31 */ @@ -189,6 +216,10 @@ _tx_thread_context_restore: li t0, 0x6000 // Set FS=Dirty (bits 14:13) for FP state or t1, t1, t0 #endif +#if defined(__riscv_vector) + li t0, 0x0600 + or t1, t1, t0 +#endif csrw sstatus, t1 // Update sstatus safely #else csrw mepc, t0 // Setup mepc @@ -203,11 +234,11 @@ _tx_thread_context_restore: andi t1, t1, ~0x8 // Clear MIE, Hardware will restore it from MPIE #if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) - li t0, 0x2000 // Set FS bits (bits 14:13 to 01) for FP state + li t0, 0x6000 or t1, t1, t0 #endif #if defined(__riscv_vector) - li t0, 0x0200 // Set VS bits (bits 10:9 to 01) for vector state + li t0, 0x0600 or t1, t1, t0 #endif csrw mstatus, t1 // Update mstatus safely @@ -231,18 +262,10 @@ _tx_thread_context_restore: ld t5, 14*8(sp) // Recover t5 ld t6, 13*8(sp) // Recover t6 -#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) - addi sp, sp, 65*8 // Recover stack frame - with floating point enabled -#else - addi sp, sp, 32*8 // Recover stack frame - without floating point enabled -#endif + addi sp, sp, TX_TRAP_FRAME_SIZE // Recover stack frame - with floating point enabled (65*8 data + 8 pad) #if defined(__riscv_vector) -#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) - addi t0, sp, -65*8 -#else - addi t0, sp, -32*8 -#endif + addi t0, sp, -TX_TRAP_FRAME_SIZE csrr t1, vlenb // Get vector register byte length slli t1, t1, 5 // Multiply by 32 (number of vector registers) addi t1, t1, 4*8 // Add vector CSR space: vstart, vtype, vl, vcsr @@ -270,7 +293,7 @@ _tx_thread_not_nested_restore: la t0, _tx_thread_preempt_disable // Pickup preempt disable flag address lw t2, 0(t0) // Pickup preempt disable flag (UINT) - bgtz t2, _tx_thread_no_preempt_restore // If set, restore interrupted thread + bnez t2, _tx_thread_no_preempt_restore // If set (nonzero), restore interrupted thread la t0, _tx_thread_execute_ptr // Pickup thread execute pointer address ld t2, 0(t0) // Pickup thread execute pointer @@ -285,8 +308,18 @@ _tx_thread_no_preempt_restore: ld sp, 8(t1) // Switch back to thread's stack + /* Recover the FP state only when the saved mstatus.FS was not Off. The + save side skips the FP stores for a thread that has not touched the + unit, so those slots hold nothing worth loading back. */ +#if (__riscv_flen == 32) || (__riscv_flen == 64) + ld t3, 29*8(sp) // Pickup saved mstatus + srli t3, t3, 13 + andi t3, t3, 0x3 + beqz t3, _tx_thread_no_preempt_skip_fp_restore // Skip if FS was Off +#endif + /* Recover floating point registers. */ -#if defined(__riscv_float_abi_single) +#if (__riscv_flen == 32) flw f0, 31*8(sp) // Recover ft0 flw f1, 32*8(sp) // Recover ft1 flw f2, 33*8(sp) // Recover ft2 @@ -309,7 +342,7 @@ _tx_thread_no_preempt_restore: flw f31,62*8(sp) // Recover ft11 ld t0, 63*8(sp) // Recover fcsr csrw fcsr, t0 // Restore fcsr -#elif defined(__riscv_float_abi_double) +#elif (__riscv_flen == 64) fld f0, 31*8(sp) // Recover ft0 fld f1, 32*8(sp) // Recover ft1 fld f2, 33*8(sp) // Recover ft2 @@ -318,6 +351,20 @@ _tx_thread_no_preempt_restore: fld f5, 36*8(sp) // Recover ft5 fld f6, 37*8(sp) // Recover ft6 fld f7, 38*8(sp) // Recover ft7 +#if defined(__riscv_float_abi_single) + fld f8, 39*8(sp) // Recover fs0 + fld f9, 40*8(sp) // Recover fs1 + fld f18, 49*8(sp) // Recover fs2 + fld f19, 50*8(sp) // Recover fs3 + fld f20, 51*8(sp) // Recover fs4 + fld f21, 52*8(sp) // Recover fs5 + fld f22, 53*8(sp) // Recover fs6 + fld f23, 54*8(sp) // Recover fs7 + fld f24, 55*8(sp) // Recover fs8 + fld f25, 56*8(sp) // Recover fs9 + fld f26, 57*8(sp) // Recover fs10 + fld f27, 58*8(sp) // Recover fs11 +#endif fld f10,41*8(sp) // Recover fa0 fld f11,42*8(sp) // Recover fa1 fld f12,43*8(sp) // Recover fa2 @@ -333,6 +380,7 @@ _tx_thread_no_preempt_restore: ld t0, 63*8(sp) // Recover fcsr csrw fcsr, t0 // Restore fcsr #endif +_tx_thread_no_preempt_skip_fp_restore: #if defined(__riscv_vector) /* Recover vector registers v0-v31 */ @@ -384,6 +432,10 @@ _tx_thread_no_preempt_restore: li t0, 0x6000 // Set FS=Dirty (bits 14:13) for FP state or t1, t1, t0 #endif +#if defined(__riscv_vector) + li t0, 0x0600 // Set VS bits (bits 10:9 to 11, Dirty) for restored vector state + or t1, t1, t0 +#endif csrw sstatus, t1 // Update sstatus safely #else csrw mepc, t0 // Setup mepc @@ -399,11 +451,11 @@ _tx_thread_no_preempt_restore: andi t1, t1, ~0x8 // Clear MIE, Hardware will restore it from MPIE #if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) - li t0, 0x2000 // Set FS bits for FP state + li t0, 0x6000 or t1, t1, t0 #endif #if defined(__riscv_vector) - li t0, 0x0200 // Set VS bits (bits 10:9 to 01) for vector state + li t0, 0x0600 or t1, t1, t0 #endif csrw mstatus, t1 // Update mstatus safely @@ -427,18 +479,10 @@ _tx_thread_no_preempt_restore: ld t5, 14*8(sp) // Recover t5 ld t6, 13*8(sp) // Recover t6 -#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) - addi sp, sp, 65*8 // Recover stack frame - with floating point enabled -#else - addi sp, sp, 32*8 // Recover stack frame - without floating point enabled -#endif + addi sp, sp, TX_TRAP_FRAME_SIZE // Recover stack frame #if defined(__riscv_vector) -#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) - addi t0, sp, -65*8 -#else - addi t0, sp, -32*8 -#endif + addi t0, sp, -TX_TRAP_FRAME_SIZE csrr t1, vlenb // Get vector register byte length slli t1, t1, 5 // Multiply by 32 (number of vector registers) addi t1, t1, 4*8 // Add vector CSR space: vstart, vtype, vl, vcsr @@ -464,8 +508,10 @@ _tx_thread_preempt_restore: ori t3, zero, 1 // Build interrupt stack type sd t3, 0(t0) // Store stack type - /* Store floating point preserved registers. */ -#ifdef __riscv_float_abi_single + /* Store floating point preserved registers. LP64F saved the complete + FLEN=64 state before the C interrupt handler ran. */ +#if !defined(__riscv_float_abi_single) || (__riscv_flen != 64) +#if (__riscv_flen == 32) fsw f8, 39*8(t0) // Store fs0 fsw f9, 40*8(t0) // Store fs1 fsw f18, 49*8(t0) // Store fs2 @@ -478,7 +524,7 @@ _tx_thread_preempt_restore: fsw f25, 56*8(t0) // Store fs9 fsw f26, 57*8(t0) // Store fs10 fsw f27, 58*8(t0) // Store fs11 -#elif defined(__riscv_float_abi_double) +#elif (__riscv_flen == 64) fsd f8, 39*8(t0) // Store fs0 fsd f9, 40*8(t0) // Store fs1 fsd f18, 49*8(t0) // Store fs2 @@ -492,36 +538,12 @@ _tx_thread_preempt_restore: fsd f26, 57*8(t0) // Store fs10 fsd f27, 58*8(t0) // Store fs11 #endif - -#if defined(__riscv_vector) - /* Store vector registers and CSRs */ -#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) - addi t1, t0, 64*8 -#else - addi t1, t0, 31*8 #endif - /* Store vector CSRs */ - csrr t2, vstart // Store vstart - sd t2, 0*8(t1) - csrr t2, vtype // Store vtype - sd t2, 1*8(t1) - csrr t2, vl // Store vl - sd t2, 2*8(t1) - csrr t2, vcsr // Store vcsr - sd t2, 3*8(t1) - /* Store vector registers v0-v31 */ - addi t2, t1, 4*8 - vsetvli t3, zero, e8, m8, ta, ma - vse8.v v0, 0(t2) // Store v0 ~ v7 - add t2, t2, t3 - vse8.v v8, 0(t2) // Store v8 ~ v15 - add t2, t2, t3 - vse8.v v16, 0(t2) // Store v16 ~ v23 - add t2, t2, t3 - vse8.v v24, 0(t2) // Store v24 ~ v31 - add t2, t2, t3 -#endif + /* No vector save here. _tx_thread_context_save stored v0-v31 and the + vector CSRs before the C handler ran. The psABI treats v0-v31, vl and + vtype as caller-saved, so the handler may have changed them; a second + store here would overwrite the good copy with the handler's state. */ /* Store standard preserved registers. */ 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 7935bfee..1924bd6e 100644 --- a/ports/risc-v64/gnu/src/tx_thread_context_save.S +++ b/ports/risc-v64/gnu/src/tx_thread_context_save.S @@ -9,6 +9,7 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Portions of this file were generated with AI assistance. /**************************************************************************/ /**************************************************************************/ @@ -21,6 +22,8 @@ /**************************************************************************/ .section .text +#include "tx_port.h" + .equ TX_TRAP_FRAME_SIZE, TX_RISCV_TRAP_FRAME_SIZE /**************************************************************************/ /* */ /* FUNCTION RELEASE */ @@ -30,6 +33,7 @@ /* AUTHOR */ /* */ /* Scott Larson, Microsoft Corporation */ +/* Wei-Chen Lai, National Cheng Kung University */ /* */ /* DESCRIPTION */ /* */ @@ -67,14 +71,14 @@ _tx_thread_context_save: sd t1, 18*8(sp) la t0, _tx_thread_system_state // Pickup address of system state - ld t1, 0(t0) // Pickup system state + lw t1, 0(t0) // Pickup system state /* Check for a nested interrupt condition. */ /* if (_tx_thread_system_state++) { */ beqz t1, _tx_thread_not_nested_save // If 0, first interrupt condition addi t1, t1, 1 // Increment the interrupt counter - sd t1, 0(t0) // Store the interrupt counter + sw t1, 0(t0) // Store the interrupt counter /* Nested interrupt condition. Save the rest of the scratch registers on the stack and return to the @@ -101,8 +105,17 @@ _tx_thread_context_save: #endif sd t0, 30*8(sp) // Save it on the stack + /* Save mstatus and skip FP state if FS is Off. */ + csrr t0, mstatus + sd t0, 29*8(sp) // Save mstatus +#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) + srli t1, t0, 13 + andi t1, t1, 0x3 + beqz t1, _tx_thread_skip_nested_fpu_save // Skip if FS was Off +#endif + /* Save floating point scratch registers if floating point is enabled. */ -#ifdef __riscv_float_abi_single +#if (__riscv_flen == 32) fsw f0, 31*8(sp) // Store ft0 fsw f1, 32*8(sp) // Store ft1 fsw f2, 33*8(sp) // Store ft2 @@ -125,7 +138,7 @@ _tx_thread_context_save: fsw f31,62*8(sp) // Store ft11 csrr t0, fcsr sd t0, 63*8(sp) // Store fcsr -#elif defined(__riscv_float_abi_double) +#elif (__riscv_flen == 64) fsd f0, 31*8(sp) // Store ft0 fsd f1, 32*8(sp) // Store ft1 fsd f2, 33*8(sp) // Store ft2 @@ -134,6 +147,20 @@ _tx_thread_context_save: fsd f5, 36*8(sp) // Store ft5 fsd f6, 37*8(sp) // Store ft6 fsd f7, 38*8(sp) // Store ft7 +#if defined(__riscv_float_abi_single) + fsd f8, 39*8(sp) // Store fs0 + fsd f9, 40*8(sp) // Store fs1 + fsd f18, 49*8(sp) // Store fs2 + fsd f19, 50*8(sp) // Store fs3 + fsd f20, 51*8(sp) // Store fs4 + fsd f21, 52*8(sp) // Store fs5 + fsd f22, 53*8(sp) // Store fs6 + fsd f23, 54*8(sp) // Store fs7 + fsd f24, 55*8(sp) // Store fs8 + fsd f25, 56*8(sp) // Store fs9 + fsd f26, 57*8(sp) // Store fs10 + fsd f27, 58*8(sp) // Store fs11 +#endif fsd f10,41*8(sp) // Store fa0 fsd f11,42*8(sp) // Store fa1 fsd f12,43*8(sp) // Store fa2 @@ -149,6 +176,7 @@ _tx_thread_context_save: csrr t0, fcsr sd t0, 63*8(sp) // Store fcsr #endif +_tx_thread_skip_nested_fpu_save: #if defined(__riscv_vector) /* Store vector registers and CSRs */ @@ -181,7 +209,11 @@ _tx_thread_context_save: #endif #ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY + addi sp, sp, -16 // Reserve space to save the return address + sd ra, 0(sp) // Save the return address call _tx_execution_isr_enter // Call the ISR execution enter function + ld ra, 0(sp) // Recover the return address + addi sp, sp, 16 // Recover the reserved stack space #endif ret // Return to calling ISR @@ -193,7 +225,7 @@ _tx_thread_not_nested_save: /* else if (_tx_thread_current_ptr) { */ addi t1, t1, 1 // Increment the interrupt counter - sd t1, 0(t0) // Store the interrupt counter + sw t1, 0(t0) // Store the interrupt counter /* Not nested: Find the user thread that was running and load our SP */ @@ -225,8 +257,17 @@ _tx_thread_not_nested_save: #endif sd t1, 30*8(sp) // Save it on the stack + /* Save mstatus and skip FP state if FS is Off. */ + csrr t1, mstatus + sd t1, 29*8(sp) // Save mstatus +#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) + srli t2, t1, 13 + andi t2, t2, 0x3 + beqz t2, _tx_thread_skip_fpu_save // Skip if FS was Off +#endif + /* Save floating point scratch registers if floating point is enabled. */ -#ifdef __riscv_float_abi_single +#if (__riscv_flen == 32) fsw f0, 31*8(sp) // Store ft0 fsw f1, 32*8(sp) // Store ft1 fsw f2, 33*8(sp) // Store ft2 @@ -249,7 +290,7 @@ _tx_thread_not_nested_save: fsw f31,62*8(sp) // Store ft11 csrr t0, fcsr sd t0, 63*8(sp) // Store fcsr -#elif defined(__riscv_float_abi_double) +#elif (__riscv_flen == 64) fsd f0, 31*8(sp) // Store ft0 fsd f1, 32*8(sp) // Store ft1 fsd f2, 33*8(sp) // Store ft2 @@ -258,6 +299,20 @@ _tx_thread_not_nested_save: fsd f5, 36*8(sp) // Store ft5 fsd f6, 37*8(sp) // Store ft6 fsd f7, 38*8(sp) // Store ft7 +#if defined(__riscv_float_abi_single) + fsd f8, 39*8(sp) // Store fs0 + fsd f9, 40*8(sp) // Store fs1 + fsd f18, 49*8(sp) // Store fs2 + fsd f19, 50*8(sp) // Store fs3 + fsd f20, 51*8(sp) // Store fs4 + fsd f21, 52*8(sp) // Store fs5 + fsd f22, 53*8(sp) // Store fs6 + fsd f23, 54*8(sp) // Store fs7 + fsd f24, 55*8(sp) // Store fs8 + fsd f25, 56*8(sp) // Store fs9 + fsd f26, 57*8(sp) // Store fs10 + fsd f27, 58*8(sp) // Store fs11 +#endif fsd f10,41*8(sp) // Store fa0 fsd f11,42*8(sp) // Store fa1 fsd f12,43*8(sp) // Store fa2 @@ -273,6 +328,7 @@ _tx_thread_not_nested_save: csrr t0, fcsr sd t0, 63*8(sp) // Store fcsr #endif +_tx_thread_skip_fpu_save: #if defined(__riscv_vector) /* Store vector registers and CSRs */ @@ -316,7 +372,11 @@ _tx_thread_not_nested_save: #ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY /* _tx_execution_isr_enter is called with thread stack pointer */ + addi sp, sp, -16 // Reserve space to save the return address + sd ra, 0(sp) // Save the return address call _tx_execution_isr_enter // Call the ISR execution enter function + ld ra, 0(sp) // Recover the return address + addi sp, sp, 16 // Recover the reserved stack space #endif la t0, _tx_thread_system_stack_ptr // Pickup system stack pointer address @@ -331,25 +391,21 @@ _tx_thread_idle_system_save: #ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY + addi sp, sp, -16 // Reserve space to save the return address + sd ra, 0(sp) // Save the return address call _tx_execution_isr_enter // Call the ISR execution enter function + ld ra, 0(sp) // Recover the return address + addi sp, sp, 16 // Recover the reserved stack space #endif /* Interrupt occurred in the scheduling loop. */ /* } } */ -#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) - addi sp, sp, 65*8 // Recover stack frame - with floating point enabled -#else - addi sp, sp, 32*8 // Recover the reserved stack space -#endif + addi sp, sp, TX_TRAP_FRAME_SIZE // Recover stack frame - with floating point enabled (65*8 data + 8 pad) #if defined(__riscv_vector) -#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) - addi t0, sp, -65*8 -#else - addi t0, sp, -32*8 -#endif + addi t0, sp, -TX_TRAP_FRAME_SIZE csrr t1, vlenb // Get vector register byte length slli t1, t1, 5 // Multiply by 32 (number of vector registers) addi t1, t1, 4*8 // Add vector CSR space: vstart, vtype, vl, vcsr 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 b43d9558..d791e508 100644 --- a/ports/risc-v64/gnu/src/tx_thread_interrupt_control.S +++ b/ports/risc-v64/gnu/src/tx_thread_interrupt_control.S @@ -27,7 +27,7 @@ /* FUNCTION RELEASE */ /* */ /* _tx_thread_interrupt_control RISC-V64/GNU */ -/* 6.2.1 */ +/* 6.5.1.202602a */ /* AUTHOR */ /* */ /* Scott Larson, Microsoft Corporation */ @@ -61,23 +61,25 @@ _tx_thread_interrupt_control: /* Pickup current interrupt lockout posture. */ #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 + li t1, 0x02 // Build SIE bit mask (bit 1) 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 + bnez a0, 1f // If set, enable interrupts + csrrc t0, sstatus, t1 // Atomically clear SIE, read old sstatus + j 2f +1: + csrrs t0, sstatus, t1 // Atomically set SIE, read old sstatus +2: + andi a0, t0, 0x02 // Return original SIE bit #else - csrr t0, mstatus - mv t1, t0 // Save original mstatus for return - li t2, ~0x08 // Build mask to clear MIE - and t0, t0, t2 // Clear MIE bit + li t1, 0x08 // Build MIE bit mask (bit 3) 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 + bnez a0, 1f // If set, enable interrupts + csrrc t0, mstatus, t1 // Atomically clear MIE, read old mstatus + j 2f +1: + csrrs t0, mstatus, t1 // Atomically set MIE, read old mstatus +2: + andi a0, t0, 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 860adf0b..26220726 100644 --- a/ports/risc-v64/gnu/src/tx_thread_schedule.S +++ b/ports/risc-v64/gnu/src/tx_thread_schedule.S @@ -58,6 +58,9 @@ /**************************************************************************/ /* VOID _tx_thread_schedule(VOID) { */ +#include "tx_port.h" + .equ TX_TRAP_FRAME_SIZE, TX_RISCV_TRAP_FRAME_SIZE + .equ TX_SOL_FRAME_SIZE, TX_RISCV_SOL_FRAME_SIZE .global _tx_thread_schedule _tx_thread_schedule: @@ -140,9 +143,18 @@ _tx_thread_schedule_loop: ld t2, 0(sp) // Pickup stack type beqz t2, _tx_thread_synch_return // If 0, solicited thread return - /* Determine if floating point registers need to be recovered. */ + /* Determine if floating point registers need to be recovered. This is an + interrupt frame, so _tx_thread_context_save built it and may have + skipped the FP stores when mstatus.FS was Off. Read back the same slot + it wrote rather than reloading whatever those slots happen to hold. */ +#if (__riscv_flen == 32) || (__riscv_flen == 64) + ld t1, 29*8(sp) // Pickup saved mstatus + srli t1, t1, 13 + andi t1, t1, 0x3 + beqz t1, _tx_thread_schedule_skip_fp_restore // Skip if FS was Off +#endif -#if defined(__riscv_float_abi_single) +#if (__riscv_flen == 32) flw f0, 31*8(sp) // Recover ft0 flw f1, 32*8(sp) // Recover ft1 flw f2, 33*8(sp) // Recover ft2 @@ -177,7 +189,7 @@ _tx_thread_schedule_loop: flw f31,62*8(sp) // Recover ft11 ld t0, 63*8(sp) // Recover fcsr csrw fcsr, t0 // Restore fcsr -#elif defined(__riscv_float_abi_double) +#elif (__riscv_flen == 64) fld f0, 31*8(sp) // Recover ft0 fld f1, 32*8(sp) // Recover ft1 fld f2, 33*8(sp) // Recover ft2 @@ -213,6 +225,7 @@ _tx_thread_schedule_loop: ld t0, 63*8(sp) // Recover fcsr csrw fcsr, t0 // Restore fcsr #endif +_tx_thread_schedule_skip_fp_restore: #if defined(__riscv_vector) /* Recover vector registers v0-v31 */ @@ -259,6 +272,10 @@ _tx_thread_schedule_loop: li t1, 0x6000 // Set FS=Dirty (bits 14:13) or t0, t0, t1 #endif +#if defined(__riscv_vector) + li t1, 0x0600 + or t0, t0, t1 +#endif csrw sstatus, t0 // Update sstatus safely #else csrw mepc, t0 // Store mepc @@ -274,7 +291,7 @@ _tx_thread_schedule_loop: or t0, t0, t1 #endif #if defined(__riscv_vector) - li t1, 0x0200 // Set VS bits (bits 10:9 to 01) for vector state + li t1, 0x0600 or t0, t0, t1 #endif csrw mstatus, t0 // Set mstatus @@ -309,17 +326,9 @@ _tx_thread_schedule_loop: ld t5, 14*8(sp) // Recover t5 ld t6, 13*8(sp) // Recover t6 -#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) - addi sp, sp, 65*8 // Recover stack frame - with floating point registers -#else - addi sp, sp, 32*8 // Recover stack frame - without floating point registers -#endif + addi sp, sp, TX_TRAP_FRAME_SIZE // Recover stack frame - with floating point registers (65*8 data + 8 pad) #if defined(__riscv_vector) -#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) - addi t0, sp, -65*8 -#else - addi t0, sp, -32*8 -#endif + addi t0, sp, -TX_TRAP_FRAME_SIZE csrr t1, vlenb // Get vector register byte length slli t1, t1, 5 // Multiply by 32 (number of vector registers) addi t1, t1, 4*8 // Add vector CSR space: vstart, vtype, vl, vcsr @@ -336,7 +345,7 @@ _tx_thread_schedule_loop: _tx_thread_synch_return: -#if defined(__riscv_float_abi_single) +#if (__riscv_flen == 32) flw f8, 15*8(sp) // Recover fs0 flw f9, 16*8(sp) // Recover fs1 flw f18,17*8(sp) // Recover fs2 @@ -351,7 +360,7 @@ _tx_thread_synch_return: flw f27,26*8(sp) // Recover fs11 ld t0, 27*8(sp) // Recover fcsr csrw fcsr, t0 // -#elif defined(__riscv_float_abi_double) +#elif (__riscv_flen == 64) fld f8, 15*8(sp) // Recover fs0 fld f9, 16*8(sp) // Recover fs1 fld f18,17*8(sp) // Recover fs2 @@ -418,11 +427,7 @@ _tx_thread_synch_return: #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 - addi sp, sp, 16*8 // Recover stack frame -#endif + addi sp, sp, TX_SOL_FRAME_SIZE // Recover stack frame (29*8 data + 8 pad) #if defined(__riscv_vector) csrr t1, vlenb // Get vector register byte length slli t1, t1, 5 // Multiply by 32 (number of vector registers) 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 86cab4f0..47e5c995 100644 --- a/ports/risc-v64/gnu/src/tx_thread_stack_build.S +++ b/ports/risc-v64/gnu/src/tx_thread_stack_build.S @@ -21,6 +21,8 @@ /**************************************************************************/ .section .text +#include "tx_port.h" + .equ TX_TRAP_FRAME_SIZE, TX_RISCV_TRAP_FRAME_SIZE /**************************************************************************/ /* */ /* FUNCTION RELEASE */ @@ -30,6 +32,7 @@ /* AUTHOR */ /* */ /* Scott Larson, Microsoft Corporation */ +/* Wei-Chen Lai, National Cheng Kung University */ /* */ /* DESCRIPTION */ /* */ @@ -92,7 +95,7 @@ _tx_thread_stack_build: x11 26 Initial a1 x10 27 Initial a0 x1 28 Initial ra - -- 29 reserved + mstatus 29 Initial mstatus mepc 30 Initial mepc If floating point support: f0 31 Inital ft0 @@ -138,6 +141,9 @@ If vector extension support: ... v31 99 Initial v31 + With floating point enabled, 8 pad bytes sit at the top of the frame + (bytes 520-527) so the 528-byte total keeps sp 16-byte aligned. + Stack Bottom: (higher memory address) */ ld t0, 24(a0) // Pickup end of stack area @@ -146,11 +152,7 @@ If vector extension support: /* Actually build the stack frame. */ -#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) - addi t0, t0, -65*8 -#else - addi t0, t0, -32*8 // Allocate space for the stack frame -#endif + addi t0, t0, -TX_TRAP_FRAME_SIZE #if defined(__riscv_vector) /* Vector extension support: calculate space based on vlenb */ @@ -192,6 +194,8 @@ 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 + li t1, 0x2000 // mstatus.FS = Initial + sd t1, 29*8(t0) // Initial mstatus 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 @@ -226,8 +230,7 @@ If vector extension support: sd zero, 60*8(t0) // Initial ft9 sd zero, 61*8(t0) // Initial ft10 sd zero, 62*8(t0) // Initial ft11 - csrr a1, fcsr // Read fcsr for initial value - sd a1, 63*8(t0) // Initial fcsr + sd zero, 63*8(t0) // Initial fcsr (0: no sticky flags, round-to-nearest) #endif #if defined(__riscv_vector) 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 583b3f3e..2f4daeed 100644 --- a/ports/risc-v64/gnu/src/tx_thread_system_return.S +++ b/ports/risc-v64/gnu/src/tx_thread_system_return.S @@ -57,17 +57,15 @@ /**************************************************************************/ /* VOID _tx_thread_system_return(VOID) { */ +#include "tx_port.h" + .equ TX_SOL_FRAME_SIZE, TX_RISCV_SOL_FRAME_SIZE .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*8 // Allocate space on the stack - with floating point enabled -#else - addi sp, sp, -16*8 // Allocate space on the stack - without floating point enabled -#endif + addi sp, sp, -TX_SOL_FRAME_SIZE // Allocate space on the stack #if defined(__riscv_vector) csrr t1, vlenb // Get vector register byte length slli t1, t1, 5 // Multiply by 32 (number of vector registers) @@ -76,7 +74,7 @@ _tx_thread_system_return: #endif /* Store floating point preserved registers. */ -#if defined(__riscv_float_abi_single) +#if (__riscv_flen == 32) fsw f8, 15*8(sp) // Store fs0 fsw f9, 16*8(sp) // Store fs1 fsw f18, 17*8(sp) // Store fs2 @@ -91,7 +89,7 @@ _tx_thread_system_return: fsw f27, 26*8(sp) // Store fs11 csrr t0, fcsr sd t0, 27*8(sp) // Store fcsr -#elif defined(__riscv_float_abi_double) +#elif (__riscv_flen == 64) fsd f8, 15*8(sp) // Store fs0 fsd f9, 16*8(sp) // Store fs1 fsd f18, 17*8(sp) // Store fs2 diff --git a/ports/risc-v64/gnu/src/tx_timer_interrupt.S b/ports/risc-v64/gnu/src/tx_timer_interrupt.S index 53e572f3..207d6677 100644 --- a/ports/risc-v64/gnu/src/tx_timer_interrupt.S +++ b/ports/risc-v64/gnu/src/tx_timer_interrupt.S @@ -88,7 +88,7 @@ _tx_timer_interrupt: /* Check for expiration. */ /* if (_tx_timer_time_slice == 0) */ - bgtz t3, _tx_timer_no_time_slice // If not 0, has not expired yet + bnez t3, _tx_timer_no_time_slice // If not 0, has not expired yet li t1, 1 // Build expired flag /* Set the time-slice expired flag. */ @@ -181,7 +181,7 @@ _tx_timer_dont_activate: /* if (_tx_timer_expired_time_slice) { */ - andi t2, t6, 1 // Is the timer expired bit set? + andi t2, t6, 1 // Is the time-slice expired bit set? beqz t2, _tx_timer_not_ts_expiration // If not, skip time slice processing /* Time slice interrupted thread. */ |
