Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

794
src/fw/kernel/core_dump.c Normal file
View file

@ -0,0 +1,794 @@
/*
* 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.
*/
/*
* This module contains the core dump logic which writes the core dump to SPI flash. It operates
* under a very limited set of constraints:
* 1.) It can NOT use most FreeRTOS functions
* 2.) It can not use the regular flash driver (because that uses FreeRTOS mutexes)
*
* There is a separate module, core_dump_protocol.c which implements the session endpoint logic for
* fetching the core dump over bluetooth. That module is free to use FreeRTOS, regular flash
* driver, etc.
*/
#include "kernel/core_dump.h"
#include "kernel/core_dump_private.h"
#include "console/dbgserial.h"
#include "kernel/logging_private.h"
#include "kernel/pulse_logging.h"
#include "drivers/flash.h"
#include "drivers/mpu.h"
#include "drivers/spi.h"
#include "drivers/watchdog.h"
#include "drivers/rtc.h"
#include "flash_region/flash_region.h"
#include "kernel/pbl_malloc.h"
#include "mfg/mfg_serials.h"
#include "pebbleos/chip_id.h"
#include "services/common/comm_session/session.h"
#include "system/bootbits.h"
#include "system/passert.h"
#include "system/reset.h"
#include "system/logging.h"
#include "system/version.h"
#include "util/attributes.h"
#include "util/build_id.h"
#include "util/math.h"
#include "util/net.h"
#include "util/size.h"
#include "util/string.h"
#define STM32F2_COMPATIBLE
#define STM32F4_COMPATIBLE
#define STM32F7_COMPATIBLE
#include <mcu.h>
#include "FreeRTOS.h" /* FreeRTOS Kernal Prototypes/Constants. */
#include "task.h" /* FreeRTOS Task Prototypes/Constants. */
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
//! Evaluates to 1 iff execution will use the process stack when returning from
//! the exception.
#define RETURNS_TO_PSP(exc_return) ((exc_return & 0x4) == 0x4)
//! This symbol and its contents are provided by the linker script, see the
//! .note.gnu.build-id section in src/fw/stm32f2xx_flash_fw.ld
extern const ElfExternalNote TINTIN_BUILD_ID;
extern const uint32_t __CCM_RAM_size__[];
extern const uint32_t __DTCM_RAM_size__[];
void cd_flash_init(void);
uint32_t cd_flash_write_bytes(const void* buffer_ptr, uint32_t start_addr, uint32_t buffer_size);
void cd_flash_erase_region(uint32_t start_addr, uint32_t total_bytes);
void cd_flash_read_bytes(void* buffer_ptr, uint32_t start_addr, uint32_t buffer_size);
// ----------------------------------------------------------------------------------------
// Private globals
static uint32_t s_flash_addr; // next address in flash to write to
// Saved registers before we trigger our interrupt: [r0-r12, sp, lr, pc, xpsr]
static ALIGN(4) CoreDumpSavedRegisters s_saved_registers;
static uint32_t s_time_stamp;
static bool s_core_dump_initiated = false;
static bool s_core_dump_is_forced = false;
static bool s_test_force_bus_fault = false; // Used for unit testing
static bool s_test_force_inf_loop = false; // Used for unit testing
static bool s_test_force_assert = false; // Used for unit testing
// List of memory regions to include in the core dump
typedef struct {
void* start;
uint32_t length;
bool word_reads_only; // Some peripherals can only be read 32 bits at a
// time, or you BusFault (maybe). Set this to true
// for memory regions where reads smaller than 32
// bits will fail. The start pointer must also be
// word-aligned.
} MemoryRegion;
// Memory regions to dump
static const MemoryRegion MEMORY_REGIONS_DUMP[] = {
#if MICRO_FAMILY_STM32F2
{ .start = (void *)SRAM_BASE, .length = COREDUMP_RAM_SIZE },
#else
{ .start = (void *)SRAM1_BASE, .length = COREDUMP_RAM_SIZE },
#endif
#if PLATFORM_SNOWY || PLATFORM_SPALDING
{ .start = (void *)CCMDATARAM_BASE, .length = (uint32_t)__CCM_RAM_size__ },
#endif
#if MICRO_FAMILY_STM32F7
{ .start = (void *)RAMDTCM_BASE, .length = (uint32_t)__DTCM_RAM_size__ },
#endif
{ .start = (void *)RCC, .length = sizeof(*RCC) },
{ .start = (void *)&NVIC->ISER, .length = sizeof(NVIC->ISER) }, // Enabled interrupts
{ .start = (void *)&NVIC->ISPR, .length = sizeof(NVIC->ISPR) }, // Pending interrupts
{ .start = (void *)&NVIC->IABR, .length = sizeof(NVIC->IABR) }, // Active interrupts
{ .start = (void *)&NVIC->IP, .length = sizeof(NVIC->IP) }, // Interrupt priorities
{ .start = (void *)RTC, .length = sizeof(*RTC) },
{ .start = (void*)DMA1_BASE, .length = 0xD0, .word_reads_only = true },
{ .start = (void*)DMA2_BASE, .length = 0xD0, .word_reads_only = true },
};
static struct {
RCC_TypeDef rcc;
SPI_TypeDef spi1;
} s_stash_data;
// -------------------------------------------------------------------------------------------------
// Flash driver dual-API.
static bool s_use_cd_flash_driver = true;
static uint32_t prv_flash_write_bytes(const void* buffer_ptr,
uint32_t start_addr, uint32_t buffer_size) {
if (s_use_cd_flash_driver) {
return cd_flash_write_bytes(buffer_ptr, start_addr, buffer_size);
} else {
flash_write_bytes(buffer_ptr, start_addr, buffer_size);
return buffer_size;
}
}
static void prv_flash_erase_region(uint32_t start_addr, uint32_t total_bytes) {
if (s_use_cd_flash_driver) {
cd_flash_erase_region(start_addr, total_bytes);
} else {
uint32_t end = start_addr + total_bytes;
flash_region_erase_optimal_range_no_watchdog(start_addr, start_addr, end, end);
}
}
static void prv_flash_read_bytes(void* buffer_ptr, uint32_t start_addr, uint32_t buffer_size) {
if (s_use_cd_flash_driver) {
cd_flash_read_bytes(buffer_ptr, start_addr, buffer_size);
} else {
flash_read_bytes(buffer_ptr, start_addr, buffer_size);
}
}
// -------------------------------------------------------------------------------------------------
// NOTE: We are explicitly avoiding use of vsniprintf and cohorts to reduce our stack
// requirements
static void prv_debug_str(const char* msg) {
kernel_pbl_log_from_fault_handler(__FILE_NAME__, 0, msg);
}
// -------------------------------------------------------------------------------------------------
// NOTE: We are explicitly avoiding use of vsniprintf and cohorts to reduce our stack
// requirements
static void prv_debug_str_str(const char* msg, const char* s) {
#if PULSE_EVERYWHERE
void *ctx = pulse_logging_log_sync_begin(LOG_LEVEL_ALWAYS, __FILE_NAME__, 0);
pulse_logging_log_sync_append(ctx, msg);
pulse_logging_log_sync_append(ctx, s);
pulse_logging_log_sync_send(ctx);
#else
int max_length = 256;
while (*msg && max_length--) {
dbgserial_putchar(*msg);
++msg;
}
dbgserial_putstr(s);
#endif
}
// -------------------------------------------------------------------------------------------------
// NOTE: We are explicitly avoiding use of vsniprintf and cohorts to reduce our stack
// requirements
static void prv_debug_str_int(const char* msg, uint32_t i) {
char buffer[12];
itoa(i, buffer, sizeof(buffer));
#if PULSE_EVERYWHERE
void *ctx = pulse_logging_log_sync_begin(LOG_LEVEL_ALWAYS, __FILE_NAME__, 0);
pulse_logging_log_sync_append(ctx, msg);
pulse_logging_log_sync_append(ctx, buffer);
pulse_logging_log_sync_send(ctx);
#else
int max_length = 256;
while (*msg && max_length--) {
dbgserial_putchar(*msg);
++msg;
}
dbgserial_putstr(buffer);
#endif
}
static NORETURN prv_reset(void) {
dbgserial_flush();
system_hard_reset();
}
// -----------------------------------------------------------------------------------------
void coredump_assert(int line) {
prv_debug_str_int("CD: assert - line ", line);
boot_bit_set(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED);
prv_reset();
}
// -------------------------------------------------------------------------------------------------
// Stash the flash status registers and peripheral clock state before the flash
// driver messes with them.
static void prv_stash_regions(void) {
memcpy(&s_stash_data.rcc, RCC, sizeof(RCC_TypeDef));
memcpy(&s_stash_data.spi1, SPI1, sizeof(SPI_TypeDef));
}
// -----------------------------------------------------------------------------------------------
// Return the start address of the flash region containing the core dump image. We write the core image to
// different regions in flash to avoid premature burnout of any particular region.
// @param[in] new If true, then return a pointer to a region where a new image can be stored.
// If false, then return the region containing the most recent stored image or
// CORE_DUMP_FLASH_INVALID_ADDR if no image has been written.
// @return flash base address to use
static uint32_t prv_flash_start_address(bool new) {
CoreDumpFlashHeader flash_hdr;
CoreDumpFlashRegionHeader region_hdr;
uint32_t base_address;
unsigned int i;
// ----------------------------------------------------------------------------------
// First, see if the flash header has been put in place
prv_flash_read_bytes(&flash_hdr, CORE_DUMP_FLASH_START, sizeof(flash_hdr));
if (flash_hdr.magic != CORE_DUMP_FLASH_HDR_MAGIC) {
prv_flash_erase_region(CORE_DUMP_FLASH_START, SUBSECTOR_SIZE_BYTES);
flash_hdr = (CoreDumpFlashHeader) {
.magic = CORE_DUMP_FLASH_HDR_MAGIC,
.unformatted = CORE_DUMP_ALL_UNFORMATTED,
};
prv_flash_write_bytes(&flash_hdr, CORE_DUMP_FLASH_START, sizeof(flash_hdr));
}
// If asking for an existing region and no regions have been formatted yet, return not found
if (!new && flash_hdr.unformatted == CORE_DUMP_ALL_UNFORMATTED) {
return CORE_DUMP_FLASH_INVALID_ADDR;
}
// ----------------------------------------------------------------------------------
// Find which region was most recently used (highest last_used value).
uint32_t max_last_used = 0;
int last_used_idx = -1;
for (i=0; i<CORE_DUMP_MAX_IMAGES; i++) {
// Skip if unformatted
if (flash_hdr.unformatted & (1 << i)) {
continue;
}
base_address = core_dump_get_slot_address(i);
prv_flash_read_bytes(&region_hdr, base_address, sizeof(region_hdr));
// Skip if not written correctly or not most recently used
if (region_hdr.magic == CORE_DUMP_FLASH_HDR_MAGIC && region_hdr.last_used > max_last_used) {
max_last_used = region_hdr.last_used;
last_used_idx = i;
}
}
// If simply trying to find most recently used image, return that now.
if (!new) {
if (max_last_used > 0) {
CD_ASSERTN(last_used_idx >= 0);
return core_dump_get_slot_address(last_used_idx);
} else {
return CORE_DUMP_FLASH_INVALID_ADDR;
}
}
// ----------------------------------------------------------------------------------
// We need to write a new image. Find which region to put it in.
// If no regions yet, pick one at random
unsigned int start_idx;
if (max_last_used == 0) {
start_idx = s_time_stamp % CORE_DUMP_MAX_IMAGES;
} else {
// Else, put it into the next region
start_idx = (last_used_idx + 1) % CORE_DUMP_MAX_IMAGES;
}
// Erase the new region and write out the region header
base_address = core_dump_get_slot_address(start_idx);
CD_ASSERTN(base_address + CORE_DUMP_MAX_SIZE <= CORE_DUMP_FLASH_END);
prv_flash_erase_region(base_address, CORE_DUMP_MAX_SIZE);
region_hdr = (CoreDumpFlashRegionHeader) {
.magic = CORE_DUMP_FLASH_HDR_MAGIC,
.last_used = max_last_used + 1,
.unread = true,
};
prv_flash_write_bytes(&region_hdr, base_address, sizeof(region_hdr));
// Clear the unformatted bit in the flash region header
flash_hdr.unformatted &= ~(1 << start_idx);
prv_flash_write_bytes(&flash_hdr, CORE_DUMP_FLASH_START, sizeof(flash_hdr));
return base_address;
}
// -------------------------------------------------------------------------------------------------
// This callback gets called by FreeRTOS for each task during the call to vTaskListWalk.
static void prvTaskInfoCallback( const xPORT_TASK_INFO * const task_info, void * data) {
CoreDumpChunkHeader chunk_hdr;
CoreDumpThreadInfo packed_info;
void *current_task_id = (void *)xTaskGetCurrentTaskHandle();
prv_debug_str_str("CD: Th info ", task_info->pcName);
// Unit testing various types of fault?
if (s_test_force_bus_fault) {
typedef void (*KaboomCallback)(void);
KaboomCallback kaboom = 0;
kaboom();
}
if (s_test_force_inf_loop) {
while (true) ;
}
if (s_test_force_assert) {
PBL_ASSERTN(false);
}
// Create the packed chunk header
strncpy ((char *)packed_info.name, task_info->pcName, CORE_DUMP_THREAD_NAME_SIZE);
packed_info.id = (uint32_t)task_info->taskHandle;
packed_info.running = (current_task_id == task_info->taskHandle);
for (int i = 0; i < portCANONICAL_REG_COUNT; i++) {
// registers [r0-r12, sp, lr, pc, sr]
packed_info.registers[i] = task_info->registers[i];
}
// If this is the current task, adjust the registers based on whether or not we were handling
// an exception at the time core_dump_reset() was called.
if (packed_info.running) {
if (!RETURNS_TO_PSP(s_saved_registers.core_reg[portCANONICAL_REG_INDEX_LR])) {
// The core dump handler got invoked from another exception, therefore the
// running task was interrupted by an exception.
// Get R0-R3, R12, R14, PC, xpsr for the task off the process stack used
// by the task.
// The information for this task is going to be incorrect: the values of
// R4-R11 will be completely bogus. The only way to recover them is to
// properly unwind the full exception stack in a debugger with unwind
// information available. Unfortunately mainline GDB is unable to unwind
// across the MSP/PSP split stack so this incomplete hack is required to
// get useable information.
for (int i = 0; i < portCANONICAL_REG_COUNT; i++) {
// Clear out all of the bogus values in the info
packed_info.registers[i] = 0xa5a5a5a5;
}
uint32_t *sp = (uint32_t *)s_saved_registers.extra_reg.psp;
packed_info.registers[portCANONICAL_REG_INDEX_R0] = sp[0];
packed_info.registers[portCANONICAL_REG_INDEX_R1] = sp[1];
packed_info.registers[portCANONICAL_REG_INDEX_R2] = sp[2];
packed_info.registers[portCANONICAL_REG_INDEX_R3] = sp[3];
packed_info.registers[portCANONICAL_REG_INDEX_R12] = sp[4];
packed_info.registers[portCANONICAL_REG_INDEX_LR] = sp[5];
packed_info.registers[portCANONICAL_REG_INDEX_PC] = sp[6];
packed_info.registers[portCANONICAL_REG_INDEX_XPSR] = sp[7];
// Pop the exception stack frame, taking stack alignment into account.
// The 10th bit of the pushed xPSR indicates whether an alignment word was
// inserted into the stack frame during exception entry in order to make
// sp 8-byte aligned.
// Note that this is going to be wrong if the floating-point registers
// were stacked. The only way to know for sure whether the FP regs were
// pushed during exception entry requires unwinding the ISR stack to
// determine the EXC_RETURN value of the bottom-most ISR.
if (sp[7] & 0x200) {
packed_info.registers[portCANONICAL_REG_INDEX_SP] = (uint32_t)(&sp[9]);
} else {
packed_info.registers[portCANONICAL_REG_INDEX_SP] = (uint32_t)(&sp[8]);
}
} else {
// If current task called core_dump_reset directly, then jam in the
// registers we saved at the beginning.
for (int i = 0; i < portCANONICAL_REG_COUNT; i++) {
// registers [r0-r12, msp, lr, pc, psr]
packed_info.registers[i] = s_saved_registers.core_reg[i];
}
// Set sp to the saved psp so that GDB can unwind the task's stack.
packed_info.registers[portCANONICAL_REG_INDEX_SP] =
s_saved_registers.extra_reg.psp;
}
}
// Write out this thread info
chunk_hdr.key = CORE_DUMP_CHUNK_KEY_THREAD;
chunk_hdr.size = sizeof(packed_info);
s_flash_addr += prv_flash_write_bytes(&chunk_hdr, s_flash_addr,
sizeof(chunk_hdr));
s_flash_addr += prv_flash_write_bytes(&packed_info, s_flash_addr,
chunk_hdr.size);
}
static void prv_write_memory_regions(const MemoryRegion *regions, unsigned int count,
uint32_t flash_base) {
CoreDumpChunkHeader chunk_hdr;
chunk_hdr.key = CORE_DUMP_CHUNK_KEY_MEMORY;
for (unsigned int i = 0; i < count; i++) {
chunk_hdr.size = regions[i].length + sizeof(CoreDumpMemoryHeader);
CD_ASSERTN(s_flash_addr + chunk_hdr.size - flash_base < CORE_DUMP_MAX_SIZE);
s_flash_addr += prv_flash_write_bytes(&chunk_hdr, s_flash_addr,
sizeof(chunk_hdr));
CoreDumpMemoryHeader mem_hdr;
mem_hdr.start = (uint32_t)regions[i].start;
s_flash_addr += prv_flash_write_bytes(&mem_hdr, s_flash_addr,
sizeof(mem_hdr));
if (regions[i].word_reads_only) {
// Copy the memory into a temporary buffer before writing it to flash so
// that we can be sure that the memory is only being accessed by word.
uint32_t temp;
for (uint32_t offset = 0;
offset < regions[i].length;
offset += sizeof(temp)) {
temp = *(volatile uint32_t*)((char *)regions[i].start + offset);
s_flash_addr += prv_flash_write_bytes(&temp, s_flash_addr,
sizeof(temp));
watchdog_feed();
}
} else {
uint32_t bytes_remaining = regions[i].length;
for (uint32_t offset = 0; offset < regions[i].length; offset += SECTOR_SIZE_BYTES) {
uint32_t bytes_to_write = MIN(bytes_remaining, SECTOR_SIZE_BYTES);
s_flash_addr += prv_flash_write_bytes(
(void *) ((uint32_t)regions[i].start + offset),
s_flash_addr, bytes_to_write);
bytes_remaining -= bytes_to_write;
watchdog_feed();
}
}
}
}
// Write the Core Dump Image Header
// Returns number of bytes written @ flash_addr
static uint32_t prv_write_image_header(uint32_t flash_addr, uint8_t core_number,
const ElfExternalNote *build_id, uint32_t timestamp) {
CoreDumpImageHeader hdr = {
.magic = CORE_DUMP_MAGIC,
.core_number = core_number,
.version = CORE_DUMP_VERSION,
.time_stamp = timestamp,
};
strncpy((char *)hdr.serial_number, mfg_get_serial_number(), sizeof(hdr.serial_number));
hdr.serial_number[sizeof(hdr.serial_number)-1] = 0;
version_copy_build_id_hex_string((char *)hdr.build_id, sizeof(hdr.build_id), build_id);
hdr.build_id[sizeof(hdr.build_id)-1] = 0;
return prv_flash_write_bytes(&hdr, flash_addr, sizeof(hdr));
}
// =================================================================================================
// Public interface
// -----------------------------------------------------------------------------------------------
// Trigger a core dump
NORETURN core_dump_reset(bool is_forced) {
// Big problem if we re-enter here - it likely means we encountered an
// exception during the core dump
if (s_core_dump_initiated) {
prv_debug_str("CD: re-entered");
prv_reset();
}
s_core_dump_initiated = true;
s_core_dump_is_forced = is_forced;
if (is_forced) {
RebootReason reason = { RebootReasonCode_ForcedCoreDump, 0};
reboot_reason_set(&reason);
}
// Pend the Non-Maskable Interrupt, as the NMI handler performs the core dump.
SCB->ICSR = SCB_ICSR_NMIPENDSET_Msk;
__DSB();
__ISB();
// Shouldn't get here
RebootReason reason = { RebootReasonCode_CoreDumpEntryFailed, 0 };
reboot_reason_set(&reason);
prv_reset();
}
void __attribute__((naked)) NMI_Handler(void) {
// Save the processor state at the moment the NMI exception was entered to a
// struct of type CoreDumpSavedRegisters.
//
// Save the processor state which is not automatically stacked during
// exception entry before any C code can clobber it.
__asm volatile (
" ldr r0, =%[s_saved_registers]\n"
" stmia r0!, {r4-r11} \n"
" str sp, [r0, #4]! \n" // sp, skipping r12
" str lr, [r0, #4]! \n" // lr
" mrs r1, xpsr \n"
" mrs r2, msp \n"
" mrs r3, psp \n"
" adds r0, #8 \n" // skip pc
" stmia r0!, {r1-r3} \n" // xpsr, msp, psp
" b core_dump_handler_c \n"
:
: [s_saved_registers] "i"
(&s_saved_registers.core_reg[portCANONICAL_REG_INDEX_R4])
: "r0", "r1", "r2", "r3", "cc"
);
}
EXTERNALLY_VISIBLE void core_dump_handler_c(void) {
// Locate the stack pointer where the processor state was stacked before the
// NMI handler was executed so that the saved state can be copied into
// s_saved_registers.
uint32_t *process_sp = (uint32_t *)(
RETURNS_TO_PSP(s_saved_registers.core_reg[portCANONICAL_REG_INDEX_LR])?
s_saved_registers.extra_reg.psp : s_saved_registers.extra_reg.msp);
s_saved_registers.core_reg[portCANONICAL_REG_INDEX_R0] = process_sp[0];
s_saved_registers.core_reg[portCANONICAL_REG_INDEX_R1] = process_sp[1];
s_saved_registers.core_reg[portCANONICAL_REG_INDEX_R2] = process_sp[2];
s_saved_registers.core_reg[portCANONICAL_REG_INDEX_R3] = process_sp[3];
// Replace the r12 saved earlier with the real value.
s_saved_registers.core_reg[portCANONICAL_REG_INDEX_R12] = process_sp[4];
// Make it look like the processor had halted at the start of this function.
s_saved_registers.core_reg[portCANONICAL_REG_INDEX_PC] = (uint32_t)NMI_Handler;
// Save the special registers that the C compiler won't clobber.
s_saved_registers.extra_reg.primask = __get_PRIMASK();
s_saved_registers.extra_reg.basepri = __get_BASEPRI();
s_saved_registers.extra_reg.faultmask = __get_FAULTMASK();
s_saved_registers.extra_reg.control = __get_CONTROL();
// if we coredump after new fw has been installed but before we reboot, the
// FW image will be overwritten with a coredump. Clear the boot bits so we
// don't try and load the resources which would result in us dropping to PRF
if (boot_bit_test(BOOT_BIT_NEW_FW_AVAILABLE)) {
boot_bit_clear(BOOT_BIT_NEW_FW_AVAILABLE);
boot_bit_clear(BOOT_BIT_NEW_SYSTEM_RESOURCES_AVAILABLE);
}
// Normally a reboot reason would be set before initiating a core dump. In
// case this isn't true, set a default reason so that we know the reboot was
// because of a core dump.
RebootReason reason;
reboot_reason_get(&reason);
if (reason.code == RebootReasonCode_Unknown) {
reason = (RebootReason) { RebootReasonCode_CoreDump, 0 };
reboot_reason_set(&reason);
}
prv_debug_str("Starting core dump");
// Save the current time now because rtc_get_ticks() disables and then re-enables interrupts
s_time_stamp = rtc_get_time();
prv_debug_str("CD: starting");
// Feed the watchdog so that we don't get watchdog reset in the middle of dumping the core
watchdog_feed();
prv_stash_regions();
// Init the flash and SPI bus
s_use_cd_flash_driver = true;
cd_flash_init();
// If there is a fairly recent unread core image already present, don't replace it. Once it is read through
// the get_bytes_protocol_msg_callback(), the unread flag gets cleared out.
uint32_t flash_base;
flash_base = prv_flash_start_address(false /*new*/);
if (!s_core_dump_is_forced && flash_base != CORE_DUMP_FLASH_INVALID_ADDR) {
CoreDumpFlashRegionHeader region_hdr;
CoreDumpImageHeader image_hdr;
prv_debug_str_int("CD: Checking: ", flash_base);
prv_flash_read_bytes(&region_hdr, flash_base, sizeof(region_hdr));
prv_flash_read_bytes(&image_hdr, flash_base + sizeof(region_hdr), sizeof(image_hdr));
if ((image_hdr.magic == CORE_DUMP_MAGIC) && region_hdr.unread
&& ((s_time_stamp - image_hdr.time_stamp) < CORE_DUMP_MIN_AGE_SECONDS)) {
prv_debug_str("CD: Still fresh");
#ifndef IS_BIGBOARD
prv_reset();
#else
prv_debug_str("CD: BigBoard, forcing dump");
#endif
}
}
// Get flash address to save new image to. This method also pre-erases the region for us.
flash_base = prv_flash_start_address(true /*new*/);
prv_debug_str_int("CD: Saving to: ", flash_base);
// ---------------------------------------------------------------------------------------
// Dump RAM and thread info into flash. We store data in flash using the following format:
//
// CoreDumpImageHeader image_header // includes magic signature, version, time stamp, serial number
// // and build id.
//
// uint32_t chunk_key // CORE_DUMP_CHUNK_KEY_MEMORY, CORE_DUMP_CHUNK_KEY_THREAD, etc.
// uint32_t chunk_size // # of bytes of data that follow
// uint8_t chunk[chunk_size] // data for the above chunk
//
// uint32_t chunk_key
// uint32_t chunk_size
// uint8_t chunk[chunk_size]
// ...
// uint32_t 0xFFFFFFFF // terminates list
//
// For threads, we store a CoreDumpThreadInfo structure as the "chunk":
// chunk_key = 'THRD'
// chunk[] = { uint8_t name[16]; // includes null termination
// uint32_t id; // thread id
// uint8_t running; // true if this thread is running
// uint32_t registers[17]; // thread registers [r0-r12, sp, lr, pc, xpsr]
// }
//
// Start at the core dump image header
s_flash_addr = flash_base + sizeof(CoreDumpFlashRegionHeader);
// Write out the core dump header -----------------------------------
s_flash_addr += prv_write_image_header(s_flash_addr, CORE_ID_MAIN_MCU, &TINTIN_BUILD_ID,
s_time_stamp);
// Write out the memory chunks ----------------------------------------
prv_write_memory_regions(MEMORY_REGIONS_DUMP, ARRAY_LENGTH(MEMORY_REGIONS_DUMP),
flash_base);
// Write out the extra registers chunk --------------------------------------------
CoreDumpChunkHeader chunk_hdr;
chunk_hdr.key = CORE_DUMP_CHUNK_KEY_EXTRA_REG;
chunk_hdr.size = sizeof(CoreDumpExtraRegInfo);
CD_ASSERTN(s_flash_addr + chunk_hdr.size - flash_base < CORE_DUMP_MAX_SIZE);
s_flash_addr += prv_flash_write_bytes(&chunk_hdr, s_flash_addr,
sizeof(chunk_hdr));
s_flash_addr += prv_flash_write_bytes(&s_saved_registers.extra_reg,
s_flash_addr, chunk_hdr.size);
// Write out each of the thread chunks ----------------------------------
// Note that we leave the threads for last just in case we encounter corrupted FreeRTOS structures.
// In that case, the core dump will at least contain the RAM and registers info and perhaps some of the
// threads. The format of the binary core dump is streamable and is read until we reach a chunk key
// of 0xFFFFFFFF (what gets placed into flash after an erase).
vTaskListWalk(prvTaskInfoCallback, NULL);
// If we core dumped from an ISR, we make up a special "ISR" thread to hold the registers
if (!RETURNS_TO_PSP(s_saved_registers.core_reg[portCANONICAL_REG_INDEX_LR])) {
// Another exception invoked the core dump handler
xPORT_TASK_INFO task_info;
task_info.pcName = "ISR";
task_info.taskHandle = (void *)1;
for (int i = 0; i < portCANONICAL_REG_COUNT; i++) {
// registers [r0-r12, sp, lr, pc, sr]
task_info.registers[i] = s_saved_registers.core_reg[i];
}
prvTaskInfoCallback(&task_info, NULL);
}
// Write out chunk terminator
chunk_hdr.key = CORE_DUMP_CHUNK_KEY_TERMINATOR;
chunk_hdr.size = 0;
s_flash_addr += prv_flash_write_bytes(&chunk_hdr, s_flash_addr,
sizeof(chunk_hdr));
// Reset!
prv_debug_str("CD: completed");
prv_reset();
}
// -----------------------------------------------------
// Warning: these functions use the normal flash driver
status_t core_dump_size(uint32_t flash_base, uint32_t *size) {
CoreDumpChunkHeader chunk_hdr;
uint32_t core_dump_base = flash_base + sizeof(CoreDumpFlashRegionHeader);
uint32_t current_offset = sizeof(CoreDumpImageHeader);
while (true) {
flash_read_bytes((uint8_t *)&chunk_hdr, core_dump_base + current_offset, sizeof(chunk_hdr));
if (chunk_hdr.key == CORE_DUMP_CHUNK_KEY_TERMINATOR) {
current_offset += sizeof(chunk_hdr);
break;
} else if (chunk_hdr.key == CORE_DUMP_CHUNK_KEY_RAM
|| chunk_hdr.key == CORE_DUMP_CHUNK_KEY_THREAD
|| chunk_hdr.key == CORE_DUMP_CHUNK_KEY_EXTRA_REG
|| chunk_hdr.key == CORE_DUMP_CHUNK_KEY_MEMORY) {
current_offset += sizeof(chunk_hdr) + chunk_hdr.size;
} else {
return E_INTERNAL;
}
// Totally bogus size?
if (current_offset > CORE_DUMP_MAX_SIZE) {
return E_INTERNAL;
}
}
*size = current_offset;
return S_SUCCESS;
}
void core_dump_mark_read(uint32_t flash_base) {
CoreDumpFlashRegionHeader region_hdr;
flash_read_bytes((uint8_t *)&region_hdr, flash_base, sizeof(region_hdr));
region_hdr.unread = 0;
flash_write_bytes((uint8_t *)&region_hdr, flash_base, sizeof(region_hdr));
}
bool core_dump_is_unread_available(uint32_t flash_base) {
if (flash_base != CORE_DUMP_FLASH_INVALID_ADDR) { // a coredump is on flash
CoreDumpFlashRegionHeader region_hdr;
CoreDumpImageHeader image_hdr;
flash_read_bytes((uint8_t *)&region_hdr, flash_base, sizeof(region_hdr));
flash_read_bytes((uint8_t *)&image_hdr, flash_base + sizeof(region_hdr),
sizeof(image_hdr));
return ((image_hdr.magic == CORE_DUMP_MAGIC) && (region_hdr.unread != 0));
}
return (false);
}
uint32_t core_dump_get_slot_address(unsigned int i) {
return (CORE_DUMP_FLASH_START + SUBSECTOR_SIZE_BYTES + i * CORE_DUMP_MAX_SIZE);
}
// BLE API - reserve core dump slot in flash
bool core_dump_reserve_ble_slot(uint32_t *flash_base, uint32_t *max_size,
ElfExternalNote *build_id) {
bool status = true;
uint32_t flash_addr, flash_addr_base;
// Use the standard flash driver
s_use_cd_flash_driver = false;
flash_addr_base = prv_flash_start_address(true /*new*/);
if (flash_addr_base == CORE_DUMP_FLASH_INVALID_ADDR) {
status = false;
goto cleanup;
}
flash_addr = flash_addr_base + sizeof(CoreDumpFlashRegionHeader);
flash_addr += prv_write_image_header(flash_addr, CORE_ID_BLE, build_id, rtc_get_time());
*flash_base = flash_addr;
*max_size = CORE_DUMP_MAX_SIZE - (flash_addr - flash_addr_base);
cleanup:
s_use_cd_flash_driver = true;
return status;
}
// --------------------------------------------------------------------------------------------------
// Used by unit tests in to cause fw/apps/demo_apps/test_core_dump_app to encounter a bus fault during the core dump
void core_dump_test_force_bus_fault(void) {
s_test_force_bus_fault = true;
}
void core_dump_test_force_inf_loop(void) {
s_test_force_inf_loop = true;
}
void core_dump_test_force_assert(void) {
s_test_force_assert = true;
}

44
src/fw/kernel/core_dump.h Normal file
View file

@ -0,0 +1,44 @@
/*
* 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.
*/
#pragma once
#include "util/attributes.h"
#include "util/build_id.h"
#include "system/status_codes.h"
#include <stdbool.h>
//! NOTE: This function performs a hard reset after the core dump and never returns
NORETURN core_dump_reset(bool is_forced);
bool is_unread_coredump_available(void);
// Used for unit tests
void core_dump_test_force_bus_fault(void);
void core_dump_test_force_inf_loop(void);
void core_dump_test_force_assert(void);
// Warning: these functions use the normal flash driver
status_t core_dump_size(uint32_t flash_base, uint32_t *size);
void core_dump_mark_read(uint32_t flash_base);
bool core_dump_is_unread_available(uint32_t flash_base);
uint32_t core_dump_get_slot_address(unsigned int i);
// Bluetooth Core Dump API
bool core_dump_reserve_ble_slot(uint32_t *flash_base, uint32_t *max_size,
ElfExternalNote *build_id);

View file

@ -0,0 +1,131 @@
/*
* 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.
*/
#pragma once
#include <inttypes.h>
#include <stdint.h>
#include "util/attributes.h"
#include "pebbleos/core_dump_structs.h"
#include "portmacro.h"
// Size of RAM
// TODO: Do we have an equate for the total size of RAM somewhere else?
#if PLATFORM_CALCULUS || PLATFORM_ROBERT
#define COREDUMP_RAM_SIZE (384 * 1024)
#elif PLATFORM_SILK
#define COREDUMP_RAM_SIZE (256 * 1024)
#elif PLATFORM_SNOWY || PLATFORM_SPALDING
#define COREDUMP_RAM_SIZE (192 * 1024)
#elif PLATFORM_TINTIN
#define COREDUMP_RAM_SIZE (128 * 1024)
#endif
// Max number of core dump images we can fit in our allocated space
#define CORE_DUMP_FLASH_START FLASH_REGION_FIRMWARE_SCRATCH_BEGIN
#define CORE_DUMP_FLASH_END FLASH_REGION_FIRMWARE_SCRATCH_END
#define CORE_DUMP_FLASH_SIZE (CORE_DUMP_FLASH_END - CORE_DUMP_FLASH_START)
#if defined(MICRO_FAMILY_STM32F2)
#define CORE_DUMP_MAX_IMAGES 3
#elif defined(MICRO_FAMILY_STM32F4)
#define CORE_DUMP_MAX_IMAGES 2
#elif defined(MICRO_FAMILY_STM32F7)
#define CORE_DUMP_MAX_IMAGES 3
#else
#error "Unsupported micro family"
#endif
// Max size of a core dump image. The first image is found at CORE_DUMP_FLASH_START +
// SUBSECTOR_SIZE_BYTES.
#define CORE_DUMP_MAX_SIZE (((CORE_DUMP_FLASH_SIZE - SUBSECTOR_SIZE_BYTES) \
/ CORE_DUMP_MAX_IMAGES) & SUBSECTOR_ADDR_MASK)
// Returned from prv_flash_start_address() if no valid region found
#define CORE_DUMP_FLASH_INVALID_ADDR 0xFFFFFFFF
// We don't overwrite an unread core-dump if it's less than CORE_DUMP_MIN_AGE seconds old and hasn't been
// fetched from the watch yet.
#define CORE_DUMP_MIN_AGE_SECONDS (60 * 60 * 24 * 1) // 1 day
// --------------------------------------------------------------------------------------------
// Core dump flash storage structures. The first thing at CORE_DUMP_FLASH_START is a CoreDumpFlashHeader.
// SUBSECTOR_SIZE_BYTES after that is the CoreDumpFlashRegionHeader for the first region.
// Every CORE_DUMP_MAX_SIZE after the first region header is another CoreDumpFlashRegionHeader, up to a
// max of CORE_DUMP_MAX_IMAGES.
// Each of the bits in the 'unformatted' field start out at 1, they get cleared as we use up to
// CORE_DUMP_MAX_IMAGES regions. When all CORE_DUMP_MAX_IMAGES have been used at least once, we rotate and
// set the active one to have the highest last_used value.
// This comes first in flash, at CORE_DUMP_FLASH_START. It is NOT returned as part of the core dump
// binary image
#define CORE_DUMP_FLASH_HDR_MAGIC 0x464C5300
#define CORE_DUMP_ALL_UNFORMATTED ((uint32_t)(~0))
typedef struct {
uint32_t magic; // Set to CORE_DUMP_FLASH_HDR_MAGIC
uint32_t unformatted; // set of 1 bit flags, bit n set means region n is still unformatted
} CoreDumpFlashHeader;
// This comes first in the front of each possibe flash region. It is NOT returned as part of the core dump
// image.
typedef struct {
uint32_t magic; // set to CORE_DUMP_FLASH_HDR_MAGIC
uint32_t last_used; // The region with the highest last_used count was the most recently used.
// This value is always >= 1
uint8_t unread; // non-zero if this core dump has not been read out yet
} CoreDumpFlashRegionHeader;
// The first item in a core dump image is a CoreDumpImageHeader. That is followed by one or more
// CoreDumpChunkHeader's, terminated by one with a key of CORE_DUMP_CHUNK_KEY_TERMINATOR
#define CORE_DUMP_MAGIC 0xF00DCAFE
#define CORE_DUMP_VERSION 1 // Current version
typedef struct PACKED {
uint32_t magic; // Set to CORE_DUMP_MAGIC
uint32_t core_number:8; // See include/pebbleos/core_id.h
uint32_t version:24; // Set to CORE_DUMP_VERSION
uint32_t time_stamp; // rtc_get_time() when core dump was created
uint8_t serial_number[16]; // null terminated watch serial number string
uint8_t build_id[64]; // null terminated build ID of firmware string
} CoreDumpImageHeader;
// Chunk header for each chunk within the core dump
#define CORE_DUMP_CHUNK_KEY_TERMINATOR 0xFFFFFFFF
#define CORE_DUMP_CHUNK_KEY_RAM 1 // Deprecated
#define CORE_DUMP_CHUNK_KEY_THREAD 2
#define CORE_DUMP_CHUNK_KEY_EXTRA_REG 3
#define CORE_DUMP_CHUNK_KEY_MEMORY 4
typedef struct PACKED {
uint32_t key; // CORE_DUMP_CHUNK_KEY_.*
uint32_t size;
// uint8_t data[size];
} CoreDumpChunkHeader;
// Header for dumped segments of memory, whether from RAM or peripheral space.
typedef struct PACKED {
uint32_t start; // start address of the chunk of dumped memory
// uint8_t data[size - sizeof(CoreDumpMemoryHeader)];
} CoreDumpMemoryHeader;
void coredump_assert(int line);
#define CD_ASSERTN(expr) \
do { \
if (!(expr)) { \
coredump_assert(__LINE__); \
} \
} while (0)

507
src/fw/kernel/event_loop.c Normal file
View file

@ -0,0 +1,507 @@
/*
* 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 "event_loop.h"
#include "events.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
#include "applib/app_launch_reason.h"
#include "applib/battery_state_service.h"
#include "applib/connection_service.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/text.h"
#include "applib/tick_timer_service.h"
#include "applib/ui/animation_private.h"
#include "applib/ui/app_window_click_glue.h"
#include "applib/ui/ui.h"
#include "applib/ui/window.h"
#include "applib/ui/window_private.h"
#include "comm/ble/kernel_le_client/kernel_le_client.h"
#include "console/serial_console.h"
#include "console/prompt.h"
#include "drivers/backlight.h"
#include "drivers/battery.h"
#include "drivers/button.h"
#include "drivers/task_watchdog.h"
#include "kernel/kernel_applib_state.h"
#include "kernel/low_power.h"
#include "kernel/panic.h"
#include "kernel/pbl_malloc.h"
#include "kernel/ui/kernel_ui.h"
#include "kernel/ui/modals/modal_manager.h"
#include "kernel/util/factory_reset.h"
#include "mcu/fpu.h"
#include "pebble_errors.h"
#include "process_management/app_install_manager.h"
#include "process_management/app_manager.h"
#include "process_management/app_run_state.h"
#include "process_management/process_manager.h"
#include "process_management/worker_manager.h"
#include "resource/resource_ids.auto.h"
#include "services/common/analytics/analytics.h"
#include "services/common/battery/battery_state.h"
#include "services/common/battery/battery_monitor.h"
#include "services/common/compositor/compositor.h"
#include "services/common/cron.h"
#include "services/common/debounced_connection_service.h"
#include "services/common/ecompass.h"
#include "services/common/event_service.h"
#include "services/common/evented_timer.h"
#include "services/common/firmware_update.h"
#include "services/common/i18n/i18n.h"
#include "services/common/light.h"
#include "services/common/new_timer/new_timer.h"
#include "services/common/put_bytes/put_bytes.h"
#include "services/common/status_led.h"
#include "services/common/system_task.h"
#include "services/common/vibe_pattern.h"
#include "services/normal/accessory/accessory_manager.h"
#include "services/normal/alarms/alarm.h"
#include "services/normal/app_fetch_endpoint.h"
#include "services/normal/blob_db/api.h"
#include "services/normal/notifications/do_not_disturb.h"
#include "services/normal/stationary.h"
#include "services/normal/timeline/reminders.h"
#include "services/normal/wakeup.h"
#include "services/runlevel.h"
#include "shell/normal/app_idle_timeout.h"
#include "shell/normal/watchface.h"
#include "shell/shell_event_loop.h"
#include "shell/system_app_state_machine.h"
#include "system/bootbits.h"
#include "system/logging.h"
#include "system/passert.h"
#include "system/reset.h"
#include "system/testinfra.h"
#include "util/bitset.h"
#include "util/struct.h"
#include "system/version.h"
#include <bluetooth/reconnect.h>
#include "FreeRTOS.h"
#include "task.h"
static const uint32_t FORCE_QUIT_HOLD_MS = 1500;
static int s_back_hold_timer = TIMER_INVALID_ID;
void launcher_task_add_callback(void (*callback)(void *data), void *data) {
PebbleEvent event = {
.type = PEBBLE_CALLBACK_EVENT,
.callback = {
.callback = callback,
.data = data,
},
};
event_put(&event);
}
bool launcher_task_is_current_task(void) {
return (pebble_task_get_current() == PebbleTask_KernelMain);
}
//! Return true if event could cause pop-up
//! Used in getting started and during firmware update
static bool launcher_is_popup_event(PebbleEvent* e) {
switch (e->type) {
case PEBBLE_SYS_NOTIFICATION_EVENT:
case PEBBLE_ALARM_CLOCK_EVENT:
case PEBBLE_BATTERY_CONNECTION_EVENT:
case PEBBLE_BATTERY_STATE_CHANGE_EVENT:
return true;
default:
return false;
}
}
static int s_block_popup_count = 0;
void launcher_block_popups(bool block) {
if (block) {
s_block_popup_count++;
} else {
PBL_ASSERTN(s_block_popup_count > 0);
s_block_popup_count--;
}
}
bool launcher_popups_are_blocked(void) {
return s_block_popup_count > 0;
}
void launcher_cancel_force_quit(void) {
new_timer_stop(s_back_hold_timer);
}
static void launcher_force_quit_app(void *data) {
if (low_power_is_active() || factory_reset_ongoing()) {
PBL_LOG(LOG_LEVEL_DEBUG, "Forcekill disabled due to low-power or factory-reset");
return;
}
PBL_LOG(LOG_LEVEL_DEBUG, "Force killing app.");
app_manager_force_quit_to_launcher();
}
static void back_button_force_quit_handler(void *data) {
launcher_task_add_callback(launcher_force_quit_app, NULL);
}
static void launcher_handle_button_event(PebbleEvent* e) {
ButtonId button_id = e->button.button_id;
const bool watchface_running = app_manager_is_watchface_running();
// trigger the backlight on any button down event
if (e->type == PEBBLE_BUTTON_DOWN_EVENT) {
analytics_inc(ANALYTICS_DEVICE_METRIC_BUTTON_PRESSED_COUNT, AnalyticsClient_System);
if (button_id == BUTTON_ID_BACK && !watchface_running &&
process_metadata_get_run_level(
app_manager_get_current_app_md()) == ProcessAppRunLevelNormal) {
// Start timer for force-quitting app
bool success = new_timer_start(s_back_hold_timer, FORCE_QUIT_HOLD_MS, back_button_force_quit_handler, NULL,
0 /*flags*/);
PBL_ASSERTN(success);
}
light_button_pressed();
} else if (e->type == PEBBLE_BUTTON_UP_EVENT) {
if (button_id == BUTTON_ID_BACK) {
launcher_cancel_force_quit();
}
light_button_released();
}
app_idle_timeout_refresh();
if (compositor_is_animating()) {
// mask the app task if we're already animating
e->task_mask |= 1 << PebbleTask_App;
return;
}
const bool is_modal_focused = (modal_manager_get_enabled() &&
!(modal_manager_get_properties() & ModalProperty_Unfocused));
if (is_modal_focused) {
// mask the app task if a modal is on top
e->task_mask |= 1 << PebbleTask_App;
modal_manager_handle_button_event(e);
return;
}
if (watchface_running) {
watchface_handle_button_event(e);
// suppress the button event from the app task
e->task_mask |= 1 << PebbleTask_App;
}
}
// This function should handle very basic events (Button clicks, app launching, battery events,
// crashes, etc.
static NOINLINE void prv_minimal_event_handler(PebbleEvent* e) {
switch (e->type) {
case PEBBLE_BUTTON_DOWN_EVENT:
case PEBBLE_BUTTON_UP_EVENT:
launcher_handle_button_event(e);
return;
case PEBBLE_BATTERY_CONNECTION_EVENT: {
const bool is_connected = e->battery_connection.is_connected;
battery_state_handle_connection_event(is_connected);
if (is_connected) {
light_enable_interaction();
} else {
// Chances are the Pebble of our dear customer has been charging away
// from the phone and is disconnected because of that. Try reconnecting
// immediately upon disconnecting the charger:
bt_driver_reconnect_reset_interval();
bt_driver_reconnect_try_now(false /*ignore_paused*/);
}
#if STATIONARY_MODE
stationary_handle_battery_connection_change_event();
#endif
return;
}
case PEBBLE_BATTERY_STATE_CHANGE_EVENT:
battery_monitor_handle_state_change_event(e->battery_state.new_state);
#if CAPABILITY_HAS_MAGNETOMETER
ecompass_handle_battery_state_change_event(e->battery_state.new_state);
#endif
return;
case PEBBLE_RENDER_READY_EVENT:
compositor_app_render_ready();
return;
case PEBBLE_ACCEL_SHAKE_EVENT:
analytics_inc(ANALYTICS_DEVICE_METRIC_ACCEL_SHAKE_COUNT, AnalyticsClient_System);
if (backlight_is_motion_enabled()) {
light_enable_interaction();
}
return;
case PEBBLE_PANIC_EVENT:
launcher_panic(e->panic.error_code);
break;
case PEBBLE_APP_LAUNCH_EVENT:
if (!app_install_is_app_running(e->launch_app.id)) {
process_manager_launch_process(&(ProcessLaunchConfig) {
.id = e->launch_app.id,
.common = NULL_SAFE_FIELD_ACCESS(e->launch_app.data, common, (LaunchConfigCommon) {}),
});
}
return;
case PEBBLE_WORKER_LAUNCH_EVENT:
if (!app_install_is_worker_running(e->launch_app.id)) {
process_manager_launch_process(&(ProcessLaunchConfig) {
.id = e->launch_app.id,
.common = NULL_SAFE_FIELD_ACCESS(e->launch_app.data, common, (LaunchConfigCommon) {}),
.worker = true,
});
}
return;
case PEBBLE_CALLBACK_EVENT:
e->callback.callback(e->callback.data);
return;
case PEBBLE_PROCESS_KILL_EVENT:
process_manager_close_process(e->kill.task, e->kill.gracefully);
return;
case PEBBLE_SUBSCRIPTION_EVENT:
// App button events depend on this, so this needs to be in the minimal event handler.
event_service_handle_subscription(&e->subscription);
return;
default:
PBL_LOG_VERBOSE("Received an unhandled event (%u)", e->type);
return;
}
}
static NOINLINE void prv_handle_app_fetch_request_event(PebbleEvent *e) {
AppInstallEntry entry;
PBL_ASSERTN(app_install_get_entry_for_install_id(e->app_fetch_request.id, &entry));
bool has_worker = app_install_entry_has_worker(&entry);
app_fetch_binaries(&entry.uuid, e->app_fetch_request.id, has_worker);
}
static NOINLINE void prv_extended_event_handler(PebbleEvent* e) {
switch (e->type) {
case PEBBLE_APP_OUTBOX_MSG_EVENT:
e->app_outbox_msg.callback(e->app_outbox_msg.data);
return;
case PEBBLE_APP_FETCH_REQUEST_EVENT:
prv_handle_app_fetch_request_event(e);
return;
case PEBBLE_PUT_BYTES_EVENT:
// TODO: inform the other things interested in put_bytes (apps?)
firmware_update_pb_event_handler(&e->put_bytes);
#ifndef RECOVERY_FW
app_fetch_put_bytes_event_handler(&e->put_bytes);
#endif
return;
case PEBBLE_SYSTEM_MESSAGE_EVENT:
firmware_update_event_handler(&e->firmware_update);
return;
case PEBBLE_ECOMPASS_SERVICE_EVENT:
#if CAPABILITY_HAS_MAGNETOMETER
ecompass_service_handle();
#endif
return;
case PEBBLE_SET_TIME_EVENT:
{
#ifndef RECOVERY_FW
PebbleSetTimeEvent *set_time_info = &e->set_time_info;
// The phone and watch time may be out of sync by a second or two (since
// we don't account for the time it takes for the request to change the
// time to propagate to the watch). Thus only update our alarm time if
// the timezone has changed or a 'substantial' time has passed, or DST
// state has changed.
if (set_time_info->gmt_offset_delta != 0 ||
set_time_info->dst_changed ||
ABS(set_time_info->utc_time_delta) > 15) {
alarm_handle_clock_change();
wakeup_handle_clock_change();
cron_service_handle_clock_change(set_time_info);
}
// TODO: evaluate if these need to change on every time update
do_not_disturb_handle_clock_change();
reminders_update_timer();
#endif
return;
}
case PEBBLE_BLE_SCAN_EVENT:
case PEBBLE_BLE_CONNECTION_EVENT:
case PEBBLE_BLE_GATT_CLIENT_EVENT:
kernel_le_client_handle_event(e);
return;
case PEBBLE_COMM_SESSION_EVENT: {
PebbleCommSessionEvent *comm_session_event = &e->bluetooth.comm_session_event;
debounced_connection_service_handle_event(comm_session_event);
put_bytes_handle_comm_session_event(comm_session_event);
#ifndef RECOVERY_FW
if (comm_session_event->is_system) {
// tell the phone which app is running
const Uuid *running_uuid = &app_manager_get_current_app_md()->uuid;
if (running_uuid != NULL) {
app_run_state_send_update(running_uuid, RUNNING);
}
}
#endif
return;
}
default:
return;
}
}
//! Tasks that have to be done in between each event.
static void event_loop_upkeep(void) {
modal_manager_event_loop_upkeep();
}
// NOTE: Marking this as NOINLINE saves us 150+ bytes on the KernelMain stack
static void NOINLINE prv_handle_event(PebbleEvent *e) {
prv_minimal_event_handler(e);
// FIXME: This logic is pretty wacky, but I'm going to leave it as is to refactor later out of
// fear of breaking something. This should mimic the exact same behaviour as before but
// flattened.
if (s_block_popup_count > 0) {
// A service has requested that the launcher block any events that may cause
// pop-ups
if (launcher_is_popup_event(e)) {
return;
}
}
if (!launcher_panic_get_current_error()) {
prv_extended_event_handler(e);
}
shell_event_loop_handle_event(e);
}
static NOINLINE void prv_launcher_main_loop_init(void) {
s_back_hold_timer = new_timer_create();
process_manager_init();
app_manager_init();
worker_manager_init();
vibes_init();
battery_monitor_init();
evented_timer_init();
#if CAPABILITY_HAS_MAGNETOMETER
ecompass_service_init();
#endif
tick_timer_service_init();
debounced_connection_service_init();
event_service_system_init();
#if CAPABILITY_HAS_ACCESSORY_CONNECTOR
accessory_manager_init();
#endif
modal_manager_init();
shell_event_loop_init();
#if STATIONARY_MODE
stationary_init();
#endif
task_watchdog_bit_set(PebbleTask_KernelMain);
// if we are in launcher panic, don't turn on any extra services.
const RunLevel run_level = launcher_panic_get_current_error() ? RunLevel_BareMinimum
: RunLevel_Normal;
services_set_runlevel(run_level);
// emulate a button press-and-release to turn on/off the backlight
light_button_pressed();
light_button_released();
#ifndef RECOVERY_FW
i18n_set_resource(RESOURCE_ID_STRINGS);
#endif
app_manager_start_first_app();
#ifndef RECOVERY_FW
// Launch the default worker. If any of the buttons are down, or we hit 2 strikes already,
// skip this. This insures that we don't enter PRF for a bad worker.
if (launcher_panic_get_current_error()) {
PBL_LOG(LOG_LEVEL_INFO, "Not launching worker because launcher panic");
} else if (button_get_state_bits() != 0) {
PBL_LOG(LOG_LEVEL_INFO, "Not launching worker because button held");
} else if (boot_bit_test(BOOT_BIT_FW_START_FAIL_STRIKE_TWO)) {
PBL_LOG(LOG_LEVEL_INFO, "Not launching worker because of 2 strikes");
} else {
process_manager_launch_process(&(ProcessLaunchConfig) {
.id = worker_manager_get_default_install_id(),
.worker = true,
});
}
#endif
notify_system_ready_for_communication();
serial_console_enable_prompt();
}
void launcher_main_loop(void) {
PBL_LOG(LOG_LEVEL_ALWAYS, "Starting Launcher");
prv_launcher_main_loop_init();
while (1) {
task_watchdog_bit_set(PebbleTask_KernelMain);
// We make this PebbleEvent static to save stack space
static PebbleEvent e;
if (event_take_timeout(&e, 1000)) {
const PebbleTaskBitset kernel_main_task_bit = (1 << PebbleTask_KernelMain);
const bool is_not_masked_out_from_kernel_main = !(e.task_mask & kernel_main_task_bit);
if (is_not_masked_out_from_kernel_main) {
prv_handle_event(&e);
}
event_service_handle_event(&e);
event_cleanup(&e);
mcu_fpu_cleanup();
event_loop_upkeep();
}
}
__builtin_unreachable();
}

View file

@ -0,0 +1,43 @@
/*
* 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.
*/
#pragma once
#include "kernel/events.h"
#include <stdbool.h>
//! Adds an event to the launcher's queue that will call the callback with
//! arbitrary data as argument. Make sure that data points to memory that lives
//! past the point of calling this function.
//! @param callback Function pointer to the callback to be called
//! @param data Pointer to arbitrary data that will be passed as an argument to the callback
void launcher_task_add_callback(CallbackEventCallback callback, void *data);
bool launcher_task_is_current_task(void);
//! Increment or decrement a reference count of services that want the launcher
//! to block pop-ups; used by getting started and firmware update
void launcher_block_popups(bool ignore);
//! Returns true if popups are currently being blocked
bool launcher_popups_are_blocked(void);
void launcher_main_loop(void);
//! Cancel the force quit timer that may currently be running if the back button
//! was pressed down.
void launcher_cancel_force_quit(void);

407
src/fw/kernel/events.c Normal file
View file

@ -0,0 +1,407 @@
/*
* 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 "events.h"
#include "debug/setup.h"
#include "system/logging.h"
#include "system/passert.h"
#include "system/reset.h"
#include "kernel/pbl_malloc.h"
#include "os/tick.h"
#include "services/normal/app_outbox_service.h"
#include "syscall/syscall.h"
#include "FreeRTOS.h"
#include "queue.h"
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
static QueueHandle_t s_kernel_event_queue = NULL;
static QueueHandle_t s_from_app_event_queue = NULL;
static QueueHandle_t s_from_worker_event_queue = NULL;
// The following conventions insure that the s_from_kernel_event_queue queue will always have sufficient space and that
// KernelMain will never deadlock trying to send an event to itself:
// 1.) KernelMain must never enqueue more than MAX_FROM_KERNEL_MAIN_EVENTS events to itself while processing another
// event.
// 2.) The ONLY task that posts events to s_from_kernel_event_queue is the KernelMain task.
// 3.) Whenever KernelMain wants to post an event to itself, it MUST use this queue.
// 4.) The KernelMain task will always service this queue first, before servicing the kernel or from_app queues.
static QueueHandle_t s_from_kernel_event_queue = NULL;
// This queue set contains the s_kernel_event_queue, s_from_app_event_queue, and s_from_worker_event_queue queues
static QueueSetHandle_t s_system_event_queue_set = NULL;
static const int MAX_KERNEL_EVENTS = 32;
static const int MAX_FROM_APP_EVENTS = 10;
static const int MAX_FROM_WORKER_EVENTS = 5;
static const int MAX_FROM_KERNEL_MAIN_EVENTS = 14;
uint32_t s_current_event;
#define EVENT_DEBUG 0
#if EVENT_DEBUG
static void prv_queue_dump(QueueHandle_t queue) {
PebbleEvent event;
PBL_LOG(LOG_LEVEL_DEBUG, "Dumping queue:");
while (xQueueReceive(queue, &event, 0) == pdTRUE) {
PBL_LOG(LOG_LEVEL_DEBUG, "Event type: %u", event.type);
}
for(;;);
}
#endif
void events_init(void) {
PBL_ASSERTN(s_system_event_queue_set == NULL);
// This assert is to make sure we don't accidentally bloat our PebbleEvent unecessarily. If you hit this
// assert and you have a good reason for making the event bigger, feel free to relax the restriction.
//PBL_LOG(LOG_LEVEL_DEBUG, "PebbleEvent size is %u", sizeof(PebbleEvent));
// FIXME:
_Static_assert(sizeof(PebbleEvent) <= 12,
"You made the PebbleEvent bigger! It should be no more than 12");
s_system_event_queue_set = xQueueCreateSet(MAX_KERNEL_EVENTS + MAX_FROM_APP_EVENTS);
s_kernel_event_queue = xQueueCreate(MAX_KERNEL_EVENTS, sizeof(PebbleEvent));
PBL_ASSERTN(s_kernel_event_queue != NULL);
s_from_app_event_queue = xQueueCreate(MAX_FROM_APP_EVENTS , sizeof(PebbleEvent));
PBL_ASSERTN(s_from_app_event_queue != NULL);
s_from_worker_event_queue = xQueueCreate(MAX_FROM_WORKER_EVENTS , sizeof(PebbleEvent));
PBL_ASSERTN(s_from_worker_event_queue != NULL);
s_from_kernel_event_queue = xQueueCreate(MAX_FROM_KERNEL_MAIN_EVENTS , sizeof(PebbleEvent));
PBL_ASSERTN(s_from_kernel_event_queue != NULL);
xQueueAddToSet(s_kernel_event_queue, s_system_event_queue_set);
xQueueAddToSet(s_from_app_event_queue, s_system_event_queue_set);
xQueueAddToSet(s_from_worker_event_queue, s_system_event_queue_set);
}
//! Get the from_process queue for a specific task
QueueHandle_t event_get_to_kernel_queue(PebbleTask task) {
if (task == PebbleTask_App) {
return s_from_app_event_queue;
} else if (task == PebbleTask_Worker) {
return s_from_worker_event_queue;
} else if (task == PebbleTask_KernelMain) {
return s_from_kernel_event_queue;
} else if ((task == PebbleTask_NewTimers) || (task == PebbleTask_KernelBackground)) {
return s_kernel_event_queue;
} else {
WTF;
return NULL;
}
}
//! Decode a bit more information out about an event and pack it into a uint32_t
static uint32_t prv_get_fancy_type_from_event(const PebbleEvent *event) {
if (event->type == PEBBLE_CALLBACK_EVENT) {
return (uint32_t) event->callback.callback;
}
return event->type;
}
static void prv_log_event_put_failure(const char *queue_name, uintptr_t saved_lr, const PebbleEvent *event) {
PBL_LOG(LOG_LEVEL_ERROR, "Error, %s queue full. Type %u", queue_name, event->type);
RebootReason reason = {
.code = RebootReasonCode_EventQueueFull,
.event_queue = {
.destination_task = PebbleTask_KernelMain,
.push_lr = saved_lr,
.current_event = s_current_event,
.dropped_event = prv_get_fancy_type_from_event(event)
}
};
reboot_reason_set(&reason);
}
static bool prv_event_put_isr(QueueHandle_t queue, const char* queue_type, uintptr_t saved_lr,
PebbleEvent* event) {
PBL_ASSERTN(queue);
portBASE_TYPE should_context_switch = pdFALSE;
if (!xQueueSendToBackFromISR(queue, event, &should_context_switch)) {
prv_log_event_put_failure(queue_type, saved_lr, event);
#ifdef NO_WATCHDOG
enable_mcu_debugging();
while (1);
#endif
reset_due_to_software_failure();
}
return should_context_switch;
}
static bool prv_try_event_put(QueueHandle_t queue, PebbleEvent *event) {
PBL_ASSERTN(queue);
return (xQueueSendToBack(queue, event, milliseconds_to_ticks(3000)) == pdTRUE);
}
static void prv_event_put(QueueHandle_t queue,
const char* queue_type,
uintptr_t saved_lr,
PebbleEvent* event) {
PBL_ASSERTN(queue);
if (!xQueueSendToBack(queue, event, milliseconds_to_ticks(3000))) {
// We waited a reasonable amount of time here before failing. We don't want to wait too long because
// if the queue really is stuck we'll just get a watchdog reset, which will be harder to debug than
// just dieing here. However, we want to wait a non-zero amount of time to provide for a little bit
// of backup to occur before killing ourselves.
prv_log_event_put_failure(queue_type, saved_lr, event);
#if EVENT_DEBUG
prv_queue_dump(queue);
#endif
reset_due_to_software_failure();
}
}
void event_deinit(PebbleEvent* event) {
void **buffer = event_get_buffer(event);
if (buffer && *buffer) {
kernel_free(*buffer);
*buffer = NULL;
}
}
void event_put(PebbleEvent* event) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
// If we are posting from the KernelMain task, use the dedicated s_from_kernel_event_queue queue for that
// See comments above where s_from_kernel_event_queue is declared.
if (pebble_task_get_current() == PebbleTask_KernelMain) {
return prv_event_put(s_from_kernel_event_queue, "from_kernel", saved_lr, event);
} else {
return prv_event_put(s_kernel_event_queue, "kernel", saved_lr, event);
}
}
bool event_put_isr(PebbleEvent* event) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
return prv_event_put_isr(s_kernel_event_queue, "kernel", saved_lr, event);
}
void event_put_from_process(PebbleTask task, PebbleEvent* event) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
QueueHandle_t queue = event_get_to_kernel_queue(task);
prv_event_put(queue, "from app", saved_lr, event);
}
bool event_try_put_from_process(PebbleTask task, PebbleEvent* event) {
QueueHandle_t queue = event_get_to_kernel_queue(task);
return prv_try_event_put(queue, event);
}
bool event_take_timeout(PebbleEvent* event, int timeout_ms) {
PBL_ASSERTN(s_system_event_queue_set);
s_current_event = 0;
// We must prioritize the from_kernel queue and always empty that first in order to avoid deadlocks in
// KernelMain. See comments at top of file where s_from_kernel_event_queue is declared.
// Check the from_kernel queue first to see if we posted any events to ourself.
portBASE_TYPE result = xQueueReceive(s_from_kernel_event_queue, event, 0);
if (result) {
s_current_event = prv_get_fancy_type_from_event(event);
return true;
}
// Wait for either the from_app, from_worker, or kernel queue to be ready.
QueueSetMemberHandle_t activated_queue = xQueueSelectFromSet(s_system_event_queue_set,
milliseconds_to_ticks(timeout_ms));
if (!activated_queue) {
return false;
}
// Always service the kernel queue first. This prevents a misbehaving app from starving us.
// If we're a little lazy servicing the app, the app will just block itself when the queue gets full.
if (xQueueReceive(s_kernel_event_queue, event, 0) == pdFALSE) {
// Process the activated queue. This insures that events are handled in FIFO order from the app and worker
// tasks. Note that sometimes the activated_queue can be the s_kernel_event_queue, even though
// the above xQueueReceive returned no event
if (activated_queue == s_from_app_event_queue || activated_queue == s_from_worker_event_queue) {
result = xQueueReceive(activated_queue, event, 0);
}
if (!result) {
result = xQueueReceive(s_from_app_event_queue, event, 0);
}
if (!result) {
result = xQueueReceive(s_from_worker_event_queue, event, 0);
}
// If there was nothing in the queue, return false. We are misusing the queue set by pulling events out
// from the s_kernel_event_queue queue before it's activated so likely, the activated queue was
// s_kernel_event_queue.
if (!result) {
return false;
}
}
s_current_event = prv_get_fancy_type_from_event(event);
return true;
}
void **event_get_buffer(PebbleEvent *event) {
switch (event->type) {
case PEBBLE_SYS_NOTIFICATION_EVENT:
if (event->sys_notification.type == NotificationActionResult) {
return (void **)&event->sys_notification.action_result;
} else if ((event->sys_notification.type == NotificationAdded) ||
(event->sys_notification.type == NotificationRemoved) ||
(event->sys_notification.type == NotificationActedUpon)) {
return (void **)&event->sys_notification.notification_id;
}
break;
case PEBBLE_BLOBDB_EVENT:
return (void **)&event->blob_db.key;
case PEBBLE_BT_PAIRING_EVENT:
if (event->bluetooth.pair.type ==
PebbleBluetoothPairEventTypePairingUserConfirmation) {
return (void **)&event->bluetooth.pair.confirmation_info;
}
break;
case PEBBLE_APP_LAUNCH_EVENT:
return (void **)&event->launch_app.data;
case PEBBLE_VOICE_SERVICE_EVENT:
return (void **)&event->voice_service.data;
case PEBBLE_REMINDER_EVENT:
return (void **)&event->reminder.reminder_id;
case PEBBLE_BLE_GATT_CLIENT_EVENT:
if (event->bluetooth.le.gatt_client.subtype == PebbleBLEGATTClientEventTypeServiceChange) {
return (void **)(&event->bluetooth.le.gatt_client_service.info);
}
break;
case PEBBLE_HRM_EVENT:
if (event->hrm.event_type == HRMEvent_Diagnostics) {
return (void **)(&event->hrm.debug);
}
break;
case PEBBLE_APP_GLANCE_EVENT:
return (void **)&event->app_glance.app_uuid;
case PEBBLE_TIMELINE_PEEK_EVENT:
return (void **)&event->timeline_peek.item_id;
default:
break; // Nothing to do!
}
return NULL;
}
void event_cleanup(PebbleEvent* event) {
event_deinit(event);
#ifndef RELEASE
// Hopefully this will catch some use after free evil
*event = (PebbleEvent){};
#endif
}
void event_reset_from_process_queue(PebbleTask task) {
// Unfortunately, current versions of FreeRTOS don't really handle resetting a queue that's part
// of a queue set all that well. See PBL-1817. We'll clean up the queue set manually.
// Notice that we don't disable the scheduler or enter a critical section here. This is because
// it is usually unsafe to do so when making other FreeRTOS calls that might cause context switch
// (see http://www.freertos.org/a00134.html). I think this is OK though - the worse that can
// happen is that we end up with extra items in the s_system_event_queue_set that don't belong
// there and event_take_timeout() is tolerant of that. Also see the discussion at
// https://github.com/pebble/tintin/pull/2416#discussion_r16641981.
// We want to remove all references to the queue we just reset, while keeping references to other
// queues in check. This would be really annoying, but luckily we only have two other queues in
// the set. Count the number of times the other queues exist in the queue set, clear the queue,
// and then restore the original count.
QueueHandle_t reset_queue, preserve_queue;
if (task == PebbleTask_App) {
reset_queue = s_from_app_event_queue;
preserve_queue = s_from_worker_event_queue;
} else if (task == PebbleTask_Worker) {
reset_queue = s_from_worker_event_queue;
preserve_queue = s_from_app_event_queue;
} else {
preserve_queue = reset_queue = NULL;
WTF;
}
xQueueReset(s_system_event_queue_set);
event_queue_cleanup_and_reset(reset_queue);
int num_kernel_events_enqueued = uxQueueMessagesWaiting(s_kernel_event_queue);
for (int i = 0; i < num_kernel_events_enqueued; ++i) {
xQueueSend(s_system_event_queue_set, &s_kernel_event_queue, 0);
}
int num_client_task_events_enqueued = uxQueueMessagesWaiting(preserve_queue);
for (int i = 0; i < num_client_task_events_enqueued; ++i) {
xQueueSend(s_system_event_queue_set, &preserve_queue, 0);
}
}
QueueHandle_t event_kernel_to_kernel_event_queue(void) {
return s_from_kernel_event_queue;
}
BaseType_t event_queue_cleanup_and_reset(QueueHandle_t queue) {
int num_events_in_queue = uxQueueMessagesWaiting(queue);
PebbleEvent event;
for (int i = 0; i < num_events_in_queue; ++i) {
PBL_ASSERTN(xQueueReceive(queue, &event, 0) != pdFAIL);
// event service does some book-keeping about events, notify it that we're dropping these.
sys_event_service_cleanup(&event);
#if !RECOVERY_FW
// app outbox service messages need to be cleaned up:
app_outbox_service_cleanup_event(&event);
#endif
// cleanup the event, free associated memory if applicable
event_cleanup(&event);
}
return xQueueReset(queue);
}

852
src/fw/kernel/events.h Normal file
View file

@ -0,0 +1,852 @@
/*
* 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.
*/
#pragma once
#include "applib/accel_service.h"
#include "applib/app_launch_button.h"
#include "applib/app_launch_reason.h"
#include "applib/app_outbox.h"
#include "applib/app_smartstrap.h"
#include "applib/bluetooth/ble_client.h"
#include "applib/health_service.h"
#include "applib/plugin_service.h"
#include "applib/tick_timer_service.h"
#include "applib/voice/dictation_session.h"
#include "applib/ui/click.h"
#include "apps/system_apps/app_fetch_ui.h"
#include "drivers/battery.h"
#include "drivers/button_id.h"
#include "process_management/app_install_types.h"
#include "services/common/battery/battery_monitor.h"
#include "services/common/bluetooth/bluetooth_ctl.h"
#include "services/common/comm_session/session_remote_os.h"
#include "services/common/comm_session/session_remote_version.h"
#include "services/common/hrm/hrm_manager.h"
#include "services/common/put_bytes/put_bytes.h"
#include "services/common/touch/touch_event.h"
#include "services/imu/units.h"
#include "services/normal/accessory/smartstrap_profiles.h"
#include "services/normal/blob_db/api.h"
#include "services/normal/music.h"
#include "services/normal/notifications/notifications.h"
#include "services/normal/voice/voice.h"
#include "services/normal/wakeup.h"
#include "services/normal/timeline/peek.h"
#include "services/normal/timeline/reminders.h"
#include "kernel/pebble_tasks.h"
#include "util/attributes.h"
#include "freertos_types.h"
#include "portmacro.h"
#include <bluetooth/bluetooth_types.h>
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
typedef struct PebblePhoneCaller PebblePhoneCaller;
typedef enum {
PEBBLE_NULL_EVENT = 0,
PEBBLE_ACCEL_SHAKE_EVENT,
PEBBLE_ACCEL_DOUBLE_TAP_EVENT,
PEBBLE_BT_CONNECTION_EVENT,
PEBBLE_BT_CONNECTION_DEBOUNCED_EVENT,
PEBBLE_BUTTON_DOWN_EVENT,
PEBBLE_BUTTON_UP_EVENT,
//! From kernel to app, ask the app to render itself
PEBBLE_RENDER_REQUEST_EVENT,
//! From app to kernel, ask the compositor to render the app
PEBBLE_RENDER_READY_EVENT,
//! From kernel to app, notification that render was completed
PEBBLE_RENDER_FINISHED_EVENT,
PEBBLE_BATTERY_CONNECTION_EVENT, // TODO: this has a poor name
PEBBLE_PUT_BYTES_EVENT,
PEBBLE_BT_PAIRING_EVENT,
// Emitted when the Pebble mobile app or third party app is (dis)connected
PEBBLE_COMM_SESSION_EVENT,
PEBBLE_MEDIA_EVENT,
PEBBLE_TICK_EVENT,
PEBBLE_SET_TIME_EVENT,
PEBBLE_SYS_NOTIFICATION_EVENT,
PEBBLE_PROCESS_DEINIT_EVENT,
PEBBLE_PROCESS_KILL_EVENT,
PEBBLE_PHONE_EVENT,
PEBBLE_APP_LAUNCH_EVENT,
PEBBLE_ALARM_CLOCK_EVENT,
PEBBLE_SYSTEM_MESSAGE_EVENT,
PEBBLE_FIRMWARE_UPDATE_EVENT,
PEBBLE_BT_STATE_EVENT,
PEBBLE_BATTERY_STATE_CHANGE_EVENT,
PEBBLE_CALLBACK_EVENT,
PEBBLE_NEW_APP_MESSAGE_EVENT,
PEBBLE_SUBSCRIPTION_EVENT,
PEBBLE_APP_WILL_CHANGE_FOCUS_EVENT,
PEBBLE_APP_DID_CHANGE_FOCUS_EVENT,
PEBBLE_DO_NOT_DISTURB_EVENT,
PEBBLE_REMOTE_APP_INFO_EVENT,
PEBBLE_ECOMPASS_SERVICE_EVENT,
PEBBLE_COMPASS_DATA_EVENT,
PEBBLE_PLUGIN_SERVICE_EVENT,
PEBBLE_WORKER_LAUNCH_EVENT,
PEBBLE_BLE_SCAN_EVENT,
PEBBLE_BLE_CONNECTION_EVENT,
PEBBLE_BLE_GATT_CLIENT_EVENT,
PEBBLE_BLE_DEVICE_NAME_UPDATED_EVENT,
PEBBLE_BLE_HRM_SHARING_STATE_UPDATED_EVENT,
PEBBLE_WAKEUP_EVENT,
PEBBLE_BLOBDB_EVENT,
PEBBLE_VOICE_SERVICE_EVENT,
PEBBLE_DICTATION_EVENT,
PEBBLE_APP_FETCH_EVENT,
PEBBLE_APP_FETCH_REQUEST_EVENT,
PEBBLE_GATHER_DEBUG_INFO_EVENT,
PEBBLE_REMINDER_EVENT,
PEBBLE_CALENDAR_EVENT,
PEBBLE_PANIC_EVENT,
PEBBLE_SMARTSTRAP_EVENT,
//! Event sent back to the app to let them know the result of their sent message.
PEBBLE_APP_OUTBOX_SENT_EVENT,
//! A request from the app to the outbox service to handle a message.
//! @note The consuming service must call app_outbox_service_consume_message() to clean up.
//! In case the event is dropped because the queue is reset, cleanup happens by events.c in
//! event_queue_cleanup_and_reset().
PEBBLE_APP_OUTBOX_MSG_EVENT,
PEBBLE_HEALTH_SERVICE_EVENT,
PEBBLE_TOUCH_EVENT,
PEBBLE_CAPABILITIES_CHANGED_EVENT,
// Emitted when ANCS disconnects or is invalidated
PEBBLE_ANCS_DISCONNECTED_EVENT,
PEBBLE_WEATHER_EVENT,
PEBBLE_HRM_EVENT,
PEBBLE_UNOBSTRUCTED_AREA_EVENT,
PEBBLE_APP_GLANCE_EVENT,
PEBBLE_TIMELINE_PEEK_EVENT,
PEBBLE_APP_CACHE_EVENT,
PEBBLE_ACTIVITY_EVENT,
PEBBLE_WORKOUT_EVENT,
PEBBLE_NUM_EVENTS
} PebbleEventType;
typedef struct PACKED { // 9 bytes
AppOutboxSentHandler sent_handler;
void *cb_ctx;
AppOutboxStatus status:8;
} PebbleAppOutboxSentEvent;
typedef struct PACKED { // 1 byte
bool is_active; //<! ANCS has become active or has become inactive
} PebbleAncsChangedEvent;
typedef struct PACKED { // 1 byte
bool is_active; //<! do not disturb has become active or has become inactive
} PebbleDoNotDisturbEvent;
typedef struct PACKED { // 1 byte?
ButtonId button_id;
} PebbleButtonEvent;
typedef enum PhoneEventType {
PhoneEventType_Invalid = 0,
PhoneEventType_Incoming,
PhoneEventType_Outgoing,
PhoneEventType_Missed,
PhoneEventType_Ring,
PhoneEventType_Start,
PhoneEventType_End,
PhoneEventType_CallerID,
PhoneEventType_Disconnect,
PhoneEventType_Hide,
} PhoneEventType;
typedef enum PhoneCallSource {
PhoneCallSource_PP,
PhoneCallSource_ANCS_Legacy,
PhoneCallSource_ANCS,
} PhoneCallSource;
typedef struct PACKED PebblePhoneEvent { // 9 bytes
PhoneEventType type:6;
PhoneCallSource source:2;
uint32_t call_identifier;
PebblePhoneCaller* caller;
} PebblePhoneEvent;
typedef enum {
NotificationAdded,
NotificationActedUpon,
NotificationRemoved,
NotificationActionResult
} PebbleSysNotificationType;
typedef struct PACKED { // 6 bytes
PebbleSysNotificationType type:8;
union {
Uuid *notification_id;
PebbleSysNotificationActionResult *action_result;
// it won't exceed the 9 bytes
};
} PebbleSysNotificationEvent;
typedef struct PACKED { // 4 bytes
time_t tick_time; //!< Needs to be converted to 'struct tm' in the event service handler
} PebbleTickEvent;
typedef struct PACKED { // 4 bytes
uint32_t error_code;
} PebblePanicEvent;
typedef enum {
PebbleMediaEventTypeNowPlayingChanged,
PebbleMediaEventTypePlaybackStateChanged,
PebbleMediaEventTypeVolumeChanged,
PebbleMediaEventTypeServerConnected,
PebbleMediaEventTypeServerDisconnected,
PebbleMediaEventTypeTrackPosChanged,
} PebbleMediaEventType;
typedef struct PACKED { // 2 bytes
PebbleMediaEventType type;
union {
MusicPlayState playback_state;
uint8_t volume_percent;
};
} PebbleMediaEvent;
typedef enum {
PebbleBluetoothPairEventTypePairingUserConfirmation,
PebbleBluetoothPairEventTypePairingComplete,
} PebbleBluetoothPairEventType;
typedef struct PairingUserConfirmationCtx PairingUserConfirmationCtx;
typedef struct {
char *device_name;
char *confirmation_token;
} PebbleBluetoothPairingConfirmationInfo;
typedef struct PACKED { // 9 bytes
const PairingUserConfirmationCtx *ctx;
union {
//! Valid if type is PebbleBluetoothPairEventTypePairingUserConfirmation
PebbleBluetoothPairingConfirmationInfo *confirmation_info;
//! Valid if type is PebbleBluetoothPairEventTypePairingComplete
bool success;
};
PebbleBluetoothPairEventType type:1;
} PebbleBluetoothPairEvent;
typedef enum {
PebbleBluetoothConnectionEventStateConnected,
PebbleBluetoothConnectionEventStateDisconnected,
} PebbleBluetoothConnectionEventState;
// FIXME: This event muddles classic + LE connection events for the phone.
typedef struct PACKED { // 9 bytes
PebbleBluetoothConnectionEventState state:1;
bool is_ble:1;
BTDeviceInternal device;
} PebbleBluetoothConnectionEvent;
typedef struct PACKED { // 9 bytes
uint8_t hci_reason;
BTBondingID bonding_id;
uint64_t bt_device_bits:50;
bool connected:1;
} PebbleBLEConnectionEvent;
typedef struct PACKED { // 4 bytes
int subscription_count;
} PebbleBLEHRMSharingStateUpdatedEvent;
typedef enum {
PebbleBLEGATTClientEventTypeServiceChange,
PebbleBLEGATTClientEventTypeCharacteristicRead,
PebbleBLEGATTClientEventTypeCharacteristicWrite,
PebbleBLEGATTClientEventTypeCharacteristicSubscribe,
PebbleBLEGATTClientEventTypeDescriptorRead,
PebbleBLEGATTClientEventTypeDescriptorWrite,
PebbleBLEGATTClientEventTypeNotification,
PebbleBLEGATTClientEventTypeBufferEmpty,
PebbleBLEGATTClientEventTypeNum,
} PebbleBLEGATTClientEventType;
typedef struct PACKED { // 9 bytes
uintptr_t object_ref;
union {
uint16_t value_length;
BLESubscription subscription_type:2;
};
BLEGATTError gatt_error:16;
//! This is here to make sure we don't accidentally add more fields here without thinking:
uint16_t zero:2;
PebbleBLEGATTClientEventType subtype:6;
} PebbleBLEGATTClientEvent;
#define BLE_GATT_MAX_SERVICES_CHANGED_BITS (5) // max. 31
#define BLE_GATT_MAX_SERVICES_CHANGED ((1 << BLE_GATT_MAX_SERVICES_CHANGED_BITS) - 1)
#define BLE_GATT_CLIENT_EVENT_SUBTYPE_BITS (3)
typedef enum {
PebbleServicesRemoved,
PebbleServicesAdded,
PebbleServicesInvalidateAll
} PebbleServiceNotificationType;
typedef struct {
uint8_t num_services_added;
BLEService services[];
} PebbleBLEGATTClientServicesAdded;
typedef struct {
BLEService service;
Uuid uuid;
uint8_t num_characteristics;
uint8_t num_descriptors;
uintptr_t char_and_desc_handles[];
} PebbleBLEGATTClientServiceHandles;
typedef struct {
uint8_t num_services_removed;
PebbleBLEGATTClientServiceHandles handles[];
} PebbleBLEGATTClientServicesRemoved;
typedef struct {
PebbleServiceNotificationType type;
BTDeviceInternal device;
BTErrno status;
union {
PebbleBLEGATTClientServicesAdded services_added_data;
PebbleBLEGATTClientServicesRemoved services_removed_data;
};
} PebbleBLEGATTClientServiceEventInfo;
typedef struct PACKED { // 9 bytes
PebbleBLEGATTClientServiceEventInfo *info;
uint64_t rsvd:34;
uint8_t subtype:BLE_GATT_CLIENT_EVENT_SUBTYPE_BITS;
} PebbleBLEGATTClientServiceEvent;
_Static_assert((1 << BLE_GATT_CLIENT_EVENT_SUBTYPE_BITS) >= PebbleBLEGATTClientEventTypeNum,
"Not enough bits to represent all PebbleBLEGATTClientEventTypes");
#ifdef __arm__
_Static_assert(sizeof(PebbleBLEGATTClientServiceEvent) == sizeof(PebbleBLEGATTClientServiceEvent),
"PebbleBLEGATTClientEvent and PebbleBLEGATTClientServiceEvent must be the same size");
#endif
#define PebbleEventToBTDeviceInternal(e) ((const BTDeviceInternal) { \
.opaque = { \
.opaque_64 = (e)->bt_device_bits \
} \
})
typedef struct PACKED { // 3 byte?
bool airplane;
bool enabled;
BtCtlModeOverride override;
} PebbleBluetoothStateEvent;
typedef enum {
PebblePutBytesEventTypeStart,
PebblePutBytesEventTypeCleanup,
PebblePutBytesEventTypeProgress,
PebblePutBytesEventTypeInitTimeout,
} PebblePutBytesEventType;
typedef struct PACKED { // 8 bytes
PebblePutBytesEventType type:8;
uint8_t progress_percent; // the percent complete for the current PB transfer
PutBytesObjectType object_type:7;
bool has_cookie:1;
bool failed;
union {
// if type != PebblePutBytesEventTypeCleanup, populated with:
uint32_t bytes_transferred; // the number of bytes transferred since the last event
// else populated with:
uint32_t total_size;
};
} PebblePutBytesEvent;
typedef struct PACKED { // 1 bytes
PreciseBatteryChargeState new_state;
} PebbleBatteryStateChangeEvent;
typedef struct PACKED { // 1 byte
bool is_connected;
} PebbleBatteryConnectionEvent;
typedef struct PACKED { // 5 bytes
int32_t magnetic_heading;
uint8_t calib_status;
} PebbleCompassDataEvent;
typedef struct PACKED { // 5 bytes
IMUCoordinateAxis axis;
int32_t direction;
} PebbleAccelTapEvent;
//! This is fired when a PP comm session is opened or closed
typedef struct PACKED PebbleCommSessionEvent { // 1 byte
//! indicates whether the we are connecting or disconnecting
bool is_open:1;
//! True if the pebble app has connected & false if a third-party app has connected
bool is_system:1;
} PebbleCommSessionEvent;
typedef struct PACKED { // 1 bytes
RemoteOS os;
} PebbleRemoteAppInfoEvent;
typedef enum {
PebbleSystemMessageFirmwareUpdateStartLegacy,
PebbleSystemMessageFirmwareUpdateStart,
PebbleSystemMessageFirmwareUpdateComplete,
PebbleSystemMessageFirmwareUpdateFailed,
PebbleSystemMessageFirmwareUpToDate,
PebbleSystemMessageFirmwareOutOfDate,
} PebbleSystemMessageEventType;
typedef struct PACKED { // 1 byte
PebbleSystemMessageEventType type;
uint32_t bytes_transferred;
uint32_t total_transfer_size;
} PebbleSystemMessageEvent;
//! We need to pass a lot of data to launch an app, more than what would normally fit in a
//! PebbleEvent. This structure is heap allocated whenever we need to launch an app.
typedef struct {
LaunchConfigCommon common;
WakeupInfo wakeup;
} PebbleLaunchAppEventExtended;
typedef struct PACKED { // 8 bytes
AppInstallId id;
PebbleLaunchAppEventExtended *data;
} PebbleLaunchAppEvent;
typedef struct PACKED { // 8 bytes
time_t alarm_time; //!< Needs to be converted to 'struct tm' in the event service handler
const char *alarm_label;
} PebbleAlarmClockEvent;
typedef void (*CallbackEventCallback)(void *data);
typedef struct PACKED { // 8 bytes
CallbackEventCallback callback;
void *data;
} PebbleCallbackEvent;
typedef struct PACKED { // 4 bytes
void* data;
} PebbleNewAppMessageEvent;
typedef struct PACKED { // 7 bytes
bool subscribe;
PebbleTask task:8;
PebbleEventType event_type;
void *event_queue;
} PebbleSubscriptionEvent;
typedef struct PACKED { // 2 bytes
bool gracefully;
PebbleTask task;
} PebbleKillEvent;
typedef struct PACKED { // 1 byte
bool in_focus;
} PebbleAppFocusEvent;
typedef struct PACKED { // 9 bytes
uint8_t type; // service event type
uint16_t service_index; // service index
PluginEventData data;
} PebblePluginServiceEvent;
typedef struct PACKED { // 8 bytes
WakeupInfo wakeup_info;
} PebbleWakeupEvent;
typedef struct PACKED { // 7 bytes
BlobDBId db_id;
BlobDBEventType type;
uint8_t *key;
uint8_t key_len;
} PebbleBlobDBEvent;
typedef enum {
VoiceEventTypeSessionSetup,
VoiceEventTypeSessionResult,
VoiceEventTypeSilenceDetected,
VoiceEventTypeSpeechDetected
} VoiceEventType;
typedef struct {
uint32_t timestamp;
char sentence[];
} PebbleVoiceServiceEventData;
typedef struct PACKED { // 6 bytes
VoiceEventType type:8;
VoiceStatus status:8;
PebbleVoiceServiceEventData *data;
} PebbleVoiceServiceEvent;
typedef struct PACKED { // 9 bytes
DictationSessionStatus result;
time_t timestamp;
char *text;
} PebbleDictationEvent;
//! Possible results that come back from the INSTALL_COMMAND
typedef enum {
AppFetchEventTypeStart,
AppFetchEventTypeProgress,
AppFetchEventTypeFinish,
AppFetchEventTypeError,
} AppFetchEventType;
typedef struct PACKED { // 6 bytes
AppFetchEventType type;
AppInstallId id;
union {
uint8_t progress_percent;
uint8_t error_code;
};
} PebbleAppFetchEvent;
typedef struct PACKED { // 9 bytes
AppInstallId id;
bool with_ui;
AppFetchUIArgs *fetch_args; //! NULL when with_ui is false, required otherwise
} PebbleAppFetchRequestEvent;
typedef enum {
DebugInfoSourceGetBytes,
DebugInfoSourceFWLogs,
} DebugInfoEventSource;
typedef enum {
DebugInfoStateStarted,
DebugInfoStateFinished,
} DebugInfoEventState;
typedef struct PACKED { // 2 bytes
DebugInfoEventSource source;
DebugInfoEventState state;
} PebbleGatherDebugInfoEvent;
typedef enum {
ReminderTriggered,
ReminderRemoved,
ReminderUpdated,
} ReminderEventType;
typedef struct PACKED { // 4 bytes
ReminderEventType type;
ReminderId *reminder_id;
} PebbleReminderEvent;
typedef struct PACKED { // 9 bytes
HealthEventData data;
HealthEventType type:8; // At the end so that data is word aligned.
} PebbleHealthEvent;
typedef struct PACKED { // 1 byte
bool is_event_ongoing;
} PebbleCalendarEvent;
typedef enum {
SmartstrapConnectionEvent,
SmartstrapDataSentEvent,
SmartstrapDataReceivedEvent,
SmartstrapNotifyEvent
} SmartstrapEventType;
typedef struct PACKED { // 9 bytes
SmartstrapEventType type:4;
SmartstrapProfile profile:4;
SmartstrapResult result:8;
uint16_t read_length;
union {
SmartstrapServiceId service_id;
SmartstrapAttribute *attribute;
};
} PebbleSmartstrapEvent;
typedef struct PACKED { // 9 bytes
int utc_time_delta;
int gmt_offset_delta;
bool dst_changed;
} PebbleSetTimeEvent;
typedef enum PebbleTouchEventType {
PebbleTouchEvent_TouchesAvailable,
PebbleTouchEvent_TouchesCancelled,
PebbleTouchEvent_PalmDetected
} PebbleTouchEventType;
typedef struct PACKED { // 2 bytes
PebbleTouchEventType type:8;
TouchIdx touch_idx;
} PebbleTouchEvent;
typedef struct PACKED { // 8 bytes
PebbleProtocolCapabilities flags_diff;
} PebbleCapabilitiesChangedEvent;
typedef enum WeatherEventType {
WeatherEventType_WeatherDataAdded,
WeatherEventType_WeatherDataRemoved,
WeatherEventType_WeatherOrderChanged,
} WeatherEventType;
typedef struct PACKED PebbleWeatherEvent {
WeatherEventType type:8;
} PebbleWeatherEvent;
typedef struct HRMBPMData { // 2 bytes
uint8_t bpm;
HRMQuality quality:8;
} HRMBPMData;
typedef struct HRMHRVData { // 3 bytes
uint16_t ppi_ms; //!< Peak-to-peak interval (ms)
HRMQuality quality:8;
} HRMHRVData;
typedef struct HRMLEDData { // 4 bytes
uint16_t current_ua;
uint16_t tia; //!< Transimpendance Amplifier value.
//!< This is used with thresholds (provided by AMS) to verify the part is
//!< functioning within specification.
} HRMLEDData;
typedef struct HRMDiagnosticsData {
HRMPPGData ppg_data;
HRMAccelData accel_data;
} HRMDiagnosticsData;
typedef struct HRMSubscriptionExpiringData { // 4 bytes
HRMSessionRef session_ref;
} HRMSubscriptionExpiringData;
typedef enum HRMEventType {
HRMEvent_BPM = 0,
HRMEvent_HRV,
HRMEvent_LEDCurrent,
HRMEvent_Diagnostics,
HRMEvent_SubscriptionExpiring
} HRMEventType;
typedef struct PACKED PebbleHRMEvent { // 5 bytes
HRMEventType event_type;
union {
HRMBPMData bpm;
HRMHRVData hrv;
HRMLEDData led;
HRMDiagnosticsData *debug;
HRMSubscriptionExpiringData expiring;
};
} PebbleHRMEvent;
typedef enum UnobstructedAreaEventType {
UnobstructedAreaEventType_WillChange,
UnobstructedAreaEventType_Change,
UnobstructedAreaEventType_DidChange,
} UnobstructedAreaEventType;
typedef struct UnobstructedAreaEventData {
GRect area;
GRect final_area; //!< The final unobstructed area. Empty for events other than will-change.
AnimationProgress progress;
} UnobstructedAreaEventData;
typedef struct PACKED {
int16_t current_y;
int16_t final_y;
AnimationProgress progress;
UnobstructedAreaEventType type:8; //!< At the end for alignment.
} PebbleUnobstructedAreaEvent;
#if !__clang__
_Static_assert(sizeof(PebbleUnobstructedAreaEvent) == 9,
"PebbleUnobstructedAreaEvent size mismatch.");
#endif
typedef struct PACKED PebbleAppGlanceEvent {
Uuid *app_uuid;
} PebbleAppGlanceEvent;
typedef struct PACKED {
TimelineItemId *item_id;
TimelinePeekTimeType time_type:8;
uint8_t num_concurrent;
bool is_first_event;
bool is_future_empty;
} PebbleTimelinePeekEvent;
#if !__clang__
_Static_assert(sizeof(PebbleTimelinePeekEvent) == 8,
"PebbleTimelinePeekEvent size mismatch.");
#endif
typedef enum PebbleAppCacheEventType {
PebbleAppCacheEvent_Removed,
PebbleAppCacehEventNum
} PebbleAppCacheEventType;
typedef struct PACKED PebbleAppCacheEvent {
PebbleAppCacheEventType cache_event_type:8;
AppInstallId install_id;
} PebbleAppCacheEvent;
#if !__clang__
_Static_assert(sizeof(PebbleAppCacheEvent) == 5,
"PebbleTimelinePeekEvent size mismatch.");
#endif
typedef enum PebbleActivityEventType {
PebbleActivityEvent_TrackingStarted,
PebbleActivityEvent_TrackingStopped,
PebbleActivityEventNum
} PebbleActivityEventType;
typedef struct PACKED PebbleActivityEvent {
PebbleActivityEventType type:8;
} PebbleActivityEvent;
typedef enum PebbleWorkoutEventType {
PebbleWorkoutEvent_Started,
PebbleWorkoutEvent_Stopped,
PebbleWorkoutEvent_Paused,
PebbleWorkoutEvent_FrontendOpened,
PebbleWorkoutEvent_FrontendClosed,
} PebbleWorkoutEventType;
typedef struct PebbleWorkoutEvent {
PebbleWorkoutEventType type;
} PebbleWorkoutEvent;
typedef struct PACKED {
union PACKED {
PebblePanicEvent panic;
PebbleButtonEvent button;
PebbleSysNotificationEvent sys_notification;
// TODO: kill these old events
PebbleBatteryStateChangeEvent battery_state;
PebbleBatteryConnectionEvent battery_connection;
PebbleSetTimeEvent set_time_info;
PebbleTickEvent clock_tick;
PebbleAccelTapEvent accel_tap;
PebbleCompassDataEvent compass_data;
PebbleMediaEvent media;
PebblePutBytesEvent put_bytes;
PebblePhoneEvent phone;
PebbleLaunchAppEvent launch_app;
PebbleSystemMessageEvent firmware_update;
PebbleAlarmClockEvent alarm_clock;
PebbleAppOutboxSentEvent app_outbox_sent;
PebbleCallbackEvent app_outbox_msg;
union {
PebbleBluetoothPairEvent pair;
PebbleBluetoothConnectionEvent connection;
PebbleCommSessionEvent comm_session_event;
PebbleBluetoothStateEvent state;
PebbleRemoteAppInfoEvent app_info_event;
union {
PebbleBLEConnectionEvent connection;
PebbleBLEGATTClientEvent gatt_client;
PebbleBLEGATTClientServiceEvent gatt_client_service;
PebbleBLEHRMSharingStateUpdatedEvent hrm_sharing_state;
} le;
} bluetooth;
PebbleAncsChangedEvent ancs_changed;
PebbleDoNotDisturbEvent do_not_disturb;
PebbleCallbackEvent callback;
PebbleNewAppMessageEvent new_app_message;
PebbleSubscriptionEvent subscription;
PebbleKillEvent kill;
PebbleAppFocusEvent app_focus;
PebbleWakeupEvent wakeup;
PebblePluginServiceEvent plugin_service;
PebbleBlobDBEvent blob_db;
PebbleVoiceServiceEvent voice_service;
PebbleDictationEvent dictation;
PebbleAppFetchEvent app_fetch;
PebbleAppFetchRequestEvent app_fetch_request;
PebbleGatherDebugInfoEvent debug_info;
PebbleReminderEvent reminder;
PebbleCalendarEvent calendar;
PebbleHealthEvent health_event;
PebbleSmartstrapEvent smartstrap;
PebbleTouchEvent touch;
PebbleCapabilitiesChangedEvent capabilities;
PebbleWeatherEvent weather;
PebbleHRMEvent hrm;
PebbleUnobstructedAreaEvent unobstructed_area;
PebbleAppGlanceEvent app_glance;
PebbleTimelinePeekEvent timeline_peek;
PebbleAppCacheEvent app_cache_event;
PebbleActivityEvent activity_event;
PebbleWorkoutEvent workout;
};
PebbleTaskBitset task_mask; // 1 == filter out, 0 == leave in
// NOTE: we put this 8 bit field at the end so that we can pack this structure and still keep the
// event data unions word aligned (and avoid unaligned access exceptions).
PebbleEventType type:8;
} PebbleEvent;
void events_init(void);
void event_put(PebbleEvent* event);
bool event_put_isr(PebbleEvent* event);
void event_put_from_process(PebbleTask task, PebbleEvent* event);
//! Like event_put_from_app but it's allowed to fail.
bool event_try_put_from_process(PebbleTask task, PebbleEvent* event);
bool event_take_timeout(PebbleEvent* event, int timeout_ms);
//! Return a reference to the allocated buffer within an event, if applicable
void **event_get_buffer(PebbleEvent *event);
//! De-initialize an event, freeing the allocated buffer if necessary
void event_deinit(PebbleEvent *event);
//! Call to clean up after an event that has been dequeued using event_take.
void event_cleanup(PebbleEvent* event);
void event_reset_from_process_queue(PebbleTask task);
//! Get the queue for messaging to the kernel from the given task
QueueHandle_t event_get_to_kernel_queue(PebbleTask task);
QueueHandle_t event_kernel_to_kernel_event_queue(void);
//! Call to reset a queue and free all memory associated w/ the events it contains
BaseType_t event_queue_cleanup_and_reset(QueueHandle_t queue);

View file

@ -0,0 +1,425 @@
/*
* 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 "kernel/logging_private.h"
#include "process_management/process_manager.h"
#include "process_management/app_manager.h"
#include "process_management/worker_manager.h"
#include "applib/app_logging.h"
#include "applib/app_heap_analytics.h"
#include "kernel/memory_layout.h"
#include "mcu/privilege.h"
#include "services/common/analytics/analytics_event.h"
#include "services/common/system_task.h"
#include "syscall/syscall.h"
#include "syscall/syscall_internal.h"
#include "system/logging.h"
#include "syscall/syscall.h"
#include <util/heap.h>
#define CMSIS_COMPATIBLE
#include <mcu.h>
#include "FreeRTOS.h"
#include "portmacro.h"
#include "task.h"
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
// These variables are assigned values when the watch faults
// TODO PBL-PBL-36253: We should probably save these in CrashInfo in the future. Saved here because
// they are easier to pull out through GDB and because we can keep the Bluetooth FW and Normal FW
// fault handling the way they are now.
static uint32_t s_fault_saved_sp;
static uint32_t s_fault_saved_lr;
static uint32_t s_fault_saved_pc;
void enable_fault_handlers(void) {
NVIC_SetPriority(MemoryManagement_IRQn, configMAX_SYSCALL_INTERRUPT_PRIORITY);
NVIC_SetPriority(BusFault_IRQn, configMAX_SYSCALL_INTERRUPT_PRIORITY);
NVIC_SetPriority(UsageFault_IRQn, configMAX_SYSCALL_INTERRUPT_PRIORITY);
SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk;
SCB->SHCSR |= SCB_SHCSR_BUSFAULTENA_Msk;
SCB->SHCSR |= SCB_SHCSR_USGFAULTENA_Msk;
}
typedef struct CrashInfo {
PebbleTask task;
Uuid app_uuid;
uint8_t build_id[BUILD_ID_EXPECTED_LEN];
uintptr_t lr;
uintptr_t pc;
bool lr_known;
bool pc_known;
bool is_rocky_app;
} CrashInfo;
CrashInfo make_crash_info_pc(uintptr_t pc) {
return (CrashInfo) { .pc = pc, .pc_known = true };
}
CrashInfo make_crash_info_pc_lr(uintptr_t pc, uintptr_t lr) {
return (CrashInfo) { .pc = pc, .pc_known = true,
.lr = lr, .lr_known = true };
}
static void prv_save_debug_registers(unsigned int* stacked_args) {
s_fault_saved_lr = (uint32_t)stacked_args[5];
s_fault_saved_pc = (uint32_t)stacked_args[6];
s_fault_saved_sp = (uint32_t)&stacked_args[8];
}
static void prv_log_app_lr_and_pc_system_task(void *data) {
CrashInfo* crash_info = (CrashInfo*) data;
char lr_str[16];
if (crash_info->lr_known) {
sniprintf(lr_str, sizeof(lr_str), "%p", (void*) crash_info->lr);
} else {
strncpy(lr_str, "???", sizeof(lr_str));
}
char pc_str[16];
if (crash_info->pc_known) {
sniprintf(pc_str, sizeof(pc_str), "%p", (void*) crash_info->pc);
} else {
strncpy(pc_str, "???", sizeof(pc_str));
}
char buffer[UUID_STRING_BUFFER_LENGTH];
uuid_to_string(&crash_info->app_uuid, buffer);
char *process_string = (crash_info->task == PebbleTask_Worker) ? "Worker" : "App";
APP_LOG(APP_LOG_LEVEL_ERROR, "%s fault! %s PC: %s LR: %s", process_string, buffer, pc_str, lr_str);
PBL_LOG(LOG_LEVEL_ERROR, "%s fault! %s", process_string, buffer);
PBL_LOG(LOG_LEVEL_ERROR, " --> PC: %s LR: %s", pc_str, lr_str);
analytics_event_app_crash(&crash_info->app_uuid,
(crash_info->pc_known) ? crash_info->pc : 0,
(crash_info->lr_known) ? crash_info->lr : 0,
crash_info->build_id, crash_info->is_rocky_app);
}
//! Converts an address from an absolute address in our memory space to one that's relative to the start
//! of the loaded app/worker
static void convert_to_process_offset(bool known, uintptr_t* pc, PebbleTask task) {
if (known) {
*pc = (uintptr_t) process_manager_address_to_offset(task, (void*) *pc);
}
}
static CrashInfo s_current_app_crash_info;
static void setup_log_app_crash_info(CrashInfo crash_info) {
// Write the information out into a global variable so it can be logged out at a less critical time.
s_current_app_crash_info = crash_info;
const PebbleProcessMd *md = sys_process_manager_get_current_process_md();
s_current_app_crash_info.app_uuid = md->uuid;
s_current_app_crash_info.is_rocky_app = md->is_rocky_app;
const uint8_t *build_id = process_metadata_get_build_id(md);
if (build_id) {
memcpy(s_current_app_crash_info.build_id, build_id, sizeof(s_current_app_crash_info.build_id));
}
PebbleTask task = pebble_task_get_current();
s_current_app_crash_info.task = task;
convert_to_process_offset(s_current_app_crash_info.pc_known, &s_current_app_crash_info.pc, task);
convert_to_process_offset(s_current_app_crash_info.lr_known, &s_current_app_crash_info.lr, task);
}
static NORETURN kernel_fault(RebootReasonCode reason_code, uint32_t lr) {
RebootReason reason = { .code = reason_code, .extra = lr };
reboot_reason_set(&reason);
reset_due_to_software_failure();
}
// TODO: Can we tell if it was the worker and not the app?
extern void sys_app_fault(uint32_t lr);
NORETURN trigger_fault(RebootReasonCode reason_code, uint32_t lr) {
if (mcu_state_is_privileged()) {
kernel_fault(reason_code, lr);
} else {
sys_app_fault(lr);
}
}
NORETURN trigger_oom_fault(size_t bytes, uint32_t lr, Heap *heap_ptr) {
if (mcu_state_is_privileged()) {
RebootReason reason = {
.code = RebootReasonCode_OutOfMemory,
.heap_data = {
.heap_alloc_lr = lr,
.heap_ptr = (uint32_t)heap_ptr,
}
};
reboot_reason_set(&reason);
reset_due_to_software_failure();
} else {
app_heap_analytics_log_native_heap_oom_fault(bytes, heap_ptr);
sys_app_fault(lr);
}
}
void NOINLINE app_crashed(void) {
// Just sit here and look pretty. The purpose of this function is to give app developers a symbol
// that they can set a breakpoint on to debug app crashes. We need to make sure this function is
// not going to get optimized away and that it's globally visible.
__asm volatile("");
}
static void prv_kill_user_process(uint32_t stashed_lr) {
PebbleTask task = pebble_task_get_current();
if (task == PebbleTask_App) {
app_crashed();
app_manager_get_task_context()->safe_to_kill = true;
} else if (task == PebbleTask_Worker) {
app_crashed();
worker_manager_get_task_context()->safe_to_kill = true;
// If not release mode, generate a core dump so we can get debugging information
#ifdef WORKER_CRASH_CAUSES_RESET
kernel_fault(RebootReasonCode_WorkerHardFault, stashed_lr);
#endif
} else {
PBL_LOG_FROM_FAULT_HANDLER("WTF?");
kernel_fault(RebootReasonCode_HardFault, stashed_lr);
}
process_manager_put_kill_process_event(task, false /* gracefully */);
// Wait for the kernel to kill us...
vTaskSuspend(xTaskGetCurrentTaskHandle());
}
DEFINE_SYSCALL(NORETURN, sys_app_fault, uint32_t stashed_lr) {
// This is the privileged side of handling a failed assert/croak from unprivileged code.
// Always run on the current task.
CrashInfo crash_info = make_crash_info_pc(stashed_lr);
setup_log_app_crash_info(crash_info);
system_task_add_callback(prv_log_app_lr_and_pc_system_task, &s_current_app_crash_info);
prv_kill_user_process(stashed_lr);
for (;;) {} // Not Reached
}
static void hardware_fault_landing_zone(void) {
prv_log_app_lr_and_pc_system_task(&s_current_app_crash_info);
prv_kill_user_process(0);
}
static void prv_return_to_landing_zone(uintptr_t stacked_pc, uintptr_t stacked_lr, unsigned int* stacked_args) {
// We got this! Let's redirect this task to a spin function and tell the app manager to kill us.
// Log about the terrible thing that just happened.
CrashInfo crash_info = make_crash_info_pc_lr(stacked_pc, stacked_lr);
setup_log_app_crash_info(crash_info);
// Alright, now to neuter the current task. We're going to do some work to make it so when we return from
// this fault handler we'll end up in a perfectly safe place while we wait to die.
SCB->BFAR &= 1 << 7; // Clear Bus Fault Address Register "address is valid" bit
SCB->MMFAR &= 1 << 7; // Clear Memory Manage Address Register "address is valid" bit
SCB->CFSR &= ~0; // Clear the complete status register
// Redirect this task to nowhere by changing the stacked PC register.
// We can't let this task resume to where it crashed or else it will just crash again.
// The kernel should come by and kill the task soon, but if it's busy doing something else just spin.
// We don't want to just spin in the fault handler because that will prevent other tasks from being
// executed, as we're currently in a higher priority interrupt.
stacked_args[6] = (int) hardware_fault_landing_zone;
// Clear the ICI bits in the Program Status Register. These bits refer to microprocessor state if we
// get interrupted during a certain set of instructions. Since we're returning to a different place, we need
// to clean up this state or else we'll just hit an INVSTATE UsageFault immediately. The only bit we leave
// set is the bit that says we're in thumb state, which must always be set on Cortex-M3, since the micro doesn't
// even support non-thumb instructions.
// See: https://pebbletech.campfirenow.com/room/508662/transcript/message/1111369053#message_1111369053
// http://stackoverflow.com/a/9538628/1546
stacked_args[7] = 1 << 24;
mcu_state_set_thread_privilege(true);
// Now return to hardware_fault_landing_zone...
}
static void attempt_handle_stack_overflow(unsigned int* stacked_args) {
PebbleTask task = pebble_task_get_current();
PBL_LOG_SYNC(LOG_LEVEL_ERROR, "Stack overflow [task: %s]", pebble_task_get_name(task));
if (mcu_state_is_thread_privileged()) {
// We're hosed! We can't recover so just reboot everything.
RebootReason reason = { .code = RebootReasonCode_StackOverflow, .data8[0] = task };
reboot_reason_set(&reason);
reset_due_to_software_failure();
return;
}
// We got this! Let's redirect this task to a spin function and tell the app manager to kill us.
prv_return_to_landing_zone(0, 0, stacked_args); // We can't get LR or PC, so just set to 0's.
}
static void attempt_handle_generic_fault(unsigned int* stacked_args) {
uintptr_t stacked_lr = (uintptr_t) stacked_args[5];;
uintptr_t stacked_pc = (uintptr_t) stacked_args[6];;;
if (mcu_state_is_thread_privileged()) {
// We're hosed! We can't recover so just reboot everything.
kernel_fault(RebootReasonCode_HardFault, stacked_lr);
return;
}
// We got this! Let's redirect this task to a spin function and tell the app manager to kill us.
prv_return_to_landing_zone(stacked_pc, stacked_lr, stacked_args); // We can't get LR or PC, so just set to 0's.
}
// Hardware Fault Handlers
///////////////////////////////////////////////////////////
extern void fault_handler_dump(char buffer[80], unsigned int* stacked_args);
extern void fault_handler_dump_cfsr(char buffer[80]);
static void mem_manage_handler_c(unsigned int* stacked_args, unsigned int lr) {
// Be very careful about touching stacked_args in this function. We can end up in the
// memfault handler because we hit the stack guard, which indicates that we've run out of stack
// space and therefore won't have any room to stack the args. Accessing stacked_args in this
// case will end up triggering a hardfault.
PBL_LOG_FROM_FAULT_HANDLER("\r\n\r\n[Memory Management Failure!]");
char buffer[80];
PBL_LOG_FROM_FAULT_HANDLER("Configured Regions: ");
memory_layout_dump_mpu_regions_to_dbgserial();
PBL_LOG_FROM_FAULT_HANDLER("");
// If if we faulted in a stack guard region, this indicates a stack overflow
bool stack_overflow = false;
const uint32_t cfsr = SCB->CFSR;
const uint8_t mmfsr = cfsr & 0xff;
if (mmfsr & (1 << 7)) {
uint32_t fault_addr = SCB->MMFAR;
MpuRegion mpu_region = mpu_get_region(MemoryRegion_IsrStackGuard);
if (memory_layout_is_pointer_in_region(&mpu_region, (void *)fault_addr)) {
stack_overflow = true;
} else {
mpu_region = mpu_get_region(MemoryRegion_TaskStackGuard);
if (memory_layout_is_pointer_in_region(&mpu_region, (void *)fault_addr)) {
stack_overflow = true;
}
}
}
// If it's a stack overflow, backup the stack so that attempt_handle_hardware_fault() can jam in
// our landing zone to return to
if (stack_overflow) {
// Zero out the saved registers. We won't have new values in them, and we want to make sure
// that they don't contain bogus values from a fault we previously handled without crashing.
s_fault_saved_lr = 0;
s_fault_saved_pc = 0;
s_fault_saved_sp = 0;
// We can't call fault_handler_dump because stacked_args isn't going to be valid, but we can at least dump
// the cfsr.
fault_handler_dump_cfsr(buffer);
stacked_args += 256; // Should be enough to get above the guard region and execute hardware_fault_landing_zone
if (lr & 0x04) {
__set_PSP((uint32_t)stacked_args);
} else {
__set_MSP((uint32_t)stacked_args);
}
attempt_handle_stack_overflow(stacked_args);
} else {
prv_save_debug_registers(stacked_args);
fault_handler_dump(buffer, stacked_args);
// BREAKPOINT;
// NOTE: If you want to get a stack trace at this point. Set a breakpoint here (you can compile in the above
// BREAKPOINT call if you want) and issue the following commands in gdb:
// set var $sp=<value of SP above>
// set var $lr=<value of LR above>
// set var $pc=<value of PC above>
// bt
attempt_handle_generic_fault(stacked_args);
}
}
void MemManage_Handler(void) {
// Grab the stack pointer, shove it into a register and call
// the c function above.
__asm("tst lr, #4\n"
"ite eq\n"
"mrseq r0, msp\n"
"mrsne r0, psp\n"
"mov r1, lr\n"
"b %0\n" :: "i" (mem_manage_handler_c));
}
static void busfault_handler_c(unsigned int* stacked_args) {
PBL_LOG_FROM_FAULT_HANDLER("\r\n\r\n[BusFault_Handler!]");
prv_save_debug_registers(stacked_args);
char buffer[80];
fault_handler_dump(buffer, stacked_args);
PBL_LOG_FROM_FAULT_HANDLER("");
attempt_handle_generic_fault(stacked_args);
}
void BusFault_Handler(void) {
__asm("tst lr, #4\n"
"ite eq\n"
"mrseq r0, msp\n"
"mrsne r0, psp\n"
"b %0\n" :: "i" (busfault_handler_c));
}
static void usagefault_handler_c(unsigned int* stacked_args) {
PBL_LOG_FROM_FAULT_HANDLER("\r\n\r\n[UsageFault_Handler!]");
prv_save_debug_registers(stacked_args);
char buffer[80];
fault_handler_dump(buffer, stacked_args);
PBL_LOG_FROM_FAULT_HANDLER("");
attempt_handle_generic_fault(stacked_args);
}
void UsageFault_Handler(void) {
__asm("tst lr, #4\n"
"ite eq\n"
"mrseq r0, msp\n"
"mrsne r0, psp\n"
"b %0\n" :: "i" (usagefault_handler_c));
}

View file

@ -0,0 +1,25 @@
/*
* 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.
*/
#pragma once
#include "system/reboot_reason.h"
typedef struct Heap Heap;
NORETURN trigger_oom_fault(size_t bytes, uint32_t lr, Heap *heap_ptr);
NORETURN trigger_fault(RebootReasonCode reason_code, uint32_t lr);
void enable_fault_handlers(void);

View file

@ -0,0 +1,173 @@
/*
* 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 "kernel_applib_state.h"
#include "applib/ui/layer.h"
#include "mcu/interrupts.h"
#include "os/mutex.h"
#include "FreeRTOS.h"
#include "task.h"
static PebbleRecursiveMutex *s_log_state_mutex = INVALID_MUTEX_HANDLE;
static bool s_log_state_task_entered[NumPebbleTask]; // which tasks have entered
// ---------------------------------------------------------------------------------------------
CompassServiceConfig **kernel_applib_get_compass_config(void) {
static CompassServiceConfig *s_compass_config;
return &s_compass_config;
}
// --------------------------------------------------------------------------------------------
AnimationState* kernel_applib_get_animation_state(void) {
static AnimationState s_kernel_animation_state;
return &s_kernel_animation_state;
}
// Get the current task. If FreeRTOS has not been initialized yet, set to KernelMain
static PebbleTask prv_get_current_task(void) {
if (pebble_task_get_handle_for_task(PebbleTask_KernelMain) == NULL) {
return PebbleTask_KernelMain;
} else {
return pebble_task_get_current();
}
}
// --------------------------------------------------------------------------------------------
// Return a pointer to the LogState to use for kernel (non app task) code. The LogState contains
// the buffers for formatting the log message.
// Returns NULL if a kernel logging operation is already in progress
LogState *kernel_applib_get_log_state(void) {
static LogState sys_log_state;
bool use_mutex;
// Return right away if we re-entered from the same task For example, if we hit an assert while
// trying to grab the s_log_state_mutex mutex below and tried to log an error.
PebbleTask task = prv_get_current_task();
if (s_log_state_task_entered[task]) {
return NULL;
}
s_log_state_task_entered[task] = true;
// We have 3 possible phases of operation:
// 1.) Before FreeRTOS has been initialized - only 1 "task", no mutexes available
// 2.) After FreeRTOS, but before our mutex has been created (via kernel_applib_init())
// 3.) After our mutex has been created.
// In phase 1, we don't bother taking the mutex but still log
// In phase 2, we just return without logging. It is too dangerous to have
// possibly multiple tasks using logging without mutex support
// In phase 3, we log after locking the mutex only.
// Note, if we are in an ISR or critical section in any of these phases, we cannot use a mutex
if ((pebble_task_get_handle_for_task(PebbleTask_KernelMain) == NULL) || mcu_state_is_isr()
|| portIN_CRITICAL() || (xTaskGetSchedulerState() != taskSCHEDULER_RUNNING)) {
// phase 1 || in an ISR || in a critical section
use_mutex = false;
} else if (s_log_state_mutex == INVALID_MUTEX_HANDLE) {
// phase 2
dbgserial_putstr("LOGGING DISABLED");
goto exit_fail;
} else {
// phase 3
use_mutex = true;
}
if (use_mutex) {
// Logging operations shouldn't take long to complete. Use a timeout in case we run into
// an unlikely deadlock situation (one task doing a synchronous log to flash and another task
// trying to log from flash code)
bool success = mutex_lock_recursive_with_timeout(s_log_state_mutex, 1000);
if (!success) {
dbgserial_putstr("kernel_applib_get_log_state timeout error");
goto exit_fail;
}
}
// Return if re-entered (logging while logging). This can happen for example if one task
// grabbed the context from an ISR or critical section and another grabbed it using the mutex
if (sys_log_state.in_progress) {
if (use_mutex) {
mutex_unlock_recursive(s_log_state_mutex);
}
goto exit_fail;
}
sys_log_state.in_progress = true;
return &sys_log_state;
exit_fail:
s_log_state_task_entered[task] = false;
return NULL;
}
// --------------------------------------------------------------------------------------------
// Release the LogState buffer obtained by kernel_applib_get_log_state()
void kernel_applib_release_log_state(LogState *state) {
state->in_progress = false;
// For phase 1 & when in an ISR, there is no mutex available
if (!portIN_CRITICAL() && !mcu_state_is_isr() &&
(xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) &&
(s_log_state_mutex != INVALID_MUTEX_HANDLE)) {
mutex_unlock_recursive(s_log_state_mutex);
}
// Clear the re-entrancy flag for this task
PebbleTask task = prv_get_current_task();
s_log_state_task_entered[task] = false;
}
// ---------------------------------------------------------------------------------------------
EventServiceInfo* kernel_applib_get_event_service_state(void) {
static EventServiceInfo s_event_service_state;
return &s_event_service_state;
}
// --------------------------------------------------------------------------------------------
TickTimerServiceState* kernel_applib_get_tick_timer_service_state(void) {
static TickTimerServiceState s_tick_timer_service_state;
return &s_tick_timer_service_state;
}
// -----------------------------------------------------------------------------------------------------------
ConnectionServiceState* kernel_applib_get_connection_service_state(void) {
static ConnectionServiceState s_connection_service_state;
return &s_connection_service_state;
}
// -----------------------------------------------------------------------------------------------------------
BatteryStateServiceState* kernel_applib_get_battery_state_service_state(void) {
static BatteryStateServiceState s_battery_state_service_state;
return &s_battery_state_service_state;
}
Layer** kernel_applib_get_layer_tree_stack(void) {
static Layer* layer_tree_stack[LAYER_TREE_STACK_SIZE];
return layer_tree_stack;
}
// -------------------------------------------------------------------------------------------------------------
void kernel_applib_init(void) {
s_log_state_mutex = mutex_create_recursive();
connection_service_state_init(kernel_applib_get_connection_service_state());
battery_state_service_state_init(kernel_applib_get_battery_state_service_state());
}

View file

@ -0,0 +1,50 @@
/*
* 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.
*/
#pragma once
#include "applib/ui/animation_private.h"
#include "kernel/logging_private.h"
#include "applib/accel_service_private.h"
#include "applib/tick_timer_service_private.h"
#include "applib/compass_service_private.h"
#include "applib/battery_state_service.h"
#include "applib/battery_state_service_private.h"
#include "applib/connection_service.h"
#include "applib/connection_service_private.h"
void kernel_applib_init(void);
AnimationState *kernel_applib_get_animation_state(void);
LogState *kernel_applib_get_log_state(void);
void kernel_applib_release_log_state(LogState *state);
CompassServiceConfig **kernel_applib_get_compass_config(void);
EventServiceInfo* kernel_applib_get_event_service_state(void);
TickTimerServiceState *kernel_applib_get_tick_timer_service_state(void);
ConnectionServiceState* kernel_applib_get_connection_service_state(void);
BatteryStateServiceState* kernel_applib_get_battery_state_service_state(void);
struct Layer;
typedef struct Layer Layer;
Layer** kernel_applib_get_layer_tree_stack(void);

View file

@ -0,0 +1,80 @@
/*
* 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 "drivers/task_watchdog.h"
#include "kernel_heap.h"
#include "mcu/interrupts.h"
#include "services/common/analytics/analytics.h"
#define CMSIS_COMPATIBLE
#include <mcu.h>
static Heap s_kernel_heap;
static bool s_interrupts_disabled_by_heap;
static uint32_t s_pri_mask; // cache basepri mask we restore to in heap_unlock
// Locking callbacks for our kernel heap.
// FIXME: Note that we use __set_BASEPRI() instead of a mutex because our heap
// has to be used before we even initialize FreeRTOS. We don't use
// __disable_irq() because we want to catch any hangs in the heap code with our
// high priority watchdog so that a coredump is triggered.
static void prv_heap_lock(void *ctx) {
if (mcu_state_are_interrupts_enabled()) {
s_pri_mask = __get_BASEPRI();
__set_BASEPRI((TASK_WATCHDOG_PRIORITY + 1) << (8 - __NVIC_PRIO_BITS));
s_interrupts_disabled_by_heap = true;
}
}
static void prv_heap_unlock(void *ctx) {
if (s_interrupts_disabled_by_heap) {
__set_BASEPRI(s_pri_mask);
s_interrupts_disabled_by_heap = false;
}
}
void kernel_heap_init(void) {
extern int _heap_start;
extern int _heap_end;
heap_init(&s_kernel_heap, &_heap_start, &_heap_end, true);
heap_set_lock_impl(&s_kernel_heap, (HeapLockImpl) {
.lock_function = prv_heap_lock,
.unlock_function = prv_heap_unlock
});
}
void analytics_external_collect_kernel_heap_stats(void) {
uint32_t headroom = heap_get_minimum_headroom(&s_kernel_heap);
// Reset the high water mark so we can see if there are certain periods of time
// where we really tax the heap
s_kernel_heap.high_water_mark = s_kernel_heap.current_size;
analytics_set(ANALYTICS_DEVICE_METRIC_KERNEL_HEAP_MIN_HEADROOM_BYTES, headroom,
AnalyticsClient_System);
}
Heap* kernel_heap_get(void) {
return &s_kernel_heap;
}
// Serial Commands
///////////////////////////////////////////////////////////
#ifdef MALLOC_INSTRUMENTATION
void command_dump_malloc_kernel(void) {
heap_dump_malloc_instrumentation_to_dbgserial(&s_kernel_heap);
}
#endif

View file

@ -0,0 +1,24 @@
/*
* 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.
*/
#pragma once
#include "util/heap.h"
void kernel_heap_init(void);
Heap* kernel_heap_get(void);

197
src/fw/kernel/logging.c Normal file
View file

@ -0,0 +1,197 @@
/*
* 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 "pulse_logging.h"
#include "pebble_tasks.h"
#include "logging_private.h"
#include "util/stack_info.h"
#include "console/console_internal.h"
#include "console/prompt.h"
#include "console/serial_console.h"
#include "debug/advanced_logging.h"
#include "drivers/rtc.h"
#include "system/logging.h"
#include "mcu/interrupts.h"
#include "mcu/privilege.h"
#include "util/math.h"
#include "util/net.h"
#include "util/string.h"
#include "FreeRTOS.h"
#include "task.h"
#include <ctype.h>
#include <stdio.h>
#include <time.h>
#ifndef PBL_LOG_LEVEL
#define PBL_LOG_LEVEL LOG_LEVEL_DEBUG
#endif
int g_pbl_log_level = PBL_LOG_LEVEL;
bool g_pbl_log_enabled = true;
static bool prv_check_serial_log_enabled(int level) {
return (g_pbl_log_enabled) &&
(level == LOG_LEVEL_ALWAYS ||
(level <= g_pbl_log_level));
}
#if !PULSE_EVERYWHERE
#define TIMESTAMP_BUFFER_SIZE 14
static void prv_log_timestamp(void) {
// Enough stack space to use sprintfs?
uint32_t stack_space = stack_free_bytes();
if (stack_space < LOGGING_MIN_STACK_FOR_SPRINTF) {
serial_console_write_log_message(LOGGING_STACK_FULL_MSG);
serial_console_write_log_message(" ");
return;
}
char buffer[TIMESTAMP_BUFFER_SIZE];
time_t time_seconds;
uint16_t time_ms;
rtc_get_time_ms(&time_seconds, &time_ms);
struct tm time_seconds_calendar;
gmtime_r(&time_seconds, &time_seconds_calendar);
sniprintf(buffer, TIMESTAMP_BUFFER_SIZE, "%02u:%02u:%02u.%03u ",
time_seconds_calendar.tm_hour, time_seconds_calendar.tm_min, time_seconds_calendar.tm_sec, time_ms);
serial_console_write_log_message(buffer);
}
static void prv_log_serial(
uint8_t log_level, const char* src_filename, int src_line_number, const char* message) {
if (!serial_console_is_logging_enabled() && log_level != LOG_LEVEL_ALWAYS) {
return;
}
// Log the log level and the current task+privilege level
{
unsigned char task_char = pebble_task_get_char(pebble_task_get_current());
if (mcu_state_is_privileged()) {
task_char = toupper(task_char);
}
char buffer[] = { pbl_log_get_level_char(log_level), ' ', task_char, ' ', 0 };
serial_console_write_log_message(buffer);
}
// Start out with the timestamp
prv_log_timestamp();
// Write out the filename
src_filename = GET_FILE_NAME(src_filename);
serial_console_write_log_message(src_filename);
// Write out the line number
{
char line_number_buffer[12];
itoa_int(src_line_number, line_number_buffer, 10);
serial_console_write_log_message(":");
serial_console_write_log_message(line_number_buffer);
serial_console_write_log_message("> ");
}
// Write the actual log message.
serial_console_write_log_message(message);
// Append our newlines and our trailing null
serial_console_write_log_message("\r\n");
}
#endif // PULSE_EVERYWHERE
void kernel_pbl_log_serial(LogBinaryMessage *log_message, bool async) {
if (!prv_check_serial_log_enabled(log_message->log_level)) {
return;
}
#if PULSE_EVERYWHERE
if (async) {
pulse_logging_log(log_message->log_level, log_message->filename,
htons(log_message->line_number), log_message->message);
} else {
pulse_logging_log_sync(
log_message->log_level, log_message->filename,
htons(log_message->line_number), log_message->message);
}
#else
prv_log_serial(log_message->log_level, log_message->filename,
htons(log_message->line_number), log_message->message);
#endif
}
void kernel_pbl_log_flash(LogBinaryMessage *log_message, bool async) {
int length = sizeof(*log_message) + log_message->message_length;
static const uint8_t FLASH_LOG_LEVEL = LOG_LEVEL_INFO;
if (g_pbl_log_enabled &&
(log_message->log_level == LOG_LEVEL_ALWAYS ||
(log_message->log_level <= FLASH_LOG_LEVEL))) {
pbl_log_advanced((const char*) log_message, length, async);
}
}
void kernel_pbl_log(LogBinaryMessage* log_message, bool async) {
kernel_pbl_log_serial(log_message, async);
if (!portIN_CRITICAL() && !mcu_state_is_isr() &&
xTaskGetSchedulerState() != taskSCHEDULER_SUSPENDED) {
kernel_pbl_log_flash(log_message, async);
}
}
void kernel_pbl_log_from_fault_handler(
const char *src_filename, uint16_t src_line_number, const char *message) {
#if PULSE_EVERYWHERE
pulse_logging_log_sync(LOG_LEVEL_ALWAYS, src_filename,
src_line_number, message);
#else
serial_console_write_log_message(message);
serial_console_write_log_message("\r\n");
#endif
}
void kernel_pbl_log_from_fault_handler_fmt(
const char *src_filename, uint16_t src_line_number, char *buffer,
unsigned int buffer_size, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vsniprintf(buffer, buffer_size, fmt, ap);
va_end(ap);
kernel_pbl_log_from_fault_handler(src_filename, src_line_number, buffer);
}
// Serial Commands
///////////////////////////////////////////////////////////
void command_log_level_set(const char* level) {
char buffer[32];
g_pbl_log_level = atoi(level);
prompt_send_response_fmt(buffer, 32, "Log level set to: %i", g_pbl_log_level);
}
void command_log_level_get(void) {
char buffer[32];
prompt_send_response_fmt(buffer, 32, "Log level: %i", g_pbl_log_level);
}

View file

@ -0,0 +1,66 @@
/*
* 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.
*/
#pragma once
#include "system/logging.h"
#include "util/attributes.h"
#include <stdint.h>
#include <stdbool.h>
typedef struct PACKED LogBinaryMessage {
uint32_t timestamp;
uint8_t log_level;
uint8_t message_length;
uint16_t line_number;
char filename[16];
char message[];
} LogBinaryMessage;
//! This structure encapsulates the buffers and state used for formatting a log message.
typedef struct {
bool in_progress; // Set true while a log is in progress
char buffer[LOG_BUFFER_LENGTH]; // For construction of the final log message
} LogState;
//! Return a single character representing the current log level. Used in serial logging.
char pbl_log_get_level_char(const uint8_t log_level);
//! Log a message to whatever specific channels are appropriate based on context and configuration.
//! Internally calls kernel_pbl_log_serial and kernel_pbl_log_flash.
void kernel_pbl_log(LogBinaryMessage* log_message, bool async);
//! Force a log message out the serial channel.
void kernel_pbl_log_serial(LogBinaryMessage *log_message, bool async);
//! Force a log message out the serial channel from a fault handler or
//! other context where OS services are unavailable or can't be trusted,
//! and where stack space is at a premium.
void kernel_pbl_log_from_fault_handler(
const char *src_filename, uint16_t src_line_number, const char *message);
void kernel_pbl_log_from_fault_handler_fmt(
const char *src_filename, uint16_t src_line_number, char *buffer,
unsigned int buffer_len, const char *fmt, ...);
#define PBL_LOG_FROM_FAULT_HANDLER(message) \
kernel_pbl_log_from_fault_handler(__FILE_NAME__, __LINE__, message)
#define PBL_LOG_FROM_FAULT_HANDLER_FMT(buffer, buffer_len, fmt, ...) \
kernel_pbl_log_from_fault_handler_fmt( \
__FILE_NAME__, __LINE__, buffer, buffer_len, fmt, __VA_ARGS__)

108
src/fw/kernel/low_power.c Normal file
View file

@ -0,0 +1,108 @@
/*
* 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 "kernel/low_power.h"
#include "apps/prf_apps/prf_low_power_app.h"
#include "drivers/rtc.h"
#include "kernel/event_loop.h"
#include "kernel/ui/modals/modal_manager.h"
#include "kernel/util/standby.h"
#include "mfg/mfg_mode/mfg_factory_mode.h"
#include "process_management/app_manager.h"
#include "process_management/worker_manager.h"
#include "services/common/analytics/analytics.h"
#include "services/common/system_task.h"
#include "services/runlevel.h"
#include <stdbool.h>
static bool s_low_power_active, s_prev_low_power_active;
static TimerID s_toggle_timer = TIMER_INVALID_ID;
static void prv_low_power_launcher_task_callback(void *unused) {
if (s_low_power_active == s_prev_low_power_active) {
// We settled into the same state as before toggling.
return;
}
if (s_low_power_active) {
analytics_stopwatch_start(ANALYTICS_DEVICE_METRIC_WATCH_ONLY_TIME,
AnalyticsClient_System);
worker_manager_disable();
services_set_runlevel(RunLevel_LowPower);
} else {
analytics_stopwatch_stop(ANALYTICS_DEVICE_METRIC_WATCH_ONLY_TIME);
worker_manager_enable();
services_set_runlevel(RunLevel_Normal);
}
s_prev_low_power_active = s_low_power_active;
}
static void prv_low_power_toggle_timer_callback(void* data) {
launcher_task_add_callback(prv_low_power_launcher_task_callback, data);
}
static void prv_low_power_transition(bool active) {
s_low_power_active = active;
if (s_toggle_timer == TIMER_INVALID_ID) {
s_toggle_timer = new_timer_create();
}
// Rapid charger connection changes (aligning magnetic connector for example)
// will cause repeated low power on/off requests. Require that a few seconds
// elapse without further transitions before acting upon it to give us some
// time to settle on one state or the other.
new_timer_start(s_toggle_timer, 3000, prv_low_power_toggle_timer_callback,
NULL, 0 /*flags*/);
// FIXME PBL-XXXXX: This should be in a shell/prf/battery_ui_fsm.c
#if RECOVERY_FW
if (active) {
app_manager_launch_new_app(&(AppLaunchConfig) {
.md = prf_low_power_app_get_info(),
});
} else {
// In MFG mode, disable low power mode above but don't close the app.
if (mfg_is_mfg_mode()) {
return;
}
app_manager_close_current_app(true);
}
#endif
}
void low_power_standby(void) {
analytics_stopwatch_stop(ANALYTICS_DEVICE_METRIC_WATCH_ONLY_TIME);
enter_standby(RebootReasonCode_LowBattery);
}
void low_power_enter(void) {
#if RECOVERY_FW
if (mfg_is_mfg_mode()) {
return;
}
#endif
prv_low_power_transition(true);
}
void low_power_exit(void) {
prv_low_power_transition(false);
}
bool low_power_is_active(void) {
return s_low_power_active;
}

31
src/fw/kernel/low_power.h Normal file
View file

@ -0,0 +1,31 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
//! Standby while in Low Power Mode
void low_power_standby(void);
//! Enter low power state
void low_power_enter(void);
//! Leave low power state
void low_power_exit(void);
//! Get low power state
bool low_power_is_active(void);

View file

@ -0,0 +1,288 @@
/*
* 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 "memory_layout.h"
#include "kernel/logging_private.h"
#include "system/passert.h"
#include "util/math.h"
#include "util/size.h"
#include "util/string.h"
#include "kernel/mpu_regions.auto.h"
#include <inttypes.h>
#include <string.h>
static const char* const MEMORY_REGION_NAMES[8] = {
"UNPRIV_FLASH",
"UNPRIV_RO_BSS",
"UNPRIV_RO_DATA",
"ISR_STACK_GUARD",
"Task Specific 1",
"Task Specific 2",
"Task Specific 3",
"Task Specific 4"
};
void memory_layout_dump_mpu_regions_to_dbgserial(void) {
static const int NUM_REGIONS = 8;
char buffer[90];
for (int i = 0; i < NUM_REGIONS; ++i) {
MpuRegion region = mpu_get_region(i);
if (!region.enabled) {
PBL_LOG_FROM_FAULT_HANDLER_FMT(buffer, sizeof(buffer), "%u Not enabled", i);
continue;
}
PBL_LOG_FROM_FAULT_HANDLER_FMT(
buffer, sizeof(buffer),
"%u < %-22s>: Addr %p Size 0x%08"PRIx32" Priv: %c%c User: %c%c",
i, MEMORY_REGION_NAMES[i], (void*) region.base_address, region.size,
region.priv_read ? 'R' : ' ', region.priv_write ? 'W' : ' ',
region.user_read ? 'R' : ' ', region.user_write ? 'W' : ' ');
if (region.disabled_subregions) {
PBL_LOG_FROM_FAULT_HANDLER_FMT(
buffer, sizeof(buffer),
" Disabled Subregions: %02x", region.disabled_subregions);
}
}
}
#ifdef UNITTEST
static const uint32_t __privileged_functions_start__ = 0;
static const uint32_t __privileged_functions_size__ = 0;
static const uint32_t __unpriv_ro_bss_start__ = 0;
static const uint32_t __unpriv_ro_bss_size__ = 0;
static const uint32_t __isr_stack_start__ = 0;
static const uint32_t __stack_guard_size__ = 0;
static const uint32_t __APP_RAM__ = 0;
static const uint32_t __WORKER_RAM__ = 0;
static const uint32_t __FLASH_start__ = 0;
static const uint32_t __FLASH_size__ = 0;
static const uint32_t __kernel_main_stack_start__ = 0;
static const uint32_t __kernel_bg_stack_start__ = 0;
#else
extern const uint32_t __privileged_functions_start__[];
extern const uint32_t __privileged_functions_size__[];
extern const uint32_t __unpriv_ro_bss_start__[];
extern const uint32_t __unpriv_ro_bss_size__[];
extern const uint32_t __isr_stack_start__[];
extern const uint32_t __stack_guard_size__[];
extern const uint32_t __APP_RAM__[];
extern const uint32_t __WORKER_RAM__[];
extern const uint32_t __FLASH_start__[];
extern const uint32_t __FLASH_size__[];
extern const uint32_t __kernel_main_stack_start__[];
extern const uint32_t __kernel_bg_stack_start__[];
#endif
// Kernel read only RAM. Parts of RAM that it's kosher for unprivileged apps to read
static const MpuRegion s_readonly_bss_region = {
.region_num = MemoryRegion_ReadOnlyBss,
.enabled = true,
.base_address = (uint32_t) __unpriv_ro_bss_start__,
.size = (uint32_t) __unpriv_ro_bss_size__,
.cache_policy = MpuCachePolicy_WriteBackWriteAllocate,
.priv_read = true,
.priv_write = true,
.user_read = true,
.user_write = false
};
// ISR stack guard
static const MpuRegion s_isr_stack_guard_region = {
.region_num = MemoryRegion_IsrStackGuard,
.enabled = true,
.base_address = (uint32_t) __isr_stack_start__,
.size = (uint32_t) __stack_guard_size__,
.cache_policy = MpuCachePolicy_NotCacheable,
.priv_read = false,
.priv_write = false,
.user_read = false,
.user_write = false
};
static const MpuRegion s_app_stack_guard_region = {
.region_num = MemoryRegion_TaskStackGuard,
.enabled = true,
.base_address = (uint32_t) __APP_RAM__,
.size = (uint32_t) __stack_guard_size__,
.cache_policy = MpuCachePolicy_NotCacheable,
.priv_read = false,
.priv_write = false,
.user_read = false,
.user_write = false
};
static const MpuRegion s_worker_stack_guard_region = {
.region_num = MemoryRegion_TaskStackGuard,
.enabled = true,
.base_address = (uint32_t) __WORKER_RAM__,
.size = (uint32_t) __stack_guard_size__,
.cache_policy = MpuCachePolicy_NotCacheable,
.priv_read = false,
.priv_write = false,
.user_read = false,
.user_write = false
};
static const MpuRegion s_app_region = {
.region_num = MemoryRegion_AppRAM,
.enabled = true,
.base_address = MPU_REGION_APP_BASE_ADDRESS,
.size = MPU_REGION_APP_SIZE,
.disabled_subregions = MPU_REGION_APP_DISABLED_SUBREGIONS,
.cache_policy = MpuCachePolicy_WriteBackWriteAllocate,
.priv_read = true,
.priv_write = true,
};
static const MpuRegion s_worker_region = {
.region_num = MemoryRegion_WorkerRAM,
.enabled = true,
.base_address = MPU_REGION_WORKER_BASE_ADDRESS,
.size = MPU_REGION_WORKER_SIZE,
.disabled_subregions = MPU_REGION_WORKER_DISABLED_SUBREGIONS,
.cache_policy = MpuCachePolicy_WriteBackWriteAllocate,
.priv_read = true,
.priv_write = true,
};
static const MpuRegion s_microflash_region = {
.region_num = MemoryRegion_Flash,
.enabled = true,
.base_address = (uint32_t) __FLASH_start__,
.size = (uint32_t) __FLASH_size__,
.cache_policy = MpuCachePolicy_WriteThrough,
.priv_read = true,
.priv_write = false,
.user_read = true,
.user_write = false
};
static const MpuRegion s_kernel_main_stack_guard_region = {
.region_num = MemoryRegion_TaskStackGuard,
.enabled = true,
.base_address = (uint32_t) __kernel_main_stack_start__,
.size = (uint32_t) __stack_guard_size__,
.cache_policy = MpuCachePolicy_NotCacheable,
.priv_read = false,
.priv_write = false,
.user_read = false,
.user_write = false
};
static const MpuRegion s_kernel_bg_stack_guard_region = {
.region_num = MemoryRegion_TaskStackGuard,
.enabled = true,
.base_address = (uint32_t) __kernel_bg_stack_start__,
.size = (uint32_t) __stack_guard_size__,
.cache_policy = MpuCachePolicy_NotCacheable,
.priv_read = false,
.priv_write = false,
.user_read = false,
.user_write = false
};
void memory_layout_setup_mpu(void) {
// Flash parts...
// Read only for executing code and loading data out of.
// Unprivileged flash, by default anyone can read any part of flash.
mpu_set_region(&s_microflash_region);
// RAM parts
// The background memory map only allows privileged access. We need to add aditional regions to
// enable access to unprivileged code.
mpu_set_region(&s_readonly_bss_region);
mpu_set_region(&s_isr_stack_guard_region);
mpu_enable();
}
const MpuRegion* memory_layout_get_app_region(void) {
return &s_app_region;
}
const MpuRegion* memory_layout_get_readonly_bss_region(void) {
return &s_readonly_bss_region;
}
const MpuRegion* memory_layout_get_app_stack_guard_region(void) {
return &s_app_stack_guard_region;
}
const MpuRegion* memory_layout_get_worker_region(void) {
return &s_worker_region;
}
const MpuRegion* memory_layout_get_worker_stack_guard_region(void) {
return &s_worker_stack_guard_region;
}
const MpuRegion* memory_layout_get_microflash_region(void) {
return &s_microflash_region;
}
const MpuRegion* memory_layout_get_kernel_main_stack_guard_region(void) {
return &s_kernel_main_stack_guard_region;
}
const MpuRegion* memory_layout_get_kernel_bg_stack_guard_region(void) {
return &s_kernel_bg_stack_guard_region;
}
bool memory_layout_is_pointer_in_region(const MpuRegion *region, const void *ptr) {
uintptr_t p = (uintptr_t) ptr;
return (p >= region->base_address && p < (region->base_address + region->size));
}
bool memory_layout_is_buffer_in_region(const MpuRegion *region, const void *buf, size_t length) {
return memory_layout_is_pointer_in_region(region, buf) && memory_layout_is_pointer_in_region(region, (char *)buf + length - 1);
}
bool memory_layout_is_cstring_in_region(const MpuRegion *region, const char *str, size_t max_length) {
uintptr_t region_end = region->base_address + region->size;
if ((uintptr_t) str < region->base_address || (uintptr_t) str >= region_end) {
return false;
}
const char *str_max_end = MIN((const char*) region_end, str + max_length);
size_t str_len = strnlen(str, str_max_end - str);
if (str[str_len] != 0) {
// No null between here and the end of the memory region.
return false;
}
return true;
}

View file

@ -0,0 +1,54 @@
/*
* 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.
*/
#pragma once
#include "drivers/mpu.h"
#include <stddef.h>
#define KERNEL_READONLY_DATA SECTION(".kernel_unpriv_ro_bss")
enum MemoryRegionAssignments {
MemoryRegion_Flash,
MemoryRegion_ReadOnlyBss,
MemoryRegion_ReadOnlyData,
MemoryRegion_IsrStackGuard,
MemoryRegion_AppRAM,
MemoryRegion_WorkerRAM,
MemoryRegion_TaskStackGuard,
MemoryRegion_Task4
};
void memory_layout_dump_mpu_regions_to_dbgserial(void);
void memory_layout_setup_mpu(void);
const MpuRegion* memory_layout_get_app_region(void);
const MpuRegion* memory_layout_get_app_stack_guard_region(void);
const MpuRegion* memory_layout_get_readonly_bss_region(void);
const MpuRegion* memory_layout_get_microflash_region(void);
const MpuRegion* memory_layout_get_worker_region(void);
const MpuRegion* memory_layout_get_worker_stack_guard_region(void);
const MpuRegion* memory_layout_get_kernel_main_stack_guard_region(void);
const MpuRegion* memory_layout_get_kernel_bg_stack_guard_region(void);
bool memory_layout_is_pointer_in_region(const MpuRegion *region, const void *ptr);
bool memory_layout_is_buffer_in_region(const MpuRegion *region, const void *buf, size_t length);
bool memory_layout_is_cstring_in_region(const MpuRegion *region, const char *str, size_t max_length);

View file

@ -0,0 +1,23 @@
/*
* 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.
*/
#define MPU_REGION_APP_BASE_ADDRESS @APP_BASE_ADDRESS@
#define MPU_REGION_APP_SIZE @APP_SIZE@
#define MPU_REGION_APP_DISABLED_SUBREGIONS @APP_DISABLED_SUBREGIONS@
#define MPU_REGION_WORKER_BASE_ADDRESS @WORKER_BASE_ADDRESS@
#define MPU_REGION_WORKER_SIZE @WORKER_SIZE@
#define MPU_REGION_WORKER_DISABLED_SUBREGIONS @WORKER_DISABLED_SUBREGIONS@

62
src/fw/kernel/panic.c Normal file
View file

@ -0,0 +1,62 @@
/*
* 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 "kernel/panic.h"
#include "kernel/event_loop.h"
#include "kernel/ui/modals/modal_manager.h"
#include "process_management/app_manager.h"
#include "shell/system_app_state_machine.h"
#include "system/logging.h"
#include "system/passert.h"
static uint32_t s_current_error = 0;
void launcher_panic(uint32_t error_code) {
PBL_ASSERT_TASK(PebbleTask_KernelMain);
s_current_error = error_code;
PBL_LOG(LOG_LEVEL_ERROR, "!!!SAD WATCH 0x%"PRIX32" SAD WATCH!!!", error_code);
if (modal_manager_get_top_window()) {
modal_manager_pop_all();
}
modal_manager_set_min_priority(ModalPriorityMax);
system_app_state_machine_panic();
}
uint32_t launcher_panic_get_current_error(void) {
return s_current_error;
}
void command_sim_panic_cb(void* data) {
PebbleEvent event = {
.type = PEBBLE_PANIC_EVENT,
.panic = {
.error_code = (uint32_t)data,
},
};
event_put(&event);
}
extern void command_sim_panic(const char *error_code_str) {
uint32_t error_code = atoi(error_code_str);
launcher_task_add_callback(command_sim_panic_cb, (void*) error_code);
}

24
src/fw/kernel/panic.h Normal file
View file

@ -0,0 +1,24 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
void launcher_panic(uint32_t error_code);
uint32_t launcher_panic_get_current_error(void);

353
src/fw/kernel/pbl_malloc.c Normal file
View file

@ -0,0 +1,353 @@
/*
* 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 "pbl_malloc.h"
#include "kernel_heap.h"
#include "pebble_tasks.h"
#include "process_management/app_manager.h"
#include "process_state/app_state/app_state.h"
#include "process_state/worker_state/worker_state.h"
#include "system/passert.h"
#include "util/heap.h"
#include <string.h>
Heap *task_heap_get_for_current_task(void) {
if (pebble_task_get_current() == PebbleTask_App) {
return app_state_get_heap();
} else if (pebble_task_get_current() == PebbleTask_Worker) {
return worker_state_get_heap();
}
return kernel_heap_get();
}
static char* prv_strdup(Heap *heap, const char* s, uintptr_t lr) {
char *dup = heap_zalloc(heap, strlen(s) + 1, lr);
if (dup) {
strcpy(dup, s);
}
return dup;
}
// task_* functions that map to other heaps depending on the current task
///////////////////////////////////////////////////////////
#if defined(MALLOC_INSTRUMENTATION)
void *task_malloc_with_pc(size_t bytes, uintptr_t client_pc) {
return heap_malloc(task_heap_get_for_current_task(), bytes, client_pc);
}
#endif
void *task_malloc(size_t bytes) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
return heap_malloc(task_heap_get_for_current_task(), bytes, saved_lr);
}
void *task_malloc_check(size_t bytes) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
Heap *heap = task_heap_get_for_current_task();
void *mem = heap_malloc(heap, bytes, saved_lr);
if (!mem && bytes != 0) {
PBL_CROAK_OOM(bytes, saved_lr, heap);
}
return mem;
}
#if defined(MALLOC_INSTRUMENTATION)
void task_free_with_pc(void *ptr, uintptr_t client_pc) {
heap_free(task_heap_get_for_current_task(), ptr, client_pc);
}
#endif
void task_free(void* ptr) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
heap_free(task_heap_get_for_current_task(), ptr, saved_lr);
}
void *task_realloc(void* ptr, size_t size) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
return heap_realloc(task_heap_get_for_current_task(), ptr, size, saved_lr);
}
void *task_zalloc(size_t size) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
return heap_zalloc(task_heap_get_for_current_task(), size, saved_lr);
}
void *task_zalloc_check(size_t bytes) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
Heap *heap = task_heap_get_for_current_task();
void *mem = heap_zalloc(heap, bytes, saved_lr);
if (!mem && bytes != 0) {
PBL_CROAK_OOM(bytes, saved_lr, heap);
}
return mem;
}
void *task_calloc(size_t count, size_t size) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
return heap_calloc(task_heap_get_for_current_task(), count, size, saved_lr);
}
void *task_calloc_check(size_t count, size_t size) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
Heap *heap = task_heap_get_for_current_task();
void *mem = heap_calloc(heap, count, size, saved_lr);
const size_t bytes = count * size;
if (!mem && bytes != 0) {
PBL_CROAK_OOM(bytes, saved_lr, heap);
}
return mem;
}
char *task_strdup(const char *s) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
return prv_strdup(task_heap_get_for_current_task(), s, saved_lr);
}
// app_* functions that allocate on the app heap
///////////////////////////////////////////////////////////
void *app_malloc(size_t bytes) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
return heap_malloc(app_state_get_heap(), bytes, saved_lr);
}
void *app_malloc_check(size_t bytes) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
Heap *heap = app_state_get_heap();
void *mem = heap_malloc(heap, bytes, saved_lr);
if (!mem && bytes != 0) {
PBL_CROAK_OOM(bytes, saved_lr, heap);
}
return mem;
}
void app_free(void *ptr) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
heap_free(app_state_get_heap(), ptr, saved_lr);
}
void *app_realloc(void *ptr, size_t bytes) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
return heap_realloc(app_state_get_heap(), ptr, bytes, saved_lr);
}
void *app_zalloc(size_t size) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
return heap_zalloc(app_state_get_heap(), size, saved_lr);
}
void *app_zalloc_check(size_t bytes) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
Heap *heap = app_state_get_heap();
void *mem = heap_zalloc(heap, bytes, saved_lr);
if (!mem && bytes != 0) {
PBL_CROAK_OOM(bytes, saved_lr, heap);
}
return mem;
}
void *app_calloc(size_t count, size_t size) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
return (heap_calloc(app_state_get_heap(), count, size, saved_lr));
}
void *app_calloc_check(size_t count, size_t size) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
Heap *heap = app_state_get_heap();
void *mem = heap_calloc(heap, count, size, saved_lr);
const size_t bytes = count * size;
if (!mem && bytes != 0) {
PBL_CROAK_OOM(bytes, saved_lr, heap);
}
return mem;
}
char *app_strdup(const char *s) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
return prv_strdup(app_state_get_heap(), s, saved_lr);
}
// kernel_* functions that allocate on the kernel heap
///////////////////////////////////////////////////////////
void *kernel_malloc(size_t bytes) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
return heap_malloc(kernel_heap_get(), bytes, saved_lr);
}
void *kernel_malloc_check(size_t bytes) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
Heap *heap = kernel_heap_get();
void *mem = heap_malloc(heap, bytes, saved_lr);
if (!mem && bytes != 0) {
PBL_CROAK_OOM(bytes, saved_lr, heap);
}
return mem;
}
void *kernel_calloc(size_t count, size_t size) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
return (heap_calloc(kernel_heap_get(), count, size, saved_lr));
}
void *kernel_calloc_check(size_t count, size_t size) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
Heap *heap = kernel_heap_get();
void *mem = heap_calloc(heap, count, size, saved_lr);
const size_t bytes = count * size;
if (!mem && bytes != 0) {
PBL_CROAK_OOM(bytes, saved_lr, heap);
}
return mem;
}
void *kernel_realloc(void *ptr, size_t bytes) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
return heap_realloc(kernel_heap_get(), ptr, bytes, saved_lr);
}
void *kernel_zalloc(size_t size) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
return heap_zalloc(kernel_heap_get(), size, saved_lr);
}
void *kernel_zalloc_check(size_t bytes) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
Heap *heap = kernel_heap_get();
void *mem = heap_zalloc(heap, bytes, saved_lr);
if (!mem && bytes != 0) {
PBL_CROAK_OOM(bytes, saved_lr, heap);
}
return mem;
}
void kernel_free(void *ptr) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
heap_free(kernel_heap_get(), ptr, saved_lr);
}
char *kernel_strdup(const char *s) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
return prv_strdup(kernel_heap_get(), s, saved_lr);
}
char *kernel_strdup_check(const char *s) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
Heap *heap = kernel_heap_get();
void *mem = prv_strdup(heap, s, saved_lr);
if (!mem) {
PBL_CROAK_OOM(strlen(s) + 1, saved_lr, heap);
}
return mem;
}
// Wrappers (Jay-Z, Tupac, etc)
// We want to keep these around for code bases that we don't own. For example, libc will want
// malloc to exist, and libc should use the appropriate heap based on the task.
////////////////////////////////////////////////////////////
void *__wrap_malloc(size_t bytes) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
return heap_malloc(task_heap_get_for_current_task(), bytes, saved_lr);
}
void __wrap_free(void *ptr) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
heap_free(task_heap_get_for_current_task(), ptr, saved_lr);
}
void *__wrap_realloc(void *ptr, size_t size) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
return heap_realloc(task_heap_get_for_current_task(), ptr, size, saved_lr);
}
void *__wrap_calloc(size_t count, size_t size) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
return heap_calloc(task_heap_get_for_current_task(), count, size, saved_lr);
}

View file

@ -0,0 +1,68 @@
/*
* 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.
*/
#pragma once
#include <inttypes.h>
#include <stddef.h>
//! @file pbl_malloc.h
//!
//! In the firmware, we actually have multiple heaps that clients can use to malloc/free on.
//! The kernel heap is located in protected memory and is used by the kernel itself. The app
//! heap is located inside the app region and is reset between each app.
typedef struct Heap Heap;
Heap *task_heap_get_for_current_task(void);
// task_* functions map to app_* or kernel_* based on which task we're calling it on.
void *task_malloc(size_t bytes);
#if defined(MALLOC_INSTRUMENTATION)
void *task_malloc_with_pc(size_t bytes, uintptr_t client_pc);
#endif
void *task_malloc_check(size_t bytes);
void *task_realloc(void *ptr, size_t size);
void *task_zalloc(size_t size);
void *task_zalloc_check(size_t size);
void *task_calloc(size_t count, size_t size);
void *task_calloc_check(size_t count, size_t size);
void task_free(void *ptr);
#if defined(MALLOC_INSTRUMENTATION)
void task_free_with_pc(void *ptr, uintptr_t client_pc);
#endif
char* task_strdup(const char* s);
void *app_malloc(size_t bytes);
void *app_malloc_check(size_t bytes);
void *app_realloc(void *ptr, size_t bytes);
void *app_zalloc(size_t size);
void *app_zalloc_check(size_t size);
void *app_calloc(size_t count, size_t size);
void *app_calloc_check(size_t count, size_t size);
void app_free(void *ptr);
char* app_strdup(const char* s);
void *kernel_malloc(size_t bytes);
void *kernel_malloc_check(size_t bytes);
void *kernel_realloc(void *ptr, size_t bytes);
void *kernel_zalloc(size_t size);
void *kernel_zalloc_check(size_t size);
void *kernel_calloc(size_t count, size_t size);
void *kernel_calloc_check(size_t count, size_t size);
void kernel_free(void *ptr);
char *kernel_strdup(const char *s);
char *kernel_strdup_check(const char *s);

View file

@ -0,0 +1,254 @@
/*
* 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 "pebble_tasks.h"
#include "kernel/memory_layout.h"
#include "process_management/app_manager.h"
#include "process_management/worker_manager.h"
#include "services/common/analytics/analytics.h"
#include "services/common/analytics/analytics_metric.h"
#include "system/passert.h"
#include "util/size.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
TaskHandle_t g_task_handles[NumPebbleTask] KERNEL_READONLY_DATA = { 0 };
static void prv_task_register(PebbleTask task, TaskHandle_t task_handle) {
g_task_handles[task] = task_handle;
}
void pebble_task_unregister(PebbleTask task) {
g_task_handles[task] = NULL;
}
const char* pebble_task_get_name(PebbleTask task) {
if (task >= NumPebbleTask) {
if (task == PebbleTask_Unknown) {
return "Unknown";
}
WTF;
}
TaskHandle_t task_handle = g_task_handles[task];
if (!task_handle) {
return "Unknown";
}
return (const char*) pcTaskGetTaskName(task_handle);
}
// NOTE: The logging support calls toupper() this character if the task is currently running privileged, so
// these identifiers should be all lower case and case-insensitive.
char pebble_task_get_char(PebbleTask task) {
switch (task) {
case PebbleTask_KernelMain:
return 'm';
case PebbleTask_KernelBackground:
return 's';
case PebbleTask_Worker:
return 'w';
case PebbleTask_App:
return 'a';
case PebbleTask_BTCallback:
return 'b';
case PebbleTask_BTRX:
return 'c';
case PebbleTask_BTTimer:
return 'd';
case PebbleTask_NewTimers:
return 't';
case PebbleTask_PULSE:
return 'p';
case NumPebbleTask:
case PebbleTask_Unknown:
;
}
return '?';
}
PebbleTask pebble_task_get_current(void) {
TaskHandle_t task_handle = xTaskGetCurrentTaskHandle();
return pebble_task_get_task_for_handle(task_handle);
}
PebbleTask pebble_task_get_task_for_handle(TaskHandle_t task_handle) {
for (int i = 0; i < (int) ARRAY_LENGTH(g_task_handles); ++i) {
if (g_task_handles[i] == task_handle) {
return i;
}
}
return PebbleTask_Unknown;
}
TaskHandle_t pebble_task_get_handle_for_task(PebbleTask task) {
return g_task_handles[task];
}
static uint16_t prv_task_get_stack_free(PebbleTask task) {
// If task doesn't exist, return a dummy with max value
if (g_task_handles[task] == NULL) {
return 0xFFFF;
}
return uxTaskGetStackHighWaterMark(g_task_handles[task]);
}
void pebble_task_suspend(PebbleTask task) {
PBL_ASSERTN(task < NumPebbleTask);
vTaskSuspend(g_task_handles[task]);
}
void analytics_external_collect_stack_free(void) {
analytics_set(ANALYTICS_DEVICE_METRIC_STACK_FREE_KERNEL_MAIN,
prv_task_get_stack_free(PebbleTask_KernelMain), AnalyticsClient_System);
analytics_set(ANALYTICS_DEVICE_METRIC_STACK_FREE_KERNEL_BACKGROUND,
prv_task_get_stack_free(PebbleTask_KernelBackground), AnalyticsClient_System);
analytics_set(ANALYTICS_DEVICE_METRIC_STACK_FREE_BLUETOPIA_BIG,
prv_task_get_stack_free(PebbleTask_BTCallback), AnalyticsClient_System);
analytics_set(ANALYTICS_DEVICE_METRIC_STACK_FREE_BLUETOPIA_MEDIUM,
prv_task_get_stack_free(PebbleTask_BTRX), AnalyticsClient_System);
analytics_set(ANALYTICS_DEVICE_METRIC_STACK_FREE_BLUETOPIA_SMALL,
prv_task_get_stack_free(PebbleTask_BTTimer), AnalyticsClient_System);
analytics_set(ANALYTICS_DEVICE_METRIC_STACK_FREE_NEWTIMERS,
prv_task_get_stack_free(PebbleTask_NewTimers), AnalyticsClient_System);
}
QueueHandle_t pebble_task_get_to_queue(PebbleTask task) {
QueueHandle_t queue;
switch (task) {
case PebbleTask_KernelMain:
queue = event_get_to_kernel_queue(pebble_task_get_current());
break;
case PebbleTask_Worker:
queue = worker_manager_get_task_context()->to_process_event_queue;
break;
case PebbleTask_App:
queue = app_manager_get_task_context()->to_process_event_queue;
break;
case PebbleTask_KernelBackground:
queue = NULL;
break;
default:
WTF;
}
return queue;
}
void pebble_task_create(PebbleTask pebble_task, TaskParameters_t *task_params,
TaskHandle_t *handle) {
MpuRegion app_region;
MpuRegion worker_region;
switch (pebble_task) {
case PebbleTask_App:
mpu_init_region_from_region(&app_region, memory_layout_get_app_region(),
true /* allow_user_access */);
mpu_init_region_from_region(&worker_region, memory_layout_get_worker_region(),
false /* allow_user_access */);
break;
case PebbleTask_Worker:
mpu_init_region_from_region(&app_region, memory_layout_get_app_region(),
false /* allow_user_access */);
mpu_init_region_from_region(&worker_region, memory_layout_get_worker_region(),
true /* allow_user_access */);
break;
case PebbleTask_KernelMain:
case PebbleTask_KernelBackground:
case PebbleTask_BTCallback:
case PebbleTask_BTRX:
case PebbleTask_BTTimer:
case PebbleTask_NewTimers:
case PebbleTask_PULSE:
mpu_init_region_from_region(&app_region, memory_layout_get_app_region(),
false /* allow_user_access */);
mpu_init_region_from_region(&worker_region, memory_layout_get_worker_region(),
false /* allow_user_access */);
break;
default:
WTF;
}
const MpuRegion *stack_guard_region = NULL;
switch (pebble_task) {
case PebbleTask_App:
stack_guard_region = memory_layout_get_app_stack_guard_region();
break;
case PebbleTask_Worker:
stack_guard_region = memory_layout_get_worker_stack_guard_region();
break;
case PebbleTask_KernelMain:
stack_guard_region = memory_layout_get_kernel_main_stack_guard_region();
break;
case PebbleTask_KernelBackground:
stack_guard_region = memory_layout_get_kernel_bg_stack_guard_region();
break;
case PebbleTask_BTCallback:
case PebbleTask_BTRX:
case PebbleTask_BTTimer:
case PebbleTask_NewTimers:
case PebbleTask_PULSE:
break;
default:
WTF;
}
const MpuRegion *region_ptrs[portNUM_CONFIGURABLE_REGIONS] = {
&app_region,
&worker_region,
stack_guard_region,
NULL
};
mpu_set_task_configurable_regions(task_params->xRegions, region_ptrs);
TaskHandle_t new_handle;
PBL_ASSERT(xTaskCreateRestricted(task_params, &new_handle) == pdTRUE, "Could not start task %s",
task_params->pcName);
if (handle) {
*handle = new_handle;
}
prv_task_register(pebble_task, new_handle);
}
void pebble_task_configure_idle_task(void) {
// We don't have the opportunity to configure the IDLE task before FreeRTOS
// creates it, so we have to configure the MPU regions properly after the
// fact. This is only an issue on platforms with a cache, as altering the base
// address, length or cacheability attributes of MPU regions (i.e. during
// context switches) causes cache incoherency when data is read/written to the
// memory covered by the regions before or after the change. This is
// problematic from the IDLE task as ISRs inherit the MPU configuration of the
// task that is currently running at the time.
MpuRegion app_region;
MpuRegion worker_region;
mpu_init_region_from_region(&app_region, memory_layout_get_app_region(),
false /* allow_user_access */);
mpu_init_region_from_region(&worker_region, memory_layout_get_worker_region(),
false /* allow_user_access */);
const MpuRegion *region_ptrs[portNUM_CONFIGURABLE_REGIONS] = {
&app_region,
&worker_region,
NULL,
NULL
};
MemoryRegion_t region_config[portNUM_CONFIGURABLE_REGIONS] = {};
mpu_set_task_configurable_regions(region_config, region_ptrs);
vTaskAllocateMPURegions(xTaskGetIdleTaskHandle(), region_config);
}

View file

@ -0,0 +1,74 @@
/*
* 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.
*/
#pragma once
#include "kernel/memory_layout.h"
#include <stdint.h>
#include "freertos_types.h"
//! This is an enumeration of different tasks we've had in our system. Please don't rearrange
//! these numbers! For example, the value of PebbleTask_Timers is hardcoded into our syscall
//! assembly and terrible things will happen if you move this around.
typedef enum PebbleTask {
PebbleTask_KernelMain,
PebbleTask_KernelBackground,
PebbleTask_Worker,
PebbleTask_App,
PebbleTask_BTCallback, // Task Bluetooth callbacks are handled on
PebbleTask_BTRX, // Task handling inbound data from BT controller
PebbleTask_BTTimer, // Timer task - only used by cc2564x BT controller today
PebbleTask_NewTimers,
PebbleTask_PULSE,
NumPebbleTask,
PebbleTask_Unknown
} PebbleTask;
typedef uint16_t PebbleTaskBitset;
_Static_assert((1 << (8*sizeof(PebbleTaskBitset))) >= (1 << NumPebbleTask),
"The type of PebbleTaskBitset is not wide enough to "
"track all tasks in the PebbleTask enum");
void pebble_task_register(PebbleTask task, TaskHandle_t task_handle);
void pebble_task_unregister(PebbleTask task);
const char* pebble_task_get_name(PebbleTask task);
//! @return a single character that indicates the task
char pebble_task_get_char(PebbleTask task);
PebbleTask pebble_task_get_current(void);
PebbleTask pebble_task_get_task_for_handle(TaskHandle_t task_handle);
TaskHandle_t pebble_task_get_handle_for_task(PebbleTask task);
void pebble_task_suspend(PebbleTask task);
//! @return The queue handle to send events to the given task.
QueueHandle_t pebble_task_get_to_queue(PebbleTask task);
void pebble_task_create(PebbleTask pebble_task, TaskParameters_t *task_params,
TaskHandle_t *handle);
void pebble_task_configure_idle_task(void);

View file

@ -0,0 +1,271 @@
/*
* 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 "pulse_logging.h"
#include "logging_private.h"
#include "pebble_tasks.h"
#include "console/pulse.h"
#include "console/pulse_protocol_impl.h"
#include "kernel/events.h"
#include "mcu/interrupts.h"
#include "mcu/privilege.h"
#include "util/attributes.h"
#include "util/circular_buffer.h"
#include "util/math.h"
#include "util/string.h"
#include "FreeRTOS.h"
#include "task.h"
#include <ctype.h>
//! This is the format for a PULSEv2 log message when sent out over the wire.
typedef struct PACKED MessageContents {
uint8_t message_type;
char src_filename[16];
char log_level_char;
unsigned char task_char;
uint64_t time_ms;
uint16_t line_number;
//! Not null-terminated message contents
char message[128];
} MessageContents;
typedef struct PACKED {
uint64_t timestamp_ms;
uint8_t log_level;
} BufferedLogInfo;
//! For ISR contexts, we can't write log message directly. Instead we write them to this
//! circular buffer and flush them out when the ISR is completed.
static CircularBuffer s_isr_log_buffer;
//! Underlying storage for s_isr_log_buffer
static uint8_t s_isr_log_buffer_storage[256];
static uint64_t prv_get_timestamp_ms(void) {
time_t time_s;
uint16_t time_ms;
rtc_get_time_ms(&time_s, &time_ms);
return ((uint64_t) time_s * 1000) + time_ms;
}
static size_t prv_serialize_log_header(MessageContents *contents,
uint8_t log_level, uint64_t timestamp_ms, PebbleTask task,
const char *src_filename, uint16_t src_line_number) {
contents->message_type = 1; // Text
// Log the log level and the current task+privilege level
contents->log_level_char = pbl_log_get_level_char(log_level);
contents->task_char = pebble_task_get_char(task);
if (mcu_state_is_privileged()) {
contents->task_char = toupper(contents->task_char);
}
contents->time_ms = timestamp_ms;
// Obtain the filename
strncpy(contents->src_filename, GET_FILE_NAME(src_filename), sizeof(contents->src_filename));
// Obtain the line number
contents->line_number = src_line_number;
return offsetof(MessageContents, message);
}
//! Serialize a message into contents, returning the number of bytes used.
static size_t prv_serialize_log(MessageContents *contents,
uint8_t log_level, uint64_t timestamp_ms, PebbleTask task,
const char *src_filename, uint16_t src_line_number,
const char *message) {
prv_serialize_log_header(contents, log_level, timestamp_ms, task,
src_filename, src_line_number);
// Write the actual log message.
strncpy(contents->message, message, sizeof(contents->message));
size_t payload_length = MIN(sizeof(MessageContents),
offsetof(MessageContents, message) + strlen(message));
return payload_length;
}
static void prv_send_pulse_packet(uint8_t log_level, const char *src_filename,
uint16_t src_line_number, const char *message) {
MessageContents *contents = pulse_push_send_begin(PULSE_PROTOCOL_LOGGING);
const size_t payload_length = prv_serialize_log(
contents, log_level, prv_get_timestamp_ms(), pebble_task_get_current(),
src_filename, src_line_number, message);
pulse_push_send(contents, payload_length);
}
static bool prv_isr_buffer_read_and_consume(void *buffer, size_t read_length) {
portENTER_CRITICAL();
const bool result = circular_buffer_copy(&s_isr_log_buffer, buffer, read_length);
if (result) {
circular_buffer_consume(&s_isr_log_buffer, read_length);
}
portEXIT_CRITICAL();
return result;
}
static void prv_event_cb(void *data) {
while (true) {
// No need to worry about reading a partial message here, we write messages to the circular
// buffer while disabling interrupts the whole time.
uint32_t log_length;
if (!prv_isr_buffer_read_and_consume(&log_length, sizeof(log_length))) {
// No more messages to read if we can't read a length
break;
}
if (log_length == sizeof(uint32_t)) {
// We dropped a message, log a message to that effect
MessageContents *contents = pulse_push_send_begin(PULSE_PROTOCOL_LOGGING);
const size_t payload_length = prv_serialize_log(
contents, LOG_LEVEL_ERROR, prv_get_timestamp_ms(), PebbleTask_Unknown,
"", 0, "ISR Message Dropped!");
pulse_push_send(contents, payload_length);
} else {
BufferedLogInfo log_info;
prv_isr_buffer_read_and_consume(&log_info, sizeof(log_info));
MessageContents *contents = pulse_push_send_begin(PULSE_PROTOCOL_LOGGING);
const size_t header_length = prv_serialize_log_header(
contents, log_info.log_level, log_info.timestamp_ms, PebbleTask_Unknown, "", 0);
const size_t message_length = log_length - sizeof(uint32_t) - sizeof(BufferedLogInfo);
prv_isr_buffer_read_and_consume(&contents->message, message_length);
pulse_push_send(contents, header_length + message_length);
}
}
}
static void prv_enqueue_log_message(uint8_t log_level, const char *message) {
const bool buffer_was_empty = (circular_buffer_get_read_space_remaining(&s_isr_log_buffer) == 0);
// Need to prevent other interrupts from corrupting the log buffer while we're writing to it
portENTER_CRITICAL();
if (circular_buffer_get_write_space_remaining(&s_isr_log_buffer) < sizeof(uint32_t)) {
// Completely out of space, can't do anything.
portEXIT_CRITICAL();
return;
}
const size_t message_length = MIN(strlen(message), 128);
const uint32_t required_space = sizeof(uint32_t) + sizeof(BufferedLogInfo) + message_length;
if (circular_buffer_get_write_space_remaining(&s_isr_log_buffer) < required_space) {
// Not enough space for the full message, just write an empty message with only the length
// word to indicate we're dropping the message.
const uint32_t insufficient_space_length = sizeof(uint32_t);
circular_buffer_write(&s_isr_log_buffer,
&insufficient_space_length, sizeof(insufficient_space_length));
} else {
circular_buffer_write(&s_isr_log_buffer, &required_space, sizeof(required_space));
const BufferedLogInfo log_info = {
.timestamp_ms = prv_get_timestamp_ms(),
.log_level = log_level
};
circular_buffer_write(&s_isr_log_buffer, &log_info, sizeof(log_info));
circular_buffer_write(&s_isr_log_buffer, message, message_length);
}
if (buffer_was_empty) {
PebbleEvent e = {
.type = PEBBLE_CALLBACK_EVENT,
.callback = {
.callback = prv_event_cb
}
};
event_put_isr(&e);
}
portEXIT_CRITICAL();
}
void pulse_logging_init(void) {
circular_buffer_init(&s_isr_log_buffer,
s_isr_log_buffer_storage, sizeof(s_isr_log_buffer_storage));
}
void pulse_logging_log(uint8_t log_level, const char* src_filename,
uint16_t src_line_number, const char* message) {
if (portIN_CRITICAL() || mcu_state_is_isr() ||
xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED) {
// We're in a state where we can't immediately send the message, save it to an internal buffer
// instead for later sending.
prv_enqueue_log_message(log_level, message);
} else {
// Send the log line inline
prv_send_pulse_packet(log_level, src_filename, src_line_number, message);
}
}
void pulse_logging_log_buffer_flush(void) {
prv_event_cb(NULL);
}
void pulse_logging_log_sync(uint8_t log_level, const char *src_filename,
uint16_t src_line_number, const char *message) {
// Send the log line inline, even if we're in a critical section or ISR
prv_send_pulse_packet(log_level, src_filename, src_line_number, message);
}
void *pulse_logging_log_sync_begin(
uint8_t log_level, const char *src_filename, uint16_t src_line_number) {
MessageContents *contents = pulse_push_send_begin(PULSE_PROTOCOL_LOGGING);
prv_serialize_log_header(contents, log_level, prv_get_timestamp_ms(),
pebble_task_get_current(), src_filename,
src_line_number);
contents->message[0] = '\0';
return contents;
}
void pulse_logging_log_sync_append(void *ctx, const char *message) {
MessageContents *contents = ctx;
strncat(contents->message, message, sizeof(contents->message));
}
void pulse_logging_log_sync_send(void *ctx) {
MessageContents *contents = ctx;
size_t payload_length = MIN(
sizeof(MessageContents),
offsetof(MessageContents, message) + strlen(contents->message));
pulse_push_send(contents, payload_length);
}

View file

@ -0,0 +1,39 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
void pulse_logging_init(void);
//! Log a message using PULSEv2
void pulse_logging_log(uint8_t log_level, const char* src_filename,
uint16_t src_line_number, const char* message);
//! Log a message using PULSEv2 synchronously, even from a critical section
void pulse_logging_log_sync(
uint8_t log_level, const char* src_filename,
uint16_t src_line_number, const char* message);
//! Log a message from a fault handler by concatenating several strings.
void *pulse_logging_log_sync_begin(
uint8_t log_level, const char *src_filename, uint16_t src_line_number);
void pulse_logging_log_sync_append(void *ctx, const char *message);
void pulse_logging_log_sync_send(void *ctx);
//! Flush the ISR log buffer. Call this when crashing.
void pulse_logging_log_buffer_flush(void);

77
src/fw/kernel/reset.c Normal file
View file

@ -0,0 +1,77 @@
/*
* 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 "system/reset.h"
#define CMSIS_COMPATIBLE
#include <mcu.h>
#include "board/board.h"
#include "drivers/pmic.h"
#include "system/bootbits.h"
#include "kernel/core_dump.h"
#include "kernel/util/fw_reset.h"
#include "mcu/interrupts.h"
#include "drivers/flash.h"
#include "system/reboot_reason.h"
#include "FreeRTOS.h"
#include "task.h"
void system_reset_prepare(bool unsafe_reset) {
fw_prepare_for_reset(unsafe_reset);
flash_stop();
}
NORETURN system_reset(void) {
static bool failure_occurred = false;
bool already_failed = failure_occurred;
if (!failure_occurred) {
// Don't overwrite failure_occurred if a failure has already occurred
failure_occurred = boot_bit_test(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED);
}
// Skip safe teardown if doing so the first time already caused a second reset attempt; or
// if we're in a critical section, interrupt or if the scheduler has been suspended
if (!already_failed && !mcu_state_is_isr() && !portIN_CRITICAL() &&
(xTaskGetSchedulerState() == taskSCHEDULER_RUNNING)) {
system_reset_prepare(failure_occurred /* skip BT teardown if failure occured */);
reboot_reason_set_restarted_safely();
}
// If a software failure occcured, do a core dump before resetting
if (failure_occurred) {
core_dump_reset(false /* don't force overwrite */);
}
system_hard_reset();
}
void system_reset_callback(void *data) {
system_reset();
(void)data;
}
NORETURN system_hard_reset(void) {
// Don't do anything fancy here. We may be in a context where nothing works, not even
// interrupts. Just reset us.
NVIC_SystemReset();
__builtin_unreachable();
}

View file

@ -0,0 +1,208 @@
/*
* 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 "kernel/system_message.h"
#include "flash_region/filesystem_regions.h"
#include "kernel/events.h"
#include "kernel/util/sleep.h"
#include "process_management/worker_manager.h"
#include "services/common/comm_session/protocol.h"
#include "services/common/comm_session/session.h"
#include "services/common/firmware_update.h"
#include "services/common/i18n/i18n.h"
#include "services/common/put_bytes/put_bytes.h"
#include "services/common/system_task.h"
#include "services/runlevel.h"
#include "system/bootbits.h"
#include "system/logging.h"
#include "system/passert.h"
#include "system/reboot_reason.h"
#include "system/reset.h"
#include "system/version.h"
#include "util/attributes.h"
#include "util/net.h"
#include "FreeRTOS.h"
#include <string.h>
#include <stdbool.h>
#include <inttypes.h>
static const uint16_t ENDPOINT_ID = 0x12;
void system_message_send(SystemMessageType type) {
uint8_t buffer[2] = { 0x00, type };
CommSession *system_session = comm_session_get_system_session();
comm_session_send_data(system_session, ENDPOINT_ID, buffer, sizeof(buffer), COMM_SESSION_DEFAULT_TIMEOUT);
PBL_LOG(LOG_LEVEL_DEBUG, "Sending sysmsg: %u", type);
}
static void prv_reset_kernel_bg_cb(void *unused) {
PBL_LOG(LOG_LEVEL_ALWAYS, "Rebooting to install firmware...");
RebootReason reason = { RebootReasonCode_SoftwareUpdate, 0 };
reboot_reason_set(&reason);
system_reset();
}
static void prv_ui_update_reset_delay_timer_callback(void *unused) {
system_task_add_callback(prv_reset_kernel_bg_cb, NULL);
}
static void prv_handle_firmware_complete_msg(void) {
uint32_t timeout = 3000;
// Wait 3 seconds before rebooting so there is time to show the update complete screen
PBL_LOG(LOG_LEVEL_ALWAYS, "Delaying reset by 3s so the UI can update...");
TimerID timer = new_timer_create(); // Don't bother cleaning up this timer, we're going to reset
PBL_ASSERTN(timer != TIMER_INVALID_ID);
new_timer_start(timer, timeout, prv_ui_update_reset_delay_timer_callback, NULL, 0);
}
//! Note: For now we just call into storage directly for the status of FW installs. Someday,
//! it would be nice for this exchange to take place as part of PutBytes
extern bool pb_storage_get_status(PutBytesObjectType obj_type, PbInstallStatus *status);
static void prv_handle_firmware_status_request(CommSession *session) {
struct PACKED {
uint8_t deprecated;
uint8_t type;
uint8_t rsvd[2];
uint32_t resource_bytes_written;
uint32_t resource_crc;
uint32_t firmware_bytes_written;
uint32_t firmware_crc;
} fw_status_resp = {
.type = SysMsgFirmwareStatusResponse,
};
PbInstallStatus status = { };
if (pb_storage_get_status(ObjectFirmware, &status)) {
fw_status_resp.firmware_bytes_written = status.num_bytes_written;
fw_status_resp.firmware_crc = status.crc_of_bytes;
}
if (pb_storage_get_status(ObjectSysResources, &status)) {
fw_status_resp.resource_bytes_written = status.num_bytes_written;
fw_status_resp.resource_crc = status.crc_of_bytes;
}
PBL_LOG(LOG_LEVEL_INFO, "FW Status Resp: res %"PRIu32" : 0x%x fw %"PRIu32" : 0x%x",
fw_status_resp.resource_bytes_written, (int)fw_status_resp.resource_crc,
fw_status_resp.firmware_bytes_written, (int)fw_status_resp.firmware_crc);
comm_session_send_data(session, ENDPOINT_ID, (uint8_t *)&fw_status_resp, sizeof(fw_status_resp),
COMM_SESSION_DEFAULT_TIMEOUT);
}
void sys_msg_protocol_msg_callback(CommSession *session, const uint8_t* data, size_t length) {
PBL_ASSERT_RUNNING_FROM_EXPECTED_TASK(PebbleTask_KernelBackground);
SystemMessageType t = data[1];
PBL_LOG(LOG_LEVEL_DEBUG, "Received sysmsg: %u", t);
switch (t) {
case SysMsgFirmwareAvailable_Deprecated: {
PBL_LOG(LOG_LEVEL_DEBUG, "Deprecated available message received.");
break;
}
case SysMsgFirmwareStart: {
PBL_LOG_VERBOSE("About to receive new firmware!");
uint32_t bytes_transferred = 0;
uint32_t total_size = 0;
bool smooth_progress_supported =
comm_session_has_capability(session, CommSessionSmoothFwInstallProgressSupport) &&
(length >= sizeof(SysMsgSmoothFirmwareStartPayload));
if (smooth_progress_supported) {
SysMsgSmoothFirmwareStartPayload *payload = (SysMsgSmoothFirmwareStartPayload *)data;
bytes_transferred = payload->bytes_already_transferred;
total_size = bytes_transferred + payload->bytes_to_transfer;
PBL_LOG(LOG_LEVEL_INFO, "Starting FW update, %"PRIu32" of %"PRIu32" bytes already "
"transferred", bytes_transferred, total_size);
}
PebbleEvent e = {
.type = PEBBLE_SYSTEM_MESSAGE_EVENT,
.firmware_update = {
.type = smooth_progress_supported ?
PebbleSystemMessageFirmwareUpdateStart : PebbleSystemMessageFirmwareUpdateStartLegacy,
.bytes_transferred = bytes_transferred,
.total_transfer_size = total_size,
}
};
event_put(&e);
break;
}
case SysMsgFirmwareStatus:
prv_handle_firmware_status_request(session);
break;
case SysMsgFirmwareComplete: {
PBL_LOG_VERBOSE("Firmware transfer succeeded, okay to restart!");
PebbleEvent e = {
.type = PEBBLE_SYSTEM_MESSAGE_EVENT,
.firmware_update.type = PebbleSystemMessageFirmwareUpdateComplete,
};
event_put(&e);
prv_handle_firmware_complete_msg();
break;
}
case SysMsgFirmwareFail: {
PBL_LOG_VERBOSE("Firmware transfer failed, time to clean up!");
PebbleEvent e = {
.type = PEBBLE_SYSTEM_MESSAGE_EVENT,
.firmware_update.type = PebbleSystemMessageFirmwareUpdateFailed,
};
event_put(&e);
break;
}
case SysMsgFirmwareUpToDate: {
PBL_LOG_VERBOSE("Firmware is up to date!");
PebbleEvent e = {
.type = PEBBLE_SYSTEM_MESSAGE_EVENT,
.firmware_update.type = PebbleSystemMessageFirmwareUpToDate,
};
event_put(&e);
break;
}
default:
PBL_LOG(LOG_LEVEL_ERROR, "Invalid message received, type is %u", data[1]);
break;
}
}
void system_message_send_firmware_start_response(FirmwareUpdateStatus status) {
struct PACKED {
uint8_t zero;
uint8_t type;
uint8_t status;
} msg = {
.zero = 0x00,
.type = SysMsgFirmwareStartResponse,
.status = status
};
CommSession *session = comm_session_get_system_session();
comm_session_send_data(session, ENDPOINT_ID, (const uint8_t*) &msg, sizeof(msg), COMM_SESSION_DEFAULT_TIMEOUT);
}

View file

@ -0,0 +1,57 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
#include "services/common/firmware_update.h"
#include <util/attributes.h>
typedef enum SystemMessageType {
SysMsgFirmwareAvailable_Deprecated = 0x00,
SysMsgFirmwareStart = 0x01,
SysMsgFirmwareComplete = 0x02,
SysMsgFirmwareFail = 0x03,
SysMsgFirmwareUpToDate = 0x04,
// SysMsgFirmarewOutOfDate = 0x05, DEPRECATED
SysMsgReconnectRequestStop = 0x06,
SysMsgReconnectRequestStart = 0x07,
SysMsgMAPRetry = 0x08, // MAP is no longer used
SysMsgMAPConnected = 0x09, // MAP is no longer used
SysMsgFirmwareStartResponse = 0x0a,
SysMsgFirmwareStatus = 0x0b, // Phone -> Watch request for partial fw install info
SysMsgFirmwareStatusResponse = 0x0c, // Watch -> Phone response of what fw is partially installed
} SystemMessageType;
typedef struct PACKED SysMsgSmoothFirmwareStartPayload {
uint8_t deprecated; // not used anymore but all messages start with 0x0
SystemMessageType type:8; // == SysMsgFirmwareStart
// The number of bytes the phone has transferred in a previous operation
uint32_t bytes_already_transferred;
// The total number of bytes the phone needs to transfer to complete the firmware update
// (i.e For a normal firmware, this would be the sum of outstanding bytes for the fw binary
// and also the pbpack)
uint32_t bytes_to_transfer;
} SysMsgSmoothFirmwareStartPayload;
void system_message_init(void);
void system_message_send(SystemMessageType type);
void system_message_send_firmware_start_response(FirmwareUpdateStatus status);

View file

@ -0,0 +1,253 @@
/*
* 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 "console/prompt.h"
#include "drivers/fpc_pinstrap.h"
#include "drivers/mcu.h"
#include "drivers/pmic.h"
#include "mfg/mfg_info.h"
#include "mfg/mfg_serials.h"
#include "resource/resource.h"
#include "resource/system_resource.h"
#include "services/common/bluetooth/bluetooth_persistent_storage.h"
#include "services/common/bluetooth/local_id.h"
#include "services/common/comm_session/protocol.h"
#include "services/common/comm_session/session.h"
#include "services/common/comm_session/session_remote_version.h"
#include "services/common/i18n/i18n.h"
#include "services/normal/activity/insights_settings.h"
#include "shell/system_app_ids.auto.h"
#include "system/bootbits.h"
#include "system/logging.h"
#include "system/passert.h"
#include "system/version.h"
#include "util/attributes.h"
#include "util/net.h"
#include "util/string.h"
#include <bluetooth/bluetooth_types.h>
#include <string.h>
#define VERSION_REQUEST 0x00
#define VERSION_RESPONSE 0x01
static const uint16_t s_endpoint_id = 0x0010;
struct PACKED VersionsMessage {
const uint8_t command;
FirmwareMetadata running_fw_metadata;
FirmwareMetadata recovery_fw_metadata;
uint32_t boot_version;
char hw_version[MFG_HW_VERSION_SIZE];
char serial_number[MFG_SERIAL_NUMBER_SIZE];
BTDeviceAddress device_address;
ResourceVersion system_resources_version;
char iso_locale[ISO_LOCALE_LENGTH];
uint16_t lang_version;
// Use this padding string for additional bit flags passed by
// >= 2.X versions of the mobile applications. ISO + locale
// on 1.X mobile application versions.
PebbleProtocolCapabilities capabilities;
bool is_unfaithful;
net16 activity_insights_version;
net16 javascript_bytecode_version;
};
static void fixup_string(char* str, unsigned int length) {
if (memchr(str, 0, length) == NULL) {
memset(str, 0, length);
}
}
static void prv_fixup_firmware_metadata(FirmwareMetadata *fw_metadata) {
fw_metadata->version_timestamp = htonl(fw_metadata->version_timestamp);
fixup_string(fw_metadata->version_tag, sizeof(fw_metadata->version_tag));
fixup_string(fw_metadata->version_short, sizeof(fw_metadata->version_short));
}
static void prv_fixup_running_firmware_metadata(FirmwareMetadata *fw_metadata) {
prv_fixup_firmware_metadata(fw_metadata);
#ifdef MANUFACTURING_FW
// Lie to the phone and force this to say we're not a MFG firmware. If we tell the phone app
// that we're a MFG firmware it will get mad at us and try to update us out of this mode. We
// want to stay in this mode to collect logs and core dumps at the factory.
// FIXME: Long term the phone should probably just be able to collect logs and core dumps
// regardless of the state of the watch, but for now just lie.
fw_metadata->is_recovery_firmware = false;
#endif
}
static void resource_version_to_network_endian(ResourceVersion *resources_version) {
resources_version->crc = htonl(resources_version->crc);
resources_version->timestamp = htonl(resources_version->timestamp);
}
static void prv_send_watch_versions(CommSession *session) {
struct VersionsMessage versions_msg = {
.command = VERSION_RESPONSE,
.boot_version = htonl(boot_version_read()),
};
_Static_assert(sizeof(struct VersionsMessage) >=
126 /* pre-v1.5 version info */ +
24 /* v1.5 version info or later, added system_resources_version */,
"");
version_copy_running_fw_metadata(&versions_msg.running_fw_metadata);
prv_fixup_running_firmware_metadata(&versions_msg.running_fw_metadata);
version_copy_recovery_fw_metadata(&versions_msg.recovery_fw_metadata);
prv_fixup_firmware_metadata(&versions_msg.recovery_fw_metadata);
// Note: Don't worry about the null terminator if it doesn't fit, the other side should deal with it.
mfg_info_get_hw_version(versions_msg.hw_version, sizeof(versions_msg.hw_version));
mfg_info_get_serialnumber(versions_msg.serial_number, sizeof(versions_msg.serial_number));
strncpy(versions_msg.iso_locale, i18n_get_locale(), ISO_LOCALE_LENGTH - 1);
versions_msg.iso_locale[ISO_LOCALE_LENGTH - 1] = '\0';
versions_msg.lang_version = htons(i18n_get_version());
PBL_LOG(LOG_LEVEL_DEBUG, "Sending lang version: %d", versions_msg.lang_version);
// Set the capabilities as zero, effectively saying that we don't support anything.
versions_msg.capabilities.flags = 0;
// Assign the individual bits for the capabilities that we support.
versions_msg.capabilities.run_state_support = 1;
versions_msg.capabilities.infinite_log_dumping_support = 1;
versions_msg.capabilities.extended_music_service = 1;
versions_msg.capabilities.extended_notification_service = 1;
versions_msg.capabilities.lang_pack_support = 1;
versions_msg.capabilities.app_message_8k_support = 1;
#if CAPABILITY_HAS_HEALTH_TRACKING
versions_msg.capabilities.activity_insights_support = 1;
#endif
versions_msg.capabilities.voice_api_support = 1;
versions_msg.capabilities.unread_coredump_support = 1;
// FIXME: PBL-31627 In PRF, APP_ID_SEND_TEXT isn't defined - requiring the #ifdef and ternary op.
#ifdef APP_ID_SEND_TEXT
versions_msg.capabilities.send_text_support = (APP_ID_SEND_TEXT != INSTALL_ID_INVALID) ? 1 : 0;
#endif
versions_msg.capabilities.notification_filtering_support = 1;
#ifdef APP_ID_WEATHER
versions_msg.capabilities.weather_app_support = (APP_ID_WEATHER != INSTALL_ID_INVALID) ? 1 : 0;
#endif
#ifdef APP_ID_REMINDERS
versions_msg.capabilities.reminders_app_support =
(APP_ID_REMINDERS != INSTALL_ID_INVALID) ? 1 : 0;
#endif
#ifdef APP_ID_WORKOUT
versions_msg.capabilities.workout_app_support = (APP_ID_WORKOUT != INSTALL_ID_INVALID) ? 1 : 0;
#endif
#if CAPABILITY_HAS_JAVASCRIPT
versions_msg.capabilities.javascript_bytecode_version_appended = 0x1;
versions_msg.javascript_bytecode_version = hton16(CAPABILITY_JAVASCRIPT_BYTECODE_VERSION);
#endif
versions_msg.capabilities.continue_fw_install_across_disconnect_support = 1;
versions_msg.capabilities.smooth_fw_install_progress_support = 1;
bt_local_id_copy_address(&versions_msg.device_address);
versions_msg.system_resources_version = resource_get_system_version();
resource_version_to_network_endian(&versions_msg.system_resources_version);
versions_msg.is_unfaithful = bt_persistent_storage_is_unfaithful();
#if CAPABILITY_HAS_HEALTH_TRACKING && !RECOVERY_FW
versions_msg.activity_insights_version = hton16(activity_insights_settings_get_version());
#endif
comm_session_send_data(session, s_endpoint_id, (uint8_t*) &versions_msg, sizeof(versions_msg),
COMM_SESSION_DEFAULT_TIMEOUT);
}
void system_version_protocol_msg_callback(CommSession *session, const uint8_t* data, size_t length) {
switch (data[0]) {
case VERSION_REQUEST: {
prv_send_watch_versions(session);
break;
}
default:
PBL_LOG(LOG_LEVEL_ERROR, "Invalid message received. First byte is %u", data[0]);
break;
}
}
void command_version_info(void) {
#ifdef MANUFACTURING_FW
prompt_send_response("MANUFACTURING FW");
#endif
bool (*fun_ptr[2])(FirmwareMetadata*) = { version_copy_running_fw_metadata,
version_copy_recovery_fw_metadata};
const char *label[2] = {"Running", "Recovery"};
FirmwareMetadata fw_metadata;
char buffer[128];
for (int i = 0; i < 2; ++i) {
bool success = fun_ptr[i](&fw_metadata);
if (success) {
prompt_send_response_fmt(
buffer, sizeof(buffer),
"%s FW:\n ts:%"PRIu32"\n tag:%s\n short:%s\n recov:%u\n platform:%u",
label[i], fw_metadata.version_timestamp, fw_metadata.version_tag,
fw_metadata.version_short, fw_metadata.is_recovery_firmware, fw_metadata.hw_platform);
} else {
prompt_send_response_fmt(buffer, sizeof(buffer), "%s FW: no version info or lookup failed",
label[i]);
}
}
char build_id_string[64];
version_copy_current_build_id_hex_string(build_id_string, sizeof(build_id_string));
prompt_send_response_fmt(buffer, sizeof(buffer), "Build Id:%s", build_id_string);
char serial_number[MFG_SERIAL_NUMBER_SIZE + 1];
mfg_info_get_serialnumber(serial_number, sizeof(serial_number));
char hw_version[MFG_HW_VERSION_SIZE + 1];
mfg_info_get_hw_version(hw_version, sizeof(hw_version));
const uint32_t* mcu_serial = mcu_get_serial();
prompt_send_response_fmt(buffer, sizeof(buffer),
"MCU Serial: %08"PRIx32" %08"PRIx32" %08"PRIx32,
mcu_serial[0], mcu_serial[1], mcu_serial[2]);
prompt_send_response_fmt(buffer, sizeof(buffer), "Boot:%"PRIu32"\nHW:%s\nSN:%s",
boot_version_read(), hw_version, serial_number);
ResourceVersion system_resources_version = resource_get_system_version();
prompt_send_response_fmt(buffer, sizeof(buffer),
"System Resources:\n CRC:0x%"PRIx32"\n Valid:%s",
system_resources_version.crc, bool_to_str(system_resource_is_valid()));
#if CAPABILITY_HAS_PMIC
uint8_t chip_id;
uint8_t chip_revision;
uint8_t buck1_vset;
pmic_read_chip_info(&chip_id, &chip_revision, &buck1_vset);
prompt_send_response_fmt(buffer,
sizeof(buffer),
"PMIC Chip Id: 0x%"PRIx8" Chip Rev: 0x%"PRIx8" Buck1 VSET: 0x%"PRIx8,
chip_id, chip_revision, buck1_vset);
#endif // CAPABILITY_HAS_PMIC
#ifdef PLATFORM_SNOWY
const uint8_t fpc_pinstrap = fpc_pinstrap_get_value();
if (fpc_pinstrap != FPC_PINSTRAP_NOT_AVAILABLE) {
// + 1 since variants are documented as being between 1-9 instead of 0-based
prompt_send_response_fmt(buffer, sizeof(buffer), "FPC Variant: %"PRIu8, fpc_pinstrap + 1);
}
#endif
}

368
src/fw/kernel/task_timer.c Normal file
View file

@ -0,0 +1,368 @@
/*
* 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 "task_timer.h"
#include "task_timer_manager.h"
#include "kernel/pebble_tasks.h"
#include "kernel/pbl_malloc.h"
#include "os/mutex.h"
#include "os/tick.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/list.h"
#include "FreeRTOS.h"
#include "queue.h"
#include "semphr.h"
// Structure of a timer
typedef struct TaskTimer {
//! Entry into either the running timers (manager->running_timers) list or the idle timers
//! (manager->idle_timers)
ListNode list_node;
//! The tick value when this timer will expire (in ticks). If the timer isn't currently
//! running (scheduled) this value will be zero.
RtcTicks expire_time;
RtcTicks period_ticks;
TaskTimerID id; //<! ID assigned to this timer
//! client provided callback function and argument
TaskTimerCallback cb;
void* cb_data;
//! True if this timer should automatically be rescheduled for period_time ticks from now
bool repeating:1;
//! True if this timer is currently having its callback executed.
bool executing:1;
//! Set by the delete function of client tries to delete a timer currently executing its
//! callback
bool defer_delete:1;
} TaskTimer;
// ------------------------------------------------------------------------------------
// Comparator function for list_sorted_add
// Returns the order in which (a, b) occurs
// returns negative int for a descending value (a > b), positive for an ascending value (b > a),
// 0 for equal
static int prv_timer_expire_compare_func(void* a, void* b) {
if (((TaskTimer*)b)->expire_time < ((TaskTimer*)a)->expire_time) {
return -1;
} else if (((TaskTimer*)b)->expire_time > ((TaskTimer*)a)->expire_time) {
return 1;
} else {
return 0;
}
}
// ------------------------------------------------------------------------------------
// Find timer by id
static bool prv_id_list_filter(ListNode* node, void* data) {
TaskTimer* timer = (TaskTimer*)node;
return timer->id == (uint32_t) data;
}
static TaskTimer* prv_find_timer(TaskTimerManager *manager, TaskTimerID timer_id) {
PBL_ASSERTN(timer_id != TASK_TIMER_INVALID_ID);
// Look for this timer in either the running or idle list
ListNode* node = list_find(manager->running_timers, prv_id_list_filter, (void*)timer_id);
if (!node) {
node = list_find(manager->idle_timers, prv_id_list_filter, (void*)timer_id);
}
PBL_ASSERTN(node);
return (TaskTimer *)node;
}
// =======================================================================================
// Client-side Implementation
// ---------------------------------------------------------------------------------------
// Create a new timer
TaskTimerID task_timer_create(TaskTimerManager *manager) {
TaskTimer *timer = kernel_malloc(sizeof(TaskTimer));
if (!timer) {
return TASK_TIMER_INVALID_ID;
}
// Grab lock on timer structures, create a unique ID for this timer and put it into our idle
// timers list
mutex_lock(manager->mutex);
*timer = (TaskTimer) {
.id = manager->next_id++,
};
// We don't expect to wrap around, this would take over 100 years if we allocated a timer every
// second
PBL_ASSERTN(timer->id != TASK_TIMER_INVALID_ID);
manager->idle_timers = list_insert_before(manager->idle_timers, &timer->list_node);
mutex_unlock(manager->mutex);
return timer->id;
}
// --------------------------------------------------------------------------------
// Schedule a timer to run.
bool task_timer_start(TaskTimerManager *manager, TaskTimerID timer_id,
uint32_t timeout_ms, TaskTimerCallback cb, void *cb_data, uint32_t flags) {
TickType_t timeout_ticks = milliseconds_to_ticks(timeout_ms);
RtcTicks current_time = rtc_get_ticks();
// Grab lock on timer structures
mutex_lock(manager->mutex);
// Find this timer
TaskTimer* timer = prv_find_timer(manager, timer_id);
PBL_ASSERTN(!timer->defer_delete);
// If this timer is currently executing it's callback, return false if
// TIMER_START_FLAG_FAIL_IF_EXECUTING is on
if (timer->executing && (flags & TIMER_START_FLAG_FAIL_IF_EXECUTING)) {
mutex_unlock(manager->mutex);
return false;
}
// If the TIMER_START_FLAG_FAIL_IF_SCHEDULED flag is on, make sure timer is not already scheduled
if ((flags & TIMER_START_FLAG_FAIL_IF_SCHEDULED) && timer->expire_time) {
mutex_unlock(manager->mutex);
return false;
}
// Remove it from its current list
if (timer->expire_time) {
PBL_ASSERTN(list_contains(manager->running_timers, &timer->list_node));
list_remove(&timer->list_node, &manager->running_timers /* &head */, NULL /* &tail */);
} else {
PBL_ASSERTN(list_contains(manager->idle_timers, &timer->list_node));
list_remove(&timer->list_node, &manager->idle_timers /* &head */, NULL /* &tail */);
}
// Set timer variables
timer->cb = cb;
timer->cb_data = cb_data;
timer->expire_time = current_time + timeout_ticks;
timer->repeating = flags & TIMER_START_FLAG_REPEATING;
timer->period_ticks = timeout_ticks;
// Insert into sorted order in the running list
manager->running_timers = list_sorted_add(manager->running_timers, &timer->list_node,
prv_timer_expire_compare_func, true);
// Wake up our service task if this is the new head so that it can recompute its wait timeout
if (manager->running_timers == &timer->list_node) {
xSemaphoreGive(manager->semaphore);
}
mutex_unlock(manager->mutex);
return true;
}
// --------------------------------------------------------------------------------
// Return scheduled status
bool task_timer_scheduled(TaskTimerManager *manager, TaskTimerID timer_id, uint32_t *expire_ms_p) {
mutex_lock(manager->mutex);
// Find this timer in our list
TaskTimer* timer = prv_find_timer(manager, timer_id);
PBL_ASSERTN(!timer->defer_delete);
// If expire timer is not 0, it means we are scheduled
bool retval = (timer->expire_time != 0);
// Figure out expire timer?
if (expire_ms_p != NULL && retval) {
RtcTicks current_ticks = rtc_get_ticks();
if (timer->expire_time > current_ticks) {
*expire_ms_p = ((timer->expire_time - current_ticks) * 1000) / configTICK_RATE_HZ;
} else {
*expire_ms_p = 0;
}
}
mutex_unlock(manager->mutex);
return retval;
}
// --------------------------------------------------------------------------------
// Stop a timer. If the timer callback is currently executing, return false, else return true.
bool task_timer_stop(TaskTimerManager *manager, TaskTimerID timer_id) {
mutex_lock(manager->mutex);
// Find this timer in our list
TaskTimer* timer = prv_find_timer(manager, timer_id);
PBL_ASSERTN(!timer->defer_delete);
// Move it to the idle list if it's currently running
if (timer->expire_time) {
PBL_ASSERTN(list_contains(manager->running_timers, &timer->list_node));
list_remove(&timer->list_node, &manager->running_timers /* &head */, NULL /* &tail */);
manager->idle_timers = list_insert_before(manager->idle_timers, &timer->list_node);
}
// Clear the repeating flag so that if they call this method from a callback it won't get
// rescheduled.
timer->repeating = false;
timer->expire_time = 0;
mutex_unlock(manager->mutex);
return (!timer->executing);
}
// --------------------------------------------------------------------------------
// Delete a timer
void task_timer_delete(TaskTimerManager *manager, TaskTimerID timer_id) {
mutex_lock(manager->mutex);
// Find this timer in our list
TaskTimer* timer = prv_find_timer(manager, timer_id);
// If it's already marked for deletion return
if (timer->defer_delete) {
mutex_unlock(manager->mutex);
return;
}
// Automatically stop it if it it's not stopped already
if (timer->expire_time) {
timer->expire_time = 0;
PBL_ASSERTN(list_contains(manager->running_timers, &timer->list_node));
list_remove(&timer->list_node, &manager->running_timers /* &head */, NULL /* &tail */);
manager->idle_timers = list_insert_before(manager->idle_timers, &timer->list_node);
}
timer->repeating = false; // In case it's currently executing, make sure we don't reschedule it
// If it's currently executing, defer the delete till after the callback returns. The next call
// to task_timer_manager_execute_expired_timers service loop will take care of this for us.
if (timer->executing) {
timer->defer_delete = true;
mutex_unlock(manager->mutex);
} else {
PBL_ASSERTN(list_contains(manager->idle_timers, &timer->list_node));
list_remove(&timer->list_node, &manager->idle_timers /* &head */, NULL /* &tail */);
mutex_unlock(manager->mutex);
kernel_free(timer);
}
}
void task_timer_manager_init(TaskTimerManager *manager, SemaphoreHandle_t semaphore) {
*manager = (TaskTimerManager) {
.mutex = mutex_create(),
// Initialize next id to be a number that's theoretically unique per-task
.next_id = (pebble_task_get_current() << 28) + 1,
.semaphore = semaphore
};
// The above shift assumes next_id is a 32-bit int and there are fewer than 16 tasks.
_Static_assert(sizeof(((TaskTimerManager*)0)->next_id) == 4, "next_id is not the right width");
_Static_assert(NumPebbleTask < 16, "Too many tasks");
}
TickType_t task_timer_manager_execute_expired_timers(TaskTimerManager *manager) {
while (1) {
TickType_t ticks_to_wait = 0;
RtcTicks next_expiry_time = 0;
// -------------------------------------------------------------------------------------
// If a timer is ready to run, set time_to_wait to 0 and put the timer into 'next_timer'.
// If no timer is ready yet, then ticks_to_wait will be > 0.
mutex_lock(manager->mutex);
TaskTimer *next_timer = (TaskTimer*) manager->running_timers;
if (next_timer != NULL) {
next_expiry_time = next_timer->expire_time;
RtcTicks current_time = rtc_get_ticks();
if (next_expiry_time <= current_time) {
// Found a timer that has expired! Move it from the running list to the idle talk and
// mark it as executing.
manager->running_timers = list_pop_head(manager->running_timers);
manager->idle_timers = list_insert_before(manager->idle_timers, &next_timer->list_node);
next_timer->executing = true;
next_timer->expire_time = 0;
// If we fell way behind (at least 1 timer period + 5 seconds) on a repeating timer
// (presumably because we were in the debugger) advance next_expiry_time so that we don't
// need to call this callback more than twice in a row in order to catch up.
if (next_timer->repeating
&& (int64_t)next_expiry_time < (int64_t)(current_time - next_timer->period_ticks
- 5 * RTC_TICKS_HZ)) {
PBL_LOG(LOG_LEVEL_WARNING, "NT: Skipping some callbacks for %p because we fell behind",
next_timer->cb);
}
} else {
// The next timer hasn't expired yet. Update
ticks_to_wait = next_expiry_time - current_time;
}
} else {
// No timers running
ticks_to_wait = portMAX_DELAY;
}
mutex_unlock(manager->mutex);
if (ticks_to_wait) {
return ticks_to_wait;
}
// Run the timer callback now
manager->current_cb = next_timer->cb_data;
next_timer->cb(next_timer->cb_data);
manager->current_cb = NULL;
// Update state after the callback
mutex_lock(manager->mutex);
next_timer->executing = false;
// Re-insert into timers list now if it's a repeating timer and wasn't re-scheduled by the
// callback (next_timer->expire_time != 0)
if (next_timer->repeating && !next_timer->expire_time) {
next_timer->expire_time = next_expiry_time + next_timer->period_ticks;
list_remove(&next_timer->list_node, &manager->idle_timers /* &head */, NULL /* &tail */);
manager->running_timers = list_sorted_add(manager->running_timers, &next_timer->list_node,
prv_timer_expire_compare_func, true);
}
// If it's been marked for deletion, take care of that now
if (next_timer->defer_delete) {
PBL_ASSERTN(list_contains(manager->idle_timers, &next_timer->list_node));
list_remove(&next_timer->list_node, &manager->idle_timers /* &head */, NULL /* &tail */);
mutex_unlock(manager->mutex);
kernel_free(next_timer);
} else {
mutex_unlock(manager->mutex);
}
}
}
void* task_timer_manager_get_current_cb(const TaskTimerManager *manager) {
return manager->current_cb;
}

View file

@ -0,0 +1,90 @@
/*
* 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.
*/
#pragma once
#include "util/list.h"
#include "os/mutex.h"
#include <stdbool.h>
#include <stdint.h>
#include "FreeRTOS.h"
#include "semphr.h"
//! task_timer.h
//!
//! This file implements timers in a way that allows any of our tasks to run timers on their own.
//! This code is heavily based on the new_timer codebase. Each task that wants to execute timers
//! should allocate their own TaskTimerManager. Timers can be created using that manager using
//! task_timer_create. Timers created with one manager aren't transferable to any other manager.
//! A handle to a given timer. IDs are used instead of pointers to avoid use-after-free issues.
//! Note that IDs are only unique for a given manager.
typedef uint32_t TaskTimerID;
static const TaskTimerID TASK_TIMER_INVALID_ID = 0;
typedef struct TaskTimerManager TaskTimerManager;
typedef void (*TaskTimerCallback)(void *data);
//! Flags for task_timer_start()
//! TIMER_START_FLAG_REPEATING make this a repeating timer
//!
//! TIMER_START_FLAG_FAIL_IF_EXECUTING If the timer callback is currently executing, do not
//! schedule the timer and return false from task_timer_start. This can be helpful in usage patterns
//! where the timer callback might be blocked on a semaphore owned by the task issuing the start.
//!
//! TIMER_START_FLAG_FAIL_IF_SCHEDULED If the timer is already scheduled, do not reschedule it and
//! return false from task_timer_start.
#define TIMER_START_FLAG_REPEATING 0x01
#define TIMER_START_FLAG_FAIL_IF_EXECUTING 0x02
#define TIMER_START_FLAG_FAIL_IF_SCHEDULED 0x04
//! Creates a new timer object. This timer will start out in the stopped state.
//! @return the non-zero timer id or TIMER_INVALID_ID if OOM
TaskTimerID task_timer_create(TaskTimerManager *manager);
//! Schedule an existing timer to execute in timeout_ms. If the timer was already started, it will
//! be rescheduled for the new time.
//! @param[in] timer ID
//! @param[in] timeout_ms timeout in milliseconds
//! @param[in] cb pointer to the user's callback procedure
//! @param[in] cb_data reference data for the callback
//! @param[in] flags one or more TIMER_START_FLAG_.* flags
//! @return True if succesful, false if timer was not rescheduled. Note that it will never return
//! false if none of the FAIL_IF_* flags are set.
bool task_timer_start(TaskTimerManager *manager, TaskTimerID timer, uint32_t timeout_ms,
TaskTimerCallback cb, void *cb_data, uint32_t flags);
//! Stop a timer. For repeating timers, even if this method returns false (callback is currently
//! executing) the timer will not run again. Safe to call on timers that aren't currently started.
//! @param[in] timer ID
//! @return False if timer's callback is current executing, true if not.
bool task_timer_stop(TaskTimerManager *manager, TaskTimerID timer);
//! Get scheduled status of a timer
//! @param[in] timer ID
//! @param[out] expire_ms_p if not NULL, the number of milliseconds until this timer will fire is
//! returned in *expire_ms_p. If the timer is not scheduled (return value
//! is false), this value should be ignored.
//! @return True if timer is scheduled, false if not
bool task_timer_scheduled(TaskTimerManager *manager, TaskTimerID timer, uint32_t *expire_ms_p);
//! Delete a timer
//! @param[in] timer ID
void task_timer_delete(TaskTimerManager *manager, TaskTimerID timer);

View file

@ -0,0 +1,57 @@
/*
* 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.
*/
#pragma once
#include "task_timer.h"
//! Internal state object. Each task that wants to execute timers should allocate their own
//! instance of this object.
typedef struct TaskTimerManager {
PebbleMutex *mutex;
//! List of timers that are currently running
ListNode *running_timers;
//! List of timers that are allocated but unscheduled
ListNode *idle_timers;
//! The next ID to assign to a new timer.
TaskTimerID next_id;
//! Externally provided semaphore that is given whenever the next timer to expire has changed.
SemaphoreHandle_t semaphore;
//! The callback we're currently executing, useful for debugging.
void *current_cb;
} TaskTimerManager;
//! Initialize a passed in manager object.
//! @param[in] semaphore a sempahore the TaskTimerManager should give if the next expiring timer
//! has changed. The task event loop should block on this same semphore to
//! handle timer updates in a timely fashion.
void task_timer_manager_init(TaskTimerManager *manager, SemaphoreHandle_t semaphore);
//! Execute any timers that are currently expired.
//! @return the number of ticks until the next timer expires. If there are no timers running,
//! returns portMAX_DELAY.
TickType_t task_timer_manager_execute_expired_timers(TaskTimerManager *manager);
//! Debugging interface to help understand why the task_timer exuction is stuck and what
//! its stuck on.
//! @return A pointer to the current callback that's running, NULL if no callback
//! is currently running.
void* task_timer_manager_get_current_cb(const TaskTimerManager *manager);

View file

@ -0,0 +1,84 @@
/*
* 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 "kernel_ui.h"
#include "kernel/kernel_applib_state.h"
#include "kernel/pebble_tasks.h"
#include "process_state/app_state/app_state.h"
#include "services/common/compositor/compositor.h"
#include "system/passert.h"
#include "applib/graphics/graphics.h"
#include "applib/ui/animation_private.h"
static GContext s_kernel_grahics_context;
T_STATIC ContentIndicatorsBuffer s_kernel_content_indicators_buffer;
static TimelineItemActionSource s_kernel_current_timeline_item_action_source;
void kernel_ui_init(void) {
graphics_context_init(&s_kernel_grahics_context, compositor_get_framebuffer(),
GContextInitializationMode_System);
animation_private_state_init(kernel_applib_get_animation_state());
content_indicator_init_buffer(&s_kernel_content_indicators_buffer);
s_kernel_current_timeline_item_action_source = TimelineItemActionSourceModalNotification;
}
GContext* kernel_ui_get_graphics_context(void) {
PBL_ASSERT_TASK(PebbleTask_KernelMain);
return &s_kernel_grahics_context;
}
GContext *graphics_context_get_current_context(void) {
if (pebble_task_get_current() == PebbleTask_App) {
return app_state_get_graphics_context();
} else {
return kernel_ui_get_graphics_context();
}
}
ContentIndicatorsBuffer *kernel_ui_get_content_indicators_buffer(void) {
PBL_ASSERT_TASK(PebbleTask_KernelMain);
return &s_kernel_content_indicators_buffer;
}
ContentIndicatorsBuffer *content_indicator_get_current_buffer(void) {
if (pebble_task_get_current() == PebbleTask_App) {
return app_state_get_content_indicators_buffer();
} else {
return kernel_ui_get_content_indicators_buffer();
}
}
TimelineItemActionSource kernel_ui_get_current_timeline_item_action_source(void) {
if (pebble_task_get_current() == PebbleTask_App) {
return app_state_get_current_timeline_item_action_source();
} else {
return s_kernel_current_timeline_item_action_source;
}
}
void kernel_ui_set_current_timeline_item_action_source(TimelineItemActionSource current_source) {
if (pebble_task_get_current() == PebbleTask_App) {
app_state_set_current_timeline_item_action_source(current_source);
} else {
s_kernel_current_timeline_item_action_source = current_source;
}
}

View file

@ -0,0 +1,34 @@
/*
* 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.
*/
#pragma once
#include "applib/graphics/gtypes.h"
#include "applib/ui/content_indicator_private.h"
#include "services/normal/timeline/timeline_actions.h"
void kernel_ui_init(void);
GContext* kernel_ui_get_graphics_context(void);
GContext *graphics_context_get_current_context(void);
ContentIndicatorsBuffer *kernel_ui_get_content_indicators_buffer(void);
ContentIndicatorsBuffer *content_indicator_get_current_buffer(void);
TimelineItemActionSource kernel_ui_get_current_timeline_item_action_source(void);
void kernel_ui_set_current_timeline_item_action_source(TimelineItemActionSource current_source);

View file

@ -0,0 +1,582 @@
/*
* 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 "modal_manager.h"
#include "applib/ui/app_window_click_glue.h"
#include "applib/ui/click_internal.h"
#include "applib/ui/window.h"
#include "applib/ui/window_private.h"
#include "applib/ui/window_stack.h"
#include "applib/ui/window_stack_animation.h"
#include "applib/ui/window_stack_private.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/graphics_private.h"
#include "console/prompt.h"
#include "kernel/panic.h"
#include "kernel/events.h"
#include "kernel/event_loop.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
#include "services/common/compositor/compositor_transitions.h"
#include "shell/normal/app_idle_timeout.h"
#include "shell/normal/watchface.h"
#include "system/passert.h"
#include "system/profiler.h"
#include "util/list.h"
#include "util/size.h"
#include "FreeRTOS.h"
#include "semphr.h"
typedef struct ModalContext {
WindowStack window_stack;
} ModalContext;
typedef struct UpdateContext {
ModalPriority highest_idx;
ModalProperty properties;
} UpdateContext;
static void prv_update_modal_stacks(UpdateContext *context);
// Static State
///////////////////
static ModalContext s_modal_window_stacks[NumModalPriorities];
static ClickManager s_modal_window_click_manager;
static ModalPriority s_modal_min_priority = ModalPriorityMin;
// Used to help us keep track various modal properties in aggregate, such as existence.
// Initialize the default to being equivalent to having no modals.
static ModalProperty s_current_modal_properties = ModalPropertyDefault;
// Used to decide the compositor transition after a modal is already removed from the stack
static ModalPriority s_last_highest_modal_priority = ModalPriorityInvalid;
// Private API
////////////////////
static bool prv_has_visible_window(ModalContext *context, void *unused) {
const bool empty = (context->window_stack.list_head == NULL);
const bool filtered_out = (context < &s_modal_window_stacks[s_modal_min_priority]);
return (!empty && !filtered_out);
}
static bool prv_has_transition_window(ModalContext *context) {
Window *window = window_stack_get_top_window(&context->window_stack);
return (window && (context > &s_modal_window_stacks[ModalPriorityDiscreet]));
}
static bool prv_has_opaque_window(ModalContext *context) {
Window *window = window_stack_get_top_window(&context->window_stack);
return (window && !window->is_transparent);
}
static bool prv_has_focusable_window(ModalContext *context) {
Window *window = window_stack_get_top_window(&context->window_stack);
return (window && !window->is_unfocusable);
}
static bool prv_has_visible_focusable_window(ModalContext *context, void *unused) {
return (prv_has_visible_window(context, NULL) && prv_has_focusable_window(context));
}
static void prv_send_will_focus_event(bool in_focus) {
static bool s_focus_lost = true;
if (s_focus_lost == in_focus) {
return;
}
s_focus_lost = in_focus;
PebbleEvent event = {
.type = PEBBLE_APP_WILL_CHANGE_FOCUS_EVENT,
.app_focus = {
.in_focus = in_focus,
}
};
event_put(&event);
}
// Public API
////////////////////
void modal_manager_init(void) {
// Don't touch s_modal_window_stacks or s_modal_min_priority, it's valid for someone to have
// disabled modals using modal_manager_set_enabled before we've initialized and we should
// honour that setting.
click_manager_init(&s_modal_window_click_manager);
}
void modal_manager_set_min_priority(ModalPriority priority) {
s_modal_min_priority = priority;
for (int i = 0; i < priority; i++) {
window_stack_lock_push(&s_modal_window_stacks[i].window_stack);
}
for (int i = priority; i < NumModalPriorities; ++i) {
window_stack_unlock_push(&s_modal_window_stacks[i].window_stack);
}
}
bool modal_manager_get_enabled(void) {
return s_modal_min_priority < ModalPriorityMax;
}
ClickManager *modal_manager_get_click_manager(void) {
return &s_modal_window_click_manager;
}
static WindowStack *prv_find_window_stack(ModalContextFilterCallback callback, void *data) {
for (ModalPriority idx = NumModalPriorities - 1; idx >= ModalPriorityMin; idx--) {
if (callback(&s_modal_window_stacks[idx], data)) {
return &s_modal_window_stacks[idx].window_stack;
}
}
return NULL;
}
WindowStack *modal_manager_find_window_stack(ModalContextFilterCallback filter_cb, void *ctx) {
return prv_find_window_stack(filter_cb, ctx);
}
WindowStack *modal_manager_get_window_stack(ModalPriority priority) {
PBL_ASSERTN((priority > ModalPriorityInvalid) && (priority < NumModalPriorities));
ModalContext *context = &s_modal_window_stacks[priority];
return &context->window_stack;
}
Window *modal_manager_get_top_window(void) {
WindowStack *stack = prv_find_window_stack(prv_has_visible_window, NULL);
return window_stack_get_top_window(stack);
}
static void prv_pop_stacks_in_range(ModalPriority low, ModalPriority high) {
// Discreet modals are transparent and unfocusable, they are not meant to be popped when
// requesting opaque focusable modals to pop.
for (ModalPriority priority = MAX(low, ModalPriorityDiscreet + 1); priority <= high;
priority++) {
ModalContext *m_context = &s_modal_window_stacks[priority];
window_stack_pop_all(&m_context->window_stack, true /* animated */);
}
}
void modal_manager_pop_all(void) {
prv_pop_stacks_in_range(ModalPriorityMin, NumModalPriorities - 1);
}
void modal_manager_pop_all_below_priority(ModalPriority priority) {
prv_pop_stacks_in_range(ModalPriorityMin, priority - 1);
}
static const CompositorTransition *prv_get_compositor_transition(bool modal_is_destination) {
bool is_top_discreet;
if (modal_is_destination) {
Window *window =
window_stack_get_top_window(&s_modal_window_stacks[ModalPriorityDiscreet].window_stack);
is_top_discreet = (window && (window == modal_manager_get_top_window()));
} else {
is_top_discreet = (s_last_highest_modal_priority == ModalPriorityDiscreet);
}
return is_top_discreet ? NULL : compositor_modal_transition_to_modal_get(modal_is_destination);
}
static void prv_handle_app_to_modal_transition_visible(void) {
// The last event resulted in a modal window being pushed where we didn't have any before.
// Start the animation!
compositor_transition(prv_get_compositor_transition(true /* modal_is_destination */));
}
static void prv_handle_modal_to_app_transition_visible(void) {
compositor_transition(prv_get_compositor_transition(false /* modal_is_destination */));
}
static void prv_handle_app_to_modal_transition_hidden_and_unfocused(void) {
#if !RECOVERY_FW && !SHELL_SDK
app_idle_timeout_pause();
#endif
}
static void prv_handle_modal_to_app_transition_hidden_and_unfocused(void) {
#if !RECOVERY_FW && !SHELL_SDK
app_idle_timeout_resume();
#endif
}
static void prv_handle_app_to_modal_transition_focus(void) {
#if !RECOVERY_FW && (!SHELL_SDK || CAPABILITY_HAS_SDK_SHELL4)
watchface_reset_click_manager();
#endif
// Let the underlying window know it has lost focus if this is the first modal
// window to show up.
prv_send_will_focus_event(false /* in_focus */);
}
static void prv_handle_modal_to_app_transition_focus(void) {
// There are no more modal windows, so we need to cleanup the modal window state.
click_manager_clear(modal_manager_get_click_manager());
prv_send_will_focus_event(true /* in_focus */);
}
void modal_manager_event_loop_upkeep(void) {
if (!modal_manager_get_enabled()) {
return;
}
UpdateContext update;
prv_update_modal_stacks(&update);
const ModalProperty last_properties = s_current_modal_properties;
s_current_modal_properties = update.properties;
const bool is_modal_transitionable = (update.properties & ModalProperty_CompositorTransitions);
const bool was_modal_transitionable = (last_properties & ModalProperty_CompositorTransitions);
if (!was_modal_transitionable && is_modal_transitionable) {
// We now have a window visible when we didn't have one before, start the transition.
prv_handle_app_to_modal_transition_visible();
} else if (was_modal_transitionable && !is_modal_transitionable) {
// This event resulted in our last visible modal window being popped, let's transition away.
prv_handle_modal_to_app_transition_visible();
}
const bool is_modal_unfocused = (update.properties & ModalProperty_Unfocused);
const bool was_modal_unfocused = (last_properties & ModalProperty_Unfocused);
if (was_modal_unfocused && !is_modal_unfocused) {
// We now have a modal window focused when we didn't have one before, start the transition.
prv_handle_app_to_modal_transition_focus();
} else if (!was_modal_unfocused && is_modal_unfocused) {
// This event resulted in our last focusable modal window being popped, let's transition away.
prv_handle_modal_to_app_transition_focus();
}
const bool is_app_hidden_and_unfocused =
(!(update.properties & ModalProperty_Transparent) && !is_modal_unfocused);
const bool was_app_hidden_and_unfocused =
(!(last_properties & ModalProperty_Transparent) && !was_modal_unfocused);
if (!was_app_hidden_and_unfocused && is_app_hidden_and_unfocused) {
// The app is now obstructed by an opaque modal and lost focus to a modal, idle.
prv_handle_app_to_modal_transition_hidden_and_unfocused();
} else if (was_app_hidden_and_unfocused && !is_app_hidden_and_unfocused) {
// The app now either is obstructed only by transparent modals or gained focus, resume.
prv_handle_modal_to_app_transition_hidden_and_unfocused();
}
// We have modal windows and we should render them, either because they asked to or because
// they recently became the top window in their respective modal stacks and haven't noticed yet.
// See the handling for off screen windows in prv_render_modal_stack.
if (update.properties & ModalProperty_RenderRequested) {
compositor_modal_render_ready();
}
s_last_highest_modal_priority = update.highest_idx;
}
typedef struct IterContext {
Window *current_top_window;
ModalPriority current_idx;
ModalPriority first_visible_idx;
ModalPriority first_transition_idx;
ModalPriority first_focus_idx;
ModalPriority first_opaque_idx;
} IterContext;
typedef bool (*ModalContextIterCallback)(ModalContext *modal, IterContext *iter, void *data);
static void prv_each_modal_stack(ModalContextIterCallback callback, void *data) {
IterContext iter = {
.first_visible_idx = ModalPriorityInvalid,
.first_transition_idx = ModalPriorityInvalid,
.first_focus_idx = ModalPriorityInvalid,
.first_opaque_idx = ModalPriorityInvalid,
};
for (ModalPriority idx = NumModalPriorities - 1; idx >= ModalPriorityMin; idx--) {
ModalContext *context = &s_modal_window_stacks[idx];
if (!prv_has_visible_window(context, NULL)) {
continue;
} else if (iter.first_visible_idx == ModalPriorityInvalid) {
iter.first_visible_idx = idx;
}
if (prv_has_transition_window(context) && (iter.first_transition_idx == ModalPriorityInvalid)) {
iter.first_transition_idx = idx;
}
if (prv_has_focusable_window(context) && (iter.first_focus_idx == ModalPriorityInvalid)) {
iter.first_focus_idx = idx;
}
if (prv_has_opaque_window(context) && (iter.first_opaque_idx == ModalPriorityInvalid)) {
iter.first_opaque_idx = idx;
}
}
if (iter.first_visible_idx != ModalPriorityInvalid) {
for (ModalPriority idx = ModalPriorityMin; idx < NumModalPriorities; idx++) {
ModalContext *modal = &s_modal_window_stacks[idx];
WindowStack *stack = &modal->window_stack;
iter.current_idx = idx;
iter.current_top_window = window_stack_get_top_window(stack);
if (!iter.current_top_window) {
continue;
}
const bool should_continue = callback(modal, &iter, data);
if (!should_continue) {
break;
}
}
}
}
static bool prv_update_modal_stack_callback(ModalContext *modal, IterContext *iter, void *data) {
UpdateContext *ctx = data;
Window *window = iter->current_top_window;
// Handle window state changes
const bool is_visible = (iter->current_idx >= iter->first_opaque_idx);
if (!window->on_screen && is_visible) {
// We've been exposed by a higher priority modal window stack emptying out, become on screen
// now.
window_set_on_screen(window, true /* new on screen */, true /* call handlers */);
}
// Setting on-screen can configure the click, but if this is a window below a transparent
// window that just disappeared, it was already on screen and may need its click configured.
const bool is_focused = (iter->current_idx == iter->first_focus_idx);
if (!window->is_click_configured && is_focused) {
// Input is now exposed by a higher priority modal window stack emptying out, gain input
window_setup_click_config_provider(window);
} else if (window->is_click_configured && !is_focused) {
// A different modal window now has focus
window->is_click_configured = false;
}
// Set the last highest visible modal priority
if (is_visible) {
ctx->highest_idx = iter->current_idx;
}
// Update properties based on state changes
// If this callback was called, there exists a modal
ctx->properties |= ModalProperty_Exists;
if (iter->current_idx > ModalPriorityDiscreet) {
// There is a modal window that has a compositor transition
ctx->properties |= ModalProperty_CompositorTransitions;
}
if (is_visible) {
if (!window->is_transparent) {
// There is a visible opaque window, remove the transparent property
ctx->properties &= ~ModalProperty_Transparent;
}
if (window->is_render_scheduled) {
// There is a visible window that will render, add the render requested property
ctx->properties |= ModalProperty_RenderRequested;
}
}
if (is_focused) {
// There is a modal with focus, remove the unfocused property
ctx->properties &= ~ModalProperty_Unfocused;
}
return true;
}
static bool prv_render_modal_stack_callback(ModalContext *modal, IterContext *iter, void *data) {
if (iter->current_idx < iter->first_opaque_idx) {
return true;
}
GContext *ctx = data;
WindowStack *stack = &modal->window_stack;
Window *window = iter->current_top_window;
if (window_stack_is_animating(stack) &&
stack->transition_context.implementation &&
stack->transition_context.implementation->render) {
// a lot of safety guards to make sure the transition can do render by its own
WindowTransitioningContext *const transition_context = &stack->transition_context;
transition_context->implementation->render(transition_context, ctx);
} else {
PROFILER_NODE_START(render_modal);
window_render(window, ctx);
PROFILER_NODE_STOP(render_modal);
}
return true;
}
static void prv_update_modal_stacks(UpdateContext *context) {
context->highest_idx = ModalPriorityInvalid;
context->properties = ModalPropertyDefault;
prv_each_modal_stack(prv_update_modal_stack_callback, context);
}
ModalProperty modal_manager_get_properties(void) {
return modal_manager_get_enabled() ? s_current_modal_properties : ModalPropertyDefault;
}
void modal_manager_render(GContext *ctx) {
PBL_ASSERTN(ctx);
prv_each_modal_stack(prv_render_modal_stack_callback, ctx);
}
typedef struct VisibleContext {
Window *window;
bool visible;
} VisibleContext;
static bool prv_is_window_visible_callback(ModalContext *modal, IterContext *iter, void *data) {
if (iter->current_idx < iter->first_opaque_idx) {
return true;
}
VisibleContext *ctx = data;
ctx->visible = (ctx->window == iter->current_top_window);
return !ctx->visible;
}
bool modal_manager_is_window_visible(Window *window) {
VisibleContext context = { .window = window };
prv_each_modal_stack(prv_is_window_visible_callback, &context);
return context.visible;
}
typedef struct FocusedContext {
Window *window;
bool focused;
} FocusedContext;
static bool prv_is_window_focused_callback(ModalContext *modal, IterContext *iter, void *data) {
FocusedContext *ctx = data;
ctx->focused = ((iter->current_top_window == ctx->window) &&
(iter->current_idx == iter->first_focus_idx));
return !ctx->focused;
}
bool modal_manager_is_window_focused(Window *window) {
FocusedContext context = { .window = window };
prv_each_modal_stack(prv_is_window_focused_callback, &context);
return context.focused;
}
static Window *prv_get_visible_focused_window(void) {
WindowStack *stack = prv_find_window_stack(prv_has_visible_focusable_window, NULL);
return window_stack_get_top_window(stack);
}
void modal_manager_handle_button_event(PebbleEvent *event) {
ClickManager *click_manager = modal_manager_get_click_manager();
switch (event->type) {
case PEBBLE_BUTTON_DOWN_EVENT: {
// If we get a button event, it must also be for the top modal window.
Window *window = prv_get_visible_focused_window();
// Ensure that this function isn't being called when a modal window
// is not present.
PBL_ASSERTN(window);
ButtonId id = event->button.button_id;
if (id == BUTTON_ID_BACK && !window->overrides_back_button) {
window_stack_remove(window, true /* animated */);
} else {
click_recognizer_handle_button_down(&click_manager->recognizers[id]);
}
break;
}
case PEBBLE_BUTTON_UP_EVENT: {
ButtonId id = event->button.button_id;
click_recognizer_handle_button_up(&click_manager->recognizers[id]);
break;
}
default:
PBL_CROAK("Invalid event type: %u", event->type);
}
}
void modal_window_push(Window *window, ModalPriority priority, bool animated) {
// Note: We do not have to adjust the `animated` argument to consider whether
// this window is higher priority than the current visible window stack, as
// this is taken care of by the transition context handlers in `window_stack.c`
window_stack_push(modal_manager_get_window_stack(priority), window, animated);
}
// Commands
////////////////////////////
typedef struct WindowStackInfoContext {
SemaphoreHandle_t interlock;
WindowStackDump *dumps[NumModalPriorities];
size_t counts[NumModalPriorities];
bool disabled;
} WindowStackInfoContext;
static void prv_modal_window_stack_info_cb(void *ctx) {
WindowStackInfoContext *info = ctx;
if (modal_manager_get_enabled()) {
for (ModalPriority priority = 0;
priority < NumModalPriorities;
++priority) {
WindowStack *window_stack = modal_manager_get_window_stack(priority);
info->counts[priority] = window_stack_dump(window_stack,
&info->dumps[priority]);
}
} else {
info->disabled = true;
}
xSemaphoreGive(info->interlock);
}
void command_modal_stack_info(void) {
WindowStackInfoContext info = {
.interlock = xSemaphoreCreateBinary(),
};
if (!info.interlock) {
prompt_send_response("Couldn't allocate semaphore for modal stack");
return;
}
launcher_task_add_callback(prv_modal_window_stack_info_cb, &info);
xSemaphoreTake(info.interlock, portMAX_DELAY);
vSemaphoreDelete(info.interlock);
prompt_send_response("Modal Stack, top to bottom:");
char buffer[128];
for (ModalPriority priority = NumModalPriorities - 1;
priority > ModalPriorityInvalid;
--priority) {
prompt_send_response_fmt(buffer, sizeof(buffer), "Priority: %d (%zu)",
priority, info.counts[priority]);
if (info.counts[priority] > 0 && !info.dumps[priority]) {
prompt_send_response("Couldn't allocate buffers for modal stack data");
} else {
for (size_t i = 0; i < info.counts[priority]; ++i) {
prompt_send_response_fmt(buffer, sizeof(buffer), "window %p <%s>",
info.dumps[priority][i].addr,
info.dumps[priority][i].name);
}
}
kernel_free(info.dumps[priority]);
}
}
void modal_manager_reset(void) {
for (ModalPriority idx = ModalPriorityInvalid + 1; idx < NumModalPriorities; idx++) {
memset(&s_modal_window_stacks[idx], 0, sizeof(ModalContext));
}
s_modal_min_priority = ModalPriorityDiscreet;
modal_manager_init();
}

View file

@ -0,0 +1,176 @@
/*
* 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.
*/
#pragma once
#include "modal_manager_private.h"
#include "applib/graphics/graphics.h"
#include "applib/ui/click_internal.h"
#include "applib/ui/window.h"
#include "applib/ui/window_stack_animation.h"
#include "applib/ui/window_stack_private.h"
#include "kernel/events.h"
struct ModalContext;
typedef struct ModalContext ModalContext;
typedef bool (*ModalContextFilterCallback)(ModalContext *context, void *data);
//! This defines the priorities for the various modals. The order in which they are
//! defined is specified by the interruption policy and determine who is able to interrupt
//! whom. If a modal has a higher priority than another modal, it is able to interrupt
//! that modal. Before adding anything to here, consider how it would affect the interrupt
//! policy.
//! Interruption Policy:
//! https://docs.google.com/presentation/d/1xIjOR9kh4jcBYonzRstdtFQW0ZNMoFZZwYOUMN2JHNI
typedef enum ModalPriority {
//! Invalid priority for a modal.
ModalPriorityInvalid = -1,
//! Min priority, all modals are below or equal to this priority.
ModalPriorityMin = 0,
//! Discreet mode for watchface overlay information such as Timeline Peek.
//! This priority should be used for modals that meant to display above the watchface without
//! completely obstructing the watchface. For this reason, Discreet modal windows do not have
//! compositor transitions because partial obstruction requires notifying the app of the
//! unobstructed regions which only the modal window is able to derive.
ModalPriorityDiscreet = ModalPriorityMin,
//! Priority for generic one-off windows such as the battery charging window.
//! This priority should be used for any modals that don't necessarily care how
//! they are shown, just that they are shown.
ModalPriorityGeneric,
//! Priority used for displaying the phone ui after a phone call has been answered.
//! Note: Notifications should always be able to subvert this.
ModalPriorityPhone,
//! Priority used for displaying notifications.
ModalPriorityNotification,
//! Priority used for displaying alerts. Alerts are important, but shouldn't
//! impact the user of the watch.
ModalPriorityAlert,
//! Priority used for displaying the voice screen for recording. Ensure this one is
//! always kept second to last.
ModalPriorityVoice,
//! Priority for displaying time-sensitive/critical windows which provide information
//! on things that may affect the user's watch experience. However, these should never
//! prevent an alarm from displaying.
ModalPriorityCritical,
//! Priority used for displaying wake up events such as alarms.
ModalPriorityAlarm,
//! Max priority, all modals are below this priority
ModalPriorityMax,
NumModalPriorities = ModalPriorityMax,
} ModalPriority;
typedef enum ModalProperty {
ModalProperty_None = 0,
//! Whether there exists a modal in a modal stack on screen.
ModalProperty_Exists = (1 << 0),
//! Whether there exists a modal in a modal stack on screen that uses compositor transitions.
ModalProperty_CompositorTransitions = (1 << 1),
//! Whether there exists a modal that requested to render.
ModalProperty_RenderRequested = (1 << 2),
//! Whether all modal stacks are transparent. Having no modal is treated as transparent.
ModalProperty_Transparent = (1 << 3),
//! Whether all modal stacks pass input through. Having no modal is treated as unfocused.
ModalProperty_Unfocused = (1 << 4),
//! The default properties equivalent to there being no modal windows.
ModalPropertyDefault = (ModalProperty_Transparent | ModalProperty_Unfocused),
} ModalProperty;
//! Initializes the modal window state. This should be called before any
//! Modal applications attempt to push windows.
void modal_manager_init(void);
//! Sets whether modal windows are enabled.
//! @param enabled Boolean indicating whether enabled or disabled
//! Note that this is usable before modal_manager_init is called and modal_manager_init will not
//! reset this state.
void modal_manager_set_min_priority(ModalPriority priority);
//! Gets whether modal windows are enabled.
// @returns boolean indicating if modals are enabled
bool modal_manager_get_enabled(void);
//! Returns the ClickManager for the modal windows.
//! @returns pointer to a \ref ClickManager
ClickManager *modal_manager_get_click_manager(void);
//! Returns the first \ref WindowStack to pass the given filter callback.
//! Iterates down from the highest priority to the lowest priority.
//! @param filter_cb The \ref ModalContextFilterCallback
//! @param context Context to pass to the callback
//! @returns pointer to a \ref WindowStack
WindowStack *modal_manager_find_window_stack(ModalContextFilterCallback filter_cb, void *ctx);
//! Returns the stack with the given window priority.
//! If the passed priority is invalid, raises an assertion.
//! @param priority The \ref ModalPriority of the desired \ref WindowStack
//! @returns pointer to a \ref WindowStack
WindowStack *modal_manager_get_window_stack(ModalPriority priority);
//! Returns the \ref Window of the current visible stack if there is one,
//! otherwise NULL.
//! @returns Pointer to a \ref Window
Window *modal_manager_get_top_window(void);
//! Handles a button press event for the Modal Window. Raises an
//! assertion if the event is not a click event.
//! @param event The \ref PebbleEvent to handle.
void modal_manager_handle_button_event(PebbleEvent *event);
//! Pops all windows from all modal stacks.
void modal_manager_pop_all(void);
//! Pops all windows from modal stacks with priorities less than the given priority
//! @param the max priorirty stack to pop all windows from
void modal_manager_pop_all_below_priority(ModalPriority priority);
//! Called from the kernel event loop between events to handle any changes that have been made
//! to the modal window stacks.
void modal_manager_event_loop_upkeep(void);
//! Enumerates through the modal stacks and returns the flattened properties of all stacks
//! combined. For example, if all modals are transparent, the Transparent property will be
//! returned. Flattened meaning the entire stack is considered in aggregate. For example, if any
//! one modal is opaque, the Transparent property won't be returned.
ModalProperty modal_manager_get_properties(void);
//! Renders the highest priority top opaque window and all windows with higher priority.
//! @param ctx The \ref GContext in which to render.
//! @return Modal properties such as whether the modals are transparent or unfocusable
void modal_manager_render(GContext *ctx);
//! Determines whether the given modal window is visible. Use window_manager_is_window_visible if
//! both app windows and modal windows need to be considered.
//! @param window The modal window to determine whether it is visible.
//! @return true if the modal window is visible, false otherwise
bool modal_manager_is_window_visible(Window *window);
//! Determines whether the given modal window is focused. Use window_manager_is_window_focused if
//! both app windows and modal windows need to be considered.
//! @param window The modal window to determine whether it is focused.
//! @return true if the modal window is focused, false otherwise
bool modal_manager_is_window_focused(Window *window);
//! Wrapper to call \ref window_stack_push() with the appropriate stack for
//! the given \ref ModalPriority
//! @param window The window to push onto the stack
//! @param priority The priority of the window stack to push to
//! @param animated `True` for animated, otherwise `False`
void modal_window_push(Window *window, ModalPriority priority, bool animated);
//! Reset the modal manager state. Useful for unit testing.
void modal_manager_reset(void);

View file

@ -0,0 +1,18 @@
/*
* 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.
*/
#pragma once

View file

@ -0,0 +1,382 @@
/*
* 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.
*/
#pragma once
#include <stddef.h>
#include "applib/graphics/gtypes.h"
// GBitmap + pixel data generated by bitmapgen.py:
/** Status Bar Icons */
static const uint8_t s_status_icon_launcher_pixels[] = {
0xdf, 0x07, 0x00, 0x00, 0x8f, 0x07, 0x00, 0x00, 0x07, 0x07, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, /* bytes 0 - 16 */
0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x04, 0x00, 0x00, 0x71, 0x04, 0x00, 0x00, /* bytes 16 - 32 */
0x71, 0x04, 0x00, 0x00, 0x71, 0x04, 0x00, 0x00,
};
static const GBitmap s_status_icon_launcher_bitmap = {
.addr = (void*) &s_status_icon_launcher_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 11, .h = 10 },
},
};
static const uint8_t s_status_icon_sms_pixels[] = {
0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* bytes 0 - 16 */
0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0xc7, 0x03, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00, /* bytes 16 - 32 */
0xf7, 0x03, 0x00, 0x00,
};
static const GBitmap s_status_icon_sms_bitmap = {
.addr = (void*) &s_status_icon_sms_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 10, .h = 9 },
},
};
static const uint8_t s_status_icon_bluetooth_pixels[] = {
0x77, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, /* bytes 0 - 16 */
0x55, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, /* bytes 16 - 32 */
0x57, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00,
};
static const GBitmap s_status_icon_bluetooth_bitmap = {
.addr = (void*) &s_status_icon_bluetooth_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 7, .h = 11 },
},
};
static const uint8_t s_status_icon_settings_pixels[] = {
0x3f, 0x03, 0x00, 0x00, 0xbf, 0x03, 0x00, 0x00, 0x3f, 0x01, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, /* bytes 0 - 16 */
0x8f, 0x03, 0x00, 0x00, 0xc7, 0x03, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xf2, 0x03, 0x00, 0x00, /* bytes 16 - 32 */
0xf7, 0x03, 0x00, 0x00, 0xf3, 0x03, 0x00, 0x00,
};
static const GBitmap s_status_icon_settings_bitmap = {
.addr = (void*) &s_status_icon_settings_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 10, .h = 10 },
},
};
static const uint8_t s_status_icon_phone_pixels[] = {
0xfd, 0x07, 0x00, 0x00, 0xf8, 0x07, 0x00, 0x00, 0xf0, 0x07, 0x00, 0x00, 0xf8, 0x07, 0x00, 0x00, /* bytes 0 - 16 */
0xf9, 0x07, 0x00, 0x00, 0xf1, 0x07, 0x00, 0x00, 0xe3, 0x07, 0x00, 0x00, 0xc7, 0x06, 0x00, 0x00, /* bytes 16 - 32 */
0x0f, 0x04, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x7f, 0x04, 0x00, 0x00,
};
static const GBitmap s_status_icon_phone_bitmap = {
.addr = (void*) &s_status_icon_phone_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 11, .h = 11 },
},
};
static const uint8_t s_status_icon_music_pixels[] = {
0x3f, 0x00, 0x00, 0x00, 0xc7, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x00, 0x00, /* bytes 0 - 16 */
0xf7, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, /* bytes 16 - 32 */
0x10, 0x01, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00,
};
static const GBitmap s_status_icon_music_bitmap = {
.addr = (void*) &s_status_icon_music_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 9, .h = 10 },
},
};
static const uint8_t s_status_icon_silent_pixels[] = {
0x7f, 0x03, 0x00, 0x00, 0x3f, 0x01, 0x00, 0x00, 0x9f, 0x04, 0x00, 0x00, 0x43, 0x06, 0x00, 0x00, /* bytes 0 - 16 */
0x23, 0x07, 0x00, 0x00, 0x93, 0x07, 0x00, 0x00, 0x4b, 0x07, 0x00, 0x00, 0x27, 0x07, 0x00, 0x00, /* bytes 16 - 32 */
0x13, 0x07, 0x00, 0x00, 0x39, 0x07, 0x00, 0x00, 0x7c, 0x07, 0x00, 0x00,
};
static const GBitmap s_status_icon_silent_bitmap = {
.addr = (void*) &s_status_icon_silent_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 11, .h = 11 },
},
};
static const uint8_t s_quiet_time_status_icon_pixels[] = {
0x03, 0x03, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* bytes 0 - 16 */
0xfc, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* bytes 16 - 32 */
0x01, 0x02, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00,
};
static const GBitmap s_quiet_time_status_icon_bitmap = {
.addr = (void*) &s_quiet_time_status_icon_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 10, .h = 10 },
},
};
/** Action Bar Icons */
static const uint8_t s_bar_icon_actions_pixels[] = {
0xff, 0xf7, 0x00, 0x00, 0xff, 0xe7, 0x00, 0x00, 0x83, 0xc7, 0x00, 0x00, 0xfd, 0x81, 0x00, 0x00, /* bytes 0 - 16 */
0x7d, 0x00, 0x00, 0x00, 0x1d, 0x80, 0x00, 0x00, 0x9d, 0xc7, 0x00, 0x00, 0xed, 0xe7, 0x00, 0x00, /* bytes 16 - 32 */
0xed, 0xf7, 0x00, 0x00, 0xfd, 0xbf, 0x00, 0x00, 0xfd, 0xbf, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00,
};
static const GBitmap s_bar_icon_actions_bitmap = {
.addr = (void*) &s_bar_icon_actions_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 16, .h = 12 },
},
};
static const uint8_t s_bar_icon_phone_pixels[] = {
0xf3, 0x7f, 0x00, 0x00, 0xe1, 0x7f, 0x00, 0x00, 0xc1, 0x7f, 0x00, 0x00, 0xc1, 0x7f, 0x00, 0x00, /* bytes 0 - 16 */
0xe1, 0x7f, 0x00, 0x00, 0xe3, 0x7f, 0x00, 0x00, 0xe3, 0x7f, 0x00, 0x00, 0xc7, 0x7f, 0x00, 0x00, /* bytes 16 - 32 */
0x87, 0x7f, 0x00, 0x00, 0x0f, 0x67, 0x00, 0x00, 0x1f, 0x40, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, /* bytes 32 - 48 */
0xff, 0x00, 0x00, 0x00, 0xff, 0x43, 0x00, 0x00,
};
static const GBitmap s_bar_icon_phone_bitmap = {
.addr = (void*) &s_bar_icon_phone_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 15, .h = 14 },
},
};
static const uint8_t s_bar_icon_x_pixels[] = {
0xfb, 0x0d, 0x00, 0x00, 0xf1, 0x08, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, /* bytes 0 - 16 */
0x03, 0x0c, 0x00, 0x00, 0x07, 0x0e, 0x00, 0x00, 0x07, 0x0e, 0x00, 0x00, 0x03, 0x0c, 0x00, 0x00, /* bytes 16 - 32 */
0x01, 0x08, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xf1, 0x08, 0x00, 0x00, 0xfb, 0x0d, 0x00, 0x00,
};
static const GBitmap s_bar_icon_x_bitmap = {
.addr = (void*) &s_bar_icon_x_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 12, .h = 12 },
},
};
static const uint8_t s_bar_icon_check_pixels[] = {
0x00, 0x80, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xf0, 0x01, 0x00, /* bytes 0 - 16 */
0x00, 0xf8, 0x00, 0x00, 0x04, 0x7c, 0x00, 0x00, 0x0e, 0x3e, 0x00, 0x00, 0x1f, 0x1f, 0x00, 0x00, /* bytes 16 - 32 */
0xbe, 0x0f, 0x00, 0x00, 0xfc, 0x07, 0x00, 0x00, 0xf8, 0x03, 0x00, 0x00, 0xf0, 0x01, 0x00, 0x00, /* bytes 32 - 48 */
0xe0, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
};
static const GBitmap s_bar_icon_check_bitmap = {
.addr = (void*) &s_bar_icon_check_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 18, .h = 14 },
},
};
static const uint8_t s_bar_icon_up_pixels[] = {
0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00, /* bytes 0 - 16 */
0xfc, 0x03, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00,
};
static const GBitmap s_bar_icon_up_bitmap = {
.addr = (void*) &s_bar_icon_up_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 12, .h = 7 },
},
};
static const uint8_t s_bar_icon_down_pixels[] = {
0xff, 0x0f, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00, /* bytes 0 - 16 */
0xf0, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const GBitmap s_bar_icon_down_bitmap = {
.addr = (void*) &s_bar_icon_down_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 12, .h = 7 },
},
};
static const uint8_t s_bar_icon_yes_pixels[] = {
0xf8, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x71, 0x04, 0x00, 0x00, /* bytes 0 - 16 */
0x71, 0x04, 0x00, 0x00, 0x71, 0x04, 0x00, 0x00, 0x23, 0x06, 0x00, 0x00, 0x23, 0x06, 0x00, 0x00, /* bytes 16 - 32 */
0x27, 0x07, 0x00, 0x00, 0x07, 0x07, 0x00, 0x00, 0x07, 0x07, 0x00, 0x00, 0x8f, 0x07, 0x00, 0x00, /* bytes 32 - 48 */
0x8f, 0x07, 0x00, 0x00, 0x8f, 0x07, 0x00, 0x00, 0x8f, 0x07, 0x00, 0x00, 0x8f, 0x07, 0x00, 0x00, /* bytes 48 - 64 */
0x8f, 0x07, 0x00, 0x00, 0x8f, 0x07, 0x00, 0x00,
};
static const GBitmap s_bar_icon_yes_bitmap = {
.addr = (void*) &s_bar_icon_yes_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 11, .h = 18 },
},
};
static const uint8_t s_bar_icon_no_pixels[] = {
0x78, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, /* bytes 0 - 16 */
0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, /* bytes 16 - 32 */
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, /* bytes 32 - 48 */
0x18, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, /* bytes 48 - 64 */
0x78, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00,
};
static const GBitmap s_bar_icon_no_bitmap = {
.addr = (void*) &s_bar_icon_no_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 10, .h = 18 },
},
};
static const uint8_t s_bar_icon_snooze_pixels[] = {
0x3f, 0x00, 0x00, 0x00, 0xff, 0x0b, 0x00, 0x00, 0xff, 0x0d, 0x00, 0x00, 0x80, 0x0e, 0x00, 0x00, /* bytes 0 - 16 */
0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0xc7, 0x0f, 0x00, 0x00, 0xe3, 0x0f, 0x00, 0x00, /* bytes 16 - 32 */
0xf1, 0x0f, 0x00, 0x00, 0xf8, 0x0f, 0x00, 0x00, 0x80, 0x0f, 0x00, 0x00, 0x80, 0x0f, 0x00, 0x00,
};
static const GBitmap s_bar_icon_snooze_bitmap = {
.addr = (void*) &s_bar_icon_snooze_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 12, .h = 12 },
},
};
// Battery Icons
////////////////////////////////////////////////////////////
static const uint8_t s_status_battery_empty_pixels[] = {
0xff, 0x3f, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x01, 0x60, 0x00, 0x00, 0x01, 0x60, 0x00, 0x00, /* bytes 0 - 16 */
0x01, 0x60, 0x00, 0x00, 0x01, 0x60, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0xff, 0x3f, 0x00, 0x00,
};
static const GBitmap s_status_battery_empty_bitmap = {
.addr = (void*) &s_status_battery_empty_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 15, .h = 8 },
},
};
static const uint8_t s_status_battery_charging_pixels[] = {
0x88, 0xff, 0x1f, 0x00, 0x8c, 0x00, 0x10, 0x00, 0x86, 0x00, 0x30, 0x00, 0x87, 0x00, 0x30, 0x00, /* bytes 0 - 16 */
0x9c, 0x00, 0x30, 0x00, 0x8c, 0x00, 0x30, 0x00, 0x86, 0x00, 0x10, 0x00, 0x82, 0xff, 0x1f, 0x00,
};
static const GBitmap s_status_battery_charging_bitmap = {
.addr = (void*) &s_status_battery_charging_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 22, .h = 8 },
},
};
static const uint8_t s_status_battery_charged_pixels[] = {
0x0f, 0x3c, 0x00, 0x00, 0xc1, 0x21, 0x00, 0x00, 0xe1, 0x67, 0x00, 0x00, 0xfd, 0x61, 0x00, 0x00, /* bytes 0 - 16 */
0xfd, 0x61, 0x00, 0x00, 0xe1, 0x67, 0x00, 0x00, 0xc1, 0x21, 0x00, 0x00, 0x0f, 0x3c, 0x00, 0x00,
};
static const GBitmap s_status_battery_charged_bitmap = {
.addr = (void*) &s_status_battery_charged_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 15, .h = 8 },
},
};
static const uint8_t s_status_icon_phone_only_pixels[] = {
0x41, 0x02, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, /* bytes 0 - 16 */
0x24, 0x00, 0x00, 0x00, 0x13, 0x02, 0x00, 0x00, 0xc9, 0x03, 0x00, 0x00, 0xe4, 0x03, 0x00, 0x00, /* bytes 16 - 32 */
0xf6, 0x03, 0x00, 0x00,
};
static const GBitmap s_status_icon_phone_only_bitmap = {
.addr = (void*) &s_status_icon_phone_only_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 10, .h = 9 },
},
};
static const uint8_t s_status_icon_airplane_mode_pixels[] = {
0xcf, 0x0f, 0x00, 0x00, 0x9f, 0x0f, 0x00, 0x00, 0x1c, 0x0f, 0x00, 0x00, 0x39, 0x0e, 0x00, 0x00, /* bytes 0 - 16 */
0x01, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x39, 0x0e, 0x00, 0x00, /* bytes 16 - 32 */
0x1c, 0x0f, 0x00, 0x00, 0x9f, 0x0f, 0x00, 0x00, 0xcf, 0x0f, 0x00, 0x00,
};
static const GBitmap s_status_icon_airplane_mode_bitmap = {
.addr = (void*) &s_status_icon_airplane_mode_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 12, .h = 11 },
},
};

View file

@ -0,0 +1,88 @@
/*
* 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 "delay.h"
#include "util/attributes.h"
#include "util/units.h"
#define STM32F2_COMPATIBLE
#define STM32F4_COMPATIBLE
#define STM32F7_COMPATIBLE
#include <mcu.h>
#include <inttypes.h>
#if MICRO_FAMILY_STM32F7
# define INSTRUCTIONS_PER_LOOP (1)
#elif MICRO_FAMILY_STM32F2 || MICRO_FAMILY_STM32F4
# define INSTRUCTIONS_PER_LOOP (3)
#else
# error "Unexpected micro family"
#endif
static uint32_t s_loops_per_us = 0;
void NOINLINE delay_us(uint32_t us) {
uint32_t delay_loops = us * s_loops_per_us;
__asm volatile (
"spinloop: \n"
" subs %[delay_loops], #1 \n"
" bne spinloop \n"
: [delay_loops] "+r" (delay_loops) // read-write operand
:
: "cc"
);
}
void delay_init(void) {
// The loop above consists of 2 instructions (output of arm-none-eabi-objdump -d
// delay.X.o):
//
// subs r0, #1
// bne.w 4 <spinloop>
//
// Subtract consumes 1 cycle & the conditional branch consumes 1 + P (pipeline fill delay,
// 1-3 cycles) if the branch is taken, or 1 if not taken. For this situation, it appears that P=1
// on the STM32F2/F4, so the loop takes 3 and 2 cycles respectively. The Cortex-M7 (STM32F7) has a
// superscalar dual-issue architecture which allows for 1-cycle loops (including the subtract).
//
// @ 64MHz 1 instructions is ~15.6ns which translates to the previously measured 47ns for one loop
// Thus we can derive that to get a duration of 1µs from an arbitrary clock frequency the count
// value needs to be:
// count = 1e-6 / (1/F * 3) where F is the core clock frequency
//
// An additional note is that delay_us is always executed from flash. The
// instruction cache in the cortex M3 & M4 cores is pretty good at saving
// instructions with simple branches which means we don't stall on flash
// reads after the first loop. Counterintuitively, executing from SRAM
// actually adds a extra delay cycle on instruction fetches and can be
// stalled if peripherals are doing DMAs. (See PBL-22265 for more details)
RCC_ClocksTypeDef clocks;
RCC_GetClocksFreq(&clocks);
// Get the frequency in MHz so we don't overflow a uint32_t.
const uint32_t frequency_mhz = clocks.HCLK_Frequency / MHZ_TO_HZ(1);
const uint32_t clock_period_ps = PS_PER_US / frequency_mhz;
s_loops_per_us = PS_PER_US / (clock_period_ps * INSTRUCTIONS_PER_LOOP);
// we always want to delay for more than the time specified so round up
// if the numbers don't divide evenly
if ((PS_PER_US % (clock_period_ps * INSTRUCTIONS_PER_LOOP)) != 0) {
s_loops_per_us += 1;
}
}

View file

@ -0,0 +1,25 @@
/*
* 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.
*/
#pragma once
#include <inttypes.h>
//! Carefully timed spinloop that allows one to delay at a microsecond
//! granularity.
void delay_us(uint32_t us);
void delay_init(void);

View file

@ -0,0 +1,132 @@
/*
* 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 "kernel/util/factory_reset.h"
#include "drivers/rtc.h"
#include "drivers/task_watchdog.h"
#include "flash_region/filesystem_regions.h"
#include "kernel/event_loop.h"
#include "kernel/util/standby.h"
#include "process_management/worker_manager.h"
#include "services/common/event_service.h"
#include "services/common/shared_prf_storage/shared_prf_storage.h"
#include "services/common/system_task.h"
#include "services/runlevel.h"
#include "shell/normal/app_idle_timeout.h"
#include "system/bootbits.h"
#include "system/logging.h"
#include "system/reboot_reason.h"
#include "system/reset.h"
#include "kernel/util/sleep.h"
#if !RECOVERY_FW
#include "services/normal/blob_db/pin_db.h"
#include "services/normal/blob_db/reminder_db.h"
#include "services/normal/filesystem/pfs.h"
#include "services/normal/timeline/event.h"
#endif
static bool s_in_factory_reset = false;
static void prv_factory_reset_non_pfs_data() {
PBL_LOG_SYNC(LOG_LEVEL_INFO, "Factory resetting...");
// This function can block the system task for a long time.
// Prevent callbacks being added to the system task so it doesn't overflow.
system_task_block_callbacks(true /* block callbacks */);
launcher_block_popups(true);
worker_manager_disable();
event_service_clear_process_subscriptions(PebbleTask_App);
shared_prf_storage_wipe_all();
services_set_runlevel(RunLevel_BareMinimum);
app_idle_timeout_stop();
while (worker_manager_get_current_worker_md()) {
// busy loop until the worker is killed
psleep(3);
}
rtc_timezone_clear();
}
void factory_reset_set_reason_and_reset(void) {
RebootReason reason = { RebootReasonCode_FactoryResetReset, 0 };
reboot_reason_set(&reason);
system_reset();
}
static void prv_factory_reset_post(bool should_shutdown) {
if (should_shutdown) {
enter_standby(RebootReasonCode_FactoryResetShutdown);
} else {
factory_reset_set_reason_and_reset();
}
}
void factory_reset(bool should_shutdown) {
s_in_factory_reset = true;
prv_factory_reset_non_pfs_data();
// TODO: wipe the registry on tintin?
filesystem_regions_erase_all();
#if !defined(RECOVERY_FW)
// "First use" is part of the PRF image for Snowy
boot_bit_set(BOOT_BIT_FORCE_PRF);
#endif
prv_factory_reset_post(should_shutdown);
}
#if !RECOVERY_FW
void close_db_files() {
// Deinit the databases and any clients
timeline_event_deinit();
reminder_db_deinit();
pin_db_deinit();
}
void factory_reset_fast(void *unused) {
s_in_factory_reset = true;
// disable the watchdog... we've got lots to do before we reset
task_watchdog_mask_clear(pebble_task_get_current());
close_db_files();
prv_factory_reset_non_pfs_data();
pfs_remove_files(NULL);
prv_factory_reset_post(false /* should_shutdown */);
}
#endif // !RECOVERY_FW
//! Used by the mfg flow to kick us out the MFG firmware and into the conumer PRF that's stored
//! on the external flash.
void command_enter_consumer_mode(void) {
boot_bit_set(BOOT_BIT_FORCE_PRF);
factory_reset(true /* should_shutdown */);
}
bool factory_reset_ongoing(void) {
return s_in_factory_reset;
}

View file

@ -0,0 +1,29 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
//! Factory resets the device by wiping the flash
//! @param should_shutdown If true, shutdown after factory resetting, otherwise reboot.
void factory_reset(bool should_shutdown);
//! Factory resets the device by deleting all files
void factory_reset_fast(void *unused);
//! Returns true if a factory reset is in progress.
bool factory_reset_ongoing(void);

View file

@ -0,0 +1,93 @@
/*
* 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 "kernel/util/fw_reset.h"
#include "console/pulse_internal.h"
#include "kernel/core_dump.h"
#include "kernel/util/factory_reset.h"
#include "services/common/comm_session/session.h"
#include "services/common/system_task.h"
#include "services/runlevel.h"
#include "system/bootbits.h"
#include "system/logging.h"
#include "system/passert.h"
#include "system/reset.h"
static void prv_reset_into_prf(void) {
RebootReason reason = { RebootReasonCode_PrfReset, 0 };
reboot_reason_set(&reason);
boot_bit_set(BOOT_BIT_FORCE_PRF);
services_set_runlevel(RunLevel_BareMinimum);
system_reset();
}
void fw_reset_into_prf(void) {
prv_reset_into_prf();
}
static const uint8_t s_prf_reset_cmd = 0xff;
typedef enum {
ResetCmdNormal = 0x00,
ResetCmdCoreDump = 0x01,
ResetCmdFactoryReset = 0xfe,
ResetCmdIntoRecovery = 0xff,
} ResetCmd;
void reset_protocol_msg_callback(CommSession *session, const uint8_t* data, unsigned int length) {
PBL_ASSERT_RUNNING_FROM_EXPECTED_TASK(PebbleTask_KernelBackground);
const uint8_t cmd = data[0];
switch (cmd) {
case ResetCmdNormal:
PBL_LOG(LOG_LEVEL_WARNING, "Rebooting");
system_reset();
break;
case ResetCmdCoreDump:
PBL_LOG(LOG_LEVEL_INFO, "Core dump + Reboot triggered");
core_dump_reset(true /* force overwrite any existing core dump */);
break;
case ResetCmdIntoRecovery:
PBL_LOG(LOG_LEVEL_WARNING, "Rebooting into PRF");
prv_reset_into_prf();
break;
case ResetCmdFactoryReset:
factory_reset(false /* should_shutdown */);
break;
default:
PBL_LOG(LOG_LEVEL_ERROR, "Invalid reset msg, data[0] %u", data[0]);
break;
}
}
void fw_prepare_for_reset(bool unsafe_reset) {
if (!unsafe_reset) {
// Tear down Bluetooth, to avoid confusing the phone:
services_set_runlevel(RunLevel_BareMinimum);
#if PULSE_EVERYWHERE
pulse_end();
#endif
} else {
pulse_prepare_to_crash();
}
}

View file

@ -0,0 +1,28 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
void fw_prepare_for_reset(bool skip_bt_teardown);
void fw_reset_into_prf(void);
typedef enum RemoteResetType {
RemoteResetRegular = 0x00,
RemoteResetPrf = 0xff,
} RemoteResetType;

View file

@ -0,0 +1,95 @@
/*
* 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 "interval_timer.h"
#include "drivers/rtc.h"
#include "FreeRTOS.h"
static uint64_t prv_get_curr_system_time_ms(void) {
time_t time_s;
uint16_t time_ms;
rtc_get_time_ms(&time_s, &time_ms);
return (((uint64_t)time_s) * 1000 + time_ms);
}
void interval_timer_init(IntervalTimer *timer, uint32_t min_expected_ms, uint32_t max_expected_ms,
uint32_t weighting_factor_inverted) {
PBL_ASSERTN(weighting_factor_inverted != 0); // Divide by zero is not awesome
*timer = (IntervalTimer) {
.min_expected_ms = min_expected_ms,
.max_expected_ms = max_expected_ms,
.weighting_factor_inverted = weighting_factor_inverted
};
}
//! Record a sample that marks the start/end of an interval.
//! Safe to call from an ISR.
void interval_timer_take_sample(IntervalTimer *timer) {
portENTER_CRITICAL();
{
const uint64_t current_time = prv_get_curr_system_time_ms();
// Handle the first sample specially. We don't have an interval until we have
// 2 samples.
if (timer->num_samples == 0) {
timer->num_samples++;
} else {
const int64_t last_interval = current_time - timer->last_sample_timestamp_ms;
// Make sure this interval is valid
if (last_interval >= timer->min_expected_ms &&
last_interval <= timer->max_expected_ms) {
// It's valid! Let's roll it into our moving average
// This is an exponential moving average.
// https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
// average_now = average_previous + (weighting_factor * (new_value - average_previous))
// Where alpha is between 0 and 1. The closer to 1 the more responsive to recent changes
// the average is.
if (timer->num_samples == 1) {
// Initialize the average to the first sample we have
timer->average_ms = last_interval;
} else {
timer->average_ms = timer->average_ms +
((last_interval - timer->average_ms) / timer->weighting_factor_inverted);
}
timer->num_samples++;
}
}
timer->last_sample_timestamp_ms = current_time;
}
portEXIT_CRITICAL();
}
uint32_t interval_timer_get(IntervalTimer *timer, uint32_t *average_ms_out) {
uint32_t num_intervals;
portENTER_CRITICAL();
{
num_intervals = timer->num_samples ? timer->num_samples - 1 : 0;
*average_ms_out = timer->average_ms;
}
portEXIT_CRITICAL();
return num_intervals;
}

View file

@ -0,0 +1,74 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
//! @file interval_timer.h
//!
//! Times the average number of milliseconds between samples. Taking a sample is ISR-safe. Uses
//! the RTC time domain which should be fairly accurate for real clock time. Note that because
//! we use the RTC we may occasionally have to discard intervals because the wall clock time was
//! changed. This is not ideal, but it's still a better source of time than our SysTick time source
//! as that is not necessarily synced to real time.
//!
//! The resolution of the recorded timer will be same as the configured resolution of our RTC
//! peripheral, which at the time of writing is 1/256 of a second.
//!
//! The average is calculated as an exponential moving average.
//! https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
//! The weighting factor (how responsive to recent changes) is configurable.
typedef struct {
uint64_t last_sample_timestamp_ms;
// The minimum and maximum values for an interval for it to be included into the average.
uint32_t min_expected_ms;
uint32_t max_expected_ms;
uint32_t weighting_factor_inverted;
//! The moving average we've calculated based on the samples we have so far.
uint32_t average_ms;
//! The number of samples we've taken.
uint32_t num_samples;
} IntervalTimer;
//! Initialize an interval timer.
//!
//! Allows the specification of an acceptable range of intervals. This is used to discard invalid
//! intervals.
//!
//! Intervals are averaged together in a moving average that includes the last n intervals. The
//! number of intervals to average over is configurable.
//!
//! @param min_expected_ms The minimum number of milliseconds between samples
//! @param min_expected_ms The maximum number of milliseconds between samples
//! @param weighting_factor_inverted
//! 1 / alpha. Specified as a inverted number to avoid dealing with floats. The higher the
//! number the less responsive to recent changes our average is.
void interval_timer_init(IntervalTimer *timer, uint32_t min_expected_ms, uint32_t max_expected_ms,
uint32_t weighting_factory_inverted);
//! Record a sample that marks the start/end of an interval.
//! Safe to call from an ISR.
void interval_timer_take_sample(IntervalTimer *timer);
//! @param[out] average_ms_out The average ms for the interval.
//! @return The number of valid intervals that are in our moving average. Note that this value
//! will never be larger than num_intervals_in_average.
uint32_t interval_timer_get(IntervalTimer *timer, uint32_t *average_ms_out);

View file

@ -0,0 +1,74 @@
/*
* 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 "segment.h"
#include "system/passert.h"
#include <stdalign.h>
// Remove once the Bamboo build agents build unit tests with a more
// C11-compliant compiler (i.e. clang >= 3.5)
#if !defined(__CLANG_MAX_ALIGN_T_DEFINED) && !defined(_GCC_MAX_ALIGN_T)
typedef long double max_align_t;
#endif
static void prv_assert_sane_segment(MemorySegment *segment) {
PBL_ASSERT(segment->start <= segment->end,
"Segment end points before segment start");
}
static void * prv_align(void *ptr) {
uintptr_t c = (uintptr_t)ptr;
// Advance the pointer to the next alignment boundary.
return (void *)((c + alignof(max_align_t) - 1) & ~(alignof(max_align_t) - 1));
}
size_t memory_segment_get_size(MemorySegment *segment) {
prv_assert_sane_segment(segment);
return (size_t)((uintptr_t)segment->end - (uintptr_t)segment->start);
}
void memory_segment_align(MemorySegment *segment) {
segment->start = prv_align(segment->start);
prv_assert_sane_segment(segment);
}
void * memory_segment_split(MemorySegment * restrict parent,
MemorySegment * restrict child, size_t size) {
prv_assert_sane_segment(parent);
char *child_start = prv_align(parent->start);
void *child_end = child_start + size;
if (child_end > parent->end) {
// Requested size is too big to fit in the parent segment.
return NULL;
}
void *adjusted_parent_start = prv_align(child_end);
if (adjusted_parent_start > parent->end) {
// The child has left no room for the adjusted parent.
return NULL;
}
parent->start = adjusted_parent_start;
if (child) {
*child = (MemorySegment) {
.start = child_start,
.end = child_end,
};
}
return child_start;
}

View file

@ -0,0 +1,59 @@
/*
* 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.
*/
//! Memory Segments
//!
//! A memory segment is a representation of a contiguous chunk of memory.
//! Memory segments can be split, dividing the segment in two. This API
//! is designed to simplify tasks such as process loading where a chunk
//! of memory must be allocated into a bunch of smaller chunks of
//! various fixed and dynamic sizes.
#pragma once
#include <stddef.h>
typedef struct MemorySegment {
void *start; //!< The lowest address of the segment
void *end; //!< One past the highest address of the segment
} MemorySegment;
//! Returns the size of the largest object that the segment can contain.
size_t memory_segment_get_size(MemorySegment *segment);
//! Align the start pointer of a segment such that it is suitably
//! aligned for any object.
void memory_segment_align(MemorySegment *segment);
//! Split a memory segment into two.
//!
//! The child memory segment is allocated from the start of the parent,
//! and the start of the parent is moved to the end of the child.
//!
//! After the split, the start addresses of both the parent and the
//! child are guaranteed to be suitably aligned for any object.
//!
//! @param parent the memory segment to split.
//! @param child the memory segment created from the split. May be NULL.
//! @param size size of the new memory segment.
//!
//! @return start of child memory segment if successful, or NULL if
//! there is not enough space in the parent segment to split
//! with the requested size.
void * memory_segment_split(MemorySegment * restrict parent,
MemorySegment * restrict child, size_t size);

View file

@ -0,0 +1,22 @@
/*
* 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 "syscall/syscall.h"
void psleep(int millis) {
sys_psleep(millis);
}

View file

@ -0,0 +1,34 @@
/*
* 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.
*/
#pragma once
//! @internal
//!
//! Waits for a certain amount of milliseconds by suspending the thread in firmware or by just busy
//! waiting in the bootloader.
//!
//! Note that the thread is slept until the current tick + millis of ticks, so that means that if
//! we're currently part way through a tick, we'll actually wait for a time that's less than
//! expected. For example, if we're at tick n and we want to wait for 1 millisecond, we'll sleep
//! the thread until tick n+1, which will result in a sleep of less than a millisecond, as we're
//! probably halfway through the n-th tick at this time. Also note that your thread isn't
//! guaranteed to be scheduled immediately after you're done sleeping, so you may sleep for longer
//! than you expect.
//!
//! @param millis The number of milliseconds to wait for
void psleep(int millis);

View file

@ -0,0 +1,43 @@
/*
* 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 "FreeRTOS.h"
#include "task.h"
#include "mcu/interrupts.h"
extern uint32_t __isr_stack_start__[];
uint32_t stack_free_bytes(void) {
// Get the current SP
register uint32_t SP __asm ("sp");
uint32_t cur_sp = SP;
// Default stack
uint32_t start = (uint32_t) __isr_stack_start__;
// On ISR stack?
if (!mcu_state_is_isr()) {
TaskHandle_t task_handle = xTaskGetCurrentTaskHandle();
if (task_handle != NULL) {
// task_handle is NULL before we start the first task
start = (uint32_t)ulTaskGetStackStart(task_handle);
}
}
return cur_sp - start;
}

View file

@ -0,0 +1,23 @@
/*
* 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.
*/
#pragma once
#include <stdint.h>
//! Return the number of free bytes left in the current stack. Returns 0 if stack space could
//! not be determined.
uint32_t stack_free_bytes(void);

View file

@ -0,0 +1,111 @@
/*
* 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 "kernel/util/standby.h"
#include "drivers/imu.h"
#include "drivers/rtc.h"
#include "drivers/flash.h"
#include "drivers/pmic.h"
#include "drivers/pwr.h"
#include "drivers/periph_config.h"
#include "system/bootbits.h"
#include "system/logging.h"
#include "system/reset.h"
#include "drivers/display/display.h"
#define STM32F2_COMPATIBLE
#define STM32F4_COMPATIBLE
#define STM32F7_COMPATIBLE
#include <mcu.h>
#include "FreeRTOS.h"
#include "task.h"
//! If we don't have a PMIC entering standby is a little more complicated.
//! See platform/tintin/boot/src/standby.c.
//! We set a bootbit and reboot, and then the bootloader is responsible for really winding us down.
//! This is necessary because:
//! - When entering standby, a system reset is the only way to disable the IWDG
//! - When shutting down, it simplifies waiting on the charger to be removed,
//! and allows for us to handle other boot bits (eg. Force PRF) before powering down.
//!
//! @param boot_bit Boot bit to set. It should cause the bootloader to shut down or enter standby.
static NORETURN prv_enter_standby_non_pmic(BootBitValue boot_bit) {
// The I2C bus is not initialized in the bootloader.
// Put the Accelerometer into low power mode before resetting
imu_power_down();
boot_bit_set(boot_bit);
PBL_LOG(LOG_LEVEL_ALWAYS, "Rebooting to enter Standby mode.");
reboot_reason_set_restarted_safely();
system_hard_reset();
}
static NORETURN prv_enter_standby_pmic(void) {
reboot_reason_set_restarted_safely();
#if defined(TARGET_QEMU)
#if MICRO_FAMILY_STM32F7
WTF; // Unsupported
#else
// QEMU does not implement i2c devices, like the PMIC, yet. Let's turn off instead
// by going into standby mode using the power control of the STM32. We can't use
// prv_enter_standby_non_pmic() because PMIC based boards don't support that feature in their
// bootloader.
periph_config_enable(PWR, RCC_APB1Periph_PWR);
pwr_enable_wakeup(true);
PWR_EnterSTANDBYMode();
#endif
#endif
PBL_LOG(LOG_LEVEL_ALWAYS, "Using the PMIC to enter standby mode.");
pmic_power_off();
PBL_CROAK("PMIC didn't shut us down!");
}
NORETURN enter_standby(RebootReasonCode reason) {
PBL_LOG(LOG_LEVEL_ALWAYS, "Preparing to enter standby mode.");
RebootReason reboot_reason = { reason, 0 };
reboot_reason_set(&reboot_reason);
// Wipe display
display_clear();
/* skip BT teardown if BT isn't working */
system_reset_prepare(reason == RebootReasonCode_DialogBootFault);
#if PLATFORM_SILK && RECOVERY_FW
// For Silk PRF & MFG firmwares, fully shutdown the watch using the bootloader.
// Always entering full shutdown in these two situations will guarantee a much
// better shelf-life, and ensure that watches are shipped in full shutdown mode.
//
// Request the bootloader to completely power down as the last thing it does,
// rather than jumping into the fw. The bootloader may spin on the charger
// connection status, as we cannot shutdown while the charger is plugged.
// Luckily, we never try to power down the watch while plugged when in PRF.
prv_enter_standby_non_pmic(BOOT_BIT_SHUTDOWN_REQUESTED);
#elif CAPABILITY_HAS_PMIC
prv_enter_standby_pmic();
#else
// Request the bootloader to enter standby mode immediately after the system is reset.
prv_enter_standby_non_pmic(BOOT_BIT_STANDBY_MODE_REQUESTED);
#endif
}

View file

@ -0,0 +1,27 @@
/*
* 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.
*/
#pragma once
#include "system/reboot_reason.h"
#include "util/attributes.h"
#if !UNITTEST
NORETURN
#else
void
#endif
enter_standby(RebootReasonCode reason);

215
src/fw/kernel/util/stop.c Normal file
View file

@ -0,0 +1,215 @@
/*
* 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 "console/dbgserial.h"
#include "console/dbgserial_input.h"
#include "drivers/flash.h"
#include "drivers/periph_config.h"
#include "drivers/rtc.h"
#include "drivers/task_watchdog.h"
#include "os/tick.h"
#include "kernel/util/stop.h"
#include "kernel/util/wfi.h"
#include "mcu/interrupts.h"
#include "services/common/analytics/analytics.h"
#include "system/passert.h"
#define STM32F2_COMPATIBLE
#define STM32F4_COMPATIBLE
#define STM32F7_COMPATIBLE
#include <mcu.h>
#include <stdbool.h>
#include <inttypes.h>
static int s_num_items_disallowing_stop_mode = 0;
#ifdef PBL_NOSLEEP
static bool s_sleep_mode_allowed = false;
#else
static bool s_sleep_mode_allowed = true;
#endif
typedef struct {
uint32_t active_count;
RtcTicks ticks_when_stop_mode_disabled;
RtcTicks total_ticks_while_disabled;
} InhibitorTickProfile;
// Note: These variables should be protected within a critical section since
// they are read and modified by multiple threads
static InhibitorTickProfile s_inhibitor_profile[InhibitorNumItems];
void enter_stop_mode(void) {
// enable the interrupt on the debug RX line so that we can use the serial
// console even when we are in stop mode.
dbgserial_enable_rx_exti();
flash_power_down_for_stop_mode();
// Turn on the power control peripheral so that we can put the regulator into low-power mode
periph_config_enable(PWR, RCC_APB1Periph_PWR);
if (mcu_state_are_interrupts_enabled()) {
// If INTs aren't disabled here, we would wind up servicing INTs
// immediately after the WFI (while running at the wrong clock speed) which
// can confuse peripherals in subtle ways
WTF;
}
// Enter stop mode.
//PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI);
// We don't use ^^ the above function because of a silicon bug which
// causes the processor to skip some instructions upon wake from STOP
// in certain sitations. See the STM32F20x and STM32F21x Errata sheet
// section 2.1.3 "Debugging Stop mode with WFE entry", or the erratum
// of the same name in section 2.1.2 of the STM32F42x and STM32F43x
// Errata sheet, for (misleading) details.
// http://www.st.com/web/en/resource/technical/document/errata_sheet/DM00027213.pdf
// http://www.st.com/web/en/resource/technical/document/errata_sheet/DM00068628.pdf
// Configure the PWR peripheral to put us in low-power STOP mode when
// the processor enters deepsleep.
#if defined(MICRO_FAMILY_STM32F7)
uint32_t temp = PWR->CR1;
temp &= ~PWR_CR1_PDDS;
temp |= PWR_CR1_LPDS;
PWR->CR1 = temp;
#else
uint32_t temp = PWR->CR;
temp &= ~PWR_CR_PDDS;
temp |= PWR_CR_LPDS;
#if STM32F412xG
// STM32F412xG suports a new "low-power regulator low voltage in deep sleep" mode.
temp |= PWR_CR_LPLVDS;
#endif
PWR->CR = temp;
#endif
// Configure the processor core to enter deepsleep mode when we
// execute a WFI or WFE instruction.
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
// Go stop now.
__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).
// Tell the processor not to emter deepsleep mode for future WFIs.
SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;
// Stop mode will change our system clock to the HSI. Move it back to the PLL.
// Enable the PLL and wait until it's ready
RCC_PLLCmd(ENABLE);
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) {}
// Select PLL as system clock source and wait until it's being used
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while (RCC_GetSYSCLKSource() != 0x08) {}
// No longer need the power control peripheral
periph_config_disable(PWR, RCC_APB1Periph_PWR);
flash_power_up_after_stop_mode();
}
void stop_mode_disable( StopModeInhibitor inhibitor ) {
portENTER_CRITICAL();
++s_num_items_disallowing_stop_mode;
++s_inhibitor_profile[inhibitor].active_count;
// TODO: We should probably check if s_inhibitor_profile.active_count == 1
// before doing this assignment. We don't seem to ever run into this case
// yet (i.e. active_count is never > 1), but when we do, this code would
// report the wrong number of nostop ticks.
s_inhibitor_profile[inhibitor].ticks_when_stop_mode_disabled = rtc_get_ticks();
portEXIT_CRITICAL();
}
void stop_mode_enable( StopModeInhibitor inhibitor ) {
portENTER_CRITICAL();
PBL_ASSERTN(s_num_items_disallowing_stop_mode != 0);
PBL_ASSERTN(s_inhibitor_profile[inhibitor].active_count != 0);
--s_num_items_disallowing_stop_mode;
--s_inhibitor_profile[inhibitor].active_count;
if (s_inhibitor_profile[inhibitor].active_count == 0) {
s_inhibitor_profile[inhibitor].total_ticks_while_disabled += rtc_get_ticks() -
s_inhibitor_profile[inhibitor].ticks_when_stop_mode_disabled;
}
portEXIT_CRITICAL();
}
bool stop_mode_is_allowed(void) {
#if PBL_NOSTOP
return false;
#else
return s_num_items_disallowing_stop_mode == 0;
#endif
}
void sleep_mode_enable(bool enable) {
s_sleep_mode_allowed = enable;
}
bool sleep_mode_is_allowed(void) {
#ifdef PBL_NOSLEEP
return false;
#endif
return s_sleep_mode_allowed;
}
static RtcTicks prv_get_nostop_ticks(StopModeInhibitor inhibitor, RtcTicks now_ticks) {
RtcTicks total_ticks = s_inhibitor_profile[inhibitor].total_ticks_while_disabled;
if (s_inhibitor_profile[inhibitor].active_count != 0) {
total_ticks += (now_ticks - s_inhibitor_profile[inhibitor].ticks_when_stop_mode_disabled);
}
return total_ticks;
}
static void prv_collect(AnalyticsMetric metric, StopModeInhibitor inhibitor, RtcTicks now_ticks) {
// operating on 64 bit values so the load/stores will _not_ be atomic
portENTER_CRITICAL();
RtcTicks ticks = prv_get_nostop_ticks(inhibitor, now_ticks);
s_inhibitor_profile[inhibitor].total_ticks_while_disabled = 0;
portEXIT_CRITICAL();
analytics_set(metric, ticks_to_milliseconds(ticks), AnalyticsClient_System);
}
void analytics_external_collect_stop_inhibitor_stats(RtcTicks now_ticks) {
prv_collect(ANALYTICS_DEVICE_METRIC_CPU_NOSTOP_MAIN_TIME, InhibitorMain, now_ticks);
// We don't care about the serial console nostop time, it should always
// be zero on watches in the field anyway. (InhibitorDbgSerial skipped)
prv_collect(ANALYTICS_DEVICE_METRIC_CPU_NOSTOP_BUTTON_TIME, InhibitorButton, now_ticks);
prv_collect(ANALYTICS_DEVICE_METRIC_CPU_NOSTOP_BLUETOOTH_TIME, InhibitorBluetooth, now_ticks);
prv_collect(ANALYTICS_DEVICE_METRIC_CPU_NOSTOP_DISPLAY_TIME, InhibitorDisplay, now_ticks);
prv_collect(ANALYTICS_DEVICE_METRIC_CPU_NOSTOP_BACKLIGHT_TIME, InhibitorBacklight, now_ticks);
prv_collect(ANALYTICS_DEVICE_METRIC_CPU_NOSTOP_COMM_TIME, InhibitorCommMode, now_ticks);
prv_collect(ANALYTICS_DEVICE_METRIC_CPU_NOSTOP_FLASH_TIME, InhibitorFlash, now_ticks);
prv_collect(ANALYTICS_DEVICE_METRIC_CPU_NOSTOP_I2C1_TIME, InhibitorI2C1, now_ticks);
prv_collect(ANALYTICS_DEVICE_METRIC_CPU_NOSTOP_ACCESSORY, InhibitorAccessory, now_ticks);
prv_collect(ANALYTICS_DEVICE_METRIC_CPU_NOSTOP_MIC, InhibitorMic, now_ticks);
// TODO PBL-37941: Add analytics for InhibitorDMA
}
void command_scheduler_force_active(void) {
sleep_mode_enable(false);
}
void command_scheduler_resume_normal(void) {
sleep_mode_enable(true);
}

76
src/fw/kernel/util/stop.h Normal file
View file

@ -0,0 +1,76 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
typedef enum {
InhibitorMain,
InhibitorDbgSerial,
InhibitorButton,
InhibitorBluetooth,
InhibitorDisplay,
InhibitorBacklight,
InhibitorCommMode,
InhibitorFlash,
InhibitorI2C1,
InhibitorI2C2,
InhibitorMic,
InhibitorAccessory,
InhibitorVibes,
InhibitorCompositor,
InhibitorI2C3,
InhibitorI2C4,
InhibitorBluetoothWatchdog,
InhibitorNumItems
} StopModeInhibitor;
/** Enter stop mode.
*
* \note Probably no good reason to call this function from most application
* code. Let the FreeRTOS scheduler do its job.
*/
void enter_stop_mode(void);
/** Prevent the scheduler from entering stop mode in idle. Usually called when
* we know that there is some resource or peripheral being used that does not
* require the use of the CPU, but that going into stop mode would interrupt.
* \note Internally this is implemented as a reference counter, so it is
* necessary to balance each call to disallow_stop_mode with a matching call to
* allow_stop_mode.
* CAUTION: This function cannot be called at priorities > Systick
*/
void stop_mode_disable(StopModeInhibitor inhibitor);
/** Allow the scheduler to enter stop mode in idle again.
* CAUTION: This function cannot be called at priorities > Systick
*/
void stop_mode_enable(StopModeInhibitor inhibitor);
//! Check whether we are permitted to go into stop mode
bool stop_mode_is_allowed(void);
//! Enable or disable sleep mode.
//! Note: When sleep mode is disabled so is stop mode. When sleep mode is enabled, stop mode is
//! controlled by stop_mode_is_allowed.
void sleep_mode_enable(bool enable);
//! Check whether we are permitted to go into sleep mode.
bool sleep_mode_is_allowed(void);

View file

@ -0,0 +1,31 @@
/*
* 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 "task_init.h"
#include "drivers/rng.h"
#include "drivers/rtc.h"
#include <stdlib.h>
void task_init(void) {
uint32_t seed;
if (!rng_rand(&seed)) {
// Fallback, time XOR'd with an approximation of the current stack pointer:
seed = rtc_get_time() ^ (uintptr_t) &seed;
}
srand(seed);
}

View file

@ -0,0 +1,22 @@
/*
* 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.
*/
#pragma once
//! Performs initialization for the current FreeRTOS task. Currently, all it does is it
//! attempts to seed the current task's REENT random seed with a value from the
//! hardware random number generator.
void task_init(void);

View file

@ -0,0 +1,76 @@
/*
* 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 "console/prompt.h"
#include "kernel/pbl_malloc.h"
#include "FreeRTOS.h"
#include "task.h"
#include <stdlib.h>
#if 0
void command_print_task_list(void) {
#if ( configUSE_TRACE_FACILITY == 1 )
char str_buffer[100];
prompt_send_response(
"name state pri fstk num stk_beg stk_ptr");
int num_tasks = uxTaskGetNumberOfTasks();
TaskStatus_t *task_info = kernel_malloc(num_tasks * sizeof( TaskStatus_t ));
if (!task_info) {
return;
}
num_tasks = uxTaskGetSystemState( task_info, num_tasks, NULL );
// Print info on each task
char status;
for (int i=0; i<num_tasks; i++) {
switch(task_info[i].eCurrentState) {
case eReady:
status = 'R';
break;
case eBlocked:
status = 'B';
break;
case eSuspended:
status = 'S';
break;
case eDeleted:
status = 'D';
break;
default:
status = '?';
break;
}
prompt_send_response_fmt(str_buffer, sizeof(str_buffer), "%-16s %6c %8u %8u %8u %p %p",
task_info[i].pcTaskName,
status,
(unsigned int) task_info[i].uxCurrentPriority,
(unsigned int)(sizeof(StackType_t) * task_info[i].usStackHighWaterMark),
(unsigned int)task_info[i].xTaskNumber,
(void *)task_info[i].pxStack,
(void *)task_info[i].pxTopOfStack);
}
kernel_free(task_info);
#else
prompt_send_response("Not available");
#endif
}
#endif

36
src/fw/kernel/util/wfi.c Normal file
View file

@ -0,0 +1,36 @@
/*
* 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 "util/attributes.h"
#include "wfi.h"
void NOINLINE NAKED_FUNC do_wfi(void) {
// Work around a very strange bug in the STM32F where, upon waking from
// STOP or SLEEP mode, the processor begins acting strangely depending on the
// contents of the bytes following the "bx lr" instruction.
__asm volatile (
".align 4 \n" // Force 16-byte alignment
"wfi \n" // This instruction cannot be placed at 0xnnnnnnn4
"nop \n"
"bx lr \n"
"nop \n" // Fill the rest of the cache line with NOPs as the bytes
"nop \n" // following the bx affect the processor for some reason.
"nop \n"
"nop \n"
"nop \n"
);
}

19
src/fw/kernel/util/wfi.h Normal file
View file

@ -0,0 +1,19 @@
/*
* 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.
*/
#pragma once
void do_wfi(void);