#include "safety_manager.h" #include "cy_project.h" #include "cy_device_headers.h" #include "motor_control.h" #include "seat_motor_manager.h" #include "seat_move_config.h" #if defined(CoreDebug) && defined(CoreDebug_DHCSR_C_DEBUGEN_Msk) #define SAFETY_DEBUG_DETACH_SUPPORTED (1u) #else #define SAFETY_DEBUG_DETACH_SUPPORTED (0u) #endif static volatile safety_fault_t latchedFault = SAFETY_FAULT_NONE; static bool watchdogReset = false; static bool watchdogInitialized = false; static bool hilWatchdogResetArmed = false; static bool hilWatchdogRecovered = false; #if (SAFETY_DEBUG_DETACH_SUPPORTED == 1u) static bool debuggerWasAttached = false; #endif static bool SafetyManager_DebuggerAttached(void) { #if (SAFETY_DEBUG_DETACH_SUPPORTED == 1u) return ((CoreDebug->DHCSR & CoreDebug_DHCSR_C_DEBUGEN_Msk) != 0ul); #else return false; #endif } static uint32_t SafetyManager_ReadDebugMarker(void) { return BACKUP->unBREG[SEAT_MOVE_DEBUG_SESSION_BREG_INDEX].u32Register; } static bool SafetyManager_WriteDebugMarker(uint32_t marker) { BACKUP->unBREG[SEAT_MOVE_DEBUG_SESSION_BREG_INDEX].u32Register = marker; __DSB(); return (SafetyManager_ReadDebugMarker() == marker); } static bool SafetyManager_WaitForWatchdogState(bool enabled) { uint32_t remaining = 200ul; uint32_t expected = enabled ? 1ul : 0ul; /* WDT ENABLE is an asynchronous request bit; ENABLED is the synchronized * LF-clock state. Configuration writes made before disable completes are * ignored by the hardware. */ while((WDT->unCTL.stcField.u1ENABLED != expected) && (remaining > 0ul)) { Cy_SysTick_DelayInUs(1u); remaining--; } return (WDT->unCTL.stcField.u1ENABLED == expected); } static void SafetyManager_ClearHilWatchdogCredential(void) { BACKUP->unBREG[SEAT_MOVE_HIL_WDT_MAGIC_BREG_INDEX].u32Register = 0u; BACKUP->unBREG[SEAT_MOVE_HIL_WDT_TOKEN_BREG_INDEX].u32Register = 0u; BACKUP->unBREG[SEAT_MOVE_HIL_WDT_CHECK_BREG_INDEX].u32Register = 0u; __DSB(); } void SafetyManager_Init(uint32_t resetReason) { const uint32_t watchdogMask = CY_SYSRESET_WDT | CY_SYSRESET_MCWDT0 | CY_SYSRESET_MCWDT1 | CY_SYSRESET_MCWDT2 | CY_SYSRESET_MCWDT3; bool watchdogStopped; bool debuggerAttached; uint32_t marker; uint32_t hilMagic; uint32_t hilToken; uint32_t hilCheck; bool authorizedHilReset; bool markerOk = true; /* A watchdog instance is retained across its own reset on this device. * Stop it before reading credentials or initializing peripherals; the * 100 ms bench timeout otherwise permits a reset loop. */ Cy_WDT_Disable(); watchdogStopped = SafetyManager_WaitForWatchdogState(false); debuggerAttached = SafetyManager_DebuggerAttached(); marker = SafetyManager_ReadDebugMarker(); hilMagic = BACKUP->unBREG[ SEAT_MOVE_HIL_WDT_MAGIC_BREG_INDEX].u32Register; hilToken = BACKUP->unBREG[ SEAT_MOVE_HIL_WDT_TOKEN_BREG_INDEX].u32Register; hilCheck = BACKUP->unBREG[ SEAT_MOVE_HIL_WDT_CHECK_BREG_INDEX].u32Register; authorizedHilReset = (hilMagic == SEAT_MOVE_HIL_WDT_MARKER) && (hilCheck == (hilMagic ^ hilToken ^ 0xFFFFFFFFu)) && (((resetReason & watchdogMask) != 0ul) || ((resetReason & CY_SYSRESET_SOFT) != 0ul)); latchedFault = SAFETY_FAULT_NONE; watchdogInitialized = false; hilWatchdogResetArmed = false; watchdogReset = ((resetReason & watchdogMask) != 0ul) || authorizedHilReset; hilWatchdogRecovered = authorizedHilReset; if((resetReason & CY_SYSRESET_PORVDDD) != 0ul) { markerOk = SafetyManager_WriteDebugMarker(0u); marker = SafetyManager_ReadDebugMarker(); SafetyManager_ClearHilWatchdogCredential(); hilWatchdogRecovered = false; } else if(hilWatchdogRecovered) { /* Consume before CAN comes online: transport retries cannot turn one * authenticated reset into a second physical reset. */ SafetyManager_ClearHilWatchdogCredential(); } else if(hilMagic == SEAT_MOVE_HIL_WDT_MARKER) { /* XRES/programming without a valid reset reason is not completion. */ SafetyManager_ClearHilWatchdogCredential(); } #if (SAFETY_DEBUG_DETACH_SUPPORTED == 1u) debuggerWasAttached = debuggerAttached; #endif if(!markerOk || (marker == SEAT_MOVE_DEBUG_EXIT_MAGIC) || ((marker == SEAT_MOVE_DEBUG_ACTIVE_MAGIC) && !debuggerAttached)) { latchedFault = SAFETY_FAULT_DEBUG_DETACH; } else if(watchdogReset && !hilWatchdogRecovered) { latchedFault = SAFETY_FAULT_BOOT_RECOVERY; } else if(debuggerAttached && !SafetyManager_WriteDebugMarker(SEAT_MOVE_DEBUG_ACTIVE_MAGIC)) { latchedFault = SAFETY_FAULT_DEBUG_DETACH; } /* The system-WDT configuration register is retained across some reset * paths and AUTO_SERVICE is not reset by Cy_WDT_Init(). If that bit is * left enabled, stopping the two software service paths can never prove a * real watchdog reset because hardware services the counter at WARN_LIMIT. * Configure only while stopped, and make software the sole WDT owner. */ if(!watchdogStopped) { latchedFault = SAFETY_FAULT_DRIVER; return; } Cy_WDT_Init(); Cy_WDT_Unlock(); Cy_WDT_SetUpperLimit(SEAT_MOVE_WDT_TIMEOUT_TICKS); Cy_WDT_SetUpperAction(CY_WDT_LOW_UPP_ACTION_RESET); Cy_WDT_SetWarnAction(CY_WDT_WARN_ACTION_NONE); Cy_WDT_SetAutoService(CY_WDT_DISABLE); Cy_WDT_SetDebugRun(CY_WDT_ENABLE); Cy_WDT_Lock(); if((WDT->unUPPER_LIMIT.stcField.u32UPPER_LIMIT != SEAT_MOVE_WDT_TIMEOUT_TICKS) || (WDT->unCONFIG.stcField.u1UPPER_ACTION != (uint32_t)CY_WDT_LOW_UPP_ACTION_RESET) || (WDT->unCONFIG.stcField.u1AUTO_SERVICE != (uint32_t)CY_WDT_DISABLE)) { latchedFault = SAFETY_FAULT_DRIVER; return; } Cy_WDT_Enable(); if(!SafetyManager_WaitForWatchdogState(true)) { latchedFault = SAFETY_FAULT_DRIVER; return; } Cy_WDT_ClearWatchdog(); Cy_SysTick_DelayInUs(1000u); if(Cy_WDT_GetCount() == 0ul) { latchedFault = SAFETY_FAULT_DRIVER; return; } watchdogInitialized = true; SafetyManager_KickWatchdog(); } void SafetyManager_KickWatchdog(void) { if((false != watchdogInitialized) && !hilWatchdogResetArmed) { Cy_WDT_ClearWatchdog(); } } bool SafetyManager_IsWatchdogInitialized(void) { return watchdogInitialized; } void SafetyManager_LatchFault(safety_fault_t fault) { if((fault != SAFETY_FAULT_NONE) && (latchedFault == SAFETY_FAULT_NONE)) { latchedFault = fault; Motor_LatchSafetyInhibitFromIsr(0u); Motor_StopAll(); SeatMotorManager_StopAll(0ul); } } void SafetyManager_Service(void) { bool debuggerAttached = SafetyManager_DebuggerAttached(); uint32_t marker = SafetyManager_ReadDebugMarker(); if(marker == SEAT_MOVE_DEBUG_EXIT_MAGIC) { SafetyManager_LatchFault(SAFETY_FAULT_DEBUG_DETACH); } if(Motor_IsDriverFaultLatched()) { SafetyManager_LatchFault(SAFETY_FAULT_DRIVER); } else { /* No newly observed driver fault. */ } #if (SAFETY_DEBUG_DETACH_SUPPORTED == 1u) if(debuggerAttached) { if((marker != SEAT_MOVE_DEBUG_ACTIVE_MAGIC) && !SafetyManager_WriteDebugMarker(SEAT_MOVE_DEBUG_ACTIVE_MAGIC)) { SafetyManager_LatchFault(SAFETY_FAULT_DEBUG_DETACH); } debuggerWasAttached = true; } else if(debuggerWasAttached) { SafetyManager_LatchFault(SAFETY_FAULT_DEBUG_DETACH); debuggerWasAttached = false; } #else (void)debuggerAttached; #endif SafetyManager_KickWatchdog(); } bool SafetyManager_IsInhibited(void) { return (latchedFault != SAFETY_FAULT_NONE); } safety_fault_t SafetyManager_GetFault(void) { return latchedFault; } bool SafetyManager_WasWatchdogReset(void) { return watchdogReset; } bool SafetyManager_TriggerHilWatchdogReset(uint16_t token) { uint32_t check; if(!watchdogInitialized || hilWatchdogResetArmed) { return false; } BACKUP->unBREG[SEAT_MOVE_HIL_WDT_MAGIC_BREG_INDEX].u32Register = SEAT_MOVE_HIL_WDT_MARKER; BACKUP->unBREG[SEAT_MOVE_HIL_WDT_TOKEN_BREG_INDEX].u32Register = token; check = SEAT_MOVE_HIL_WDT_MARKER ^ (uint32_t)token ^ 0xFFFFFFFFu; BACKUP->unBREG[SEAT_MOVE_HIL_WDT_CHECK_BREG_INDEX].u32Register = check; __DSB(); if((BACKUP->unBREG[ SEAT_MOVE_HIL_WDT_MAGIC_BREG_INDEX].u32Register != SEAT_MOVE_HIL_WDT_MARKER) || (BACKUP->unBREG[ SEAT_MOVE_HIL_WDT_CHECK_BREG_INDEX].u32Register != check)) { SafetyManager_ClearHilWatchdogCredential(); return false; } /* Long-running motor calibration/coast loops normally service WDT * directly so they remain safe even while the application loop is busy. * The armed HIL case must suspend both service paths or a real watchdog * reset can never occur. Startup re-enables this after the reset. */ Motor_SetWatchdogServiceEnabled(false); Cy_WDT_ClearWatchdog(); hilWatchdogResetArmed = true; return true; } void SafetyManager_CommitHilWatchdogReset(void) { if(!hilWatchdogResetArmed) { return; } /* This bench's connected CMSIS-DAP can suppress the WDT reset output even * after C-SPY detaches. First prove physical WDT expiry, then use the * reference project's authenticated processor-reset fallback. */ while(Cy_WDT_GetCount() < SEAT_MOVE_WDT_TIMEOUT_TICKS) { __NOP(); } __DSB(); NVIC_SystemReset(); for(;;) { __NOP(); } } bool SafetyManager_WasHilWatchdogRecovered(void) { return hilWatchdogRecovered; }