fix(esp_hw_support): clear LP core SW interrupt in wake stub before sleep

When ulp_lp_core_wakeup_main_processor() is called, it sets the
PMU_SW_INT_RAW bit on the HP side. The normal sleep path clears this
bit before re-entering sleep, but esp_wake_stub_sleep() did not, leaving
the wakeup cause sticky. This caused the PMU to immediately re-trigger a
wakeup as soon as sleep was requested from the wake stub, producing a
rapid re-wakeup loop that eventually triggered LP_WDT_SYS resets.

Add a test case that verifies the wake stub can return to sleep correctly
across multiple LP core wakeup cycles without the re-wakeup bug.

Closing https://github.com/espressif/esp-idf/issues/18308

Made-with: Cursor
This commit is contained in:
Marius Vikhammer
2026-03-06 11:20:18 +08:00
parent 1a27f44f04
commit cc9a929ea6
4 changed files with 123 additions and 2 deletions
+8 -1
View File
@@ -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
*/
@@ -62,6 +62,13 @@ void RTC_IRAM_ATTR esp_wake_stub_sleep(esp_deep_sleep_wake_stub_fn_t new_stub)
pmu_ll_hp_clear_wakeup_intr_status(&PMU);
pmu_ll_hp_clear_reject_intr_status(&PMU);
pmu_ll_hp_clear_reject_cause(&PMU);
#if CONFIG_ULP_COPROC_TYPE_LP_CORE
/* Clear the pending LP core SW interrupt before sleeping. Without this,
* the LP core wakeup cause bit (PMU_SW_INT_RAW) remains asserted after
* ulp_lp_core_wakeup_main_processor(), causing the PMU to immediately
* re-trigger a wakeup as soon as sleep is requested. */
pmu_ll_hp_clear_sw_intr_status(&PMU);
#endif
pmu_ll_hp_set_sleep_enable(&PMU);
#else
rtc_cntl_ll_sleep_enable();
@@ -29,6 +29,7 @@ list(APPEND app_sources "test_lp_core_prefix.c")
set(lp_core_sources "lp_core/test_main.c")
set(lp_core_sources_counter "lp_core/test_main_counter.c")
set(lp_core_sources_wake_stub "lp_core/test_main_wake_stub.c")
if(CONFIG_SOC_RTC_TIMER_SUPPORTED)
set(lp_core_sources_set_timer_wakeup "lp_core/test_main_set_timer_wakeup.c")
@@ -61,7 +62,7 @@ endif()
idf_component_register(SRCS ${app_sources}
INCLUDE_DIRS "lp_core"
REQUIRES ulp unity esp_timer test_utils
PRIV_REQUIRES driver esp_driver_gptimer esp_driver_tsens
PRIV_REQUIRES driver esp_driver_gptimer esp_driver_tsens esp_hal_rtc_timer
WHOLE_ARCHIVE
EMBED_FILES "test_vad_8k.pcm")
@@ -69,6 +70,7 @@ set(lp_core_exp_dep_srcs ${app_sources})
ulp_embed_binary(lp_core_test_app "${lp_core_sources}" "${lp_core_exp_dep_srcs}")
ulp_embed_binary(lp_core_test_app_counter "${lp_core_sources_counter}" "${lp_core_exp_dep_srcs}")
ulp_embed_binary(lp_core_test_app_wake_stub "${lp_core_sources_wake_stub}" "${lp_core_exp_dep_srcs}")
ulp_embed_binary(lp_core_test_app_isr "lp_core/test_main_isr.c" "${lp_core_exp_dep_srcs}")
if(CONFIG_SOC_RTC_TIMER_SUPPORTED)
@@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "ulp_lp_core_utils.h"
/* Incremented on every LP timer wakeup, readable by HP core after test */
volatile uint32_t lp_wake_count = 0;
int main(void)
{
lp_wake_count++;
ulp_lp_core_wakeup_main_processor();
return 0;
}
@@ -11,6 +11,9 @@
#include "esp_rom_caps.h"
#include "lp_core_test_app.h"
#include "lp_core_test_app_counter.h"
#if CONFIG_ESP_ROM_SUPPORT_DEEP_SLEEP_WAKEUP_STUB
#include "lp_core_test_app_wake_stub.h"
#endif
#include "lp_core_test_app_isr.h"
#if SOC_RTC_TIMER_SUPPORTED
@@ -23,7 +26,13 @@
#include "test_shared.h"
#include "unity.h"
#include "esp_sleep.h"
#if CONFIG_ESP_ROM_SUPPORT_DEEP_SLEEP_WAKEUP_STUB
#include "esp_wake_stub.h"
#endif
#include "esp_timer.h"
#include "esp_private/esp_clk.h"
#include "hal/rtc_timer_ll.h"
#include "soc/rtc.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
@@ -37,6 +46,11 @@ extern const uint8_t lp_core_main_bin_end[] asm("_binary_lp_core_test_app_bin_
extern const uint8_t lp_core_main_counter_bin_start[] asm("_binary_lp_core_test_app_counter_bin_start");
extern const uint8_t lp_core_main_counter_bin_end[] asm("_binary_lp_core_test_app_counter_bin_end");
#if CONFIG_ESP_ROM_SUPPORT_DEEP_SLEEP_WAKEUP_STUB
extern const uint8_t lp_core_main_wake_stub_bin_start[] asm("_binary_lp_core_test_app_wake_stub_bin_start");
extern const uint8_t lp_core_main_wake_stub_bin_end[] asm("_binary_lp_core_test_app_wake_stub_bin_end");
#endif
extern const uint8_t lp_core_main_set_timer_wakeup_bin_start[] asm("_binary_lp_core_test_app_set_timer_wakeup_bin_start");
extern const uint8_t lp_core_main_set_timer_wakeup_bin_end[] asm("_binary_lp_core_test_app_set_timer_wakeup_bin_end");
@@ -231,6 +245,86 @@ TEST_CASE_MULTIPLE_STAGES("LP Timer can wakeup lp core periodically during deep
do_ulp_wakeup_with_lp_timer_deepsleep,
check_reset_reason_and_sleep_duration);
#if CONFIG_ESP_ROM_SUPPORT_DEEP_SLEEP_WAKEUP_STUB
/* Number of LP core wakeup cycles the stub handles before falling through to full boot */
#define WAKE_STUB_LP_WAKEUP_COUNT 10
/* LP timer period for wake stub test */
#define WAKE_STUB_LP_TIMER_PERIOD_US 2000000 /* 200 ms */
/* Safety backup timer in case LP core never wakes up HP */
#define WAKE_STUB_BACKUP_TIMER_US 5000000 /* 5 s */
static RTC_DATA_ATTR uint32_t s_wake_stub_run_count;
static RTC_DATA_ATTR uint64_t s_stub_first_rtc_tick;
static RTC_DATA_ATTR uint64_t s_stub_last_rtc_tick;
static void RTC_IRAM_ATTR wake_stub_lp_core_test(void)
{
s_wake_stub_run_count++;
if (s_wake_stub_run_count == 1) {
s_stub_first_rtc_tick = rtc_timer_ll_get_cycle_count(0);
}
if (s_wake_stub_run_count < WAKE_STUB_LP_WAKEUP_COUNT) {
esp_wake_stub_uart_tx_wait_idle(0);
esp_wake_stub_sleep(&wake_stub_lp_core_test);
}
/* On the Nth run record the final tick and fall through to full boot */
s_stub_last_rtc_tick = rtc_timer_ll_get_cycle_count(0);
}
static void do_lp_core_wake_stub_deepsleep(void)
{
s_wake_stub_run_count = 0;
s_stub_first_rtc_tick = 0;
s_stub_last_rtc_tick = 0;
ulp_lp_core_cfg_t cfg = {
.wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER,
.lp_timer_sleep_duration_us = WAKE_STUB_LP_TIMER_PERIOD_US,
};
load_and_start_lp_core_firmware(&cfg, lp_core_main_wake_stub_bin_start, lp_core_main_wake_stub_bin_end);
TEST_ASSERT(esp_sleep_enable_ulp_wakeup() == ESP_OK);
/* Backup timer so test doesn't hang if LP core never wakes HP */
//TEST_ASSERT(esp_sleep_enable_timer_wakeup(WAKE_STUB_BACKUP_TIMER_US) == ESP_OK);
esp_set_deep_sleep_wake_stub(&wake_stub_lp_core_test);
esp_deep_sleep_start();
UNITY_TEST_FAIL(__LINE__, "Should not get here!");
}
static void check_lp_core_wake_stub_ran_correctly(void)
{
/* Must have woken via ULP (stub fell through on Nth run), not the backup timer */
TEST_ASSERT_EQUAL(BIT(ESP_SLEEP_WAKEUP_ULP), esp_sleep_get_wakeup_causes() & BIT(ESP_SLEEP_WAKEUP_ULP));
/* Stub should have run exactly WAKE_STUB_LP_WAKEUP_COUNT times */
TEST_ASSERT_EQUAL(WAKE_STUB_LP_WAKEUP_COUNT, s_wake_stub_run_count);
/* Compute the time between first and last stub run using RTC timer ticks
* recorded inside the stub itself, excluding any boot or menu latency.
* Between run 1 and run N there are (N-1) LP timer periods.
* With the PMU re-wakeup bug the PMU re-triggers immediately after
* esp_wake_stub_sleep(), so all 10 runs complete in ~162ms.
* With the fix each cycle respects the 200ms LP timer, so the inter-run
* span is at least (N-1)*period/2 = 900ms. */
uint64_t rtc_ticks = s_stub_last_rtc_tick - s_stub_first_rtc_tick;
uint32_t cal = esp_clk_slowclk_cal_get();
uint64_t elapsed_ms = (((uint64_t)rtc_ticks * cal) >> RTC_CLK_CAL_FRACT) / 1000;
uint64_t min_expected_ms = (uint64_t)(WAKE_STUB_LP_WAKEUP_COUNT - 1) * WAKE_STUB_LP_TIMER_PERIOD_US / 2000;
printf("Wake stub ran %" PRIu32 " times, inter-run span: %" PRIu64 "ms (min expected: %" PRIu64 "ms)\n",
s_wake_stub_run_count, elapsed_ms, min_expected_ms);
TEST_ASSERT_MESSAGE(elapsed_ms >= min_expected_ms,
"Wake stub ran too quickly - possible PMU re-wakeup bug");
}
TEST_CASE_MULTIPLE_STAGES("LP core wake stub can sleep again without immediate re-wakeup", "[ulp]",
do_lp_core_wake_stub_deepsleep,
check_lp_core_wake_stub_ran_correctly);
#endif //#if CONFIG_ESP_ROM_SUPPORT_DEEP_SLEEP_WAKEUP_STUB
#endif //#if SOC_DEEP_SLEEP_SUPPORTED
TEST_CASE("LP Timer can wakeup lp core periodically", "[lp_core]")