/** * @file error_handling.cpp * * @date Apr 1, 2014 * @author Andrey Belomutskiy, (c) 2012-2020 */ #include "pch.h" #include "rusefi/efistringutil.h" #include "os_util.h" #include "backup_ram.h" #include "error_handling_led.h" #include "error_handling_c.h" #include "log_hard_fault.h" #include "rusefi/critical_error.h" // Ignore following (and similar) errors // error: 'strncpy' output may be truncated copying 119 bytes from a string of length 119 _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wstringop-truncation\"") static critical_msg_t warningBuffer; static critical_msg_t criticalErrorMessageBuffer; static critical_msg_t configErrorMessageBuffer; // recoverable configuration error, non-critical extern int warningEnabled; bool hasCriticalFirmwareErrorFlag = false; static bool hasConfigErrorFlag = false; const char *dbg_panic_file; int dbg_panic_line; // todo: need vararg version of 'firmwareError' to make this method vararg? void efiCriticalError(const char *message) { criticalError(message); } const char* getCriticalErrorMessage() { return criticalErrorMessageBuffer; } bool hasConfigError() { return hasConfigErrorFlag; } void clearConfigErrorMessage() { hasConfigErrorFlag = false; } const char* getConfigErrorMessageBuffer() { return configErrorMessageBuffer; } #if EFI_PROD_CODE #if EFI_BACKUP_SRAM static backupErrorState lastBootError; static uint32_t bootCount = 0; #endif void errorHandlerInit() { #if EFI_BACKUP_SRAM /* copy error state from backup RAM and clear it in backup RAM. * so few users can access previous error state and we should not care about who sohuld clear backup ram. */ auto sramState = getBackupSram(); memcpy(&lastBootError, &sramState->err, sizeof(backupErrorState)); memset(&sramState->err, 0x00, sizeof(sramState->err)); // Reset cookie so we don't report it again. sramState->err.Cookie = ErrorCookie::None; //bootcount if (sramState->BootCountCookie != 0xdeadbeef) { sramState->BootCountCookie = 0xdeadbeef; sramState->BootCount = 0; } // save current bootcounter bootCount = sramState->BootCount; sramState->BootCount++; if (engineConfiguration->rethrowHardFault) { backupErrorState *err = &lastBootError; if (err->Cookie == ErrorCookie::HardFault) { criticalError("Last boot had hard fault type: %lx addr: %lx CSFR: %lx", err->FaultType, err->FaultAddress, err->Csfr); } } Reset_Cause_t cause = getMCUResetCause(); // if reset by watchdog, signal a fatal error if ((cause == Reset_Cause_IWatchdog) || (cause == Reset_Cause_WWatchdog)) { firmwareError(ObdCode::OBD_PCM_Processor_Fault, "Watchdog Reset"); } #endif // see https://github.com/rusefi/rusefi/wiki/Resilience addConsoleAction("chibi_fault", [](){ chDbgCheck(0); } ); addConsoleAction("soft_fault", [](){ firmwareError(ObdCode::RUNTIME_CRITICAL_TEST_ERROR, "firmwareError: %d", getRusEfiVersion()); }); addConsoleAction("hard_fault", [](){ causeHardFault(); } ); } bool errorHandlerIsStartFromError() { #if EFI_BACKUP_SRAM return (lastBootError.Cookie != ErrorCookie::None); #else return 0; #endif } // TODO: reuse this code for writing crash report file void errorHandlerShowBootReasonAndErrors() { efiPrintf("Reset Cause: %s", getMCUResetCause(getMCUResetCause())); #if EFI_BACKUP_SRAM auto sramState = getBackupSram(); backupErrorState *err = &lastBootError; efiPrintf("Power cycle count: %lu", sramState->BootCount); if (err->Cookie == ErrorCookie::None) { return; } efiPrintf("Last error cookie 0x%08lx", (uint32_t)err->Cookie); switch (err->Cookie) { case ErrorCookie::FirmwareError: { efiPrintf("Firmware error: %s", err->msg); } break; case ErrorCookie::HardFault: { efiPrintf("HardFault"); efiPrintf("type: 0x%08lx addr: 0x%08lx CSFR: 0x%08lx", err->FaultType, err->FaultAddress, err->Csfr); auto ctx = &err->FaultCtx; efiPrintf("r0 0x%08lx", ctx->r0); efiPrintf("r1 0x%08lx", ctx->r1); efiPrintf("r2 0x%08lx", ctx->r2); efiPrintf("r3 0x%08lx", ctx->r3); efiPrintf("r12 0x%08lx", ctx->r12); efiPrintf("lr (thread) 0x%08lx", ctx->lr_thd); efiPrintf("pc 0x%08lx", ctx->pc); efiPrintf("xpsr 0x%08lx", ctx->xpsr); /* FPU registers - not very useful for debug */ if (0) { // Print rest the context as a sequence of uintptr uintptr_t* data = reinterpret_cast(&err->FaultCtx); for (size_t i = 8; i < sizeof(port_extctx) / sizeof(uintptr_t); i++) { efiPrintf("Fault ctx %d: 0x%08x", i, data[i]); } } } break; case ErrorCookie::ChibiOsPanic: { efiPrintf("ChibiOS panic"); efiPrintf("msg %s", err->msg); efiPrintf("file %s", err->file); efiPrintf("line %d", err->line); } break; default: // No cookie stored or invalid cookie (ie, backup RAM contains random garbage) break; } #endif // EFI_BACKUP_SRAM } #if EFI_FILE_LOGGING #include "ff.h" #define BOOT_REPORT_PREFIX "boot_" PUBLIC_API_WEAK void onBoardWriteErrorFile(FIL *) { } void errorHandlerWriteReportFile(FIL *fd) { #if EFI_BACKUP_SRAM backupErrorState *err = &lastBootError; if (err->Cookie != ErrorCookie::None) { char fileName[_MAX_FILLER + 20]; memset(fd, 0, sizeof(FIL)); // clear the memory sprintf(fileName, "%s%ld.txt", BOOT_REPORT_PREFIX, bootCount); FRESULT ret = f_open(fd, fileName, FA_CREATE_ALWAYS | FA_WRITE); if (ret == FR_OK) { onBoardWriteErrorFile(fd); f_printf(fd, "type=%d\n", err->FaultType); // todo: figure out what else would be useful f_close(fd); } } #endif // EFI_BACKUP_SRAM } #endif backupErrorState *errorHandlerGetLastErrorDescriptor(void) { #ifdef EFI_BACKUP_SRAM return &lastBootError; #else return nullptr; #endif } void logHardFault(uint32_t type, uintptr_t faultAddress, port_extctx* ctx, uint32_t csfr) { criticalShutdown(); #if EFI_BACKUP_SRAM auto bkpram = getBackupSram(); auto err = &bkpram->err; err->Cookie = ErrorCookie::HardFault; err->FaultType = type; err->FaultAddress = faultAddress; err->Csfr = csfr; memcpy(&err->FaultCtx, ctx, sizeof(port_extctx)); #endif // EFI_BACKUP_SRAM } #endif /* EFI_PROD_CODE */ #if EFI_SIMULATOR || EFI_PROD_CODE void chDbgPanic3(const char *msg, const char * file, int line) { #if EFI_PROD_CODE // Attempt to break in to the debugger, if attached __asm volatile("BKPT #0\n"); #endif #if EFI_BACKUP_SRAM auto bkpram = getBackupSram(); auto err = &bkpram->err; strncpy(err->file, file, efi::size(err->file) - 1); err->line = line; strncpy(err->msg, msg, efi::size(err->msg) - 1); err->Cookie = ErrorCookie::ChibiOsPanic; #endif // EFI_BACKUP_SRAM if (hasOsPanicError()) return; dbg_panic_file = file; dbg_panic_line = line; #if CH_DBG_SYSTEM_STATE_CHECK ch0.dbg.panic_msg = msg; #endif /* CH_DBG_SYSTEM_STATE_CHECK */ #if !EFI_PROD_CODE printf("chDbgPanic3 %s %s%d", msg, file, line); exit(-1); #else // EFI_PROD_CODE criticalError("assert fail %s %s:%d", msg, file, line); // If on the main thread, longjmp back to the init process so we can keep USB alive if (chThdGetSelfX()->threadId == 0) { // Force unlock, since we may be throwing-under-lock chSysUnconditionalUnlock(); // there was a port_disable in chSysHalt, reenable interrupts so USB works port_enable(); __NO_RETURN void onAssertionFailure(); onAssertionFailure(); } else { // Not the main thread. // All hope is now lost. // Reboot! NVIC_SystemReset(); } #endif // EFI_PROD_CODE } #endif /* EFI_SIMULATOR || EFI_PROD_CODE */ /** * ObdCode::OBD_PCM_Processor_Fault is the general error code for now * * @returns TRUE in case there were warnings recently */ bool warning(ObdCode code, const char *fmt, ...) { if (hasCriticalFirmwareErrorFlag) { return true; } bool known = engine->engineState.warnings.isWarningNow(code); // if known - just reset timer engine->engineState.warnings.addWarningCode(code); #if EFI_SIMULATOR || EFI_PROD_CODE // we just had this same warning, let's not spam if (known) { return true; } // print Pxxxx (for standard OBD) or Cxxxx (for custom) prefix size_t size = snprintf(warningBuffer, sizeof(warningBuffer), "%s%04d: ", code < ObdCode::CUSTOM_NAN_ENGINE_LOAD ? "P" : "C", (int) code); va_list ap; va_start(ap, fmt); chvsnprintf(warningBuffer + size, sizeof(warningBuffer) - size, fmt, ap); va_end(ap); efiPrintf("WARNING: %s", warningBuffer); #else printf("unit_test_warning: "); va_list ap; va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); printf("\r\n"); #endif /* EFI_SIMULATOR || EFI_PROD_CODE */ return false; } #if EFI_CLOCK_LOCKS uint32_t lastLockTime; /** * Maximum time before requesting lock and releasing lock at the end of critical section */ uint32_t maxLockedDuration = 0; /** * this depends on chdebug.h patch #if CH_DBG_SYSTEM_STATE_CHECK == TRUE -#define _dbg_enter_lock() (ch.dbg.lock_cnt = (cnt_t)1) -#define _dbg_leave_lock() (ch.dbg.lock_cnt = (cnt_t)0) +#define _dbg_enter_lock() {(ch.dbg.lock_cnt = (cnt_t)1); ON_LOCK_HOOK;} +#define _dbg_leave_lock() {ON_UNLOCK_HOOK;(ch.dbg.lock_cnt = (cnt_t)0);} #endif */ #endif /* EFI_CLOCK_LOCKS */ void onLockHook(void) { #if ENABLE_PERF_TRACE perfEventInstantGlobal(PE::GlobalLock); #endif /* ENABLE_PERF_TRACE */ #if EFI_CLOCK_LOCKS lastLockTime = getTimeNowLowerNt(); #endif /* EFI_CLOCK_LOCKS */ } void onUnlockHook(void) { #if EFI_CLOCK_LOCKS uint32_t lockedDuration = getTimeNowLowerNt() - lastLockTime; if (lockedDuration > maxLockedDuration) { maxLockedDuration = lockedDuration; } // if (lockedDuration > 2800) { // // un-comment this if you want a nice stop for a breakpoint // maxLockedDuration = lockedDuration + 1; // } #endif /* EFI_CLOCK_LOCKS */ #if ENABLE_PERF_TRACE perfEventInstantGlobal(PE::GlobalUnlock); #endif /* ENABLE_PERF_TRACE */ } #if EFI_SIMULATOR || EFI_UNIT_TEST #include #endif void configError(const char *fmt, ...) { va_list ap; va_start(ap, fmt); chvsnprintf(configErrorMessageBuffer, sizeof(configErrorMessageBuffer), fmt, ap); va_end(ap); hasConfigErrorFlag = true; } const char* getConfigErrorMessage() { return configErrorMessageBuffer; } void firmwareError(ObdCode code, const char *fmt, ...) { #if EFI_PROD_CODE if (hasCriticalFirmwareErrorFlag) return; hasCriticalFirmwareErrorFlag = true; #if EFI_ENGINE_CONTROL getLimpManager()->fatalError(); #endif // EFI_ENGINE_CONTROL engine->engineState.warnings.addWarningCode(code); criticalShutdown(); enginePins.communicationLedPin.setValue(1, /*force*/true); if (indexOf(fmt, '%') == -1) { /** * in case of simple error message let's reduce stack usage * chvsnprintf could cause an overflow if we're already low */ strncpy((char*) criticalErrorMessageBuffer, fmt, sizeof(criticalErrorMessageBuffer) - 1); criticalErrorMessageBuffer[sizeof(criticalErrorMessageBuffer) - 1] = 0; // just to be sure } else { va_list ap; va_start(ap, fmt); chvsnprintf(criticalErrorMessageBuffer, sizeof(criticalErrorMessageBuffer), fmt, ap); va_end(ap); } int errorMessageSize = strlen((char*)criticalErrorMessageBuffer); static char versionBuffer[32]; chsnprintf(versionBuffer, sizeof(versionBuffer), " %d@%s", getRusEfiVersion(), FIRMWARE_ID); if (errorMessageSize + strlen(versionBuffer) < sizeof(criticalErrorMessageBuffer)) { strcpy((char*)(criticalErrorMessageBuffer) + errorMessageSize, versionBuffer); } #if EFI_BACKUP_SRAM auto bkpram = getBackupSram(); auto err = &bkpram->err; strncpy(err->msg, criticalErrorMessageBuffer, sizeof(err->msg) - 1); err->Cookie = ErrorCookie::FirmwareError; #endif // EFI_BACKUP_SRAM #else // large buffer on stack is risky we better use normal memory static char errorBuffer[200]; va_list ap; va_start(ap, fmt); vsnprintf(errorBuffer, sizeof(errorBuffer), fmt, ap); va_end(ap); printf("\x1B[31m>>>>>>>>>> firmwareError [%s]\r\n\x1B[0m\r\n", errorBuffer); #if EFI_SIMULATOR || EFI_UNIT_TEST throw std::logic_error(errorBuffer); #endif /* EFI_SIMULATOR */ #endif } void criticalErrorM(const char *msg) { criticalError(msg); } _Pragma("GCC diagnostic pop")