/************************************************************************** * * Copyright (c) 2026-2027 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 NVMgmtDD.c * * @author (original) Arpita Srivastava * @date (original) 31-Mar-2026 * ***************************************************************************/ #include // For memcpy #include "Common.h" #include "NVDriver.h" #include "NVMgmtDD.h" #include "NVMsgQ.h" #include "NVRecordsDD.h" #include "Timers.h" /** * @addtogroup NVMgmtDD * @{ */ // ********** private definitions ********** #define COMMAND_TIME_OUT (1 * MS_PER_SECOND) ///< Timeout for an EEPROM or RTC command in ms. /// EEPROM functions use the buffer length as the size of U32. So before send the length to any of FAPI functions, it should be divided by 4. #define EEPROM_OPS_SIZE_OF_CONVERTER 4 /// NVDataMgmt Exec states enumeration. typedef enum NVDataMgmt_Exec_State { NVDATAMGMT_EXEC_STATE_IDLE = 0, ///< Exec state Idle. NVDATAMGMT_EXEC_STATE_WRITE_TO_EEPROM, ///< Exec state write to EEPROM. NVDATAMGMT_EXEC_STATE_VERIFY_EEPROM_WRITE, ///< Exec state verify EEPROM write. NVDATAMGMT_EXEC_STATE_READ_FROM_EEPROM, ///< Exec state read from EEPROM. NVDATAMGMT_EXEC_STATE_ERASE_EEPROM, ///< Exec state erase EEPROM. NUM_OF_NVDATAMGMT_EXEC_STATES ///< Total number of exec states. } NVDATAMGMT_EXEC_STATE_T; /// NVDataMgmt write record validity check states typedef enum NVDataMgmt_Write_Record_Validity_Check { NVDATAMGMT_RECORD_NOT_CHECKED = 0, ///< NVDataMgmt (written) record not checked. NVDATAMGMT_RECORD_VALID, ///< NVDataMgmt record is valid. NVDATAMGMT_RECORD_NOT_VALID, ///< NVDataMgmt record is not valid. NUM_OF_NVDATAMGMT_RECORD_VALIDITY_CHECK ///< Number of NVDataMgmt validity check states. } RECORD_VALIDITY_CHECK_T; // ********** private data ********** static NVDATAMGMT_EXEC_STATE_T nvDataMgmtExecState; ///< NVDataMgmt exec state variable. static volatile BOOL powerOffIsImminent; ///< Power off warning has been signaled. Non-volatile memory operations should be completed ASAP and then ceased. static RECORD_VALIDITY_CHECK_T writtenRecordStatus; ///< Record data write validity check. static U08 writtenRecordCheckBuffer[ MAX_EEPROM_WRITE_BUFFER_BYTES ]; ///< Written record validity check buffer. static U32 currentTime; ///< Current time. static U32 recordAddressOffset; ///< Record address offset. // ********** private function prototypes ********** // Exec functions static NVDATAMGMT_EXEC_STATE_T handleExecIdleState( void ); static NVDATAMGMT_EXEC_STATE_T handleExecEraseState( void ); static NVDATAMGMT_EXEC_STATE_T handleExecWriteToEEPROMState( void ); static NVDATAMGMT_EXEC_STATE_T handleExecReadFromEEPROMState( void ); static NVDATAMGMT_EXEC_STATE_T handleExecVerifyEEPROMWriteState( void ); // Helper functions static BOOL didCommandTimeout( NVDATAMGMT_EXEC_STATE_T state ); void initNVMgmtDD( void ) { nvDataMgmtExecState = NVDATAMGMT_EXEC_STATE_IDLE; powerOffIsImminent = FALSE; currentTime = 0; writtenRecordStatus = NVDATAMGMT_RECORD_NOT_CHECKED; recordAddressOffset = 0; initNVDriver(); initNVMsgQ(); initNVRecordsDD(); enqueuePOSTReadRecords(); } /*********************************************************************//** * @brief * The signalPowerOffWarning signals this module that system power off is * imminent and any non-volatile data writes in progress should be wrapped * up quickly and any pending non-volatile data writes should not be started. * @details Inputs: powerOffIsImminent * @details Outputs: powerOffIsImminent * @return none *************************************************************************/ void signalPowerOffWarning( void ) { powerOffIsImminent = TRUE; } /*********************************************************************//** * @brief * The execNVDataMgmt runs the NVDataMgmt main tasks. * @details Inputs: nvDataMgmtExecState * @details Outputs: nvDataMgmtExecState * @return none *************************************************************************/ void execNVDataMgmt( void ) { switch ( nvDataMgmtExecState ) { case NVDATAMGMT_EXEC_STATE_IDLE: nvDataMgmtExecState = handleExecIdleState(); break; case NVDATAMGMT_EXEC_STATE_ERASE_EEPROM: nvDataMgmtExecState = handleExecEraseState(); break; case NVDATAMGMT_EXEC_STATE_WRITE_TO_EEPROM: nvDataMgmtExecState = handleExecWriteToEEPROMState(); break; case NVDATAMGMT_EXEC_STATE_READ_FROM_EEPROM: nvDataMgmtExecState = handleExecReadFromEEPROMState(); break; case NVDATAMGMT_EXEC_STATE_VERIFY_EEPROM_WRITE: nvDataMgmtExecState = handleExecVerifyEEPROMWriteState(); break; default: // SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DD_SOFTWARE_FAULT, SW_FAULT_ID_NVDATAMGMT_EXEC_INVALID_STATE, nvDataMgmtExecState ); nvDataMgmtExecState = NVDATAMGMT_EXEC_STATE_IDLE; break; } } /*********************************************************************//** * @brief * The handleExecIdleState checks if the queue is empty and if it is not * empty, it sets the state of the job. * @details Inputs: powerOffIsImminent * @details Outputs: none * @return next state *************************************************************************/ static NVDATAMGMT_EXEC_STATE_T handleExecIdleState ( void ) { NVDATAMGMT_EXEC_STATE_T state = NVDATAMGMT_EXEC_STATE_IDLE; BOOL areQueuesNotEmpty = FALSE; NVDATAMGMT_OPERATION_STATE_T ops; U32 startAddress; U08* bufferAddress; PROCESS_RECORD_JOB_T recordCurrentJob; RECORD_JOBS_STATE_T job; PROCESS_RECORD_SPECS_T jobSpecs; // If power off command has been issued, do not process any new jobs if ( powerOffIsImminent != TRUE ) { // If the record processing queue is not empty, process the queues if ( ( FALSE == isRecordQueueEmpty() ) && ( TRUE == isFlashReady() ) ) { dequeueRecordJob(); recordCurrentJob = getCurrentProcessRecordJob(); job = recordCurrentJob.recordJob; jobSpecs = getProcessRecord( job ); // Set the record address offset to 0 since a job will just be started recordAddressOffset = 0; areQueuesNotEmpty = TRUE; ops = recordCurrentJob.memoryOperation; currentTime = getMSTimerCount(); startAddress = jobSpecs.startAddress + recordAddressOffset; bufferAddress = jobSpecs.structAddressPtr + recordAddressOffset; } } // Check if a queue job is available if ( TRUE == areQueuesNotEmpty ) { switch ( ops ) { case NVDATAMGMT_ERASE_SECTOR: eraseSector( (U32*)startAddress ); state = NVDATAMGMT_EXEC_STATE_ERASE_EEPROM; break; case NVDATAMGMT_WRITE: writeSector( (U32*)startAddress, (U08*)bufferAddress, jobSpecs.maxWriteBufferSize ); currentTime = getMSTimerCount(); state = NVDATAMGMT_EXEC_STATE_WRITE_TO_EEPROM; break; case NVDATAMGMT_READ: readSector( (U32*)startAddress, (U32*)bufferAddress, ( jobSpecs.maxReadBufferSize / EEPROM_OPS_SIZE_OF_CONVERTER ) ); currentTime = getMSTimerCount(); state = NVDATAMGMT_EXEC_STATE_READ_FROM_EEPROM; break; default: break; } } // Update Self Test Read Records Flag if ( ( TRUE == isRecordQueueEmpty() ) && ( state == NVDATAMGMT_EXEC_STATE_IDLE ) ) { updateSelfTestReadRecordsFlag( TRUE ); } else { updateSelfTestReadRecordsFlag( FALSE ); } return state; } /*********************************************************************//** * @brief * The handleExecEraseState issues an erase command to EEPROM on entry * and if the erase was successful, it sets the state to Idle. * @details Inputs: none * @details Outputs: none * @return next state *************************************************************************/ static NVDATAMGMT_EXEC_STATE_T handleExecEraseState ( void ) { NVDATAMGMT_EXEC_STATE_T state = NVDATAMGMT_EXEC_STATE_ERASE_EEPROM; BOOL timeoutStatus = didCommandTimeout( state ); if ( TRUE == isFlashReady() || timeoutStatus == TRUE ) { state = NVDATAMGMT_EXEC_STATE_IDLE; } return state; } /*********************************************************************//** * @brief * The handleExecWriteToEEPROMState issues a write command to EEPROM and * if the write was successful, it sets the state to Idle. * @details Inputs: recordAddressOffset, writtenRecordCheckBuffer, * writtenRecordStatus * @details Outputs: recordAddressOffset, writtenRecordCheckBuffer, * writtenRecordStatus * @return next state of the state machine *************************************************************************/ static NVDATAMGMT_EXEC_STATE_T handleExecWriteToEEPROMState ( void ) { NVDATAMGMT_EXEC_STATE_T state = NVDATAMGMT_EXEC_STATE_WRITE_TO_EEPROM; BOOL timeoutStatus = didCommandTimeout( state ); PROCESS_RECORD_JOB_T recordCurrentJob = getCurrentProcessRecordJob(); RECORD_JOBS_STATE_T job = recordCurrentJob.recordJob; PROCESS_RECORD_SPECS_T jobSpecs = getProcessRecord ( job ); // Check if the fapi has finished if ( TRUE == isFlashReady() ) { // Check the integrity of data (the 16 bytes that were written to EEPROM should be read and be checked for each byte) if ( writtenRecordStatus == NVDATAMGMT_RECORD_NOT_CHECKED ) { currentTime = getMSTimerCount(); U32 startAddress = jobSpecs.startAddress + recordAddressOffset; U32 maxBufferLength = jobSpecs.maxWriteBufferSize / EEPROM_OPS_SIZE_OF_CONVERTER; // Clear the buffer from the previous content memset( writtenRecordCheckBuffer, 0, sizeof( writtenRecordCheckBuffer ) ); // Issue a FAPI read command but only the bytes that were written, so use maxWriteBufferSize readSector( (U32*)startAddress, (U32*)writtenRecordCheckBuffer, maxBufferLength ); state = NVDATAMGMT_EXEC_STATE_VERIFY_EEPROM_WRITE; } else if ( writtenRecordStatus == NVDATAMGMT_RECORD_VALID ) { // If the data is valid, and if it is at the end of the write, change to idle if ( jobSpecs.sizeofJob == recordAddressOffset ) { state = NVDATAMGMT_EXEC_STATE_IDLE; } else { // Update the variables and issue the next write command currentTime = getMSTimerCount(); recordAddressOffset += jobSpecs.maxWriteBufferSize; U32 memoryPtr = jobSpecs.startAddress + recordAddressOffset; U08* structPtr = jobSpecs.structAddressPtr + recordAddressOffset; writtenRecordStatus = NVDATAMGMT_RECORD_NOT_CHECKED; // Issue the write command writeSector( (U32*)memoryPtr, structPtr, jobSpecs.maxWriteBufferSize ); } } } // If timed out, get back to Idle else if ( timeoutStatus == TRUE ) { recordAddressOffset = 0; state = NVDATAMGMT_EXEC_STATE_IDLE; } return state; } /*********************************************************************//** * @brief * The handleExecReadFromEEPROMState issues a read command to EEPROM on entry * and if the read was successful, it sets the state to Idle. * @details Inputs: None * @details Outputs: None * @return next state *************************************************************************/ static NVDATAMGMT_EXEC_STATE_T handleExecReadFromEEPROMState ( void ) { NVDATAMGMT_EXEC_STATE_T state = NVDATAMGMT_EXEC_STATE_READ_FROM_EEPROM; BOOL timeoutStatus = didCommandTimeout( state ); if ( ( TRUE == isFlashReady() ) || ( TRUE == timeoutStatus ) ) { state = NVDATAMGMT_EXEC_STATE_IDLE; } return state; } /*********************************************************************//** * @brief * The handleExecVerifyEEPROMWriteState checks all the bytes that were * written to EEPROM to ensure they match the data in the NV memory. * @details Inputs: recordAddressOffset, writtenRecordCheckBuffer, * writtenRecordStatus * @details Outputs: recordAddressOffset, writtenRecordCheckBuffer, * writtenRecordStatus * @return next state of the state machine *************************************************************************/ static NVDATAMGMT_EXEC_STATE_T handleExecVerifyEEPROMWriteState ( void ) { NVDATAMGMT_EXEC_STATE_T state = NVDATAMGMT_EXEC_STATE_VERIFY_EEPROM_WRITE; BOOL timeoutStatus = didCommandTimeout( state ); PROCESS_RECORD_JOB_T recordCurrentJob = getCurrentProcessRecordJob(); RECORD_JOBS_STATE_T job = recordCurrentJob.recordJob; PROCESS_RECORD_SPECS_T jobSpecs = getProcessRecord ( job ); // Check if the write job is EEPROM or RTC RAM if ( TRUE == isFlashReady() ) { U32 i; U08* bufferPtr = jobSpecs.structAddressPtr + recordAddressOffset; // Loop through the bytes in the buffer for ( i = 0; i < jobSpecs.maxWriteBufferSize; i++ ) { // Check if data in the buffer is not the same as the data in the structure if ( writtenRecordCheckBuffer[ i ] != *bufferPtr ) { // Data is not valid. Schedule an erase and write of the calibration record since only a part of // data cannot be written to EEPROM. writtenRecordStatus = NVDATAMGMT_RECORD_NOT_VALID; enqueueRecordJob( NVDATAMGMT_ERASE_SECTOR, NVDATAMGMT_CALIBRATION_RECORD ); enqueueRecordJob( NVDATAMGMT_WRITE, NVDATAMGMT_CALIBRATION_RECORD ); state = NVDATAMGMT_EXEC_STATE_IDLE; // Exit the loop since there is no point to check the rest of data break; } else { // Everything is good increment the pointer to check the next byte. bufferPtr++; // Record data is valid so far writtenRecordStatus = NVDATAMGMT_RECORD_VALID; // Go back write to EEPROM state to continue writing the record data state = NVDATAMGMT_EXEC_STATE_WRITE_TO_EEPROM; } } } return state; } /*********************************************************************//** * @brief * The resetNVDataMgmtPOSTState function resets the NV data management POST * state and enqueues all the NV records to be read again from the NV memory. * @details Inputs: none * @details Outputs: nvDataMgmtSelfTestState, nvDataMgmtSelfTestResult * @return none *************************************************************************/ void resetNVDataMgmtPOSTState( void ) { updateNVSelfTestResult( SELF_TEST_STATUS_IN_PROGRESS ); updateNVSelfTestState( NVDATAMGMT_SELF_TEST_STATE_READ_RECORDS ); } /*********************************************************************//** * @brief * The didCommandTimedout checks whether the a command whether RTC RAM or * EEPROM has timedout. If it has timedout, it sets the alarm and turns * flag to TRUE. * @details Inputs: none * @details Outputs: alarm if command timed out * @param state the state that the command timed out * @return TRUE if a command timed out *************************************************************************/ static BOOL didCommandTimeout ( NVDATAMGMT_EXEC_STATE_T state ) { BOOL status = FALSE; if ( TRUE == didTimeout( currentTime, COMMAND_TIME_OUT ) ) { // SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DD_SOFTWARE_FAULT, SW_FAULT_ID_NVDATA_MANAGEMENT_OPS_TIMEOUT, state ) status = TRUE; } return status; } /**@}*/