mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
Merge branch 'feature/use_zcmp_by_default_v6.1' into 'release/v6.1'
feat(build): add ZCMP workaround post-build check (v6.1) See merge request espressif/esp-idf!51325
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
# Enable SPIRAM
|
||||
CONFIG_SPIRAM=y
|
||||
|
||||
# TODO: IDF-14145
|
||||
CONFIG_COMPILER_ENABLE_RISCV_ZCMP=n
|
||||
|
||||
@@ -10,3 +10,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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -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);
|
||||
|
||||
@@ -30,6 +30,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;
|
||||
@@ -168,6 +171,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);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,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;
|
||||
@@ -160,6 +163,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);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
#endif
|
||||
#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;
|
||||
@@ -178,6 +181,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);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,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;
|
||||
@@ -252,6 +255,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);
|
||||
}
|
||||
|
||||
@@ -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=$<TARGET_FILE:${target}>
|
||||
-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=$<TARGET_FILE:$<GENEX_EVAL:${elf_target}>>
|
||||
-DCMAKE_OBJDUMP=${CMAKE_OBJDUMP}
|
||||
-P "${CMAKE_CURRENT_LIST_DIR}/check_zcmp_workaround.cmake"
|
||||
COMMAND ${CMAKE_COMMAND} -E touch "${_riscv_zcmp_check_marker}"
|
||||
DEPENDS "$<TARGET_FILE:$<GENEX_EVAL:${elf_target}>>"
|
||||
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()
|
||||
|
||||
179
components/riscv/check_zcmp_workaround.cmake
Normal file
179
components/riscv/check_zcmp_workaround.cmake
Normal file
@@ -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 <elf>.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()
|
||||
3
components/riscv/hints.yml
Normal file
3
components/riscv/hints.yml
Normal file
@@ -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'."
|
||||
13
components/riscv/test_apps/.build-test-rules.yml
Normal file
13
components/riscv/test_apps/.build-test-rules.yml
Normal file
@@ -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
|
||||
@@ -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)
|
||||
@@ -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``.
|
||||
@@ -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()
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdint.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_COMPILER_ENABLE_RISCV_ZCMP=y
|
||||
34
tools/test_build_system/buildv2/test_zcmp_workaround.py
Normal file
34
tools/test_build_system/buildv2/test_zcmp_workaround.py
Normal file
@@ -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'
|
||||
23
tools/test_build_system/test_zcmp_workaround.py
Normal file
23
tools/test_build_system/test_zcmp_workaround.py
Normal file
@@ -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'
|
||||
Reference in New Issue
Block a user