From fe62bfcc2dc1bc2cb7e57dab8d519a51c3665fac Mon Sep 17 00:00:00 2001 From: Marius Vikhammer Date: Fri, 6 Mar 2026 11:20:18 +0800 Subject: [PATCH] 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 --- components/esp_hw_support/sleep_wake_stub.c | 7 ++ .../lp_core_basic_tests/main/CMakeLists.txt | 2 + .../main/lp_core/test_main_wake_stub.c | 18 +++ .../lp_core_basic_tests/main/test_lp_core.c | 113 +++++++++++++++++- 4 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_wake_stub.c diff --git a/components/esp_hw_support/sleep_wake_stub.c b/components/esp_hw_support/sleep_wake_stub.c index 4c8adefa32..109d216621 100644 --- a/components/esp_hw_support/sleep_wake_stub.c +++ b/components/esp_hw_support/sleep_wake_stub.c @@ -71,6 +71,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(); diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/CMakeLists.txt b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/CMakeLists.txt index b15fe5c4a5..b635774982 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/CMakeLists.txt +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/CMakeLists.txt @@ -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_LP_TIMER_SUPPORTED) set(lp_core_sources_set_timer_wakeup "lp_core/test_main_set_timer_wakeup.c") @@ -68,6 +69,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_LP_TIMER_SUPPORTED) diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_wake_stub.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_wake_stub.c new file mode 100644 index 0000000000..b0e0197178 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_wake_stub.c @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#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; +} diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core.c index 249953167a..07d1cf217d 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core.c +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core.c @@ -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 */ @@ -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_LP_TIMER_SUPPORTED @@ -23,7 +26,18 @@ #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 "soc/rtc.h" +#if SOC_LP_TIMER_SUPPORTED +#include "hal/lp_timer_ll.h" +#include "soc/lp_timer_struct.h" +#else +#include "hal/rtc_cntl_ll.h" +#endif #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -37,6 +51,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"); @@ -228,6 +247,98 @@ 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 uint64_t RTC_IRAM_ATTR get_rtc_timer_cycle_count(void) +{ +#if SOC_LP_TIMER_SUPPORTED + lp_timer_ll_counter_snapshot(&LP_TIMER); + uint32_t lo = lp_timer_ll_get_counter_value_low(&LP_TIMER, 0); + uint32_t hi = lp_timer_ll_get_counter_value_high(&LP_TIMER, 0); + return ((uint64_t)hi << 32) | lo; +#else + return rtc_cntl_ll_get_rtc_time(); +#endif +} + +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 = get_rtc_timer_cycle_count(); + } + 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 = get_rtc_timer_cycle_count(); +} + +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]")