diff --git a/components/esp32/include/esp_pm.h b/components/esp32/include/esp_pm.h new file mode 100644 index 000000000..3887e77a9 --- /dev/null +++ b/components/esp32/include/esp_pm.h @@ -0,0 +1,179 @@ +// Copyright 2016-2017 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include +#include +#include "esp_err.h" + +// Include SoC-specific definitions. Only ESP32 supported for now. +#include "esp32/pm.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Power management constraints + */ +typedef enum { + /** + * Require CPU frequency to be at the maximum value set via esp_pm_configure. + * Argument is unused and should be set to 0. + */ + ESP_PM_CPU_FREQ_MAX, + /** + * Require APB frequency to be at the maximum value supported by the chip. + * Argument is unused and should be set to 0. + */ + ESP_PM_APB_FREQ_MAX, + /** + * Prevent the system from going into light sleep. + * Argument is unused and should be set to 0. + */ + ESP_PM_NO_LIGHT_SLEEP, +} esp_pm_lock_type_t; + +/** + * @brief Set implementation-specific power management configuration + * @param config pointer to implementation-specific configuration structure (e.g. esp_pm_config_esp32) + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if the configuration values are not correct + * - ESP_ERR_NOT_SUPPORTED if certain combination of values is not supported, + * or if CONFIG_PM_ENABLE is not enabled in sdkconfig + */ +esp_err_t esp_pm_configure(const void* config); + + +/** + * @brief Opaque handle to the power management lock + */ +typedef struct esp_pm_lock* esp_pm_lock_handle_t; + + +/** + * @brief Initialize a lock handle for certain power management parameter + * + * When lock is created, initially it is not taken. + * Call esp_pm_lock_acquire to take the lock. + * + * This function must not be called from an ISR. + * + * @param lock_type Power management constraint which the lock should control + * @param arg argument, value depends on lock_type, see esp_pm_lock_type_t + * @param name arbitrary string identifying the lock (e.g. "wifi" or "spi"). + * Used by the esp_pm_dump_locks function to list existing locks. + * May be set to NULL. If not set to NULL, must point to a string which is valid + * for the lifetime of the lock. + * @param[out] out_handle handle returned from this function. Use this handle when calling + * esp_pm_lock_delete, esp_pm_lock_acquire, esp_pm_lock_release. + * Must not be NULL. + * @return + * - ESP_OK on success + * - ESP_ERR_NO_MEM if the lock structure can not be allocated + * - ESP_ERR_INVALID_ARG if out_handle is NULL or type argument is not valid + * - ESP_ERR_NOT_SUPPORTED if CONFIG_PM_ENABLE is not enabled in sdkconfig + */ +esp_err_t esp_pm_lock_create(esp_pm_lock_type_t lock_type, int arg, + const char* name, esp_pm_lock_handle_t* out_handle); + +/** + * @brief Take a power management lock + * + * Once the lock is taken, power management algorithm will not switch to the + * mode specified in a call to esp_pm_lock_create, or any of the lower power + * modes (higher numeric values of 'mode'). + * + * The lock is recursive, in the sense that if esp_pm_lock_acquire is called + * a number of times, esp_pm_lock_release has to be called the same number of + * times in order to release the lock. + * + * This function may be called from an ISR. + * + * This function is not thread-safe w.r.t. calls to other esp_pm_lock_* + * functions for the same handle. + * + * @param handle handle obtained from esp_pm_lock_create function + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if the handle is invalid + * - ESP_ERR_NOT_SUPPORTED if CONFIG_PM_ENABLE is not enabled in sdkconfig + */ +esp_err_t esp_pm_lock_acquire(esp_pm_lock_handle_t handle); + +/** + * @brief Release the lock taken using esp_pm_lock_acquire. + * + * Call to this functions removes power management restrictions placed when + * taking the lock. + * + * Locks are recursive, so if esp_pm_lock_acquire is called a number of times, + * esp_pm_lock_release has to be called the same number of times in order to + * actually release the lock. + * + * This function may be called from an ISR. + * + * This function is not thread-safe w.r.t. calls to other esp_pm_lock_* + * functions for the same handle. + * + * @param handle handle obtained from esp_pm_lock_create function + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if the handle is invalid + * - ESP_ERR_INVALID_STATE if lock is not acquired + * - ESP_ERR_NOT_SUPPORTED if CONFIG_PM_ENABLE is not enabled in sdkconfig + */ +esp_err_t esp_pm_lock_release(esp_pm_lock_handle_t handle); + +/** + * @brief Delete a lock created using esp_pm_lock + * + * The lock must be released before calling this function. + * + * This function must not be called from an ISR. + * + * @param handle handle obtained from esp_pm_lock_create function + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if the handle argument is NULL + * - ESP_ERR_INVALID_STATE if the lock is still acquired + * - ESP_ERR_NOT_SUPPORTED if CONFIG_PM_ENABLE is not enabled in sdkconfig + */ +esp_err_t esp_pm_lock_delete(esp_pm_lock_handle_t handle); + +/** + * Dump the list of all locks to stderr + * + * This function dumps debugging information about locks created using + * esp_pm_lock_create to an output stream. + * + * This function must not be called from an ISR. If esp_pm_lock_acquire/release + * are called while this function is running, inconsistent results may be + * reported. + * + * @param stream stream to print information to; use stdout or stderr to print + * to the console; use fmemopen/open_memstream to print to a + * string buffer. + * @return + * - ESP_OK on success + * - ESP_ERR_NOT_SUPPORTED if CONFIG_PM_ENABLE is not enabled in sdkconfig + */ +esp_err_t esp_pm_dump_locks(FILE* stream); + + + +#ifdef __cplusplus +} +#endif diff --git a/components/esp32/pm_locks.c b/components/esp32/pm_locks.c new file mode 100644 index 000000000..bbd8f04d6 --- /dev/null +++ b/components/esp32/pm_locks.c @@ -0,0 +1,205 @@ +// Copyright 2016-2017 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include "esp_pm.h" +#include "esp_system.h" +#include "rom/queue.h" +#include "freertos/FreeRTOS.h" +#include "pm_impl.h" +#include "esp_timer.h" +#include "sdkconfig.h" + + +typedef struct esp_pm_lock { + esp_pm_lock_type_t type; /*!< type passed to esp_pm_lock_create */ + int arg; /*!< argument passed to esp_pm_lock_create */ + pm_mode_t mode; /*!< implementation-defined mode for this type of lock*/ + const char* name; /*!< used to identify the lock */ + SLIST_ENTRY(esp_pm_lock) next; /*!< linked list pointer */ + size_t count; /*!< lock count */ + portMUX_TYPE spinlock; /*!< spinlock used when operating on 'count' */ +#ifdef WITH_PROFILING + pm_time_t last_taken; /*!< time what the lock was taken (valid if count > 0) */ + pm_time_t time_held; /*!< total time the lock was taken. + If count > 0, this doesn't include the time since last_taken */ + size_t times_taken; /*!< number of times the lock was ever taken */ +#endif +} esp_pm_lock_t; + + +static const char* s_lock_type_names[] = { + "CPU_FREQ_MAX", + "APB_FREQ_MAX", + "NO_LIGHT_SLEEP" +}; + +/* List of all existing locks, used for esp_pm_dump_locks */ +static SLIST_HEAD(esp_pm_locks_head, esp_pm_lock) s_list = + SLIST_HEAD_INITIALIZER(s_head); +/* Protects the above list */ +static _lock_t s_list_lock; + + +esp_err_t esp_pm_lock_create(esp_pm_lock_type_t lock_type, int arg, + const char* name, esp_pm_lock_handle_t* out_handle) +{ +#ifndef CONFIG_PM_ENABLE + return ESP_ERR_NOT_SUPPORTED; +#endif + + if (out_handle == NULL) { + return ESP_ERR_INVALID_ARG; + } + esp_pm_lock_t* new_lock = (esp_pm_lock_t*) calloc(1, sizeof(*new_lock)); + if (!new_lock) { + return ESP_ERR_NO_MEM; + } + new_lock->type = lock_type; + new_lock->arg = arg; + new_lock->mode = esp_pm_impl_get_mode(lock_type, arg); + new_lock->name = name; + new_lock->spinlock = (portMUX_TYPE) portMUX_INITIALIZER_UNLOCKED; + *out_handle = new_lock; + + _lock_acquire(&s_list_lock); + SLIST_INSERT_HEAD(&s_list, new_lock, next); + _lock_release(&s_list_lock); + return ESP_OK; +} + +esp_err_t esp_pm_lock_delete(esp_pm_lock_handle_t handle) +{ +#ifndef CONFIG_PM_ENABLE + return ESP_ERR_NOT_SUPPORTED; +#endif + + if (handle == NULL) { + return ESP_ERR_INVALID_ARG; + } + + if (handle->count > 0) { + return ESP_ERR_INVALID_STATE; + } + _lock_acquire(&s_list_lock); + SLIST_REMOVE(&s_list, handle, esp_pm_lock, next); + _lock_release(&s_list_lock); + free(handle); + return ESP_OK; +} + +esp_err_t IRAM_ATTR esp_pm_lock_acquire(esp_pm_lock_handle_t handle) +{ +#ifndef CONFIG_PM_ENABLE + return ESP_ERR_NOT_SUPPORTED; +#endif + + if (handle == NULL) { + return ESP_ERR_INVALID_ARG; + } + + portENTER_CRITICAL(&handle->spinlock); + if (handle->count++ == 0) { + pm_time_t now = 0; +#ifdef WITH_PROFILING + now = pm_get_time(); +#endif + esp_pm_impl_switch_mode(handle->mode, MODE_LOCK, now); +#ifdef WITH_PROFILING + handle->last_taken = now; + handle->times_taken++; +#endif + } + portEXIT_CRITICAL(&handle->spinlock); + return ESP_OK; +} + +esp_err_t IRAM_ATTR esp_pm_lock_release(esp_pm_lock_handle_t handle) +{ +#ifndef CONFIG_PM_ENABLE + return ESP_ERR_NOT_SUPPORTED; +#endif + + if (handle == NULL) { + return ESP_ERR_INVALID_ARG; + } + esp_err_t ret = ESP_OK; + portENTER_CRITICAL(&handle->spinlock); + if (handle->count == 0) { + ret = ESP_ERR_INVALID_STATE; + goto out; + } + if (--handle->count == 0) { + pm_time_t now = 0; +#ifdef WITH_PROFILING + now = pm_get_time(); + handle->time_held += now - handle->last_taken; +#endif + esp_pm_impl_switch_mode(handle->mode, MODE_UNLOCK, now); + } +out: + portEXIT_CRITICAL(&handle->spinlock); + return ret; +} + + +esp_err_t esp_pm_dump_locks(FILE* stream) +{ +#ifndef CONFIG_PM_ENABLE + return ESP_ERR_NOT_SUPPORTED; +#endif + +#ifdef WITH_PROFILING + pm_time_t cur_time = pm_get_time(); + pm_time_t cur_time_d100 = cur_time / 100; +#endif // WITH_PROFILING + + _lock_acquire(&s_list_lock); +#ifdef WITH_PROFILING + fprintf(stream, "Time: %lld\n", cur_time); +#endif + + fprintf(stream, "Lock stats:\n"); + esp_pm_lock_t* it; + SLIST_FOREACH(it, &s_list, next) { + portENTER_CRITICAL(&it->spinlock); + if (it->name == NULL) { + fprintf(stream, "lock@%p ", it); + } else { + fprintf(stream, "%-15s ", it->name); + } +#ifdef WITH_PROFILING + pm_time_t time_held = it->time_held; + if (it->count > 0) { + time_held += cur_time - it->last_taken; + } + fprintf(stream, "%10s %3d %3d %9d %9lld %3lld%%\n", + s_lock_type_names[it->type], it->arg, + it->count, it->times_taken, time_held, + (time_held + cur_time_d100 - 1) / cur_time_d100); +#else + fprintf(stream, "%10s %3d %3d\n", s_lock_type_names[it->type], it->arg, it->count); +#endif // WITH_PROFILING + portEXIT_CRITICAL(&it->spinlock); + } + _lock_release(&s_list_lock); +#ifdef WITH_PROFILING + esp_pm_impl_dump_stats(stream); +#endif + return ESP_OK; +} + + diff --git a/components/esp32/test/test_pm.c b/components/esp32/test/test_pm.c new file mode 100644 index 000000000..2214cec59 --- /dev/null +++ b/components/esp32/test/test_pm.c @@ -0,0 +1,12 @@ +#include +#include +#include +#include +#include "unity.h" +#include "esp_pm.h" + + +TEST_CASE("Can dump power management lock stats", "[pm]") +{ + esp_pm_dump_locks(stdout); +}