From f19d8c267d4b9f9495407f36e154c7ea6deea63a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Wed, 30 Sep 2026 09:40:59 -0400 Subject: [PATCH] Added simulator tests for Erbium interrupt regressions Long polling UART writes can mask timer interrupts across several tick periods. PLIC enable-word updates can overwrite a change made by an interrupt handler. The board example had no simulator tests for either case. Added two simulator images and a runner. One compares hardware timer progress with ThreadX ticks after a long print. The other toggles one PLIC source in timer context while a thread changes another. Both tests fail on the PR code and pass with the corresponding local fixes. GCC 15.2.0 and Ninja built both images. erbium_emu reproduced both failures; control runs passed both tests. Port consistency checks passed. No silicon run. Assisted-by: Codex (GPT-6-Sol) --- .../gnu/example_build/erbium/CMakeLists.txt | 25 ++++++ .../erbium/test/run_simulator_tests.sh | 55 ++++++++++++ .../erbium/test/test_plic_race.c | 90 +++++++++++++++++++ .../erbium/test/test_plic_trap.c | 55 ++++++++++++ .../erbium/test/test_uart_ticks.c | 81 +++++++++++++++++ 5 files changed, 306 insertions(+) create mode 100755 ports/risc-v64/gnu/example_build/erbium/test/run_simulator_tests.sh create mode 100644 ports/risc-v64/gnu/example_build/erbium/test/test_plic_race.c create mode 100644 ports/risc-v64/gnu/example_build/erbium/test/test_plic_trap.c create mode 100644 ports/risc-v64/gnu/example_build/erbium/test/test_uart_ticks.c diff --git a/ports/risc-v64/gnu/example_build/erbium/CMakeLists.txt b/ports/risc-v64/gnu/example_build/erbium/CMakeLists.txt index 244d7656..51b07714 100644 --- a/ports/risc-v64/gnu/example_build/erbium/CMakeLists.txt +++ b/ports/risc-v64/gnu/example_build/erbium/CMakeLists.txt @@ -41,3 +41,28 @@ target_link_options(demo_threadx PRIVATE -Wl,--gc-sections -Wl,-Map=${CMAKE_CURRENT_BINARY_DIR}/demo_threadx.map ) + +function(add_erbium_test name source handler) + add_executable(${name} + ${ERBIUM_DIR}/entry.S + ${ERBIUM_DIR}/tx_initialize_low_level.S + ${ERBIUM_DIR}/board.c + ${ERBIUM_DIR}/hwtimer.c + ${ERBIUM_DIR}/plic.c + ${ERBIUM_DIR}/uart.c + ${source} + ${handler} + ) + target_include_directories(${name} PRIVATE ${ERBIUM_DIR}) + target_link_libraries(${name} PRIVATE threadx) + target_compile_options(${name} PRIVATE -ffreestanding) + target_link_options(${name} PRIVATE + -T${ERBIUM_DIR}/link.lds + -nostdlib + -Wl,--gc-sections + ) + set_target_properties(${name} PROPERTIES LINK_DEPENDS ${ERBIUM_DIR}/link.lds) +endfunction() + +add_erbium_test(test_uart_ticks ${ERBIUM_DIR}/test/test_uart_ticks.c ${ERBIUM_DIR}/trap.c) +add_erbium_test(test_plic_race ${ERBIUM_DIR}/test/test_plic_race.c ${ERBIUM_DIR}/test/test_plic_trap.c) diff --git a/ports/risc-v64/gnu/example_build/erbium/test/run_simulator_tests.sh b/ports/risc-v64/gnu/example_build/erbium/test/run_simulator_tests.sh new file mode 100755 index 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 + +#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); +}