mirror of
https://github.com/google/pebble.git
synced 2025-05-01 15:51:40 -04:00
298 lines
12 KiB
C
298 lines
12 KiB
C
/*
|
|
* Copyright 2024 Google LLC
|
|
*
|
|
* 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 "debug/power_tracking.h"
|
|
#include "drivers/mcu.h"
|
|
#include "drivers/rtc.h"
|
|
#include "drivers/task_watchdog.h"
|
|
|
|
#include "kernel/memory_layout.h"
|
|
#include "kernel/pbl_malloc.h"
|
|
#include "os/tick.h"
|
|
#include "kernel/util/stop.h"
|
|
#include "kernel/util/wfi.h"
|
|
#include "process_management/worker_manager.h"
|
|
#include "services/common/analytics/analytics.h"
|
|
#include "system/logging.h"
|
|
#include "util/math.h"
|
|
|
|
#define STM32F2_COMPATIBLE
|
|
#define STM32F4_COMPATIBLE
|
|
#define STM32F7_COMPATIBLE
|
|
#include <mcu.h>
|
|
|
|
#include "FreeRTOS.h"
|
|
#include "task.h"
|
|
#include "freertos_application.h"
|
|
|
|
static uint64_t s_analytics_device_sleep_cpu_cycles = 0;
|
|
static RtcTicks s_analytics_device_stop_ticks = 0;
|
|
|
|
static uint64_t s_analytics_app_sleep_cpu_cycles = 0;
|
|
static RtcTicks s_analytics_app_stop_ticks = 0;
|
|
|
|
// We need different timings for our different platforms since we use different mechanisms to keep
|
|
// time and to wake us up out of stop mode. On stm32f2 we don't have a millisecond register so we
|
|
// use the "retina rtc" and a RTC Alarm peripheral. On stm32f4 we do have a millisecond register
|
|
// so use the RTC running at normal speed and a RTC Wakeup peripheral. These have different
|
|
// accuracies when going into and out of stop mode.
|
|
#if defined(MICRO_FAMILY_STM32F2)
|
|
//! Stop mode until this number of ticks before the next scheduled task
|
|
static const RtcTicks EARLY_WAKEUP_TICKS = 2;
|
|
// slightly larger than the 2 permitted by FreeRTOS in tasks.c
|
|
static const RtcTicks MIN_STOP_TICKS = 5;
|
|
#elif defined(MICRO_FAMILY_STM32F4) || defined(MICRO_FAMILY_STM32F7)
|
|
//! Stop mode until this number of ticks before the next scheduled task
|
|
static const RtcTicks EARLY_WAKEUP_TICKS = 4;
|
|
//! Stop mode until this number of ticks before the next scheduled task
|
|
static const RtcTicks MIN_STOP_TICKS = 8;
|
|
#endif
|
|
|
|
|
|
// 1024 ticks so that we only wake up once every regular timer interval.
|
|
static const RtcTicks MAX_STOP_TICKS = 1024;
|
|
|
|
extern void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ) {
|
|
if (!rtc_alarm_is_initialized() || !sleep_mode_is_allowed()) {
|
|
// the RTC is not yet initialized to the point where it can wake us from sleep or sleep/stop
|
|
// is disabled. Just returning will cause a busy loop where the caller thought we slept for
|
|
// 0 ticks and will reevaluate what to do next (probably just try again).
|
|
return;
|
|
}
|
|
|
|
// Note: all tasks are suspended at this point, but we can still be interrupted
|
|
// so the critical section is necessary. taskENTER_CRITICAL() is not used here
|
|
// as that method would mask interrupts that should exit the low-power mode.
|
|
// The __disable_irq() function sets the PRIMASK bit which globally prevents
|
|
// interrupt execution while still allowing interrupts to wake the processor
|
|
// from WFI.
|
|
// Conversely, taskEnter_CRITICAL() sets the BASEPRI register, which masks
|
|
// interrupts with priorities lower than configMAX_SYSCALL_INTERRUPT_PRIORITY
|
|
// from executing and from waking the processor.
|
|
// See: http://infocenter.arm.com/help/topic/com.arm.doc.dui0552a/BABGGICD.html#BGBHDHAI
|
|
__disable_irq();
|
|
|
|
power_tracking_stop(PowerSystemMcuCoreRun);
|
|
|
|
if (eTaskConfirmSleepModeStatus() != eAbortSleep) {
|
|
if (xExpectedIdleTime < MIN_STOP_TICKS || !stop_mode_is_allowed()) {
|
|
// We assume that a WFI to trigger sleep mode will not last longer than 1
|
|
// SysTick. (The SysTick INT doesn't automatically get suppressed) Thus,
|
|
// we use the SysTick timer to get a better estimate of our sleep time
|
|
//
|
|
// TODO: It would be nice if there was a clean way to actually 'suppress
|
|
// ticks' while in sleep mode. If we figure that out, we would likely
|
|
// need to update how this calculation works
|
|
uint32_t systick_start = SysTick->VAL;
|
|
|
|
power_tracking_start(PowerSystemMcuCoreSleep);
|
|
__DSB(); // Drain any pending memory writes before entering sleep.
|
|
do_wfi(); // Wait for Interrupt (enter sleep mode). Work around F2/F4 errata.
|
|
__ISB(); // Let the pipeline catch up (force the WFI to activate before moving on).
|
|
power_tracking_stop(PowerSystemMcuCoreSleep);
|
|
|
|
uint32_t systick_stop = SysTick->VAL;
|
|
uint32_t cycles_elapsed;
|
|
if (systick_stop < systick_start) {
|
|
cycles_elapsed = systick_start - systick_stop;
|
|
} else {
|
|
cycles_elapsed = (SysTick->LOAD - systick_stop) + systick_start;
|
|
}
|
|
|
|
s_analytics_device_sleep_cpu_cycles += cycles_elapsed;
|
|
s_analytics_app_sleep_cpu_cycles += cycles_elapsed;
|
|
} else {
|
|
const RtcTicks stop_duration = MIN(xExpectedIdleTime - EARLY_WAKEUP_TICKS, MAX_STOP_TICKS);
|
|
|
|
// Go into stop mode until the wakeup_tick.
|
|
rtc_alarm_set(stop_duration);
|
|
enter_stop_mode();
|
|
|
|
RtcTicks ticks_elapsed = rtc_alarm_get_elapsed_ticks();
|
|
vTaskStepTick(ticks_elapsed);
|
|
|
|
// Update the task watchdog every time we come out of STOP mode (which is
|
|
// at least once/second) since the timer peripheral will not have been
|
|
// incremented
|
|
task_watchdog_step_elapsed_time_ms((ticks_elapsed * 1000) / RTC_TICKS_HZ);
|
|
|
|
s_analytics_device_stop_ticks += ticks_elapsed;
|
|
s_analytics_app_stop_ticks += ticks_elapsed;
|
|
}
|
|
}
|
|
|
|
power_tracking_start(PowerSystemMcuCoreRun);
|
|
|
|
__enable_irq();
|
|
}
|
|
|
|
void vApplicationStackOverflowHook(TaskHandle_t task_handle, signed char *name) {
|
|
PebbleTask task = pebble_task_get_task_for_handle(task_handle);
|
|
|
|
// If the task is application or worker, ignore this hook. We have a memory protection region
|
|
// setup at the bottom of those stacks and the code that catches MPU violiations to that
|
|
// area in fault_handling.c has the logic to safely kill those user tasks without forcing
|
|
// a reboot.
|
|
if ((task != PebbleTask_App) && (task != PebbleTask_Worker)) {
|
|
PBL_LOG_SYNC(LOG_LEVEL_ERROR, "Stack overflow [task: %s]", name);
|
|
RebootReason reason = {
|
|
.code = RebootReasonCode_StackOverflow,
|
|
.data8[0] = task
|
|
};
|
|
reboot_reason_set(&reason);
|
|
|
|
reset_due_to_software_failure();
|
|
}
|
|
}
|
|
|
|
bool xApplicationIsAllowedToRaisePrivilege(uint32_t caller_pc) {
|
|
// This function is called by portSVCHandler with the PC value of the
|
|
// function which initiated the SVC call requesting privilege elevation.
|
|
|
|
// The memory_region.c functions are not used for this check as this function
|
|
// is in a hot code-path and needs to execute as quickly as possible.
|
|
|
|
// All syscall functions are lumped together in one place in the firmware
|
|
// image to reduce the attack surface. Don't allow privilege to be raised by
|
|
// any code outside of that region, even if that code is in flash.
|
|
// See WHT-114 and PBL-34044.
|
|
extern const uint32_t __syscall_text_start__[];
|
|
extern const uint32_t __syscall_text_end__[];
|
|
const uint32_t priv_code_start = (uint32_t) __syscall_text_start__;
|
|
const uint32_t priv_code_end = (uint32_t) __syscall_text_end__;
|
|
return (caller_pc >= priv_code_start && caller_pc < priv_code_end);
|
|
}
|
|
|
|
#undef vPortFree
|
|
void vPortFree(void* pv) {
|
|
kernel_free(pv);
|
|
}
|
|
|
|
#undef pvPortMalloc
|
|
void* pvPortMalloc(size_t xSize) {
|
|
return kernel_malloc(xSize);
|
|
}
|
|
|
|
// Called from the SysTick handler ISR to adjust ticks for situations where the CPU might
|
|
// occasionally fall behind and miss some tick interrupts (like when running under emulation).
|
|
bool vPortCorrectTicks(void) {
|
|
static uint8_t s_check_counter = 0;
|
|
static int64_t s_rtc_ticks_to_rtos_ticks = 0;
|
|
|
|
if (++s_check_counter < 10) {
|
|
// Just check occasionally so we don't incur the overhead of reading the RTC on every
|
|
// systick
|
|
return false;
|
|
}
|
|
s_check_counter = 0;
|
|
|
|
// Compute what ticks should be based on the real time clock.
|
|
time_t seconds;
|
|
uint16_t milliseconds;
|
|
rtc_get_time_ms(&seconds, &milliseconds);
|
|
int64_t rtc_ticks = ((((int64_t)seconds * 1000) + milliseconds) * RTC_TICKS_HZ) / 1000;
|
|
uint32_t target_rtos_ticks = rtc_ticks + s_rtc_ticks_to_rtos_ticks;
|
|
uint32_t act_ticks = xTaskGetTickCountFromISR();
|
|
|
|
if (act_ticks > target_rtos_ticks + 100 || act_ticks < target_rtos_ticks - 100) {
|
|
// If we are too far out of range of the target ticks, just reset our offsets. This could
|
|
// be caused either by the RTC time being changed or by staying in the debugger too long
|
|
s_rtc_ticks_to_rtos_ticks = (int64_t)act_ticks - rtc_ticks;
|
|
return false;
|
|
} else if (act_ticks >= target_rtos_ticks) {
|
|
// No correction needed
|
|
return false;
|
|
}
|
|
|
|
// Let's advance the RTOS ticks until we catch up
|
|
bool need_context_switch = false;
|
|
while (act_ticks < target_rtos_ticks) {
|
|
/* Increment the RTOS ticks. */
|
|
need_context_switch |= (xTaskIncrementTick() != 0);
|
|
act_ticks++;
|
|
}
|
|
return need_context_switch;
|
|
}
|
|
|
|
|
|
// CPU analytics
|
|
///////////////////////////////////////////////////////////
|
|
|
|
static uint32_t s_last_ticks = 0;
|
|
void dump_current_runtime_stats(void) {
|
|
uint32_t stop_ms = ticks_to_milliseconds(s_analytics_device_stop_ticks);
|
|
uint32_t sleep_ms = mcu_cycles_to_milliseconds(s_analytics_device_sleep_cpu_cycles);
|
|
|
|
uint32_t now_ticks = rtc_get_ticks();
|
|
uint32_t running_ms =
|
|
ticks_to_milliseconds(now_ticks - s_last_ticks) - stop_ms - sleep_ms;
|
|
|
|
uint32_t tot_time = running_ms + sleep_ms + stop_ms;
|
|
|
|
char buf[80];
|
|
dbgserial_putstr_fmt(buf, sizeof(buf), "Run: %"PRIu32" ms (%"PRIu32" %%)",
|
|
running_ms, (running_ms * 100) / tot_time);
|
|
dbgserial_putstr_fmt(buf, sizeof(buf), "Sleep: %"PRIu32" ms (%"PRIu32" %%)",
|
|
sleep_ms, (sleep_ms * 100) / tot_time);
|
|
dbgserial_putstr_fmt(buf, sizeof(buf), "Stop: %"PRIu32" ms (%"PRIu32" %%)",
|
|
stop_ms, (stop_ms * 100) / tot_time);
|
|
dbgserial_putstr_fmt(buf, sizeof(buf), "Tot: %"PRIu32" ms", tot_time);
|
|
}
|
|
|
|
void analytics_external_collect_cpu_stats(void) {
|
|
uint32_t stop_ms = ticks_to_milliseconds(s_analytics_device_stop_ticks);
|
|
uint32_t sleep_ms = mcu_cycles_to_milliseconds(s_analytics_device_sleep_cpu_cycles);
|
|
|
|
analytics_set(ANALYTICS_DEVICE_METRIC_CPU_STOP_TIME, stop_ms, AnalyticsClient_System);
|
|
analytics_set(ANALYTICS_DEVICE_METRIC_CPU_SLEEP_TIME, sleep_ms, AnalyticsClient_System);
|
|
|
|
uint32_t now_ticks = rtc_get_ticks();
|
|
uint32_t ms_running =
|
|
ticks_to_milliseconds(now_ticks - s_last_ticks) - stop_ms - sleep_ms;
|
|
analytics_set(ANALYTICS_DEVICE_METRIC_CPU_RUNNING_TIME, ms_running, AnalyticsClient_System);
|
|
|
|
s_last_ticks = now_ticks;
|
|
s_analytics_device_sleep_cpu_cycles = 0;
|
|
s_analytics_device_stop_ticks = 0;
|
|
}
|
|
|
|
void analytics_external_collect_app_cpu_stats(void) {
|
|
static uint32_t s_last_ticks = 0;
|
|
|
|
uint32_t sleep_ms = mcu_cycles_to_milliseconds(s_analytics_app_sleep_cpu_cycles);
|
|
|
|
uint32_t now_ticks = rtc_get_ticks();
|
|
uint32_t stop_ms = ticks_to_milliseconds(s_analytics_app_stop_ticks);
|
|
uint32_t awake_ms = ticks_to_milliseconds(now_ticks - s_last_ticks) - stop_ms - sleep_ms;
|
|
|
|
analytics_set(ANALYTICS_APP_METRIC_CPU_RUNNING_TIME, awake_ms, AnalyticsClient_App);
|
|
analytics_set(ANALYTICS_APP_METRIC_CPU_SLEEP_TIME,
|
|
mcu_cycles_to_milliseconds(s_analytics_app_sleep_cpu_cycles),
|
|
AnalyticsClient_App);
|
|
analytics_set(ANALYTICS_APP_METRIC_CPU_STOP_TIME, stop_ms, AnalyticsClient_App);
|
|
|
|
// NOTE: When we are running, we can't really tell how much of the time was spent in each task, so
|
|
// the best we can do as attribute the elapsed running time to both the foreground and background worker
|
|
if (worker_manager_get_current_worker_md() != NULL) {
|
|
analytics_set(ANALYTICS_APP_METRIC_BG_CPU_RUNNING_TIME, awake_ms, AnalyticsClient_Worker);
|
|
analytics_set(ANALYTICS_APP_METRIC_BG_CPU_SLEEP_TIME, sleep_ms, AnalyticsClient_Worker);
|
|
analytics_set(ANALYTICS_APP_METRIC_BG_CPU_STOP_TIME, stop_ms, AnalyticsClient_Worker);
|
|
}
|
|
|
|
s_last_ticks = now_ticks;
|
|
s_analytics_app_sleep_cpu_cycles = 0;
|
|
s_analytics_app_stop_ticks = 0;
|
|
}
|