feat(esp_system): add CPU lockup debug support for esp32h4 and esp32s31

This commit is contained in:
Marius Vikhammer
2026-04-15 15:56:38 +08:00
parent 38e2cacc27
commit 8e2b416c38
38 changed files with 533 additions and 246 deletions
+2 -2
View File
@@ -83,8 +83,8 @@ else()
list(APPEND srcs "systick_etm.c")
endif()
if(CONFIG_ESP_SYSTEM_HW_STACK_GUARD)
list(APPEND srcs "hw_stack_guard.c")
if(CONFIG_ESP_SYSTEM_HW_STACK_GUARD OR CONFIG_ESP_SYSTEM_HW_PC_RECORD OR CONFIG_SOC_CPU_LOCKUP_DEBUG_SUPPORTED)
list(APPEND srcs "debug_assist.c")
endif()
idf_component_register(SRCS "${srcs}"
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include "hal/assist_debug_hal.h"
#include "hal/assist_debug_ll.h"
#include "esp_private/hw_stack_guard.h"
#include "esp_private/periph_ctrl.h"
#include "esp_private/startup_internal.h"
@@ -12,12 +13,8 @@
#include "esp_rom_sys.h"
#include "esp_cpu.h"
ESP_SYSTEM_INIT_FN(esp_hw_stack_guard_init, SECONDARY, ESP_SYSTEM_INIT_ALL_CORES, 101)
static void esp_hw_debug_assist_enable_module(uint32_t core_id)
{
uint32_t core_id = esp_cpu_get_core_id();
ESP_INTR_DISABLE(ETS_ASSIST_DEBUG_INUM);
#if SOC_CPU_CORES_NUM > 1
PERIPH_RCC_ATOMIC()
#endif
@@ -25,16 +22,20 @@ ESP_SYSTEM_INIT_FN(esp_hw_stack_guard_init, SECONDARY, ESP_SYSTEM_INIT_ALL_CORES
assist_debug_ll_enable_bus_clock(core_id, true);
assist_debug_ll_reset_register(core_id);
}
}
assist_debug_ll_enable_pc_recording(core_id, true);
#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD
static void esp_hw_stack_guard_init(uint32_t core_id)
{
ESP_INTR_DISABLE(ETS_ASSIST_DEBUG_INUM);
/* set interrupt to matrix */
/* Set interrupt to matrix. */
esp_rom_route_intr_matrix(core_id, ETS_ASSIST_DEBUG_INTR_SOURCE, ETS_ASSIST_DEBUG_INUM);
esprv_int_set_type(ETS_ASSIST_DEBUG_INUM, INTR_TYPE_LEVEL);
esprv_int_set_priority(ETS_ASSIST_DEBUG_INUM, SOC_INTERRUPT_LEVEL_MEDIUM);
/*
* enable interrupt
* Enable interrupt.
* Note: to control hw_stack_guard use monitor enable/disable because in case:
* - monitor == active
* - interrupt != active
@@ -46,6 +47,27 @@ ESP_SYSTEM_INIT_FN(esp_hw_stack_guard_init, SECONDARY, ESP_SYSTEM_INIT_ALL_CORES
assist_debug_hal_sp_int_enable(core_id);
ESP_INTR_ENABLE(ETS_ASSIST_DEBUG_INUM);
}
#endif
ESP_SYSTEM_INIT_FN(esp_hw_debug_assist_init, SECONDARY, ESP_SYSTEM_INIT_ALL_CORES, 101)
{
uint32_t core_id = esp_cpu_get_core_id();
esp_hw_debug_assist_enable_module(core_id);
#if CONFIG_ESP_SYSTEM_HW_PC_RECORD
assist_debug_ll_enable_pc_recording(core_id, true);
#endif
#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD
esp_hw_stack_guard_init(core_id);
#endif
#if SOC_CPU_LOCKUP_DEBUG_SUPPORTED
assist_debug_ll_lockup_monitor_enable(core_id, true);
#endif
return ESP_OK;
}
@@ -56,14 +78,15 @@ void esp_hw_stack_guard_monitor_start(void)
{
uint32_t core_id = esp_cpu_get_core_id();
/* enable monitor. Interrupt is always enabled (see comment in esp_hw_stack_guard_init()) */
/* Enable monitor. Interrupt is configured during assist-debug init. */
assist_debug_hal_sp_mon_enable(core_id);
}
void esp_hw_stack_guard_monitor_stop(void)
{
uint32_t core_id = esp_cpu_get_core_id();
/* disable monitor. Interrupt is always enabled (see comment in esp_hw_stack_guard_init()) */
/* Disable monitor. Interrupt is configured during assist-debug init. */
assist_debug_hal_sp_mon_disable(core_id);
}
+3 -3
View File
@@ -8,9 +8,9 @@ entries:
cache_err_int (noflash)
reset_reason:esp_reset_reason_get_hint (noflash)
if ESP_SYSTEM_HW_STACK_GUARD = y:
hw_stack_guard:esp_hw_stack_guard_get_bounds (noflash)
hw_stack_guard:esp_hw_stack_guard_get_fired_cpu (noflash)
hw_stack_guard:esp_hw_stack_guard_get_pc (noflash)
debug_assist:esp_hw_stack_guard_get_bounds (noflash)
debug_assist:esp_hw_stack_guard_get_fired_cpu (noflash)
debug_assist:esp_hw_stack_guard_get_pc (noflash)
# These functions are called when the cache is disabled
system_internal:esp_restart_noos (noflash)
+1 -1
View File
@@ -257,7 +257,7 @@ __attribute__((weak)) void esp_perip_clk_init(void)
#if !CONFIG_ESP_SYSTEM_HW_PC_RECORD
/* Disable ASSIST Debug module clock if PC recoreding function is not used,
* if stack guard function needs it, it will be re-enabled at esp_hw_stack_guard_init */
* if stack guard function needs it, it will be re-enabled at esp_hw_debug_assist_init */
CLEAR_PERI_REG_MASK(SYSTEM_CPU_PERI_CLK_EN_REG, SYSTEM_CLK_EN_ASSIST_DEBUG);
SET_PERI_REG_MASK(SYSTEM_CPU_PERI_RST_EN_REG, SYSTEM_RST_EN_ASSIST_DEBUG);
#endif
+1 -1
View File
@@ -299,7 +299,7 @@ __attribute__((weak)) void esp_perip_clk_init(void)
#if !CONFIG_ESP_SYSTEM_HW_PC_RECORD
/* Disable ASSIST Debug module clock if PC recoreding function is not used,
* if stack guard function needs it, it will be re-enabled at esp_hw_stack_guard_init */
* if stack guard function needs it, it will be re-enabled at esp_hw_debug_assist_init */
CLEAR_PERI_REG_MASK(SYSTEM_CPU_PERI_CLK_EN_REG, SYSTEM_CLK_EN_ASSIST_DEBUG);
SET_PERI_REG_MASK(SYSTEM_CPU_PERI_RST_EN_REG, SYSTEM_RST_EN_ASSIST_DEBUG);
#endif
+2 -2
View File
@@ -72,8 +72,8 @@ CORE: 170: init_xt_wdt in components/esp_system/startup_funcs.c on BIT(0)
# esp_timer has to be initialized early, since it is used by several other components
SECONDARY: 100: esp_timer_init_os in components/esp_timer/src/esp_timer.c on ESP_TIMER_INIT_MASK
# HW stack guard via assist-debug module.
SECONDARY: 101: esp_hw_stack_guard_init in components/esp_system/hw_stack_guard.c on ESP_SYSTEM_INIT_ALL_CORES
# Assist-debug early init for stack guard and PC recording.
SECONDARY: 101: esp_hw_debug_assist_init in components/esp_system/debug_assist.c on ESP_SYSTEM_INIT_ALL_CORES
# Initialize RNG (enable clock which is disabled in `esp_perip_clk_init`, configure entropy sources)
SECONDARY: 102: init_rng in components/esp_hw_support/hw_random.c on BIT(0)
@@ -8,13 +8,14 @@ set(requires "unity"
"esp_psram")
set(SRC "test_app_main.c"
"test_backtrace.c"
"test_ipc.c"
"test_reset_reason.c"
"test_shared_stack_printf.c"
"test_stack_check.c"
"test_system_time.c"
"test_task_wdt.c")
"test_backtrace.c"
"test_ipc.c"
"test_panic.c"
"test_reset_reason.c"
"test_shared_stack_printf.c"
"test_stack_check.c"
"test_system_time.c"
"test_task_wdt.c")
if(CONFIG_SOC_LIGHT_SLEEP_SUPPORTED OR CONFIG_SOC_DEEP_SLEEP_SUPPORTED)
list(APPEND SRC "test_sleep.c"
@@ -42,3 +43,9 @@ idf_component_register(SRCS ${SRC}
PRIV_INCLUDE_DIRS .
PRIV_REQUIRES "${requires}"
WHOLE_ARCHIVE)
if(CONFIG_SOC_CPU_LOCKUP_DEBUG_SUPPORTED)
# Wrap esp_panic_handler so the lockup test can inject a second exception
# inside the panic handler. All other panics in the test binary are unaffected.
target_link_options(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=esp_panic_handler")
endif()
@@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdbool.h>
#include "sdkconfig.h"
#include "unity.h"
#include "soc/soc_caps.h"
#if SOC_CPU_LOCKUP_DEBUG_SUPPORTED
static bool s_trigger_lockup_in_panic;
/* __wrap_esp_panic_handler intercepts all panics in this test binary.
* Normally it calls through to the real handler. When s_trigger_lockup_in_panic
* is set it causes a second exception inside the handler, which triggers a
* hardware CPU lockup reset.
*/
void __real_esp_panic_handler(void *info);
void __wrap_esp_panic_handler(void *info)
{
if (s_trigger_lockup_in_panic) {
__asm__ volatile("unimp"); /* exception inside exception handler -> lockup */
}
__real_esp_panic_handler(info);
}
TEST_CASE("CPU lockup output", "[cpu_lockup][ignore]")
{
s_trigger_lockup_in_panic = true;
__asm__ volatile("unimp"); /* first exception -> panic handler -> second exception -> lockup */
}
#endif // SOC_CPU_LOCKUP_DEBUG_SUPPORTED
@@ -1,11 +1,12 @@
/*
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "sdkconfig.h"
#include <inttypes.h>
#include "unity.h"
#include "soc/soc_caps.h"
#include "esp_system.h"
#include "esp_task_wdt.h"
#include "esp_attr.h"
@@ -123,6 +123,40 @@ def test_sleep_uart_handling(dut: Dut) -> None:
)
@pytest.mark.generic
@idf_parametrize('config', ['default'], indirect=['config'])
@idf_parametrize(
'target',
[target for target in soc_filtered_targets('SOC_CPU_LOCKUP_DEBUG_SUPPORTED == 1')],
indirect=['target'],
)
def test_cpu_lockup_trap_chain(dut: Dut) -> None:
"""Trigger a PRO CPU lockup and verify the lockup output."""
esp_reset_and_wait_ready(dut)
dut.write('"CPU lockup output"')
if dut.target == 'esp32s31':
# ROM should print a non-zero Core0 trap PC for both exceptions.
dut.expect(r'\[Core0\]', timeout=10)
dut.expect(r'1st Exception:', timeout=5)
dut.expect(r'PCAddr:\s+0x(?!00000000)[0-9a-f]{8}', timeout=5)
dut.expect(r'2nd Exception:', timeout=5)
dut.expect(r'PCAddr:\s+0x(?!00000000)[0-9a-f]{8}', timeout=5)
else:
# 2nd stage bootloader should log the trap chain after the lockup reset.
dut.expect('PRO CPU reset due to CPU lockup', timeout=10)
dut.expect('PRO CPU trap chain:', timeout=5)
# Both traps should be illegal instruction (RISC-V mcause=2)
dut.expect(
r'\[latest trap\] cause=0x02 PCAddr=0x(?!00000000)[0-9a-f]{8} tval=0x[0-9a-f]{8} priv=[0-9]+', timeout=5
)
dut.expect(
r'\[previous trap\] cause=0x02 PCAddr=0x(?!00000000)[0-9a-f]{8} tval=0x[0-9a-f]{8} priv=[0-9]+', timeout=5
)
dut.expect_exact('Press ENTER to see the list of tests', timeout=30)
@pytest.mark.generic
@idf_parametrize('config', ['default'], indirect=['config'])
@idf_parametrize('target', ['supported_targets'], indirect=['target'])