summaryrefslogtreecommitdiff
path: root/ports/risc-v64/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-v64/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-v64/gnu/example_build/qemu_virt')
-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
11 files changed, 317 insertions, 111 deletions
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