/* ; Project: Open Vehicle Monitor System ; Date: 14th March 2017 ; ; Changes: ; 1.0 Initial release ; ; (C) 2011 Michael Stegen / Stegen Electronics ; (C) 2011-2017 Mark Webb-Johnson ; (C) 2011 Sonny Chen @ EPRO/DX ; ; Permission is hereby granted, free of charge, to any person obtaining a copy ; of this software and associated documentation files (the "Software"), to deal ; in the Software without restriction, including without limitation the rights ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ; copies of the Software, and to permit persons to whom the Software is ; furnished to do so, subject to the following conditions: ; ; The above copyright notice and this permission notice shall be included in ; all copies or substantial portions of the Software. ; ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ; THE SOFTWARE. */ #include "ovms_log.h" static const char *TAG = "boot"; #include "freertos/FreeRTOS.h" #include "freertos/xtensa_api.h" #include "rom/rtc.h" #include "rom/uart.h" #include "soc/rtc_cntl_reg.h" #include "esp_system.h" #include "esp_panic.h" #include "esp_task_wdt.h" #include #include "ovms.h" #include "ovms_boot.h" #include "ovms_command.h" #include "ovms_metrics.h" #include "ovms_notify.h" #include "ovms_config.h" #include "metrics_standard.h" #include "string_writer.h" #include boot_data_t __attribute__((section(".rtc.noload"))) boot_data; Boot MyBoot __attribute__ ((init_priority (1100))); extern void xt_unhandled_exception(XtExcFrame *frame); // Exception descriptions copied from esp32/panic.c: static const char *edesc[] = { "IllegalInstruction", "Syscall", "InstructionFetchError", "LoadStoreError", "Level1Interrupt", "Alloca", "IntegerDivideByZero", "PCValue", "Privileged", "LoadStoreAlignment", "res", "res", "InstrPDAddrError", "LoadStorePIFDataError", "InstrPIFAddrError", "LoadStorePIFAddrError", "InstTLBMiss", "InstTLBMultiHit", "InstFetchPrivilege", "res", "InstrFetchProhibited", "res", "res", "res", "LoadStoreTLBMiss", "LoadStoreTLBMultihit", "LoadStorePrivilege", "res", "LoadProhibited", "StoreProhibited", "res", "res", "Cp0Dis", "Cp1Dis", "Cp2Dis", "Cp3Dis", "Cp4Dis", "Cp5Dis", "Cp6Dis", "Cp7Dis" }; #define NUM_EDESCS (sizeof(edesc) / sizeof(char *)) // Register names copied from esp32/panic.c: static const char *sdesc[] = { "PC ", "PS ", "A0 ", "A1 ", "A2 ", "A3 ", "A4 ", "A5 ", "A6 ", "A7 ", "A8 ", "A9 ", "A10 ", "A11 ", "A12 ", "A13 ", "A14 ", "A15 ", "SAR ", "EXCCAUSE", "EXCVADDR", "LBEG ", "LEND ", "LCOUNT " }; // Boot reasons [bootreason_t] static const char* const bootreason_name[] = { "PowerOn", "Wakeup", "SoftReset", "FirmwareUpdate", "EarlyCrash", "Crash", }; #define NUM_BOOTREASONS (sizeof(bootreason_name) / sizeof(char *)) // Reset reasons [esp_reset_reason_t] static const char *resetreason_name[] = { "Unknown/unset", "Power-on event", "External pin", "esp_restart", "Exception/panic", "Interrupt watchdog", "Task watchdog", "Other watchdogs", "Exiting deep sleep", "Brownout", "SDIO", }; #define NUM_RESETREASONS (sizeof(resetreason_name) / sizeof(char *)) /** * ovms_reset_reason_get_hint() * * This is a copy of esp_reset_reason_get_hint() from esp32/reset_reason.c. Because the * default esp32 init only checks the reason for CPU0 and then clears the reason register, * we need to store a copy in our boot_data record, but esp_reset_reason_get_hint() * is an internal method. */ #define RST_REASON_BIT 0x80000000 #define RST_REASON_MASK 0x7FFF #define RST_REASON_SHIFT 16 /* in IRAM, can be called from panic handler */ static esp_reset_reason_t IRAM_ATTR ovms_reset_reason_get_hint(void) { uint32_t reset_reason_hint = REG_READ(RTC_RESET_CAUSE_REG); uint32_t high = (reset_reason_hint >> RST_REASON_SHIFT) & RST_REASON_MASK; uint32_t low = reset_reason_hint & RST_REASON_MASK; if ((reset_reason_hint & RST_REASON_BIT) == 0 || high != low) { return ESP_RST_UNKNOWN; } return (esp_reset_reason_t) low; } void boot_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { time_t rawtime; struct tm* tml; char tb[32]; writer->printf("Last boot was %d second(s) ago\n",monotonictime); time(&rawtime); rawtime = rawtime-(time_t)monotonictime; tml = localtime(&rawtime); if ((strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S %Z", tml) > 0) && rawtime > 0) writer->printf("Time at boot: %s\n", tb); writer->printf(" This is reset #%d since last power cycle\n",boot_data.boot_count); writer->printf(" Detected boot reason: %s (%d/%d)\n",MyBoot.GetBootReasonName(),boot_data.bootreason_cpu0,boot_data.bootreason_cpu1); writer->printf(" Reset reason: %s (%d)\n",MyBoot.GetResetReasonName(),MyBoot.GetResetReason()); writer->printf(" Crash counters: %d total, %d early\n",MyBoot.GetCrashCount(),MyBoot.GetEarlyCrashCount()); if (MyBoot.m_shutdown_timer>0) { writer->printf("\nShutdown for %s in progress (%d secs, waiting for %d tasks)\n", MyBoot.m_shutdown_deepsleep ? "DeepSleep" : "Restart", MyBoot.m_shutdown_timer, MyBoot.m_shutdown_pending); } if (MyBoot.GetCrashCount() > 0) { // output data of last crash: writer->printf("\nLast crash: "); if (boot_data.crash_data.is_abort) { // Software controlled panic: writer->printf("abort() was called on core %d\n", boot_data.crash_data.core_id); if (boot_data.stack_overflow_taskname[0]) writer->printf(" Stack overflow in task %s\n", boot_data.stack_overflow_taskname); else if (boot_data.curr_task[0].name[0] && !boot_data.curr_task[0].stackfree) writer->printf(" Pending stack overflow in task %s\n", boot_data.curr_task[0].name); else if (boot_data.curr_task[1].name[0] && !boot_data.curr_task[1].stackfree) writer->printf(" Pending stack overflow in task %s\n", boot_data.curr_task[1].name); } else { // Hardware exception: int exccause = boot_data.crash_data.reg[19]; writer->printf("%s exception on core %d\n", (exccause < NUM_EDESCS) ? edesc[exccause] : "Unknown", boot_data.crash_data.core_id); writer->printf(" Registers:\n"); for (int i=0; i<24; i++) writer->printf(" %s: 0x%08lx%s", sdesc[i], boot_data.crash_data.reg[i], ((i+1)%4) ? "" : "\n"); } for (int core = 0; core < portNUM_PROCESSORS; core++) { if (boot_data.curr_task[core].name[0]) writer->printf(" Current task on core %d: %s, %u stack bytes free\n", core, boot_data.curr_task[core].name, boot_data.curr_task[core].stackfree); } writer->printf(" Backtrace:\n "); for (int i=0; iprintf(" 0x%08lx", boot_data.crash_data.bt[i].pc); if (boot_data.curr_event_name[0]) { writer->printf("\n Event: %s@%s %u secs", boot_data.curr_event_name, boot_data.curr_event_handler, boot_data.curr_event_runtime); } if (MyBoot.GetResetReason() == ESP_RST_TASK_WDT) { writer->printf("\n WDT tasks: %s", boot_data.wdt_tasknames); } writer->printf("\n Version: %s\n", StdMetrics.ms_m_version->AsString("").c_str()); writer->printf("\n Hardware: %s\n", StdMetrics.ms_m_hardware->AsString("").c_str()); } } void boot_clear(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { memset(&boot_data,0,sizeof(boot_data_t)); boot_data.crc = boot_data.calc_crc(); writer->puts("Boot status data has been cleared."); } Boot::Boot() { ESP_LOGI(TAG, "Initialising BOOT (1100)"); RESET_REASON cpu0 = rtc_get_reset_reason(0); RESET_REASON cpu1 = rtc_get_reset_reason(1); m_shutdown_timer = 0; m_shutdown_pending = 0; m_shutdown_deepsleep = false; m_shutting_down = false; m_resetreason = esp_reset_reason(); // Note: necessary to link reset_reason module if (cpu0 == POWERON_RESET) { memset(&boot_data,0,sizeof(boot_data_t)); m_bootreason = BR_PowerOn; ESP_LOGI(TAG, "Power cycle reset detected"); } else if (cpu0 == DEEPSLEEP_RESET) { memset(&boot_data,0,sizeof(boot_data_t)); m_bootreason = BR_Wakeup; esp_sleep_wakeup_cause_t wakeup_cause = esp_sleep_get_wakeup_cause(); ESP_LOGI(TAG, "Wakeup from deep sleep detected, wakeup cause %d", wakeup_cause); // There is currently only one deep sleep application: saving the 12V battery // from depletion. So we need to check if the voltage level is sufficient for // normal operation now. MyPeripherals has not been initialized yet, so we need // to read the ADC manually here. #ifdef CONFIG_OVMS_COMP_ADC // Note: RTC_MODULE nags about a lock release before aquire, this can be ignored // (reason: RTC_MODULE needs FreeRTOS for locking, which hasn't been started yet) adc1_config_width(ADC_WIDTH_12Bit); adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_11db); uint32_t adc_level = 0; for (int i = 0; i < 5; i++) adc_level += adc1_get_raw(ADC1_CHANNEL_0); float level_12v = (float) adc_level / 5 / 195.7; ESP_LOGI(TAG, "12V level: ~%.1fV", level_12v); if (level_12v > 11.0) ESP_LOGI(TAG, "12V level sufficient, proceeding with boot"); else if (level_12v < 1.0) ESP_LOGI(TAG, "Assuming USB powered, proceeding with boot"); else { ESP_LOGE(TAG, "12V level insufficient, re-entering deep sleep"); esp_deep_sleep(1000000LL * 60); } #else ESP_LOGW(TAG, "ADC not available, cannot check 12V level"); #endif // CONFIG_OVMS_COMP_ADC } else if (boot_data.crc != boot_data.calc_crc()) { memset(&boot_data,0,sizeof(boot_data_t)); m_bootreason = BR_PowerOn; ESP_LOGW(TAG, "Boot data corruption detected, data cleared"); } else { boot_data.boot_count++; ESP_LOGI(TAG, "Boot #%d reasons for CPU0=%d and CPU1=%d",boot_data.boot_count,cpu0,cpu1); if (boot_data.soft_reset) { boot_data.crash_count_total = 0; boot_data.crash_count_early = 0; m_bootreason = BR_SoftReset; m_resetreason = ESP_RST_SW; ESP_LOGI(TAG, "Soft reset by user"); } else if (boot_data.firmware_update) { boot_data.crash_count_total = 0; boot_data.crash_count_early = 0; m_bootreason = BR_FirmwareUpdate; ESP_LOGI(TAG, "Firmware update reset"); m_resetreason = ESP_RST_SW; } else if (!boot_data.stable_reached) { boot_data.crash_count_total++; boot_data.crash_count_early++; m_bootreason = BR_EarlyCrash; ESP_LOGE(TAG, "Early crash #%d detected", boot_data.crash_count_early); m_resetreason = boot_data.reset_hint; ESP_LOGI(TAG, "Reset reason %s (%d)", GetResetReasonName(), GetResetReason()); } else { boot_data.crash_count_total++; m_bootreason = BR_Crash; ESP_LOGE(TAG, "Crash #%d detected", boot_data.crash_count_total); m_resetreason = boot_data.reset_hint; ESP_LOGI(TAG, "Reset reason %s (%d)", GetResetReasonName(), GetResetReason()); } } m_crash_count_early = boot_data.crash_count_early; m_stack_overflow = boot_data.stack_overflow; if (!m_stack_overflow) boot_data.stack_overflow_taskname[0] = 0; boot_data.bootreason_cpu0 = cpu0; boot_data.bootreason_cpu1 = cpu1; boot_data.reset_hint = ESP_RST_UNKNOWN; // reset flags: boot_data.soft_reset = false; boot_data.firmware_update = false; boot_data.stable_reached = false; boot_data.stack_overflow = false; boot_data.crc = boot_data.calc_crc(); // install error handler: xt_set_error_handler_callback(ErrorCallback); // Register our commands OvmsCommand* cmd_boot = MyCommandApp.RegisterCommand("boot","BOOT framework",boot_status, "", 0, 0, false); cmd_boot->RegisterCommand("status","Show boot system status",boot_status,"", 0, 0, false); cmd_boot->RegisterCommand("clear","Clear/reset boot system status",boot_clear,"", 0, 0, false); } Boot::~Boot() { } void Boot::SetStable() { boot_data.stable_reached = true; boot_data.crash_count_early = 0; boot_data.crc = boot_data.calc_crc(); } void Boot::SetSoftReset() { boot_data.soft_reset = true; boot_data.crc = boot_data.calc_crc(); } void Boot::SetFirmwareUpdate() { boot_data.soft_reset = false; boot_data.firmware_update = true; boot_data.crc = boot_data.calc_crc(); } const char* Boot::GetBootReasonName() { return (m_bootreason >= 0 && m_bootreason < NUM_BOOTREASONS) ? bootreason_name[m_bootreason] : "Unknown boot reason"; } const char* Boot::GetResetReasonName() { if (m_stack_overflow) return "Stack overflow"; return (m_resetreason >= 0 && m_resetreason < NUM_RESETREASONS) ? resetreason_name[m_resetreason] : "Unknown reset reason"; } static void boot_shutdown_done(const char* event, void* data) { MyConfig.unmount(); if (MyBoot.m_shutdown_deepsleep) { // For consistency with init, instead of calling MyPeripherals->m_esp32->SetPowerMode(DeepSleep): esp_deep_sleep(1000000LL * 60); } else { esp_restart(); } } static void boot_shuttingdown_done(const char* event, void* data) { if (MyBoot.m_shutdown_pending == 0) MyBoot.m_shutdown_timer = 2; } void Boot::Restart(bool hard) { SetSoftReset(); if (hard) { esp_restart(); return; } ESP_LOGI(TAG,"Shutting down for %s...", m_shutdown_deepsleep ? "DeepSleep" : "Restart"); OvmsMutexLock lock(&m_shutdown_mutex); m_shutting_down = true; m_shutdown_pending = 0; m_shutdown_timer = 60; // Give them 60 seconds to shutdown MyEvents.SignalEvent("system.shuttingdown", NULL, boot_shuttingdown_done); #undef bind // Kludgy, but works using std::placeholders::_1; using std::placeholders::_2; MyEvents.RegisterEvent(TAG,"ticker.1", std::bind(&Boot::Ticker1, this, _1, _2)); } void Boot::DeepSleep() { m_shutdown_deepsleep = true; Restart(false); } void Boot::ShutdownPending(const char* tag) { OvmsMutexLock lock(&m_shutdown_mutex); m_shutdown_pending++; ESP_LOGW(TAG, "Shutdown: %s pending, %d total", tag, m_shutdown_pending); } void Boot::ShutdownReady(const char* tag) { OvmsMutexLock lock(&m_shutdown_mutex); m_shutdown_pending--; ESP_LOGI(TAG, "Shutdown: %s ready, %d pending", tag, m_shutdown_pending); if (m_shutdown_pending == 0) m_shutdown_timer = 2; } void Boot::Ticker1(std::string event, void* data) { if (m_shutdown_timer > 0) { OvmsMutexLock lock(&m_shutdown_mutex); m_shutdown_timer--; if (m_shutdown_timer == 1) { ESP_LOGI(TAG, "%s now", m_shutdown_deepsleep ? "DeepSleep" : "Restart"); } else if (m_shutdown_timer == 0) { MyEvents.SignalEvent("system.shutdown", NULL, boot_shutdown_done); return; } else if ((m_shutdown_timer % 5)==0) ESP_LOGI(TAG, "%s in %d seconds (%d pending)...", m_shutdown_deepsleep ? "DeepSleep" : "Restart", m_shutdown_timer, m_shutdown_pending); } } bool Boot::IsShuttingDown() { return m_shutting_down; } /* * Direct UART output utils borrowed from esp32/panic.c */ static void panicPutChar(char c) { while (((READ_PERI_REG(UART_STATUS_REG(CONFIG_CONSOLE_UART_NUM)) >> UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT) >= 126) ; WRITE_PERI_REG(UART_FIFO_REG(CONFIG_CONSOLE_UART_NUM), c); } static void panicPutStr(const char *c) { int x = 0; while (c[x] != 0) { panicPutChar(c[x]); x++; } } /* * This function is called by task_wdt_isr function (ISR for when TWDT times out). * It can be redefined in user code to handle twdt events. * Note: It has the same limitations as the interrupt function. * Do not use ESP_LOGI functions inside. */ extern "C" void esp_task_wdt_isr_user_handler(void) { panicPutStr("\r\n[OVMS] ***TWDT***\r\n"); // Save TWDT task info: esp_task_wdt_get_trigger_tasknames(boot_data.wdt_tasknames, sizeof(boot_data.wdt_tasknames)); } /* * This function is called if FreeRTOS detects a stack overflow. */ extern "C" void vApplicationStackOverflowHook( TaskHandle_t xTask, signed char *pcTaskName ) { panicPutStr("\r\n[OVMS] ***ERROR*** A stack overflow in task "); panicPutStr((char *)pcTaskName); panicPutStr(" has been detected.\r\n"); strlcpy(boot_data.stack_overflow_taskname, (const char*)pcTaskName, sizeof(boot_data.stack_overflow_taskname)); boot_data.stack_overflow = true; abort(); } void Boot::ErrorCallback(XtExcFrame *frame, int core_id, bool is_abort) { boot_data.reset_hint = ovms_reset_reason_get_hint(); boot_data.crash_data.core_id = core_id; boot_data.crash_data.is_abort = is_abort; // Save registers: for (int i=0; i<24; i++) boot_data.crash_data.reg[i] = ((uint32_t*)frame)[i+1]; // Save backtrace: // (see panic.c::doBacktrace() for code template) #define _adjusted_pc(pc) (((pc) & 0x80000000) ? (((pc) & 0x3fffffff) | 0x40000000) : (pc)) uint32_t i = 0, pc = frame->pc, sp = frame->a1; boot_data.crash_data.bt[i++].pc = _adjusted_pc(pc); pc = frame->a0; while (i < OVMS_BT_LEVELS) { uint32_t psp = sp; if (!esp_stack_ptr_is_sane(sp)) break; sp = *((uint32_t *) (sp - 0x10 + 4)); boot_data.crash_data.bt[i++].pc = _adjusted_pc(pc - 3); pc = *((uint32_t *) (psp - 0x10)); if (pc < 0x40000000) break; } while (i < OVMS_BT_LEVELS) boot_data.crash_data.bt[i++].pc = 0; // Save Event debug info: if (!MyEvents.m_current_event.empty()) { strlcpy(boot_data.curr_event_name, MyEvents.m_current_event.c_str(), sizeof(boot_data.curr_event_name)); if (MyEvents.m_current_callback) strlcpy(boot_data.curr_event_handler, MyEvents.m_current_callback->m_caller.c_str(), sizeof(boot_data.curr_event_handler)); else strlcpy(boot_data.curr_event_handler, "EventScript", sizeof(boot_data.curr_event_handler)); boot_data.curr_event_runtime = monotonictime - MyEvents.m_current_started; } else { boot_data.curr_event_name[0] = 0; boot_data.curr_event_handler[0] = 0; boot_data.curr_event_runtime = 0; } // Save current tasks: panicPutStr("\r\n[OVMS] Current tasks: "); for (int core=0; core 0) panicPutChar('|'); panicPutStr(name); strlcpy(boot_data.curr_task[core].name, name, sizeof(boot_data.curr_task[core].name)); boot_data.curr_task[core].stackfree = stackfree; if (!stackfree) boot_data.stack_overflow = true; } else { boot_data.curr_task[core].name[0] = 0; boot_data.curr_task[core].stackfree = 0; } } panicPutStr("\r\n"); boot_data.crc = boot_data.calc_crc(); } void Boot::NotifyDebugCrash() { if (GetCrashCount() > 0) { // Send crash data notification: // H type "*-OVM-DebugCrash" // , // ,,,, // ,,,,,, // ,, // ,,, // , // , // , // ,,,, StringWriter buf; buf.reserve(2048); buf.append("*-OVM-DebugCrash,0,2592000,"); buf.append(mp_encode(StdMetrics.ms_m_version->AsString(""))); buf.printf(",%d,%s,%d,%d,%d,%d" , boot_data.boot_count, GetBootReasonName(), boot_data.bootreason_cpu0, boot_data.bootreason_cpu1 , GetCrashCount(), GetEarlyCrashCount()); // type, core, registers: if (boot_data.crash_data.is_abort) { buf.printf(",abort(),%d,", boot_data.crash_data.core_id); } else { int exccause = boot_data.crash_data.reg[19]; buf.printf(",%s,%d,", (exccause < NUM_EDESCS) ? edesc[exccause] : "Unknown", boot_data.crash_data.core_id); for (int i=0; i<24; i++) buf.printf("0x%08lx ", boot_data.crash_data.reg[i]); } // backtrace: buf.append(","); for (int i=0; iAsString(""))); // Stack overflow task: std::string name = boot_data.stack_overflow_taskname; buf.append(","); buf.append(mp_encode(name)); // Current tasks: for (int i = 0; i < 2; i++) { name = boot_data.curr_task[i].name; buf.append(","); buf.append(mp_encode(name)); buf.printf(",%u", boot_data.curr_task[i].stackfree); } MyNotify.NotifyString("data", "debug.crash", buf.c_str()); } }