From da104a5ab14ad2ba9b69f7af88f6d6c8b2c0a2cf Mon Sep 17 00:00:00 2001 From: Afonso Oliveira Date: Wed, 30 Sep 2026 12:13:03 +0100 Subject: [PATCH 1/3] Added an Erbium example to the RISC-V64 GNU port ThreadX had no board support for Erbium, the OpenHW Foundation CORE-ET RISC-V platform that Zephyr and NuttX support. Its hart implements only part of the F extension, needs a 4 KiB aligned mtvec and uses the original Shakti UART. The example runs the standard demo on hart 0 in machine mode. It builds the library and the demo for rv64imc_zicsr_zifencei with the soft-float lp64 ABI and links without libc or libgcc, so the pinned riscv64-unknown-elf toolchain can build it. It programs the machine timer, a polling UART console and the PLIC, writing the source priorities and threshold that silicon hardwires because the simulator resets them to 0. No shared file changes. The demo built without warnings and ran on erbium_emu from et-platform 836a4ab, with all eight threads reporting and five thread 0 wakeups in 100M cycles, which matches the 2 MHz tick. Separate test images took five PLIC UART interrupts through the ThreadX ISR path and halted with mcause 2 on an illegal instruction. check_ai_disclosure.sh and check_ports.sh passed. Assisted-by: Claude Code (Opus 5.5) --- .../gnu/example_build/erbium/CMakeLists.txt | 43 ++ .../gnu/example_build/erbium/README.md | 125 ++++++ .../risc-v64/gnu/example_build/erbium/board.c | 49 +++ .../gnu/example_build/erbium/build.sh | 28 ++ ports/risc-v64/gnu/example_build/erbium/csr.h | 66 +++ .../gnu/example_build/erbium/demo_threadx.c | 382 ++++++++++++++++++ .../risc-v64/gnu/example_build/erbium/entry.S | 50 +++ .../gnu/example_build/erbium/erbium_gnu.cmake | 39 ++ .../gnu/example_build/erbium/hwtimer.c | 42 ++ .../gnu/example_build/erbium/hwtimer.h | 41 ++ .../gnu/example_build/erbium/link.lds | 92 +++++ .../risc-v64/gnu/example_build/erbium/plic.c | 112 +++++ .../risc-v64/gnu/example_build/erbium/plic.h | 54 +++ .../risc-v64/gnu/example_build/erbium/trap.c | 78 ++++ .../erbium/tx_initialize_low_level.S | 142 +++++++ .../risc-v64/gnu/example_build/erbium/uart.c | 106 +++++ .../risc-v64/gnu/example_build/erbium/uart.h | 40 ++ 17 files changed, 1489 insertions(+) create mode 100644 ports/risc-v64/gnu/example_build/erbium/CMakeLists.txt create mode 100644 ports/risc-v64/gnu/example_build/erbium/README.md create mode 100644 ports/risc-v64/gnu/example_build/erbium/board.c create mode 100755 ports/risc-v64/gnu/example_build/erbium/build.sh create mode 100644 ports/risc-v64/gnu/example_build/erbium/csr.h create mode 100644 ports/risc-v64/gnu/example_build/erbium/demo_threadx.c create mode 100644 ports/risc-v64/gnu/example_build/erbium/entry.S create mode 100644 ports/risc-v64/gnu/example_build/erbium/erbium_gnu.cmake create mode 100644 ports/risc-v64/gnu/example_build/erbium/hwtimer.c create mode 100644 ports/risc-v64/gnu/example_build/erbium/hwtimer.h create mode 100644 ports/risc-v64/gnu/example_build/erbium/link.lds create mode 100644 ports/risc-v64/gnu/example_build/erbium/plic.c create mode 100644 ports/risc-v64/gnu/example_build/erbium/plic.h create mode 100644 ports/risc-v64/gnu/example_build/erbium/trap.c create mode 100644 ports/risc-v64/gnu/example_build/erbium/tx_initialize_low_level.S create mode 100644 ports/risc-v64/gnu/example_build/erbium/uart.c create mode 100644 ports/risc-v64/gnu/example_build/erbium/uart.h 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 000000000..244d7656e --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/CMakeLists.txt @@ -0,0 +1,43 @@ +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 +) 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 000000000..826d7cab7 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/README.md @@ -0,0 +1,125 @@ +# 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. + +## 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 | + +## 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 000000000..b801b55d4 --- /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 +#include + +/* 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 000000000..effaed0ad --- /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 000000000..e2aa4e182 --- /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 + +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 000000000..2023df128 --- /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 000000000..201f986e4 --- /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 000000000..0d55dd89f --- /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 000000000..511bea596 --- /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 000000000..3b9bf4e3d --- /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 + +/* 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 000000000..25ba92190 --- /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 000000000..93b1f3171 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/plic.c @@ -0,0 +1,112 @@ +/*************************************************************************** + * 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 + +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()); + + if (!plic_source_valid(irqno)) + return; + + *reg = *reg | (1u << (unsigned int)irqno); +} + +void plic_irq_disable(int irqno) +{ + volatile uint32_t *reg = (volatile uint32_t *)PLIC_MENABLE(riscv_get_core()); + + if (!plic_source_valid(irqno)) + return; + + *reg = *reg & ~(1u << (unsigned int)irqno); +} + +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 000000000..ffc3a5391 --- /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 + +/* 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/trap.c b/ports/risc-v64/gnu/example_build/erbium/trap.c new file mode 100644 index 000000000..d839e6eab --- /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 +#include + +#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 000000000..fe0653ae8 --- /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 000000000..ee065e8a7 --- /dev/null +++ b/ports/risc-v64/gnu/example_build/erbium/uart.c @@ -0,0 +1,106 @@ +/*************************************************************************** + * 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 + +#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; +} + +static inline void uart_putc_nolock(int ch) +{ + while ((UART_REG(UART_STATUS) & STATUS_TX_FULL) != 0u) + ; + UART_REG(UART_TX) = (uint32_t)(ch & 0xFF); +} + +int uart_putc(int ch) +{ + int intr_enable = riscv_mintr_save(); + uart_putc_nolock(ch); + riscv_mintr_restore(intr_enable); + return 1; +} + +int uart_puts(const char *str) +{ + int i; + int intr_enable = riscv_mintr_save(); + for (i = 0; str[i] != 0; i++) + uart_putc_nolock(str[i]); + uart_putc_nolock('\r'); + uart_putc_nolock('\n'); + riscv_mintr_restore(intr_enable); + 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 000000000..74a6c1d05 --- /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 */ From 01e123ada8fa1382662f97739f47e09aa91929b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Wed, 30 Sep 2026 09:40:59 -0400 Subject: [PATCH 2/3] Added simulator tests for Erbium interrupt regressions Long polling UART writes can mask timer interrupts across several tick periods. PLIC enable-word updates can overwrite a change made by an interrupt handler. The board example had no simulator tests for either case. Added two simulator images and a runner. One compares hardware timer progress with ThreadX ticks after a long print. The other toggles one PLIC source in timer context while a thread changes another. Both tests fail on the PR code and pass with the corresponding local fixes. GCC 15.2.0 and Ninja built both images. erbium_emu reproduced both failures; control runs passed both tests. Port consistency checks passed. No silicon run. Assisted-by: Codex (GPT-6-Sol) --- .../gnu/example_build/erbium/CMakeLists.txt | 25 ++++++ .../erbium/test/run_simulator_tests.sh | 55 ++++++++++++ .../erbium/test/test_plic_race.c | 90 +++++++++++++++++++ .../erbium/test/test_plic_trap.c | 55 ++++++++++++ .../erbium/test/test_uart_ticks.c | 81 +++++++++++++++++ 5 files changed, 306 insertions(+) create mode 100755 ports/risc-v64/gnu/example_build/erbium/test/run_simulator_tests.sh create mode 100644 ports/risc-v64/gnu/example_build/erbium/test/test_plic_race.c create mode 100644 ports/risc-v64/gnu/example_build/erbium/test/test_plic_trap.c create mode 100644 ports/risc-v64/gnu/example_build/erbium/test/test_uart_ticks.c diff --git a/ports/risc-v64/gnu/example_build/erbium/CMakeLists.txt b/ports/risc-v64/gnu/example_build/erbium/CMakeLists.txt index 244d7656e..51b077149 100644 --- a/ports/risc-v64/gnu/example_build/erbium/CMakeLists.txt +++ b/ports/risc-v64/gnu/example_build/erbium/CMakeLists.txt @@ -41,3 +41,28 @@ target_link_options(demo_threadx PRIVATE -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/test/run_simulator_tests.sh b/ports/risc-v64/gnu/example_build/erbium/test/run_simulator_tests.sh new file mode 100755 index 000000000..7d54a5f8a --- /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 000000000..d6f2a3b57 --- /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 000000000..8dabb42b2 --- /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 + +#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 000000000..188af2e7d --- /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); +} From 42017ead54041a0bb0701808a544ae89eac97ea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Wed, 30 Sep 2026 09:55:51 -0400 Subject: [PATCH 3/3] Fixed Erbium interrupt masking and PLIC update races Polling UART output held machine interrupts off for an entire string, which could lose ThreadX ticks. PLIC enable-word changes could overwrite an update made by an interrupt handler. The UART now polls with interrupts enabled and protects only the final status check and byte write. PLIC enable and disable changes are protected across their read-modify-write. The README explains the new simulator tests. Both simulator tests failed on the original code and passed with these fixes. The 100-million-cycle demo run reported all eight threads and five thread 0 wakeups. AI disclosure and port consistency checks passed. No silicon run. Assisted-by: Codex (GPT-6-Sol) --- .../gnu/example_build/erbium/README.md | 15 ++++++++ .../risc-v64/gnu/example_build/erbium/plic.c | 6 ++++ .../risc-v64/gnu/example_build/erbium/uart.c | 35 ++++++++++--------- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/ports/risc-v64/gnu/example_build/erbium/README.md b/ports/risc-v64/gnu/example_build/erbium/README.md index 826d7cab7..8cfa28282 100644 --- a/ports/risc-v64/gnu/example_build/erbium/README.md +++ b/ports/risc-v64/gnu/example_build/erbium/README.md @@ -99,6 +99,20 @@ 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 | @@ -114,6 +128,7 @@ It does not verify baud timing or the timer rate of real silicon. | `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 diff --git a/ports/risc-v64/gnu/example_build/erbium/plic.c b/ports/risc-v64/gnu/example_build/erbium/plic.c index 93b1f3171..9ca25f1b0 100644 --- a/ports/risc-v64/gnu/example_build/erbium/plic.c +++ b/ports/risc-v64/gnu/example_build/erbium/plic.c @@ -29,21 +29,27 @@ static int plic_source_valid(int irqno) 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) diff --git a/ports/risc-v64/gnu/example_build/erbium/uart.c b/ports/risc-v64/gnu/example_build/erbium/uart.c index ee065e8a7..1b8f2dfc7 100644 --- a/ports/risc-v64/gnu/example_build/erbium/uart.c +++ b/ports/risc-v64/gnu/example_build/erbium/uart.c @@ -78,29 +78,32 @@ int uart_init(void) return 0; } -static inline void uart_putc_nolock(int ch) -{ - while ((UART_REG(UART_STATUS) & STATUS_TX_FULL) != 0u) - ; - UART_REG(UART_TX) = (uint32_t)(ch & 0xFF); -} - int uart_putc(int ch) { - int intr_enable = riscv_mintr_save(); - uart_putc_nolock(ch); - riscv_mintr_restore(intr_enable); - return 1; + 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; - int intr_enable = riscv_mintr_save(); for (i = 0; str[i] != 0; i++) - uart_putc_nolock(str[i]); - uart_putc_nolock('\r'); - uart_putc_nolock('\n'); - riscv_mintr_restore(intr_enable); + uart_putc(str[i]); + uart_putc('\r'); + uart_putc('\n'); return i; }