Merge branch 'feature/dfs' into 'master'

Dynamic frequency scaling

See merge request !1189
This commit is contained in:
Ivan Grokhotkov
2017-10-22 12:34:11 +08:00
55 changed files with 1988 additions and 156 deletions

View File

@@ -945,3 +945,68 @@ config ESP32_PHY_MAX_TX_POWER
default ESP32_PHY_MAX_WIFI_TX_POWER
endmenu # PHY
menu "Power Management"
config PM_ENABLE
bool "Support for power management"
default n
help
If enabled, application is compiled with support for power management.
This option has run-time overhead (increased interrupt latency,
longer time to enter idle state), and it also reduces accuracy of
RTOS ticks and timers used for timekeeping.
Enable this option if application uses power management APIs.
config PM_DFS_INIT_AUTO
bool "Enable dynamic frequency scaling (DFS) at startup"
depends on PM_ENABLE
default n
help
If enabled, startup code configures dynamic frequency scaling.
Max CPU frequency is set to CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ setting,
min frequency is set to XTAL frequency.
If disabled, DFS will not be active until the application
configures it using esp_pm_configure function.
config PM_USE_RTC_TIMER_REF
bool "Use RTC timer to prevent time drift (EXPERIMENTAL)"
depends on PM_ENABLE && (ESP32_TIME_SYSCALL_USE_RTC || ESP32_TIME_SYSCALL_USE_RTC_FRC1)
default n
help
When APB clock frequency changes, high-resolution timer (esp_timer)
scale and base value need to be adjusted. Each adjustment may cause
small error, and over time such small errors may cause time drift.
If this option is enabled, RTC timer will be used as a reference to
compensate for the drift.
It is recommended that this option is only used if 32k XTAL is selected
as RTC clock source.
config PM_PROFILING
bool "Enable profiling counters for PM locks"
depends on PM_ENABLE
default n
help
If enabled, esp_pm_* functions will keep track of the amount of time
each of the power management locks has been held, and esp_pm_dump_locks
function will print this information.
This feature can be used to analyze which locks are preventing the chip
from going into a lower power state, and see what time the chip spends
in each power saving mode. This feature does incur some run-time
overhead, so should typically be disabled in production builds.
config PM_TRACE
bool "Enable debug tracing of PM using GPIOs"
depends on PM_ENABLE
default n
help
If enabled, some GPIOs will be used to signal events such as RTOS ticks,
frequency switching, entry/exit from idle state. Refer to pm_trace.c
file for the list of GPIOs.
This feature is intended to be used when analyzing/debugging behavior
of power management implementation, and should be kept disabled in
applications.
endmenu # "Power Management"

View File

@@ -15,10 +15,12 @@
#include <stdint.h>
#include <sys/cdefs.h>
#include <sys/time.h>
#include <sys/param.h>
#include "sdkconfig.h"
#include "esp_attr.h"
#include "esp_log.h"
#include "esp_clk.h"
#include "esp_clk_internal.h"
#include "rom/ets_sys.h"
#include "rom/uart.h"
#include "rom/rtc.h"
@@ -40,14 +42,13 @@
static void select_rtc_slow_clk(rtc_slow_freq_t slow_clk);
// g_ticks_us defined in ROMs for PRO and APP CPU
extern uint32_t g_ticks_per_us_pro;
extern uint32_t g_ticks_per_us_app;
static const char* TAG = "clk";
/*
* This function is not exposed as an API at this point,
* because FreeRTOS doesn't yet support dynamic changing of
* CPU frequency. Also we need to implement hooks for
* components which want to be notified of CPU frequency
* changes.
*/
void esp_clk_init(void)
{
rtc_config_t cfg = RTC_CONFIG_DEFAULT();
@@ -90,10 +91,19 @@ void esp_clk_init(void)
XTHAL_SET_CCOUNT( XTHAL_GET_CCOUNT() * freq_after / freq_before );
}
int IRAM_ATTR esp_clk_cpu_freq(void)
{
return g_ticks_per_us_pro * 1000000;
}
int IRAM_ATTR esp_clk_apb_freq(void)
{
return MIN(g_ticks_per_us_pro, 80) * 1000000;
}
void IRAM_ATTR ets_update_cpu_frequency(uint32_t ticks_per_us)
{
extern uint32_t g_ticks_per_us_pro; // g_ticks_us defined in ROM for PRO CPU
extern uint32_t g_ticks_per_us_app; // same defined for APP CPU
/* Update scale factors used by ets_delay_us */
g_ticks_per_us_pro = ticks_per_us;
g_ticks_per_us_app = ticks_per_us;
}

View File

@@ -23,6 +23,7 @@
#include "esp_panic.h"
#include "esp_partition.h"
#include "esp_clk.h"
#if CONFIG_ESP32_ENABLE_COREDUMP
#define LOG_LOCAL_LEVEL CONFIG_ESP32_CORE_DUMP_LOG_LEVEL
@@ -522,10 +523,11 @@ void esp_core_dump_to_uart(XtExcFrame *frame)
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD_U0TXD);
ESP_COREDUMP_LOGI("Press Enter to print core dump to UART...");
tm_end = xthal_get_ccount() / (XT_CLOCK_FREQ / 1000) + CONFIG_ESP32_CORE_DUMP_UART_DELAY;
const int cpu_ticks_per_ms = esp_clk_cpu_freq() / 1000;
tm_end = xthal_get_ccount() / cpu_ticks_per_ms + CONFIG_ESP32_CORE_DUMP_UART_DELAY;
ch = esp_core_dump_uart_get_char();
while (!(ch == '\n' || ch == '\r')) {
tm_cur = xthal_get_ccount() / (XT_CLOCK_FREQ / 1000);
tm_cur = xthal_get_ccount() / cpu_ticks_per_ms;
if (tm_cur >= tm_end)
break;
ch = esp_core_dump_uart_get_char();

View File

@@ -64,8 +64,10 @@
#include "esp_app_trace.h"
#include "esp_efuse.h"
#include "esp_spiram.h"
#include "esp_clk.h"
#include "esp_clk_internal.h"
#include "esp_timer.h"
#include "esp_pm.h"
#include "pm_impl.h"
#include "trax.h"
#define STRINGIFY(s) STRINGIFY2(s)
@@ -287,9 +289,18 @@ void start_cpu0_default(void)
esp_clk_init();
esp_perip_clk_init();
intr_matrix_clear();
#ifndef CONFIG_CONSOLE_UART_NONE
uart_div_modify(CONFIG_CONSOLE_UART_NUM, (rtc_clk_apb_freq_get() << 4) / CONFIG_CONSOLE_UART_BAUDRATE);
#endif
#ifdef CONFIG_PM_ENABLE
const int uart_clk_freq = REF_CLK_FREQ;
/* When DFS is enabled, use REFTICK as UART clock source */
CLEAR_PERI_REG_MASK(UART_CONF0_REG(CONFIG_CONSOLE_UART_NUM), UART_TICK_REF_ALWAYS_ON);
#else
const int uart_clk_freq = APB_CLK_FREQ;
#endif // CONFIG_PM_DFS_ENABLE
uart_div_modify(CONFIG_CONSOLE_UART_NUM, (uart_clk_freq << 4) / CONFIG_CONSOLE_UART_BAUDRATE);
#endif // CONFIG_CONSOLE_UART_NONE
#if CONFIG_BROWNOUT_DET
esp_brownout_init();
#endif
@@ -337,6 +348,18 @@ void start_cpu0_default(void)
spi_flash_init();
/* init default OS-aware flash access critical section */
spi_flash_guard_set(&g_flash_guard_default_ops);
#ifdef CONFIG_PM_ENABLE
esp_pm_impl_init();
#ifdef CONFIG_PM_DFS_INIT_AUTO
rtc_cpu_freq_t max_freq;
rtc_clk_cpu_freq_from_mhz(CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ, &max_freq);
esp_pm_config_esp32_t cfg = {
.max_cpu_freq = max_freq,
.min_cpu_freq = RTC_CPU_FREQ_XTAL
};
esp_pm_configure(&cfg);
#endif //CONFIG_PM_DFS_INIT_AUTO
#endif //CONFIG_PM_ENABLE
#if CONFIG_ESP32_ENABLE_COREDUMP
esp_core_dump_init();

View File

@@ -34,20 +34,25 @@
#include "freertos/portmacro.h"
#define REASON_YIELD (1<<0)
#define REASON_YIELD BIT(0)
#define REASON_FREQ_SWITCH BIT(1)
static portMUX_TYPE reasonSpinlock = portMUX_INITIALIZER_UNLOCKED;
static portMUX_TYPE reason_spinlock = portMUX_INITIALIZER_UNLOCKED;
static volatile uint32_t reason[ portNUM_PROCESSORS ];
/*
ToDo: There is a small chance the CPU already has yielded when this ISR is serviced. In that case, it's running the intended task but
the ISR will cause it to switch _away_ from it. portYIELD_FROM_ISR will probably just schedule the task again, but have to check that.
*/
static void esp_crosscore_isr_handle_yield()
{
portYIELD_FROM_ISR();
}
static void IRAM_ATTR esp_crosscore_isr(void *arg) {
uint32_t myReasonVal;
uint32_t my_reason_val;
//A pointer to the correct reason array item is passed to this ISR.
volatile uint32_t *myReason=arg;
volatile uint32_t *my_reason=arg;
//Clear the interrupt first.
if (xPortGetCoreID()==0) {
@@ -56,43 +61,59 @@ static void IRAM_ATTR esp_crosscore_isr(void *arg) {
DPORT_WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_1_REG, 0);
}
//Grab the reason and clear it.
portENTER_CRITICAL(&reasonSpinlock);
myReasonVal=*myReason;
*myReason=0;
portEXIT_CRITICAL(&reasonSpinlock);
portENTER_CRITICAL(&reason_spinlock);
my_reason_val=*my_reason;
*my_reason=0;
portEXIT_CRITICAL(&reason_spinlock);
//Check what we need to do.
if (myReasonVal&REASON_YIELD) {
portYIELD_FROM_ISR();
if (my_reason_val & REASON_YIELD) {
esp_crosscore_isr_handle_yield();
}
if (my_reason_val & REASON_FREQ_SWITCH) {
/* Nothing to do here; the frequency switch event was already
* handled by a hook in xtensa_vectors.S. Could be used in the future
* to allow DFS features without the extra latency of the ISR hook.
*/
}
}
//Initialize the crosscore interrupt on this core. Call this once
//on each active core.
void esp_crosscore_int_init() {
portENTER_CRITICAL(&reasonSpinlock);
portENTER_CRITICAL(&reason_spinlock);
reason[xPortGetCoreID()]=0;
portEXIT_CRITICAL(&reasonSpinlock);
portEXIT_CRITICAL(&reason_spinlock);
esp_err_t err;
if (xPortGetCoreID()==0) {
err = esp_intr_alloc(ETS_FROM_CPU_INTR0_SOURCE, ESP_INTR_FLAG_IRAM, esp_crosscore_isr, (void*)&reason[xPortGetCoreID()], NULL);
err = esp_intr_alloc(ETS_FROM_CPU_INTR0_SOURCE, ESP_INTR_FLAG_IRAM, esp_crosscore_isr, (void*)&reason[0], NULL);
} else {
err = esp_intr_alloc(ETS_FROM_CPU_INTR1_SOURCE, ESP_INTR_FLAG_IRAM, esp_crosscore_isr, (void*)&reason[xPortGetCoreID()], NULL);
err = esp_intr_alloc(ETS_FROM_CPU_INTR1_SOURCE, ESP_INTR_FLAG_IRAM, esp_crosscore_isr, (void*)&reason[1], NULL);
}
assert(err == ESP_OK);
}
void IRAM_ATTR esp_crosscore_int_send_yield(int coreId) {
assert(coreId<portNUM_PROCESSORS);
static void IRAM_ATTR esp_crosscore_int_send(int core_id, uint32_t reason_mask) {
assert(core_id<portNUM_PROCESSORS);
//Mark the reason we interrupt the other CPU
portENTER_CRITICAL(&reasonSpinlock);
reason[coreId]|=REASON_YIELD;
portEXIT_CRITICAL(&reasonSpinlock);
portENTER_CRITICAL(&reason_spinlock);
reason[core_id] |= reason_mask;
portEXIT_CRITICAL(&reason_spinlock);
//Poke the other CPU.
if (coreId==0) {
if (core_id==0) {
DPORT_WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_0_REG, DPORT_CPU_INTR_FROM_CPU_0);
} else {
DPORT_WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_1_REG, DPORT_CPU_INTR_FROM_CPU_1);
}
}
void IRAM_ATTR esp_crosscore_int_send_yield(int core_id)
{
esp_crosscore_int_send(core_id, REASON_YIELD);
}
void IRAM_ATTR esp_crosscore_int_send_freq_switch(int core_id)
{
esp_crosscore_int_send(core_id, REASON_FREQ_SWITCH);
}

View File

@@ -0,0 +1,39 @@
// Copyright 2015-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
/**
* @file esp_clk_internal.h
*
* Private clock-related functions
*/
/**
* @brief Initialize clock-related settings
*
* Called from cpu_start.c, not intended to be called from other places.
* This function configures the CPU clock, RTC slow and fast clocks, and
* performs RTC slow clock calibration.
*/
void esp_clk_init(void);
/**
* @brief Disables clock of some peripherals
*
* Called from cpu_start.c, not intended to be called from other places.
* This function disables clock of useless peripherals when cpu starts.
*/
void esp_perip_clk_init(void);

View File

@@ -19,6 +19,7 @@
#include "esp_attr.h"
#include "esp_intr_alloc.h"
#include "esp_log.h"
#include "esp_clk.h"
#include "esp_timer_impl.h"
#include "soc/frc_timer_reg.h"
#include "soc/rtc.h"
@@ -112,6 +113,14 @@ static uint32_t s_timer_us_per_overflow;
// will not increment s_time_base_us if this flag is set.
static bool s_mask_overflow;
#ifdef CONFIG_PM_DFS_USE_RTC_TIMER_REF
// If DFS is enabled, upon the first frequency change this value is set to the
// difference between esp_timer value and RTC timer value. On every subsequent
// frequency change, s_time_base_us is adjusted to maintain the same difference
// between esp_timer and RTC timer. (All mentioned values are in microseconds.)
static uint64_t s_rtc_time_diff = 0;
#endif
// Spinlock used to protect access to static variables above and to the hardware
// registers.
portMUX_TYPE s_time_update_lock = portMUX_INITIALIZER_UNLOCKED;
@@ -208,6 +217,55 @@ static void IRAM_ATTR timer_alarm_isr(void *arg)
(*s_alarm_handler)(arg);
}
void IRAM_ATTR esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us)
{
portENTER_CRITICAL(&s_time_update_lock);
/* Bail out if the timer is not initialized yet */
if (s_timer_interrupt_handle == NULL) {
portEXIT_CRITICAL(&s_time_update_lock);
return;
}
uint32_t new_ticks_per_us = apb_ticks_per_us / TIMER_DIV;
uint32_t alarm = REG_READ(FRC_TIMER_ALARM_REG(1));
uint32_t count = REG_READ(FRC_TIMER_COUNT_REG(1));
uint64_t ticks_to_alarm = alarm - count;
uint64_t new_ticks = (ticks_to_alarm * new_ticks_per_us) / s_timer_ticks_per_us;
uint32_t new_alarm_val;
if (alarm > count && new_ticks <= FRC_TIMER_LOAD_VALUE(1)) {
new_alarm_val = new_ticks;
} else {
new_alarm_val = ALARM_OVERFLOW_VAL;
if (alarm != ALARM_OVERFLOW_VAL) {
s_mask_overflow = true;
}
}
REG_WRITE(FRC_TIMER_ALARM_REG(1), new_alarm_val);
REG_WRITE(FRC_TIMER_LOAD_REG(1), 0);
s_time_base_us += count / s_timer_ticks_per_us;
#ifdef CONFIG_PM_DFS_USE_RTC_TIMER_REF
// Due to the extra time required to read RTC time, don't attempt this
// adjustment when switching to a higher frequency (which usually
// happens in an interrupt).
if (new_ticks_per_us < s_timer_ticks_per_us) {
uint64_t rtc_time = esp_clk_rtc_time();
uint64_t new_rtc_time_diff = s_time_base_us - rtc_time;
if (s_rtc_time_diff != 0) {
uint64_t correction = new_rtc_time_diff - s_rtc_time_diff;
s_time_base_us -= correction;
} else {
s_rtc_time_diff = new_rtc_time_diff;
}
}
#endif // CONFIG_PM_DFS_USE_RTC_TIMER_REF
s_timer_ticks_per_us = new_ticks_per_us;
s_timer_us_per_overflow = FRC_TIMER_LOAD_VALUE(1) / new_ticks_per_us;
portEXIT_CRITICAL(&s_time_update_lock);
}
esp_err_t esp_timer_impl_init(intr_handler_t alarm_handler)
{

View File

@@ -51,6 +51,15 @@ void esp_timer_impl_deinit();
*/
void esp_timer_impl_set_alarm(uint64_t timestamp);
/**
* @brief Notify esp_timer implementation that APB frequency has changed
*
* Called by the frequency switching code.
*
* @param apb_ticks_per_us new number of APB clock ticks per microsecond
*/
void esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us);
/**
* @brief Get time, in microseconds, since esp_timer_impl_init was called
* @return timestamp in microseconds

View File

@@ -20,6 +20,10 @@
#include "esp_attr.h"
#include "esp_freertos_hooks.h"
#include "sdkconfig.h"
#include "esp_pm.h"
#include "pm_impl.h"
//We use just a static array here because it's not expected many components will need
//an idle or tick hook.
#define MAX_HOOKS 8
@@ -41,20 +45,21 @@ void IRAM_ATTR esp_vApplicationTickHook()
void esp_vApplicationIdleHook()
{
bool doWait=true;
bool r;
int n;
bool can_go_idle=true;
int core = xPortGetCoreID();
for (n=0; n<MAX_HOOKS; n++) {
if (idle_cb[core][n]!=NULL) {
r=idle_cb[core][n]();
if (!r) doWait=false;
for (int n = 0; n < MAX_HOOKS; n++) {
if (idle_cb[core][n] != NULL && !idle_cb[core][n]()) {
can_go_idle = false;
}
}
if (doWait) {
//Wait for whatever interrupt comes next... this should save some power.
asm("waiti 0");
if (!can_go_idle) {
return;
}
#ifdef CONFIG_PM_ENABLE
esp_pm_impl_idle_hook();
#endif
asm("waiti 0");
}
esp_err_t esp_register_freertos_idle_hook_for_cpu(esp_freertos_idle_cb_t new_idle_cb, UBaseType_t cpuid)

View File

@@ -17,6 +17,7 @@
#include <stddef.h>
#include <string.h>
#include "esp_attr.h"
#include "esp_clk.h"
#include "soc/wdev_reg.h"
#include "freertos/FreeRTOSConfig.h"
#include "xtensa/core-macros.h"
@@ -35,13 +36,21 @@ uint32_t IRAM_ATTR esp_random(void)
* WDEV_RND_REG reads while waiting.
*/
/* This code does not run in a critical section, so CPU frequency switch may
* happens while this code runs (this will not happen in the current
* implementation, but possible in the future). However if that happens,
* the number of cycles spent on frequency switching will certainly be more
* than the number of cycles we need to wait here.
*/
uint32_t cpu_to_apb_freq_ratio = esp_clk_cpu_freq() / esp_clk_apb_freq();
static uint32_t last_ccount = 0;
uint32_t ccount;
uint32_t result = 0;
do {
ccount = XTHAL_GET_CCOUNT();
result ^= REG_READ(WDEV_RND_REG);
} while (ccount - last_ccount < XT_CLOCK_FREQ / APB_CLK_FREQ * 16);
} while (ccount - last_ccount < cpu_to_apb_freq_ratio * 16);
last_ccount = ccount;
return result ^ REG_READ(WDEV_RND_REG);
}

View File

@@ -0,0 +1,42 @@
// 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/rtc.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Power management config for ESP32
*
* Pass a pointer to this structure as an argument to esp_pm_configure function.
*/
typedef struct {
rtc_cpu_freq_t max_cpu_freq; /*!< Maximum CPU frequency to use */
rtc_cpu_freq_t min_cpu_freq; /*!< Minimum CPU frequency to use when no frequency locks are taken */
bool light_sleep_enable; /*!< Enter light sleep when no locks are taken */
} esp_pm_config_esp32_t;
#ifdef __cplusplus
}
#endif

View File

@@ -18,20 +18,8 @@
* @file esp_clk.h
*
* This file contains declarations of clock related functions.
* These functions are used in ESP-IDF components, but should not be considered
* to be part of public API.
*/
/**
* @brief Initialize clock-related settings
*
* Called from cpu_start.c, not intended to be called from other places.
* This function configures the CPU clock, RTC slow and fast clocks, and
* performs RTC slow clock calibration.
*/
void esp_clk_init(void);
/**
* @brief Get the calibration value of RTC slow clock
*
@@ -42,7 +30,6 @@ void esp_clk_init(void);
*/
uint32_t esp_clk_slowclk_cal_get();
/**
* @brief Update the calibration value of RTC slow clock
*
@@ -55,10 +42,34 @@ uint32_t esp_clk_slowclk_cal_get();
void esp_clk_slowclk_cal_set(uint32_t value);
/**
* @brief Disables clock of some peripherals
* @brief Return current CPU clock frequency
* When frequency switching is performed, this frequency may change.
* However it is guaranteed that the frequency never changes with a critical
* section.
*
* Called from cpu_start.c, not intended to be called from other places.
* This function disables clock of useless peripherals when cpu starts.
* @return CPU clock frequency, in Hz
*/
void esp_perip_clk_init(void);
int esp_clk_cpu_freq(void);
/**
* @brief Return current APB clock frequency
*
* When frequency switching is performed, this frequency may change.
* However it is guaranteed that the frequency never changes with a critical
* section.
*
* @return APB clock frequency, in Hz
*/
int esp_clk_apb_freq(void);
/**
* @brief Read value of RTC counter, converting it to microseconds
* @attention The value returned by this function may change abruptly when
* calibration value of RTC counter is updated via esp_clk_slowclk_cal_set
* function. This should not happen unless application calls esp_clk_slowclk_cal_set.
* In ESP-IDF, esp_clk_slowclk_cal_set is only called in startup code.
*
* @return Value or RTC counter, expressed in microseconds
*/
uint64_t esp_clk_rtc_time();

View File

@@ -35,8 +35,20 @@ void esp_crosscore_int_init();
* This is used internally by FreeRTOS in multicore mode
* and should not be called by the user.
*
* @param coreID Core that should do the yielding
* @param core_id Core that should do the yielding
*/
void esp_crosscore_int_send_yield(int coreId);
void esp_crosscore_int_send_yield(int core_id);
#endif
/**
* Send an interrupt to a CPU indicating it should update its
* CCOMPARE1 value due to a frequency switch.
*
* This is used internally when dynamic frequency switching is
* enabled, and should not be called from application code.
*
* @param core_id Core that should update its CCOMPARE1 value
*/
void esp_crosscore_int_send_freq_switch(int core_id);
#endif

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

View File

@@ -18,9 +18,6 @@
* @file esp_timer.h
* @brief microsecond-precision 64-bit timer API, replacement for ets_timer
*
* Not a public header yet. To be moved into include/ directory when it is made
* public.
*
* esp_timer APIs allow components to receive callbacks when a hardware timer
* reaches certain value. The timer provides microsecond accuracy and
* up to 64 bit range. Note that while the timer itself provides microsecond

456
components/esp32/pm_esp32.c Normal file
View File

@@ -0,0 +1,456 @@
// 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 <stdbool.h>
#include <string.h>
#include <sys/param.h>
#include "esp_attr.h"
#include "esp_err.h"
#include "esp_pm.h"
#include "esp_log.h"
#include "esp_crosscore_int.h"
#include "soc/rtc.h"
#include "freertos/FreeRTOS.h"
#include "freertos/xtensa_timer.h"
#include "xtensa/core-macros.h"
#include "pm_impl.h"
#include "pm_trace.h"
#include "esp_timer_impl.h"
#include "esp32/pm.h"
/* CCOMPARE update timeout, in CPU cycles. Any value above ~600 cycles will work
* for the purpose of detecting a deadlock.
*/
#define CCOMPARE_UPDATE_TIMEOUT 1000000
#ifdef CONFIG_PM_PROFILING
#define WITH_PROFILING
#endif
static portMUX_TYPE s_switch_lock = portMUX_INITIALIZER_UNLOCKED;
/* The following state variables are protected using s_switch_lock: */
/* Current sleep mode; When switching, contains old mode until switch is complete */
static pm_mode_t s_mode = PM_MODE_CPU_MAX;
/* True when switch is in progress */
static volatile bool s_is_switching;
/* When switch is in progress, this is the mode we are switching into */
static pm_mode_t s_new_mode = PM_MODE_CPU_MAX;
/* Number of times each mode was locked */
static size_t s_mode_lock_counts[PM_MODE_COUNT];
/* Bit mask of locked modes. BIT(i) is set iff s_mode_lock_counts[i] > 0. */
static uint32_t s_mode_mask;
/* Divider and multiplier used to adjust (ccompare - ccount) duration.
* Only set to non-zero values when switch is in progress.
*/
static uint32_t s_ccount_div;
static uint32_t s_ccount_mul;
/* Indicates to the ISR hook that CCOMPARE needs to be updated on the given CPU.
* Used in conjunction with cross-core interrupt to update CCOMPARE on the other CPU.
*/
static volatile bool s_need_update_ccompare[portNUM_PROCESSORS];
/* When no RTOS tasks are active, these locks are released to allow going into
* a lower power mode. Used by ISR hook and idle hook.
*/
static esp_pm_lock_handle_t s_rtos_lock_handle[portNUM_PROCESSORS];
/* A flag indicating that Idle hook has run on a given CPU;
* Next interrupt on the same CPU will take s_rtos_lock_handle.
*/
static bool s_core_idle[portNUM_PROCESSORS];
/* g_ticks_us defined in ROM for PRO CPU */
extern uint32_t g_ticks_per_us_pro;
/* Lookup table of CPU frequencies to be used in each mode.
* Modified by esp_pm_configure.
*/
rtc_cpu_freq_t s_cpu_freq_by_mode[PM_MODE_COUNT] = {
[PM_MODE_LIGHT_SLEEP] = (rtc_cpu_freq_t) -1, /* unused */
[PM_MODE_APB_MIN] = RTC_CPU_FREQ_XTAL,
[PM_MODE_APB_MAX] = RTC_CPU_FREQ_80M,
[PM_MODE_CPU_MAX] = RTC_CPU_FREQ_80M,
};
/* Lookup table of CPU ticks per microsecond for each RTC_CPU_FREQ_ value.
* Essentially the same as returned by rtc_clk_cpu_freq_value(), but without
* the function call. Not const because XTAL frequency is only known at run time.
*/
static uint32_t s_cpu_freq_to_ticks[] = {
[RTC_CPU_FREQ_XTAL] = 0, /* This is set by esp_pm_impl_init */
[RTC_CPU_FREQ_80M] = 80,
[RTC_CPU_FREQ_160M] = 160,
[RTC_CPU_FREQ_240M] = 240,
[RTC_CPU_FREQ_2M] = 2
};
/* Lookup table of names for each RTC_CPU_FREQ_ value. Used for logging only. */
static const char* s_freq_names[] __attribute__((unused)) = {
[RTC_CPU_FREQ_XTAL] = "XTAL",
[RTC_CPU_FREQ_80M] = "80",
[RTC_CPU_FREQ_160M] = "160",
[RTC_CPU_FREQ_240M] = "240",
[RTC_CPU_FREQ_2M] = "2"
};
/* Whether automatic light sleep is enabled. Currently always false */
static bool s_light_sleep_en = false;
#ifdef WITH_PROFILING
/* Time, in microseconds, spent so far in each mode */
static pm_time_t s_time_in_mode[PM_MODE_COUNT];
/* Timestamp, in microseconds, when the mode switch last happened */
static pm_time_t s_last_mode_change_time;
/* User-readable mode names, used by esp_pm_impl_dump_stats */
static const char* s_mode_names[] = {
"SLEEP",
"APB_MIN",
"APB_MAX",
"CPU_MAX"
};
#endif // WITH_PROFILING
static const char* TAG = "pm_esp32";
static void update_ccompare();
static void do_switch(pm_mode_t new_mode);
static void leave_idle();
static void on_freq_update(uint32_t old_ticks_per_us, uint32_t ticks_per_us);
pm_mode_t esp_pm_impl_get_mode(esp_pm_lock_type_t type, int arg)
{
(void) arg;
if (type == ESP_PM_CPU_FREQ_MAX) {
return PM_MODE_CPU_MAX;
} else if (type == ESP_PM_APB_FREQ_MAX) {
return PM_MODE_APB_MAX;
} else if (type == ESP_PM_NO_LIGHT_SLEEP) {
return PM_MODE_APB_MIN;
} else {
// unsupported mode
abort();
}
}
esp_err_t esp_pm_configure(const void* vconfig)
{
#ifndef CONFIG_PM_ENABLE
return ESP_ERR_NOT_SUPPORTED;
#endif
const esp_pm_config_esp32_t* config = (const esp_pm_config_esp32_t*) vconfig;
if (config->light_sleep_enable) {
return ESP_ERR_NOT_SUPPORTED;
}
rtc_cpu_freq_t min_freq = config->min_cpu_freq;
rtc_cpu_freq_t max_freq = config->max_cpu_freq;
rtc_cpu_freq_t apb_max_freq; /* CPU frequency in APB_MAX mode */
if (max_freq == RTC_CPU_FREQ_240M) {
/* We can't switch between 240 and 80/160 without disabling PLL,
* so use 240MHz CPU frequency when 80MHz APB frequency is requested.
*/
apb_max_freq = RTC_CPU_FREQ_240M;
} else {
/* Otherwise (max CPU frequency is 80MHz or 160MHz), can use 80MHz
* CPU frequency when 80MHz APB frequency is requested.
*/
apb_max_freq = RTC_CPU_FREQ_80M;
}
apb_max_freq = MAX(apb_max_freq, min_freq);
ESP_LOGI(TAG, "Frequency switching config: "
"CPU_MAX: %s, APB_MAX: %s, APB_MIN: %s, Light sleep: %s",
s_freq_names[max_freq],
s_freq_names[apb_max_freq],
s_freq_names[min_freq],
config->light_sleep_enable ? "ENABLED" : "DISABLED");
portENTER_CRITICAL(&s_switch_lock);
s_cpu_freq_by_mode[PM_MODE_CPU_MAX] = max_freq;
s_cpu_freq_by_mode[PM_MODE_APB_MAX] = apb_max_freq;
s_cpu_freq_by_mode[PM_MODE_APB_MIN] = min_freq;
s_light_sleep_en = config->light_sleep_enable;
portEXIT_CRITICAL(&s_switch_lock);
return ESP_OK;
}
static pm_mode_t IRAM_ATTR get_lowest_allowed_mode()
{
/* TODO: optimize using ffs/clz */
if (s_mode_mask >= BIT(PM_MODE_CPU_MAX)) {
return PM_MODE_CPU_MAX;
} else if (s_mode_mask >= BIT(PM_MODE_APB_MAX)) {
return PM_MODE_APB_MAX;
} else if (s_mode_mask >= BIT(PM_MODE_APB_MIN) || !s_light_sleep_en) {
return PM_MODE_APB_MIN;
} else {
return PM_MODE_LIGHT_SLEEP;
}
}
void IRAM_ATTR esp_pm_impl_switch_mode(pm_mode_t mode,
pm_mode_switch_t lock_or_unlock, pm_time_t now)
{
bool need_switch = false;
uint32_t mode_mask = BIT(mode);
portENTER_CRITICAL(&s_switch_lock);
uint32_t count;
if (lock_or_unlock == MODE_LOCK) {
count = ++s_mode_lock_counts[mode];
} else {
count = s_mode_lock_counts[mode]--;
}
if (count == 1) {
if (lock_or_unlock == MODE_LOCK) {
s_mode_mask |= mode_mask;
} else {
s_mode_mask &= ~mode_mask;
}
need_switch = true;
}
pm_mode_t new_mode = s_mode;
if (need_switch) {
new_mode = get_lowest_allowed_mode();
#ifdef WITH_PROFILING
if (s_last_mode_change_time != 0) {
pm_time_t diff = now - s_last_mode_change_time;
s_time_in_mode[s_mode] += diff;
}
s_last_mode_change_time = now;
#endif // WITH_PROFILING
}
portEXIT_CRITICAL(&s_switch_lock);
if (need_switch && new_mode != s_mode) {
do_switch(new_mode);
}
}
/**
* @brief Update clock dividers in esp_timer and FreeRTOS, and adjust CCOMPARE
* values on both CPUs.
* @param old_ticks_per_us old CPU frequency
* @param ticks_per_us new CPU frequency
*/
static void IRAM_ATTR on_freq_update(uint32_t old_ticks_per_us, uint32_t ticks_per_us)
{
uint32_t old_apb_ticks_per_us = MIN(old_ticks_per_us, 80);
uint32_t apb_ticks_per_us = MIN(ticks_per_us, 80);
/* Update APB frequency value used by the timer */
if (old_apb_ticks_per_us != apb_ticks_per_us) {
esp_timer_impl_update_apb_freq(apb_ticks_per_us);
}
/* Calculate new tick divisor */
_xt_tick_divisor = ticks_per_us * 1000000 / XT_TICK_PER_SEC;
int core_id = xPortGetCoreID();
if (s_rtos_lock_handle[core_id] != NULL) {
ESP_PM_TRACE_ENTER(CCOMPARE_UPDATE, core_id);
/* ccount_div and ccount_mul are used in esp_pm_impl_update_ccompare
* to calculate new CCOMPARE value.
*/
s_ccount_div = old_ticks_per_us;
s_ccount_mul = ticks_per_us;
/* Update CCOMPARE value on this CPU */
update_ccompare();
#if portNUM_PROCESSORS == 2
/* Send interrupt to the other CPU to update CCOMPARE value */
int other_core_id = (core_id == 0) ? 1 : 0;
s_need_update_ccompare[other_core_id] = true;
esp_crosscore_int_send_freq_switch(other_core_id);
int timeout = 0;
while (s_need_update_ccompare[other_core_id]) {
if (++timeout == CCOMPARE_UPDATE_TIMEOUT) {
assert(false && "failed to update CCOMPARE, possible deadlock");
}
}
#endif // portNUM_PROCESSORS == 2
s_ccount_mul = 0;
s_ccount_div = 0;
ESP_PM_TRACE_EXIT(CCOMPARE_UPDATE, core_id);
}
}
/**
* Perform the switch to new power mode.
* Currently only changes the CPU frequency and adjusts clock dividers.
* No light sleep yet.
* @param new_mode mode to switch to
*/
static void IRAM_ATTR do_switch(pm_mode_t new_mode)
{
const int core_id = xPortGetCoreID();
do {
portENTER_CRITICAL_ISR(&s_switch_lock);
if (!s_is_switching) {
break;
}
if (s_new_mode <= new_mode) {
portEXIT_CRITICAL_ISR(&s_switch_lock);
return;
}
if (s_need_update_ccompare[core_id]) {
s_need_update_ccompare[core_id] = false;
}
portEXIT_CRITICAL_ISR(&s_switch_lock);
} while (true);
s_new_mode = new_mode;
s_is_switching = true;
portEXIT_CRITICAL_ISR(&s_switch_lock);
rtc_cpu_freq_t old_freq = s_cpu_freq_by_mode[s_mode];
rtc_cpu_freq_t new_freq = s_cpu_freq_by_mode[new_mode];
if (new_freq != old_freq) {
uint32_t old_ticks_per_us = g_ticks_per_us_pro;
uint32_t new_ticks_per_us = s_cpu_freq_to_ticks[new_freq];
bool switch_down = new_ticks_per_us < old_ticks_per_us;
ESP_PM_TRACE_ENTER(FREQ_SWITCH, core_id);
if (switch_down) {
on_freq_update(old_ticks_per_us, new_ticks_per_us);
}
rtc_clk_cpu_freq_set_fast(new_freq);
if (!switch_down) {
on_freq_update(old_ticks_per_us, new_ticks_per_us);
}
ESP_PM_TRACE_EXIT(FREQ_SWITCH, core_id);
}
portENTER_CRITICAL_ISR(&s_switch_lock);
s_mode = new_mode;
s_is_switching = false;
portEXIT_CRITICAL_ISR(&s_switch_lock);
}
/**
* @brief Calculate new CCOMPARE value based on s_ccount_{mul,div}
*
* Adjusts CCOMPARE value so that the interrupt happens at the same time as it
* would happen without the frequency change.
* Assumes that the new_frequency = old_frequency * s_ccount_mul / s_ccount_div.
*/
static void IRAM_ATTR update_ccompare()
{
const uint32_t ccompare_min_cycles_in_future = 1000;
uint32_t ccount = XTHAL_GET_CCOUNT();
uint32_t ccompare = XTHAL_GET_CCOMPARE(XT_TIMER_INDEX);
if ((ccompare - ccompare_min_cycles_in_future) - ccount < UINT32_MAX / 2) {
uint32_t diff = ccompare - ccount;
uint32_t diff_scaled = (diff * s_ccount_mul + s_ccount_div - 1) / s_ccount_div;
if (diff_scaled < _xt_tick_divisor) {
uint32_t new_ccompare = ccount + diff_scaled;
XTHAL_SET_CCOMPARE(XT_TIMER_INDEX, new_ccompare);
}
}
}
static void IRAM_ATTR leave_idle()
{
int core_id = xPortGetCoreID();
if (s_core_idle[core_id]) {
// TODO: possible optimization: raise frequency here first
esp_pm_lock_acquire(s_rtos_lock_handle[core_id]);
s_core_idle[core_id] = false;
}
}
void esp_pm_impl_idle_hook()
{
int core_id = xPortGetCoreID();
uint32_t state = portENTER_CRITICAL_NESTED();
if (!s_core_idle[core_id]) {
esp_pm_lock_release(s_rtos_lock_handle[core_id]);
s_core_idle[core_id] = true;
}
portEXIT_CRITICAL_NESTED(state);
ESP_PM_TRACE_ENTER(IDLE, core_id);
}
void IRAM_ATTR esp_pm_impl_isr_hook()
{
int core_id = xPortGetCoreID();
ESP_PM_TRACE_ENTER(ISR_HOOK, core_id);
#if portNUM_PROCESSORS == 2
if (s_need_update_ccompare[core_id]) {
update_ccompare();
s_need_update_ccompare[core_id] = false;
} else {
leave_idle();
}
#else
leave_idle();
#endif // portNUM_PROCESSORS == 2
ESP_PM_TRACE_EXIT(ISR_HOOK, core_id);
}
#ifdef WITH_PROFILING
void esp_pm_impl_dump_stats(FILE* out)
{
pm_time_t time_in_mode[PM_MODE_COUNT];
portENTER_CRITICAL_ISR(&s_switch_lock);
memcpy(time_in_mode, s_time_in_mode, sizeof(time_in_mode));
pm_time_t last_mode_change_time = s_last_mode_change_time;
pm_mode_t cur_mode = s_mode;
pm_time_t now = pm_get_time();
portEXIT_CRITICAL_ISR(&s_switch_lock);
time_in_mode[cur_mode] += now - last_mode_change_time;
for (int i = 0; i < PM_MODE_COUNT; ++i) {
fprintf(out, "%8s %12lld %2d%%\n",
s_mode_names[i],
time_in_mode[i],
(int) (time_in_mode[i] * 100 / now));
}
}
#endif // WITH_PROFILING
void esp_pm_impl_init()
{
s_cpu_freq_to_ticks[RTC_CPU_FREQ_XTAL] = rtc_clk_xtal_freq_get();
#ifdef CONFIG_PM_TRACE
esp_pm_trace_init();
#endif
ESP_ERROR_CHECK(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "rtos0",
&s_rtos_lock_handle[0]));
ESP_ERROR_CHECK(esp_pm_lock_acquire(s_rtos_lock_handle[0]));
#if portNUM_PROCESSORS == 2
ESP_ERROR_CHECK(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "rtos1",
&s_rtos_lock_handle[1]));
ESP_ERROR_CHECK(esp_pm_lock_acquire(s_rtos_lock_handle[1]));
#endif // portNUM_PROCESSORS == 2
}

117
components/esp32/pm_impl.h Normal file
View File

@@ -0,0 +1,117 @@
// 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
/**
* @file pm_impl.h
*
* This header file defines interface between PM lock functions (pm_locks.c)
* and the chip-specific power management (DFS/light sleep) implementation.
*/
#include "soc/rtc.h"
#include "esp_pm.h"
#include "esp_timer.h"
#include "sdkconfig.h"
/**
* This is an enum of possible power modes supported by the implementation
*/
typedef enum {
PM_MODE_LIGHT_SLEEP,//!< Light sleep
PM_MODE_APB_MIN, //!< Idle (no CPU frequency or APB frequency locks)
PM_MODE_APB_MAX, //!< Maximum APB frequency mode
PM_MODE_CPU_MAX, //!< Maximum CPU frequency mode
PM_MODE_COUNT //!< Number of items
} pm_mode_t;
/**
* @brief Get the mode corresponding to a certain lock
* @param type lock type
* @param arg argument value for this lock (passed to esp_pm_lock_create)
* @return lowest power consumption mode which meets the constraints of the lock
*/
pm_mode_t esp_pm_impl_get_mode(esp_pm_lock_type_t type, int arg);
/**
* If profiling is enabled, this data type will be used to store microsecond
* timestamps.
*/
typedef int64_t pm_time_t;
/**
* See \ref esp_pm_impl_switch_mode
*/
typedef enum {
MODE_LOCK,
MODE_UNLOCK
} pm_mode_switch_t;
/**
* @brief Switch between power modes when lock is taken or released
* @param mode pm_mode_t corresponding to the lock being taken or released,
* as returned by \ref esp_pm_impl_get_mode
* @param lock_or_unlock
* - MODE_LOCK: lock was taken. Implementation needs to make sure
* that the constraints of the lock are met by switching to the
* given 'mode' or any of the higher power ones.
* - MODE_UNLOCK: lock was released. If all the locks for given
* mode are released, and no locks for higher power modes are
* taken, implementation can switch to one of lower power modes.
* @param now timestamp when the lock was taken or released. Passed as
* a minor optimization, so that the implementation does not need to
* call pm_get_time again.
*/
void esp_pm_impl_switch_mode(pm_mode_t mode, pm_mode_switch_t lock_or_unlock, pm_time_t now);
/**
* @brief Call once at startup to initialize pm implementation
*/
void esp_pm_impl_init();
/**
* @brief Hook function for the idle task
* Must be called from the IDLE task on each CPU before entering waiti state.
*/
void esp_pm_impl_idle_hook();
/**
* @brief Hook function for the interrupt dispatcher
* Must be called soon after entering the ISR
*/
void esp_pm_impl_isr_hook();
/**
* @brief Dump the information about time spent in each of the pm modes.
*
* Prints three columns:
* mode name, total time in mode (in microseconds), percentage of time in mode
*
* @param out stream to dump the information to
*/
void esp_pm_impl_dump_stats(FILE* out);
#ifdef CONFIG_PM_PROFILING
#define WITH_PROFILING
#endif
#ifdef WITH_PROFILING
static inline pm_time_t IRAM_ATTR pm_get_time()
{
return esp_timer_get_time();
}
#endif // WITH_PROFILING

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,51 @@
// 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 "pm_trace.h"
#include "driver/gpio.h"
#include "soc/gpio_reg.h"
/* GPIOs to use for tracing of esp_pm events.
* Two entries in the array for each type, one for each CPU.
* Feel free to change when debugging.
*/
static const int DRAM_ATTR s_trace_io[] = {
BIT(4), BIT(5), // ESP_PM_TRACE_IDLE
BIT(16), BIT(17), // ESP_PM_TRACE_TICK
BIT(18), BIT(18), // ESP_PM_TRACE_FREQ_SWITCH
BIT(19), BIT(19), // ESP_PM_TRACE_CCOMPARE_UPDATE
BIT(25), BIT(26), // ESP_PM_TRACE_ISR_HOOK
};
void esp_pm_trace_init()
{
for (size_t i = 0; i < sizeof(s_trace_io)/sizeof(s_trace_io[0]); ++i) {
int io = __builtin_ffs(s_trace_io[i]);
if (io == 0) {
continue;
}
gpio_set_direction(io - 1, GPIO_MODE_OUTPUT);
}
}
void IRAM_ATTR esp_pm_trace_enter(esp_pm_trace_event_t event, int core_id)
{
REG_WRITE(GPIO_OUT_W1TS_REG, s_trace_io[2 * event + core_id]);
}
void IRAM_ATTR esp_pm_trace_exit(esp_pm_trace_event_t event, int core_id)
{
REG_WRITE(GPIO_OUT_W1TC_REG, s_trace_io[2 * event + core_id]);
}

View File

@@ -0,0 +1,44 @@
// 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 "sdkconfig.h"
typedef enum {
ESP_PM_TRACE_IDLE,
ESP_PM_TRACE_TICK,
ESP_PM_TRACE_FREQ_SWITCH,
ESP_PM_TRACE_CCOMPARE_UPDATE,
ESP_PM_TRACE_ISR_HOOK,
ESP_PM_TRACE_TYPE_MAX
} esp_pm_trace_event_t;
void esp_pm_trace_init();
void esp_pm_trace_enter(esp_pm_trace_event_t event, int core_id);
void esp_pm_trace_exit(esp_pm_trace_event_t event, int core_id);
#ifdef CONFIG_PM_TRACE
#define ESP_PM_TRACE_ENTER(event, core_id) \
esp_pm_trace_enter(ESP_PM_TRACE_ ## event, core_id)
#define ESP_PM_TRACE_EXIT(event, core_id) \
esp_pm_trace_exit(ESP_PM_TRACE_ ## event, core_id)
#else // CONFIG_PM_TRACE
#define ESP_PM_TRACE_ENTER(type, core_id) do { (void) core_id; } while(0);
#define ESP_PM_TRACE_EXIT(type, core_id) do { (void) core_id; } while(0);
#endif // CONFIG_PM_TRACE

View File

@@ -3,7 +3,7 @@
#include <time.h>
#include <sys/time.h>
#include "unity.h"
#include "../esp_timer.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
@@ -113,6 +113,7 @@ TEST_CASE("periodic esp_timer produces correct delays", "[esp_timer]")
size_t cur_interval;
int intervals[NUM_INTERVALS];
int64_t t_start;
SemaphoreHandle_t done;
} test_args_t;
void timer_func(void* arg)
@@ -128,6 +129,7 @@ TEST_CASE("periodic esp_timer produces correct delays", "[esp_timer]")
if (p_args->cur_interval == NUM_INTERVALS) {
printf("done\n");
TEST_ESP_OK(esp_timer_stop(p_args->timer));
xSemaphoreGive(p_args->done);
}
}
@@ -137,15 +139,16 @@ TEST_CASE("periodic esp_timer produces correct delays", "[esp_timer]")
esp_timer_create_args_t create_args = {
.callback = &timer_func,
.arg = &args,
.name = "timer1"
.name = "timer1",
};
TEST_ESP_OK(esp_timer_create(&create_args, &timer1));
ref_clock_init();
args.timer = timer1;
args.t_start = ref_clock_get();
args.done = xSemaphoreCreateBinary();
TEST_ESP_OK(esp_timer_start_periodic(timer1, delay_ms * 1000));
vTaskDelay(delay_ms * (NUM_INTERVALS + 1));
TEST_ASSERT(xSemaphoreTake(args.done, delay_ms * NUM_INTERVALS * 2));
TEST_ASSERT_EQUAL_UINT32(NUM_INTERVALS, args.cur_interval);
for (size_t i = 0; i < NUM_INTERVALS; ++i) {
@@ -155,6 +158,7 @@ TEST_CASE("periodic esp_timer produces correct delays", "[esp_timer]")
TEST_ESP_OK( esp_timer_dump(stdout) );
TEST_ESP_OK( esp_timer_delete(timer1) );
vSemaphoreDelete(args.done);
#undef NUM_INTERVALS
}
@@ -370,3 +374,8 @@ TEST_CASE("esp_timer_get_time returns monotonic values", "[esp_timer][ignore]")
vSemaphoreDelete(done_2);
ref_clock_deinit();
}
TEST_CASE("Can dump esp_timer stats", "[esp_timer]")
{
esp_timer_dump(stdout);
}

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);
}

View File

@@ -1,9 +1,9 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
// Copyright 2015-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
@@ -14,10 +14,43 @@
#include <esp_event.h>
#include <esp_wifi.h>
#include "esp_log.h"
#include "esp_wifi_internal.h"
#include "esp_pm.h"
#include "soc/rtc.h"
#ifdef CONFIG_PM_ENABLE
static esp_pm_lock_handle_t s_wifi_modem_sleep_lock;
#endif
esp_err_t esp_wifi_init(const wifi_init_config_t *config)
{
esp_event_set_default_wifi_handlers();
return esp_wifi_init_internal(config);
#ifdef CONFIG_PM_ENABLE
if (s_wifi_modem_sleep_lock == NULL) {
esp_err_t err = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "wifi",
&s_wifi_modem_sleep_lock);
if (err != ESP_OK) {
return err;
}
}
#endif
esp_event_set_default_wifi_handlers();
return esp_wifi_init_internal(config);
}
#ifdef CONFIG_PM_ENABLE
void wifi_apb80m_request(void)
{
assert(s_wifi_modem_sleep_lock);
esp_pm_lock_acquire(s_wifi_modem_sleep_lock);
if (rtc_clk_apb_freq_get() != APB_CLK_FREQ) {
ESP_LOGE(__func__, "WiFi needs 80MHz APB frequency to work, but got %dHz", rtc_clk_apb_freq_get());
}
}
void wifi_apb80m_release(void)
{
assert(s_wifi_modem_sleep_lock);
esp_pm_lock_release(s_wifi_modem_sleep_lock);
}
#endif //CONFIG_PM_ENABLE