pm: initial version of power management APIs

This commit is contained in:
Ivan Grokhotkov 2017-09-22 23:29:33 +08:00
parent 4798b7d775
commit 47e3c9dd4b
3 changed files with 396 additions and 0 deletions

View File

@ -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 <stdint.h>
#include <stdbool.h>
#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

205
components/esp32/pm_locks.c Normal file
View File

@ -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 <stdlib.h>
#include <string.h>
#include <sys/lock.h>
#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;
}

View File

@ -0,0 +1,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include "unity.h"
#include "esp_pm.h"
TEST_CASE("Can dump power management lock stats", "[pm]")
{
esp_pm_dump_locks(stdout);
}