diff --git a/components/esp_pm/Kconfig b/components/esp_pm/Kconfig index fbdfab1e3d..1a7de6b123 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 auto 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 7e34b21380..687acb3655 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,62 @@ esp_err_t esp_pm_lock_delete(esp_pm_lock_handle_t handle); */ esp_err_t esp_pm_dump_locks(FILE* stream); +/** + * @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 0cf529912a..3aa17f64ca 100644 --- a/components/esp_pm/pm_impl.c +++ b/components/esp_pm/pm_impl.c @@ -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,146 @@ 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) + */ +typedef struct { + /** + * 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; +} 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 esp_err_t 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) { + enter_current->cb(sleep_time_us, enter_current->arg); + enter_current = enter_current->next; + } + return ESP_OK; +} + +static esp_err_t 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) { + exit_current->cb(sleep_time_us, exit_current->arg); + exit_current = exit_current->next; + } + return ESP_OK; +} +#endif + static esp_err_t esp_pm_sleep_configure(const void *vconfig) { esp_err_t err = ESP_OK; @@ -628,6 +769,20 @@ void IRAM_ATTR vApplicationSleep( TickType_t xExpectedIdleTime ) int64_t wakeup_delay_us = portTICK_PERIOD_MS * 1000LL * xExpectedIdleTime; int64_t sleep_time_us = MIN(wakeup_delay_us, time_until_next_alarm); if (sleep_time_us >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP * portTICK_PERIOD_MS * 1000LL) { + int64_t slept_us = 0; +#if CONFIG_PM_LIGHT_SLEEP_CALLBACKS + if (s_light_sleep_enter_cb_config != NULL && s_light_sleep_enter_cb_config->cb) { + uint32_t cycle = esp_cpu_get_cycle_count(); + esp_err_t err = esp_pm_execute_enter_sleep_callbacks(sleep_time_us); + if (err != ESP_OK) { + portEXIT_CRITICAL(&s_switch_lock); + return; + } + sleep_time_us -= (esp_cpu_get_cycle_count() - cycle) / (esp_clk_cpu_freq() / 1000000ULL); + } + if (sleep_time_us >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP * portTICK_PERIOD_MS * 1000LL) + { +#endif esp_sleep_enable_timer_wakeup(sleep_time_us - LIGHT_SLEEP_EARLY_WAKEUP_US); #if CONFIG_PM_TRACE && SOC_PM_SUPPORT_RTC_PERIPH_PD /* to force tracing GPIOs to keep state */ @@ -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); @@ -666,6 +821,12 @@ void IRAM_ATTR vApplicationSleep( TickType_t xExpectedIdleTime ) #endif } other_core_should_skip_light_sleep(core_id); +#if CONFIG_PM_LIGHT_SLEEP_CALLBACKS + } + if (s_light_sleep_exit_cb_config != NULL && s_light_sleep_exit_cb_config->cb) { + esp_pm_execute_exit_sleep_callbacks(slept_us); + } +#endif } } portEXIT_CRITICAL(&s_switch_lock);