summaryrefslogtreecommitdiff
path: root/ports/risc-v64
diff options
context:
space:
mode:
authorFrédéric Desbiens <[email protected]>2026-10-02 10:42:44 -0400
committerGitHub <[email protected]>2026-10-02 10:42:44 -0400
commit93387b0a60382a29579bbad5045c68328e3751df (patch)
tree42be790d602cb76f55090e5a7c418df7ea2b3428 /ports/risc-v64
parent44d7c95c582d415c4ad84527180b29c93c3bf664 (diff)
parent28281cd5647d4671236ca08d19bd18ed15b2265a (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')
-rw-r--r--ports/risc-v64/gnu/CMakeLists.txt1
-rw-r--r--ports/risc-v64/gnu/example_build/erbium/CMakeLists.txt68
-rw-r--r--ports/risc-v64/gnu/example_build/erbium/README.md140
-rw-r--r--ports/risc-v64/gnu/example_build/erbium/board.c49
-rwxr-xr-xports/risc-v64/gnu/example_build/erbium/build.sh28
-rw-r--r--ports/risc-v64/gnu/example_build/erbium/csr.h66
-rw-r--r--ports/risc-v64/gnu/example_build/erbium/demo_threadx.c382
-rw-r--r--ports/risc-v64/gnu/example_build/erbium/entry.S50
-rw-r--r--ports/risc-v64/gnu/example_build/erbium/erbium_gnu.cmake39
-rw-r--r--ports/risc-v64/gnu/example_build/erbium/hwtimer.c42
-rw-r--r--ports/risc-v64/gnu/example_build/erbium/hwtimer.h41
-rw-r--r--ports/risc-v64/gnu/example_build/erbium/link.lds92
-rw-r--r--ports/risc-v64/gnu/example_build/erbium/plic.c118
-rw-r--r--ports/risc-v64/gnu/example_build/erbium/plic.h54
-rwxr-xr-xports/risc-v64/gnu/example_build/erbium/test/run_simulator_tests.sh55
-rw-r--r--ports/risc-v64/gnu/example_build/erbium/test/test_plic_race.c90
-rw-r--r--ports/risc-v64/gnu/example_build/erbium/test/test_plic_trap.c55
-rw-r--r--ports/risc-v64/gnu/example_build/erbium/test/test_uart_ticks.c81
-rw-r--r--ports/risc-v64/gnu/example_build/erbium/trap.c78
-rw-r--r--ports/risc-v64/gnu/example_build/erbium/tx_initialize_low_level.S142
-rw-r--r--ports/risc-v64/gnu/example_build/erbium/uart.c109
-rw-r--r--ports/risc-v64/gnu/example_build/erbium/uart.h40
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/.gitignore4
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/CMakeLists.txt51
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/board.c14
-rwxr-xr-xports/risc-v64/gnu/example_build/qemu_virt/build_libthreadx.sh28
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/demo_threadx.c3
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/entry.S7
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/hwtimer.c31
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/hwtimer.h4
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/link.lds27
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv64_qemu.py228
-rw-r--r--ports/risc-v64/gnu/example_build/qemu_virt/tx_initialize_low_level.S31
-rw-r--r--ports/risc-v64/gnu/inc/tx_port.h57
-rw-r--r--ports/risc-v64/gnu/readme_threadx.txt9
-rw-r--r--ports/risc-v64/gnu/src/tx_initialize_low_level.S16
-rw-r--r--ports/risc-v64/gnu/src/tx_thread_context_restore.S142
-rw-r--r--ports/risc-v64/gnu/src/tx_thread_context_save.S90
-rw-r--r--ports/risc-v64/gnu/src/tx_thread_interrupt_control.S32
-rw-r--r--ports/risc-v64/gnu/src/tx_thread_schedule.S47
-rw-r--r--ports/risc-v64/gnu/src/tx_thread_stack_build.S19
-rw-r--r--ports/risc-v64/gnu/src/tx_thread_system_return.S12
-rw-r--r--ports/risc-v64/gnu/src/tx_timer_interrupt.S4
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. */