pebble/src/fw/freertos_application.c
2025-01-27 11:38:16 -08:00

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