summaryrefslogtreecommitdiff
path: root/ports/risc-v32/gnu/example_build/qemu_virt
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-v32/gnu/example_build/qemu_virt
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-v32/gnu/example_build/qemu_virt')
-rw-r--r--ports/risc-v32/gnu/example_build/qemu_virt/.gitignore4
-rw-r--r--ports/risc-v32/gnu/example_build/qemu_virt/CMakeLists.txt24
-rw-r--r--ports/risc-v32/gnu/example_build/qemu_virt/board.c14
-rw-r--r--ports/risc-v32/gnu/example_build/qemu_virt/demo_threadx.c4
-rw-r--r--ports/risc-v32/gnu/example_build/qemu_virt/hwtimer.c69
-rw-r--r--ports/risc-v32/gnu/example_build/qemu_virt/hwtimer.h4
-rw-r--r--ports/risc-v32/gnu/example_build/qemu_virt/link.lds21
-rw-r--r--ports/risc-v32/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv32_qemu.py277
-rw-r--r--ports/risc-v32/gnu/example_build/qemu_virt/tx_initialize_low_level.S21
9 files changed, 311 insertions, 127 deletions
diff --git a/ports/risc-v32/gnu/example_build/qemu_virt/.gitignore b/ports/risc-v32/gnu/example_build/qemu_virt/.gitignore
new file mode 100644
index 00000000..18a4075c
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/qemu_virt/.gitignore
@@ -0,0 +1,4 @@
+# Ignore generated QEMU test files.
+kernel.elf
+qemu-riscv32.log
+test_cmds.gdb
diff --git a/ports/risc-v32/gnu/example_build/qemu_virt/CMakeLists.txt b/ports/risc-v32/gnu/example_build/qemu_virt/CMakeLists.txt
index 43e757ac..cbf67798 100644
--- a/ports/risc-v32/gnu/example_build/qemu_virt/CMakeLists.txt
+++ b/ports/risc-v32/gnu/example_build/qemu_virt/CMakeLists.txt
@@ -11,7 +11,7 @@ add_executable(kernel.elf EXCLUDE_FROM_ALL
${QEMU_DEMO_DIR}/tx_initialize_low_level.S
)
-target_link_libraries(kernel.elf PRIVATE threadx)
+target_link_libraries(kernel.elf PRIVATE threadx gcc)
target_include_directories(kernel.elf PRIVATE
${CMAKE_SOURCE_DIR}/common/inc
@@ -28,18 +28,32 @@ target_link_options(kernel.elf PRIVATE
# QEMU/GDB functional test runner. Optional: skipped silently if the
# host has no Python 3 interpreter on PATH.
find_package(Python3 COMPONENTS Interpreter)
-if(Python3_FOUND)
+# The runner needs a GDB that understands riscv:rv32 remote targets
+find_program(RISCV32_GDB NAMES riscv32-unknown-elf-gdb gdb-multiarch)
+find_program(RISCV32_QEMU NAMES qemu-system-riscv32)
+if(Python3_FOUND AND RISCV32_GDB AND RISCV32_QEMU)
+ set(RISCV32_QEMU_TEST_ARGS)
+ if(SOFT_FLOAT)
+ list(APPEND RISCV32_QEMU_TEST_ARGS --skip-fpu)
+ endif()
add_custom_target(check-functional-riscv32
COMMAND ${Python3_EXECUTABLE}
${QEMU_DEMO_DIR}/test/threadx_test_tx_gnu_riscv32_qemu.py
--elf $<TARGET_FILE:kernel.elf>
- --qemu qemu-system-riscv32
- --gdb gdb
+ --qemu ${RISCV32_QEMU}
+ --gdb ${RISCV32_GDB}
+ ${RISCV32_QEMU_TEST_ARGS}
DEPENDS kernel.elf
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "Running RISC-V32 QEMU/GDB functional test runner..."
)
-else()
+elseif(NOT Python3_FOUND)
message(STATUS
"Python3 not found; check-functional-riscv32 target unavailable.")
+elseif(NOT RISCV32_GDB)
+ message(STATUS
+ "RISC-V GDB not found; check-functional-riscv32 target unavailable.")
+else()
+ message(STATUS
+ "qemu-system-riscv32 not found; check-functional-riscv32 target unavailable.")
endif()
diff --git a/ports/risc-v32/gnu/example_build/qemu_virt/board.c b/ports/risc-v32/gnu/example_build/qemu_virt/board.c
index fc75de66..4d0056a8 100644
--- a/ports/risc-v32/gnu/example_build/qemu_virt/board.c
+++ b/ports/risc-v32/gnu/example_build/qemu_virt/board.c
@@ -14,15 +14,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-v32/gnu/example_build/qemu_virt/demo_threadx.c b/ports/risc-v32/gnu/example_build/qemu_virt/demo_threadx.c
index 8288e749..c88ac9e2 100644
--- a/ports/risc-v32/gnu/example_build/qemu_virt/demo_threadx.c
+++ b/ports/risc-v32/gnu/example_build/qemu_virt/demo_threadx.c
@@ -20,7 +20,9 @@
#define DEMO_BYTE_POOL_SIZE 9120
#define DEMO_BLOCK_POOL_SIZE 100
#define DEMO_QUEUE_SIZE 100
+#ifdef __riscv_flen
float fpu_test_val = 0.0f;
+#endif
char *_to_str(ULONG val)
{
@@ -375,8 +377,10 @@ UINT status;
if (status != TX_SUCCESS)
break;
+#ifdef __riscv_flen
/* FPU Test */
fpu_test_val += 1.1f;
+#endif
/* 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-v32/gnu/example_build/qemu_virt/hwtimer.c b/ports/risc-v32/gnu/example_build/qemu_virt/hwtimer.c
index 33c49b63..8d2f4e15 100644
--- a/ports/risc-v32/gnu/example_build/qemu_virt/hwtimer.c
+++ b/ports/risc-v32/gnu/example_build/qemu_virt/hwtimer.c
@@ -8,7 +8,7 @@
* SPDX-License-Identifier: MIT
**************************************************************************/
-#include "tx_port.h"
+#include "tx_api.h"
#include "csr.h"
#include "hwtimer.h"
@@ -16,20 +16,69 @@
#define CLINT_TIME (CLINT+0xBFF8)
#define CLINT_TIMECMP(hart_id) (CLINT+0x4000+8*(hart_id))
+/* RV32 has no 64-bit load/store, so the 64-bit CLINT registers are
+ accessed as two volatile 32-bit MMIO words. */
+#define MTIME_LO (*(volatile uint32_t *)(CLINT_TIME))
+#define MTIME_HI (*(volatile uint32_t *)(CLINT_TIME + 4))
+#define MTIMECMP_LO(hart) (*(volatile uint32_t *)(CLINT_TIMECMP(hart)))
+#define MTIMECMP_HI(hart) (*(volatile uint32_t *)(CLINT_TIMECMP(hart) + 4))
+
+/* RV32 mtime re-read the high word until it is stable, so a
+ low-word rollover between the two loads cannot tear the value. */
+static uint64_t clint_time_read(void)
+{
+ uint32_t hi;
+ uint32_t lo;
+
+ do
+ {
+ hi = MTIME_HI;
+ lo = MTIME_LO;
+ } while (hi != MTIME_HI);
+
+ return ((uint64_t)hi << 32) | lo;
+}
+
+/* Only this hart writes its own mtimecmp, so a plain two-word read
+ cannot tear. */
+static uint64_t clint_timecmp_read(int hart)
+{
+ uint32_t lo = MTIMECMP_LO(hart);
+ uint32_t hi = MTIMECMP_HI(hart);
+
+ return ((uint64_t)hi << 32) | lo;
+}
+
+/* RV32 mtimecmp write, safe sequence: park the low word at all-ones
+ first, so no intermediate 64-bit value compares below mtime and
+ raises a spurious timer interrupt. */
+static void clint_timecmp_write(int hart, uint64_t value)
+{
+ MTIMECMP_LO(hart) = 0xFFFFFFFF;
+ MTIMECMP_HI(hart) = (uint32_t)(value >> 32);
+ MTIMECMP_LO(hart) = (uint32_t)value;
+}
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();
+
+ clint_timecmp_write(hart, clint_time_read() + 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 = clint_timecmp_read(hart) + TICKNUM_PER_TIMER;
+ uint64_t now = clint_time_read();
+ if (next <= now)
+ next = now + TICKNUM_PER_TIMER;
+
+ clint_timecmp_write(hart, next);
+ return 0;
+}
diff --git a/ports/risc-v32/gnu/example_build/qemu_virt/hwtimer.h b/ports/risc-v32/gnu/example_build/qemu_virt/hwtimer.h
index 966b1abf..7bc2d0e3 100644
--- a/ports/risc-v32/gnu/example_build/qemu_virt/hwtimer.h
+++ b/ports/risc-v32/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);
int hwtimer_handler(void);
diff --git a/ports/risc-v32/gnu/example_build/qemu_virt/link.lds b/ports/risc-v32/gnu/example_build/qemu_virt/link.lds
index d2ef5f28..42f5d366 100644
--- a/ports/risc-v32/gnu/example_build/qemu_virt/link.lds
+++ b/ports/risc-v32/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
{
/*
@@ -14,15 +20,16 @@ SECTIONS
*(.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);
/* Centre __global_pointer$ in the small-data window so the +/-2 KiB
@@ -31,23 +38,23 @@ SECTIONS
*(.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;
_sysstack_end = .;
- }
+ } :data
PROVIDE(_end = .);
}
diff --git a/ports/risc-v32/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv32_qemu.py b/ports/risc-v32/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv32_qemu.py
index 6123cd73..bc97e745 100644
--- a/ports/risc-v32/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv32_qemu.py
+++ b/ports/risc-v32/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv32_qemu.py
@@ -17,134 +17,186 @@ import socket
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
+ """Write content to nonblocking stdout with os.write."""
+ 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."""
+ """Get an unused TCP port."""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('', 0))
return s.getsockname()[1]
-def run_qemu_test(elf_path, qemu_bin, gdb_bin):
- """
- Runs a test cycle using QEMU and GDB.
- """
+def run_qemu_test(elf_path, qemu_bin, gdb_bin, skip_fpu):
+ """Run one QEMU and GDB test cycle."""
print(f"Testing ELF: {elf_path}")
print(f"QEMU: {qemu_bin}")
print(f"GDB: {gdb_bin}")
- # Find a free port for GDB connection
+ # Get an unused GDB port.
gdb_port = get_free_port()
print(f"Using GDB port: {gdb_port}")
- # 1. Start QEMU in the background
+ # Start QEMU and wait for GDB.
+ GDB_TIMEOUT_S = 120
+ qemu_log_path = os.path.abspath("qemu-riscv32.log")
qemu_cmd = [
qemu_bin,
"-M", "virt",
"-nographic",
- "-bios", "none", # Disable default OpenSBI to avoid overlap at 0x80000000
+ "-bios", "none", # Start without OpenSBI at 0x80000000.
"-kernel", elf_path,
"-gdb", f"tcp::{gdb_port}", "-S",
- "-monitor", "none", # Disable monitor to avoid clutter
- "-serial", "stdio" # Redirect serial output to stdio so we can see it
+ "-monitor", "none", # Disable the QEMU monitor.
+ "-serial", "stdio" # Send serial output to stdout.
]
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
- # We use a defined command for the timer interrupt to perform the check automatically
+ # Create the GDB command file.
gdb_cmds = """
file {elf}
target remote :{port}
set pagination off
set confirm off
-# Setup Breakpoints
+# Set breakpoints.
break tx_application_define
break thread_0_entry
break thread_6_and_7_entry
break _tx_timer_interrupt
-# Execute to Application Definition
+disable 4
+
+# Run to the application setup.
continue
-# Inspect mstatus once thread_0 has started (FS bits should be observable;
-# kept as a smoke check, the lazy-save logic itself is targeted by a
-# follow-up PR).
+
continue
print/x $mstatus
-
-# Verify FPU Logic and Register State exercised by thread_6/7
continue
-finish
-step
-step
-step
+
+# Check for fpu_test_val before GDB uses it.
+# An absent symbol fails the required FPU test.
+python gdb.execute("set $fpu_sym = %d" % (1 if gdb.lookup_global_symbol("fpu_test_val") else 0))
+set $fpu_required = {fpu_required}
+
+set $fpu_iter = 0
+if $fpu_sym != 0
+ set $fpu_before = fpu_test_val
+ # Continue until one FPU addition completes.
+ # Limit the wait to 64 thread passes.
+ 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
+ if $fpu_required != 0
+ printf "FPU_VERIFIED_FAIL_NO_SYMBOL\\n"
+ else
+ printf "FPU_VERIFIED_SKIP_SOFT_FLOAT\\n"
+ end
+else
+ print fpu_test_val
+ # Check for a multiple of 1.1f with a 0.01 tolerance.
+ 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
+
+# Enable only the timer breakpoint.
+# Then $ra contains the ISR return address.
+disable 1
+disable 2
+disable 3
+enable 4
continue
print "Hit Timer Interrupt"
-# Verify MEPC Integrity - Save State
+# Save mepc at timer entry.
print/x $mepc
set $saved_pc = $mepc
-# Verify System Timer Before ISR
+# Read the system clock before the ISR completes.
set $clock_before = _tx_timer_system_clock
print $clock_before
-# Configure Time-Slice Test Conditions
+# Force time-slice processing.
set _tx_timer_time_slice = 1
set _tx_timer_expired_time_slice = 0
set $ts_handler_called = 0
-# Set Breakpoint at Time-Slice Handler with Auto-Continue
+# Record time-slice handler calls.
tbreak _tx_thread_time_slice
commands
set $ts_handler_called = 1
continue
end
-# Set Breakpoint at ISR Return Address
+# Stop at the ISR return address.
+# Disable the timer breakpoint during this interval.
set $ret_addr = $ra
+disable 4
tbreak *$ret_addr
continue
-# Verify Time-Slice Handler Was Called
+# Check the live CSR and the saved frame PC.
+set $frame_pc = ((unsigned int *)_tx_thread_current_ptr->tx_thread_stack_ptr)[30]
+if $mepc == $saved_pc && $frame_pc == $saved_pc
+ printf "MEPC_VERIFIED_OK mepc=0x%x frame_pc=0x%x\\n", $mepc, $frame_pc
+else
+ printf "MEPC_VERIFIED_FAIL saved=0x%x mepc=0x%x frame_pc=0x%x\\n", $saved_pc, $mepc, $frame_pc
+end
+
+# Check time-slice handler execution.
if $ts_handler_called == 1
print "SUCCESS: Time-slice handler called."
else
print "FAILURE: Time-slice handler NOT called."
end
-# Verify System Timer Increment (Monotonicity)
+# Check that the system clock increased.
set $clock_after = _tx_timer_system_clock
print $clock_after
@@ -154,37 +206,56 @@ else
print "FAILURE: System timer did not increment."
end
-# Verify Preemption Logic (Thread Priority)
-#
-# We are now stopped at the return address from _tx_timer_interrupt,
-# after _tx_thread_time_slice has had a chance to update
-# _tx_thread_execute_ptr but before trap_handler returns into
-# _tx_thread_context_restore. At this point, a pending preemption is
-# observable directly by comparing current_ptr (interrupted thread)
-# and execute_ptr (thread chosen by the scheduler).
-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"
+# Compare the current and selected thread priorities at the ISR return.
+# Check each tick until preemption occurs or 250 ticks expire.
+# Each retry uses the same ISR point and starts a new time slice.
+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
-""".format(port=gdb_port, elf=elf_path)
+""".format(port=gdb_port, elf=elf_path, fpu_required=0 if skip_fpu else 1)
gdb_cmd_file = "test_cmds.gdb"
with open(gdb_cmd_file, "w") as f:
f.write(gdb_cmds)
- # 3. Run GDB
+ # Run GDB in batch mode.
gdb_cmd = [
gdb_bin,
"--batch",
@@ -193,10 +264,8 @@ quit
print_content(f"Starting GDB: {' '.join(gdb_cmd)}")
- # Cap the GDB session to 30 s so a wedged batch script (e.g. a
- # `continue` that never hits its breakpoint) cannot hang CI.
- GDB_TIMEOUT_S = 30
-
+ # Limit GDB to 120 seconds.
+ # The loop uses at most 250 ticks and two gdbstub exchanges for each tick.
try:
gdb_process = subprocess.run(
gdb_cmd,
@@ -232,25 +301,46 @@ quit
return False
finally:
- # 4. Clean up
+ # Stop QEMU and close its log.
print_content("Stopping QEMU...")
qemu_process.terminate()
try:
qemu_process.wait(timeout=2)
except subprocess.TimeoutExpired:
- print_content("QEMU did not terminate gracefully, killing it forcefullly.")
+ print_content("QEMU did not terminate gracefully, killing it forcefully.")
qemu_process.kill()
+ qemu_process.wait()
+ qemu_log.close()
- # Verify results
+ # Check each required test result.
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 skip_fpu and "FPU_VERIFIED_SKIP_SOFT_FLOAT" in stdout:
+ print_content("SUCCESS: Soft-float mode omitted the hardware FPU check.")
+ fpu_verified = True
+ elif skip_fpu:
+ print_content("FAILURE: Soft-float build did not report the FPU skip, so "
+ "the GDB script did not run to that point.")
+ elif "Breakpoint 3, thread_6_and_7_entry" 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.")
@@ -259,6 +349,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).")
@@ -274,7 +379,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
@@ -284,10 +391,12 @@ if __name__ == "__main__":
parser.add_argument("--elf", required=True, help="Path to the kernel ELF file")
parser.add_argument("--qemu", default="qemu-system-riscv32", help="Path to QEMU binary")
parser.add_argument("--gdb", default="riscv-none-elf-gdb", help="Path to GDB binary")
+ parser.add_argument("--skip-fpu", action="store_true",
+ help="Skip the hardware FPU check for a soft-float build")
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.skip_fpu)
if success:
sys.exit(0)
diff --git a/ports/risc-v32/gnu/example_build/qemu_virt/tx_initialize_low_level.S b/ports/risc-v32/gnu/example_build/qemu_virt/tx_initialize_low_level.S
index 9a7a74ff..9cda8096 100644
--- a/ports/risc-v32/gnu/example_build/qemu_virt/tx_initialize_low_level.S
+++ b/ports/risc-v32/gnu/example_build/qemu_virt/tx_initialize_low_level.S
@@ -59,12 +59,12 @@
.global trap_entry
.extern trap_handler
.extern _tx_thread_context_restore
+/* Trap frame: integer slots 0-31 at i*4; FP build adds f0-f31 as doubles
+ at 128 + i*8, fcsr at 384, pad to 400 (total mod 16 = 0). */
+#include "tx_port.h"
+ .equ TX_TRAP_FRAME_SIZE, TX_RISCV_TRAP_FRAME_SIZE
trap_entry:
-#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
- addi sp, sp, -260 // Allocate space for all registers - with floating point enabled (65*4)
-#else
- addi sp, sp, -128 // Allocate space for all registers - without floating point enabled (32*4)
-#endif
+ addi sp, sp, -TX_TRAP_FRAME_SIZE // Allocate space for all registers (400 with FP double, 128 without)
sw x1, 112(sp) // Store RA (28*4 = 112, because call will override ra [ra is a callee register in riscv])
@@ -73,11 +73,11 @@
csrr a0, mcause
csrr a1, mepc
csrr a2, mtval
- addi sp, sp, -4
+ addi sp, sp, -16 // 16-byte temporary frame: ilp32 psABI requires sp = 0 mod 16 at the call
sw ra, 0(sp)
call trap_handler
lw ra, 0(sp)
- addi sp, sp, 4
+ addi sp, sp, 16
call _tx_thread_context_restore
// it will nerver return
_err:
@@ -128,7 +128,6 @@ _err:
/* VOID _tx_initialize_low_level(VOID)
{ */
.global _tx_initialize_low_level
- .weak _tx_initialize_low_level
.extern _end
.extern board_init
_tx_initialize_low_level:
@@ -147,16 +146,16 @@ _tx_initialize_low_level:
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
+#if 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
#endif
- addi sp, sp, -4
+ addi sp, sp, -16 // 16-byte temporary frame: ilp32 psABI requires sp = 0 mod 16 at the call
sw ra, 0(sp)
call board_init
lw ra, 0(sp)
- addi sp, sp, 4
+ addi sp, sp, 16
la t0, trap_entry
csrw mtvec, t0
ret