
/*
 **********************************************************************************************************************
 * Includes
 **********************************************************************************************************************
 */
#include "NvM.h"

#include "NvM_Prv.h"
#include "NvM_Prv_Job.h"
#include "NvM_Prv_Crc.h"
#include "NvM_Prv_BlockData.h"
#include "NvM_Prv_JobResource.h"
#include "NvM_Prv_InternalBuffer.h"
#include "NvM_Prv_ExplicitSynchronization.h"

/*
 **********************************************************************************************************************
 * Declarations
 **********************************************************************************************************************
 */
#define NVM_START_SEC_CODE
#include "NvM_MemMap.h"

static void NvM_Prv_JobWrite_CopyToIntBuf(NvM_Prv_stJob_ten* stJob_pen,
                                          NvM_Prv_JobResult_tst* JobResult_pst,
                                          NvM_Prv_JobData_tst const* JobData_pcst);

static void NvM_Prv_JobWrite_Crc(NvM_Prv_stJob_ten* stJob_pen,
                                 NvM_Prv_JobResult_tst* JobResult_pst,
                                 NvM_Prv_JobData_tst const* JobData_pcst);

static void NvM_Prv_JobWrite_IsWriteRequired(NvM_Prv_stJob_ten* stJob_pen,
                                             NvM_Prv_JobResult_tst* JobResult_pst,
                                             NvM_Prv_JobData_tst const* JobData_pcst);

static void NvM_Prv_JobWrite_DoCrypto(NvM_Prv_stJob_ten* stJob_pen,
                                      NvM_Prv_JobResult_tst* JobResult_pst,
                                      NvM_Prv_JobData_tst const* JobData_pcst);

static void NvM_Prv_JobWrite_DoMemIf(NvM_Prv_stJob_ten* stJob_pen,
                                     NvM_Prv_JobResult_tst* JobResult_pst,
                                     NvM_Prv_JobData_tst const* JobData_pcst);

#define NVM_STOP_SEC_CODE
#include "NvM_MemMap.h"

/*
 **********************************************************************************************************************
 * Code
 **********************************************************************************************************************
 */
#define NVM_START_SEC_CODE
#include "NvM_MemMap.h"

/**
 * \brief
 * This private function returns the pointer to the job step for the current state of the write job.
 * \details
 * This function returns a NULL pointer if passed write job state is invalid. In this case the write job will fail.
 *
 * \param [inout] stJob_en
 * Pointer to the current state of the write job
 *
 * \return
 * Pointer to the job step function or NULL pointer for invalid write job state
 */
NvM_Prv_Job_State_tpfct NvM_Prv_JobWrite_GetStateFct(NvM_Prv_stJob_ten* stJob_pen)
{
    NvM_Prv_Job_State_tpfct JobWrite_State_pfct = NULL_PTR;

    switch (*stJob_pen)
    {
        case NvM_Prv_stJob_Idle_e:
        case NvM_Prv_stJobWrite_CopyToIntBuf_e:
            JobWrite_State_pfct = NvM_Prv_JobWrite_CopyToIntBuf;
            // set job state to ease debugging and to avoid MISRA warning that stJob_pen can be a pointer to const
            *stJob_pen = NvM_Prv_stJobWrite_CopyToIntBuf_e;
        break;

        case NvM_Prv_stJobWrite_Crc_e:
            JobWrite_State_pfct = NvM_Prv_JobWrite_Crc;
        break;

        case NvM_Prv_stJobWrite_IsWriteRequired_e:
            JobWrite_State_pfct = NvM_Prv_JobWrite_IsWriteRequired;
        break;

        case NvM_Prv_stJob_DoCrypto_e:
        case NvM_Prv_stJobWrite_CryptoStartGenerationRandom_e:
        case NvM_Prv_stJobWrite_CryptoStartEncryption_e:
        case NvM_Prv_stJobWrite_CryptoStartGenerationSignature_e:
        case NvM_Prv_stJobWrite_CryptoCrcAppend_e:
        case NvM_Prv_stJobWrite_CryptoPollGenerationRandom_e:
        case NvM_Prv_stJobWrite_CryptoPollEncryption_e:
        case NvM_Prv_stJobWrite_CryptoPollGenerationSignature_e:
            JobWrite_State_pfct = NvM_Prv_JobWrite_DoCrypto;
        break;

        case NvM_Prv_stJob_DoMemIf_e:
        case NvM_Prv_stJob_PollMemIf_e:
            JobWrite_State_pfct = NvM_Prv_JobWrite_DoMemIf;
        break;

        default:
            JobWrite_State_pfct = NULL_PTR;
        break;
    }

    return JobWrite_State_pfct;
}

/**
 * \brief
 * This local private function is a write job step function and copies user data into the internal buffer if required.
 * \details
 * After successful copying the NvM internal buffer contains user data.
 *
 * \param [inout] stJob_pen
 * Pointer to the current job state
 * \param [inout] JobResult_pst
 * Pointer to the job results,  possible values:
 * - NvM_Prv_JobResult_Pending_e   = Copying of user data is still pending -> wait 1 cycle
 * - NvM_Prv_JobResult_Failed_e    = Copying of user data has failed -> finish write job
 * - NvM_Prv_JobResult_Continue_e  = Copying of user data has succeeded -> following states are required:
 *                                   - recalculate CRC if RAM block CRC is configured
 *                                     (CRC will be used to check if write can be omitted)
 *                                   - recalculate CRC if NV CRC is configured
 *                                     (CRC will be written with user data)
 *                                   - recalculate CRC if cryptographic services are configured
 *                                     (CRC will be used for cryptographic services)
 *                                   - intiate MemIf job (no CRC is required)
 * \param [in] JobData_pcst
 * Constant pointer to the job data
 */
static void NvM_Prv_JobWrite_CopyToIntBuf(NvM_Prv_stJob_ten* stJob_pen,
                                          NvM_Prv_JobResult_tst* JobResult_pst,
                                          NvM_Prv_JobData_tst const* JobData_pcst)
{
    uint16 OffsetPlainData_u16 = JobData_pcst->IntBuffer_st.OffsetPlainData_u16;

    JobResult_pst->Result_en = NvM_Prv_JobResult_Succeeded_e;

#if (NVM_RB_REQ_MONITOR == NVM_RB_REQ_MON_WRITE)
    // In case write-monitoring is active and the block to be written is the WriteMon-block itself the counters
    // have to be increased before the CRC is calculated. This can be safely done here as the WriteMon-block is
    // always written.
    // CAUTION: WriteMonitorCounters shall be updated before copying block data into the internal buffer.
    //          Otherwise NvM will copy outdated values into the internal buffer.
    NvM_Prv_WriteMonCntrHandling(JobData_pcst->idBlock_uo,
                                 NvM_Prv_GetBlockSize(JobData_pcst->idBlock_uo),
                                 TRUE);
#endif

    *JobData_pcst->IntBuffer_st.UsedSizeInBytes_pu16 = NvM_Prv_GetBlockSize(JobData_pcst->idBlock_uo);

    if (JobData_pcst->isIntBufferToUse_b)
    {
        // Check whether explicit synchronization is enabled
        // In this case permanent RAM block is used
        if (NvM_Prv_IsBlockSelected(JobData_pcst->idBlock_uo, NVM_PRV_BLOCK_FLAG_USE_SYNC_MECHANISM))
        {
            JobResult_pst->Result_en = NvM_Prv_ExplicitSync_CopyData(NvM_Prv_GetCopyFctForWrite(JobData_pcst->idBlock_uo),
                                                                     JobData_pcst->idBlock_uo,
                                                                     &JobData_pcst->cntrExpSyncOperations_u8,
                                                                     &JobData_pcst->IntBuffer_st.Buffer_pu8[OffsetPlainData_u16]);
        }
        else
        {
            JobResult_pst->Result_en = NvM_Prv_InternalBuffer_CopyData(JobData_pcst->idBlock_uo,
                                                                       JobData_pcst->UserData_un,
                                                                       &JobData_pcst->IntBuffer_st.Buffer_pu8[OffsetPlainData_u16],
                                                                       TRUE);
        }
    }

    if (NvM_Prv_JobResult_Succeeded_e == JobResult_pst->Result_en)
    {
        // Copying data into the internal buffer has succeeded ->

        boolean isNvCrcUsed_b = NvM_Prv_IsBlockSelected(JobData_pcst->idBlock_uo, NVM_PRV_BLOCK_FLAG_USE_NV_CRC);
        boolean isRamCrcUsed_b = NvM_Prv_IsBlockSelected(JobData_pcst->idBlock_uo, NVM_PRV_BLOCK_FLAG_USE_RAM_CRC);
        boolean isCryptoUsed_b = NvM_Prv_IsBlockSelected(JobData_pcst->idBlock_uo, NVM_PRV_BLOCK_FLAG_USE_CRYPTO);

        if (isRamCrcUsed_b || (isNvCrcUsed_b && !isCryptoUsed_b))
        {
            // TRACE[SWS_NvM_00801] Write job for NvM_WritePRAMBlock shall recalculate CRC over the RAM block
            //                      before the copy process if confogured

            // start CRC recalculation in following cases:
            // - NV CRC but no cryptographic services is configured
            // - RAM CRC is configured
            *stJob_pen = NvM_Prv_stJobWrite_Crc_e;
        }
        else if (isCryptoUsed_b)
        {
            // start cryptographic job if no RAM CRC is configured
            *stJob_pen = NvM_Prv_stJob_DoCrypto_e;
        }
        else
        {
            // No RAM CRC and no NV CRC and no Crypto is configured -> write is required -> initiate MemIf job
            *stJob_pen = NvM_Prv_stJob_DoMemIf_e;
        }
    }
    else if (NvM_Prv_JobResult_Failed_e == JobResult_pst->Result_en)
    {
        // Copying data for explicit synchronization has failed ->
        // finish write job, update final job result
        *stJob_pen = NvM_Prv_stJob_Finished_e;
    }
    else
    {
        // Copying data into the internal buffer is still pending ->
        // remain in this state, do not update final job result
    }
}

/**
 * \brief
 * This local private function is a write job step function and recalculates CRC over user data.
 * \details
 * The NvM reaches this job step only if following preconditions are true:
 * - CRC recalculation is requied
 *
 * \param [inout] stJob_pen
 * Pointer to the current job state
 * \param [inout] JobResult_pst
 * Pointer to the job results,  possible values:
 * - NvM_Prv_JobResult_Pending_e  = CRC recalulation is still pending -> wait 1 cycle
 * - NvM_Prv_JobResult_Continue_e = CRC recalulation has succeeded -> check if data write can be skipped
 * \param [in] JobData_pcst
 * Constant pointer to the job data
 */
static void NvM_Prv_JobWrite_Crc(NvM_Prv_stJob_ten* stJob_pen,
                                 NvM_Prv_JobResult_tst* JobResult_pst,
                                 NvM_Prv_JobData_tst const* JobData_pcst)
{
    NvM_Prv_JobResource_DoStateMachine(NvM_Prv_idJobResource_Crc_e, stJob_pen, JobResult_pst, JobData_pcst);
    *stJob_pen = NvM_Prv_stJobWrite_Crc_e;

    if (NvM_Prv_JobResult_Succeeded_e == JobResult_pst->Result_en)
    {
        *stJob_pen = NvM_Prv_stJobWrite_IsWriteRequired_e;
    }
}

/**
 * \brief
 * This local private function is a write job step function and checks if writing user data can be skipped.
 * \details
 * The NvM reaches this job step only if following preconditions are true:
 * - CRC recalculation has succeeded
 *
 * If permanent RAM block is used and user has not updated data in the meantime and CRC compare mechanism is configured
 * then this function checks if recalculated CRC is identical to the current RAM block CRC.
 * If so then current write job can be finished immediately.
 * If not then this function updates RAM block CRC and appends recalculated CRC to the job buffer if configured and
 * starts cryptographic services or initiates a MemIf job due to configuration.
 *
 * \param [inout] stJob_pen
 * Pointer to the current job state
 * \param [inout] JobResult_pst
 * Pointer to the job results,  possible values:
 * - NvM_Prv_JobResult_Continue_e  = write is required -> go to the next state:
 *                                   - handling of cryptograpic services if configured
 *                                   - initiate MemIf job if no cryptographic services are configured
 * - NvM_Prv_JobResult_Succeeded_e = no write is required -> finish job
 * \param [in] JobData_pcst
 * Pointer to the job data
 */
static void NvM_Prv_JobWrite_IsWriteRequired(NvM_Prv_stJob_ten* stJob_pen,
                                             NvM_Prv_JobResult_tst* JobResult_pst,
                                             NvM_Prv_JobData_tst const* JobData_pcst)
{
    boolean isRamCrcNewData_b;
    boolean isBlockWriteRequired_b = TRUE;
    JobResult_pst->Result_en = NvM_Prv_JobResult_Succeeded_e;

    isRamCrcNewData_b = NvM_Prv_Block_IsRamCrcRecalcFinished(JobData_pcst->idBlock_uo);

    if (   NvM_Prv_IsBlockSelected(JobData_pcst->idBlock_uo, NVM_PRV_BLOCK_FLAG_USE_CRC_COMP_MECHANISM)
        && JobData_pcst->isPRamBlockUsed_b
        && !isRamCrcNewData_b
       )
    {
        // If CRC compare mechanism is enabled for a block
        // then NvM can rely that RAM block CRC is enabled for this block too

        // CRC compare mechanism is enabled for current block and user has not updated RAM block data ->
        // block shall be written only if RAM block CRC is different from now calculated CRC
        if (NvM_Prv_Crc_CheckRamBlockCrc(JobData_pcst->idBlock_uo, &JobData_pcst->Crc_un))
        {
            // calculated CRC is equal to RAM block CRC ->
            // NvM can finish write job successfully without writing into the NV memory
            // TRACE[SWS_NvM_00852]
            // TRACE[SWS_NvM_00854] Skip writing because the data has not been changed since last successful
            //                      read or write job
            JobResult_pst->Result_en = NvM_Prv_JobResult_Succeeded_e;
            *stJob_pen = NvM_Prv_stJob_Finished_e;
            isBlockWriteRequired_b = FALSE;
        }
    }

    if (isBlockWriteRequired_b)
    {
        // CRC compare mechanism is disabled OR
        // user has updated RAM block data OR
        // calculated CRC is different from RAM block CRC ->
        // NvM updates RAM block CRC and writes block data into the NV memory
        if (NvM_Prv_IsBlockSelected(JobData_pcst->idBlock_uo, NVM_PRV_BLOCK_FLAG_USE_RAM_CRC))
        {
            NvM_Prv_Crc_SetRamBlockCrc(JobData_pcst->idBlock_uo, &JobData_pcst->Crc_un);
        }

        if (NvM_Prv_IsBlockSelected(JobData_pcst->idBlock_uo, NVM_PRV_BLOCK_FLAG_USE_CRYPTO))
        {
            *stJob_pen = NvM_Prv_stJob_DoCrypto_e;
        }
        else
        {
            if (NvM_Prv_IsBlockSelected(JobData_pcst->idBlock_uo, NVM_PRV_BLOCK_FLAG_USE_NV_CRC))
            {
                // Append the Crc to the user data
                NvM_Prv_Crc_AppendCrc(JobData_pcst->idBlock_uo,
                                      JobData_pcst->IntBuffer_st.Buffer_pu8,
                                      JobData_pcst->IntBuffer_st.UsedSizeInBytes_pu16,
                                      &JobData_pcst->Crc_un);
            }
            *stJob_pen = NvM_Prv_stJob_DoMemIf_e;
        }
    }
}

/**
 * \brief
 * This local private function is a write job step function and handles cryptographic services.
 * \details
 * The NvM reaches this job step only if following preconditions are true:
 * - CRC recalculation has succeeded
 * - write is required
 * - cryptographic services are configured
 *
 * This function executes the state machine for cryptographic write job
 * (see NvM_Prv_Crypto_DoStateMachine and NvM_Prv_Crypto_GetJobStepWrite).
 *
 * \param [inout] stJob_pen
 * Pointer to the current job state
 * \param [inout] JobResult_pst
 * Pointer to the job results,  possible values:
 * - NvM_Prv_JobResult_Pending_e   = Cryptographic services are still pending -> wait 1 cycle
 * - NvM_Prv_JobResult_Failed_e    = Cryptographic services has failed -> finish job
 * - NvM_Prv_JobResult_Succeeded_e = Cryptographic services has succeeded -> initiate MemIf job
 * \param [in] JobData_pcst
 * Constant pointer to the job data
 */
static void NvM_Prv_JobWrite_DoCrypto(NvM_Prv_stJob_ten* stJob_pen,
                                      NvM_Prv_JobResult_tst* JobResult_pst,
                                      NvM_Prv_JobData_tst const* JobData_pcst)
{
    NvM_Prv_JobResource_DoStateMachine(NvM_Prv_idJobResource_Crypto_e, stJob_pen, JobResult_pst, JobData_pcst);

    if (NvM_Prv_JobResult_Succeeded_e == JobResult_pst->Result_en)
    {
        *stJob_pen = NvM_Prv_stJob_DoMemIf_e;
    }
    else if (NvM_Prv_JobResult_Failed_e == JobResult_pst->Result_en)
    {
        // Cryptographic services for write job have failed ->
        // finish write job, update final job result
        *stJob_pen = NvM_Prv_stJob_Finished_e;
    }
    else
    {
        // Cryptographic services for write job are still pending ->
        // remain in this state, do not update final job result
    }
}

/**
 * \brief
 * This local private function is a write job step function and polls the MemIf to complete the write job.
 * \details
 * The NvM reaches this job step only if following preconditions are true:
 * - CRC recalculation has succeeded
 * - write is required
 * - cryptographic services has succeeded
 * - job buffer contains data to be written
 * - MemIf write job initiation has succeeded
 *
 * \param [inout] stJob_pen
 * State of the read job
 * \param [inout] JobResult_pst
 * Pointer to the job results,  possible values:
 * - NvM_Prv_JobResult_Pending_e           = MemIf job is still pending -> wait 1 cycle
 * - NvM_Prv_JobResult_Succeeded_e         = MemIf job has written data to the medium -> finish job
 * - NvM_Prv_JobResult_Failed_e            = MemIf write job has failed -> finish job
 * \param [in] JobData_pcst
 * Pointer to the data of the current NvM job
 */
static void NvM_Prv_JobWrite_DoMemIf(NvM_Prv_stJob_ten* stJob_pen,
                                     NvM_Prv_JobResult_tst* JobResult_pst,
                                     NvM_Prv_JobData_tst const* JobData_pcst)
{
    NvM_Prv_JobResource_DoStateMachine(NvM_Prv_idJobResource_MemIf_e, stJob_pen, JobResult_pst, JobData_pcst);

    if (NvM_Prv_JobResult_Succeeded_e == JobResult_pst->Result_en)
    {
        // MemIf job has succeeded -> finish write job
        *stJob_pen = NvM_Prv_stJob_Finished_e;
    }
    else if (NvM_Prv_JobResult_Pending_e == JobResult_pst->Result_en)
    {
        // MemIf job is still pending -> remain in this state, do not update final job result
    }
    else
    {
        // MemIf job has failed -> finish write job, update final job result
        JobResult_pst->Result_en = NvM_Prv_JobResult_Failed_e;
        *stJob_pen = NvM_Prv_stJob_Finished_e;
    }
}

#define NVM_STOP_SEC_CODE
#include "NvM_MemMap.h"

