diff --git a/components/esp_system/sleep_modes.c b/components/esp_system/sleep_modes.c index b8d7b67cd8..ea26419728 100644 --- a/components/esp_system/sleep_modes.c +++ b/components/esp_system/sleep_modes.c @@ -135,8 +135,8 @@ static sleep_config_t s_config = { static bool s_light_sleep_wakeup = false; /* Updating RTC_MEMORY_CRC_REG register via set_rtc_memory_crc() - is not thread-safe. */ -static _lock_t lock_rtc_memory_crc; + is not thread-safe, so we need to disable interrupts before going to deep sleep. */ +static portMUX_TYPE spinlock_rtc_deep_sleep = portMUX_INITIALIZER_UNLOCKED; static const char* TAG = "sleep"; @@ -155,16 +155,6 @@ static void touch_wakeup_prepare(void); */ esp_deep_sleep_wake_stub_fn_t esp_get_deep_sleep_wake_stub(void) { - _lock_acquire(&lock_rtc_memory_crc); - uint32_t stored_crc = REG_READ(RTC_MEMORY_CRC_REG); - set_rtc_memory_crc(); - uint32_t calc_crc = REG_READ(RTC_MEMORY_CRC_REG); - REG_WRITE(RTC_MEMORY_CRC_REG, stored_crc); - _lock_release(&lock_rtc_memory_crc); - - if(stored_crc != calc_crc) { - return NULL; - } esp_deep_sleep_wake_stub_fn_t stub_ptr = (esp_deep_sleep_wake_stub_fn_t) REG_READ(RTC_ENTRY_ADDR_REG); if (!esp_ptr_executable(stub_ptr)) { return NULL; @@ -174,10 +164,7 @@ esp_deep_sleep_wake_stub_fn_t esp_get_deep_sleep_wake_stub(void) void esp_set_deep_sleep_wake_stub(esp_deep_sleep_wake_stub_fn_t new_stub) { - _lock_acquire(&lock_rtc_memory_crc); REG_WRITE(RTC_ENTRY_ADDR_REG, (uint32_t)new_stub); - set_rtc_memory_crc(); - _lock_release(&lock_rtc_memory_crc); } void RTC_IRAM_ATTR esp_default_wake_deep_sleep(void) { @@ -260,12 +247,25 @@ static void IRAM_ATTR resume_uarts(void) } } +inline static uint32_t IRAM_ATTR call_rtc_sleep_start(uint32_t reject_triggers); + static uint32_t IRAM_ATTR esp_sleep_start(uint32_t pd_flags) { // Stop UART output so that output is not lost due to APB frequency change. // For light sleep, suspend UART output — it will resume after wakeup. // For deep sleep, wait for the contents of UART FIFO to be sent. - if (pd_flags & RTC_SLEEP_PD_DIG) { + bool deep_sleep = pd_flags & RTC_SLEEP_PD_DIG; + +#if !CONFIG_FREERTOS_UNICORE && ESP32S3_ALLOW_RTC_FAST_MEM_AS_HEAP + /* Currently only safe to use deep sleep wake stub & RTC memory as heap in single core mode. + + For ESP32-S3, either disable ESP32S3_ALLOW_RTC_FAST_MEM_AS_HEAP in config or find a way to set the + deep sleep wake stub to NULL. + */ + assert(!deep_sleep || esp_get_deep_sleep_wake_stub() == NULL); +#endif + + if (deep_sleep) { flush_uarts(); } else { suspend_uarts(); @@ -320,12 +320,30 @@ static uint32_t IRAM_ATTR esp_sleep_start(uint32_t pd_flags) timer_wakeup_prepare(); } -#ifdef CONFIG_IDF_TARGET_ESP32 - uint32_t result = rtc_sleep_start(s_config.wakeup_triggers, reject_triggers); -#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 - uint32_t result = rtc_sleep_start(s_config.wakeup_triggers, reject_triggers, 1); + uint32_t result; + if (deep_sleep) { + /* Disable interrupts in case another task writes to RTC memory while we + * calculate RTC memory CRC + * + * Note: for ESP32-S3 running in dual core mode this is currently not enough, + * see the assert at top of this function. + */ + portENTER_CRITICAL(&spinlock_rtc_deep_sleep); + +#if !CONFIG_ESP32_ALLOW_RTC_FAST_MEM_AS_HEAP && !CONFIG_ESP32S2_ALLOW_RTC_FAST_MEM_AS_HEAP && !CONFIG_ESP32S3_ALLOW_RTC_FAST_MEM_AS_HEAP + /* If not possible stack is in RTC FAST memory, use the ROM function to calculate the CRC and save ~140 bytes IRAM */ + set_rtc_memory_crc(); + result = call_rtc_sleep_start(reject_triggers); +#else + /* Otherwise, need to call the dedicated soc function for this */ + result = rtc_deep_sleep_start(s_config.wakeup_triggers, reject_triggers); #endif + portEXIT_CRITICAL(&spinlock_rtc_deep_sleep); + } else { + result = call_rtc_sleep_start(reject_triggers); + } + // Restore CPU frequency rtc_clk_cpu_freq_set_config(&cpu_freq_config); @@ -335,6 +353,15 @@ static uint32_t IRAM_ATTR esp_sleep_start(uint32_t pd_flags) return result; } +inline static uint32_t IRAM_ATTR call_rtc_sleep_start(uint32_t reject_triggers) +{ +#ifdef CONFIG_IDF_TARGET_ESP32 + return rtc_sleep_start(s_config.wakeup_triggers, reject_triggers); +#else + return rtc_sleep_start(s_config.wakeup_triggers, reject_triggers, 1); +#endif +} + void IRAM_ATTR esp_deep_sleep_start(void) { // record current RTC time diff --git a/components/soc/soc/esp32/include/soc/rtc.h b/components/soc/soc/esp32/include/soc/rtc.h index c6cccfb20b..44a43fe411 100644 --- a/components/soc/soc/esp32/include/soc/rtc.h +++ b/components/soc/soc/esp32/include/soc/rtc.h @@ -581,6 +581,29 @@ void rtc_sleep_set_wakeup_time(uint64_t t); */ uint32_t rtc_sleep_start(uint32_t wakeup_opt, uint32_t reject_opt); +/** + * @brief Enter deep sleep mode + * + * Similar to rtc_sleep_start(), but additionally uses hardware to calculate the CRC value + * of RTC FAST memory. On wake, this CRC is used to determine if a deep sleep wake + * stub is valid to execute (if a wake address is set). + * + * No RAM is accessed while calculating the CRC and going into deep sleep, which makes + * this function safe to use even if the caller's stack is in RTC FAST memory. + * + * @note If no deep sleep wake stub address is set then calling rtc_sleep_start() will + * have the same effect and takes less time as CRC calculation is skipped. + * + * @note This function should only be called after rtc_sleep_init() has been called to + * configure the system for deep sleep. + * + * @param wakeup_opt - same as for rtc_sleep_start + * @param reject_opt - same as for rtc_sleep_start + * + * @return non-zero if sleep was rejected by hardware + */ +uint32_t rtc_deep_sleep_start(uint32_t wakeup_opt, uint32_t reject_opt); + /** * RTC power and clock control initialization settings */ diff --git a/components/soc/soc/esp32s2/include/soc/rtc.h b/components/soc/soc/esp32s2/include/soc/rtc.h index d90804e312..928695fbfd 100644 --- a/components/soc/soc/esp32s2/include/soc/rtc.h +++ b/components/soc/soc/esp32s2/include/soc/rtc.h @@ -726,10 +726,36 @@ void rtc_sleep_init(rtc_sleep_config_t cfg); * - RTC_CNTL_SDIO_REJECT_EN * These flags are used to prevent entering sleep when e.g. * an external host is communicating via SDIO slave + * @param lslp_mem_inf_fpu If non-zero then the low power config is restored + * immediately on wake. Recommended for light sleep, + * has no effect if the system goes into deep sleep. * @return non-zero if sleep was rejected by hardware */ uint32_t rtc_sleep_start(uint32_t wakeup_opt, uint32_t reject_opt, uint32_t lslp_mem_inf_fpu); +/** + * @brief Enter deep sleep mode + * + * Similar to rtc_sleep_start(), but additionally uses hardware to calculate the CRC value + * of RTC FAST memory. On wake, this CRC is used to determine if a deep sleep wake + * stub is valid to execute (if a wake address is set). + * + * No RAM is accessed while calculating the CRC and going into deep sleep, which makes + * this function safe to use even if the caller's stack is in RTC FAST memory. + * + * @note If no deep sleep wake stub address is set then calling rtc_sleep_start() will + * have the same effect and takes less time as CRC calculation is skipped. + * + * @note This function should only be called after rtc_sleep_init() has been called to + * configure the system for deep sleep. + * + * @param wakeup_opt - same as for rtc_sleep_start + * @param reject_opt - same as for rtc_sleep_start + * + * @return non-zero if sleep was rejected by hardware + */ +uint32_t rtc_deep_sleep_start(uint32_t wakeup_opt, uint32_t reject_opt); + /** * RTC power and clock control initialization settings */ diff --git a/components/soc/soc/esp32s3/include/soc/rtc.h b/components/soc/soc/esp32s3/include/soc/rtc.h index 73e90b5597..da3fd3839b 100644 --- a/components/soc/soc/esp32s3/include/soc/rtc.h +++ b/components/soc/soc/esp32s3/include/soc/rtc.h @@ -706,10 +706,37 @@ void rtc_sleep_set_wakeup_time(uint64_t t); * - RTC_CNTL_SDIO_REJECT_EN * These flags are used to prevent entering sleep when e.g. * an external host is communicating via SDIO slave + * @param lslp_mem_inf_fpu If non-zero then the low power config is restored + * immediately on wake. Recommended for light sleep, + * has no effect if the system goes into deep sleep. + * * @return non-zero if sleep was rejected by hardware */ uint32_t rtc_sleep_start(uint32_t wakeup_opt, uint32_t reject_opt, uint32_t lslp_mem_inf_fpu); +/** + * @brief Enter deep sleep mode + * + * Similar to rtc_sleep_start(), but additionally uses hardware to calculate the CRC value + * of RTC FAST memory. On wake, this CRC is used to determine if a deep sleep wake + * stub is valid to execute (if a wake address is set). + * + * No RAM is accessed while calculating the CRC and going into deep sleep, which makes + * this function safe to use even if the caller's stack is in RTC FAST memory. + * + * @note If no deep sleep wake stub address is set then calling rtc_sleep_start() will + * have the same effect and takes less time as CRC calculation is skipped. + * + * @note This function should only be called after rtc_sleep_init() has been called to + * configure the system for deep sleep. + * + * @param wakeup_opt - same as for rtc_sleep_start + * @param reject_opt - same as for rtc_sleep_start + * + * @return non-zero if sleep was rejected by hardware + */ +uint32_t rtc_deep_sleep_start(uint32_t wakeup_opt, uint32_t reject_opt); + /** * RTC power and clock control initialization settings */ diff --git a/components/soc/src/esp32/rtc_sleep.c b/components/soc/src/esp32/rtc_sleep.c index d1db52fc42..e487500568 100644 --- a/components/soc/src/esp32/rtc_sleep.c +++ b/components/soc/src/esp32/rtc_sleep.c @@ -24,7 +24,9 @@ #include "soc/fe_reg.h" #include "soc/rtc.h" #include "esp32/rom/ets_sys.h" +#include "esp32/rom/rtc.h" #include "hal/rtc_cntl_ll.h" +#include "esp_rom_sys.h" #define MHZ (1000000) @@ -223,6 +225,9 @@ void rtc_sleep_set_wakeup_time(uint64_t t) rtc_cntl_ll_set_wakeup_timer(t); } +/* Read back 'reject' status when waking from light or deep sleep */ +static uint32_t rtc_sleep_finish(void); + uint32_t rtc_sleep_start(uint32_t wakeup_opt, uint32_t reject_opt) { REG_SET_FIELD(RTC_CNTL_WAKEUP_STATE_REG, RTC_CNTL_WAKEUP_ENA, wakeup_opt); @@ -232,9 +237,92 @@ uint32_t rtc_sleep_start(uint32_t wakeup_opt, uint32_t reject_opt) SET_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_SLEEP_EN); while (GET_PERI_REG_MASK(RTC_CNTL_INT_RAW_REG, - RTC_CNTL_SLP_REJECT_INT_RAW | RTC_CNTL_SLP_WAKEUP_INT_RAW) == 0) { + RTC_CNTL_SLP_REJECT_INT_RAW | RTC_CNTL_SLP_WAKEUP_INT_RAW) == 0) { ; } + + return rtc_sleep_finish(); +} + +#define STR2(X) #X +#define STR(X) STR2(X) + +uint32_t rtc_deep_sleep_start(uint32_t wakeup_opt, uint32_t reject_opt) +{ + REG_SET_FIELD(RTC_CNTL_WAKEUP_STATE_REG, RTC_CNTL_WAKEUP_ENA, wakeup_opt); + WRITE_PERI_REG(RTC_CNTL_SLP_REJECT_CONF_REG, reject_opt); + + /* Calculate RTC Fast Memory CRC (for wake stub) & go to deep sleep + + Because we may be running from RTC memory as stack, we can't easily call any + functions to do this (as registers may spill to stack, corrupting the CRC). + + Instead, load all the values we need into registers (triggering any stack spills) + then use register ops only to calculate the CRC value, write it to the RTC CRC value + register, and immediately go into deep sleep. + */ + + /* Values used to set the RTC_MEM_CONFG value */ + const unsigned CRC_START_ADDR = 0; + const unsigned CRC_LEN = 0x7ff; + const unsigned RTC_MEM_PID = 1; + + asm volatile( + "movi a2, 0\n" // trigger a stack spill on working register if needed + + /* Start CRC calculation */ + "s32i %1, %0, 0\n" // set RTC_MEM_CRC_ADDR & RTC_MEM_CRC_LEN + "or a2, %1, %2\n" + "s32i a2, %0, 0\n" // set RTC_MEM_CRC_START + + /* Wait for the CRC calculation to finish */ + ".Lwaitcrc:\n" + "memw\n" + "l32i a2, %0, 0\n" + "bbci a2, "STR(RTC_MEM_CRC_FINISH_S)", .Lwaitcrc\n" + "and a2, a2, %3\n" // clear RTC_MEM_CRC_START + "s32i a2, %0, 0\n" + "memw\n" + + /* Store the calculated value in RTC_MEM_CRC_REG */ + "l32i a2, %4, 0\n" + "s32i a2, %5, 0\n" + "memw\n" + + /* Set register bit to go into deep sleep */ + "l32i a2, %6, 0\n" + "or a2, a2, %7\n" + "s32i a2, %6, 0\n" + "memw\n" + + /* Set wait cycle for touch or COCPU after deep sleep. */ + ".Lwaitsleep:" + "memw\n" + "l32i a2, %8, 0\n" + "and a2, a2, %9\n" + "beqz a2, .Lwaitsleep\n" + + : + : "r" (RTC_MEM_CONF), // %0 + "r" ( (CRC_START_ADDR << RTC_MEM_CRC_ADDR_S) + | (CRC_LEN << RTC_MEM_CRC_LEN_S) + | (RTC_MEM_PID << RTC_MEM_PID_CONF_S) ), // %1 + "r" (RTC_MEM_CRC_START), // %2 + "r" (~RTC_MEM_CRC_START), // %3 + "r" (RTC_MEM_CRC_RES), // %4 + "r" (RTC_MEMORY_CRC_REG), // %5 + "r" (RTC_CNTL_STATE0_REG), // %6 + "r" (RTC_CNTL_SLEEP_EN), // %7 + "r" (RTC_CNTL_INT_RAW_REG), // %8 + "r" (RTC_CNTL_SLP_REJECT_INT_RAW | RTC_CNTL_SLP_WAKEUP_INT_RAW) // %9 + : "a2" // working register + ); + + return rtc_sleep_finish(); +} + +static uint32_t rtc_sleep_finish(void) +{ /* In deep sleep mode, we never get here */ uint32_t reject = REG_GET_FIELD(RTC_CNTL_INT_RAW_REG, RTC_CNTL_SLP_REJECT_INT_RAW); SET_PERI_REG_MASK(RTC_CNTL_INT_CLR_REG, diff --git a/components/soc/src/esp32s2/rtc_sleep.c b/components/soc/src/esp32s2/rtc_sleep.c index f212654bcc..62eb560a78 100644 --- a/components/soc/src/esp32s2/rtc_sleep.c +++ b/components/soc/src/esp32s2/rtc_sleep.c @@ -26,6 +26,7 @@ #include "soc/fe_reg.h" #include "soc/rtc.h" #include "esp32s2/rom/ets_sys.h" +#include "esp32s2/rom/rtc.h" #include "hal/rtc_cntl_ll.h" /** @@ -131,6 +132,11 @@ void rtc_sleep_set_wakeup_time(uint64_t t) rtc_cntl_ll_set_wakeup_timer(t); } +/* Read back 'reject' status when waking from light or deep sleep */ +static uint32_t rtc_sleep_finish(uint32_t lslp_mem_inf_fpu); + +static const unsigned DEEP_SLEEP_TOUCH_WAIT_CYCLE = 0xFF; + uint32_t rtc_sleep_start(uint32_t wakeup_opt, uint32_t reject_opt, uint32_t lslp_mem_inf_fpu) { REG_SET_FIELD(RTC_CNTL_WAKEUP_STATE_REG, RTC_CNTL_WAKEUP_ENA, wakeup_opt); @@ -139,16 +145,108 @@ uint32_t rtc_sleep_start(uint32_t wakeup_opt, uint32_t reject_opt, uint32_t lslp REG_SET_BIT(RTC_CNTL_SLP_REJECT_CONF_REG, RTC_CNTL_LIGHT_SLP_REJECT_EN); } + /* Set wait cycle for touch or COCPU after deep sleep. */ + REG_SET_FIELD(RTC_CNTL_TIMER2_REG, RTC_CNTL_ULPCP_TOUCH_START_WAIT, DEEP_SLEEP_TOUCH_WAIT_CYCLE); + /* Start entry into sleep mode */ SET_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_SLEEP_EN); - /* Set wait cycle for touch or COCPU after deep sleep. */ - REG_SET_FIELD(RTC_CNTL_TIMER2_REG, RTC_CNTL_ULPCP_TOUCH_START_WAIT, 0xFF); - while (GET_PERI_REG_MASK(RTC_CNTL_INT_RAW_REG, RTC_CNTL_SLP_REJECT_INT_RAW | RTC_CNTL_SLP_WAKEUP_INT_RAW) == 0) { ; } + + return rtc_sleep_finish(lslp_mem_inf_fpu); +} + +#define STR2(X) #X +#define STR(X) STR2(X) + +uint32_t rtc_deep_sleep_start(uint32_t wakeup_opt, uint32_t reject_opt) +{ + REG_SET_FIELD(RTC_CNTL_WAKEUP_STATE_REG, RTC_CNTL_WAKEUP_ENA, wakeup_opt); + WRITE_PERI_REG(RTC_CNTL_SLP_REJECT_CONF_REG, reject_opt); + + /* Calculate RTC Fast Memory CRC (for wake stub) & go to deep sleep + + Because we may be running from RTC memory as stack, we can't easily call any + functions to do this (as registers may spill to stack, corrupting the CRC). + + Instead, load all the values we need into registers (triggering any stack spills) + then use register ops only to calculate the CRC value, write it to the RTC CRC value + register, and immediately go into deep sleep. + */ + + /* Values used to set the DPORT_RTC_FASTMEM_CONFIG_REG value */ + const unsigned CRC_START_ADDR = 0; + const unsigned CRC_LEN = 0x7ff; + + asm volatile( + "movi a2, 0\n" // trigger a stack spill on working register if needed + + /* Start CRC calculation */ + "s32i %1, %0, 0\n" // set RTC_MEM_CRC_ADDR & RTC_MEM_CRC_LEN + "or a2, %1, %2\n" + "s32i a2, %0, 0\n" // set RTC_MEM_CRC_START + + /* Wait for the CRC calculation to finish */ + ".Lwaitcrc:\n" + "memw\n" + "l32i a2, %0, 0\n" + "bbci a2, "STR(DPORT_RTC_MEM_CRC_FINISH_S)", .Lwaitcrc\n" + "xor %2, %2, %2\n" // %2 -> ~DPORT_RTC_MEM_CRC_START + "and a2, a2, %2\n" + "s32i a2, %0, 0\n" // clear RTC_MEM_CRC_START + "memw\n" + "xor %2, %2, %2\n" // %2 -> DPORT_RTC_MEM_CRC_START, probably unnecessary but gcc assumes inputs unchanged + + /* Store the calculated value in RTC_MEM_CRC_REG */ + "l32i a2, %3, 0\n" + "s32i a2, %4, 0\n" + "memw\n" + + /* Set wait cycle for touch or COCPU after deep sleep (can be moved to C code part?) */ + "l32i a2, %5, 0\n" + "and a2, a2, %6\n" + "or a2, a2, %7\n" + "s32i a2, %5, 0\n" + + /* Set register bit to go into deep sleep */ + "l32i a2, %8, 0\n" + "or a2, a2, %9\n" + "s32i a2, %8, 0\n" + "memw\n" + + /* Wait for sleep reject interrupt (never finishes if successful) */ + ".Lwaitsleep:" + "memw\n" + "l32i a2, %10, 0\n" + "and a2, a2, %11\n" + "beqz a2, .Lwaitsleep\n" + + : + : /* Note, at -O0 this is the limit of available registers in this function */ + "r" (DPORT_RTC_FASTMEM_CONFIG_REG), // %0 + "r" ( (CRC_START_ADDR << DPORT_RTC_MEM_CRC_START_S) + | (CRC_LEN << DPORT_RTC_MEM_CRC_LEN_S)), // %1 + "r" (DPORT_RTC_MEM_CRC_START), // %2 + "r" (DPORT_RTC_FASTMEM_CRC_REG), // %3 + "r" (RTC_MEMORY_CRC_REG), // %4 + "r" (RTC_CNTL_TIMER2_REG), // %5 + "r" (~RTC_CNTL_ULPCP_TOUCH_START_WAIT_M), // %6 + "r" (DEEP_SLEEP_TOUCH_WAIT_CYCLE << RTC_CNTL_ULPCP_TOUCH_START_WAIT_S), // %7 + "r" (RTC_CNTL_STATE0_REG), // %8 + "r" (RTC_CNTL_SLEEP_EN), // %9 + "r" (RTC_CNTL_INT_RAW_REG), // %10 + "r" (RTC_CNTL_SLP_REJECT_INT_RAW | RTC_CNTL_SLP_WAKEUP_INT_RAW) // %11 + : "a2" // working register + ); + + return rtc_sleep_finish(0); +} + +static uint32_t rtc_sleep_finish(uint32_t lslp_mem_inf_fpu) +{ /* In deep sleep mode, we never get here */ uint32_t reject = REG_GET_FIELD(RTC_CNTL_INT_RAW_REG, RTC_CNTL_SLP_REJECT_INT_RAW); SET_PERI_REG_MASK(RTC_CNTL_INT_CLR_REG,