diff --git a/components/esp_hal_security/test_apps/tee/main/CMakeLists.txt b/components/esp_hal_security/test_apps/tee/main/CMakeLists.txt index a73b1b83003..9feb9c83b4b 100644 --- a/components/esp_hal_security/test_apps/tee/main/CMakeLists.txt +++ b/components/esp_hal_security/test_apps/tee/main/CMakeLists.txt @@ -7,11 +7,3 @@ idf_component_register(SRCS "${srcs}" INCLUDE_DIRS "" PRIV_REQUIRES pms_and_cpu_intr esp_psram esp_hal_security WHOLE_ARCHIVE) - -# TODO: IDF-14145 -if(CONFIG_IDF_TARGET_ESP32C5 OR CONFIG_IDF_TARGET_ESP32C61) - # Disable zcmp extension to avoid hardware issue with interrupts (DIG-661) - set(march_option "-march=rv32imac_zicsr_zifencei_zaamo_zalrsc") - idf_build_set_property(COMPILE_OPTIONS "${march_option}" APPEND) - idf_build_set_property(LINK_OPTIONS "${march_option}" APPEND) -endif() diff --git a/components/esp_hal_security/test_apps/tee/sdkconfig.defaults.esp32c5 b/components/esp_hal_security/test_apps/tee/sdkconfig.defaults.esp32c5 index 0139d521ec0..d87ea7b932e 100644 --- a/components/esp_hal_security/test_apps/tee/sdkconfig.defaults.esp32c5 +++ b/components/esp_hal_security/test_apps/tee/sdkconfig.defaults.esp32c5 @@ -5,3 +5,6 @@ CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=n # Enable SPIRAM CONFIG_SPIRAM=y + +# TODO: IDF-14145 +CONFIG_COMPILER_ENABLE_RISCV_ZCMP=n diff --git a/components/esp_hal_security/test_apps/tee/sdkconfig.defaults.esp32c61 b/components/esp_hal_security/test_apps/tee/sdkconfig.defaults.esp32c61 index 5665cfbb5cd..3125c3a97af 100644 --- a/components/esp_hal_security/test_apps/tee/sdkconfig.defaults.esp32c61 +++ b/components/esp_hal_security/test_apps/tee/sdkconfig.defaults.esp32c61 @@ -1,2 +1,5 @@ # Enable SPIRAM CONFIG_SPIRAM=y + +# TODO: IDF-14145 +CONFIG_COMPILER_ENABLE_RISCV_ZCMP=n diff --git a/components/esp_hw_support/lowpower/port/esp32c61/sleep_cpu.c b/components/esp_hw_support/lowpower/port/esp32c61/sleep_cpu.c index 350e726a7c9..71b2596c796 100644 --- a/components/esp_hw_support/lowpower/port/esp32c61/sleep_cpu.c +++ b/components/esp_hw_support/lowpower/port/esp32c61/sleep_cpu.c @@ -70,20 +70,6 @@ FORCE_INLINE_ATTR void restore_csr_enable_global_int(uint32_t mstatus_val, uint3 #endif } -#if __riscv_zcmp && SOC_CPU_ZCMP_WORKAROUND -FORCE_INLINE_ATTR uint32_t save_mintthresh_and_disable_global_int(void) -{ - /* Due to the reason described in IDF-14279, when mie is set to 0, mintthresh needs to be set to 0xff. */ - // TODO: IDF-14279 DIG-661 - return RV_READ_MINTTHRESH_AND_DISABLE_INTR(); -} - -FORCE_INLINE_ATTR void restore_mintthresh(uint32_t mintthresh_val) -{ - RV_RESTORE_MINTTHRESH(mintthresh_val); -} -#endif - static IRAM_ATTR RvCoreNonCriticalSleepFrame * rv_core_noncritical_regs_save(void) { assert(s_cpu_retention.retent.non_critical_frame); diff --git a/components/esp_system/port/soc/esp32c5/system_internal.c b/components/esp_system/port/soc/esp32c5/system_internal.c index ea641db79f1..d9406b1c5cf 100644 --- a/components/esp_system/port/soc/esp32c5/system_internal.c +++ b/components/esp_system/port/soc/esp32c5/system_internal.c @@ -28,6 +28,9 @@ #endif #include "esp_private/cache_err_int.h" #include "esp_memory_utils.h" +#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD && CONFIG_COMPILER_ENABLE_RISCV_ZCMP +#include "esp_private/hw_stack_guard.h" +#endif #define ALIGN_DOWN(val, align) ((val) & ~((align) - 1)) extern int _bss_end; @@ -163,6 +166,12 @@ void esp_restart_noos(void) // If stack is in external RAM (CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM), switch SP to // internal RAM before disabling the cache to avoid a "Cache disabled but cached memory // region accessed" crash. +#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD && CONFIG_COMPILER_ENABLE_RISCV_ZCMP + // Stop hw stack guard before changing SP: new SP is outside task stack bounds. + // rtc_clk_cpu_set_to_default_config() (called later) uses vPortEnterCritical() via + // ENABLE_CLK_GATE, which can trigger DIG-661 (assist-debug interrupt with mstatus.mie=0). + esp_hw_stack_guard_monitor_stop(); +#endif uint32_t new_sp = ALIGN_DOWN((uint32_t)&_bss_end, 16); rv_utils_set_sp((void *)new_sp); } diff --git a/components/esp_system/port/soc/esp32c61/system_internal.c b/components/esp_system/port/soc/esp32c61/system_internal.c index 2fe762e97a0..19495ab3235 100644 --- a/components/esp_system/port/soc/esp32c61/system_internal.c +++ b/components/esp_system/port/soc/esp32c61/system_internal.c @@ -24,6 +24,9 @@ #include "hal/uart_ll.h" #include "esp_private/cache_err_int.h" #include "esp_memory_utils.h" +#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD && CONFIG_COMPILER_ENABLE_RISCV_ZCMP +#include "esp_private/hw_stack_guard.h" +#endif #define ALIGN_DOWN(val, align) ((val) & ~((align) - 1)) extern int _bss_end; @@ -153,6 +156,12 @@ void esp_restart_noos(void) // If stack is in external RAM (CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM), switch SP to // internal RAM before disabling the cache to avoid a "Cache disabled but cached memory // region accessed" crash. +#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD && CONFIG_COMPILER_ENABLE_RISCV_ZCMP + // Stop hw stack guard before changing SP: new SP is outside task stack bounds. + // rtc_clk_cpu_set_to_default_config() (called later) uses vPortEnterCritical() via + // ENABLE_CLK_GATE, which can trigger DIG-661 (assist-debug interrupt with mstatus.mie=0). + esp_hw_stack_guard_monitor_stop(); +#endif uint32_t new_sp = ALIGN_DOWN((uint32_t)&_bss_end, 16); rv_utils_set_sp((void *)new_sp); } diff --git a/components/esp_system/port/soc/esp32h4/system_internal.c b/components/esp_system/port/soc/esp32h4/system_internal.c index a6e1080d694..d5b0a082735 100644 --- a/components/esp_system/port/soc/esp32h4/system_internal.c +++ b/components/esp_system/port/soc/esp32h4/system_internal.c @@ -23,6 +23,9 @@ #include "hal/wdt_hal.h" #include "hal/uart_ll.h" #include "esp_memory_utils.h" +#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD && CONFIG_COMPILER_ENABLE_RISCV_ZCMP +#include "esp_private/hw_stack_guard.h" +#endif #define ALIGN_DOWN(val, align) ((val) & ~((align) - 1)) extern int _bss_end; @@ -170,6 +173,12 @@ void esp_restart_noos(void) // If stack is in external RAM (CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM), switch SP to // internal RAM before disabling the cache to avoid a "Cache disabled but cached memory // region accessed" crash. +#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD && CONFIG_COMPILER_ENABLE_RISCV_ZCMP + // Stop hw stack guard before changing SP: new SP is outside task stack bounds. + // rtc_clk_cpu_set_to_default_config() (called later) uses vPortEnterCritical() via + // ENABLE_CLK_GATE, which can trigger DIG-661 (assist-debug interrupt with mstatus.mie=0). + esp_hw_stack_guard_monitor_stop(); +#endif uint32_t new_sp = ALIGN_DOWN((uint32_t)&_bss_end, 16); rv_utils_set_sp((void *)new_sp); } diff --git a/components/esp_system/port/soc/esp32p4/system_internal.c b/components/esp_system/port/soc/esp32p4/system_internal.c index 9b3c089b1e4..9a6a1950529 100644 --- a/components/esp_system/port/soc/esp32p4/system_internal.c +++ b/components/esp_system/port/soc/esp32p4/system_internal.c @@ -34,6 +34,9 @@ #include "hal/dma2d_ll.h" #include "hal/efuse_hal.h" #include "esp_memory_utils.h" +#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD && CONFIG_COMPILER_ENABLE_RISCV_ZCMP +#include "esp_private/hw_stack_guard.h" +#endif #define ALIGN_DOWN(val, align) ((val) & ~((align) - 1)) extern int _bss_end; @@ -249,6 +252,12 @@ void esp_restart_noos(void) // If stack is in external RAM (CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM), switch SP to // internal RAM before disabling the cache to avoid a "Cache disabled but cached memory // region accessed" crash. +#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD && CONFIG_COMPILER_ENABLE_RISCV_ZCMP + // Stop hw stack guard before changing SP: new SP is outside task stack bounds. + // rtc_clk_cpu_set_to_default_config() (called later) uses vPortEnterCritical() via + // ENABLE_CLK_GATE, which can trigger DIG-661 (assist-debug interrupt with mstatus.mie=0). + esp_hw_stack_guard_monitor_stop(); +#endif uint32_t new_sp = ALIGN_DOWN((uint32_t)&_bss_end, 16); rv_utils_set_sp((void *)new_sp); } diff --git a/components/riscv/CMakeLists.txt b/components/riscv/CMakeLists.txt index 12872530fe6..2f62cbe16a2 100644 --- a/components/riscv/CMakeLists.txt +++ b/components/riscv/CMakeLists.txt @@ -41,3 +41,37 @@ idf_component_register(SRCS "${srcs}" PRIV_REQUIRES ${priv_requires}) target_linker_script(${COMPONENT_LIB} INTERFACE "ld/rom.api.ld") + +# TODO: IDF-14145 +if(CONFIG_COMPILER_ENABLE_RISCV_ZCMP AND + NOT CONFIG_ESP32P4_SELECTS_REV_LESS_V3 AND + NOT CONFIG_SECURE_ENABLE_TEE AND + NOT ESP_TEE_BUILD) + if(IDF_BUILD_V2) + function(riscv_check_zcmp_workaround target) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${CMAKE_COMMAND} + -DELF_FILE=$ + -DCMAKE_OBJDUMP=${CMAKE_OBJDUMP} + -P "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/check_zcmp_workaround.cmake" + COMMENT "Checking ZCMP workaround in linked executable" + VERBATIM) + endfunction() + idf_component_register_build_event_callback(EVENT POST_ELF CALLBACK riscv_check_zcmp_workaround) + else() + idf_build_get_property(build_dir BUILD_DIR) + idf_build_get_property(elf_target EXECUTABLE GENERATOR_EXPRESSION) + set(_riscv_zcmp_check_marker "${build_dir}/.zcmp_workaround_checked") + add_custom_command(OUTPUT "${_riscv_zcmp_check_marker}" + COMMAND ${CMAKE_COMMAND} + -DELF_FILE=$> + -DCMAKE_OBJDUMP=${CMAKE_OBJDUMP} + -P "${CMAKE_CURRENT_LIST_DIR}/check_zcmp_workaround.cmake" + COMMAND ${CMAKE_COMMAND} -E touch "${_riscv_zcmp_check_marker}" + DEPENDS "$>" + COMMENT "Checking ZCMP workaround in linked executable" + VERBATIM) + add_custom_target(riscv_check_zcmp_workaround DEPENDS "${_riscv_zcmp_check_marker}") + idf_build_add_post_elf_dependency("${CMAKE_PROJECT_NAME}.elf" riscv_check_zcmp_workaround) + endif() +endif() diff --git a/components/riscv/check_zcmp_workaround.cmake b/components/riscv/check_zcmp_workaround.cmake new file mode 100644 index 00000000000..6e39a0495d7 --- /dev/null +++ b/components/riscv/check_zcmp_workaround.cmake @@ -0,0 +1,179 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +# +# Post-link check for RISC-V ZCMP workaround compliance. +# +# Required variables: +# ELF_FILE - path to the linked executable +# CMAKE_OBJDUMP - objdump tool from the toolchain +# +# Pure CMake implementation: objdump output is written to a temporary .objdump file, +# scanned line-by-line with file(STRINGS), then removed. Empty objdump lines are skipped. +# +# For every instruction that clears mstatus.MIE (bit 3) this check verifies that a +# mintthresh write (csrrw ...,0x347,...) of 0xff appears earlier in the same function. +# Instructions considered to clear MIE: +# csrrci mstatus,8 - immediate clear of bit 3 +# csrrw ...,mstatus,... - full mstatus overwrite +# csrrc ...,mstatus,... - register clear, only when the mask loaded +# on the previous line has bit 3 set +# csrrs (bit set) is intentionally ignored: it cannot clear MIE. + +set(_zcmp_disasm_file "${ELF_FILE}.objdump") +set(_mstatus_mie_re "csrrci.*mstatus,8|csrrw.*mstatus") +set(_mstatus_csrrc_re "csrrc.*mstatus") +set(_mintthresh_csrrw_re "csrrw.*,0x347,") +set(_mintthresh_value_base_re "(255|-256|0xff)") +set(_mintthresh_stack_move_re "(^|[ \t])(c\\.sw|c\\.lw|sw|lw)") +set(_mintthresh_call_re "(^|[ \t])(c\\.jal|jal)") +set(_function_start_re "^[0-9a-fA-F]+ <([^>]+)>:$") + +# Extract the last comma-separated operand of a disassembly line (e.g. "li a5,8" -> "8"). +function(_zcmp_last_operand line out_var) + set(${out_var} "" PARENT_SCOPE) + if(line MATCHES ",([^, \t]+)[ \t]*$") + set(${out_var} "${CMAKE_MATCH_1}" PARENT_SCOPE) + endif() +endfunction() + +# TRUE when the numeric operand (decimal or hex) has bit 3 set, i.e. it would clear MIE. +function(_zcmp_operand_clears_mie operand out_var) + set(_value 0) + if(operand MATCHES "^-?(0[xX][0-9a-fA-F]+|[0-9]+)$") + string(TOLOWER "${operand}" _norm) + math(EXPR _value "${_norm}") + endif() + math(EXPR _bit3 "${_value} & 0x8") + if(_bit3 EQUAL 0) + set(${out_var} FALSE PARENT_SCOPE) + else() + set(${out_var} TRUE PARENT_SCOPE) + endif() +endfunction() + +if(NOT DEFINED ELF_FILE) + message(FATAL_ERROR "ELF_FILE is not set") +endif() + +if(NOT DEFINED CMAKE_OBJDUMP) + message(FATAL_ERROR "CMAKE_OBJDUMP is not set") +endif() + +if(NOT EXISTS "${ELF_FILE}") + message(FATAL_ERROR "ELF file not found: ${ELF_FILE}") +endif() + +execute_process( + COMMAND "${CMAKE_OBJDUMP}" -d -M no-aliases "${ELF_FILE}" + OUTPUT_FILE "${_zcmp_disasm_file}" + ERROR_VARIABLE _zcmp_objdump_err + RESULT_VARIABLE _zcmp_objdump_result +) + +if(NOT _zcmp_objdump_result EQUAL 0) + message(FATAL_ERROR "Failed to disassemble ${ELF_FILE}: ${_zcmp_objdump_err}") +endif() + +file(STRINGS "${_zcmp_disasm_file}" _zcmp_lines) +file(REMOVE "${_zcmp_disasm_file}") + +set(_zcmp_function "") +set(_zcmp_function_lines "") +set(_zcmp_skip_function FALSE) + +# Functions exempt from the workaround (hand-audited / run with interrupts already masked). +set(_idf_trusted_function "rv_core_critical_regs_restore") + +foreach(_line IN LISTS _zcmp_lines) + string(STRIP "${_line}" _line) + if(_line STREQUAL "") + continue() + endif() + string(REPLACE ";" "\\;" _line "${_line}") + + # New function: reset the accumulated instruction window. The header line itself + # is not part of the window. + if(_line MATCHES "${_function_start_re}") + set(_zcmp_function "${CMAKE_MATCH_1}") + set(_zcmp_function_lines "") + # Trusted functions are exempt: skip every instruction until the next function header. + set(_zcmp_skip_function FALSE) + foreach(_trusted_function IN LISTS _idf_trusted_function) + if(_zcmp_function STREQUAL "${_trusted_function}") + set(_zcmp_skip_function TRUE) + break() + endif() + endforeach() + continue() + endif() + + if(_zcmp_skip_function) + continue() + endif() + + list(APPEND _zcmp_function_lines "${_line}") + list(LENGTH _zcmp_function_lines _line_count) + math(EXPR _cur_index "${_line_count} - 1") + + # ---- Does this line clear mstatus.MIE? ---- + set(_clears_mie FALSE) + if(_line MATCHES "${_mstatus_mie_re}") + set(_clears_mie TRUE) + elseif(_line MATCHES "${_mstatus_csrrc_re}") + set(_prev_operand "") + if(_cur_index GREATER 0) + math(EXPR _prev_index "${_cur_index} - 1") + list(GET _zcmp_function_lines ${_prev_index} _prev_line) + _zcmp_last_operand("${_prev_line}" _prev_operand) + endif() + _zcmp_operand_clears_mie("${_prev_operand}" _clears_mie) + endif() + + if(NOT _clears_mie) + continue() + endif() + + # ---- Require a preceding mintthresh write of 0xff in this function ---- + set(_has_mintthresh FALSE) + foreach(_scan_index RANGE 0 ${_cur_index}) + list(GET _zcmp_function_lines ${_scan_index} _scan_line) + if(NOT _scan_line MATCHES "${_mintthresh_csrrw_re}") + continue() + endif() + + # Found the mintthresh write. Extract its source register to narrow the value search. + _zcmp_last_operand("${_scan_line}" _mintthresh_src_reg) + if(_mintthresh_src_reg STREQUAL "") + continue() + endif() + set(_mintthresh_value_re "(^|[ \t])${_mintthresh_src_reg},(zero,)?${_mintthresh_value_base_re}") + + # Walk backward to the instruction that loaded 0xff into that register, + # skipping sw/lw stack traffic (-O0) and (c.)jal calls (-Os) the compiler may emit. + math(EXPR _value_index "${_scan_index} - 1") + while(_value_index GREATER_EQUAL 0) + list(GET _zcmp_function_lines ${_value_index} _value_line) + if(_value_line MATCHES "${_mintthresh_stack_move_re}" OR + _value_line MATCHES "${_mintthresh_call_re}") + math(EXPR _value_index "${_value_index} - 1") + continue() + endif() + if(NOT _value_line MATCHES "${_mintthresh_value_re}") + message(FATAL_ERROR + "ZCMP workaround violation: mintthresh write not preceded by value 0xff " + "(or equivalent 255/-256) in function ${_zcmp_function} in ${ELF_FILE}:\n${_value_line}") + endif() + set(_has_mintthresh TRUE) + break() + endwhile() + if(_has_mintthresh) + break() + endif() + endforeach() + + if(NOT _has_mintthresh) + message(FATAL_ERROR + "ZCMP workaround violation: mstatus access without mintthresh write " + "earlier in function ${_zcmp_function} in ${ELF_FILE}:\n${_line}") + endif() +endforeach() diff --git a/components/riscv/hints.yml b/components/riscv/hints.yml new file mode 100644 index 00000000000..14583514cce --- /dev/null +++ b/components/riscv/hints.yml @@ -0,0 +1,3 @@ +- + re: "ZCMP workaround violation" + hint: "Global interrupt disable (csrrc/csrrci mstatus,8) was found without the required mintthresh write (csrrw to CSR 0x347) within the previous instructions. This is unsafe with RISC-V ZCMP enabled on affected chips.\nEither apply the workaround before clearing mstatus.MIE (see SOC_CPU_ZCMP_WORKAROUND examples in ESP-IDF), or disable the ZCMP extension: set CONFIG_COMPILER_ENABLE_RISCV_ZCMP=n in sdkconfig / sdkconfig.defaults, or use 'idf.py menuconfig' -> 'Compiler options' -> disable 'Enable RISCV ZCMP extension'." diff --git a/components/riscv/test_apps/.build-test-rules.yml b/components/riscv/test_apps/.build-test-rules.yml new file mode 100644 index 00000000000..80f784ac3da --- /dev/null +++ b/components/riscv/test_apps/.build-test-rules.yml @@ -0,0 +1,13 @@ +# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps + +components/riscv/test_apps/test_zcmp_workaround_checking: + enable: + - if: SOC_CPU_ZCMP_WORKAROUND == 1 + reason: builds chips that require the ZCMP mintthresh workaround + disable_test: + - if: SOC_CPU_ZCMP_WORKAROUND == 1 + reason: build-only check for ZCMP workaround + depends_components: + - riscv + - soc + - freertos diff --git a/components/riscv/test_apps/test_zcmp_workaround_checking/CMakeLists.txt b/components/riscv/test_apps/test_zcmp_workaround_checking/CMakeLists.txt new file mode 100644 index 00000000000..e66304815f4 --- /dev/null +++ b/components/riscv/test_apps/test_zcmp_workaround_checking/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +idf_build_set_property(MINIMAL_BUILD ON) + +project(test_zcmp_workaround_checking) diff --git a/components/riscv/test_apps/test_zcmp_workaround_checking/README.md b/components/riscv/test_apps/test_zcmp_workaround_checking/README.md new file mode 100644 index 00000000000..62d84838365 --- /dev/null +++ b/components/riscv/test_apps/test_zcmp_workaround_checking/README.md @@ -0,0 +1,11 @@ +| Supported Targets | ESP32-C5 | ESP32-C61 | ESP32-H4 | ESP32-P4 | +| ----------------- | -------- | --------- | -------- | -------- | + +Build-only check for the post-link ZCMP workaround validator. + +CI builds the default app on every target with ``SOC_CPU_ZCMP_WORKAROUND`` +(valid mintthresh workaround + ZCMP). No flash; a successful link is the pass +criterion. + +Negative coverage lives in ``tools/test_build_system`` (cmake v1/v2): build with +``-DTEST_INVALID_WORKAROUND=1`` must fail with ``ZCMP workaround violation``. diff --git a/components/riscv/test_apps/test_zcmp_workaround_checking/main/CMakeLists.txt b/components/riscv/test_apps/test_zcmp_workaround_checking/main/CMakeLists.txt new file mode 100644 index 00000000000..e05cf4c8790 --- /dev/null +++ b/components/riscv/test_apps/test_zcmp_workaround_checking/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register(SRCS "test_zcmp_workaround_checking.c") + +if(TEST_INVALID_WORKAROUND) + target_compile_definitions(${COMPONENT_LIB} PRIVATE TEST_INVALID_WORKAROUND) +endif() diff --git a/components/riscv/test_apps/test_zcmp_workaround_checking/main/test_zcmp_workaround_checking.c b/components/riscv/test_apps/test_zcmp_workaround_checking/main/test_zcmp_workaround_checking.c new file mode 100644 index 00000000000..cd76b795125 --- /dev/null +++ b/components/riscv/test_apps/test_zcmp_workaround_checking/main/test_zcmp_workaround_checking.c @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include + +void app_main(void) +{ + uint32_t mstatus; +#ifdef TEST_INVALID_WORKAROUND + /* Intentional ZCMP violation: clear MIE without mintthresh write. */ + __asm__ volatile("csrrci %0, mstatus, 0x8" : "=r"(mstatus)); +#else + __asm__ volatile( + "li t0, 0xff\n" + "csrrw zero, 0x347, t0\n" + "csrrci %0, mstatus, 0x8" + : "=r"(mstatus) + : + : "t0"); +#endif + (void)mstatus; +} diff --git a/components/riscv/test_apps/test_zcmp_workaround_checking/sdkconfig.defaults b/components/riscv/test_apps/test_zcmp_workaround_checking/sdkconfig.defaults new file mode 100644 index 00000000000..7d659180e76 --- /dev/null +++ b/components/riscv/test_apps/test_zcmp_workaround_checking/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_COMPILER_ENABLE_RISCV_ZCMP=y diff --git a/tools/test_build_system/buildv2/test_zcmp_workaround.py b/tools/test_build_system/buildv2/test_zcmp_workaround.py new file mode 100644 index 00000000000..082a8e28f51 --- /dev/null +++ b/tools/test_build_system/buildv2/test_zcmp_workaround.py @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +import logging +from pathlib import Path + +import pytest +from test_build_system_helpers import IdfPyFunc + +_IDF_TARGET = 'esp32c5' + +_CMAKEV2_CMAKELISTS = """\ +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake) + +project(test_zcmp_workaround_checking C CXX ASM) + +idf_project_default() +""" + + +@pytest.mark.test_app_copy('components/riscv/test_apps/test_zcmp_workaround_checking') +@pytest.mark.usefixtures('test_app_copy') +def test_zcmp_workaround_checking(idf_py: IdfPyFunc, test_app_copy: Path) -> None: + """ZCMP POST_ELF check must fail when mstatus.mie is cleared without mintthresh workaround.""" + logging.info('Building zcmp workaround checking test app (build system v2)') + (test_app_copy / 'CMakeLists.txt').write_text(_CMAKEV2_CMAKELISTS, encoding='utf-8') + + idf_py('set-target', _IDF_TARGET) + ret = idf_py('-DTEST_INVALID_WORKAROUND=1', 'build', check=False) + output = (ret.stdout or '') + (ret.stderr or '') + + assert ret.returncode != 0, 'Build must fail when ZCMP workaround is violated' + assert 'ZCMP workaround violation' in output, 'Build output must report the ZCMP workaround violation' diff --git a/tools/test_build_system/test_zcmp_workaround.py b/tools/test_build_system/test_zcmp_workaround.py new file mode 100644 index 00000000000..ab8a59d3e0b --- /dev/null +++ b/tools/test_build_system/test_zcmp_workaround.py @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +import logging + +import pytest +from test_build_system_helpers import IdfPyFunc + +_IDF_TARGET = 'esp32c5' + + +@pytest.mark.test_app_copy('components/riscv/test_apps/test_zcmp_workaround_checking') +@pytest.mark.usefixtures('test_app_copy') +@pytest.mark.buildv2_skip('Uses build system v1 project.cmake test app') +def test_zcmp_workaround_checking(idf_py: IdfPyFunc) -> None: + """ZCMP post-ELF check must fail when mstatus.mie is cleared without mintthresh workaround.""" + logging.info('Building zcmp workaround checking test app') + + idf_py('set-target', _IDF_TARGET) + ret = idf_py('-DTEST_INVALID_WORKAROUND=1', 'build', check=False) + output = (ret.stdout or '') + (ret.stderr or '') + + assert ret.returncode != 0, 'Build must fail when ZCMP workaround is violated' + assert 'ZCMP workaround violation' in output, 'Build output must report the ZCMP workaround violation'