/* * 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 #include #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 #include "FreeRTOS.h" #include "task.h" #include "mfg/mfg_info.h" #include "mfg/mfg_serials.h" #include "pebble_errors.h" #include #include 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(); }