diff options
Diffstat (limited to 'ports/risc-v32/gnu/example_build')
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 |
