From cc9a929ea6de530c8036067847f6b43bd24ba3c0 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 | 9 +- .../lp_core_basic_tests/main/CMakeLists.txt | 4 +- .../main/lp_core/test_main_wake_stub.c | 18 ++++ .../lp_core_basic_tests/main/test_lp_core.c | 94 +++++++++++++++++++ 4 files changed, 123 insertions(+), 2 deletions(-) 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 c1bd74cd47..c7946b7274 100644 --- a/components/esp_hw_support/sleep_wake_stub.c +++ b/components/esp_hw_support/sleep_wake_stub.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 */ @@ -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(); 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 577e258453..8670177496 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_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) 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 a055ef9e73..bd7aa88baf 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 @@ -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]")