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

507 lines
14 KiB
C

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdio.h>
#include <setjmp.h>
#include "debug/power_tracking.h"
#include "board/board.h"
#include "console/dbgserial.h"
#include "console/dbgserial_input.h"
#include "console/pulse.h"
#include "drivers/clocksource.h"
#include "drivers/rtc.h"
#include "drivers/periph_config.h"
#include "drivers/flash.h"
#include "drivers/debounced_button.h"
#include "drivers/accessory.h"
#include "drivers/ambient_light.h"
#include "drivers/backlight.h"
#include "drivers/battery.h"
#include "drivers/display/display.h"
#include "drivers/gpio.h"
#include "drivers/hrm.h"
#include "drivers/imu.h"
#include "drivers/led_controller.h"
#include "drivers/mic.h"
#include "drivers/otp.h"
#include "drivers/pmic.h"
#include "drivers/pwr.h"
#include "drivers/spi.h"
#include "drivers/system_flash.h"
#include "drivers/task_watchdog.h"
#include "drivers/temperature.h"
#include "drivers/touch/touch_sensor.h"
#include "drivers/vibe.h"
#include "drivers/voltage_monitor.h"
#include "drivers/watchdog.h"
#include "resource/resource.h"
#include "resource/system_resource.h"
#include "kernel/util/stop.h"
#include "kernel/util/task_init.h"
#include "kernel/util/sleep.h"
#include "kernel/events.h"
#include "kernel/kernel_heap.h"
#include "kernel/fault_handling.h"
#include "kernel/memory_layout.h"
#include "kernel/panic.h"
#include "kernel/pulse_logging.h"
#include "services/services.h"
#include "services/common/clock.h"
#include "services/common/compositor/compositor.h"
#include "services/common/regular_timer.h"
#include "services/common/system_task.h"
#include "services/common/new_timer/new_timer_service.h"
#include "services/common/new_timer/new_timer.h"
#include "services/common/analytics/analytics.h"
#include "services/common/prf_update.h"
#include "kernel/ui/kernel_ui.h"
#include "kernel/kernel_applib_state.h"
#include "kernel/util/delay.h"
#include "util/mbuf.h"
#include "system/version.h"
#include "kernel/event_loop.h"
#include "applib/fonts/fonts.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/text.h"
#include "applib/ui/ui.h"
#include "applib/ui/window_stack_private.h"
#include "console/serial_console.h"
#include "system/bootbits.h"
#include "system/logging.h"
#include "system/passert.h"
#include "system/reset.h"
#include "syscall/syscall_internal.h"
#include "debug/debug.h"
#include "debug/setup.h"
#define STM32F2_COMPATIBLE
#define STM32F4_COMPATIBLE
#define STM32F7_COMPATIBLE
#include <mcu.h>
#include "FreeRTOS.h"
#include "task.h"
#include "mfg/mfg_info.h"
#include "mfg/mfg_serials.h"
#include "pebble_errors.h"
#include <bluetooth/init.h>
#include <string.h>
static TimerID s_lowpower_timer = TIMER_INVALID_ID;
static TimerID s_uptime_timer = TIMER_INVALID_ID;
static void main_task(void *parameter);
static void print_splash_screen(void)
{
#if defined(MANUFACTURING_FW)
PBL_LOG(LOG_LEVEL_ALWAYS, "__TINTIN__ - MANUFACTURING MODE");
#elif defined(RECOVERY_FW)
PBL_LOG(LOG_LEVEL_ALWAYS, "__TINTIN__ - RECOVERY MODE");
#else
PBL_LOG(LOG_LEVEL_ALWAYS, "__TINTIN__");
#endif
PBL_LOG(LOG_LEVEL_ALWAYS, "%s", TINTIN_METADATA.version_tag);
PBL_LOG(LOG_LEVEL_ALWAYS, "(c) 2013 Pebble");
PBL_LOG(LOG_LEVEL_ALWAYS, " ");
}
#ifdef DUMP_GPIO_CFG_STATE
static void dump_gpio_configuration_state(void) {
char name[2] = { 0 };
name[0] = 'A'; // GPIO Port A
GPIO_TypeDef *gpio_pin;
for (uint32_t gpio_addr = (uint32_t)GPIOA; gpio_addr <= (uint32_t)GPIOI;
gpio_addr += 0x400) {
gpio_pin = (GPIO_TypeDef *)gpio_addr;
gpio_use(gpio_pin);
uint32_t mode = gpio_pin->MODER;
gpio_release(gpio_pin);
uint16_t pin_cfg_mask = 0;
char buf[80];
for (int pin = 0; pin < 16; pin++) {
if ((mode & GPIO_MODER_MODER0) != GPIO_Mode_AN) {
pin_cfg_mask |= (0x1 << pin);
}
mode >>= 2;
}
dbgserial_putstr_fmt(buf, sizeof(buf), "Non Analog P%s cfg: 0x%"PRIx16,
name, (int)pin_cfg_mask);
name[0]++;
}
}
#endif /* DUMP_GPIO_CFG_STATE */
int main(void) {
gpio_init_all();
#if defined(MICRO_FAMILY_STM32F4) && !defined(LOW_POWER_DEBUG)
// If we're on a snowy board using the stm32f4, we experience random hardfaults after leaving a
// wfi instruction if we have mcu debugging enabled. For now, just turn off mcu debugging
// entirely unless we explicitly want it. See PBL-10174
disable_mcu_debugging();
#else
// Turn on MCU debugging at boot. This consumes some power so we'll turn it off after a short
// time has passed (see prv_low_power_debug_config_callback) to allow us to connect after a
// reset but not passively consume power after we've been running for a bit.
enable_mcu_debugging();
#endif
extern void * __ISR_VECTOR_TABLE__; // Defined in linker script
SCB->VTOR = (uint32_t)&__ISR_VECTOR_TABLE__;
NVIC_SetPriorityGrouping(3); // 4 bits for group priority; 0 bits for subpriority
enable_fault_handlers();
kernel_heap_init();
mbuf_init();
delay_init();
periph_config_init();
dbgserial_init();
pulse_early_init();
print_splash_screen();
rtc_init();
#if BOOTLOADER_TEST_STAGE2
#define BLTEST_LOG(x...) pbl_log(LOG_LEVEL_ALWAYS, __FILE__, __LINE__, x)
BLTEST_LOG("BOOTLOADER TEST STAGE 2");
boot_bit_set(BOOT_BIT_FW_STABLE);
BLTEST_LOG("STAGE 2 -- Checking test boot bits");
if (boot_bit_test(BOOT_BIT_BOOTLOADER_TEST_A) && !boot_bit_test(BOOT_BIT_BOOTLOADER_TEST_B)) {
BLTEST_LOG("ALL BOOTLOADER TESTS PASSED");
} else {
BLTEST_LOG("STAGE 2 -- Boot bits incorrect!");
BLTEST_LOG("BOOTLOADER TEST FAILED");
}
boot_bit_clear(BOOT_BIT_BOOTLOADER_TEST_A | BOOT_BIT_BOOTLOADER_TEST_B);
psleep(10000);
system_hard_reset();
for (;;) {}
// won't get here, rest gets optimized out
#endif
#ifdef RECOVERY_FW
boot_bit_clear(BOOT_BIT_RECOVERY_START_IN_PROGRESS);
#endif
extern uint32_t __kernel_main_stack_start__[];
extern uint32_t __kernel_main_stack_size__[];
extern uint32_t __stack_guard_size__[];
const uint32_t kernel_main_stack_words = ( (uint32_t)__kernel_main_stack_size__
- (uint32_t) __stack_guard_size__ ) / sizeof(portSTACK_TYPE);
TaskParameters_t task_params = {
.pvTaskCode = main_task,
.pcName = "KernelMain",
.usStackDepth = kernel_main_stack_words,
.uxPriority = (tskIDLE_PRIORITY + 3) | portPRIVILEGE_BIT,
.puxStackBuffer = (void*)(uintptr_t)((uint32_t)__kernel_main_stack_start__
+ (uint32_t)__stack_guard_size__)
};
pebble_task_create(PebbleTask_KernelMain, &task_params, NULL);
// Always start the firmware in a state where we explicitly do not allow stop mode.
// FIXME: This seems overly cautious to me, we shouldn't have to do this.
stop_mode_disable(InhibitorMain);
// Turn off power to internal flash when in stop mode
periph_config_enable(PWR, RCC_APB1Periph_PWR);
pwr_flash_power_down_stop_mode(true /* power_down */);
periph_config_disable(PWR, RCC_APB1Periph_PWR);
vTaskStartScheduler();
for(;;);
}
static void watchdog_timer_callback(void* data) {
task_watchdog_bit_set(PebbleTask_NewTimers);
}
static void vcom_timer_callback(void* data) {
display_pulse_vcom();
}
static void register_system_timers(void) {
static RegularTimerInfo watchdog_timer = { .list_node = { 0, 0 }, .cb = watchdog_timer_callback };
regular_timer_add_seconds_callback(&watchdog_timer);
if (BOARD_CONFIG.lcd_com.gpio != 0) {
static RegularTimerInfo vcom_timer = { .list_node = { 0, 0 }, .cb = vcom_timer_callback };
regular_timer_add_seconds_callback(&vcom_timer);
}
}
static void init_drivers(void) {
board_init();
// The dbgserial input support requires timer support, so it is initialized here, much later
// than the core dbgserial_init().
dbgserial_input_init();
serial_console_init();
voltage_monitor_init();
battery_init();
vibe_init();
#if CAPABILITY_HAS_ACCESSORY_CONNECTOR
accessory_init();
#endif
flash_init();
flash_sleep_when_idle(true);
flash_enable_write_protection();
flash_prf_set_protection(true);
#if CAPABILITY_HAS_PMIC
pmic_init();
#endif // CAPABILITY_HAS_PMIC
#if CAPABILITY_HAS_MICROPHONE
mic_init(MIC);
#endif
#if CAPABILITY_HAS_TOUCHSCREEN
touch_sensor_init();
#endif
imu_init();
backlight_init();
ambient_light_init();
#if CAPABILITY_HAS_TEMPERATURE
temperature_init();
#endif
rtc_init_timers();
rtc_alarm_init();
power_tracking_init();
}
static void clear_reset_loop_detection_bits(void) {
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_ONE);
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_TWO);
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_THREE);
}
static void uptime_callback(void* data) {
PBL_LOG_VERBOSE("Uptime reached 15 minutes, set stable bit.");
new_timer_delete(s_uptime_timer);
boot_bit_set(BOOT_BIT_FW_STABLE);
}
static void prv_low_power_debug_config_callback(void* data) {
new_timer_delete(s_lowpower_timer);
// Turn off sleep and stop mode debugging if it is not explicitly enabled
// 4 cases:
// STM32F2/STM32F7, low power debug off: we need to disable it after the first 10 seconds
// STM32F2/STM32F7, low power debug on: we want to leave it on
// STM32F4, low power debug off: we never turned it on in the first place
// STM32F4, low power debug on: we want to leave it on
#if (defined(MICRO_FAMILY_STM32F2) || defined(MICRO_FAMILY_STM32F7)) && !defined(LOW_POWER_DEBUG)
disable_mcu_debugging();
#endif
}
#ifdef TEST_SJLJ
static jmp_buf s_sjlj_jmpbuf;
static volatile int s_sjlj_num;
static void prv_sjlj_second(int r) {
PBL_ASSERT(s_sjlj_num == 1, "SJLJ TRACK INCORRECT @ SECOND");
s_sjlj_num++;
longjmp(s_sjlj_jmpbuf, 0);
}
static void prv_sjlj_first(int r) {
PBL_ASSERT(s_sjlj_num == 0, "SJLJ TRACK INCORRECT @ FIRST");
s_sjlj_num++;
prv_sjlj_second(r);
PBL_ASSERT(1, "SJLJ IS BROKEN (longjmp didn't occur)");
}
static void prv_sjlj_main(int r) {
PBL_ASSERT(s_sjlj_num == 2, "SJLJ TRACK INCORRECT @ MAIN");
s_sjlj_num++;
PBL_ASSERT(r == 1, "SETJMP IS BROKEN (longjmp value wasn't correct)");
}
static void prv_test_sjlj(void) {
int r;
s_sjlj_num = 0;
if (!(r = setjmp(s_sjlj_jmpbuf)))
prv_sjlj_first(r);
else
prv_sjlj_main(r);
PBL_ASSERT(s_sjlj_num == 3, "SJLJ TRACK INCORRECT @ END");
PBL_LOG(LOG_LEVEL_ALWAYS, "sjlj works \\o/");
}
#endif
static NOINLINE void prv_main_task_init(void) {
// The Snowy bootloader does not clear the watchdog flag itself. Clear the
// flag ourselves so that a future safe reset does not look like a watchdog
// reset to the bootloader.
static McuRebootReason s_mcu_reboot_reason;
s_mcu_reboot_reason = watchdog_clear_reset_flag();
#if PULSE_EVERYWHERE
pulse_init();
pulse_logging_init();
#endif
pebble_task_configure_idle_task();
task_init();
memory_layout_setup_mpu();
board_early_init();
display_show_splash_screen();
kernel_applib_init();
system_task_init();
events_init();
new_timer_service_init();
regular_timer_init();
clock_init();
task_watchdog_init();
analytics_init();
register_system_timers();
system_task_timer_init();
init_drivers();
#if defined(IS_BIGBOARD)
// Program a random S/N into the Bigboard in case it's not been done yet:
mfg_write_bigboard_serial_number();
#endif
#if defined(MANUFACTURING_FW)
mfg_info_update_constant_data();
#endif
debug_init(s_mcu_reboot_reason);
services_early_init();
debug_print_last_launched_app();
// Do this early before things can screw ith it.
check_prf_update();
// When there are new system resources waiting to be installed, this call
// will actually install them:
resource_init();
system_resource_init();
#if CAPABILITY_HAS_BUILTIN_HRM
if (mfg_info_is_hrm_present()) {
hrm_init(HRM);
}
#endif
// The display has to be initialized before bluetooth because on Snowy the
// display FPGA shares the 32 kHz clock signal with bluetooth. If the FPGA is
// not programmed when we attempt to initialize bluetooth, it prevents the
// clock from reaching the bluetooth module and initialization fails.
display_init();
// Use the MFG calibrated display offset to adjust the display
GPoint mfg_offset = mfg_info_get_disp_offsets();
display_set_offset(mfg_offset);
// Log display offsets for use in contact support logs
PBL_LOG(LOG_LEVEL_INFO, "MFG Display Offsets (%"PRIi16",%"PRIi16").", mfg_offset.x, mfg_offset.y);
// Can't use the compositor framebuffer until the compositor is initialized
compositor_init();
kernel_ui_init();
bt_driver_init();
services_init();
// The RTC needs be calibrated after the mfg registry service has been initialized so we can
// load the measured frequency.
rtc_calibrate_frequency(mfg_info_get_rtc_freq());
clear_reset_loop_detection_bits();
task_watchdog_mask_set(PebbleTask_KernelMain);
stop_mode_enable(InhibitorMain);
// Leave the board with stop and sleep mode debugging enabled for at least 10
// seconds to give OpenOCD time to start and still able to connect when it is
// ready to flash in the new image via JTAG
s_lowpower_timer = new_timer_create();
new_timer_start(s_lowpower_timer,
10 * 1000, prv_low_power_debug_config_callback, NULL, 0 /*flags*/);
s_uptime_timer = new_timer_create();
new_timer_start(s_uptime_timer, 15 * 60 * 1000, uptime_callback, NULL, 0 /*flags*/);
// Initialize button driver at the last moment to prevent "system on" button press from
// entering the kernel event queue.
debounced_button_init();
#ifdef DUMP_GPIO_CFG_STATE
// at this point everything should be configured!
dump_gpio_configuration_state();
#endif
#ifdef TEST_SJLJ
// Test setjmp/longjmp
prv_test_sjlj();
#endif
}
static void main_task(void *parameter) {
prv_main_task_init();
launcher_main_loop();
}