Merge branch 'feat/support_get_pm_lock_aquired_counts_v6.0' into 'release/v6.0'

feat(esp_pm): add APIs to get PM lock statistics / add light sleep tick overflow protection configuration  (v6.0)

See merge request espressif/esp-idf!45885
This commit is contained in:
Jiang Jiang Jian
2026-03-20 10:13:48 +08:00
8 changed files with 394 additions and 7 deletions
+38
View File
@@ -241,6 +241,44 @@ menu "Power Management"
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
config PM_LIGHTSLEEP_TICK_OVERFLOW_PROTECTION
bool "Enable light sleep tick overflow protection"
depends on FREERTOS_USE_TICKLESS_IDLE
default n
help
Limits tick compensation after light sleep wakeup to prevent vTaskStepTick() assertion
failure when actual wakeup overhead exceeds estimation (due to cache misses, CPU
frequency changes, or flash latency variations).
When enabled:
- Silently limits slept_ticks to xExpectedIdleTime when oversleep is within tolerance
(configured by PM_LIGHTSLEEP_TICK_OVERFLOW_TOLERANCE), preventing assertion failure
- Does not limit when oversleep exceeds tolerance (may indicate a bug), assertion failure may occur
- May lose ticks in rare cases, causing xTickCount to lag behind esp_timer
When disabled (default):
- Accurate tick compensation, better precision
- May trigger assertion failure and system crash if overslept
Keep disabled by default to maintain RTOS tick accuracy. Enable only when you experience
assertion failures related to vTaskStepTick() and can accept slight inaccuracy of RTOS
tick time compared to real time.
config PM_LIGHTSLEEP_TICK_OVERFLOW_TOLERANCE
int "Light sleep tick overflow tolerance"
depends on PM_LIGHTSLEEP_TICK_OVERFLOW_PROTECTION
default 2
range 1 10
help
Maximum number of ticks that can be overslept before triggering.
When oversleep is within this tolerance, the system silently limits slept_ticks to
prevent assertion failure. When oversleep exceeds this tolerance, assertion failure
may occur and system may crash.
Higher values provide more tolerance for estimation variance but may hide real issues
and cause more tick loss, leading to greater RTOS tick time inaccuracy compared to real time.
Lower values provide better RTOS tick accuracy but may cause false positives.
config PM_WORKAROUND_FREQ_LIMIT_ENABLED
bool
default y if SPI_FLASH_FREQ_LIMIT_C5_240MHZ
+49 -1
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -196,6 +196,54 @@ esp_err_t esp_pm_lock_delete(esp_pm_lock_handle_t handle);
*/
esp_err_t esp_pm_dump_locks(FILE* stream);
/**
* @brief Structure to store PM lock statistics for each lock type
*/
typedef struct {
size_t created; /*!< Number of locks of this type that have been created */
size_t acquired; /*!< Total number of times locks of this type have been acquired */
} esp_pm_lock_stats_t;
/**
* @brief Structure to store statistics for a single PM lock instance
*/
typedef struct {
size_t acquired; /*!< Current reference count of the lock (number of times it has been acquired without corresponding release) */
#ifdef CONFIG_PM_PROFILING
size_t times_taken; /*!< Number of times the lock has been taken (from not held to held state) */
int64_t time_held; /*!< Total time the lock has been held (in microseconds) */
#endif
} esp_pm_lock_instance_stats_t;
/**
* @brief Get statistics for all PM lock types
*
* This function returns the number of locks created for each lock type
* and the total number of times locks of each type have been acquired.
*
* @param stats pointer to array of esp_pm_lock_stats_t with ESP_PM_LOCK_MAX elements
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG if stats pointer is invalid
* - ESP_ERR_NOT_SUPPORTED if CONFIG_PM_ENABLE is not enabled in sdkconfig
*/
esp_err_t esp_pm_get_lock_stats_all(esp_pm_lock_stats_t stats[ESP_PM_LOCK_MAX]);
/**
* @brief Get statistics for a single PM lock instance
*
* This function returns statistics for a specific lock instance,
* including the number of times it has been acquired and released.
*
* @param handle handle of the lock to get statistics for
* @param stats pointer to esp_pm_lock_instance_stats_t structure to fill
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG if handle or stats pointer is invalid
* - ESP_ERR_NOT_SUPPORTED if CONFIG_PM_ENABLE is not enabled in sdkconfig
*/
esp_err_t esp_pm_lock_get_stats(esp_pm_lock_handle_t handle, esp_pm_lock_instance_stats_t *stats);
#if CONFIG_PM_LIGHT_SLEEP_CALLBACKS
/**
* @brief Function prototype for light sleep callback functions (if CONFIG_FREERTOS_USE_TICKLESS_IDLE)
+15 -3
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2016-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -8,6 +8,7 @@
#include <stdbool.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>
#include <sys/lock.h>
#include <sys/param.h>
#include <assert.h>
@@ -834,10 +835,21 @@ static inline void IRAM_ATTR other_core_should_skip_light_sleep(int core_id)
}
// Adjust RTOS tick count based on the amount of time spent in sleep.
FORCE_INLINE_ATTR void pm_step_tick(int64_t slept_us)
FORCE_INLINE_ATTR void pm_step_tick(int64_t slept_us, TickType_t xExpectedIdleTime)
{
uint32_t slept_ticks = slept_us / (portTICK_PERIOD_MS * 1000LL);
if (slept_ticks) {
#if CONFIG_PM_LIGHTSLEEP_TICK_OVERFLOW_PROTECTION
/* Limit slept_ticks when oversleep is within tolerance to prevent assertion failure */
if ((slept_ticks > xExpectedIdleTime) &&
(slept_ticks <= (xExpectedIdleTime + CONFIG_PM_LIGHTSLEEP_TICK_OVERFLOW_TOLERANCE))) {
slept_ticks = xExpectedIdleTime;
}
#endif // CONFIG_PM_LIGHTSLEEP_TICK_OVERFLOW_PROTECTION
if (slept_ticks > xExpectedIdleTime) {
ESP_EARLY_LOGE(TAG, "Light sleep overslept: expect %"PRIu32" idle ticks but slept %"PRIu32" ticks.",
(uint32_t)xExpectedIdleTime, slept_ticks);
}
/* Adjust RTOS tick count based on the amount of time spent in sleep */
vTaskStepTick(slept_ticks);
@@ -886,7 +898,7 @@ void vApplicationSleep( TickType_t xExpectedIdleTime )
// In this case, there is no need to call vTaskStepTick, because the OS tick count will
// automatically catch up in the next systick interrupt handler.
if (err == ESP_OK) {
pm_step_tick(slept_us);
pm_step_tick(slept_us, xExpectedIdleTime);
}
other_core_should_skip_light_sleep(core_id);
#ifdef WITH_PROFILING
+68 -2
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -147,6 +147,72 @@ out:
return ret;
}
esp_err_t esp_pm_get_lock_stats_all(esp_pm_lock_stats_t stats[ESP_PM_LOCK_MAX])
{
#ifndef CONFIG_PM_ENABLE
return ESP_ERR_NOT_SUPPORTED;
#endif
if (stats == NULL) {
return ESP_ERR_INVALID_ARG;
}
// Initialize stats array
for (int i = 0; i < ESP_PM_LOCK_MAX; i++) {
stats[i].created = 0;
stats[i].acquired = 0;
}
_lock_acquire(&s_list_lock);
// Iterate through all locks and accumulate stats
esp_pm_lock_t* it;
SLIST_FOREACH(it, &s_list, next) {
if (it->type < ESP_PM_LOCK_MAX) {
stats[it->type].created++;
// Sum the count of currently held locks
stats[it->type].acquired += it->count;
}
}
_lock_release(&s_list_lock);
return ESP_OK;
}
esp_err_t esp_pm_lock_get_stats(esp_pm_lock_handle_t handle, esp_pm_lock_instance_stats_t *stats)
{
#ifndef CONFIG_PM_ENABLE
return ESP_ERR_NOT_SUPPORTED;
#endif
if (handle == NULL || stats == NULL) {
return ESP_ERR_INVALID_ARG;
}
// Initialize stats structure
stats->acquired = 0;
#ifdef WITH_PROFILING
stats->times_taken = 0;
stats->time_held = 0;
#endif
portENTER_CRITICAL(&handle->spinlock);
stats->acquired = handle->count;
#ifdef WITH_PROFILING
stats->times_taken = handle->times_taken;
stats->time_held = handle->time_held;
// If the lock is currently held, add the time since it was last taken
if (handle->count > 0) {
pm_time_t now = pm_get_time();
stats->time_held += now - handle->last_taken;
}
#endif
portEXIT_CRITICAL(&handle->spinlock);
return ESP_OK;
}
esp_err_t esp_pm_dump_locks(FILE* stream)
{
#ifndef CONFIG_PM_ENABLE
@@ -160,7 +226,7 @@ esp_err_t esp_pm_dump_locks(FILE* stream)
_lock_acquire(&s_list_lock);
#ifdef WITH_PROFILING
fprintf(stream, "Time since bootup: %lld us\n", cur_time);
fprintf(stream, "Time since boot up: %lld us\n", cur_time);
#endif
fprintf(stream, "Lock stats:\n");
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -47,6 +47,154 @@ TEST_CASE("Can dump power management lock stats", "[pm]")
esp_pm_dump_locks(stdout);
}
TEST_CASE("Test get PM lock statistics API", "[pm]")
{
#ifdef CONFIG_PM_ENABLE
esp_pm_lock_stats_t init_stats[ESP_PM_LOCK_MAX], stats[ESP_PM_LOCK_MAX];
// Get initial stats
TEST_ESP_OK(esp_pm_get_lock_stats_all(init_stats));
// Create a few locks of different types
esp_pm_lock_handle_t lock1, lock2, lock3;
TEST_ESP_OK(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "cpu_lock", &lock1));
TEST_ESP_OK(esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "apb_lock", &lock2));
TEST_ESP_OK(esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "sleep_lock", &lock3));
// Check stats after creating locks
TEST_ESP_OK(esp_pm_get_lock_stats_all(stats));
TEST_ASSERT_EQUAL(1, stats[ESP_PM_CPU_FREQ_MAX].created - init_stats[ESP_PM_CPU_FREQ_MAX].created);
TEST_ASSERT_EQUAL(1, stats[ESP_PM_APB_FREQ_MAX].created - init_stats[ESP_PM_APB_FREQ_MAX].created);
TEST_ASSERT_EQUAL(1, stats[ESP_PM_NO_LIGHT_SLEEP].created - init_stats[ESP_PM_NO_LIGHT_SLEEP].created);
// Acquire locks multiple times
TEST_ESP_OK(esp_pm_lock_acquire(lock1));
TEST_ESP_OK(esp_pm_lock_acquire(lock1)); // Acquire again (recursive)
TEST_ESP_OK(esp_pm_lock_acquire(lock2));
// Check stats after acquiring locks
TEST_ESP_OK(esp_pm_get_lock_stats_all(stats));
// Count total held locks (sum of all lock counts)
TEST_ASSERT_EQUAL(2, stats[ESP_PM_CPU_FREQ_MAX].acquired - init_stats[ESP_PM_CPU_FREQ_MAX].acquired); // lock1 acquired twice
TEST_ASSERT_EQUAL(1, stats[ESP_PM_APB_FREQ_MAX].acquired - init_stats[ESP_PM_APB_FREQ_MAX].acquired); // lock2 acquired once
// Release locks
TEST_ESP_OK(esp_pm_lock_release(lock1));
TEST_ESP_OK(esp_pm_lock_release(lock1)); // Release second acquisition
TEST_ESP_OK(esp_pm_lock_release(lock2));
// Delete locks
TEST_ESP_OK(esp_pm_lock_delete(lock1));
TEST_ESP_OK(esp_pm_lock_delete(lock2));
TEST_ESP_OK(esp_pm_lock_delete(lock3));
// Check stats after deleting locks
TEST_ESP_OK(esp_pm_get_lock_stats_all(stats));
TEST_ASSERT_EQUAL(0, stats[ESP_PM_CPU_FREQ_MAX].created - init_stats[ESP_PM_CPU_FREQ_MAX].created);
TEST_ASSERT_EQUAL(0, stats[ESP_PM_APB_FREQ_MAX].created - init_stats[ESP_PM_APB_FREQ_MAX].created);
TEST_ASSERT_EQUAL(0, stats[ESP_PM_NO_LIGHT_SLEEP].created - init_stats[ESP_PM_NO_LIGHT_SLEEP].created);
// Test error cases
// NULL stats pointer
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_pm_get_lock_stats_all(NULL));
#else
// When PM is not enabled, function should return ESP_ERR_NOT_SUPPORTED
esp_pm_lock_stats_t stats[ESP_PM_LOCK_MAX];
TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, esp_pm_get_lock_stats_all(stats));
#endif
}
TEST_CASE("Test get PM lock instance statistics API", "[pm]")
{
#ifdef CONFIG_PM_ENABLE
// Create a lock
esp_pm_lock_handle_t lock;
TEST_ESP_OK(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "test_lock", &lock));
// Get initial stats for the lock
esp_pm_lock_instance_stats_t lock_stats;
TEST_ESP_OK(esp_pm_lock_get_stats(lock, &lock_stats));
TEST_ASSERT_EQUAL(0, lock_stats.acquired);
#if CONFIG_PM_PROFILING
TEST_ASSERT_EQUAL(0, lock_stats.times_taken);
TEST_ASSERT_EQUAL(0, lock_stats.time_held);
#endif
// Acquire the lock multiple times
TEST_ESP_OK(esp_pm_lock_acquire(lock));
TEST_ESP_OK(esp_pm_lock_acquire(lock));
TEST_ESP_OK(esp_pm_lock_acquire(lock));
// Get stats again
TEST_ESP_OK(esp_pm_lock_get_stats(lock, &lock_stats));
TEST_ASSERT_EQUAL(3, lock_stats.acquired);
#if CONFIG_PM_PROFILING
TEST_ASSERT_EQUAL(1, lock_stats.times_taken);
// The time_held should be greater than 0 if the lock is currently held
// We can't predict the exact value, so we just check that it's non-negative
TEST_ASSERT_GREATER_OR_EQUAL(0, lock_stats.time_held);
// Store the time_held value for later comparison
int32_t first_time_held = (int32_t)lock_stats.time_held;
int64_t start_time = esp_timer_get_time();
// Delay for a short period to increase the held time
vTaskDelay(100 / portTICK_PERIOD_MS);
int64_t end_time = esp_timer_get_time();
uint32_t expected_time_held = first_time_held + (uint32_t)(end_time - start_time);
// Get stats again to check that time_held has increased
TEST_ESP_OK(esp_pm_lock_get_stats(lock, &lock_stats));
TEST_ASSERT_GREATER_THAN(first_time_held, lock_stats.time_held);
// Check that time_held is within a reasonable range using TEST_ASSERT_UINT64_WITHIN
TEST_ASSERT_UINT32_WITHIN(10000, (uint32_t)expected_time_held, (uint32_t)lock_stats.time_held); // Allow 10ms tolerance
#endif
// Release the lock once
TEST_ESP_OK(esp_pm_lock_release(lock));
// Get stats again
TEST_ESP_OK(esp_pm_lock_get_stats(lock, &lock_stats));
TEST_ASSERT_EQUAL(2, lock_stats.acquired);
// Release remaining locks
TEST_ESP_OK(esp_pm_lock_release(lock));
TEST_ESP_OK(esp_pm_lock_release(lock));
#if CONFIG_PM_PROFILING
// After releasing all locks, get stats to check time_held is updated correctly
TEST_ESP_OK(esp_pm_lock_get_stats(lock, &lock_stats));
TEST_ASSERT_EQUAL(0, lock_stats.acquired);
// Store the time_held value after releasing all locks
int64_t time_after_release = lock_stats.time_held;
// Delay for a short period
vTaskDelay(100 / portTICK_PERIOD_MS);
// Get stats again - time_held should not change since lock is not held
TEST_ESP_OK(esp_pm_lock_get_stats(lock, &lock_stats));
TEST_ASSERT_EQUAL(time_after_release, lock_stats.time_held);
#endif
// Test error cases
// NULL handle
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_pm_lock_get_stats(NULL, &lock_stats));
// NULL stats pointer
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_pm_lock_get_stats(lock, NULL));
// Clean up
TEST_ESP_OK(esp_pm_lock_delete(lock));
#else
// When PM is not enabled, function should return ESP_ERR_NOT_SUPPORTED
esp_pm_lock_handle_t lock;
esp_pm_lock_instance_stats_t lock_stats;
TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, esp_pm_lock_get_stats(lock, &lock_stats));
#endif
}
#ifdef CONFIG_PM_ENABLE
static void switch_freq(int mhz)
@@ -6,3 +6,4 @@ CONFIG_ESP_TASK_WDT_INIT=n
# SMP FreeRTOS currently does not support power management IDF-4997
CONFIG_FREERTOS_SMP=n
CONFIG_PM_PROFILING=y