mirror of
https://github.com/google/pebble.git
synced 2025-05-22 03:14:52 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
794
src/fw/kernel/core_dump.c
Normal file
794
src/fw/kernel/core_dump.c
Normal 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(®ion_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(®ion_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(®ion_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 *)®ion_hdr, flash_base, sizeof(region_hdr));
|
||||
region_hdr.unread = 0;
|
||||
flash_write_bytes((uint8_t *)®ion_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 *)®ion_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
44
src/fw/kernel/core_dump.h
Normal 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);
|
131
src/fw/kernel/core_dump_private.h
Normal file
131
src/fw/kernel/core_dump_private.h
Normal 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
507
src/fw/kernel/event_loop.c
Normal 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();
|
||||
}
|
43
src/fw/kernel/event_loop.h
Normal file
43
src/fw/kernel/event_loop.h
Normal 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
407
src/fw/kernel/events.c
Normal 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
852
src/fw/kernel/events.h
Normal 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);
|
425
src/fw/kernel/fault_handling.c
Normal file
425
src/fw/kernel/fault_handling.c
Normal 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));
|
||||
}
|
||||
|
25
src/fw/kernel/fault_handling.h
Normal file
25
src/fw/kernel/fault_handling.h
Normal 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);
|
||||
|
173
src/fw/kernel/kernel_applib_state.c
Normal file
173
src/fw/kernel/kernel_applib_state.c
Normal 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());
|
||||
}
|
||||
|
||||
|
50
src/fw/kernel/kernel_applib_state.h
Normal file
50
src/fw/kernel/kernel_applib_state.h
Normal 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);
|
80
src/fw/kernel/kernel_heap.c
Normal file
80
src/fw/kernel/kernel_heap.c
Normal 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
|
24
src/fw/kernel/kernel_heap.h
Normal file
24
src/fw/kernel/kernel_heap.h
Normal 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
197
src/fw/kernel/logging.c
Normal 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);
|
||||
}
|
||||
|
66
src/fw/kernel/logging_private.h
Normal file
66
src/fw/kernel/logging_private.h
Normal 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
108
src/fw/kernel/low_power.c
Normal 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
31
src/fw/kernel/low_power.h
Normal 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);
|
288
src/fw/kernel/memory_layout.c
Normal file
288
src/fw/kernel/memory_layout.c
Normal 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;
|
||||
}
|
54
src/fw/kernel/memory_layout.h
Normal file
54
src/fw/kernel/memory_layout.h
Normal 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);
|
23
src/fw/kernel/mpu_regions.template.h
Normal file
23
src/fw/kernel/mpu_regions.template.h
Normal 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
62
src/fw/kernel/panic.c
Normal 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
24
src/fw/kernel/panic.h
Normal 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
353
src/fw/kernel/pbl_malloc.c
Normal 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);
|
||||
}
|
||||
|
68
src/fw/kernel/pbl_malloc.h
Normal file
68
src/fw/kernel/pbl_malloc.h
Normal 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);
|
254
src/fw/kernel/pebble_tasks.c
Normal file
254
src/fw/kernel/pebble_tasks.c
Normal 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);
|
||||
}
|
74
src/fw/kernel/pebble_tasks.h
Normal file
74
src/fw/kernel/pebble_tasks.h
Normal 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);
|
271
src/fw/kernel/pulse_logging.c
Normal file
271
src/fw/kernel/pulse_logging.c
Normal 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);
|
||||
}
|
39
src/fw/kernel/pulse_logging.h
Normal file
39
src/fw/kernel/pulse_logging.h
Normal 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
77
src/fw/kernel/reset.c
Normal 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();
|
||||
}
|
||||
|
208
src/fw/kernel/system_message.c
Normal file
208
src/fw/kernel/system_message.c
Normal 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);
|
||||
}
|
57
src/fw/kernel/system_message.h
Normal file
57
src/fw/kernel/system_message.h
Normal 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);
|
||||
|
253
src/fw/kernel/system_versions.c
Normal file
253
src/fw/kernel/system_versions.c
Normal 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
368
src/fw/kernel/task_timer.c
Normal 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;
|
||||
}
|
90
src/fw/kernel/task_timer.h
Normal file
90
src/fw/kernel/task_timer.h
Normal 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);
|
57
src/fw/kernel/task_timer_manager.h
Normal file
57
src/fw/kernel/task_timer_manager.h
Normal 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);
|
84
src/fw/kernel/ui/kernel_ui.c
Normal file
84
src/fw/kernel/ui/kernel_ui.c
Normal 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;
|
||||
}
|
||||
}
|
34
src/fw/kernel/ui/kernel_ui.h
Normal file
34
src/fw/kernel/ui/kernel_ui.h
Normal 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);
|
582
src/fw/kernel/ui/modals/modal_manager.c
Normal file
582
src/fw/kernel/ui/modals/modal_manager.c
Normal 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();
|
||||
}
|
176
src/fw/kernel/ui/modals/modal_manager.h
Normal file
176
src/fw/kernel/ui/modals/modal_manager.h
Normal 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);
|
18
src/fw/kernel/ui/modals/modal_manager_private.h
Normal file
18
src/fw/kernel/ui/modals/modal_manager_private.h
Normal 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
|
||||
|
382
src/fw/kernel/ui/system_icons.h
Normal file
382
src/fw/kernel/ui/system_icons.h
Normal 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 },
|
||||
},
|
||||
};
|
88
src/fw/kernel/util/delay.c
Normal file
88
src/fw/kernel/util/delay.c
Normal 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;
|
||||
}
|
||||
}
|
25
src/fw/kernel/util/delay.h
Normal file
25
src/fw/kernel/util/delay.h
Normal 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);
|
132
src/fw/kernel/util/factory_reset.c
Normal file
132
src/fw/kernel/util/factory_reset.c
Normal 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;
|
||||
}
|
29
src/fw/kernel/util/factory_reset.h
Normal file
29
src/fw/kernel/util/factory_reset.h
Normal 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);
|
93
src/fw/kernel/util/fw_reset.c
Normal file
93
src/fw/kernel/util/fw_reset.c
Normal 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();
|
||||
}
|
||||
}
|
||||
|
28
src/fw/kernel/util/fw_reset.h
Normal file
28
src/fw/kernel/util/fw_reset.h
Normal 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;
|
95
src/fw/kernel/util/interval_timer.c
Normal file
95
src/fw/kernel/util/interval_timer.c
Normal 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;
|
||||
}
|
74
src/fw/kernel/util/interval_timer.h
Normal file
74
src/fw/kernel/util/interval_timer.h
Normal 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);
|
74
src/fw/kernel/util/segment.c
Normal file
74
src/fw/kernel/util/segment.c
Normal 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;
|
||||
}
|
59
src/fw/kernel/util/segment.h
Normal file
59
src/fw/kernel/util/segment.h
Normal 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);
|
22
src/fw/kernel/util/sleep.c
Normal file
22
src/fw/kernel/util/sleep.c
Normal 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);
|
||||
}
|
||||
|
34
src/fw/kernel/util/sleep.h
Normal file
34
src/fw/kernel/util/sleep.h
Normal 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);
|
||||
|
43
src/fw/kernel/util/stack_info.c
Normal file
43
src/fw/kernel/util/stack_info.c
Normal 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;
|
||||
}
|
23
src/fw/kernel/util/stack_info.h
Normal file
23
src/fw/kernel/util/stack_info.h
Normal 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);
|
111
src/fw/kernel/util/standby.c
Normal file
111
src/fw/kernel/util/standby.c
Normal 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
|
||||
}
|
27
src/fw/kernel/util/standby.h
Normal file
27
src/fw/kernel/util/standby.h
Normal 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
215
src/fw/kernel/util/stop.c
Normal 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
76
src/fw/kernel/util/stop.h
Normal 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);
|
||||
|
31
src/fw/kernel/util/task_init.c
Normal file
31
src/fw/kernel/util/task_init.c
Normal 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);
|
||||
}
|
22
src/fw/kernel/util/task_init.h
Normal file
22
src/fw/kernel/util/task_init.h
Normal 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);
|
76
src/fw/kernel/util/task_telemetry.c
Normal file
76
src/fw/kernel/util/task_telemetry.c
Normal 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
36
src/fw/kernel/util/wfi.c
Normal 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
19
src/fw/kernel/util/wfi.h
Normal 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);
|
Loading…
Add table
Add a link
Reference in a new issue