/* * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "debug.h" #include "advanced_logging.h" #include #include #include "comm/ble/gatt_service_changed.h" #include "drivers/pmic.h" #include "kernel/core_dump.h" #include "kernel/event_loop.h" #include "kernel/pbl_malloc.h" #include "popups/crashed_ui.h" #include "services/common/analytics/analytics.h" #include "system/logging.h" #include "system/reboot_reason.h" static void log_reboot_reason_cb(void *reason) { AnalyticsEventCrash *crash_report = (AnalyticsEventCrash *)reason; analytics_event_crash(crash_report->crash_code, crash_report->link_register); kernel_free(crash_report); } static RebootReasonCode s_last_reboot_reason_code = RebootReasonCode_Unknown; RebootReasonCode reboot_reason_get_last_reboot_reason(void) { return s_last_reboot_reason_code; } void debug_reboot_reason_print(McuRebootReason mcu_reboot_reason) { RebootReason reason; reboot_reason_get(&reason); bool show_reset_alert = !reason.restarted_safely; s_last_reboot_reason_code = reason.code; // We're out of flash space, scrape a few bytes back! static const char* rebooted_due_to = " rebooted due to "; const char* restarted_safely_string = "Safely"; if (!reason.restarted_safely) { restarted_safely_string = "Dangerously"; } // Keep hourly logging to keep track of hours without crashes. analytics_set(ANALYTICS_DEVICE_METRIC_SYSTEM_CRASH_CODE, 0xDEAD0000 | reason.code, AnalyticsClient_System); uint32_t lr = reason.extra; // Leave this NULL to do your own printing. const char *reason_string = NULL; switch (reason.code) { // Normal stuff case RebootReasonCode_Unknown: reason_string = "We don't know why we %s rebooted."; lr = mcu_reboot_reason.reset_mask; break; case RebootReasonCode_LowBattery: reason_string = "%s%sLowBattery"; break; case RebootReasonCode_SoftwareUpdate: gatt_service_changed_server_handle_fw_update(); reason_string = "%s%sSoftwareUpdate"; break; case RebootReasonCode_ResetButtonsHeld: // Since we forced the reset, it isn't unexpected show_reset_alert = false; reason_string = "%s%sResetButtonsHeld"; break; case RebootReasonCode_ShutdownMenuItem: reason_string = "%s%sLowBattery"; break; case RebootReasonCode_FactoryResetReset: reason_string = "%s%sFactoryResetReset"; break; case RebootReasonCode_FactoryResetShutdown: reason_string = "%s%sFactoryResetShutdown"; break; case RebootReasonCode_MfgShutdown: reason_string = "%s%sMfgShutdown"; break; case RebootReasonCode_Serial: reason_string = "%s%sSerial"; break; case RebootReasonCode_RemoteReset: reason_string = "%s%sa Remote Reset"; break; case RebootReasonCode_ForcedCoreDump: reason_string = "%s%sa Forced Coredump"; break; case RebootReasonCode_PrfIdle: reason_string = "%s%sIdle PRF"; break; // Error occurred case RebootReasonCode_Assert: show_reset_alert = true; reason_string = "%s%sAssert: LR %#"PRIxPTR; break; case RebootReasonCode_HardFault: show_reset_alert = true; reason_string = "%s%sHardFault: LR %#"PRIxPTR; break; case RebootReasonCode_LauncherPanic: show_reset_alert = true; reason_string = "%s%sLauncherPanic: code 0x%"PRIx32; break; case RebootReasonCode_ClockFailure: reason_string = "%s%sClock Failure"; break; case RebootReasonCode_WorkerHardFault: show_reset_alert = true; reason_string = "%s%sWorker HardFault"; break; case RebootReasonCode_OutOfMemory: show_reset_alert = true; reason_string = "%s%sOOM"; break; case RebootReasonCode_BtCoredump: show_reset_alert = true; reason_string = "%s%sBT Coredump"; break; default: reason_string = "%s%sUnrecognized Reason"; break; // Error occurred case RebootReasonCode_Watchdog: show_reset_alert = true; DEBUG_LOG(LOG_LEVEL_INFO, "%s%sWatchdog: Bits 0x%" PRIx8 ", Mask 0x%" PRIx8, restarted_safely_string, rebooted_due_to, reason.data8[0], reason.data8[1]); if (reason.watchdog.stuck_task_pc != 0) { DEBUG_LOG(LOG_LEVEL_INFO, "Stuck task PC: 0x%" PRIx32 ", LR: 0x%" PRIx32, reason.watchdog.stuck_task_pc, reason.watchdog.stuck_task_lr); if (reason.watchdog.stuck_task_callback) { DEBUG_LOG(LOG_LEVEL_INFO, "Stuck callback: 0x%" PRIx32, reason.watchdog.stuck_task_callback); } } break; case RebootReasonCode_StackOverflow: show_reset_alert = true; PebbleTask task = (PebbleTask) reason.data8[0]; DEBUG_LOG(LOG_LEVEL_INFO, "%s%sStackOverflow: Task #%d (%s)", restarted_safely_string, rebooted_due_to, task, pebble_task_get_name(task)); break; case RebootReasonCode_EventQueueFull: show_reset_alert = true; DEBUG_LOG(LOG_LEVEL_INFO, "%s%sEvent Queue Full", restarted_safely_string, rebooted_due_to); DEBUG_LOG(LOG_LEVEL_INFO, "Task: <%s> LR: 0x%"PRIx32" Current: 0x%"PRIx32" Dropped: 0x%"PRIx32, pebble_task_get_name(reason.event_queue.destination_task), reason.event_queue.push_lr, reason.event_queue.current_event, reason.event_queue.dropped_event); break; } // Generic reason string if (reason_string) { DEBUG_LOG(LOG_LEVEL_INFO, reason_string, restarted_safely_string, rebooted_due_to, reason.extra); } analytics_set(ANALYTICS_DEVICE_METRIC_SYSTEM_CRASH_LR, lr, AnalyticsClient_System); // We need to wait for the logging service to initialize. AnalyticsEventCrash *crash_report = kernel_malloc_check(sizeof(AnalyticsEventCrash)); *crash_report = (AnalyticsEventCrash) { .crash_code = reason.code, .link_register = lr }; launcher_task_add_callback(log_reboot_reason_cb, crash_report); if (is_unread_coredump_available()) { DEBUG_LOG(LOG_LEVEL_INFO, "Unread coredump file is present!"); } DEBUG_LOG(LOG_LEVEL_INFO, "MCU reset reason mask: 0x%x", (int)mcu_reboot_reason.reset_mask); #if CAPABILITY_HAS_PMIC uint32_t pmic_reset_reason = pmic_get_last_reset_reason(); if (pmic_reset_reason != 0) { DEBUG_LOG(LOG_LEVEL_INFO, "PMIC reset reason mask: 0x%x", (int)pmic_reset_reason); } #endif #ifdef SHOW_PEBBLE_JUST_RESET_ALERT // Trigger an alert display so that the user knows the watch rebooted due to a crash. This event // will be caught and handled by the launcher.c event loop. if (show_reset_alert) { crashed_ui_show_pebble_reset(); } #endif reboot_reason_clear(); }