diff --git a/components/esp_hw_support/CMakeLists.txt b/components/esp_hw_support/CMakeLists.txt index e64c9552ec8..477d9ad5189 100644 --- a/components/esp_hw_support/CMakeLists.txt +++ b/components/esp_hw_support/CMakeLists.txt @@ -28,6 +28,7 @@ if(NOT BOOTLOADER_BUILD) "rtc_module.c" "sleep_modes.c" "sleep_gpio.c" + "sleep_event.c" "sleep_modem.c" "regi2c_ctrl.c" "esp_gpio_reserve.c" diff --git a/components/esp_hw_support/Kconfig b/components/esp_hw_support/Kconfig index a968c45543c..8a3b226bf73 100644 --- a/components/esp_hw_support/Kconfig +++ b/components/esp_hw_support/Kconfig @@ -161,6 +161,20 @@ menu "Hardware Settings" help When using rtc gpio wakeup source during deepsleep without external pull-up/downs, you may want to make use of the internal ones. + + config ESP_SLEEP_EVENT_CALLBACKS + bool "Enable registration of sleep event callbacks" + depends on FREERTOS_USE_TICKLESS_IDLE + default n + help + If enabled, it allows user to register sleep event callbacks. It is primarily designed for internal + developers and customers can use PM_LIGHT_SLEEP_CALLBACKS as an alternative. + + NOTE: These callbacks are executed from the IDLE task context hence you cannot have any blocking calls + in your callbacks. + + NOTE: Enabling these callbacks may change sleep duration calculations based on time spent in + callback and hence it is highly recommended to keep them as short as possible. endmenu menu "ESP_SLEEP_WORKAROUND" diff --git a/components/esp_hw_support/include/esp_private/sleep_event.h b/components/esp_hw_support/include/esp_private/sleep_event.h new file mode 100644 index 00000000000..7a7efaabfcf --- /dev/null +++ b/components/esp_hw_support/include/esp_private/sleep_event.h @@ -0,0 +1,120 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef enum { + /** + * Using SLEEP_EVENT to determine the execution of specific + * code at a particular point in the sleep flow. + */ + SLEEP_EVENT_HW_EXIT_SLEEP, // CPU wake up and start to work + SLEEP_EVENT_SW_CLK_READY, // CPU frequency restore + SLEEP_EVENT_SW_EXIT_SLEEP, // End of esp_light_sleep_start + SLEEP_EVENT_SW_GOTO_SLEEP, // Beginning of esp_light_sleep_start + SLEEP_EVENT_HW_TIME_START, // Start timing the sleep time + SLEEP_EVENT_HW_GOTO_SLEEP, // Hardware is about to power off + SLEEP_EVENT_SW_CPU_TO_MEM_START, // CPU registers are starting to be saved + SLEEP_EVENT_SW_CPU_TO_MEM_END, // CPU registers have finished saving +#if CONFIG_IDF_TARGET_ESP32H2 + SLEEP_EVENT_HW_FLASH_BBPLL_EN_START, // Beginning of rtc_clk_bbpll_enable when using FLASH_PLL + SLEEP_EVENT_HW_FLASH_BBPLL_EN_STOP, // End of rtc_clk_bbpll_enable when using FLASH_PLL +#endif + SLEEP_EVENT_HW_BBPLL_EN_START, // Beginning of rtc_clk_bbpll_enable + SLEEP_EVENT_HW_BBPLL_EN_STOP, // End of rtc_clk_bbpll_enable + SLEEP_EVENT_CB_INDEX_NUM, +} esp_sleep_event_cb_index_t; + +/** + * @brief Function prototype for light sleep event callback functions (if CONFIG_FREERTOS_USE_TICKLESS_IDLE). + * @param user_arg is the user provided argument while registering callbacks. + * @param ext_arg is an externally provided parameter that is used when the callback is executed. + * @return None + */ + +typedef esp_err_t (*esp_sleep_event_cb_t)(void *user_arg, void *ext_arg); + +/** + * @brief Function entry parameter types for light sleep event callback functions (if CONFIG_FREERTOS_USE_TICKLESS_IDLE) + */ +struct _esp_sleep_event_cb_config_t { + /** + * Callback function defined by internal developers. + */ + esp_sleep_event_cb_t cb; + /** + * Input parameters of callback function defined by internal developers. + */ + void *user_arg; + /** + * Execution priority of callback function defined by internal developers. + * The smaller the priority, the earlier it executes when call esp_sleep_execute_event_callbacks. + * If functions have the same priority, the function registered first will be executed first. + */ + uint32_t prior; + /** + * Next callback configuration defined by internal developer. + */ + struct _esp_sleep_event_cb_config_t *next; +}; + +typedef struct _esp_sleep_event_cb_config_t esp_sleep_event_cb_config_t; + +struct _esp_sleep_event_cbs_config_t { + /** + * Callback configurations defined by internal developers. + */ + esp_sleep_event_cb_config_t *sleep_event_cb_config[SLEEP_EVENT_CB_INDEX_NUM]; +}; + +typedef struct _esp_sleep_event_cbs_config_t esp_sleep_event_cbs_config_t; + +/** + * @brief Register event callbacks for light sleep internal events (if CONFIG_FREERTOS_USE_TICKLESS_IDLE) + * @param event_id Designed to register the corresponding event_cb in g_sleep_event_cbs_config + * @param event_cb_conf Config struct containing event callback function and corresponding argument + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if the input parameter event_cb_conf is NULL or event_id is out of range + * - ESP_ERR_NO_MEM if the remaining memory is insufficient to support malloc + * - ESP_FAIL if register the same function repeatedly + * + * @note Some of these callback functions are called from IDLE task context hence they cannot call any blocking functions + * @note Passing NULL value will not deregister the callbacks, it will silently ignore and return ESP_OK + */ +esp_err_t esp_sleep_register_event_callback(esp_sleep_event_cb_index_t event_id, const esp_sleep_event_cb_config_t *event_cb_conf); + +/** + * @brief Unregister event callbacks for light sleep internal events (if CONFIG_FREERTOS_USE_TICKLESS_IDLE) + * @param event_id Designed to unregister the corresponding event_cb in g_sleep_event_cbs_config + * @param event_cb_conf Config struct containing event callback function and corresponding argument + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if the input parameter cb is NULL or event_id is out of range + */ +esp_err_t esp_sleep_unregister_event_callback(esp_sleep_event_cb_index_t event_id, esp_sleep_event_cb_t cb); + +/** + * @brief Designed to execute functions in the esp_sleep_event_cb_config_t linked list + * + * @param event_id Designed to annotate the corresponding event_cb in g_sleep_event_cbs_config + * @param ext_arg Designed to pass external parameters + * @return None + */ +void esp_sleep_execute_event_callbacks(esp_sleep_event_cb_index_t event_id, void *ext_arg); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_hw_support/port/esp32c6/rtc_clk.c b/components/esp_hw_support/port/esp32c6/rtc_clk.c index ed6eb4126f2..53644617ca3 100644 --- a/components/esp_hw_support/port/esp32c6/rtc_clk.c +++ b/components/esp_hw_support/port/esp32c6/rtc_clk.c @@ -19,6 +19,7 @@ #include "hal/regi2c_ctrl_ll.h" #include "soc/io_mux_reg.h" #include "soc/lp_aon_reg.h" +#include "esp_private/sleep_event.h" #ifdef BOOTLOADER_BUILD #include "hal/modem_lpcon_ll.h" @@ -252,6 +253,10 @@ bool rtc_clk_cpu_freq_mhz_to_config(uint32_t freq_mhz, rtc_cpu_freq_config_t *ou return true; } +__attribute__((weak)) void rtc_clk_set_cpu_switch_to_bbpll(int event_id) +{ +} + void rtc_clk_cpu_freq_set_config(const rtc_cpu_freq_config_t *config) { soc_cpu_clk_src_t old_cpu_clk_src = clk_ll_cpu_get_src(); @@ -263,10 +268,12 @@ void rtc_clk_cpu_freq_set_config(const rtc_cpu_freq_config_t *config) } } else if (config->source == SOC_CPU_CLK_SRC_PLL) { if (old_cpu_clk_src != SOC_CPU_CLK_SRC_PLL) { + rtc_clk_set_cpu_switch_to_bbpll(SLEEP_EVENT_HW_BBPLL_EN_START); rtc_clk_bbpll_enable(); rtc_clk_bbpll_configure(rtc_clk_xtal_freq_get(), config->source_freq_mhz); } rtc_clk_cpu_freq_to_pll_mhz(config->freq_mhz); + rtc_clk_set_cpu_switch_to_bbpll(SLEEP_EVENT_HW_BBPLL_EN_STOP); } else if (config->source == SOC_CPU_CLK_SRC_RC_FAST) { rtc_clk_cpu_freq_to_8m(); if ((old_cpu_clk_src == SOC_CPU_CLK_SRC_PLL) && !s_bbpll_digi_consumers_ref_count) { diff --git a/components/esp_hw_support/port/esp32h2/rtc_clk.c b/components/esp_hw_support/port/esp32h2/rtc_clk.c index ce0a0e260b8..bb8e288b53b 100644 --- a/components/esp_hw_support/port/esp32h2/rtc_clk.c +++ b/components/esp_hw_support/port/esp32h2/rtc_clk.c @@ -20,6 +20,7 @@ #include "soc/io_mux_reg.h" #include "soc/lp_aon_reg.h" #include "soc/lp_clkrst_reg.h" +#include "esp_private/sleep_event.h" #ifdef BOOTLOADER_BUILD #include "hal/modem_lpcon_ll.h" @@ -297,6 +298,10 @@ bool rtc_clk_cpu_freq_mhz_to_config(uint32_t freq_mhz, rtc_cpu_freq_config_t *ou return true; } +__attribute__((weak)) void rtc_clk_set_cpu_switch_to_bbpll(int event_id) +{ +} + void rtc_clk_cpu_freq_set_config(const rtc_cpu_freq_config_t *config) { soc_cpu_clk_src_t old_cpu_clk_src = clk_ll_cpu_get_src(); @@ -308,10 +313,12 @@ void rtc_clk_cpu_freq_set_config(const rtc_cpu_freq_config_t *config) } } else if (config->source == SOC_CPU_CLK_SRC_PLL) { if (old_cpu_clk_src != SOC_CPU_CLK_SRC_PLL && old_cpu_clk_src != SOC_CPU_CLK_SRC_FLASH_PLL) { + rtc_clk_set_cpu_switch_to_bbpll(SLEEP_EVENT_HW_BBPLL_EN_START); rtc_clk_bbpll_enable(); rtc_clk_bbpll_configure(rtc_clk_xtal_freq_get(), config->source_freq_mhz); } rtc_clk_cpu_freq_to_pll_mhz(config->freq_mhz); + rtc_clk_set_cpu_switch_to_bbpll(SLEEP_EVENT_HW_BBPLL_EN_STOP); } else if (config->source == SOC_CPU_CLK_SRC_RC_FAST) { rtc_clk_cpu_freq_to_8m(); if ((old_cpu_clk_src == SOC_CPU_CLK_SRC_PLL || old_cpu_clk_src == SOC_CPU_CLK_SRC_FLASH_PLL) && @@ -322,10 +329,12 @@ void rtc_clk_cpu_freq_set_config(const rtc_cpu_freq_config_t *config) if (old_cpu_clk_src != SOC_CPU_CLK_SRC_PLL && old_cpu_clk_src != SOC_CPU_CLK_SRC_FLASH_PLL) { // On ESP32H2, FLASH_PLL (64MHz) is directly derived from the BBPLL (96MHz) // Therefore, enabling and configuration are applied to BBPLL. + rtc_clk_set_cpu_switch_to_bbpll(SLEEP_EVENT_HW_FLASH_BBPLL_EN_START); rtc_clk_bbpll_enable(); rtc_clk_bbpll_configure(rtc_clk_xtal_freq_get(), CLK_LL_PLL_96M_FREQ_MHZ); } rtc_clk_cpu_freq_to_flash_pll(config->freq_mhz, config->div); + rtc_clk_set_cpu_switch_to_bbpll(SLEEP_EVENT_HW_FLASH_BBPLL_EN_STOP); } } diff --git a/components/esp_hw_support/sleep_cpu.c b/components/esp_hw_support/sleep_cpu.c index b0f01badb7e..8fb577883e1 100644 --- a/components/esp_hw_support/sleep_cpu.c +++ b/components/esp_hw_support/sleep_cpu.c @@ -20,6 +20,7 @@ #include "esp_heap_caps.h" #include "soc/soc_caps.h" #include "esp_private/sleep_cpu.h" +#include "esp_private/sleep_event.h" #include "sdkconfig.h" #if SOC_PMU_SUPPORTED @@ -685,6 +686,7 @@ static IRAM_ATTR esp_err_t do_cpu_retention(sleep_cpu_entry_cb_t goto_sleep, { RvCoreCriticalSleepFrame * frame = rv_core_critical_regs_save(); if ((frame->pmufunc & 0x3) == 0x1) { + esp_sleep_execute_event_callbacks(SLEEP_EVENT_SW_CPU_TO_MEM_END, (void *)0); #if CONFIG_PM_CHECK_SLEEP_RETENTION_FRAME /* Minus 2 * sizeof(long) is for bypass `pmufunc` and `frame_crc` field */ update_retention_frame_crc((uint32_t*)frame, RV_SLEEP_CTX_FRMSZ - 2 * sizeof(long), (uint32_t *)(&frame->frame_crc)); @@ -704,6 +706,7 @@ static IRAM_ATTR esp_err_t do_cpu_retention(sleep_cpu_entry_cb_t goto_sleep, esp_err_t IRAM_ATTR esp_sleep_cpu_retention(uint32_t (*goto_sleep)(uint32_t, uint32_t, uint32_t, bool), uint32_t wakeup_opt, uint32_t reject_opt, uint32_t lslp_mem_inf_fpu, bool dslp) { + esp_sleep_execute_event_callbacks(SLEEP_EVENT_SW_CPU_TO_MEM_START, (void *)0); uint32_t mstatus = save_mstatus_and_disable_global_int(); cpu_domain_dev_regs_save(s_cpu_retention.retent.plic_frame); @@ -728,7 +731,6 @@ esp_err_t IRAM_ATTR esp_sleep_cpu_retention(uint32_t (*goto_sleep)(uint32_t, uin cpu_domain_dev_regs_restore(s_cpu_retention.retent.intpri_frame); cpu_domain_dev_regs_restore(s_cpu_retention.retent.clint_frame); cpu_domain_dev_regs_restore(s_cpu_retention.retent.plic_frame); - restore_mstatus(mstatus); return err; } diff --git a/components/esp_hw_support/sleep_cpu_asm.S b/components/esp_hw_support/sleep_cpu_asm.S index b84dd4d4216..65d0e6de066 100644 --- a/components/esp_hw_support/sleep_cpu_asm.S +++ b/components/esp_hw_support/sleep_cpu_asm.S @@ -166,9 +166,16 @@ wait_sync_done: .section .iram1,"ax" .global rv_core_critical_regs_restore + .weak rv_core_critical_regs_restore .type rv_core_critical_regs_restore,@function + .global _rv_core_critical_regs_restore + .type _rv_core_critical_regs_restore,@function .align 4 +_rv_core_critical_regs_restore: /* export a strong symbol to jump to here, used + * for a static callback */ + nop + rv_core_critical_regs_restore: la t0, rv_core_critical_regs_frame diff --git a/components/esp_hw_support/sleep_event.c b/components/esp_hw_support/sleep_event.c new file mode 100644 index 00000000000..02bc704f58c --- /dev/null +++ b/components/esp_hw_support/sleep_event.c @@ -0,0 +1,92 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "sdkconfig.h" +#include "soc/soc_caps.h" +#include "esp_private/sleep_event.h" + +#include "esp_sleep.h" +#include "esp_log.h" +#include "esp_check.h" +#include "freertos/FreeRTOS.h" + +static __attribute__((unused)) const char *TAG = "sleep_event"; + +#if CONFIG_ESP_SLEEP_EVENT_CALLBACKS +esp_sleep_event_cbs_config_t g_sleep_event_cbs_config; +static portMUX_TYPE s_sleep_event_mutex = portMUX_INITIALIZER_UNLOCKED; + +esp_err_t esp_sleep_register_event_callback(esp_sleep_event_cb_index_t event_id, const esp_sleep_event_cb_config_t *event_cb_conf) { + if (event_cb_conf == NULL || event_id >= SLEEP_EVENT_CB_INDEX_NUM) { + return ESP_ERR_INVALID_ARG; + } + esp_sleep_event_cb_config_t *new_config = (esp_sleep_event_cb_config_t *)heap_caps_malloc(sizeof(esp_sleep_event_cb_config_t), MALLOC_CAP_INTERNAL); + if (new_config == NULL) { + return ESP_ERR_NO_MEM; /* Memory allocation failed */ + } + + portENTER_CRITICAL(&s_sleep_event_mutex); + esp_sleep_event_cb_config_t **current_ptr = &(g_sleep_event_cbs_config.sleep_event_cb_config[event_id]); + while (*current_ptr != NULL) { + if (((*current_ptr)->cb) == (event_cb_conf->cb)) { + free(new_config); + portEXIT_CRITICAL(&s_sleep_event_mutex); + return ESP_FAIL; + } + current_ptr = &((*current_ptr)->next); + } + + *new_config = *event_cb_conf; + while (*current_ptr != NULL && (*current_ptr)->prior <= new_config->prior) { + current_ptr = &((*current_ptr)->next); + } + new_config->next = *current_ptr; + *current_ptr = new_config; + portEXIT_CRITICAL(&s_sleep_event_mutex); + return ESP_OK; +} + +esp_err_t esp_sleep_unregister_event_callback(esp_sleep_event_cb_index_t event_id, esp_sleep_event_cb_t cb) { + if (cb == NULL || event_id >= SLEEP_EVENT_CB_INDEX_NUM) { + return ESP_ERR_INVALID_ARG; + } + portENTER_CRITICAL(&s_sleep_event_mutex); + esp_sleep_event_cb_config_t **current_ptr = &(g_sleep_event_cbs_config.sleep_event_cb_config[event_id]); + while (*current_ptr != NULL) { + if (((*current_ptr)->cb) == cb) { + esp_sleep_event_cb_config_t *temp = *current_ptr; + *current_ptr = (*current_ptr)->next; + free(temp); + break; + } + current_ptr = &((*current_ptr)->next); + } + portEXIT_CRITICAL(&s_sleep_event_mutex); + return ESP_OK; +} +#endif + +void IRAM_ATTR esp_sleep_execute_event_callbacks(esp_sleep_event_cb_index_t event_id, void *ext_arg) +{ +#if CONFIG_ESP_SLEEP_EVENT_CALLBACKS + if (event_id >= SLEEP_EVENT_CB_INDEX_NUM) { + ESP_EARLY_LOGW(TAG, "event_id out of range"); + return; + } + esp_sleep_event_cb_config_t *current = g_sleep_event_cbs_config.sleep_event_cb_config[event_id]; + while (current != NULL) { + if (current->cb != NULL) { + if (ESP_OK != (*current->cb)(current->user_arg, ext_arg)) { + ESP_EARLY_LOGW(TAG, "esp_sleep_execute_event_callbacks has an err, current->cb = %p", current->cb); + } + } + current = current->next; + } +#endif +} diff --git a/components/esp_hw_support/sleep_modes.c b/components/esp_hw_support/sleep_modes.c index d8d2e7b8ed9..d20a8fbb1c3 100644 --- a/components/esp_hw_support/sleep_modes.c +++ b/components/esp_hw_support/sleep_modes.c @@ -14,6 +14,7 @@ #include "esp_sleep.h" #include "esp_private/esp_sleep_internal.h" #include "esp_private/esp_timer_private.h" +#include "esp_private/sleep_event.h" #include "esp_private/system_internal.h" #include "esp_log.h" #include "esp_newlib.h" @@ -746,12 +747,14 @@ static esp_err_t IRAM_ATTR esp_sleep_start(uint32_t pd_flags, esp_sleep_mode_t m #if SOC_PMU_SUPPORTED #if SOC_PM_CPU_RETENTION_BY_SW + esp_sleep_execute_event_callbacks(SLEEP_EVENT_HW_GOTO_SLEEP, (void *)0); if (pd_flags & PMU_SLEEP_PD_CPU) { result = esp_sleep_cpu_retention(pmu_sleep_start, s_config.wakeup_triggers, reject_triggers, config.power.hp_sys.dig_power.mem_dslp, deep_sleep); } else { #endif result = call_rtc_sleep_start(reject_triggers, config.power.hp_sys.dig_power.mem_dslp, deep_sleep); } + esp_sleep_execute_event_callbacks(SLEEP_EVENT_HW_EXIT_SLEEP, (void *)0); #else result = call_rtc_sleep_start(reject_triggers, config.lslp_mem_inf_fpu, deep_sleep); #endif @@ -796,6 +799,8 @@ static esp_err_t IRAM_ATTR esp_sleep_start(uint32_t pd_flags, esp_sleep_mode_t m #endif } + esp_sleep_execute_event_callbacks(SLEEP_EVENT_SW_CLK_READY, (void *)0); + if (!deep_sleep) { s_config.ccount_ticks_record = esp_cpu_get_cycle_count(); misc_modules_wake_prepare(); @@ -966,6 +971,7 @@ static inline bool can_power_down_vddsdio(uint32_t pd_flags, const uint32_t vdds esp_err_t esp_light_sleep_start(void) { s_config.ccount_ticks_record = esp_cpu_get_cycle_count(); + esp_sleep_execute_event_callbacks(SLEEP_EVENT_SW_GOTO_SLEEP, (void *)0); #if CONFIG_ESP_TASK_WDT_USE_ESP_TIMER esp_err_t timerret = ESP_OK; @@ -1003,9 +1009,9 @@ esp_err_t esp_light_sleep_start(void) s_config.rtc_ticks_at_sleep_start = rtc_time_get(); #endif uint32_t ccount_at_sleep_start = esp_cpu_get_cycle_count(); + esp_sleep_execute_event_callbacks(SLEEP_EVENT_HW_TIME_START, (void *)0); uint64_t high_res_time_at_start = esp_timer_get_time(); uint32_t sleep_time_overhead_in = (ccount_at_sleep_start - s_config.ccount_ticks_record) / (esp_clk_cpu_freq() / 1000000ULL); - esp_ipc_isr_stall_other_cpu(); // Decide which power domains can be powered down @@ -1165,6 +1171,7 @@ esp_err_t esp_light_sleep_start(void) } #endif // CONFIG_ESP_TASK_WDT_USE_ESP_TIMER + esp_sleep_execute_event_callbacks(SLEEP_EVENT_SW_EXIT_SLEEP, (void *)0); s_config.sleep_time_overhead_out = (esp_cpu_get_cycle_count() - s_config.ccount_ticks_record) / (esp_clk_cpu_freq() / 1000000ULL); return err; } diff --git a/components/esp_pm/Kconfig b/components/esp_pm/Kconfig index fbdfab1e3d0..d7661c09230 100644 --- a/components/esp_pm/Kconfig +++ b/components/esp_pm/Kconfig @@ -138,4 +138,18 @@ menu "Power Management" bool default y if PM_ENABLE && BTDM_CTRL_HLI + config PM_LIGHT_SLEEP_CALLBACKS + bool "Enable registration of pm light sleep callbacks" + depends on FREERTOS_USE_TICKLESS_IDLE + default n + help + If enabled, it allows user to register entry and exit callbacks which are called before and after + entering auto light sleep. + + NOTE: These callbacks are executed from the IDLE task context hence you cannot have any blocking calls + in your callbacks. + + NOTE: Enabling these callbacks may change sleep duration calculations based on time spent in callback and + hence it is highly recommended to keep them as short as possible + endmenu # "Power Management" diff --git a/components/esp_pm/include/esp_pm.h b/components/esp_pm/include/esp_pm.h index 7e34b21380a..b7f7d045fdd 100644 --- a/components/esp_pm/include/esp_pm.h +++ b/components/esp_pm/include/esp_pm.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -192,6 +192,63 @@ esp_err_t esp_pm_lock_delete(esp_pm_lock_handle_t handle); */ esp_err_t esp_pm_dump_locks(FILE* stream); +#if CONFIG_PM_LIGHT_SLEEP_CALLBACKS +/** + * @brief Function prototype for light sleep callback functions (if CONFIG_FREERTOS_USE_TICKLESS_IDLE) + * + * @param sleep_time_us supplied by the power management framework. + * For entry callback, sleep_time_us indicates the expected sleep time in us + * For exit callback, sleep_time_us indicates the actual sleep time in us + * @param arg is the user provided argument while registering callbacks + * + * @return + * - ESP_OK allow entry light sleep mode + */ +typedef esp_err_t (*esp_pm_light_sleep_cb_t)(int64_t sleep_time_us, void *arg); + +typedef struct { + /** + * Callback function defined by internal developers. + */ + esp_pm_light_sleep_cb_t enter_cb; + esp_pm_light_sleep_cb_t exit_cb; + /** + * Input parameters of callback function defined by internal developers. + */ + void *enter_cb_user_arg; + void *exit_cb_user_arg; + /** + * Execution priority of callback function defined by internal developers. + * The smaller the priority, the earlier it executes when call esp_sleep_execute_event_callbacks. + * If functions have the same priority, the function registered first will be executed first. + */ + uint32_t enter_cb_prior; + uint32_t exit_cb_prior; +} esp_pm_sleep_cbs_register_config_t; + +/** + * @brief Register entry or exit callbacks for light sleep (if CONFIG_FREERTOS_USE_TICKLESS_IDLE) + * @param cbs_conf Config struct containing entry or exit callbacks function and corresponding argument + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if the input parameter enter_cb and exit_cb in cbs_conf are NULL + * - ESP_ERR_NO_MEM if the remaining memory is insufficient to support malloc + * - ESP_FAIL if register the same function repeatedly + * + * @note These callback functions are called from IDLE task context hence they cannot call any blocking functions + */ +esp_err_t esp_pm_light_sleep_register_cbs(esp_pm_sleep_cbs_register_config_t *cbs_conf); + +/** + * @brief Unregister entry or exit callbacks for light sleep (if CONFIG_FREERTOS_USE_TICKLESS_IDLE) + * @param cbs_conf Config struct containing entry or exit callbacks function and corresponding argument + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if the input parameter enter_cb and exit_cb in cbs_conf are NULL + */ +esp_err_t esp_pm_light_sleep_unregister_cbs(esp_pm_sleep_cbs_register_config_t *cbs_conf); +#endif + #ifdef __cplusplus } #endif diff --git a/components/esp_pm/pm_impl.c b/components/esp_pm/pm_impl.c index 0cf529912aa..8131f5a5f5d 100644 --- a/components/esp_pm/pm_impl.c +++ b/components/esp_pm/pm_impl.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -43,6 +43,7 @@ #include "esp_private/sleep_gpio.h" #include "esp_private/sleep_modem.h" #include "esp_sleep.h" +#include "esp_memory_utils.h" #include "sdkconfig.h" @@ -201,6 +202,154 @@ pm_mode_t esp_pm_impl_get_mode(esp_pm_lock_type_t type, int arg) } } +#if CONFIG_PM_LIGHT_SLEEP_CALLBACKS +/** + * @brief Function entry parameter types for light sleep callback functions (if CONFIG_FREERTOS_USE_TICKLESS_IDLE) + */ +struct _esp_pm_sleep_cb_config_t { + /** + * Callback function defined by user. + */ + esp_pm_light_sleep_cb_t cb; + /** + * Input parameters of callback function defined by user. + */ + void *arg; + /** + * Execution priority of callback function defined by user. + */ + uint32_t prior; + /** + * Next callback function defined by user. + */ + struct _esp_pm_sleep_cb_config_t *next; +}; + +typedef struct _esp_pm_sleep_cb_config_t esp_pm_sleep_cb_config_t; + +static esp_pm_sleep_cb_config_t *s_light_sleep_enter_cb_config; +static esp_pm_sleep_cb_config_t *s_light_sleep_exit_cb_config; +static portMUX_TYPE s_sleep_pm_cb_mutex = portMUX_INITIALIZER_UNLOCKED; + +esp_err_t esp_pm_light_sleep_register_cbs(esp_pm_sleep_cbs_register_config_t *cbs_conf) +{ + if (cbs_conf->enter_cb == NULL && cbs_conf->exit_cb == NULL) { + return ESP_ERR_INVALID_ARG; + } + portENTER_CRITICAL(&s_sleep_pm_cb_mutex); + if (cbs_conf->enter_cb != NULL) { + esp_pm_sleep_cb_config_t **current_enter_ptr = &(s_light_sleep_enter_cb_config); + while (*current_enter_ptr != NULL) { + if (((*current_enter_ptr)->cb) == (cbs_conf->enter_cb)) { + portEXIT_CRITICAL(&s_sleep_pm_cb_mutex); + return ESP_FAIL; + } + current_enter_ptr = &((*current_enter_ptr)->next); + } + esp_pm_sleep_cb_config_t *new_enter_config = (esp_pm_sleep_cb_config_t *)heap_caps_malloc(sizeof(esp_pm_sleep_cb_config_t), MALLOC_CAP_INTERNAL); + if (new_enter_config == NULL) { + portEXIT_CRITICAL(&s_sleep_pm_cb_mutex); + return ESP_ERR_NO_MEM; /* Memory allocation failed */ + } + new_enter_config->cb = cbs_conf->enter_cb; + new_enter_config->arg = cbs_conf->enter_cb_user_arg; + new_enter_config->prior = cbs_conf->enter_cb_prior; + while (*current_enter_ptr != NULL && (*current_enter_ptr)->prior <= new_enter_config->prior) { + current_enter_ptr = &((*current_enter_ptr)->next); + } + new_enter_config->next = *current_enter_ptr; + *current_enter_ptr = new_enter_config; + } + + if (cbs_conf->exit_cb != NULL) { + esp_pm_sleep_cb_config_t **current_exit_ptr = &(s_light_sleep_exit_cb_config); + while (*current_exit_ptr != NULL) { + if (((*current_exit_ptr)->cb) == (cbs_conf->exit_cb)) { + portEXIT_CRITICAL(&s_sleep_pm_cb_mutex); + return ESP_FAIL; + } + current_exit_ptr = &((*current_exit_ptr)->next); + } + esp_pm_sleep_cb_config_t *new_exit_config = (esp_pm_sleep_cb_config_t *)heap_caps_malloc(sizeof(esp_pm_sleep_cb_config_t), MALLOC_CAP_INTERNAL); + if (new_exit_config == NULL) { + portEXIT_CRITICAL(&s_sleep_pm_cb_mutex); + return ESP_ERR_NO_MEM; /* Memory allocation failed */ + } + new_exit_config->cb = cbs_conf->exit_cb; + new_exit_config->arg = cbs_conf->exit_cb_user_arg; + new_exit_config->prior = cbs_conf->exit_cb_prior; + while (*current_exit_ptr != NULL && (*current_exit_ptr)->prior <= new_exit_config->prior) { + current_exit_ptr = &((*current_exit_ptr)->next); + } + new_exit_config->next = *current_exit_ptr; + *current_exit_ptr = new_exit_config; + } + portEXIT_CRITICAL(&s_sleep_pm_cb_mutex); + return ESP_OK; +} + +esp_err_t esp_pm_light_sleep_unregister_cbs(esp_pm_sleep_cbs_register_config_t *cbs_conf) +{ + if (cbs_conf->enter_cb == NULL && cbs_conf->exit_cb == NULL) { + return ESP_ERR_INVALID_ARG; + } + portENTER_CRITICAL(&s_sleep_pm_cb_mutex); + if (cbs_conf->enter_cb != NULL) { + esp_pm_sleep_cb_config_t **current_enter_ptr = &(s_light_sleep_enter_cb_config); + while (*current_enter_ptr != NULL) { + if ((*current_enter_ptr)->cb == cbs_conf->enter_cb) { + esp_pm_sleep_cb_config_t *temp = *current_enter_ptr; + *current_enter_ptr = (*current_enter_ptr)->next; + free(temp); + break; + } + current_enter_ptr = &((*current_enter_ptr)->next); + } + } + + if (cbs_conf->exit_cb != NULL) { + esp_pm_sleep_cb_config_t **current_exit_ptr = &(s_light_sleep_exit_cb_config); + while (*current_exit_ptr != NULL) { + if ((*current_exit_ptr)->cb == cbs_conf->exit_cb) { + esp_pm_sleep_cb_config_t *temp = *current_exit_ptr; + *current_exit_ptr = (*current_exit_ptr)->next; + free(temp); + break; + } + current_exit_ptr = &((*current_exit_ptr)->next); + } + } + portEXIT_CRITICAL(&s_sleep_pm_cb_mutex); + return ESP_OK; +} + +static void IRAM_ATTR esp_pm_execute_enter_sleep_callbacks(int64_t sleep_time_us) +{ + esp_pm_sleep_cb_config_t *enter_current = s_light_sleep_enter_cb_config; + while (enter_current != NULL) { + if (enter_current->cb != NULL) { + if (ESP_OK != (*enter_current->cb)(sleep_time_us, enter_current->arg)) { + ESP_EARLY_LOGW(TAG, "esp_pm_execute_enter_sleep_callbacks has an err, enter_current = %p", enter_current); + } + } + enter_current = enter_current->next; + } +} + +static void IRAM_ATTR esp_pm_execute_exit_sleep_callbacks(int64_t sleep_time_us) +{ + esp_pm_sleep_cb_config_t *exit_current = s_light_sleep_exit_cb_config; + while (exit_current != NULL) { + if (exit_current->cb != NULL) { + if (ESP_OK != (*exit_current->cb)(sleep_time_us, exit_current->arg)) { + ESP_EARLY_LOGW(TAG, "esp_pm_execute_exit_sleep_callbacks has an err, exit_current = %p", exit_current); + } + } + exit_current = exit_current->next; + } +} +#endif + static esp_err_t esp_pm_sleep_configure(const void *vconfig) { esp_err_t err = ESP_OK; @@ -627,6 +776,12 @@ void IRAM_ATTR vApplicationSleep( TickType_t xExpectedIdleTime ) int64_t time_until_next_alarm = next_esp_timer_alarm - now; int64_t wakeup_delay_us = portTICK_PERIOD_MS * 1000LL * xExpectedIdleTime; int64_t sleep_time_us = MIN(wakeup_delay_us, time_until_next_alarm); + int64_t slept_us = 0; +#if CONFIG_PM_LIGHT_SLEEP_CALLBACKS + uint32_t cycle = esp_cpu_get_cycle_count(); + esp_pm_execute_enter_sleep_callbacks(sleep_time_us); + sleep_time_us -= (esp_cpu_get_cycle_count() - cycle) / (esp_clk_cpu_freq() / 1000000ULL); +#endif if (sleep_time_us >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP * portTICK_PERIOD_MS * 1000LL) { esp_sleep_enable_timer_wakeup(sleep_time_us - LIGHT_SLEEP_EARLY_WAKEUP_US); #if CONFIG_PM_TRACE && SOC_PM_SUPPORT_RTC_PERIPH_PD @@ -643,7 +798,7 @@ void IRAM_ATTR vApplicationSleep( TickType_t xExpectedIdleTime ) s_light_sleep_counts++; #endif } - int64_t slept_us = esp_timer_get_time() - sleep_start; + slept_us = esp_timer_get_time() - sleep_start; ESP_PM_TRACE_EXIT(SLEEP, core_id); uint32_t slept_ticks = slept_us / (portTICK_PERIOD_MS * 1000LL); @@ -667,6 +822,9 @@ void IRAM_ATTR vApplicationSleep( TickType_t xExpectedIdleTime ) } other_core_should_skip_light_sleep(core_id); } +#if CONFIG_PM_LIGHT_SLEEP_CALLBACKS + esp_pm_execute_exit_sleep_callbacks(slept_us); +#endif } portEXIT_CRITICAL(&s_switch_lock); }