diff options
| author | Frédéric Desbiens <[email protected]> | 2026-10-02 10:42:44 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-10-02 10:42:44 -0400 |
| commit | 93387b0a60382a29579bbad5045c68328e3751df (patch) | |
| tree | 42be790d602cb76f55090e5a7c418df7ea2b3428 /ports/risc-v64/gnu/example_build/qemu_virt | |
| parent | 44d7c95c582d415c4ad84527180b29c93c3bf664 (diff) | |
| parent | 28281cd5647d4671236ca08d19bd18ed15b2265a (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')
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 |
