diff --git a/patch/keystone/0001-CMake-Add-support-for-modern-CMake.patch b/patch/keystone/0001-CMake-Add-support-for-modern-CMake.patch deleted file mode 100644 index 833cef91..00000000 --- a/patch/keystone/0001-CMake-Add-support-for-modern-CMake.patch +++ /dev/null @@ -1,120 +0,0 @@ -From f7cf646a865d469f9abcdc49ae36d1f1b1077af4 Mon Sep 17 00:00:00 2001 -From: Jerome Haxhiaj -Date: Wed, 10 Dec 2025 09:20:21 +0100 -Subject: [PATCH 1/2] CMake: Add support for modern CMake - -Signed-off-by: Jerome Haxhiaj ---- - CMakeLists.txt | 35 ++++++++++------------------------- - kstool/CMakeLists.txt | 2 +- - llvm/CMakeLists.txt | 15 +-------------- - 3 files changed, 12 insertions(+), 40 deletions(-) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index eb52c19..32f1bff 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -1,7 +1,7 @@ - # Keystone Assembler Engine (www.keystone-engine.org) - # By Nguyen Anh Quynh, 2016 - --cmake_minimum_required(VERSION 2.8.7) -+cmake_minimum_required(VERSION 3.10.0) - project(keystone) - - set(KEYSTONE_VERSION_MAJOR 0) -@@ -14,23 +14,6 @@ if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - set(CMAKE_BUILD_TYPE "Debug") - endif() - --if (POLICY CMP0022) -- cmake_policy(SET CMP0022 NEW) # automatic when 2.8.12 is required --endif() -- --if (POLICY CMP0051) -- # CMake 3.1 and higher include generator expressions of the form -- # $ in the SOURCES property. These need to be -- # stripped everywhere that access the SOURCES property, so we just -- # defer to the OLD behavior of not including generator expressions -- # in the output for now. -- cmake_policy(SET CMP0051 OLD) --endif() -- --if (POLICY CMP0063) -- set(CMAKE_POLICY_DEFAULT_CMP0063 NEW) # automatic when 3.3.2 is required --endif() -- - if (CMAKE_VERSION VERSION_LESS 3.1.20141117) - set(cmake_3_2_USES_TERMINAL) - else() -@@ -107,12 +90,14 @@ INSTALL(FILES "${PKG_CONFIG_FILE_PATH}" - DESTINATION lib${LLVM_LIBDIR_SUFFIX}/pkgconfig) - - # uninstall target --configure_file( -- "${CMAKE_CURRENT_SOURCE_DIR}/CMakeUninstall.in" -- "${CMAKE_CURRENT_BINARY_DIR}/CMakeUninstall.cmake" -- IMMEDIATE @ONLY) -- --add_custom_target(uninstall -- COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/CMakeUninstall.cmake) -+if(NOT TARGET uninstall) -+ configure_file( -+ "${CMAKE_CURRENT_SOURCE_DIR}/CMakeUninstall.in" -+ "${CMAKE_CURRENT_BINARY_DIR}/CMakeUninstall.cmake" -+ IMMEDIATE @ONLY) -+ -+ add_custom_target(uninstall -+ COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/CMakeUninstall.cmake) -+endif() - - add_subdirectory(suite/fuzz) -\ No newline at end of file -diff --git a/kstool/CMakeLists.txt b/kstool/CMakeLists.txt -index d28da48..2511382 100644 ---- a/kstool/CMakeLists.txt -+++ b/kstool/CMakeLists.txt -@@ -1,7 +1,7 @@ - # Kstool for Keystone assembler engine. - # By Nguyen Anh Quynh, 2016 - --cmake_minimum_required(VERSION 2.8) -+cmake_minimum_required(VERSION 3.10.0) - - project(kstool) - -diff --git a/llvm/CMakeLists.txt b/llvm/CMakeLists.txt -index e324ef3..8ef8e27 100644 ---- a/llvm/CMakeLists.txt -+++ b/llvm/CMakeLists.txt -@@ -1,6 +1,6 @@ - # See docs/CMake.html for instructions about how to build LLVM with CMake. - --cmake_minimum_required(VERSION 2.8.7) -+cmake_minimum_required(VERSION 3.10.0) - - set(LLVM_INSTALL_TOOLCHAIN_ONLY ON) - -@@ -9,19 +9,6 @@ if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - set(CMAKE_BUILD_TYPE "Debug") - endif() - --if(POLICY CMP0022) -- cmake_policy(SET CMP0022 NEW) # automatic when 2.8.12 is required --endif() -- --if (POLICY CMP0051) -- # CMake 3.1 and higher include generator expressions of the form -- # $ in the SOURCES property. These need to be -- # stripped everywhere that access the SOURCES property, so we just -- # defer to the OLD behavior of not including generator expressions -- # in the output for now. -- cmake_policy(SET CMP0051 OLD) --endif() -- - if(CMAKE_VERSION VERSION_LESS 3.1.20141117) - set(cmake_3_2_USES_TERMINAL) - else() --- -2.54.0 - diff --git a/patch/keystone/0002-Honor-parent-project-s-CMAKE_RUNTIME_OUTPUT_DIRECTOR.patch b/patch/keystone/0002-Honor-parent-project-s-CMAKE_RUNTIME_OUTPUT_DIRECTOR.patch deleted file mode 100644 index 5676f804..00000000 --- a/patch/keystone/0002-Honor-parent-project-s-CMAKE_RUNTIME_OUTPUT_DIRECTOR.patch +++ /dev/null @@ -1,47 +0,0 @@ -From bc790fabb24e6ed9a6d284f280a8876788850431 Mon Sep 17 00:00:00 2001 -From: Jerome Haxhiaj -Date: Mon, 4 May 2026 21:56:50 +0200 -Subject: [PATCH 2/2] Honor parent project's CMAKE_RUNTIME_OUTPUT_DIRECTORY - -LLVM_RUNTIME_OUTPUT_INTDIR is what set_output_directory() actually uses -to place shared libraries. When a parent project has already set -CMAKE_RUNTIME_OUTPUT_DIRECTORY, use that value so DLLs land alongside -the rest of the build tree instead of in keystone's private bin/ dir. - -Signed-off-by: Jerome Haxhiaj ---- - llvm/CMakeLists.txt | 11 +++++++++-- - 1 file changed, 9 insertions(+), 2 deletions(-) - -diff --git a/llvm/CMakeLists.txt b/llvm/CMakeLists.txt -index 8ef8e27..f90e7e5 100644 ---- a/llvm/CMakeLists.txt -+++ b/llvm/CMakeLists.txt -@@ -136,7 +136,11 @@ endif() - set(LLVM_LIBDIR_SUFFIX "" CACHE STRING "Define suffix of library directory name (32/64)" ) - - # They are used as destination of target generators. --set(LLVM_RUNTIME_OUTPUT_INTDIR ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/bin) -+if(CMAKE_RUNTIME_OUTPUT_DIRECTORY) -+ set(LLVM_RUNTIME_OUTPUT_INTDIR ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) -+else() -+ set(LLVM_RUNTIME_OUTPUT_INTDIR ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/bin) -+endif() - set(LLVM_LIBRARY_OUTPUT_INTDIR ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/lib${LLVM_LIBDIR_SUFFIX}) - if(WIN32 OR CYGWIN) - # DLL platform -- put DLLs into bin. -@@ -436,7 +440,10 @@ configure_file( - ${LLVM_INCLUDE_DIR}/llvm/Support/DataTypes.h) - - # They are not referenced. See set_output_directory(). --set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${LLVM_BINARY_DIR}/bin ) -+if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) -+ set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${LLVM_BINARY_DIR}/bin ) -+endif (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) -+ - set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${LLVM_BINARY_DIR}/lib${LLVM_LIBDIR_SUFFIX} ) - set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${LLVM_BINARY_DIR}/lib${LLVM_LIBDIR_SUFFIX} ) - --- -2.54.0 - diff --git a/scripts/install_dependencies.sh b/scripts/install_dependencies.sh index 3a41c61a..903c0e33 100755 --- a/scripts/install_dependencies.sh +++ b/scripts/install_dependencies.sh @@ -19,19 +19,19 @@ if { [ -e /etc/os-release ] && OS_RELEASE="/etc/os-release"; } || \ if [ "${VERSION_ID}" = "24.04" ]; then apt install cmake g++ gcc git libasio-dev libelf-dev libepoxy-dev \ libglib2.0-dev libjpeg-dev libpixman-1-dev libsdl2-dev \ - libslirp-dev libasio-dev libvirglrenderer-dev meson \ + libslirp-dev libasio-dev libvirglrenderer-dev lld llvm meson \ ninja-build ocl-icd-opencl-dev python3 python3-dev \ python3-venv elif [ "${VERSION_ID}" = "22.04" ]; then apt install cmake g++ gcc git libasio-dev libelf-dev libepoxy-dev \ libglib2.0-dev libjpeg-dev libpixman-1-dev libsdl2-dev \ - libslirp-dev libasio-dev libvirglrenderer-dev meson \ + libslirp-dev libasio-dev libvirglrenderer-dev lld llvm meson \ ninja-build ocl-icd-opencl-dev python3 python3-dev \ python3-tomli python3-venv elif [ "${VERSION_ID}" = "20.04" ]; then apt install cmake g++ gcc git libasio-dev libelf-dev libepoxy-dev \ libglib2.0-dev libjpeg-dev libpixman-1-dev libsdl2-dev \ - libslirp-dev libasio-dev libvirglrenderer-dev meson \ + libslirp-dev libasio-dev libvirglrenderer-dev lld llvm meson \ ninja-build ocl-icd-opencl-dev python3 python3-dev \ python3-pip python3-venv pip install --user tomli @@ -45,8 +45,8 @@ if { [ -e /etc/os-release ] && OS_RELEASE="/etc/os-release"; } || \ # Pacboy handle package prefixes automatically pacboy -S --noconfirm --needed \ - toolchain cmake gtk3 libnfs libssh meson ninja pixman pkgconf \ - python python-pexpect SDL2 zstd libelf asio libslirp + toolchain cmake gtk3 libnfs libssh lld llvm meson ninja pixman \ + pkgconf python python-pexpect SDL2 zstd libelf asio libslirp fi else case "$(uname)" in @@ -55,12 +55,12 @@ else readonly MAJOR_VERSION if [ "${MAJOR_VERSION}" = "26" ]; then - brew install asio bison cmake jpeg libelf libslirp meson ninja \ - python3 sdl2 + brew install asio bison cmake jpeg libelf libslirp lld llvm \ + meson ninja python3 sdl2 pip install --user numpy pexpect elif [ "${MAJOR_VERSION}" = "15" ]; then - brew install asio bison cmake jpeg libelf libslirp meson ninja \ - python3 sdl2 + brew install asio bison cmake jpeg libelf libslirp lld llvm \ + meson ninja python3 sdl2 pip install --user numpy pexpect else unsupported_operating_system diff --git a/tests/components/uart/uart-ibex-biflow-stdio-test.cc b/tests/components/uart/uart-ibex-biflow-stdio-test.cc index adb4d6ab..cfe7bd3f 100644 --- a/tests/components/uart/uart-ibex-biflow-stdio-test.cc +++ b/tests/components/uart/uart-ibex-biflow-stdio-test.cc @@ -109,7 +109,7 @@ TEST_BENCH(TestUart, IbexStdioBackend) for (std::string::iterator itr = str.begin(); itr != str.end(); ++itr) { m_initiator_1.do_write(ibex_uart::REG_WDATA, *itr); // data - sc_core::wait(1, sc_core::SC_NS); // wait for txn transication + sc_core::wait(1, sc_core::SC_NS); // wait for txn transication } fflush(stdout); diff --git a/tests/qbox/CMakeLists.txt b/tests/qbox/CMakeLists.txt index a3c4eb58..2288ac96 100644 --- a/tests/qbox/CMakeLists.txt +++ b/tests/qbox/CMakeLists.txt @@ -22,6 +22,7 @@ list(APPEND QBOX_CPU_TEST_NUM_CPU_COMBINATION "riscv.*num-cpu=[^2]" # reduce redundency "hexagon.*num-cpu=[^14]" # reduce redundency "hexagon-smmu-stress-test-v2:.*num-cpu=[^1].*:threading=COROUTINE" # takes too long to execute + "hexagon-smmu-stress-test-v2:.*num-cpu=[^1].*:threading=MULTI" # takes too long to execute "hexagon-smmu-stress-test-v2:.*num-cpu=[^1].*:threading=SINGLE" # takes too long to execute # "num-cpu=32:.*:accel=hvf" # takes too long # "num-cpu=32:.*:accel=kvm" # Due to sync issues. @@ -33,6 +34,7 @@ list(APPEND QBOX_CPU_TEST_NUM_CPU_COMBINATION "aarch64-ld-st-excl-fail-test:.*:accel=(hvf|kvm)" # Test fails due to ld/st ex "reset-test-cpu:.*:accel=hvf" # Test fails "aarch64-smmu-router-stress-test-v2:.*:num-cpu=1:.*:accel=hvf" # Doesn't start with only one CPU + "aarch64-dmi-test-reset:.*num-cpu=(4|32)" # Intermittent SEGFAULT on Ubuntu (flaky, num-cpu>2) # Nonsensical tests "hexagon-reset-test.*num-cpu=[^1]" # only works for 1 cpu @@ -43,26 +45,152 @@ list(APPEND QBOX_CPU_TEST_NUM_CPU_COMBINATION ) - FILE(GLOB KEYSTONE_PATCH_FILES "${PROJECT_SOURCE_DIR}/patch/keystone/*.patch") + # LLVM toolchain is used to assemble firmware binaries at configure time + find_program(LLVM_MC llvm-mc HINTS /opt/homebrew/opt/llvm/bin) + find_program(LLD ld.lld HINTS /opt/homebrew/opt/llvm/bin) + find_program(LLVM_OBJCOPY llvm-objcopy HINTS /opt/homebrew/opt/llvm/bin) - message(STATUS "Applying patches to keystone: ${KEYSTONE_PATCH_FILES}") - - if(WIN32) - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + if(NOT LLVM_MC OR NOT LLD OR NOT LLVM_OBJCOPY) + message(FATAL_ERROR + "Qbox CPU tests require the LLVM toolchain (llvm-mc, ld.lld, llvm-objcopy) " + "to assemble test firmware. Run scripts/install_dependencies.sh to install " + "all required dependencies, then re-run cmake.\n" + " llvm-mc: ${LLVM_MC}\n ld.lld: ${LLD}\n llvm-objcopy: ${LLVM_OBJCOPY}") endif() - cpmfindpackage( - NAME keystone - GIT_REPOSITORY https://github.com/keystone-engine/keystone.git - GIT_TAG 0.9.2 - GIT_SHALLOW TRUE - PATCH_COMMAND ${PATCH_EXEC_CMD} ${PATCH_ARGS} "${KEYSTONE_PATCH_FILES}" - UPDATE_DISCONNECTED 1 - OPTIONS "BUILD_LIBS_ONLY" - ) + # qbox_build_firmware_bin( [EXTRA_LD_ARGS ...]) + # + # Assembles /.S with llvm-mc, links it with ld.lld using + # /.ld, and objcopies the result to a raw .bin. Creates a + # custom target -firmware that depends on the .bin. + # + # is passed to `llvm-mc -arch=` (e.g. aarch64, hexagon, riscv32). + # EXTRA_LD_ARGS can supply additional `ld.lld` flags such as `--defsym`. + function(qbox_build_firmware_bin name arch src_dir) + cmake_parse_arguments(FW "" "" "EXTRA_LD_ARGS" ${ARGN}) + + # Accept either name.S or name.s + if(EXISTS "${src_dir}/${name}.S") + set(src "${src_dir}/${name}.S") + elseif(EXISTS "${src_dir}/${name}.s") + set(src "${src_dir}/${name}.s") + else() + message(FATAL_ERROR "qbox_build_firmware_bin: no ${name}.S or ${name}.s in ${src_dir}") + endif() + + # Prefer a per-firmware linker script; fall back to a shared firmware.ld + # in the same directory so tests that share the same layout don't need + # a duplicated .ld file each. + if(EXISTS "${src_dir}/${name}.ld") + set(ld_script "${src_dir}/${name}.ld") + elseif(EXISTS "${src_dir}/firmware.ld") + set(ld_script "${src_dir}/firmware.ld") + else() + message(FATAL_ERROR "qbox_build_firmware_bin: no ${name}.ld or firmware.ld in ${src_dir}") + endif() + set(obj "${CMAKE_CURRENT_BINARY_DIR}/${name}.o") + set(elf "${CMAKE_CURRENT_BINARY_DIR}/${name}.elf") + set(bin "${CMAKE_CURRENT_BINARY_DIR}/${name}.bin") + + set(mc_triple_args "-arch=${arch}" -filetype=obj) + if("${arch}" STREQUAL "aarch64") + list(APPEND mc_triple_args -triple=aarch64-none-elf) + set(lld_emulation aarch64elf) + elseif("${arch}" STREQUAL "hexagon") + set(lld_emulation hexagonelf) + elseif("${arch}" STREQUAL "riscv32") + set(lld_emulation elf32lriscv) + elseif("${arch}" STREQUAL "riscv64") + set(lld_emulation elf64lriscv) + else() + message(FATAL_ERROR "qbox_build_firmware_bin: unsupported arch '${arch}'") + endif() + + # On MSYS2/Windows, ld.lld defaults to PE-COFF and ignores -flavor gnu, + # so an explicit -m is required to force ELF/GNU mode. + # Linux/macOS ld.lld already defaults to ELF and older LLDs (e.g. + # Ubuntu's llvm-18) don't even know some of the ELF emulation names + # (hexagonelf lands first), so we only pass -m on Windows. + if(WIN32) + set(lld_mode_args -m ${lld_emulation}) + else() + set(lld_mode_args) + endif() + + get_filename_component(src_name "${src}" NAME) + add_custom_command( + OUTPUT ${obj} + COMMAND ${LLVM_MC} ${mc_triple_args} ${src} -o ${obj} + DEPENDS ${src} + COMMENT "Assembling ${src_name}" + ) + + add_custom_command( + OUTPUT ${elf} + COMMAND ${LLD} ${lld_mode_args} -T ${ld_script} ${obj} ${FW_EXTRA_LD_ARGS} -o ${elf} + DEPENDS ${obj} ${ld_script} + COMMENT "Linking ${name}.elf" + ) + + add_custom_command( + OUTPUT ${bin} + COMMAND ${LLVM_OBJCOPY} -O binary ${elf} ${bin} + DEPENDS ${elf} + COMMENT "Creating ${name}.bin" + ) + + add_custom_target(${name}-firmware DEPENDS ${bin}) + endfunction() + + # qbox_attach_firmware( ) + # + # Wires a pre-built firmware binary (produced by qbox_build_firmware_bin) + # to a test executable: adds the build dependency and defines + # FIRMWARE_BIN_PATH so the test knows where to mmap/load the .bin at runtime. + function(qbox_attach_firmware target fw_name) + add_dependencies(${target} ${fw_name}-firmware) + target_compile_definitions(${target} PRIVATE + FIRMWARE_BIN_PATH="${CMAKE_CURRENT_BINARY_DIR}/${fw_name}.bin") + endfunction() + + # qbox_add_cpu_test( ... + # [ARCH ] + # [FIRMWARE ] + # [FIRMWARE_SRC_DIR ] + # [EXTRA_LD_ARGS ...]) + # + # Builds (via qbox_build_firmware_bin) and attaches a firmware binary to + # the test. By default the firmware base name is derived from the first + # source file: .cc → firmware , so .S/.ld + # files share that stem. Pass FIRMWARE only to override the + # default (e.g. when sharing firmware across tests). ARCH is required + # whenever a firmware target needs to be built. FIRMWARE_SRC_DIR + # defaults to CMAKE_CURRENT_SOURCE_DIR. EXTRA_LD_ARGS is forwarded to + # ld.lld. function(qbox_add_cpu_test target timeout_s) - if(NOT ${keystone_FOUND}) - return() + cmake_parse_arguments(QBT "" "ARCH;FIRMWARE;FIRMWARE_SRC_DIR" "EXTRA_LD_ARGS" ${ARGN}) + set(test_sources ${QBT_UNPARSED_ARGUMENTS}) + + set(firmware "${QBT_FIRMWARE}") + if(NOT firmware) + list(GET test_sources 0 _first_source) + get_filename_component(firmware "${_first_source}" NAME_WE) + endif() + + if(NOT TARGET ${firmware}-firmware) + if(NOT QBT_ARCH) + message(FATAL_ERROR + "qbox_add_cpu_test(${target}): ARCH is required to build firmware '${firmware}'") + endif() + set(_fw_src_dir "${QBT_FIRMWARE_SRC_DIR}") + if(NOT _fw_src_dir) + set(_fw_src_dir "${CMAKE_CURRENT_SOURCE_DIR}") + endif() + set(_extra_ld_args) + if(QBT_EXTRA_LD_ARGS) + set(_extra_ld_args EXTRA_LD_ARGS ${QBT_EXTRA_LD_ARGS}) + endif() + qbox_build_firmware_bin(${firmware} ${QBT_ARCH} ${_fw_src_dir} ${_extra_ld_args}) endif() list(APPEND QBOX_TEST_ACCELS "tcg") @@ -73,7 +201,7 @@ function(qbox_add_cpu_test target timeout_s) list(APPEND QBOX_TEST_ACCELS "kvm") endif() - add_executable(${target} ${ARGN}) + add_executable(${target} ${test_sources}) # Apply HVF entitlements on MacOS if(APPLE) @@ -88,9 +216,9 @@ function(qbox_add_cpu_test target timeout_s) endif() - target_include_directories(${target} PRIVATE ${PROJECT_SOURCE_DIR}/tests/qbox/include ${keystone_SOURCE_DIR}/include) + target_include_directories(${target} PRIVATE ${PROJECT_SOURCE_DIR}/tests/qbox/include) - target_link_libraries(${target} PRIVATE cpu_arm_cortexA53 qemu_cpu_hexagon cpu_riscv32 global_peripheral_initiator router reset_gpio gs_memory smmu500 pass exclusive_monitor display plic_sifive ${TARGET_LIBS} keystone hexagon_globalreg) + target_link_libraries(${target} PRIVATE cpu_arm_cortexA53 qemu_cpu_hexagon cpu_riscv32 global_peripheral_initiator router reset_gpio gs_memory smmu500 pass exclusive_monitor display plic_sifive ${TARGET_LIBS} hexagon_globalreg) foreach (accel ${QBOX_TEST_ACCELS}) foreach(sync_pol ${QBOX_CPU_TEST_SYNC_POLICY_COMBINATION}) @@ -179,6 +307,8 @@ function(qbox_add_cpu_test target timeout_s) endforeach() endforeach() endforeach() + + qbox_attach_firmware(${target} ${firmware}) endfunction(qbox_add_cpu_test) add_subdirectory(cpu) diff --git a/tests/qbox/cpu/CMakeLists.txt b/tests/qbox/cpu/CMakeLists.txt index 568ed6a5..709d88bc 100644 --- a/tests/qbox/cpu/CMakeLists.txt +++ b/tests/qbox/cpu/CMakeLists.txt @@ -1,3 +1,9 @@ -add_subdirectory(aarch64) -add_subdirectory(hexagon) -add_subdirectory(riscv32) +if("aarch64" IN_LIST LIBQEMU_TARGETS) + add_subdirectory(aarch64) +endif() +if("hexagon" IN_LIST LIBQEMU_TARGETS) + add_subdirectory(hexagon) +endif() +if("riscv32" IN_LIST LIBQEMU_TARGETS) + add_subdirectory(riscv32) +endif() diff --git a/tests/qbox/cpu/aarch64/CMakeLists.txt b/tests/qbox/cpu/aarch64/CMakeLists.txt index a370a432..83de60b6 100644 --- a/tests/qbox/cpu/aarch64/CMakeLists.txt +++ b/tests/qbox/cpu/aarch64/CMakeLists.txt @@ -1,59 +1,21 @@ -qbox_add_cpu_test(aarch64-simple-write-test 100 simple-write-test.cc) -qbox_add_cpu_test(aarch64-dmi-test 100 dmi-test.cc) -qbox_add_cpu_test(aarch64-dmi-test-concurrent-inval 100 dmi-test-concurrent-inval.cc) -# Build assembly firmware for DMI reset test -find_program(LLVM_MC llvm-mc HINTS /opt/homebrew/opt/llvm/bin) -find_program(LLD ld.lld) -find_program(LLVM_OBJCOPY llvm-objcopy HINTS /opt/homebrew/opt/llvm/bin) +# All AArch64 CPU tests assemble their firmware with llvm-mc + ld.lld and load +# the resulting .bin at runtime (see load_firmware_binary in test/cpu.h). -# "and APPLE" workaround: QQVPQSP-714 -if(LLVM_MC AND LLD AND LLVM_OBJCOPY AND APPLE) - # Assemble the ARM64 assembly file - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/dmi-test-reset.o - COMMAND ${LLVM_MC} -arch=aarch64 -filetype=obj -triple=aarch64-none-elf - ${CMAKE_CURRENT_SOURCE_DIR}/dmi-test-reset.S - -o ${CMAKE_CURRENT_BINARY_DIR}/dmi-test-reset.o - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/dmi-test-reset.S - COMMENT "Assembling dmi-test-reset.S" - ) +# Thin wrapper that pins ARCH=aarch64. Firmware base name is derived from +# the first source file's stem, so call sites only state target, timeout, +# and sources. +function(qbox_add_cpu_aarch64_test target timeout) + qbox_add_cpu_test(${target} ${timeout} ${ARGN} ARCH aarch64) +endfunction() - # Link the object file - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/dmi-test-reset.elf - COMMAND ${LLD} -T ${CMAKE_CURRENT_SOURCE_DIR}/dmi-test-reset.ld - ${CMAKE_CURRENT_BINARY_DIR}/dmi-test-reset.o - -o ${CMAKE_CURRENT_BINARY_DIR}/dmi-test-reset.elf - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/dmi-test-reset.o - ${CMAKE_CURRENT_SOURCE_DIR}/dmi-test-reset.ld - COMMENT "Linking dmi-test-reset.elf" - ) - - # Create binary file - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/dmi-test-reset.bin - COMMAND ${LLVM_OBJCOPY} -O binary - ${CMAKE_CURRENT_BINARY_DIR}/dmi-test-reset.elf - ${CMAKE_CURRENT_BINARY_DIR}/dmi-test-reset.bin - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/dmi-test-reset.elf - COMMENT "Creating dmi-test-reset.bin" - ) - - # Create a custom target for the firmware - add_custom_target(dmi-test-reset-firmware - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/dmi-test-reset.bin) - - qbox_add_cpu_test(aarch64-dmi-test-reset 100 dmi-test-reset.cc) - add_dependencies(aarch64-dmi-test-reset dmi-test-reset-firmware) - target_compile_definitions(aarch64-dmi-test-reset PRIVATE - FIRMWARE_BIN_PATH="${CMAKE_CURRENT_BINARY_DIR}/dmi-test-reset.bin") -else() - message(WARNING "LLVM tools not found - skipping aarch64-dmi-test-reset") -endif() -qbox_add_cpu_test(aarch64-ld-st-excl-fail-test 100 ld-st-excl-fail.cc) -qbox_add_cpu_test(aarch64-write_read 100 write_read.cc) -qbox_add_cpu_test(aarch64-dmi-test-async-inval 100 dmi-test-async-inval.cc) -qbox_add_cpu_test(halt-tests 100 halt-tests.cc) -qbox_add_cpu_test(reset-test-system 100 reset-test-system.cc) -qbox_add_cpu_test(reset-test-cpu 100 reset-test-cpu.cc) -qbox_add_cpu_test(aarch64-smmu-router-stress-test-v2 100 smmu_router_stress_test_v2.cc) +qbox_add_cpu_aarch64_test(aarch64-simple-write-test 100 simple-write-test.cc) +qbox_add_cpu_aarch64_test(aarch64-dmi-test 100 dmi-test.cc) +qbox_add_cpu_aarch64_test(aarch64-dmi-test-concurrent-inval 100 dmi-test-concurrent-inval.cc) +qbox_add_cpu_aarch64_test(aarch64-dmi-test-reset 100 dmi-test-reset.cc) +qbox_add_cpu_aarch64_test(aarch64-ld-st-excl-fail-test 100 ld-st-excl-fail.cc) +qbox_add_cpu_aarch64_test(aarch64-write-read 100 write-read.cc) +qbox_add_cpu_aarch64_test(aarch64-dmi-test-async-inval 100 dmi-test-async-inval.cc) +qbox_add_cpu_aarch64_test(halt-tests 100 halt-tests.cc) +qbox_add_cpu_aarch64_test(reset-test-system 100 reset-test-system.cc) +qbox_add_cpu_aarch64_test(reset-test-cpu 100 reset-test-cpu.cc) +qbox_add_cpu_aarch64_test(aarch64-smmu-router-stress-test-v2 100 smmu-router-stress-test-v2.cc) diff --git a/tests/qbox/cpu/aarch64/dmi-test-async-inval.S b/tests/qbox/cpu/aarch64/dmi-test-async-inval.S new file mode 100644 index 00000000..31034e06 --- /dev/null +++ b/tests/qbox/cpu/aarch64/dmi-test-async-inval.S @@ -0,0 +1,80 @@ +.text +.global _start + +_start: + adr x2, mmio_addr + ldr x2, [x2] // x2 = MMIO_ADDR + adr x1, dmi_addr + ldr x1, [x1] // x1 = DMI_ADDR + adr x5, dmi_size_mask + ldr x5, [x5] // x5 = DMI_SIZE-1 + adr x6, rng_seed + ldr x6, [x6] // x6 = random seed + adr x3, num_writes + ldr x3, [x3] // x3 = NUM_WRITES_PER_CPU + + mrs x0, mpidr_el1 + + and x10, x0, #0xff + and x0, x0, #0xff00 + lsr x0, x0, #5 + orr x0, x0, x10 + lsl x0, x0, #3 + + add x2, x2, x0 + ror x6, x6, x0 + +loop: + mov x0, #1 + str x0, [x2] + str x3, [x2] + + mov x10, x6 + ror x10, x10, #17 + eor x6, x6, x10 + ror x10, x10, #54 + eor x6, x6, x10 + ror x10, x10, #29 + eor x6, x6, x10 + mov x6, x10 + + and x10, x10, x5 + + add x10, x10, x1 + and x10, x10, #-8 + str x10, [x10] + + mov x0, #2 + str x0, [x2] + str x10, [x2] + + ldr x7, [x10] + cmp x10, x7 + b.ne fail + + sub x3, x3, #1 + cmp x3, #0 + b.ne loop + +end: + wfi + mov x0, #0 + str x0, [x2] + b end + +fail: + mov x0, #-2 + str x0, [x2] + b end + +.align 3 +mmio_addr: + .quad 0 +dmi_addr: + .quad 0 +dmi_size_mask: + .quad 0 +rng_seed: + .quad 0 +num_writes: + .quad 0 diff --git a/tests/qbox/cpu/aarch64/dmi-test-async-inval.cc b/tests/qbox/cpu/aarch64/dmi-test-async-inval.cc index 5a6f19be..33504317 100644 --- a/tests/qbox/cpu/aarch64/dmi-test-async-inval.cc +++ b/tests/qbox/cpu/aarch64/dmi-test-async-inval.cc @@ -8,9 +8,9 @@ #include -#include +#include +#include #include -#include #include "test/cpu.h" #include "test/tester/dmi_soak.h" @@ -36,70 +36,6 @@ class CpuArmCortexA53DmiAsyncInvalTest : public CpuArmTestBench(n) { - char buf[2048]; SCP_DEBUG(SCMOD) << "CpuArmCortexA53DmiAsyncInvalTest constructor"; m_num_write_per_cpu = NUM_WRITES / (p_num_cpu * 2); - std::snprintf(buf, sizeof(buf), FIRMWARE, CpuTesterDmiSoak::MMIO_ADDR, CpuTesterDmiSoak::DMI_ADDR, - CpuTesterDmiSoak::DMI_SIZE - 1, (((uint64_t)std::rand() << 32) | rand()), m_num_write_per_cpu); - set_firmware(buf); + uint64_t rng_seed = ((static_cast(std::rand()) << 32) | std::rand()); + load_firmware_binary( + FIRMWARE_BIN_PATH, MEM_ADDR, + { static_cast(CpuTesterDmiSoak::MMIO_ADDR), static_cast(CpuTesterDmiSoak::DMI_ADDR), + static_cast(CpuTesterDmiSoak::DMI_SIZE - 1), rng_seed, m_num_write_per_cpu }); } virtual void start_of_simulation() override @@ -192,6 +129,4 @@ class CpuArmCortexA53DmiAsyncInvalTest : public CpuArmTestBench(argc, argv); } diff --git a/tests/qbox/cpu/aarch64/dmi-test-concurrent-inval.S b/tests/qbox/cpu/aarch64/dmi-test-concurrent-inval.S new file mode 100644 index 00000000..c5103813 --- /dev/null +++ b/tests/qbox/cpu/aarch64/dmi-test-concurrent-inval.S @@ -0,0 +1,81 @@ +.text +.global _start + +_start: + adr x2, mmio_addr + ldr x2, [x2] // x2 = MMIO_ADDR + adr x1, dmi_addr + ldr x1, [x1] // x1 = DMI_ADDR + adr x3, num_writes + ldr x3, [x3] // x3 = NUM_WRITES_PER_CPU + + mrs x0, mpidr_el1 + + and x7, x0, #0xff + and x0, x0, #0xff00 + lsr x0, x0, #5 + orr x0, x0, x7 + + lsl x0, x0, #3 + add x1, x1, x0 + add x2, x2, x0 + + mov x4, #0 + +loop: + // Read/Write on DMI socket + ldr x0, [x1] + cmp x0, x4 + b.ne fail + add x0, x0, #1 + str x0, [x1] + mov x4, x0 + + // Read/Write on DMI socket + ldr x0, [x1] + cmp x0, x4 + b.ne fail + add x0, x0, #1 + str x0, [x1] + mov x4, x0 + + // Do DMI invalidation + str x0, [x2] + + // Read/Write on DMI socket + ldr x0, [x1] + cmp x0, x4 + b.ne fail + add x0, x0, #1 + str x0, [x1] + mov x4, x0 + + // Read/Write on DMI socket + ldr x0, [x1] + cmp x0, x4 + b.ne fail + add x0, x0, #1 + str x0, [x1] + mov x4, x0 + + cmp x0, x3 + b.lt loop + +end: + wfi + ldr x5, =0x80000000 + str x5, [x2] + b end + +fail: + mov x0, #-1 + str x0, [x2] + b end + +.align 3 +mmio_addr: + .quad 0 +dmi_addr: + .quad 0 +num_writes: + .quad 0 diff --git a/tests/qbox/cpu/aarch64/dmi-test-concurrent-inval.cc b/tests/qbox/cpu/aarch64/dmi-test-concurrent-inval.cc index bab620fd..43422688 100644 --- a/tests/qbox/cpu/aarch64/dmi-test-concurrent-inval.cc +++ b/tests/qbox/cpu/aarch64/dmi-test-concurrent-inval.cc @@ -7,9 +7,9 @@ */ #include -#include +#include +#include #include -#include #include "test/cpu.h" #include "test/tester/dmi.h" @@ -35,76 +35,6 @@ class CpuArmCortexA53DmiConcurrentInvalTest : public CpuArmTestBench(n), invalidated(p_num_cpu, false) { - char buf[2048]; SCP_DEBUG(SCMOD) << "CpuArmCortexA53DmiConcurrentInvalTest constructor"; m_num_write_per_cpu = NUM_WRITES / p_num_cpu; - std::snprintf(buf, sizeof(buf), FIRMWARE, CpuTesterDmi::MMIO_ADDR, CpuTesterDmi::DMI_ADDR, m_num_write_per_cpu); - set_firmware(buf); + load_firmware_binary( + FIRMWARE_BIN_PATH, MEM_ADDR, + { static_cast(CpuTesterDmi::MMIO_ADDR), static_cast(CpuTesterDmi::DMI_ADDR), + static_cast(m_num_write_per_cpu) }); } virtual ~CpuArmCortexA53DmiConcurrentInvalTest() {} @@ -206,6 +137,4 @@ class CpuArmCortexA53DmiConcurrentInvalTest : public CpuArmTestBench(argc, argv); } diff --git a/tests/qbox/cpu/aarch64/dmi-test-reset.cc b/tests/qbox/cpu/aarch64/dmi-test-reset.cc index 5e05d602..f7ac858d 100644 --- a/tests/qbox/cpu/aarch64/dmi-test-reset.cc +++ b/tests/qbox/cpu/aarch64/dmi-test-reset.cc @@ -70,44 +70,17 @@ class CpuArmCortexA53DmiResetTest : public CpuArmTestBench firmware_data(size); - if (!file.read(reinterpret_cast(firmware_data.data()), size)) { - SCP_FATAL(SCMOD) << "Failed to read firmware file: " << firmware_path; - TEST_ASSERT(false); - } - - // Patch the addresses in the binary - // The constants are at the end of the binary (last 16 bytes) - if (size >= 16) { - uint64_t* mmio_addr_ptr = reinterpret_cast(firmware_data.data() + size - 16); - uint64_t* dmi_addr_ptr = reinterpret_cast(firmware_data.data() + size - 8); - - *mmio_addr_ptr = CpuTesterDmi::MMIO_ADDR; - *dmi_addr_ptr = CpuTesterDmi::DMI_ADDR; - } - - // Load the firmware data directly into memory without keystone assembly - m_mem.load.ptr_load(firmware_data.data(), 0, size); + load_firmware_binary( + FIRMWARE_BIN_PATH, MEM_ADDR, + { static_cast(CpuTesterDmi::MMIO_ADDR), static_cast(CpuTesterDmi::DMI_ADDR) }); } virtual void mmio_write(int id, uint64_t addr, uint64_t data, size_t len) override diff --git a/tests/qbox/cpu/aarch64/dmi-test.S b/tests/qbox/cpu/aarch64/dmi-test.S new file mode 100644 index 00000000..4761c489 --- /dev/null +++ b/tests/qbox/cpu/aarch64/dmi-test.S @@ -0,0 +1,53 @@ +.text +.global _start + +_start: + adr x2, mmio_addr + ldr x2, [x2] // x2 = MMIO_ADDR + adr x1, dmi_addr + ldr x1, [x1] // x1 = DMI_ADDR + adr x7, num_writes + ldr x7, [x7] // x7 = NUM_WRITES_PER_CPU + + mrs x0, mpidr_el1 + + and x3, x0, #0xff + and x0, x0, #0xff00 + lsr x0, x0, #5 + orr x0, x0, x3 + + lsl x0, x0, #3 + add x1, x1, x0 + add x2, x2, x0 + + // Init: will ask for a DMI pointer and go from ST_START to ST_READ_DMI + ldr x0, [x1] + str x0, [x2] + +loop: + // Read on DMI socket, report the read on the control socket + ldr x0, [x1] + str x0, [x2] + + add x0, x0, #1 + + // Write on DMI socket, report the write on the control socket + str x0, [x1] + str x0, [x2] + + cmp x0, x7 + b.ne loop + +end: + wfi + ldr x5, =0x80000000 + str x5, [x2] + b end + +.align 3 +mmio_addr: + .quad 0 +dmi_addr: + .quad 0 +num_writes: + .quad 0 diff --git a/tests/qbox/cpu/aarch64/dmi-test.cc b/tests/qbox/cpu/aarch64/dmi-test.cc index 563926e4..99d756ce 100644 --- a/tests/qbox/cpu/aarch64/dmi-test.cc +++ b/tests/qbox/cpu/aarch64/dmi-test.cc @@ -8,7 +8,6 @@ #include -#include #include #include @@ -36,47 +35,6 @@ class CpuArmCortexA53DmiTest : public CpuArmTestBench(n) { - char buf[1024]; SCP_DEBUG(SCMOD) << "CpuArmCortexA53DmiTest constructor"; m_num_write_per_cpu = NUM_WRITES / p_num_cpu; - std::snprintf(buf, sizeof(buf), FIRMWARE, CpuTesterDmi::MMIO_ADDR, CpuTesterDmi::DMI_ADDR, m_num_write_per_cpu); - set_firmware(buf); + load_firmware_binary( + FIRMWARE_BIN_PATH, MEM_ADDR, + { static_cast(CpuTesterDmi::MMIO_ADDR), static_cast(CpuTesterDmi::DMI_ADDR), + static_cast(m_num_write_per_cpu) }); m_last_access_io.resize(p_num_cpu); std::fill(m_last_access_io.begin(), m_last_access_io.end(), false); @@ -248,6 +207,4 @@ class CpuArmCortexA53DmiTest : public CpuArmTestBench(argc, argv); } diff --git a/tests/qbox/cpu/aarch64/firmware.ld b/tests/qbox/cpu/aarch64/firmware.ld new file mode 100644 index 00000000..ed83ad7d --- /dev/null +++ b/tests/qbox/cpu/aarch64/firmware.ld @@ -0,0 +1,12 @@ +ENTRY(_start) + +SECTIONS +{ + . = 0x0; + .text : { + *(.text) + } + .data : { + *(.data) + } +} diff --git a/tests/qbox/cpu/aarch64/halt-tests.S b/tests/qbox/cpu/aarch64/halt-tests.S new file mode 100644 index 00000000..8faa128c --- /dev/null +++ b/tests/qbox/cpu/aarch64/halt-tests.S @@ -0,0 +1,36 @@ +.text +.global _start + +_start: + adr x1, mmio_addr + ldr x1, [x1] // x1 = MMIO_ADDR + adr x6, num_writes + ldr x6, [x6] // x6 = NUM_WRITES (=NUM_WRITES * 2) + + mrs x0, mpidr_el1 + + and x2, x0, #0xff + and x0, x0, #0xff00 + lsr x0, x0, #5 + orr x0, x0, x2 + + lsl x0, x0, #3 + add x1, x1, x0 + + mov x0, #0 + +loop: + str x0, [x1] + add x0, x0, #1 + cmp x0, x6 + b.ne loop + +end: + wfi + b end + +.align 3 +mmio_addr: + .quad 0 +num_writes: + .quad 0 diff --git a/tests/qbox/cpu/aarch64/halt-tests.cc b/tests/qbox/cpu/aarch64/halt-tests.cc index ca143cd4..19aa6645 100644 --- a/tests/qbox/cpu/aarch64/halt-tests.cc +++ b/tests/qbox/cpu/aarch64/halt-tests.cc @@ -8,7 +8,8 @@ #include -#include +#include +#include #include #include "test/cpu.h" @@ -35,33 +36,6 @@ class CpuArmCortexA53SimpleHalt : public CpuArmTestBench m_writes; std::vector im_halted; @@ -72,8 +46,6 @@ class CpuArmCortexA53SimpleHalt : public CpuArmTestBench(n), halt("halt", p_num_cpu) { - char buf[1024]; - map_halt_to_cpus(halt); im_halted.resize(p_num_cpu); @@ -84,8 +56,9 @@ class CpuArmCortexA53SimpleHalt : public CpuArmTestBench(CpuTesterMmio::MMIO_ADDR), static_cast(NUM_WRITES * 2) }); m_writes.resize(p_num_cpu); for (int i = 0; i < p_num_cpu; i++) { @@ -161,6 +134,4 @@ class CpuArmCortexA53SimpleHalt : public CpuArmTestBench(argc, argv); } diff --git a/tests/qbox/cpu/aarch64/ld-st-excl-fail.S b/tests/qbox/cpu/aarch64/ld-st-excl-fail.S new file mode 100644 index 00000000..a36ff731 --- /dev/null +++ b/tests/qbox/cpu/aarch64/ld-st-excl-fail.S @@ -0,0 +1,26 @@ +.text +.global _start + +_start: + adr x1, mmio_addr + ldr x1, [x1] // x1 = MMIO_ADDR + +loop: + ldxr x0, [x1] + stxr w2, x0, [x1] + + // Make sure the exclusive store failed + cmp w2, #0 + beq fail + +end: + wfi + b end + +fail: + str x0, [x1] + b end + +.align 3 +mmio_addr: + .quad 0 diff --git a/tests/qbox/cpu/aarch64/ld-st-excl-fail.cc b/tests/qbox/cpu/aarch64/ld-st-excl-fail.cc index 4cd3292f..14ec36d1 100644 --- a/tests/qbox/cpu/aarch64/ld-st-excl-fail.cc +++ b/tests/qbox/cpu/aarch64/ld-st-excl-fail.cc @@ -8,10 +8,6 @@ #include -#include -#include -#include - #include "test/cpu.h" #include "test/tester/exclusive.h" @@ -29,36 +25,10 @@ class CpuArmCortexA53LdStExclTest : public CpuArmTestBench(n) { - char buf[2048]; - - std::snprintf(buf, sizeof(buf), FIRMWARE, CpuTesterExclusive::MMIO_ADDR); - set_firmware(buf); + load_firmware_binary(FIRMWARE_BIN_PATH, MEM_ADDR, { static_cast(CpuTesterExclusive::MMIO_ADDR) }); } virtual ~CpuArmCortexA53LdStExclTest() {} @@ -82,6 +52,4 @@ class CpuArmCortexA53LdStExclTest : public CpuArmTestBench(argc, argv); } diff --git a/tests/qbox/cpu/aarch64/reset-test-base.h b/tests/qbox/cpu/aarch64/reset-test-base.h index e88b96f7..63e48e32 100644 --- a/tests/qbox/cpu/aarch64/reset-test-base.h +++ b/tests/qbox/cpu/aarch64/reset-test-base.h @@ -39,34 +39,6 @@ class CpuArmCortexA53SimpleResetBase : public CpuArmTestBench m_writes; MultiInitiatorSignalSocket reset; @@ -94,8 +66,6 @@ class CpuArmCortexA53SimpleResetBase : public CpuArmTestBench(CpuTesterMmio::MMIO_ADDR), static_cast(NUM_WRITES) }); m_writes.resize(p_num_cpu); m_cpu_acked.resize(p_num_cpu, 0); diff --git a/tests/qbox/cpu/aarch64/reset-test-cpu.S b/tests/qbox/cpu/aarch64/reset-test-cpu.S new file mode 100644 index 00000000..e0499461 --- /dev/null +++ b/tests/qbox/cpu/aarch64/reset-test-cpu.S @@ -0,0 +1,38 @@ +.text +.global _start + +_start: + adr x1, mmio_addr + ldr x1, [x1] // x1 = MMIO_ADDR + adr x6, num_writes + ldr x6, [x6] // x6 = NUM_WRITES + + mrs x0, mpidr_el1 + + and x2, x0, #0xff + and x0, x0, #0xff00 + lsr x0, x0, #5 + orr x0, x0, x2 + + lsl x0, x0, #3 + add x1, x1, x0 + ldr x0, [x1] + str x1, [x1] + +loop: + cmp x0, x6 + b.ge end + + str x0, [x1] + add x0, x0, #1 + b loop + +end: + wfi + b end + +.align 3 +mmio_addr: + .quad 0 +num_writes: + .quad 0 diff --git a/tests/qbox/cpu/aarch64/reset-test-system.S b/tests/qbox/cpu/aarch64/reset-test-system.S new file mode 100644 index 00000000..e0499461 --- /dev/null +++ b/tests/qbox/cpu/aarch64/reset-test-system.S @@ -0,0 +1,38 @@ +.text +.global _start + +_start: + adr x1, mmio_addr + ldr x1, [x1] // x1 = MMIO_ADDR + adr x6, num_writes + ldr x6, [x6] // x6 = NUM_WRITES + + mrs x0, mpidr_el1 + + and x2, x0, #0xff + and x0, x0, #0xff00 + lsr x0, x0, #5 + orr x0, x0, x2 + + lsl x0, x0, #3 + add x1, x1, x0 + ldr x0, [x1] + str x1, [x1] + +loop: + cmp x0, x6 + b.ge end + + str x0, [x1] + add x0, x0, #1 + b loop + +end: + wfi + b end + +.align 3 +mmio_addr: + .quad 0 +num_writes: + .quad 0 diff --git a/tests/qbox/cpu/aarch64/simple-write-test.S b/tests/qbox/cpu/aarch64/simple-write-test.S new file mode 100644 index 00000000..5ed237a9 --- /dev/null +++ b/tests/qbox/cpu/aarch64/simple-write-test.S @@ -0,0 +1,37 @@ +.text +.global _start + +_start: + adr x1, mmio_addr + ldr x1, [x1] // x1 = MMIO_ADDR + adr x6, num_writes + ldr x6, [x6] // x6 = NUM_WRITES + + mrs x0, mpidr_el1 + + and x2, x0, #0xff + and x0, x0, #0xff00 + lsr x0, x0, #5 + orr x0, x0, x2 + + lsl x0, x0, #3 + add x1, x1, x0 + + mov x0, #0 + +loop: + str x0, [x1] + add x0, x0, #1 + cmp x0, x6 + b.ne loop + +end: + wfi + b end + +// Constants patched at load time (see load_firmware_binary). +.align 3 +mmio_addr: + .quad 0 +num_writes: + .quad 0 diff --git a/tests/qbox/cpu/aarch64/simple-write-test.cc b/tests/qbox/cpu/aarch64/simple-write-test.cc index eae3fa24..a1ddc1e3 100644 --- a/tests/qbox/cpu/aarch64/simple-write-test.cc +++ b/tests/qbox/cpu/aarch64/simple-write-test.cc @@ -8,7 +8,6 @@ #include -#include #include #include @@ -36,33 +35,6 @@ class CpuArmCortexA53SimpleWriteTest : public CpuArmTestBench m_writes; gs::async_event m_aev; @@ -71,10 +43,9 @@ class CpuArmCortexA53SimpleWriteTest : public CpuArmTestBench(n), m_aev("aev") { - char buf[1024]; m_aev.async_attach_suspending(); - std::snprintf(buf, sizeof(buf), FIRMWARE, CpuTesterMmio::MMIO_ADDR, NUM_WRITES); - set_firmware(buf); + load_firmware_binary(FIRMWARE_BIN_PATH, MEM_ADDR, + { static_cast(CpuTesterMmio::MMIO_ADDR), static_cast(NUM_WRITES) }); m_writes.resize(p_num_cpu); for (int i = 0; i < p_num_cpu; i++) { @@ -111,6 +82,4 @@ class CpuArmCortexA53SimpleWriteTest : public CpuArmTestBench(argc, argv); } diff --git a/tests/qbox/cpu/aarch64/smmu-router-stress-test-v2.S b/tests/qbox/cpu/aarch64/smmu-router-stress-test-v2.S new file mode 100644 index 00000000..daeee0cd --- /dev/null +++ b/tests/qbox/cpu/aarch64/smmu-router-stress-test-v2.S @@ -0,0 +1,186 @@ +// Combined firmware for the aarch64 SMMU router stress test v2. +// Three blobs at fixed offsets inside one flat binary: +// 0x0000 boot loader (jumps to main) +// 0x0200 diagnostic error handler +// 0x1000 main test logic +// +// All numeric constants (TESTER_ADDR, VIRTUAL_TEST_ADDR, MAX_ITERATIONS, +// BOUNDARY_BYTES, NUM_PAGES) are fixed by the test bench, so they are baked in +// here rather than patched at load time. + +.set TESTER_ADDR, 0x40000 +.set MAIN_FIRMWARE_ADDR, 0x1000 +.set MAX_ITERATIONS, 0x7ff +.set VIRTUAL_TEST_ADDR, 0x80000000 +.set BOUNDARY_BYTES, 80 +.set NUM_PAGES, 2 + +.text +.global _start +.global boot_start + +// -------------------------------------------------------------------------- +// BOOT LOADER at 0x0 +// -------------------------------------------------------------------------- +.org 0x0 +_start: +boot_start: + movz x0, #MAIN_FIRMWARE_ADDR + br x0 + +// -------------------------------------------------------------------------- +// DIAGNOSTIC ERROR HANDLER at 0x200 +// -------------------------------------------------------------------------- +.org 0x200 +diagnostic_error: + mrs x0, mpidr_el1 + and x0, x0, #0xff + + ldr x1, =TESTER_ADDR + mov x2, #0x100 + mul x2, x0, x2 + add x1, x1, x2 + + mov x2, #0xE000 + orr x2, x2, x0 + str x2, [x1, #0x28] + + mov x0, #1 + hlt #0 + +diagnostic_loop: + wfi + b diagnostic_loop + +// -------------------------------------------------------------------------- +// MAIN FIRMWARE at 0x1000 +// -------------------------------------------------------------------------- +.org 0x1000 +main_start: + mrs x0, mpidr_el1 + and x0, x0, #0xff + mov x20, x0 // x20 = CPU ID + + ldr x1, =TESTER_ADDR + mov x2, #0x100 + mul x2, x20, x2 + add x21, x1, x2 // x21 = CPU-specific tester base + + mov x0, #0x1000 + str x0, [x21, #0x28] // signal startup + + mov x22, #0 // x22 = iteration counter + +main_loop: + mov x3, #MAX_ITERATIONS + cmp x22, x3 + b.ge test_complete + + mov x0, #0x2000 + orr x0, x0, x22 + str x0, [x21, #0x28] // debug: entering main loop + + mov x0, #1 // FILL_REQUEST + str x0, [x21, #0x00] + +poll_fill: + mov x0, #0x3000 + str x0, [x21, #0x28] // debug: polling + + ldr x0, [x21, #0x08] // REG_STATUS + cmp x0, #1 // READY? + b.eq fill_ready + cmp x0, #0 // BUSY? + b.eq try_check + b poll_fill + +fill_ready: + ldr x23, [x21, #0x10] // x23 = region ID + + mov x0, #0x4000 + orr x0, x0, x23 + str x0, [x21, #0x28] // debug: starting work + + ldr x3, =VIRTUAL_TEST_ADDR // x3 = VA base + mov x4, #BOUNDARY_BYTES + lsr x4, x4, #3 // x4 = words per boundary + mov x26, #0 // x26 = page number + mov x19, #NUM_PAGES // x19 = pages per region + +fill_page_loop: + cmp x26, x19 + b.ge fill_done + + mov x27, #1 + lsl x27, x27, #12 // x27 = 4KB + mul x28, x26, x27 // x28 = page offset + add x29, x3, x28 // x29 = page base + + mov x0, x29 + bl fill_boundary + + mov x6, #1 + lsl x6, x6, #12 + sub x6, x6, x4, lsl #3 + add x0, x29, x6 + bl fill_boundary + + add x26, x26, #1 + b fill_page_loop + +fill_done: + mov x0, #1 + str x0, [x21, #0x18] // REG_COMPLETE = FILL_DONE + + add x22, x22, #1 + b main_loop + +try_check: + b main_loop + +test_complete: + b end + +end: + str x0, [x21, #0x30] + wfi + b end + +// -------------------------------------------------------------------------- +// fill_boundary: writes BOUNDARY_BYTES/8 pattern words starting at x0. +// Pattern encoding (read/write global regs x20=cpu, x23=region, x26=page, +// x4=word count, x5=word index/scratch): +// bits 63:32 = CPU ID +// bits 31:16 = region ID +// bits 15:8 = page number +// bits 7:0 = word offset +// -------------------------------------------------------------------------- +fill_boundary: + mov x5, #0 +fill_boundary_loop: + cmp x5, x4 + b.ge fill_boundary_done + + mov x6, #0 + + lsl x7, x20, #32 + orr x6, x6, x7 + + lsl x7, x23, #16 + orr x6, x6, x7 + + lsl x7, x26, #8 + orr x6, x6, x7 + + orr x6, x6, x5 + + lsl x7, x5, #3 + add x7, x0, x7 + + str x6, [x7] + + add x5, x5, #1 + b fill_boundary_loop + +fill_boundary_done: + ret diff --git a/tests/qbox/cpu/aarch64/smmu_router_stress_test_v2.cc b/tests/qbox/cpu/aarch64/smmu-router-stress-test-v2.cc similarity index 85% rename from tests/qbox/cpu/aarch64/smmu_router_stress_test_v2.cc rename to tests/qbox/cpu/aarch64/smmu-router-stress-test-v2.cc index c28e107d..ca447949 100644 --- a/tests/qbox/cpu/aarch64/smmu_router_stress_test_v2.cc +++ b/tests/qbox/cpu/aarch64/smmu-router-stress-test-v2.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -15,7 +16,6 @@ #include #include #include -#include #include #include "cci/cfg/cci_broker_if.h" #include "test/cpu.h" @@ -570,31 +570,26 @@ class CpuArmCortexA53SMMUStressTestV2 : public TestBench, public CpuTesterCallba void reconfigure_context_bank(uint32_t cb, uint64_t page_table_addr); protected: - void set_firmware(const char* assembly, uint64_t addr = 0) + // Firmware is assembled ahead of time from + // smmu-router-stress-test-v2.S — all layout constants are baked in at + // build time, so we just load the .bin at 0x0 and the three sub-blobs + // (boot @ 0x0, diagnostic @ 0x200, main @ 0x1000) land at their expected + // offsets automatically. + void load_stress_test_firmware() { - ks_engine* ks; - ks_err err; - size_t size, count; - uint8_t* fw; - - err = ks_open(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN, &ks); - - if (err != KS_ERR_OK) { - SCP_FATAL(()) << "Unable to initialize keystone"; + std::ifstream file(FIRMWARE_BIN_PATH, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + SCP_FATAL(()) << "Failed to open firmware file: " << FIRMWARE_BIN_PATH; + TEST_FAIL("Failed to open firmware file"); } - - if (ks_asm(ks, assembly, addr, &fw, &size, &count) != KS_ERR_OK || size == 0) { - std::cerr << assembly << "\n"; - std::cerr << "errno: " << ks_errno(ks) << "\n"; - std::cerr << "error: " << ks_strerror(ks_errno(ks)) << "\n"; - SCP_INFO() << assembly; - TEST_FAIL("Unable to assemble the test firmware\n"); + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + std::vector data(size); + if (!file.read(reinterpret_cast(data.data()), size)) { + SCP_FATAL(()) << "Failed to read firmware file: " << FIRMWARE_BIN_PATH; + TEST_FAIL("Failed to read firmware file"); } - - m_mem.load.ptr_load(fw, addr, size); - - ks_free(fw); - ks_close(ks); + m_mem.load.ptr_load(data.data(), MEM_ADDR, size); } public: @@ -732,7 +727,7 @@ class CpuArmCortexA53SMMUStressTestV2 : public TestBench, public CpuTesterCallba SCP_INFO(()) << " TESTER: 0x" << std::hex << TESTER_ADDR; SCP_INFO(()) << " VIRTUAL_TEST: 0x" << std::hex << VIRTUAL_TEST_ADDR; - generate_tester_controlled_firmware(); + load_stress_test_firmware(); SC_THREAD(configure_test); } @@ -1133,247 +1128,6 @@ class CpuArmCortexA53SMMUStressTestV2 : public TestBench, public CpuTesterCallba m_cpu_to_region[cpu] = 0xFFFFFFFF; } - void generate_tester_controlled_firmware() - { - SCP_INFO(()) << "Generating tester-controlled firmware with proper layout"; - - // 1. BOOT LOADER at 0x0 - Just jumps to main firmware - static constexpr const char* BOOT_LOADER = R"( - // BOOT LOADER at 0x0 - boot_start: - // Jump to main firmware at 0x1000 - movz x0, #0x%04)" PRIx32 R"( // Main firmware address (0x1000) - br x0 // Jump to main firmware - )"; - - char boot_buf[strlen(BOOT_LOADER) + 1000]; - std::snprintf(boot_buf, sizeof(boot_buf), BOOT_LOADER, static_cast(MAIN_FIRMWARE_ADDR)); - set_firmware(boot_buf, BOOT_ADDR); - - // 2. DIAGNOSTIC HANDLER at 0x200 - Error reporting - static constexpr const char* DIAGNOSTIC_HANDLER = R"( - // DIAGNOSTIC ERROR HANDLER at 0x200 - diagnostic_error: - // Get CPU ID - mrs x0, mpidr_el1 - and x0, x0, #0xff - - // Calculate tester base address for this CPU - ldr x1, =0x%08)" PRIx64 R"( // TESTER_ADDR - mov x2, #0x100 // Register size per CPU - mul x2, x0, x2 // CPU offset - add x1, x1, x2 // x1 = CPU-specific tester base - - // Send error message: 0xE000 + CPU_ID - mov x2, #0xE000 - orr x2, x2, x0 // Error code + CPU ID - str x2, [x1, #0x28] // Write to REG_DEBUG - - // Stop the test with error - mov x0, #1 // Exit with error - hlt #0 // Halt with error - - diagnostic_loop: - wfi - b diagnostic_loop - )"; - - char diagnostic_buf[strlen(DIAGNOSTIC_HANDLER) + 1000]; - std::snprintf(diagnostic_buf, sizeof(diagnostic_buf), DIAGNOSTIC_HANDLER, TESTER_ADDR); - set_firmware(diagnostic_buf, DIAGNOSTIC_ADDR); - - // 3. MAIN FIRMWARE at 0x1000 - The actual test logic - static constexpr const char* MAIN_FIRMWARE = R"( - // MAIN FIRMWARE at 0x1000 - main_start: - // Get CPU ID - mrs x0, mpidr_el1 - and x0, x0, #0xff - mov x20, x0 // Save CPU ID in x20 - - // Calculate tester base address for this CPU - ldr x1, =0x%08)" PRIx64 R"( // TESTER_ADDR - mov x2, #0x100 // Register size per CPU - mul x2, x20, x2 // CPU offset - add x21, x1, x2 // x21 = CPU-specific tester base - - // Signal startup - mov x0, #0x1000 - str x0, [x21, #0x28] // Write to REG_DEBUG - - // Initialize iteration counter - mov x22, #0 // x22 = iteration counter - - main_loop: - // Check if we've reached max iterations - mov x3, #%d // Max iterations - cmp x22, x3 - b.ge test_complete - - // Signal entering main loop - mov x0, #0x2000 - orr x0, x0, x22 // Include iteration count - str x0, [x21, #0x28] // Write to REG_DEBUG - - // Try to get a region to fill - mov x0, #1 // FILL_REQUEST - str x0, [x21, #0x00] // Write to REG_REQUEST - - // Poll for readiness - poll_fill: - mov x0, #0x3000 // Polling debug message - str x0, [x21, #0x28] // Write to REG_DEBUG - - ldr x0, [x21, #0x08] // Read REG_STATUS - cmp x0, #1 // READY? - b.eq fill_ready - cmp x0, #0 // BUSY? - b.eq try_check // Try checking instead - b poll_fill - - fill_ready: - // Get assigned region ID - ldr x23, [x21, #0x10] // Read REG_REGION_ID - - // Signal starting work - mov x0, #0x4000 - orr x0, x0, x23 // Include region ID - str x0, [x21, #0x28] // Write to REG_DEBUG - - // Fill the region at virtual address - DYNAMIC PAGE COUNT - ldr x3, =0x%016)" PRIx64 R"( // VIRTUAL_TEST_ADDR (base) - mov x4, #%u // Boundary bytes to write at each boundary - lsr x4, x4, #3 // Convert bytes to 8-byte words - mov x26, #0 // Page number (starts at 0) - mov x19, #%u // Number of pages in region (use x19, not x30) - - fill_page_loop: - cmp x26, x19 // Loop over num_pages (using x19) - b.ge fill_done - - // Calculate page base address: base + (page_num * PAGE_SIZE) - mov x27, #1 // Start with 1 - lsl x27, x27, #12 // Shift left by 12 to get 0x1000 (4KB) - mul x28, x26, x27 // page_offset = page_num * PAGE_SIZE - add x29, x3, x28 // page_base = VIRTUAL_TEST_ADDR + page_offset - - // Call fill_boundary for the start of the page - mov x0, x29 // Arg 1: base address - bl fill_boundary - - // Calculate end boundary address - mov x6, #1 - lsl x6, x6, #12 // x6 = 0x1000 (PAGE_SIZE) - sub x6, x6, x4, lsl #3 // x6 = 0x1000 - (words * 8) - add x0, x29, x6 // end_boundary_addr = page_base + offset - - // Call fill_boundary for the end of the page - bl fill_boundary - - fill_page_next: - add x26, x26, #1 // Next page - b fill_page_loop - - fill_done: - // Signal fill complete - mov x0, #1 - str x0, [x21, #0x18] - - add x22, x22, #1 - b main_loop - - try_check: - // No regions to check - just loop back - b main_loop - - test_complete: - b end - - end: - str x0, [x21, #0x30] - wfi - b end - - // ------------------------------------------------------------- - // fill_boundary function - // - // Fills a memory boundary with an enhanced, verifiable pattern. - // Pattern: CPU_ID | REGION_ID | PAGE_NUM | WORD_OFFSET - // - // Assumed Global Registers (read-only): - // - x20: CPU ID - // - x23: Region ID - // - x26: Current Page Number - // - x4: Number of 8-byte words to write - // - // Arguments: - // - x0: Base address to start writing from - // - // Clobbered Registers (Temporaries): - // - x5, x6, x7 - // ------------------------------------------------------------- - fill_boundary: - mov x5, #0 // x5: Word offset, loop counter - fill_boundary_loop: - cmp x5, x4 // Loop for `x4` words - b.ge fill_boundary_done - - // --- Start Pattern Generation --- - mov x6, #0 // x6: The final pattern register. Clear before use. - - // 1. CPU ID (bits 63:32) - lsl x7, x20, #32 // Shift CPU ID into temp register x7 - orr x6, x6, x7 // OR into pattern - - // 2. Region ID (bits 31:16) - lsl x7, x23, #16 // Shift Region ID into temp register x7 - orr x6, x6, x7 // OR into pattern - - // 3. Page Number (bits 15:8) - lsl x7, x26, #8 // Shift Page Number into temp register x7 - orr x6, x6, x7 // OR into pattern - - // 4. Word Offset (bits 7:0) - orr x6, x6, x5 // OR Word Offset directly into pattern - - // --- End Pattern Generation --- - - // Calculate write address and store the pattern - lsl x7, x5, #3 // word_offset_in_bytes = word_offset * 8 - add x7, x0, x7 // final_addr = base_addr + word_offset_in_bytes - - // DEBUG: Write the pattern and target address to the debug registers - //str x6, [x21, #0x28] - //str x7, [x21, #0x30] - - str x6, [x7] - - add x5, x5, #1 // Increment word offset - b fill_boundary_loop - - fill_boundary_done: - ret - )"; - - // Calculate number of pages per region - uint32_t num_pages = REGION_SIZE / PAGE_SIZE; - - char main_buf[strlen(MAIN_FIRMWARE) + 1000]; - std::snprintf(main_buf, sizeof(main_buf), MAIN_FIRMWARE, - TESTER_ADDR, // %1: TESTER_ADDR - MAX_ITERATIONS, // %2: MAX_ITERATIONS - VIRTUAL_TEST_ADDR, // %3: VIRTUAL_TEST_ADDR (fill) - static_cast(BOUNDARY_BYTES), // %4: boundary bytes (fill) - num_pages); // %5: number of pages per region - - set_firmware(main_buf, MAIN_FIRMWARE_ADDR); - - SCP_INFO(()) << "Firmware layout completed:"; - SCP_INFO(()) << " - Boot loader at 0x" << std::hex << BOOT_ADDR; - SCP_INFO(()) << " - Diagnostic handler at 0x" << std::hex << DIAGNOSTIC_ADDR; - SCP_INFO(()) << " - Main firmware at 0x" << std::hex << MAIN_FIRMWARE_ADDR; - } - void write_smmu_register(uint32_t addr, uint32_t value) { SCP_INFO(()) << "ATTEMPTING SMMU WRITE: addr=0x" << std::hex << addr << ", value=0x" << value; diff --git a/tests/qbox/cpu/aarch64/write-read.S b/tests/qbox/cpu/aarch64/write-read.S new file mode 100644 index 00000000..c51c1edc --- /dev/null +++ b/tests/qbox/cpu/aarch64/write-read.S @@ -0,0 +1,62 @@ +.text +.global _start + +_start: + adr x1, mmio_addr + ldr x1, [x1] // x1 = MMIO_ADDR + adr x3, bulk_addr + ldr x3, [x3] // x3 = BULKMEM_ADDR + adr x10, num_writes + ldr x10, [x10] // x10 = NUM_WRITES / p_num_cpu + + mrs x0, mpidr_el1 + + and x2, x0, #0xff + and x0, x0, #0xff00 + lsr x0, x0, #5 + orr x0, x0, x2 + + lsl x0, x0, #3 + add x1, x1, x0 + + mov x0, #0 + mov x9, x3 +loop: + str x0, [x9] + add x9, x9, #8 + add x0, x0, #1 + cmp x0, x10 + b.ne loop + + mov x0, #0 + mov x9, x3 +loop2: + ldr x4, [x9] + cmp x0, x4 + b.ne fail + add x9, x9, #8 + add x0, x0, #1 + cmp x0, x10 + b.ne loop2 + +end: + wfi + mov x0, #0 + str x0, [x1] + b end + +fail: + str x9, [x1] + str x0, [x1] + str x4, [x1] + mov x0, #-1 + str x0, [x1] + b end + +.align 3 +mmio_addr: + .quad 0 +bulk_addr: + .quad 0 +num_writes: + .quad 0 diff --git a/tests/qbox/cpu/aarch64/write_read.cc b/tests/qbox/cpu/aarch64/write-read.cc similarity index 60% rename from tests/qbox/cpu/aarch64/write_read.cc rename to tests/qbox/cpu/aarch64/write-read.cc index 580a2513..112fc798 100644 --- a/tests/qbox/cpu/aarch64/write_read.cc +++ b/tests/qbox/cpu/aarch64/write-read.cc @@ -8,7 +8,6 @@ #include -#include #include #include #include @@ -36,66 +35,13 @@ class CpuArmCortexA53WriteReadTest : public CpuArmTestBench(n) { - char buf[1024]; - - std::snprintf(buf, sizeof(buf), FIRMWARE, CpuTesterMmio::MMIO_ADDR, CpuTestBench::BULKMEM_ADDR, - NUM_WRITES / p_num_cpu); - set_firmware(buf); + load_firmware_binary( + FIRMWARE_BIN_PATH, MEM_ADDR, + { static_cast(CpuTesterMmio::MMIO_ADDR), static_cast(CpuTestBench::BULKMEM_ADDR), + static_cast(NUM_WRITES / p_num_cpu) }); } virtual ~CpuArmCortexA53WriteReadTest() {} @@ -123,6 +69,4 @@ class CpuArmCortexA53WriteReadTest : public CpuArmTestBench(argc, argv); } diff --git a/tests/qbox/cpu/hexagon/CMakeLists.txt b/tests/qbox/cpu/hexagon/CMakeLists.txt index 0ffd1835..9e50c23f 100644 --- a/tests/qbox/cpu/hexagon/CMakeLists.txt +++ b/tests/qbox/cpu/hexagon/CMakeLists.txt @@ -1,83 +1,51 @@ -qbox_add_cpu_test(hexagon-ld-st-mmio-test 100 ld-st-mmio.cc) -qbox_add_cpu_test(hexagon-reset-test 100 hex-reset.cc) +# Hexagon firmwares: assembled with llvm-mc -arch=hexagon, linked with ld.lld, +# objcopied to a raw .bin that the test loads via load_firmware_binary. +# Thin wrapper that pins ARCH=hexagon. Firmware base name is derived from +# the first source file's stem, so call sites only state target, timeout, +# and sources. +function(qbox_add_cpu_hexagon_test target timeout) + qbox_add_cpu_test(${target} ${timeout} ${ARGN} ARCH hexagon) +endfunction() + +qbox_add_cpu_hexagon_test(hexagon-ld-st-mmio-test 100 ld-st-mmio.cc) +qbox_add_cpu_hexagon_test(hexagon-reset-test 100 hexagon-reset.cc) + +# load-store-test has a bespoke executable (not a qbox_add_cpu_test caller), +# so its firmware is built explicitly here. set(HEXAGON_STANDALONE_TEST_NAME "load-store-test") +qbox_build_firmware_bin(${HEXAGON_STANDALONE_TEST_NAME} hexagon ${CMAKE_CURRENT_SOURCE_DIR}) + add_executable( ${HEXAGON_STANDALONE_TEST_NAME} ${HEXAGON_STANDALONE_TEST_NAME}.cc ) -target_include_directories( - ${HEXAGON_STANDALONE_TEST_NAME} - PRIVATE - ${keystone_SOURCE_DIR}/include -) - target_link_libraries( ${HEXAGON_STANDALONE_TEST_NAME} PRIVATE ${TARGET_LIBS} - keystone gs_memory router qemu_cpu_hexagon hexagon_globalreg ) -add_test(NAME hexagon-${HEXAGON_STANDALONE_TEST_NAME} COMMAND ${HEXAGON_STANDALONE_TEST_NAME}) - -# Build assembly firmware for Hexagon SMMU stress test -find_program(LLVM_MC llvm-mc) -find_program(LLD ld.lld) -find_program(LLVM_OBJCOPY llvm-objcopy) - -# Check if LLVM tools are available for building Hexagon firmware -if(LLVM_MC AND LLD AND LLVM_OBJCOPY AND APPLE) - # Assemble the Hexagon assembly file - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/hexagon_smmu_firmware.o - COMMAND ${LLVM_MC} -arch=hexagon -filetype=obj - ${CMAKE_CURRENT_SOURCE_DIR}/hexagon_smmu_firmware.s - -o ${CMAKE_CURRENT_BINARY_DIR}/hexagon_smmu_firmware.o - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/hexagon_smmu_firmware.s - COMMENT "Assembling hexagon_smmu_firmware.s" - ) +add_dependencies(${HEXAGON_STANDALONE_TEST_NAME} ${HEXAGON_STANDALONE_TEST_NAME}-firmware) +target_compile_definitions(${HEXAGON_STANDALONE_TEST_NAME} PRIVATE + FIRMWARE_BIN_PATH="${CMAKE_CURRENT_BINARY_DIR}/${HEXAGON_STANDALONE_TEST_NAME}.bin") - # Link the object file (with optional symbol overrides) - # Note: You can override constants by adding --defsym options like: - # --defsym TESTER_ADDR=0x50000 --defsym PAGE_SIZE=0x2000 - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/hexagon_smmu_firmware.elf - COMMAND ${LLD} -T ${CMAKE_CURRENT_SOURCE_DIR}/hexagon_smmu_firmware.ld - ${CMAKE_CURRENT_BINARY_DIR}/hexagon_smmu_firmware.o - -o ${CMAKE_CURRENT_BINARY_DIR}/hexagon_smmu_firmware.elf - --defsym TESTER_ADDR=0x40000 - --defsym REG_DEBUG=0x28 - --defsym PAGE_SIZE=0x1000 - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/hexagon_smmu_firmware.o - ${CMAKE_CURRENT_SOURCE_DIR}/hexagon_smmu_firmware.ld - COMMENT "Linking hexagon_smmu_firmware.elf (demonstrating --defsym capability)" - ) - - # Create binary file - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/hexagon_smmu_firmware.bin - COMMAND ${LLVM_OBJCOPY} -O binary - ${CMAKE_CURRENT_BINARY_DIR}/hexagon_smmu_firmware.elf - ${CMAKE_CURRENT_BINARY_DIR}/hexagon_smmu_firmware.bin - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/hexagon_smmu_firmware.elf - COMMENT "Creating hexagon_smmu_firmware.bin" - ) - - # Create a custom target for the firmware - add_custom_target(hexagon-smmu-firmware - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/hexagon_smmu_firmware.bin) +add_test(NAME hexagon-${HEXAGON_STANDALONE_TEST_NAME} COMMAND ${HEXAGON_STANDALONE_TEST_NAME}) - qbox_add_cpu_test(hexagon-smmu-stress-test-v2 100 hexagon_smmu_stress_test_v2.cc) - add_dependencies(hexagon-smmu-stress-test-v2 hexagon-smmu-firmware) - target_compile_definitions(hexagon-smmu-stress-test-v2 PRIVATE - FIRMWARE_BIN_PATH="${CMAKE_CURRENT_BINARY_DIR}/hexagon_smmu_firmware.bin") +# hexagon-smmu-stress-test-v2.s uses V73 ops (tlbw, dmwait, dmlink) that older +# Hexagon LLVM backends (e.g. Ubuntu's stock llvm-18) don't recognise. See +# QQVPQSP-714. Only build it on platforms where a recent-enough Hexagon +# backend ships: macOS via Homebrew's `llvm`, and Windows via MSYS2's +# bundled LLVM. +if(APPLE OR WIN32) + qbox_add_cpu_hexagon_test(hexagon-smmu-stress-test-v2 100 hexagon-smmu-stress-test-v2.cc + EXTRA_LD_ARGS --defsym TESTER_ADDR=0x40000 --defsym REG_DEBUG=0x28 --defsym PAGE_SIZE=0x1000) else() - message(WARNING "LLVM tools not found - skipping hexagon-smmu-stress-test-v2") + message(STATUS "Skipping hexagon-smmu-stress-test-v2: requires recent Hexagon LLVM backend (QQVPQSP-714)") endif() diff --git a/tests/qbox/cpu/hexagon/HEXAGON_SMMU_README.md b/tests/qbox/cpu/hexagon/HEXAGON_SMMU_README.md index d4c9a387..a21874bd 100644 --- a/tests/qbox/cpu/hexagon/HEXAGON_SMMU_README.md +++ b/tests/qbox/cpu/hexagon/HEXAGON_SMMU_README.md @@ -4,8 +4,8 @@ This directory contains the Hexagon version of the SMMU router stress test v2. ## Files -- `hexagon_smmu_firmware.s` - Hexagon assembly firmware (translated from ARM64) -- `hexagon_smmu_stress_test_v2.cc` - C++ test bench +- `hexagon-smmu-stress-test-v2.s` - Hexagon assembly firmware (translated from ARM64) +- `hexagon-smmu-stress-test-v2.cc` - C++ test bench - `HEXAGON_SMMU_README.md` - This file ## Hexagon MMU Configuration @@ -76,34 +76,34 @@ You need the Hexagon SDK installed with the following tools: ```bash # Assemble the .s file to object file -hexagon-clang -march=hexagon -c hexagon_smmu_firmware.s -o hexagon_smmu_firmware.o +hexagon-clang -march=hexagon -c hexagon-smmu-stress-test-v2.s -o hexagon-smmu-stress-test-v2.o # Or use the assembler directly: -hexagon-as -march=hexagon hexagon_smmu_firmware.s -o hexagon_smmu_firmware.o +hexagon-as -march=hexagon hexagon-smmu-stress-test-v2.s -o hexagon-smmu-stress-test-v2.o ``` ### Step 2: Extract Binary ```bash # Convert object file to raw binary -hexagon-objcopy -O binary hexagon_smmu_firmware.o hexagon_smmu_firmware.bin +hexagon-objcopy -O binary hexagon-smmu-stress-test-v2.o hexagon-smmu-stress-test-v2.bin ``` ### Step 3: Generate C Array (Optional) ```bash # Generate hex dump for embedding in C++ -hexdump -v -e '16/1 "0x%02x, " "\n"' hexagon_smmu_firmware.bin > hexagon_smmu_firmware.hex +hexdump -v -e '16/1 "0x%02x, " "\n"' hexagon-smmu-stress-test-v2.bin > hexagon-smmu-stress-test-v2.hex ``` ### Step 4: Verify (Optional) ```bash # Disassemble to verify instructions -hexagon-objdump -d hexagon_smmu_firmware.o > hexagon_smmu_firmware.dis +hexagon-objdump -d hexagon-smmu-stress-test-v2.o > hexagon-smmu-stress-test-v2.dis # Check the disassembly to ensure instructions are correct -cat hexagon_smmu_firmware.dis +cat hexagon-smmu-stress-test-v2.dis ``` ## Integration with C++ Test @@ -112,7 +112,7 @@ cat hexagon_smmu_firmware.dis ```cpp // In your test bench constructor -std::ifstream firmware_file("hexagon_smmu_firmware.bin", std::ios::binary); +std::ifstream firmware_file("hexagon-smmu-stress-test-v2.bin", std::ios::binary); std::vector firmware_data( (std::istreambuf_iterator(firmware_file)), std::istreambuf_iterator() @@ -125,7 +125,7 @@ m_mem.load.ptr_load(firmware_data.data(), MEM_ADDR, firmware_data.size()); ```cpp // Include the generated hex file static const uint8_t HEXAGON_FIRMWARE[] = { - #include "hexagon_smmu_firmware.hex" + #include "hexagon-smmu-stress-test-v2.hex" }; // Load into memory @@ -136,7 +136,7 @@ m_mem.load.ptr_load(HEXAGON_FIRMWARE, MEM_ADDR, sizeof(HEXAGON_FIRMWARE)); ```cpp // Extract individual sections from object file -hexagon-objcopy -O binary --only-section=.text hexagon_smmu_firmware.o boot.bin +hexagon-objcopy -O binary --only-section=.text hexagon-smmu-stress-test-v2.o boot.bin // Load each section at its specific address m_mem.load.ptr_load(boot_data, BOOT_ADDR, boot_size); diff --git a/tests/qbox/cpu/hexagon/firmware.ld b/tests/qbox/cpu/hexagon/firmware.ld new file mode 100644 index 00000000..fe1f6c81 --- /dev/null +++ b/tests/qbox/cpu/hexagon/firmware.ld @@ -0,0 +1,12 @@ +ENTRY(boot_start) + +SECTIONS +{ + . = 0x0; + .text : { + *(.text) + } + .data : { + *(.data) + } +} diff --git a/tests/qbox/cpu/hexagon/hexagon-reset.S b/tests/qbox/cpu/hexagon/hexagon-reset.S new file mode 100644 index 00000000..835599f0 --- /dev/null +++ b/tests/qbox/cpu/hexagon/hexagon-reset.S @@ -0,0 +1,30 @@ +.text +.globl boot_start +boot_start: +_start: + // r0 = MMIO address, r1 = trigger/done value, r2 = done value + r0 = memw(##mmio_addr) + r1 = memw(##trigger_val) + r2 = memw(##done_val) + p0 = cmp.eq(r1, r2) + // `if (p0) jump reset_done` — encoded as the bytes shown; some Hexagon + // llvm-mc versions reject the plain mnemonic for this predicate form. + .word 0x5c00c006 + +reset: + memw(r0) = r1 + // `jump .` (infinite loop) + .word 0x5800c000 + +reset_done: + memw(r0) = r1 + // `wait(r0)` (see ld-st-mmio.S for why this is encoded directly) + .word 0x6440c000 + +.align 4 +mmio_addr: + .word 0 +trigger_val: + .word 0 +done_val: + .word 0 diff --git a/tests/qbox/cpu/hexagon/hex-reset.cc b/tests/qbox/cpu/hexagon/hexagon-reset.cc similarity index 82% rename from tests/qbox/cpu/hexagon/hex-reset.cc rename to tests/qbox/cpu/hexagon/hexagon-reset.cc index 29f6b960..aee209bc 100644 --- a/tests/qbox/cpu/hexagon/hex-reset.cc +++ b/tests/qbox/cpu/hexagon/hexagon-reset.cc @@ -4,9 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ -#include -#include -#include +#include #include "async_event.h" #include "test/cpu.h" @@ -35,6 +33,10 @@ class CpuHexagonResetGPIOTest : public CpuTestBench reset; reset_gpio reset_controller; hexagon_globalreg hex_gregs; @@ -44,24 +46,12 @@ class CpuHexagonResetGPIOTest : public CpuTestBench{ static_cast(CpuTesterMmio::MMIO_ADDR), + trigger_val, static_cast(RESET_DONE) }); + } public: CpuHexagonResetGPIOTest(const sc_core::sc_module_name& n) @@ -85,10 +75,7 @@ class CpuHexagonResetGPIOTest : public CpuTestBench(CpuTesterMmio::MMIO_ADDR), RESET_TRIGGER, - RESET_DONE); - set_firmware(buf, MEM_ADDR); + load_reset_firmware(RESET_TRIGGER); } void before_end_of_elaboration() override @@ -108,7 +95,6 @@ class CpuHexagonResetGPIOTest : public CpuTestBench(CpuTesterMmio::MMIO_ADDR), RESET_DONE, - RESET_DONE); - set_firmware(buf, MEM_ADDR); + load_reset_firmware(RESET_DONE); /* * Invalidate translation blocks for the firmware region so QEMU re-fetches the new code. * Without this, QEMU would execute stale cached instructions after reset. */ - m_inst_a.get().tb_invalidate_phys_range(MEM_ADDR, MEM_ADDR + sizeof(buf)); + m_inst_a.get().tb_invalidate_phys_range(MEM_ADDR, MEM_ADDR + FIRMWARE_SPAN); /* * Triggers the reset, this is setup via "sensitive << reset_event" above */ diff --git a/tests/qbox/cpu/hexagon/hexagon_smmu_stress_test_v2.cc b/tests/qbox/cpu/hexagon/hexagon-smmu-stress-test-v2.cc similarity index 99% rename from tests/qbox/cpu/hexagon/hexagon_smmu_stress_test_v2.cc rename to tests/qbox/cpu/hexagon/hexagon-smmu-stress-test-v2.cc index a6208455..7ccee2c7 100644 --- a/tests/qbox/cpu/hexagon/hexagon_smmu_stress_test_v2.cc +++ b/tests/qbox/cpu/hexagon/hexagon-smmu-stress-test-v2.cc @@ -18,7 +18,6 @@ #include #include #include -#include #include #include "cci/cfg/cci_broker_if.h" #include "sysc/kernel/sc_time.h" diff --git a/tests/qbox/cpu/hexagon/hexagon_smmu_firmware.ld b/tests/qbox/cpu/hexagon/hexagon-smmu-stress-test-v2.ld similarity index 100% rename from tests/qbox/cpu/hexagon/hexagon_smmu_firmware.ld rename to tests/qbox/cpu/hexagon/hexagon-smmu-stress-test-v2.ld diff --git a/tests/qbox/cpu/hexagon/hexagon_smmu_firmware.s b/tests/qbox/cpu/hexagon/hexagon-smmu-stress-test-v2.s similarity index 99% rename from tests/qbox/cpu/hexagon/hexagon_smmu_firmware.s rename to tests/qbox/cpu/hexagon/hexagon-smmu-stress-test-v2.s index b0fd9423..92f4fd5a 100644 --- a/tests/qbox/cpu/hexagon/hexagon_smmu_firmware.s +++ b/tests/qbox/cpu/hexagon/hexagon-smmu-stress-test-v2.s @@ -1,6 +1,6 @@ /* * Hexagon SMMU Stress Test Firmware - * Translated from ARM64 version in smmu_router_stress_test_v2.cc + * Translated from ARM64 version in smmu-router-stress-test-v2.cc * * This firmware runs on Hexagon CPUs behind SMMU TBUs, testing address * translation and multi-CPU memory access patterns using DMA operations. diff --git a/tests/qbox/cpu/hexagon/ld-st-mmio.S b/tests/qbox/cpu/hexagon/ld-st-mmio.S new file mode 100644 index 00000000..2674782f --- /dev/null +++ b/tests/qbox/cpu/hexagon/ld-st-mmio.S @@ -0,0 +1,25 @@ +.text +.globl boot_start +boot_start: + // boot_start is the entry point for all Hexagon firmwares in Qbox tests + // (see firmware.ld). +_start: + // Load MMIO address from the constants section at the end of the binary + r0 = memw(##mmio_addr) + + // Load and discard from MMIO_ADDR + r2 = memw(r0) + + // Store 0x0f0f0f0f to MMIO_ADDR + r3 = #0x0f0f0f0f + memw(r0) = r3 + +end: + // `wait(r0)` — encoded literally because some Hexagon versions of llvm-mc + // don't accept the mnemonic; the bytes match the machine encoding. + .word 0x6440c000 + jump end + +.align 4 +mmio_addr: + .word 0 diff --git a/tests/qbox/cpu/hexagon/ld-st-mmio.cc b/tests/qbox/cpu/hexagon/ld-st-mmio.cc index d9b4b5cd..a7aea81e 100644 --- a/tests/qbox/cpu/hexagon/ld-st-mmio.cc +++ b/tests/qbox/cpu/hexagon/ld-st-mmio.cc @@ -4,10 +4,6 @@ * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ -#include -#include -#include - #include "test/cpu.h" #include "test/tester/mmio.h" @@ -23,25 +19,6 @@ class CpuHexagonLdStTest : public CpuTestBench bool passed = false; hexagon_globalreg hex_gregs; - static constexpr const char* FIRMWARE = R"( -_start: - r0 = #0x%08)" PRIx32 R"( - - //load and discard the result from CpuTesterMmio::MMIO_ADDR: - r2 = memw(r0) - - //store a known value to CpuTesterMmio::MMIO_ADDR: - r3 = #0x0f0f0f0f - memw(r0) = r3 -end: - // This instruction is not supported by keystone: - // wait(r0) - // So we'll just bypass it and use the word we got from a reference - // assembler: - .word 0x6440c000 - jump end - )"; - public: CpuHexagonLdStTest(const sc_core::sc_module_name& n) : CpuTestBench(n), hex_gregs("hexagon_globalreg", &m_inst_a) @@ -53,9 +30,8 @@ class CpuHexagonLdStTest : public CpuTestBench } hex_gregs.p_hexagon_start_addr = MEM_ADDR; - char buf[1024]; - std::snprintf(buf, sizeof(buf), FIRMWARE, static_cast(CpuTesterMmio::MMIO_ADDR)); - set_firmware(buf, MEM_ADDR); + load_firmware_binary(FIRMWARE_BIN_PATH, MEM_ADDR, + std::initializer_list{ static_cast(CpuTesterMmio::MMIO_ADDR) }); } virtual ~CpuHexagonLdStTest() {} diff --git a/tests/qbox/cpu/hexagon/load-store-test.S b/tests/qbox/cpu/hexagon/load-store-test.S new file mode 100644 index 00000000..99acc8c5 --- /dev/null +++ b/tests/qbox/cpu/hexagon/load-store-test.S @@ -0,0 +1,21 @@ +.text +.globl boot_start +boot_start: +_start: + r0 = memw(##mmio_addr) + + // load and discard the result from MMIO_ADDR + r2 = memw(r0) + + // store 0x0f0f0f0f to MMIO_ADDR + r3 = #0x0f0f0f0f + memw(r0) = r3 + +end: + // wait(r0) — encoded literally, see ld-st-mmio.S + .word 0x6440c000 + jump end + +.align 4 +mmio_addr: + .word 0 diff --git a/tests/qbox/cpu/hexagon/load-store-test.cc b/tests/qbox/cpu/hexagon/load-store-test.cc index 57a8da83..6317a077 100644 --- a/tests/qbox/cpu/hexagon/load-store-test.cc +++ b/tests/qbox/cpu/hexagon/load-store-test.cc @@ -14,25 +14,6 @@ class LoadStoreTest : public sc_core::sc_module, public MmioWriter, public MmioReader { private: - static constexpr const char* FIRMWARE = R"( - _start: - r0 = #0x%08)" PRIx32 R"( - - //load and discard the result from CpuTesterMmio::MMIO_ADDR: - r2 = memw(r0) - - //store a known value to CpuTesterMmio::MMIO_ADDR: - r3 = #0x0f0f0f0f - memw(r0) = r3 - end: - // This instruction is not supported by keystone: - // wait(r0) - // So we'll just bypass it and use the word we got from a reference - // assembler: - .word 0x6440c000 - jump end - )"; - static constexpr uint64_t MEM_ADDR = 0x0; static constexpr size_t MEM_SIZE = 256 * 1024; static constexpr uint32_t QUANTUM = 1000000; @@ -70,9 +51,8 @@ class LoadStoreTest : public sc_core::sc_module, public MmioWriter, public MmioR router.add_target(memory.socket, MEM_ADDR, MEM_SIZE); router.add_initiator(cpu.socket); - char buf[1024]; - std::snprintf(buf, sizeof(buf), FIRMWARE, static_cast(MmioProbe::MMIO_ADDR)); - load_firmware(memory, buf, MEM_ADDR); + load_firmware(memory, FIRMWARE_BIN_PATH, MEM_ADDR, + std::initializer_list{ static_cast(MmioProbe::MMIO_ADDR) }); mmio_probe.connect_to_mmio_writer(this); mmio_probe.connect_to_mmio_reader(this); diff --git a/tests/qbox/cpu/hexagon/test.h b/tests/qbox/cpu/hexagon/test.h index b273ba59..18b22834 100644 --- a/tests/qbox/cpu/hexagon/test.h +++ b/tests/qbox/cpu/hexagon/test.h @@ -4,9 +4,14 @@ #ifndef TEST_H #define TEST_H +#include +#include +#include +#include #include #include #include +#include #include @@ -14,7 +19,6 @@ #include "cciutils.h" #include "cci/utils/broker.h" #include "gs_memory.h" -#include "keystone/keystone.h" #include "scp/report.h" bool run_systemc() @@ -37,28 +41,42 @@ bool run_systemc() return test_failed; } -void load_firmware(gs::gs_memory<>& destination, const char* assembly, uint64_t addr = 0) +/* + * Load a pre-assembled firmware .bin (built by llvm-mc + ld.lld + + * llvm-objcopy at CMake-configure time). Each entry in patch_words overwrites + * a 32-bit word at the tail of the binary — the Nth entry lands at + * (size - (N+1)*4). The firmware .S file declares matching .word placeholders + * at the end of its .data section. + */ +inline void load_firmware(gs::gs_memory<>& destination, const char* bin_path, uint64_t addr, + std::initializer_list patch_words = {}) { - ks_engine* ks = nullptr; - - ks_err err = ks_open(KS_ARCH_HEXAGON, KS_MODE_LITTLE_ENDIAN, &ks); - if (KS_ERR_OK != err) { - SCP_FATAL() << "Unable to initialize keystone"; + std::ifstream file(bin_path, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + SCP_FATAL() << "Failed to open firmware file: " << bin_path; } - size_t count = 0; - uint8_t* fw = nullptr; - size_t size = 0; + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); - if (ks_asm(ks, assembly, addr, &fw, &size, &count)) { - SCP_FATAL() << "Unable to assemble the test firmware: " << ks_strerror(ks_errno(ks)) << " (" << ks_errno(ks) - << ")"; + std::vector data(size); + if (!file.read(reinterpret_cast(data.data()), size)) { + SCP_FATAL() << "Failed to read firmware file: " << bin_path; } - destination.load.ptr_load(fw, addr, size); + const size_t needed = patch_words.size() * sizeof(uint32_t); + if (data.size() < needed) { + SCP_FATAL() << "Firmware " << bin_path << " size " << data.size() << " < " << needed + << " bytes needed for patches"; + } + size_t idx = 0; + for (uint32_t v : patch_words) { + size_t offset = data.size() - (patch_words.size() - idx) * sizeof(uint32_t); + std::memcpy(data.data() + offset, &v, sizeof(uint32_t)); + ++idx; + } - ks_free(fw); - ks_close(ks); + destination.load.ptr_load(data.data(), addr, data.size()); } template diff --git a/tests/qbox/cpu/riscv32/CMakeLists.txt b/tests/qbox/cpu/riscv32/CMakeLists.txt index cee84e75..0e8e6dcb 100644 --- a/tests/qbox/cpu/riscv32/CMakeLists.txt +++ b/tests/qbox/cpu/riscv32/CMakeLists.txt @@ -1,176 +1,20 @@ -# Build assembly firmware for RISC-V reset test -find_program(LLVM_MC llvm-mc) -find_program(LLD ld.lld) -find_program(LLVM_OBJCOPY llvm-objcopy) - -# "and APPLE" workaround: QQVPQSP-714 -if(LLVM_MC AND LLD AND LLVM_OBJCOPY AND APPLE) - # Assemble the RISC-V 32-bit assembly file - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/riscv32-reset.o - COMMAND ${LLVM_MC} -arch=riscv32 -filetype=obj - ${CMAKE_CURRENT_SOURCE_DIR}/riscv32-reset.S - -o ${CMAKE_CURRENT_BINARY_DIR}/riscv32-reset.o - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/riscv32-reset.S - COMMENT "Assembling riscv32-reset.S" - ) - - # Link the object file - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/riscv32-reset.elf - COMMAND ${LLD} -T ${CMAKE_CURRENT_SOURCE_DIR}/riscv32-reset.ld - ${CMAKE_CURRENT_BINARY_DIR}/riscv32-reset.o - -o ${CMAKE_CURRENT_BINARY_DIR}/riscv32-reset.elf - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/riscv32-reset.o - ${CMAKE_CURRENT_SOURCE_DIR}/riscv32-reset.ld - COMMENT "Linking riscv32-reset.elf" - ) - - # Create binary file - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/riscv32-reset.bin - COMMAND ${LLVM_OBJCOPY} -O binary - ${CMAKE_CURRENT_BINARY_DIR}/riscv32-reset.elf - ${CMAKE_CURRENT_BINARY_DIR}/riscv32-reset.bin - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/riscv32-reset.elf - COMMENT "Creating riscv32-reset.bin" - ) - - # Create a custom target for the firmware - add_custom_target(riscv32-reset-firmware - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/riscv32-reset.bin) - - qbox_add_cpu_test(riscv32-reset-test 100 riscv32-reset.cc) - add_dependencies(riscv32-reset-test riscv32-reset-firmware) - target_compile_definitions(riscv32-reset-test PRIVATE - FIRMWARE_BIN_PATH="${CMAKE_CURRENT_BINARY_DIR}/riscv32-reset.bin") - - # Build assembly firmware for RISC-V DMI test - # Assemble the RISC-V 32-bit DMI test assembly file - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/riscv32-dmi-test.o - COMMAND ${LLVM_MC} -arch=riscv32 -filetype=obj - ${CMAKE_CURRENT_SOURCE_DIR}/riscv32-dmi-test.S - -o ${CMAKE_CURRENT_BINARY_DIR}/riscv32-dmi-test.o - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/riscv32-dmi-test.S - COMMENT "Assembling riscv32-dmi-test.S" - ) - - # Link the DMI test object file - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/riscv32-dmi-test.elf - COMMAND ${LLD} -T ${CMAKE_CURRENT_SOURCE_DIR}/riscv32-dmi-test.ld - ${CMAKE_CURRENT_BINARY_DIR}/riscv32-dmi-test.o - -o ${CMAKE_CURRENT_BINARY_DIR}/riscv32-dmi-test.elf - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/riscv32-dmi-test.o - ${CMAKE_CURRENT_SOURCE_DIR}/riscv32-dmi-test.ld - COMMENT "Linking riscv32-dmi-test.elf" - ) - - # Create DMI test binary file - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/riscv32-dmi-test.bin - COMMAND ${LLVM_OBJCOPY} -O binary - ${CMAKE_CURRENT_BINARY_DIR}/riscv32-dmi-test.elf - ${CMAKE_CURRENT_BINARY_DIR}/riscv32-dmi-test.bin - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/riscv32-dmi-test.elf - COMMENT "Creating riscv32-dmi-test.bin" - ) - - # Create a custom target for the DMI test firmware - add_custom_target(riscv32-dmi-test-firmware - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/riscv32-dmi-test.bin) - - qbox_add_cpu_test(riscv32-dmi-test 100 riscv32-dmi-test.cc) - add_dependencies(riscv32-dmi-test riscv32-dmi-test-firmware) - target_compile_definitions(riscv32-dmi-test PRIVATE - FIRMWARE_BIN_PATH="${CMAKE_CURRENT_BINARY_DIR}/riscv32-dmi-test.bin") - - # Build assembly firmware for RISC-V IRQ test - # Assemble the RISC-V 32-bit IRQ test assembly file - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/riscv32-irq-test.o - COMMAND ${LLVM_MC} -arch=riscv32 -filetype=obj - ${CMAKE_CURRENT_SOURCE_DIR}/riscv32-irq-test.S - -o ${CMAKE_CURRENT_BINARY_DIR}/riscv32-irq-test.o - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/riscv32-irq-test.S - COMMENT "Assembling riscv32-irq-test.S" - ) - - # Link the IRQ test object file - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/riscv32-irq-test.elf - COMMAND ${LLD} -T ${CMAKE_CURRENT_SOURCE_DIR}/riscv32-irq-test.ld - ${CMAKE_CURRENT_BINARY_DIR}/riscv32-irq-test.o - -o ${CMAKE_CURRENT_BINARY_DIR}/riscv32-irq-test.elf - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/riscv32-irq-test.o - ${CMAKE_CURRENT_SOURCE_DIR}/riscv32-irq-test.ld - COMMENT "Linking riscv32-irq-test.elf" - ) - - # Create IRQ test binary file - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/riscv32-irq-test.bin - COMMAND ${LLVM_OBJCOPY} -O binary - ${CMAKE_CURRENT_BINARY_DIR}/riscv32-irq-test.elf - ${CMAKE_CURRENT_BINARY_DIR}/riscv32-irq-test.bin - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/riscv32-irq-test.elf - COMMENT "Creating riscv32-irq-test.bin" - ) - - # Create a custom target for the IRQ test firmware - add_custom_target(riscv32-irq-test-firmware - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/riscv32-irq-test.bin) - - qbox_add_cpu_test(riscv32-irq-test 100 riscv32-irq-test.cc) - add_dependencies(riscv32-irq-test riscv32-irq-test-firmware) - target_compile_definitions(riscv32-irq-test PRIVATE - FIRMWARE_BIN_PATH="${CMAKE_CURRENT_BINARY_DIR}/riscv32-irq-test.bin") - target_include_directories(riscv32-irq-test PRIVATE - $) - - # Build assembly firmware for RISC-V QTimer test - # Assemble the RISC-V 32-bit QTimer test assembly file - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/riscv32-qtimer-test.o - COMMAND ${LLVM_MC} -arch=riscv32 -filetype=obj - ${CMAKE_CURRENT_SOURCE_DIR}/riscv32-qtimer-test.S - -o ${CMAKE_CURRENT_BINARY_DIR}/riscv32-qtimer-test.o - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/riscv32-qtimer-test.S - COMMENT "Assembling riscv32-qtimer-test.S" - ) - - # Link the QTimer test object file - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/riscv32-qtimer-test.elf - COMMAND ${LLD} -T ${CMAKE_CURRENT_SOURCE_DIR}/riscv32-qtimer-test.ld - ${CMAKE_CURRENT_BINARY_DIR}/riscv32-qtimer-test.o - -o ${CMAKE_CURRENT_BINARY_DIR}/riscv32-qtimer-test.elf - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/riscv32-qtimer-test.o - ${CMAKE_CURRENT_SOURCE_DIR}/riscv32-qtimer-test.ld - COMMENT "Linking riscv32-qtimer-test.elf" - ) - - # Create QTimer test binary file - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/riscv32-qtimer-test.bin - COMMAND ${LLVM_OBJCOPY} -O binary - ${CMAKE_CURRENT_BINARY_DIR}/riscv32-qtimer-test.elf - ${CMAKE_CURRENT_BINARY_DIR}/riscv32-qtimer-test.bin - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/riscv32-qtimer-test.elf - COMMENT "Creating riscv32-qtimer-test.bin" - ) - - # Create a custom target for the QTimer test firmware - add_custom_target(riscv32-qtimer-test-firmware - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/riscv32-qtimer-test.bin) - - qbox_add_cpu_test(riscv32-qtimer-test 100 riscv32-qtimer-test.cc) - add_dependencies(riscv32-qtimer-test riscv32-qtimer-test-firmware) - target_compile_definitions(riscv32-qtimer-test PRIVATE - FIRMWARE_BIN_PATH="${CMAKE_CURRENT_BINARY_DIR}/riscv32-qtimer-test.bin") - target_include_directories(riscv32-qtimer-test PRIVATE - $) -else() - message(WARNING "LLVM tools not found - skipping riscv32-reset-test, riscv32-dmi-test, riscv32-irq-test, and riscv32-qtimer-test") -endif() +# Each RISC-V test ships a paired .S + .ld and gets its firmware built via the +# shared helper defined in tests/qbox/CMakeLists.txt. + +# Thin wrapper that pins ARCH=riscv32. Firmware base name is derived from +# the first source file's stem, so call sites only state target, timeout, +# and sources. +function(qbox_add_cpu_riscv32_test target timeout) + qbox_add_cpu_test(${target} ${timeout} ${ARGN} ARCH riscv32) +endfunction() + +qbox_add_cpu_riscv32_test(riscv32-reset-test 100 riscv32-reset.cc) +qbox_add_cpu_riscv32_test(riscv32-dmi-test 100 riscv32-dmi-test.cc) + +qbox_add_cpu_riscv32_test(riscv32-irq-test 100 riscv32-irq-test.cc) +target_include_directories(riscv32-irq-test PRIVATE + $) + +qbox_add_cpu_riscv32_test(riscv32-qtimer-test 100 riscv32-qtimer-test.cc) +target_include_directories(riscv32-qtimer-test PRIVATE + $) diff --git a/tests/qbox/cpu/riscv32/riscv32-reset.cc b/tests/qbox/cpu/riscv32/riscv32-reset.cc index dc07d98f..3f6d814c 100644 --- a/tests/qbox/cpu/riscv32/riscv32-reset.cc +++ b/tests/qbox/cpu/riscv32/riscv32-reset.cc @@ -48,56 +48,11 @@ class CpuRiscv32ResetGPIOTest : public CpuTestBench int reset_done; int time_elapsed_ms; - /* - * Load RISC-V 32-bit firmware from binary file compiled with LLVM tools. - * The binary contains the constants at the end that need to be patched. - */ - void load_firmware_binary(uint32_t mmio_addr, uint32_t trigger_val, uint32_t done_val) + void load_reset_firmware(uint32_t mmio_addr, uint32_t trigger_val, uint32_t done_val) { - SCP_DEBUG(SCMOD) << "load_firmware_binary called with mmio_addr=0x" << std::hex << mmio_addr - << ", trigger_val=" << std::dec << trigger_val << ", done_val=" << done_val; - // Load the compiled firmware binary - const char* firmware_path = FIRMWARE_BIN_PATH; - std::ifstream file(firmware_path, std::ios::binary | std::ios::ate); - - if (!file.is_open()) { - SCP_FATAL(SCMOD) << "Failed to open RISC-V firmware file: " << firmware_path; - TEST_ASSERT(false); - } - - std::streamsize size = file.tellg(); - file.seekg(0, std::ios::beg); - - std::vector firmware_data(size); - if (!file.read(reinterpret_cast(firmware_data.data()), size)) { - SCP_FATAL(SCMOD) << "Failed to read RISC-V firmware file: " << firmware_path; - TEST_ASSERT(false); - } - - // Patch the constants in the binary (last 12 bytes: mmio_addr, trigger_val, done_val) - TEST_ASSERT(size >= 12); - uint32_t* mmio_addr_ptr = reinterpret_cast(firmware_data.data() + size - 12); - uint32_t* trigger_val_ptr = reinterpret_cast(firmware_data.data() + size - 8); - uint32_t* done_val_ptr = reinterpret_cast(firmware_data.data() + size - 4); - - *mmio_addr_ptr = mmio_addr; - *trigger_val_ptr = trigger_val; - *done_val_ptr = done_val; - - // Log firmware being loaded (for verification during testing) - if (trigger_val == RESET_TRIGGER) { - SCP_INFO(SCMOD) << "Loading initial RISC-V firmware with trigger_val=" << trigger_val << " from " - << firmware_path; - } else { - SCP_INFO(SCMOD) << "Loading reset firmware with trigger_val=" << trigger_val << " from " << firmware_path; - } - - // Load firmware directly into memory without keystone assembly - SCP_DEBUG(SCMOD) << "Loading RISC-V firmware at 0x" << std::hex << MEM_ADDR << " (MEM_ADDR), size=" << std::dec - << size << " bytes"; - m_mem.load.ptr_load(firmware_data.data(), MEM_ADDR, size); - - SCP_DEBUG(SCMOD) << "Firmware loaded successfully"; + SCP_INFO(SCMOD) << "Loading RISC-V firmware (trigger_val=" << trigger_val << ") from " << FIRMWARE_BIN_PATH; + load_firmware_binary(FIRMWARE_BIN_PATH, MEM_ADDR, + std::initializer_list{ mmio_addr, trigger_val, done_val }); } public: @@ -124,7 +79,7 @@ class CpuRiscv32ResetGPIOTest : public CpuTestBench dont_initialize(); // Load RISC-V 32-bit binary firmware compiled with LLVM tools - load_firmware_binary(static_cast(CpuTesterMmio::MMIO_ADDR), RESET_TRIGGER, RESET_DONE); + load_reset_firmware(static_cast(CpuTesterMmio::MMIO_ADDR), RESET_TRIGGER, RESET_DONE); } virtual ~CpuRiscv32ResetGPIOTest() {} @@ -148,9 +103,9 @@ class CpuRiscv32ResetGPIOTest : public CpuTestBench * Load the final firmware image, now the image will write to this address again * with a RESET_DONE value and then wait. */ - SCP_DEBUG(SCMOD) << "About to call load_firmware_binary with RESET_DONE"; - load_firmware_binary(static_cast(CpuTesterMmio::MMIO_ADDR), RESET_DONE, RESET_DONE); - SCP_DEBUG(SCMOD) << "load_firmware_binary call completed"; + SCP_DEBUG(SCMOD) << "About to call load_reset_firmware with RESET_DONE"; + load_reset_firmware(static_cast(CpuTesterMmio::MMIO_ADDR), RESET_DONE, RESET_DONE); + SCP_DEBUG(SCMOD) << "load_reset_firmware call completed"; /* * Invalidate TB cache to ensure cached translations of old firmware are cleared diff --git a/tests/qbox/include/test/cpu.h b/tests/qbox/include/test/cpu.h index 199ac68e..e1a6e998 100644 --- a/tests/qbox/include/test/cpu.h +++ b/tests/qbox/include/test/cpu.h @@ -9,7 +9,11 @@ #ifndef TESTS_INCLUDE_TEST_CPU_H #define TESTS_INCLUDE_TEST_CPU_H -#include +#include +#include +#include +#include +#include #include #include @@ -40,26 +44,6 @@ class CpuTestBenchBase : public TestBench, public CpuTesterCallbackIface static constexpr uint64_t BULKMEM_ADDR = 0x100000000; static constexpr size_t BULKMEM_SIZE = 1024 * 1024 * 1024; -private: - ks_arch qemu_to_ks_arch(qemu::Target arch) - { - switch (arch) { - case qemu::Target::AARCH64: - return KS_ARCH_ARM64; - - case qemu::Target::HEXAGON: - return KS_ARCH_HEXAGON; - - case qemu::Target::RISCV32: - // RISC-V 32-bit not supported by Keystone, but we can compile firmware manually - return KS_ARCH_MAX; - - default: - SCP_FATAL(SCMOD) << "Unsupported QEMU architecture for Keystone"; - return KS_ARCH_MAX; /* avoid compiler warning */ - } - } - protected: qemu::Target m_arch; @@ -70,31 +54,51 @@ class CpuTestBenchBase : public TestBench, public CpuTesterCallbackIface gs::gs_memory<> m_mem; gs::gs_memory<> m_bulkmem; - void set_firmware(const char* assembly, uint64_t addr = 0) + /* + * Load a pre-assembled firmware .bin (built at CMake-configure time by + * llvm-mc + ld.lld + llvm-objcopy) into m_mem at load_addr. + * + * Each entry in the patches list overwrites a sizeof(T)-wide word at the + * tail of the binary — the Nth entry lands at (size - (N+1)*sizeof(T)). + * Firmware .S files declare matching placeholders at the end of their + * .data section: .quad for uint64_t (aarch64), .word for uint32_t + * (RISC-V / Hexagon). + */ + template + void load_firmware_binary(const char* bin_path, uint64_t load_addr, std::initializer_list patches = {}) { - ks_engine* ks; - ks_err err; - size_t size, count; - uint8_t* fw; - - err = ks_open(qemu_to_ks_arch(m_arch), KS_MODE_LITTLE_ENDIAN, &ks); - - if (err != KS_ERR_OK) { - SCP_FATAL(SCMOD) << "Unable to initialize keystone"; + std::vector data = read_firmware_file(bin_path); + const size_t needed = patches.size() * sizeof(T); + if (data.size() < needed) { + SCP_FATAL(SCMOD) << "Firmware " << bin_path << " size " << data.size() << " < " << needed + << " bytes needed for patches"; + TEST_FAIL("Firmware too small for patch values"); } - - if (ks_asm(ks, assembly, addr, &fw, &size, &count) != KS_ERR_OK || size == 0) { - std::cerr << assembly << "\n"; - std::cerr << "errno: " << ks_errno(ks) << "\n"; - std::cerr << "error: " << ks_strerror(ks_errno(ks)) << "\n"; - SCP_INFO() << assembly; - TEST_FAIL("Unable to assemble the test firmware\n"); + size_t idx = 0; + for (T v : patches) { + size_t offset = data.size() - (patches.size() - idx) * sizeof(T); + std::memcpy(data.data() + offset, &v, sizeof(T)); + ++idx; } + m_mem.load.ptr_load(data.data(), load_addr, data.size()); + } - m_mem.load.ptr_load(fw, addr, size); - - ks_free(fw); - ks_close(ks); +private: + std::vector read_firmware_file(const char* bin_path) + { + std::ifstream file(bin_path, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + SCP_FATAL(SCMOD) << "Failed to open firmware file: " << bin_path; + TEST_FAIL("Failed to open firmware file"); + } + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + std::vector data(size); + if (!file.read(reinterpret_cast(data.data()), size)) { + SCP_FATAL(SCMOD) << "Failed to read firmware file: " << bin_path; + TEST_FAIL("Failed to read firmware file"); + } + return data; } public: