From da0ae0e56d5cc7b9eeba0188790a2c0419024cb1 Mon Sep 17 00:00:00 2001 From: wuzhenghui Date: Tue, 15 Apr 2025 11:58:11 +0800 Subject: [PATCH] feat(esp_hw_support): support configurable uarts behavior before sleep --- components/esp_hw_support/CMakeLists.txt | 1 + .../include/esp_private/sleep_uart.h | 54 +++++++ components/esp_hw_support/include/esp_sleep.h | 54 ++++++- components/esp_hw_support/sleep_modes.c | 119 +------------- components/esp_hw_support/sleep_uart.c | 152 ++++++++++++++++++ 5 files changed, 266 insertions(+), 114 deletions(-) create mode 100644 components/esp_hw_support/include/esp_private/sleep_uart.h create mode 100644 components/esp_hw_support/sleep_uart.c diff --git a/components/esp_hw_support/CMakeLists.txt b/components/esp_hw_support/CMakeLists.txt index 2df8c2553f4..4d909d8bdc6 100644 --- a/components/esp_hw_support/CMakeLists.txt +++ b/components/esp_hw_support/CMakeLists.txt @@ -63,6 +63,7 @@ if(NOT non_os_build) if(CONFIG_SOC_LIGHT_SLEEP_SUPPORTED) list(APPEND srcs "sleep_modem.c" "sleep_modes.c" + "sleep_uart.c" "sleep_console.c" "sleep_mspi.c" "sleep_usb.c" diff --git a/components/esp_hw_support/include/esp_private/sleep_uart.h b/components/esp_hw_support/include/esp_private/sleep_uart.h new file mode 100644 index 00000000000..2905a0710d7 --- /dev/null +++ b/components/esp_hw_support/include/esp_private/sleep_uart.h @@ -0,0 +1,54 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once +#include +#include +#include "sdkconfig.h" +#include "esp_sleep.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Prepare all enabled UARTs for sleep based on their configured handling modes + * + * This function iterates through all UART ports and applies the appropriate handling + * strategy (flush, suspend, or discard) based on each UART's configured mode and + * the sleep parameters. Only enabled UARTs are processed. + * + * @param sleep_flags Sleep configuration flags (e.g., PMU_SLEEP_PD_TOP to indicate TOP domain power down) + * @param deep_sleep true if entering deep sleep, false for light sleep + */ +void sleep_uart_prepare(uint32_t sleep_flags, bool deep_sleep); + +/** + * @brief Resume UARTs that were suspended during sleep preparation + * + * This function restores transmission for UARTs that were suspended (via XOFF) + * during sleep preparation. Only UARTs that were actually suspended are resumed + * (tracked via internal bitmap). UARTs that were flushed or discarded are not affected. + */ +void sleep_uart_resume(void); + +/** + * @brief Set the UART handling mode for a specific UART port + * + * This function configures how a specific UART port should be handled during + * sleep entry. The configured mode will be used (and possibly resolved if set + * to ESP_SLEEP_AUTO_FLUSH_SUSPEND_UART) when sleep_uart_prepare() is called. + * + * @param uart_num UART port number (0 to SOC_UART_HP_NUM-1) + * @param handling_mode Handling mode to configure (see esp_sleep_uart_handling_mode_t) + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if uart_num is out of range or handling_mode is invalid + */ +esp_err_t sleep_uart_set_handling_mode(int uart_num, esp_sleep_uart_handling_mode_t handling_mode); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_hw_support/include/esp_sleep.h b/components/esp_hw_support/include/esp_sleep.h index 19e9462cf4e..89c2bab628d 100644 --- a/components/esp_hw_support/include/esp_sleep.h +++ b/components/esp_hw_support/include/esp_sleep.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -139,6 +139,21 @@ enum { ESP_ERR_SLEEP_TOO_SHORT_SLEEP_DURATION = ESP_ERR_INVALID_ARG, }; +/** + * @brief UART handling mode before entering sleep + * + * These modes define how UARTs are handled when the chip enters sleep mode. + * The behavior affects data integrity, power consumption, and sleep entry time. + */ +typedef enum { + ESP_SLEEP_AUTO_FLUSH_SUSPEND_UART, //!< Automatically select flush or suspend based on sleep type and power domain configuration. For deep sleep, always flush. For light sleep, suspend if UART remains powered, flush/discard if UART power domain is powered down. + ESP_SLEEP_ALWAYS_FLUSH_UART, //!< Always wait for all data in TX FIFO to be transmitted before sleep. Ensures data integrity but increases power consumption and sleep entry time. + ESP_SLEEP_ALWAYS_SUSPEND_UART, //!< Suspend UART transmission after current frame completes. If UART remains powered during sleep, transmission resumes after wake. If UART power domain is powered down, unsent data will be lost. + ESP_SLEEP_ALWAYS_DISCARD_UART, //!< Discard all data in TX/RX FIFOs and enter sleep immediately. Fastest sleep entry and lowest power, but all unsent data is lost. + ESP_SLEEP_NO_HANDLING, //!< Do not perform any UART handling before sleep. UART state is not modified. +} esp_sleep_uart_handling_mode_t; + + /** * @brief Disable wakeup source * @@ -775,6 +790,43 @@ void esp_default_wake_deep_sleep(void); */ void esp_deep_sleep_disable_rom_logging(void); +/** + * @brief Configure how the console UART is handled when entering sleep + * + * This function configures the handling behavior for the console UART (CONFIG_ESP_CONSOLE_UART_NUM) + * during sleep modes. The console UART is typically used for debug output, so its handling mode + * affects whether debug messages are preserved or discarded before sleep. + * + * @param handling_mode Handling method, one of the following strategies: + * - ESP_SLEEP_AUTO_FLUSH_SUSPEND_UART (default): + * Automatically selects the appropriate strategy based on sleep type and power domain: + * - Deep sleep: Always flush to avoid data loss + * - Light sleep: Suspend if UART remains powered, flush if UART power domain is powered down + * - ESP_SLEEP_ALWAYS_FLUSH_UART: + * Wait for all data in TX FIFO to be fully transmitted before entering sleep. + * Ensures all debug output is visible but increases sleep entry time and power consumption. + * - ESP_SLEEP_ALWAYS_SUSPEND_UART: + * Wait for current UART frame to complete, then suspend the UART state machine. + * If UART remains powered during light sleep, transmission resumes after wake. + * If UART power domain is powered down, unsent data will be lost. + * - ESP_SLEEP_ALWAYS_DISCARD_UART: + * Discard all unsent data in UART FIFO and enter sleep immediately. + * Fastest sleep entry and lowest power, but all unsent debug output is lost. + * - ESP_SLEEP_NO_HANDLING: + * Do not perform any handling on the console UART before sleep. + * Can be used to disable the default UART handling behavior. + * + * @note When CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION is enabled, the console UART + * will always be flushed regardless of the configured mode to ensure debug + * output is visible even when cache is disabled. + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if handling_mode is not valid + * - ESP_ERR_NOT_SUPPORTED if no console UART is configured (CONFIG_ESP_CONSOLE_UART_NUM == -1) + */ +esp_err_t esp_sleep_set_console_uart_handling_mode(esp_sleep_uart_handling_mode_t handling_mode); + #if CONFIG_IDF_TARGET_ESP32 /** * @brief If analog-related peripherals(ADC, TOUCH) is not used in monitor mode, analog low power mode diff --git a/components/esp_hw_support/sleep_modes.c b/components/esp_hw_support/sleep_modes.c index 80e848b18c8..306047d5c26 100644 --- a/components/esp_hw_support/sleep_modes.c +++ b/components/esp_hw_support/sleep_modes.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -77,6 +77,7 @@ #include "esp_private/cache_utils.h" #include "esp_private/brownout.h" #include "esp_private/sleep_console.h" +#include "esp_private/sleep_uart.h" #include "esp_private/sleep_cpu.h" #include "esp_private/sleep_modem.h" #include "esp_private/sleep_flash.h" @@ -214,17 +215,6 @@ // Actually costs 80us, using the fastest slow clock 150K calculation takes about 16 ticks #define SLEEP_TIMER_ALARM_TO_SLEEP_TICKS (16) - -#define SLEEP_UART_FLUSH_DONE_TO_SLEEP_US (450) - -#if SOC_PM_SUPPORT_TOP_PD -// IDF console uses 8 bits data mode without parity, so each char occupy 8(data)+1(start)+1(stop)=10bits -#define UART_FLUSH_US_PER_CHAR (10*1000*1000 / CONFIG_ESP_CONSOLE_UART_BAUDRATE) -#define CONCATENATE_HELPER(x, y) (x##y) -#define CONCATENATE(x, y) CONCATENATE_HELPER(x, y) -#define CONSOLE_UART_DEV (&CONCATENATE(UART, CONFIG_ESP_CONSOLE_UART_NUM)) -#endif - #define LIGHT_SLEEP_TIME_OVERHEAD_US DEFAULT_HARDWARE_OUT_OVERHEAD_US #ifdef CONFIG_ESP_SYSTEM_RTC_EXT_XTAL #define DEEP_SLEEP_TIME_OVERHEAD_US (650 + 100 * 240 / CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ) @@ -242,9 +232,9 @@ (source == value)) #if CONFIG_PM_SLP_IRAM_OPT -# define SLEEP_FN_ATTR FORCE_IRAM_ATTR +#define SLEEP_FN_ATTR FORCE_IRAM_ATTR #else -# define SLEEP_FN_ATTR +#define SLEEP_FN_ATTR #endif #define MAX_DSLP_HOOKS 3 @@ -626,99 +616,6 @@ static SLEEP_FN_ATTR void resume_timers(uint32_t sleep_flags) { } } -// [refactor-todo] provide target logic for body of uart functions below -static SLEEP_FN_ATTR void flush_uarts(void) -{ - for (int i = 0; i < SOC_UART_HP_NUM; ++i) { - if (uart_ll_is_enabled(i)) { - esp_rom_output_tx_wait_idle(i); - } - } -} - -static uint32_t s_suspended_uarts_bmap = 0; - -/** - * Suspend enabled uarts and return suspended uarts bit map. - * Must be called from critical sections. - */ -static SLEEP_FN_ATTR void suspend_uarts(void) -{ - s_suspended_uarts_bmap = 0; - for (int i = 0; i < SOC_UART_HP_NUM; ++i) { - if (!uart_ll_is_enabled(i)) { - continue; - } - uart_ll_force_xoff(i); - s_suspended_uarts_bmap |= BIT(i); -#ifdef UART_LL_FSM_TX_WAIT_SEND - uint32_t uart_fsm = 0; - do { - uart_fsm = uart_ll_get_tx_fsm_status(i); - } while (!(uart_fsm == UART_LL_FSM_IDLE || uart_fsm == UART_LL_FSM_TX_WAIT_SEND)); -#else - while (uart_ll_get_tx_fsm_status(i) != 0) {} -#endif - } -} - -// Must be called from critical sections -static SLEEP_FN_ATTR void resume_uarts(void) -{ - for (int i = 0; i < SOC_UART_HP_NUM; ++i) { - if (s_suspended_uarts_bmap & 0x1) { - uart_ll_force_xon(i); - } - s_suspended_uarts_bmap >>= 1; - } -} - -/* - UART prepare strategy in sleep: - Deepsleep : flush the fifo before enter sleep to avoid data loss - - Lightsleep: - Chips not support PD_TOP: Suspend uart before cpu freq switch - - Chips support PD_TOP: - For sleep which will not power down the TOP domain (uart belongs it), we can just suspend the UART. - - For sleep which will power down the TOP domain, we need to consider whether the uart flushing will - block the sleep process and cause the rtos target tick to be missed upon waking up. It's need to - estimate the flush time based on the number of bytes in the uart FIFO, if the predicted flush - completion time has exceeded the wakeup time, we should abandon the flush, skip the sleep and - return ESP_ERR_SLEEP_REJECT. - */ -static SLEEP_FN_ATTR bool light_sleep_uart_prepare(uint32_t sleep_flags, int64_t sleep_duration) -{ - bool should_skip_sleep = false; -#if !SOC_PM_SUPPORT_TOP_PD || !CONFIG_ESP_CONSOLE_UART - suspend_uarts(); -#else -#ifdef CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION -#define FORCE_FLUSH_CONSOLE_UART 1 -#else -#define FORCE_FLUSH_CONSOLE_UART 0 -#endif - if (FORCE_FLUSH_CONSOLE_UART || (sleep_flags & PMU_SLEEP_PD_TOP)) { - if ((s_config.wakeup_triggers & RTC_TIMER_TRIG_EN) && - // +1 is for cover the last character flush time - (sleep_duration < (int64_t)((UART_LL_FIFO_DEF_LEN - uart_ll_get_txfifo_len(CONSOLE_UART_DEV) + 1) * UART_FLUSH_US_PER_CHAR) + SLEEP_UART_FLUSH_DONE_TO_SLEEP_US)) { - should_skip_sleep = true; - } else { - /* Only flush the uart_num configured to console, the transmission integrity of - other uarts is guaranteed by the UART driver */ - if (CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM != -1) { - esp_rom_output_tx_wait_idle(CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM); - } - } - } else { - suspend_uarts(); - } -#endif - return should_skip_sleep; -} - /** * LP peripherals prepare XTAL, FOSC or other clocks as the clock source for sleep. */ @@ -1037,11 +934,7 @@ static esp_err_t SLEEP_FN_ATTR esp_sleep_start(uint32_t sleep_flags, uint32_t cl int64_t sleep_duration = (int64_t) s_config.sleep_duration - (int64_t) s_config.sleep_time_adjustment; // Sleep UART prepare - if (deep_sleep) { - flush_uarts(); - } else { - should_skip_sleep = light_sleep_uart_prepare(sleep_flags, sleep_duration); - } + sleep_uart_prepare(sleep_flags, deep_sleep); #if CONFIG_ESP_PHY_ENABLED && SOC_DEEP_SLEEP_SUPPORTED // Do deep-sleep PHY related callback, which need to be executed when the PLL clock is exists. @@ -1235,7 +1128,7 @@ static esp_err_t SLEEP_FN_ATTR esp_sleep_start(uint32_t sleep_flags, uint32_t cl esp_clk_utils_mspi_speed_mode_sync_after_cpu_freq_switching(cpu_freq_config.source_freq_mhz, cpu_freq_config.freq_mhz); #endif // re-enable UART output - resume_uarts(); + sleep_uart_resume(); return result ? ESP_ERR_SLEEP_REJECT : ESP_OK; } diff --git a/components/esp_hw_support/sleep_uart.c b/components/esp_hw_support/sleep_uart.c new file mode 100644 index 00000000000..72a8d6175b2 --- /dev/null +++ b/components/esp_hw_support/sleep_uart.c @@ -0,0 +1,152 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "sdkconfig.h" +#include "soc/soc_caps.h" +#include "esp_attr.h" +#include "esp_err.h" +#include "esp_sleep.h" +#include "esp_private/sleep_uart.h" +#include "esp_rom_serial_output.h" +#include "hal/uart_ll.h" + +#if SOC_PM_SUPPORT_TOP_PD +#include "esp_private/esp_pmu.h" +#include "hal/pmu_ll.h" +#endif + +#if CONFIG_PM_SLP_IRAM_OPT +#define SLEEP_UART_FN_ATTR FORCE_IRAM_ATTR +#else +#define SLEEP_UART_FN_ATTR +#endif + +// UART handling mode configuration for each UART port +static esp_sleep_uart_handling_mode_t s_uart_handling[SOC_UART_HP_NUM] = { + [0 ... SOC_UART_HP_NUM - 1] = ESP_SLEEP_AUTO_FLUSH_SUSPEND_UART +}; + +// Bitmap of suspended UARTs for resume +static uint32_t s_suspended_uarts_bmap = 0; + +// Suspend a single UART and record it in the bitmap +static SLEEP_UART_FN_ATTR void suspend_uart(int uart_num) +{ + uart_ll_force_xoff(uart_num); + s_suspended_uarts_bmap |= BIT(uart_num); +#ifdef UART_LL_FSM_TX_WAIT_SEND + uint32_t uart_fsm = 0; + do { + uart_fsm = uart_ll_get_tx_fsm_status(uart_num); + } while (!(uart_fsm == UART_LL_FSM_IDLE || uart_fsm == UART_LL_FSM_TX_WAIT_SEND)); +#else + while (uart_ll_get_tx_fsm_status(uart_num) != 0) {} +#endif +} + +/** + * @brief Determine the actual UART handling mode based on configuration and sleep parameters + * + * Default strategy selection: + * - Deep sleep: Always flush FIFO before entering sleep to avoid data loss + * - Light sleep (chips without PD_TOP support): Suspend UART before CPU frequency switch + * - Light sleep (chips with PD_TOP support): + * - If TOP domain is NOT powered down: Suspend UART for faster sleep entry + * - If TOP domain IS powered down: Flush console UART for data integrity, + * discard non-console UARTs to save power + * + * Special handling: + * - Console UART with cache-safe assertion enabled: Always flush to ensure + * debug output is visible even if cache is disabled + */ +static SLEEP_UART_FN_ATTR esp_sleep_uart_handling_mode_t get_uart_handling_mode(int uart_num, esp_sleep_uart_handling_mode_t configured_handling, uint32_t sleep_flags, bool deep_sleep) +{ + esp_sleep_uart_handling_mode_t handling = configured_handling; + __attribute__((unused)) bool is_console_uart = false; +#if (CONFIG_ESP_CONSOLE_UART_NUM != -1) + is_console_uart = (uart_num == CONFIG_ESP_CONSOLE_UART_NUM); +#if CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION + if (is_console_uart) { + handling = ESP_SLEEP_ALWAYS_FLUSH_UART; + } +#endif /* CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION */ +#endif /* CONFIG_ESP_CONSOLE_UART_NUM != -1 */ + + // Resolve AUTO mode into specific strategy + if (handling == ESP_SLEEP_AUTO_FLUSH_SUSPEND_UART) { + // Default: flush for deep sleep, suspend for light sleep + handling = deep_sleep ? ESP_SLEEP_ALWAYS_FLUSH_UART : ESP_SLEEP_ALWAYS_SUSPEND_UART; +#if SOC_PM_SUPPORT_TOP_PD + // If TOP domain (where UART belongs) is powered down during sleep: + // - Console UART: flush to preserve debug output + // - Non-console UART: discard to save power and enter sleep faster + if (sleep_flags & PMU_SLEEP_PD_TOP) { + handling = is_console_uart ? ESP_SLEEP_ALWAYS_FLUSH_UART : ESP_SLEEP_ALWAYS_DISCARD_UART; + } +#endif + } + return handling; +} + +void SLEEP_UART_FN_ATTR sleep_uart_prepare(uint32_t sleep_flags, bool deep_sleep) +{ + s_suspended_uarts_bmap = 0; + for (int i = 0; i < SOC_UART_HP_NUM; ++i) { + if (!uart_ll_is_enabled(i)) { + continue; + } + esp_sleep_uart_handling_mode_t handling = get_uart_handling_mode(i, s_uart_handling[i], sleep_flags, deep_sleep); + switch (handling) { + case ESP_SLEEP_ALWAYS_FLUSH_UART: + esp_rom_output_tx_wait_idle(i); + break; + case ESP_SLEEP_ALWAYS_SUSPEND_UART: + suspend_uart(i); + break; + case ESP_SLEEP_ALWAYS_DISCARD_UART: + // Suspend uart first before reset uart to avoid garbled code. + suspend_uart(i); + uart_ll_txfifo_rst(UART_LL_GET_HW(i)); + uart_ll_rxfifo_rst(UART_LL_GET_HW(i)); + break; + default: + // ESP_SLEEP_AUTO_FLUSH_SUSPEND_UART should have been resolved in get_uart_handling_mode + break; + } + } +} + +void SLEEP_UART_FN_ATTR sleep_uart_resume(void) +{ + for (int i = 0; i < SOC_UART_HP_NUM; ++i) { + if (s_suspended_uarts_bmap & 0x1) { + uart_ll_force_xon(i); + } + s_suspended_uarts_bmap >>= 1; + } +} + +esp_err_t sleep_uart_set_handling_mode(int uart_num, esp_sleep_uart_handling_mode_t handling_mode) +{ + if (handling_mode > ESP_SLEEP_NO_HANDLING) { + return ESP_ERR_INVALID_ARG; + } + if (uart_num < 0 || uart_num >= SOC_UART_HP_NUM) { + return ESP_ERR_INVALID_ARG; + } + s_uart_handling[uart_num] = handling_mode; + return ESP_OK; +} + +esp_err_t esp_sleep_set_console_uart_handling_mode(esp_sleep_uart_handling_mode_t handling_mode) +{ + esp_err_t ret = ESP_ERR_NOT_SUPPORTED; +#if (CONFIG_ESP_CONSOLE_UART_NUM != -1) + ret = sleep_uart_set_handling_mode(CONFIG_ESP_CONSOLE_UART_NUM, handling_mode); +#endif + return ret; +}