]> granicus.if.org Git - esp-idf/commitdiff
pm: initial version of power management APIs
authorIvan Grokhotkov <ivan@espressif.com>
Fri, 22 Sep 2017 15:29:33 +0000 (23:29 +0800)
committerIvan Grokhotkov <ivan@espressif.com>
Wed, 18 Oct 2017 06:19:16 +0000 (14:19 +0800)
components/esp32/include/esp_pm.h [new file with mode: 0644]
components/esp32/pm_locks.c [new file with mode: 0644]
components/esp32/test/test_pm.c [new file with mode: 0644]

diff --git a/components/esp32/include/esp_pm.h b/components/esp32/include/esp_pm.h
new file mode 100644 (file)
index 0000000..3887e77
--- /dev/null
@@ -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
diff --git a/components/esp32/pm_locks.c b/components/esp32/pm_locks.c
new file mode 100644 (file)
index 0000000..bbd8f04
--- /dev/null
@@ -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;
+}
+
+
diff --git a/components/esp32/test/test_pm.c b/components/esp32/test/test_pm.c
new file mode 100644 (file)
index 0000000..2214cec
--- /dev/null
@@ -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);
+}