mirror of
https://github.com/eclipse-threadx/threadx.git
synced 2026-10-06 06:59:08 +08:00
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) <noreply@openai.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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++;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user