mirror of
https://github.com/espressif/esp-idf.git
synced 2026-04-27 19:13:21 +00:00
feat(esp_system): add CPU lockup debug support for esp32h4 and esp32s31
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
+34
@@ -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'])
|
||||
|
||||
Reference in New Issue
Block a user