Просмотр исходного кода

Merge branch 'bringup/support_callback_mechanism_in_lightsleep_flow' into 'master'

Power Management: support_callback_mechanism_in_lightsleep_flow

Closes WIFI-5936

See merge request espressif/esp-idf!24597
Lou Tian Hao 2 лет назад
Родитель
Сommit
adae54faca

+ 1 - 0
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"

+ 14 - 0
components/esp_hw_support/Kconfig

@@ -176,6 +176,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"

+ 122 - 0
components/esp_hw_support/include/esp_private/sleep_event.h

@@ -0,0 +1,122 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <stdbool.h>
+#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 void (*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
+ *      - ESP_OK on success
+ *      - ESP_ERR_INVALID_ARG if event_id is out of range
+ */
+esp_err_t esp_sleep_execute_event_callbacks(esp_sleep_event_cb_index_t event_id, void *ext_arg);
+
+#ifdef __cplusplus
+}
+#endif

+ 7 - 0
components/esp_hw_support/port/esp32c6/rtc_clk.c

@@ -20,6 +20,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"
@@ -265,6 +266,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();
@@ -276,10 +281,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) {

+ 9 - 0
components/esp_hw_support/port/esp32h2/rtc_clk.c

@@ -21,6 +21,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"
@@ -314,6 +315,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();
@@ -325,10 +330,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) &&
@@ -339,10 +346,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);
     }
 }
 

+ 3 - 1
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;
 }

+ 7 - 0
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

+ 91 - 0
components/esp_hw_support/sleep_event.c

@@ -0,0 +1,91 @@
+/*
+ * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stddef.h>
+#include <string.h>
+
+#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"
+
+#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
+
+#if CONFIG_ESP_SLEEP_EVENT_CALLBACKS
+esp_err_t IRAM_ATTR esp_sleep_execute_event_callbacks(esp_sleep_event_cb_index_t event_id, void *ext_arg)
+{
+    if (event_id >= SLEEP_EVENT_CB_INDEX_NUM) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    esp_sleep_event_cb_config_t *current = g_sleep_event_cbs_config.sleep_event_cb_config[event_id];
+    while (current != NULL) {
+        (current->cb)(current->user_arg, ext_arg);
+        current = current->next;
+    }
+    return ESP_OK;
+}
+#else
+esp_err_t IRAM_ATTR esp_sleep_execute_event_callbacks(esp_sleep_event_cb_index_t event_id, void *ext_arg)
+{
+    return ESP_OK;
+}
+#endif

+ 8 - 1
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"
@@ -762,12 +763,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
@@ -809,6 +812,8 @@ static esp_err_t IRAM_ATTR esp_sleep_start(uint32_t pd_flags, esp_sleep_mode_t m
         rtc_clk_cpu_freq_set_config(&cpu_freq_config);
     }
 
+    esp_sleep_execute_event_callbacks(SLEEP_EVENT_SW_CLK_READY, (void *)0);
+
     if (cpu_freq_config.source == SOC_CPU_CLK_SRC_PLL) {
         // Turn up MSPI speed if switch to PLL
         mspi_timing_change_speed_mode_cache_safe(false);
@@ -975,6 +980,7 @@ FORCE_INLINE_ATTR bool can_power_down_vddsdio(uint32_t pd_flags, const uint32_t
 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;
 
@@ -1012,9 +1018,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();
 
 #if CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION && CONFIG_PM_SLP_IRAM_OPT
@@ -1196,6 +1202,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;
 }

+ 14 - 0
components/esp_pm/Kconfig

@@ -152,4 +152,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"

+ 58 - 1
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

+ 162 - 1
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"
 
@@ -203,6 +204,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;
@@ -629,6 +770,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 */
@@ -644,7 +799,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,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);