/************************************************************************** * * 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 NVMsgQ.c * * @author (original) Arpita Srivastava * @date (original) 31-Mar-2026 * ***************************************************************************/ #include // For memcpy #include // For ceilf function #include "Common.h" #include "NVMsgQ.h" #include "NVRecordsDD.h" #include "TaskGeneral.h" #include "Timers.h" #include "Utilities.h" // For crc calculation /** * @addtogroup NVMsgQ * @{ */ // ********** private definitions ********** #define QUEUE_MAX_SIZE 20U ///< Max queue size. #define QUEUE_START_INDEX 0U ///< Queue start index. #define MAX_JOB_DATA_SIZE_BYTES 32U ///< Max bytes per job (32 bytes). #define NUM_OF_BYTES_PER_CAL_PAYLOAD 150U ///< Number of bytes per calibration payload. #define CAL_DATA_SEND_INTERVAL_COUNT (MS_PER_SECOND / (5 * TASK_GENERAL_INTERVAL)) ///< Calibration data send time interval in counts. #define RECORD_DATA_RECEIVE_TIMEOUT_MS (4 * MS_PER_SECOND) ///< Record data receive all the data packets timeout in ms. #define RECORD_DATA_MAX_MESSAGE_DFFIRENCE 1 ///< Calibration data receive message different from the previous message. #define RECORD_DATA_FIRST_RECEIVING_MSG_NUM 1 ///< Calibration data first receiving message number. // Once a new calibration data is available the driver, sets a signal for the defined time. Once the time is out, it turns the signal off. #define NEW_CAL_AVAILABLE_SIGNAL_TIMEOUT_MS (1 * MS_PER_SECOND) ///< New calibration available signal timeout in milliseconds. /// NVDataMgmt process records states typedef enum NVDataMgmt_Process_Records_States { NVDATAMGMT_PROCESS_RECORD_STATE_IDLE = 0, ///< NVDataMgmt process record idle state. NVDATAMGMT_PROCESS_RECORD_STATE_SEND_RECORD, ///< NVDataMgmt process record send record state. NUM_OF_NVDATAMGMT_PROCESS_RECORD_STATES ///< Number of NVDataMgmt process records state. } PROCESS_RECORD_STATE_T; /// Memory operations structure. typedef struct { NVDATAMGMT_OPERATION_STATE_T memoryOperation; ///< Memory operation. U32 startAddress; ///< Operation start address. U08 buffer[ MAX_JOB_DATA_SIZE_BYTES ]; ///< Buffer. U32 length; ///< Length of a buffer. } MEMORY_OPS_T; /// NVDataMgmt receive records states typedef enum NVDataMgmt_Receive_Records_States { NVDATAMGMT_RECEIVE_RECORD_IDLE = 0, ///< NVDataMgmt receive record idle. NVDATAMGMT_RECEIVE_RECORD_RECEIVE, ///< NVDataMgmt receive record receive. NUM_OF_NVDATAMGMT_RECEIVE_RECORD_STATES ///< Number of NVDataMgmt receive record. } RECEIVE_RECORD_STATE_T; // ********** private data ********** static PROCESS_RECORD_JOB_T recordJobQueue[ QUEUE_MAX_SIZE ]; ///< Record queue jobs. static U08 recordQueueRearIndex; ///< Record queue rear index. static U08 recordQueueFrontIndex; ///< Record queue front index. static U08 recordQueueCount; ///< Record queue count. static PROCESS_RECORD_JOB_T recordCurrentJob; ///< Record queue current job. static NVDATAMGMT_RECORDS_READ_STATUS_T recordsReadStatus; ///< NVDataMgmt records read status. static BOOL isNewCalAvailable; ///< Signal to indicate whether a new calibration data is available. static RECORD_JOBS_STATE_T recordToPublish; ///< Record to publish (i.e. calibration, system) static PROCESS_RECORD_STATE_T nvDataMgmtExecProcessRecordState; ///< NVDataMgmt exec process record state. static RECEIVE_RECORD_STATE_T nvDataMgmtExecReceiveRecordState; ///< NVDataMgmt exec receive record state. static BOOL hasPublishRecordBeenRequested; ///< Record state machine publish request flag. static U32 calPublishMessageCount; ///< Calibration data publish message counter. static U32 calPublishTotalMessages; ///< Calibration data total number of messages to be sent. static U32 calSendDataIntervalCounter; ///< Calibration data send to CAN bust interval counter. static U32 previousCalMessageNum; ///< Calibration previous message number. static U32 recordUpdateAddress; ///< DG record update address for all the write operations. static U32 recordReceiveStartTime; ///< Time stamp the calibration/service was received. static U32 newCalStartTimer; ///< New calibration availability start timer. // ********** private function prototypes ********** // Process record functions static PROCESS_RECORD_STATE_T handleExecProcessRecordIdleState( void ); static PROCESS_RECORD_STATE_T handleExecProcessRecordSendRecordState( void ); static void monitorNewCalSignal( void ); void initNVMsgQ ( void ) { recordUpdateAddress = 0; recordQueueRearIndex = QUEUE_START_INDEX; recordQueueFrontIndex = QUEUE_START_INDEX; recordQueueCount = 0; recordsReadStatus = NVDATAMGMT_RECORDS_NOT_STARTED; isNewCalAvailable = FALSE; nvDataMgmtExecProcessRecordState = NVDATAMGMT_PROCESS_RECORD_STATE_IDLE; nvDataMgmtExecReceiveRecordState = NVDATAMGMT_RECEIVE_RECORD_IDLE; hasPublishRecordBeenRequested = FALSE; newCalStartTimer = 0; calPublishMessageCount = 1; calPublishTotalMessages = 1; calSendDataIntervalCounter = 0; previousCalMessageNum = 0; recordReceiveStartTime = 0; } /*********************************************************************//** * @brief * The execNVDataMgmtProcessRecord runs the NVDataMgmt send records related * to tasks. * @details Inputs: nvDataMgmtExecProcessRecordState, * nvDataMgmtExecReceiveRecordState, recordReceiveStartTime * @details Outputs: nvDataMgmtExecProcessRecordState, * nvDataMgmtExecReceiveRecordState * @return none *************************************************************************/ void execNVDataMgmtProcessRecord( void ) { switch ( nvDataMgmtExecProcessRecordState ) { case NVDATAMGMT_PROCESS_RECORD_STATE_IDLE: nvDataMgmtExecProcessRecordState = handleExecProcessRecordIdleState(); break; case NVDATAMGMT_PROCESS_RECORD_STATE_SEND_RECORD: nvDataMgmtExecProcessRecordState = handleExecProcessRecordSendRecordState(); break; default: // SET_ALARM_WITH_2_U32_DATA( ALARM_ID_DD_SOFTWARE_FAULT, SW_FAULT_ID_INVALID_NVDATAMGMT_EXEC_CAL_STATE, nvDataMgmtExecProcessRecordState ); nvDataMgmtExecProcessRecordState = NVDATAMGMT_PROCESS_RECORD_STATE_IDLE; break; } // Check if the exec receive records is not idle // This section checks the status of the asynchronous state machine that receives // data from Dialin. if ( nvDataMgmtExecReceiveRecordState != NVDATAMGMT_RECEIVE_RECORD_IDLE ) { // Check if the data receiving process has timed out. The exec receive record // state machine is asynchronous so it is checked in this state machine if ( TRUE == didTimeout( recordReceiveStartTime, RECORD_DATA_RECEIVE_TIMEOUT_MS ) ) { // Exec receive state machine timed out. Schedule a read to update the structure enqueueRecordJob( NVDATAMGMT_READ, NVDATAMGMT_CALIBRATION_RECORD ); nvDataMgmtExecReceiveRecordState = NVDATAMGMT_RECEIVE_RECORD_IDLE; } } // Check the calibration signal monitorNewCalSignal(); } /*********************************************************************//** * @brief * The handleExecProcessRecordIdleState handles the idle state of the * exec cal state machine. * @details Inputs: hasPublishRecordBeenRequested, calPublishTotalMessages, * calPublishMessageCount, calSendDataIntervalCounter * @details Outputs: hasPublishRecordBeenRequested, calPublishTotalMessages, * calPublishMessageCount, calSendDataIntervalCounter * @return next state of the state machine *************************************************************************/ static PROCESS_RECORD_STATE_T handleExecProcessRecordIdleState( void ) { PROCESS_RECORD_STATE_T state = NVDATAMGMT_PROCESS_RECORD_STATE_IDLE; if ( TRUE == hasPublishRecordBeenRequested ) { // Set the publish flag to FALSE hasPublishRecordBeenRequested = FALSE; // Get the record specifications to find the size of the job PROCESS_RECORD_SPECS_T recordSpec = getProcessRecord( recordToPublish ); // Calculate the total number of messages required to be sent using ceilf function. This function rounds up the // value and its result is converted to U32. calPublishTotalMessages = (U32)ceilf( (F32)recordSpec.sizeofJob / (F32)NUM_OF_BYTES_PER_CAL_PAYLOAD ); calPublishMessageCount = 0; // Time interval in between data to be sent. It is set to the interval count so on the first call // of the send calibration record function, the first packet of data is sent calSendDataIntervalCounter = CAL_DATA_SEND_INTERVAL_COUNT; state = NVDATAMGMT_PROCESS_RECORD_STATE_SEND_RECORD; } return state; } /*********************************************************************//** * @brief * The handleExecProcessRecordSendRecordState handles the send calibration * record state of the state machine. * @details Inputs: dgCalibrationRecord, calPublishTotalMessages, * calPublishMessageCount, calSendDataIntervalCounter * @details Outputs: calPublishTotalMessages, calPublishMessageCount, * calSendDataIntervalCounter * @return next state of the state machine *************************************************************************/ static PROCESS_RECORD_STATE_T handleExecProcessRecordSendRecordState( void ) { PROCESS_RECORD_STATE_T state = NVDATAMGMT_PROCESS_RECORD_STATE_SEND_RECORD; // If the current message number is less than the total, keep sending if ( calPublishMessageCount < calPublishTotalMessages ) { // If it is time to send data if ( ++calSendDataIntervalCounter >= CAL_DATA_SEND_INTERVAL_COUNT ) { // Set to default cal data payload length // U32 length = NUM_OF_BYTES_PER_CAL_PAYLOAD; PROCESS_RECORD_SPECS_T recordSpec = getProcessRecord( recordToPublish ); U08* startPtr = recordSpec.structAddressPtr; // If this is the last calibration data payload, calculate the remainder of the bytes to send if ( ( calPublishMessageCount + 1 ) == calPublishTotalMessages ) { // length = recordSpec.sizeofJob - ( calPublishMessageCount * NUM_OF_BYTES_PER_CAL_PAYLOAD ); } // Find the new location of the pointer which is the start of the calibration payload to be sent startPtr += calPublishMessageCount * NUM_OF_BYTES_PER_CAL_PAYLOAD; switch( recordToPublish ) { case NVDATAMGMT_CALIBRATION_RECORD: // Pass the information to the CAN bus // sendDGCalibrationRecord( calPublishMessageCount + 1, calPublishTotalMessages, length, startPtr ); break; case NVDATAMGMT_SYSTEM_RECORD: // Pass the information to the CAN bus // sendDGSystemRecord( calPublishMessageCount + 1, calPublishTotalMessages, length, startPtr ); break; case NVDATAMGMT_SERVICE_RECORD: // Pass the information to the CAN bus // sendDGServiceRecord( calPublishMessageCount + 1, calPublishTotalMessages, length, startPtr ); break; #ifndef _RELEASE_ case NVDATAMGMT_SW_CONFIG_RECORD: // sendDGSWConfigRecord( calPublishMessageCount + 1, calPublishTotalMessages, length, startPtr ); break; #endif case NVDATAMGMT_USAGE_INFO_RECORD: // sendDGUsageInfoRecord( calPublishMessageCount + 1, calPublishTotalMessages, length, startPtr ); break; default: //Ignore break; } calPublishMessageCount++; calSendDataIntervalCounter = 0; } } else { state = NVDATAMGMT_PROCESS_RECORD_STATE_IDLE; } return state; } /*********************************************************************//** * @brief * The enqueueRecordJob function enqueues a new record job. * @details Inputs: recordQueueRearIndex, queueCount, recordJobQueue * @details Outputs: recordQueueRearIndex, queueCount, recordJobQueue * @param ops: memory operation (i.e write, read) * @param job: type of job (i.e write calibration data) * @return none *************************************************************************/ void enqueueRecordJob( NVDATAMGMT_OPERATION_STATE_T ops, RECORD_JOBS_STATE_T job ) { PROCESS_RECORD_JOB_T currentJob; currentJob.memoryOperation = ops; currentJob.recordJob = job; recordJobQueue[ recordQueueRearIndex ] = currentJob; recordQueueCount++; recordQueueRearIndex = INC_WRAP( recordQueueRearIndex, 0, QUEUE_MAX_SIZE - 1 ); } /*********************************************************************//** * @brief * The dequeueRecordJob increments the front index counter and if it is * equal to rear index, it sets it to -1, meaning that the queue is empty. * @details Inputs: recordQueueFrontIndex, recordQueueCount, recordCurrentJob, * recordJobQueue * @details Outputs: recordQueueFrontIndex, recordQueueCount, recordCurrentJob * @return none *************************************************************************/ void dequeueRecordJob( void ) { U32 tempIndex; _disable_IRQ(); tempIndex = recordQueueFrontIndex; if ( FALSE == isRecordQueueEmpty() ) { recordQueueFrontIndex = INC_WRAP( recordQueueFrontIndex, 0, QUEUE_MAX_SIZE - 1 ); recordCurrentJob = recordJobQueue[ tempIndex ]; } if ( recordQueueCount > 0 ) { recordQueueCount--; } _enable_IRQ(); } /*********************************************************************//** * @brief * The isRecordQueueEmpty checks whether the queue is empty and if it is * empty, it will return a false. * @details Inputs: recordQueueCount * @details Outputs: none * @return TRUE if queue is not empty *************************************************************************/ BOOL isRecordQueueEmpty( void ) { BOOL isEmpty = TRUE; if ( recordQueueCount > 0 ) { isEmpty = FALSE; } return isEmpty; } BOOL isRecordQueueFull( void ) { BOOL isFull = FALSE; if ( recordQueueCount >= ( QUEUE_MAX_SIZE - 1 ) ) { isFull = TRUE; } return isFull; } /*********************************************************************//** * @brief * The getAvailableRecordQueueCount returns the number of available record * queues. * @details Inputs: recordQueueCount * @details Outputs: none * @return available record queues *************************************************************************/ U32 getAvailableRecordQueueCount( void ) { return QUEUE_MAX_SIZE - recordQueueCount; } /*********************************************************************//** * @brief * The enqueueSector0Records checks whether there are enough jobs available * to be able to enqueue to the records. * @details Inputs: none * @details Outputs: none * @return TRUE if the job were successfully enqueued otherwise, FLASE *************************************************************************/ BOOL enqueueSector0Records( void ) { BOOL status = FALSE; if ( getAvailableRecordQueueCount() >= MIN_JOBS_NEEDED_FOR_SECTOR_0 ) { RECORD_JOBS_STATE_T record; // It is sector 0 of bank 7 so the sector must be erased first enqueueRecordJob( NVDATAMGMT_ERASE_SECTOR, NVDATAMGMT_CALIBRATION_RECORD ); for ( record = NVDATAMGMT_CALIBRATION_RECORD; record < NUM_OF_NVDATMGMT_RECORDS_JOBS; record++ ) { enqueueRecordJob( NVDATAMGMT_WRITE, record ); } status = TRUE; } return status; } /*********************************************************************//** * @brief * The enqueuePOSTReadRecords enqueues the NV records to be read during POST. * @details Inputs: none * @details Outputs: recordsReadStatus * @return none *************************************************************************/ void enqueuePOSTReadRecords( void ) { RECORD_JOBS_STATE_T record; for ( record = NVDATAMGMT_CALIBRATION_RECORD; record < NUM_OF_NVDATMGMT_RECORDS_JOBS; record++ ) { enqueueRecordJob( NVDATAMGMT_READ, record ); } // Set the status to records were queued successfully recordsReadStatus = NVDATAMGMT_RECORDS_QUEUED; } /*********************************************************************//** * @brief * The getNVRecordsReadStatus function returns the status of the NV POST. * @details Inputs: none * @details Outputs: none * @return recordsReadStatus *************************************************************************/ NVDATAMGMT_RECORDS_READ_STATUS_T getNVRecordsReadStatus( void ) { return recordsReadStatus; } /*********************************************************************//** * @brief * The sendRecordToDialin function prepares the process record state machine * to send a record to Dialin. * @details Inputs: nvDataMgmtExecProcessRecordState * @details Outputs: hasPublishRecordBeenRequested, recordToPublish * @param job type of job that is requested (i.e. get calibration record, ...) * @return TRUE if the request was successfully registered *************************************************************************/ BOOL sendRecordToDialin( RECORD_JOBS_STATE_T job ) { BOOL status = FALSE; // Check if the state machine is in idle state and then set the request if ( NVDATAMGMT_PROCESS_RECORD_STATE_IDLE == nvDataMgmtExecProcessRecordState ) { hasPublishRecordBeenRequested = TRUE; recordToPublish = job; status = TRUE; } return status; } /*********************************************************************//** * @brief * The receiveRecordFromDialin function receives the record that has been sent * from Dialin and if the CRCs passed, it schedules a write to the NV data. * @details Inputs: currentMessage, nvDataMgmtExecReceiveRecordState, * previousCalMessageNum, totalMessages, previousCalMessageNum, * @details Outputs: nvDataMgmtExecReceiveRecordState, recordReceiveStartTime, * previousCalMessageNum, recordUpdateAddress, recordUpdateAddress * @param job: the job that has to be received and written (i.e. service record) * @param currentMessage: current message number that is received from Dialin * @param totalMessages: total number of messages from Dialin * @param length: message length in bytes * @param *addressPtr: address to the beginning of the calibration data from Dialin * @return TRUE if the request was successfully registered *************************************************************************/ BOOL receiveRecordFromDialin( RECORD_JOBS_STATE_T job, U32 currentMessage, U32 totalMessages, U32 length, U08 *addressPtr ) { BOOL status = TRUE; // If the calibration message number is the first message number and receive exec state is idle, switch to idle if ( ( RECORD_DATA_FIRST_RECEIVING_MSG_NUM == currentMessage ) && ( NVDATAMGMT_RECEIVE_RECORD_IDLE == nvDataMgmtExecReceiveRecordState ) ) { nvDataMgmtExecReceiveRecordState = NVDATAMGMT_RECEIVE_RECORD_RECEIVE; recordReceiveStartTime = getMSTimerCount(); previousCalMessageNum = 0; recordUpdateAddress = 0; } // Check if there is still a message left to be received if ( ( NVDATAMGMT_RECEIVE_RECORD_RECEIVE == nvDataMgmtExecReceiveRecordState ) && ( currentMessage <= totalMessages ) ) { // Check if the current message is different from the previous message by 1 if ( RECORD_DATA_MAX_MESSAGE_DFFIRENCE == ( currentMessage - previousCalMessageNum ) ) { // Define a pointer that points to the DG calibration record PROCESS_RECORD_SPECS_T recordSpec = getProcessRecord( job ); U08* ptr = recordSpec.structAddressPtr; // Offset the pointer to length that we should start writing from ptr += recordUpdateAddress; memcpy( ptr, addressPtr, length ); // Check if the current message is total messages // and 0 everything out since we are done writing if ( currentMessage == totalMessages ) { // Assume the institutional record is fine unless the record is really the institutional record BOOL isInstRcrdValid = TRUE; U16 calcCRC = crc16 ( recordSpec.structAddressPtr, recordSpec.sizeofJob - sizeof(U16) ); // Get the CRC of the structure without the last 16 bits which is the CRC as well as the padding values U16 recordCRC = *(U16*)recordSpec.structCRCPtr; if ( ( calcCRC != recordCRC ) || ( FALSE == isInstRcrdValid ) ) { // Institutional record has failed so do not write it into the NV memory and read back what was in the NV memory // CRC failed, request a read to read the data back from NV memory and update the structure enqueueRecordJob( NVDATAMGMT_READ, job ); nvDataMgmtExecReceiveRecordState = NVDATAMGMT_RECEIVE_RECORD_IDLE; status = FALSE; } else { // CRC passed, enqueue an erase, a write of calibration data and a write of service record BOOL scheduleStatus = enqueueSector0Records(); // Signal that there is a new calibration record available. // NOTE: as of now, this signal will be sent even after the system record is sent newCalStartTimer = getMSTimerCount(); isNewCalAvailable = TRUE; // Update the event of the received record that has been accepted SEND_EVENT_WITH_2_U32_DATA( recordSpec.nvEvent, 0, 0 ) // Done with receiving data, go back to idle nvDataMgmtExecReceiveRecordState = NVDATAMGMT_RECEIVE_RECORD_IDLE; } } else { // Update the length as it has successfully been written recordUpdateAddress += length; // Now the current message is the previous message previousCalMessageNum = currentMessage; } } } return status; } /*********************************************************************//** * @brief * The monitorNewCalSignal function monitors the new calibration signal * and if it on and time to on has elapsed, it turns it off. * @details Inputs: isNewCalAvailable * @details Outputs: isNewCalAvailable * @return none *************************************************************************/ static void monitorNewCalSignal( void ) { if ( ( TRUE == didTimeout( newCalStartTimer, NEW_CAL_AVAILABLE_SIGNAL_TIMEOUT_MS ) ) && ( TRUE == isNewCalAvailable ) ) { isNewCalAvailable = FALSE; } } /*********************************************************************//** * @brief * The isNewCalibrationRecordAvailable function returns the status of a * new calibration availability. * @details Inputs: none * @details Outputs: none * @return isNewCalAvailable which is TRUE if new calibration is available, * otherwise FALSE *************************************************************************/ BOOL isNewCalibrationRecordAvailable( void ) { return isNewCalAvailable; } void updateNewCalAvailableFlag( BOOL value ) { isNewCalAvailable = value; } void updateRecordReadStatus( NVDATAMGMT_RECORDS_READ_STATUS_T status ) { recordsReadStatus = status; } void updateCalStartTimer( U32 value ) { newCalStartTimer = value; } PROCESS_RECORD_JOB_T getCurrentProcessRecordJob ( void ) { return recordCurrentJob; } /**@}*/