/************************************************************************** * * Copyright (c) 2024-2024 Diality Inc. - All Rights Reserved. * * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. * * @file WatchdogMgmt.c * * @author (last) Sean * @date (last) 30-Jul-2024 * * @author (original) Sean * @date (original) 30-Jul-2024 * ***************************************************************************/ #ifdef _TD_ #include "CpldInterface.h" #include "FpgaTD.h" #endif #ifdef _DD_ #include "FpgaDD.h" #include "SafetyShutdown.h" #endif #ifdef _RO_ #include "FpgaRO.h" #include "SafetyShutdown.h" #endif //#include "InternalADC.h" #include "Messaging.h" #include "OperationModes.h" #include "Timers.h" #include "WatchdogMgmt.h" /** * @addtogroup WatchdogMgmt * @{ */ // ********** private definitions ********** #define MIN_WATCHDOG_PET_INTERVAL_MS 45 ///< Minimum watchdog pet interval (in ms). #define WATCHDOG_POST_TIMEOUT_MS 500 ///< Watchdog POST test timeout (in ms). #define WATCHDOG_RECOVERY_TIME_MS 500 ///< After watchdog POST test, wait this long (in ms) before moving on. #define MAX_24V_LEVEL_ON_WATCHDOG_EXPIRED 5.0F ///< Maximum voltage on 24V line when watchdog is expired. // TODO - check w/ Systems. Takes time for V to bleed off. Had to raise to 5V. #define MIN_24V_LEVEL_ON_WATCHDOG_RECOVER 22.6F ///< Minimum voltage on 24V line when watchdog is recovered. #define MIN_BACKUP_ALARM_CURRENT_MA 200.0F ///< Minimum backup alarm audio current (in mA) detected when watchdog is expired. #define MAX_BACKUP_ALARM_CURRENT_MA 10.0F ///< Maximum backup alarm audio current (in mA) detected when watchdog is recovered. #define MAX_SAFETY_SHUTDOWN_MISMATCH_MS 100 ///< Maximum time (in ms) that safety shutdown cmd vs. feedback can be mismatched. /// Enumeration of watchdog self-test states. typedef enum Watchdog_Self_Test_States { WATCHDOG_SELF_TEST_STATE_START = 0, ///< Watchdog self-test start state WATCHDOG_SELF_TEST_STATE_IN_PROGRESS, ///< Watchdog self-test in progress state WATCHDOG_SELF_TEST_STATE_RECOVER, ///< Watchdog self-test recovery state WATCHDOG_SELF_TEST_STATE_COMPLETE, ///< Watchdog self-test completed state NUM_OF_WATCHDOG_SELF_TEST_STATES ///< Number of watchdog self-test states } WATCHDOG_SELF_TEST_STATE_T; // ********** private data ********** static U32 lastWatchdogPetTime; ///< Timestamp (ms counter) since last watchdog pet. static OVERRIDE_U32_T watchdogTaskCheckedIn[ NUM_OF_TASKS ]; ///< Array of flags indicating whether individual tasks have checked in with watchdog manager. static WATCHDOG_SELF_TEST_STATE_T watchdogSelfTestState; ///< Current watchdog self-test state. static SELF_TEST_STATUS_T watchdogSelfTestStatus; ///< Watchdog self-test state status. static U32 watchdogSelfTestTimerCount; ///< Watchdog self-test state timer counter. static U32 safetyShutdownFeedbackMismatchTS; ///< Persistence timestamp for safety shutdown cmd vs. feedback mismatch. // ********** private function prototypes ********** static void resetWDTaskCheckIns( void ); static BOOL haveAllTasksCheckedIn( void ); static void petWatchdog( void ); static BOOL hasTaskGeneralCheckedIn( U32 task ); /*********************************************************************//** * @brief * The initWatchdogMgmt function initializes the watchdog management unit. * @details \b Inputs: none * @details \b Outputs: Watchdog management unit initialized. * @return none *************************************************************************/ void initWatchdogMgmt( void ) { U32 i; lastWatchdogPetTime = 0; watchdogSelfTestState = WATCHDOG_SELF_TEST_STATE_START; watchdogSelfTestStatus = SELF_TEST_STATUS_IN_PROGRESS; watchdogSelfTestTimerCount = 0; safetyShutdownFeedbackMismatchTS = getMSTimerCount(); // Initialize task check-ins to false for ( i = 0; i < NUM_OF_TASKS; i++ ) { watchdogTaskCheckedIn[ i ].data = FALSE; watchdogTaskCheckedIn[ i ].ovData = FALSE; watchdogTaskCheckedIn[ i ].ovInitData = FALSE; watchdogTaskCheckedIn[ i ].override = OVERRIDE_RESET; } } /*********************************************************************//** * @brief * The execWatchdogMgmt function executes the watchdog management service. * If all tasks have checked in, the watchdog is pet. * @details \b Inputs: watchdogTaskCheckedIn[] * @details \b Outputs: watchdogTaskCheckedIn[] * @details \b Alarm: ALARM_ID_XX_WATCHDOG_EXPIRED if safety activated * @warning: It may be necessary to comment out the alarm activation lines to * prevent the alarm occurring while debugging. * @return none *************************************************************************/ void execWatchdogMgmt( void ) { BOOL allTasksCheckedIn; #ifdef _TD_ PIN_SIGNAL_STATE_T safetyShutdownFeedbackSignal = getCPLDSafety(); #endif #ifdef _DD_ // TODO : clean up once we get the clarity PIN_SIGNAL_STATE_T safetyShutdownFeedbackSignal = PIN_SIGNAL_HIGH; #endif #ifdef _RO_ // TODO : clean up once we get the clarity PIN_SIGNAL_STATE_T safetyShutdownFeedbackSignal = PIN_SIGNAL_HIGH; #endif PIN_SIGNAL_STATE_T safetyShutdownSoftwareCmd = ( TRUE == isSafetyShutdownActivated() ? PIN_SIGNAL_LOW : PIN_SIGNAL_HIGH ); // Called by background task, so give bg task credit for checking in checkInWithWatchdogMgmt( TASK_BG ); // Check to see if all monitored tasks have checked in allTasksCheckedIn = haveAllTasksCheckedIn(); // If all monitored tasks checked in, pet watchdog and clear the slate if ( ( TRUE == allTasksCheckedIn ) && ( TRUE == didTimeout( lastWatchdogPetTime, MIN_WATCHDOG_PET_INTERVAL_MS ) ) ) { petWatchdog(); resetWDTaskCheckIns(); } #ifdef _TD_ // Check to see if watchdog has expired or safety shutdown feedback does not match s/w command (only after POST completed) if ( ( safetyShutdownSoftwareCmd != safetyShutdownFeedbackSignal ) && ( getCurrentOperationMode() != MODE_INIT ) ) #endif #ifdef _DD_ // Check to see if watchdog has expired or safety shutdown feedback does not match s/w command (only after POST completed) if ( ( safetyShutdownSoftwareCmd != safetyShutdownFeedbackSignal ) && ( getCurrentOperationMode() != DD_MODE_INIT ) ) #endif #ifdef _RO_ // Check to see if watchdog has expired or safety shutdown feedback does not match s/w command (only after POST completed) if ( ( safetyShutdownSoftwareCmd != safetyShutdownFeedbackSignal ) && ( getCurrentOperationMode() != RO_MODE_INIT ) ) #endif { if ( ( PIN_SIGNAL_LOW == safetyShutdownFeedbackSignal ) || ( TRUE == didTimeout( safetyShutdownFeedbackMismatchTS, MAX_SAFETY_SHUTDOWN_MISMATCH_MS ) ) ) { #ifdef _TD_ activateAlarmNoData( ALARM_ID_TD_WATCHDOG_EXPIRED ); #endif #ifdef _DD_ activateAlarmNoData( ALARM_ID_DD_WATCHDOG_EXPIRED ); #endif #ifdef _RO_ //activateAlarmNoData( ALARM_ID_RO_WATCHDOG_EXPIRED ); #endif } } else { safetyShutdownFeedbackMismatchTS = getMSTimerCount(); } } /*********************************************************************//** * @brief * The checkInWithWatchdogMgmt function checks a given task in with the * watchdog management service. * @details \b Inputs: none * @details \b Outputs: task is checked in * @details \b Alarms: ALARM_ID_TD_SOFTWARE_FAULT if invalid task given * @param task the task that is checking in * @return none *************************************************************************/ void checkInWithWatchdogMgmt( TASK_T task ) { if ( task < NUM_OF_TASKS ) { watchdogTaskCheckedIn[ task ].data = TRUE; } else { SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_WATCHDOG_INVALID_TASK, task ) } } /*********************************************************************//** * @brief * The execWatchdogTest function executes the watchdog self-test. * @warning This function should be called periodically until a pass or fail * result is returned. * @details \b Inputs: watchdogSelfTestState, watchdogSelfTestTimerCount * @details \b Outputs: watchdogSelfTestState, watchdogSelfTestTimerCount, * watchdogSelfTestStatus * @details \b Alarm: ALARM_ID_XX_WATCHDOG_POST_TEST_FAILED if self-test fails. * @details \b Alarm: ALARM_ID_XX_SOFTWARE_FAULT if self-test state is invalid. * @return in progress, passed, or failed *************************************************************************/ SELF_TEST_STATUS_T execWatchdogTest( void ) { SELF_TEST_STATUS_T result = SELF_TEST_STATUS_IN_PROGRESS; switch ( watchdogSelfTestState ) { case WATCHDOG_SELF_TEST_STATE_START: watchdogSelfTestState = WATCHDOG_SELF_TEST_STATE_IN_PROGRESS; watchdogSelfTestTimerCount = getMSTimerCount(); // No break here so we pass through directly to in progress processing case WATCHDOG_SELF_TEST_STATE_IN_PROGRESS: while ( FALSE == didTimeout( watchdogSelfTestTimerCount, WATCHDOG_POST_TIMEOUT_MS ) ) { // Waiting here for w.d. test period to prevent this task from checking in - watchdog should expire } #ifdef _TD_ // if ( PIN_SIGNAL_LOW == getCPLDSafety() ) #else // if ( TBD ) #endif // { // F32 v24 = getIntADCVoltageConverted( INT_ADC_24V_ACTUATORS ); // F32 audioCurrent = getFPGABackupAlarmAudioCurrent(); // // // Verify 24V is down when w.d. expired // if ( v24 > MAX_24V_LEVEL_ON_WATCHDOG_EXPIRED ) // { // SET_ALARM_WITH_2_F32_DATA( ALARM_ID_HD_WATCHDOG_POST_TEST_FAILED, 2.0F, v24 ); // watchdogSelfTestStatus = SELF_TEST_STATUS_FAILED; // } // // Verify backup alarm audio is on when w.d. expired // else if ( audioCurrent < MIN_BACKUP_ALARM_CURRENT_MA ) // { //#ifndef _RELEASE_ // if ( getSoftwareConfigStatus( SW_CONFIG_DISABLE_ALARM_AUDIO ) != SW_CONFIG_ENABLE_VALUE ) //#endif // { // SET_ALARM_WITH_2_F32_DATA( ALARM_ID_HD_WATCHDOG_POST_TEST_FAILED, 3.0F, audioCurrent ); // watchdogSelfTestStatus = SELF_TEST_STATUS_FAILED; // } // } // } // else // { // SET_ALARM_WITH_1_U32_DATA( ALARM_ID_HD_WATCHDOG_POST_TEST_FAILED, 1 ); // watchdogSelfTestStatus = SELF_TEST_STATUS_FAILED; // } // watchdogSelfTestTimerCount = getMSTimerCount(); // watchdogSelfTestState = WATCHDOG_SELF_TEST_STATE_RECOVER; break; case WATCHDOG_SELF_TEST_STATE_RECOVER: if ( TRUE == didTimeout( watchdogSelfTestTimerCount, WATCHDOG_RECOVERY_TIME_MS ) ) { // Verify watchdog expired signal no longer active #ifdef _TD_ // if ( PIN_SIGNAL_HIGH == getCPLDSafety() ) #else // if ( TBD ) #endif // { // F32 v24 = getIntADCVoltageConverted( INT_ADC_24V_ACTUATORS ); // F32 audioCurrent = getFPGABackupAlarmAudioCurrent(); // // // Verify 24V is down when w.d. recovered // if ( v24 < MIN_24V_LEVEL_ON_WATCHDOG_RECOVER ) // { // SET_ALARM_WITH_2_F32_DATA( ALARM_ID_HD_WATCHDOG_POST_TEST_FAILED, 4.0F, v24 ); // watchdogSelfTestStatus = SELF_TEST_STATUS_FAILED; // } // // Verify backup alarm audio is on when w.d. recovered // else if ( audioCurrent > MAX_BACKUP_ALARM_CURRENT_MA ) // { //#ifndef _RELEASE_ // if ( getSoftwareConfigStatus( SW_CONFIG_DISABLE_ALARM_AUDIO ) != SW_CONFIG_ENABLE_VALUE ) //#endif // { // SET_ALARM_WITH_2_F32_DATA( ALARM_ID_HD_WATCHDOG_POST_TEST_FAILED, 5.0F, audioCurrent ); // watchdogSelfTestStatus = SELF_TEST_STATUS_FAILED; // } // } // else // { // watchdogSelfTestStatus = SELF_TEST_STATUS_PASSED; // } // } // else // { // SET_ALARM_WITH_1_U32_DATA( ALARM_ID_HD_WATCHDOG_POST_TEST_FAILED, 6 ); // watchdogSelfTestStatus = SELF_TEST_STATUS_FAILED; // } // result = watchdogSelfTestStatus; // watchdogSelfTestState = WATCHDOG_SELF_TEST_STATE_COMPLETE; } break; case WATCHDOG_SELF_TEST_STATE_COMPLETE: // If we get called in this state, assume we are doing self-test again watchdogSelfTestStatus = SELF_TEST_STATUS_IN_PROGRESS; watchdogSelfTestState = WATCHDOG_SELF_TEST_STATE_START; break; default: result = SELF_TEST_STATUS_FAILED; SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_WATCHDOG_INVALID_SELF_TEST_STATE, watchdogSelfTestState ) break; } return result; } /*********************************************************************//** * @brief * The resetWatchdogPOSTState function resets the watchdog POST state so * it can be run again if needed. * @details \b Inputs: none * @details \b Outputs: watchdogSelfTestState * @return none *************************************************************************/ void resetWatchdogPOSTState( void ) { watchdogSelfTestState = WATCHDOG_SELF_TEST_STATE_START; } /*********************************************************************//** * @brief * The resetWDTaskCheckIns function resets the task check-ins with the watchdog. * @details \b Inputs: none * @details \b Outputs: watchdogTaskCheckedIn[] array reset to all false. * @return none *************************************************************************/ static void resetWDTaskCheckIns( void ) { U32 i; // Initialize task check-ins to false for ( i = 0; i < NUM_OF_TASKS; i++ ) { watchdogTaskCheckedIn[ i ].data = FALSE; } } /*********************************************************************//** * @brief * The haveAllTasksCheckedIn function determines whether all tasks have * checked in. * @details \b Inputs: watchdogTaskCheckedIn[] * @details \b Outputs: none * @return TRUE if all tasks have checked in since last watchdog pet, FALSE if not. *************************************************************************/ static BOOL haveAllTasksCheckedIn( void ) { BOOL result = TRUE; U32 i; // Check that each task has checked in for ( i = 0; i < NUM_OF_TASKS; i++ ) { if ( FALSE == hasTaskGeneralCheckedIn( i ) ) { result = FALSE; break; } } return result; } /*********************************************************************//** * @brief * The hasTaskGeneralCheckedIn function gets the checked in status of a given * task. * @details \b Inputs: watchdogTaskCheckedIn[] * @details \b Outputs: none * @details \b Alarm: ALARM_ID_XX_SOFTWARE_FAULT if given task is invalid. * @param task ID of task to determine checked in status of * @return TRUE if given task has checked in, FALSE if not *************************************************************************/ BOOL hasTaskGeneralCheckedIn( U32 task ) { BOOL result = FALSE; if ( task < NUM_OF_TASKS ) { if ( OVERRIDE_KEY == watchdogTaskCheckedIn[ task ].override ) { result = (BOOL)watchdogTaskCheckedIn[ task ].ovData; } else { result = (BOOL)watchdogTaskCheckedIn[ task ].data; } } else { #ifdef _TD_ SET_ALARM_WITH_2_U32_DATA( ALARM_ID_TD_SOFTWARE_FAULT, SW_FAULT_ID_WATCHDOG_INVALID_TASK, task ) #endif #ifdef _DD_ SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DD_SOFTWARE_FAULT, SW_FAULT_ID_WATCHDOG_INVALID_TASK, task ) #endif #ifdef _RO_ //SET_ALARM_WITH_2_U32_DATA( ALARM_ID_RO_SOFTWARE_FAULT, TBD, task ) #endif } return result; } /*********************************************************************//** * @brief * The petWatchdog function pets the watchdog by pulsing the watchdog pet * signal. * @details \b Inputs: none * @details \b Outputs: Watchdog pet signal is pulsed * @return none *************************************************************************/ static void petWatchdog( void ) { // Pulse the watchdog signal #ifdef _TD_ toggleCPLDWatchdog(); #else // TBD #endif // Remember when we last pet the watchdog lastWatchdogPetTime = getMSTimerCount(); } /************************************************************************* * TEST SUPPORT FUNCTIONS *************************************************************************/ /*********************************************************************//** * @brief * The testWatchdogTaskCheckInOverride function overrides the state of the * check-in for a given task with a given check-in state. * @warning Dialin must be logged into related firmware stack to perform * an override successfully. * @details \b Inputs: none * @details \b Outputs: watchdogTaskCheckedIn[] * @param message Override message from Dialin which includes an ID of * the task to override and the check-in status to override the task to. * @return TRUE if override successful, FALSE if not *************************************************************************/ BOOL testWatchdogTaskCheckInOverride( MESSAGE_T *message ) { BOOL result = u32ArrayOverride( message, &watchdogTaskCheckedIn[0], NUM_OF_TASKS - 1, FALSE, TRUE ); return result; } /**@}*/