/*
* This source file is part of the EtherCAT Slave Stack Code licensed by Beckhoff Automation GmbH & Co KG, 33415 Verl, Germany.
* The corresponding license agreement applies. This hint shall not be removed.
* https://www.beckhoff.com/media/downloads/slave-stack-code/ethercat_ssc_license.pdf
*/

/**
\addtogroup FoE FileTransfer over EtherCAT
@{
*/

/**
\file ecatfoe.c
\author EthercatSSC@beckhoff.com
\brief Implementation
This file contains the FoE mailbox interface

\version 5.13

<br>Changes to version V5.12:<br>
V5.13 EOE1: <br>
V5.13 FOE1: handle failure on continue indication<br>
<br>Changes to version V5.11:<br>
V5.12 FOE2: handle 16bit only access<br>
V5.12 MBX4: in case of a disable mailbox queue and two consecutive foe uploads the mailbox receive handler is blocked<br>
<br>Changes to version V5.10:<br>
V5.11 FOE1: handle busy on a read request, change OPMode to OPCode (same terms as in the spec)<br>
<br>Changes to version V5.0:<br>
V5.10 MBX1: Remove multiple swapping of length filed in mailbox header<br>
<br>Changes to version V4.40:<br>
V5.0 FOE1: Free memory if FoE_ServciceInd() is left.<br>
V5.0 FOE2: Update error code (Bit 15 shall not be set).<br>
V5.0 FOE3: Update byte swapping for big endian and big endian 16 configurations.<br>
V5.0 FOE4: Update Error text handling for 16Bit operations.<br>
<br>Changes to version V4.11:<br>
V4.40 FOE1: correct FoE busy values<br>
V4.40 MBX2: HW_CopyToSendMailbox merge to a generic function (new: MBX_CopyToSendMailbox)<br>
<br>Changes to version V4.10:<br>
V4.11 FOE 1 fixed a problem with a huge FOE_MADATA define<br>
V4.11 FOE 2 u16PacketNo is now a 32bit variable (u32PacketNo) to conform with the spec<br>
<br>Changes to version V4.00:<br>
V4.10 FOE 1-3 when downloading the firmware the FoE state machine was not set to FOE_READY<br>
              when the last data segment was downloaded so that the next FoE service failed<br>
<br>Changes to version V3.20:<br>
V4.00 FOE 1 - if a FoE Error service is received it should not be acknowledged<br>
V4.00 FOE 2 - if a FoE service response could not be sent because the mailbox is full<br>
              it has to be re-transmit otherwise the response get lost<br>

*/

/*-----------------------------------------------------------------------------------------
------
------    Includes
------
-----------------------------------------------------------------------------------------*/

#include "ecat_def.h"



#include "ecatslv.h"

#define    _ECATFOE_ 1
#include "ecatfoe.h"
#undef    _ECATFOE_
#define    _ECATFOE_ 0

#include "foeappl.h"
/*-----------------------------------------------------------------------------------------
------
------    functions
------
-----------------------------------------------------------------------------------------*/

/////////////////////////////////////////////////////////////////////////////////////////
/**

 \brief    This function initialize the FoE Interface.
*////////////////////////////////////////////////////////////////////////////////////////

void FOE_Init(void)
{
    /* no file transmission sequence is running */
    u16FileAccessState    = FOE_READY;
    /* initialize the expected packet number */
    u32PacketNo = 0;
    pFoeSendStored = NULL;
}


/////////////////////////////////////////////////////////////////////////////////////////
/**
 \param     pFoeInd      Pointer to the received mailbox data from the master.

 \return    result of the operation (0 (success) or mailbox error code (MBXERR_.... defined in
            mailbox.h))

 \brief    This function is called when a FoE (File Access over EtherCAT) service is received from
             the master.
*////////////////////////////////////////////////////////////////////////////////////////

UINT8 FOE_ServiceInd(TFOEMBX MBXMEM * pFoeInd)
{
    /* initialize the result of the service checking */
    UINT16 nextState = ECAT_FOE_ERRCODE_ILLEGAL;
    /* dataSize contains the size of the file data */
    UINT16 dataSize = SWAPWORD(pFoeInd->MbxHeader.Length) - FOE_HEADER_SIZE;


    /* it has to be checked if the mailbox protocol is correct, the sent mailbox data length has to
       great enough for the service header of the FoE service */
    if ( SWAPWORD(pFoeInd->MbxHeader.Length) < FOE_HEADER_SIZE )
    {
        return MBXERR_SIZETOOSHORT;
    }

    switch ( SWAPWORD(pFoeInd->FoeHeader.OpCode) )
    {
    case ECAT_FOE_OPCODE_RRQ:
        /* file read is requested */
        if ( u16FileAccessState == FOE_READY )
        {
            UINT32 u32Password = SWAPDWORD((((UINT32)pFoeInd->FoeHeader.Cmd.Password[FOE_COMMAND_LOWWORD]) | ((UINT32)pFoeInd->FoeHeader.Cmd.Password[FOE_COMMAND_HIGHWORD]) << 16));
            /* last FoE sequence was finished, call application function */
            nextState = FOE_Read((UINT16 MBXMEM *)pFoeInd->Data, dataSize, (UINT16*)pFoeInd->Data, u32Password);

            /* u32LastFileOffset contains the offset of the file which is sent now */
            u32LastFileOffset = 0;

            if (nextState <= FOE_MAXDATA)
            {
                /* the first data packet shall be sent */
                u32PacketNo    = 1;
                /* u32FileOffset contains the offset of the file which shall be sent when the next FoE ACK is received */
                u32FileOffset = nextState;
            }
            else if (nextState <= FOE_MAXBUSY)
            {
                u32PacketNo    = 0;
                u32FileOffset = 0;
                u16FileAccessState = FOE_WAIT_FOR_ACK;
            }
        }
        break;

    case ECAT_FOE_OPCODE_WRQ:
        /* file write is requested */
        if ( u16FileAccessState == FOE_READY )
        {
            UINT32 u32Password = SWAPDWORD((((UINT32)pFoeInd->FoeHeader.Cmd.Password[FOE_COMMAND_LOWWORD]) | ((UINT32)pFoeInd->FoeHeader.Cmd.Password[FOE_COMMAND_HIGHWORD]) << 16));
            /* last FoE sequence was finished, call application function */
            nextState = FOE_Write((UINT16 MBXMEM *)pFoeInd->Data, dataSize, u32Password);
            if ( nextState == 0 )
            {
                /* checking was successful, sent a FoE Ack service */
                nextState = FOE_ACK;
            }
            /* initialize the packet number */
            u32PacketNo    = 0;
        }
        break;

    case ECAT_FOE_OPCODE_DATA:
        /* file data is received */
        if ( u16FileAccessState == FOE_WAIT_FOR_DATA
          || u16FileAccessState == FOE_WAIT_FOR_LAST_DATA )
        {
            UINT32 u32CmdPacketNo = SWAPDWORD((((UINT32)pFoeInd->FoeHeader.Cmd.PacketNo[FOE_COMMAND_LOWWORD]) | ((UINT32)pFoeInd->FoeHeader.Cmd.PacketNo[FOE_COMMAND_HIGHWORD]) << 16));

/*ET9300 Project Handler :(#if BIG_ENDIAN_16BIT) lines 170 to 172 deleted*/
            /* we are waiting for file data, service is correct */
            if ( u32CmdPacketNo == u32PacketNo )
            {
                /* the packet number is correct, call application function to store the file data */
                nextState = FOE_Data((UINT16 MBXMEM *)pFoeInd->Data, dataSize);
                if ( nextState == 0 )
                {
                    /* checking was successful, sent a FoE Ack service */
                    nextState = FOE_ACK;
                }
            }
            else
            {
                nextState = ECAT_FOE_ERRCODE_PACKENO;
            }
        }
        break;

    case ECAT_FOE_OPCODE_ACK:
        /* acknowledge is received, next file part can be sent */
        if ( u16FileAccessState == FOE_WAIT_FOR_ACK )
        {
            /* we are waiting for an acknowledge, service is correct, call the application function
               to get the next part of the file */
            nextState = FOE_Ack( u32FileOffset, (UINT16 MBXMEM *)pFoeInd->Data );
            /* u32LastFileOffset contains the offset of the file which is sent now */
            u32LastFileOffset = u32FileOffset;

            if (nextState <= FOE_MAXDATA) 
            {
                /* u32FileOffset contains the offset of the file which shall be sent when the next FoE ACK is received */
                u32FileOffset += nextState;
                /* increment the packet number */
                u32PacketNo++;
            }
        }
        else if ( u16FileAccessState == FOE_WAIT_FOR_LAST_ACK )
        {
            /* we were waiting for the last acknowledge, now the sequence is finished */
            nextState = FOE_FINISHED_NOACK;
        }
        break;

    case ECAT_FOE_OPCODE_ERR:
        /* a FoE Error service is received */
        if ( u16FileAccessState != FOE_READY )
        {
            UINT32 u32CmdErrorCode = SWAPDWORD((((UINT32)pFoeInd->FoeHeader.Cmd.ErrorCode[FOE_COMMAND_LOWWORD]) | ((UINT32)pFoeInd->FoeHeader.Cmd.ErrorCode[FOE_COMMAND_HIGHWORD]) << 16));

            /* a file transmission sequence is active, inform the application, that this sequence
               was stopped */
            FOE_Error( u32CmdErrorCode );

            nextState = FOE_FINISHED;
        }
        break;

    case ECAT_FOE_OPCODE_BUSY:
        /* a FoE Busy service is received */
        if ( u16FileAccessState == FOE_WAIT_FOR_ACK
          || u16FileAccessState == FOE_WAIT_FOR_LAST_ACK )
        {
            /* we are waiting for an acknowledge, service is correct, call the application function
               to resend the last part of the file */
            nextState = FOE_Busy( SWAPWORD(pFoeInd->FoeHeader.Cmd.Busy.Done), u32LastFileOffset, (UINT16 MBXMEM *)pFoeInd->Data );
        }
        break;

    }

    if ( nextState <= FOE_MAXDATA )
    {
        /* we send DATA and wait for ACK */
        UINT32 d = SWAPDWORD(u32PacketNo);

        /* store the OpCode in the mailbox buffer */
        pFoeInd->FoeHeader.OpCode           = SWAPWORD(ECAT_FOE_OPCODE_DATA);
        /* store the packet number in the mailbox buffer */
        pFoeInd->FoeHeader.Cmd.PacketNo[FOE_COMMAND_LOWWORD] = ((d & 0x0000FFFF));
        pFoeInd->FoeHeader.Cmd.PacketNo[FOE_COMMAND_HIGHWORD] = ((d & 0xFFFF0000) >> 16);
     
        /* store the size of the mailbox data in the mailbox buffer */
        pFoeInd->MbxHeader.Length           = FOE_HEADER_SIZE + nextState;

        if ( nextState == u16SendMbxSize - FOE_HEADER_SIZE - MBX_HEADER_SIZE )
        {
            /* packets still following, we wait for an ACK */
            u16FileAccessState = FOE_WAIT_FOR_ACK;
        }
        else
        {
            /* it was the last Packet, we wait for the last ACK */
            u16FileAccessState = FOE_WAIT_FOR_LAST_ACK;
        }
    }
    else if ( nextState <= FOE_MAXBUSY )
    {
        /* we are still storing the received file data (in flash for example) and
           send BUSY and wait for the DATA to be sent again */
        /* store the OpCode in the mailbox buffer */
        pFoeInd->FoeHeader.OpCode                   = SWAPWORD(ECAT_FOE_OPCODE_BUSY);
        /* store the information how much progress we made until we can receive file data again */
        pFoeInd->FoeHeader.Cmd.Busy.Done            = SWAPWORD(nextState-FOE_MAXBUSY_ZERO);

        pFoeInd->FoeHeader.Cmd.Busy.Entire          = 0;
        /* store the size of the mailbox data in the mailbox buffer */

        pFoeInd->MbxHeader.Length                   = FOE_HEADER_SIZE;
    }
    else if ( nextState == FOE_ACK || nextState == FOE_ACKFINISHED )
    {
        /* we send ACK and wait for DATA
            the next file data is expected with an incremented packet number, but
            we have to acknowledge the old packet first */
        UINT32 d = SWAPDWORD(u32PacketNo);
        u32PacketNo++;
        
        /* store the OpCode in the mailbox buffer */
        pFoeInd->FoeHeader.OpCode                   = SWAPWORD(ECAT_FOE_OPCODE_ACK);
        /* store the packet number in the mailbox buffer */
        pFoeInd->FoeHeader.Cmd.PacketNo[FOE_COMMAND_LOWWORD] = ((d & 0x0000FFFF));
        pFoeInd->FoeHeader.Cmd.PacketNo[FOE_COMMAND_HIGHWORD] = ((d & 0xFFFF0000) >> 16);

        /* store the size of the mailbox data in the mailbox buffer */
        pFoeInd->MbxHeader.Length                   = SIZEOF(TFOEHEADER);

        /* we wait for the next data part */
        if ( nextState == FOE_ACK )
        {
            /* we wait for the next data part */
            u16FileAccessState = FOE_WAIT_FOR_DATA;
        }
        else
        {
            /* the last data part was received */
            u16FileAccessState = FOE_READY;
        }
    }
    else if ( nextState < FOE_ERROR )
    {
        /* the file transmission sequence is finished, we have to send nothing */
        u16FileAccessState = FOE_READY;
        APPL_FreeMailboxBuffer(pFoeInd);
/*ET9300 Project Handler :(#if MAILBOX_QUEUE #else) lines 317 to 320 deleted*/
        return 0;
    }
    else
    {
        UINT32 d = SWAPDWORD(nextState);
        UINT8 b = 0;

        /* store the OpCode in the mailbox buffer */
        pFoeInd->FoeHeader.OpCode                    = SWAPWORD(ECAT_FOE_OPCODE_ERR);
        /* store the ErrorCode in the mailbox buffer */
        pFoeInd->FoeHeader.Cmd.ErrorCode[FOE_COMMAND_LOWWORD] = ((d & 0x0000FFFF));
        pFoeInd->FoeHeader.Cmd.ErrorCode[FOE_COMMAND_HIGHWORD] = ((d & 0xFFFF0000) >> 16);

        /* store the size of the mailbox data in the mailbox buffer */
        pFoeInd->MbxHeader.Length                   = SIZEOF(TFOEHEADER);

        /* Error Text is returned in pFoeInd->Data */
        while (b < 32)
        {
            UINT16 data = ((UINT16 MBXMEM *) pFoeInd->Data)[(b >> 1)];

            if ((data & 0x00FF) == 0)
            {
                break;
            }
            b++;
        
            if ((data & 0xFF00) == 0)
            {
                break;
            }
            b++;
        }
        if ( b < 32 )
        {
            pFoeInd->MbxHeader.Length    += b+1;
        }

        /* the file transmission sequence is finished */
        u16FileAccessState = FOE_READY;
    }

/*ET9300 Project Handler :(#if BOOTSTRAPMODE_SUPPORTED) lines 363 to 385 deleted*/
    {
        if ( MBX_MailboxSendReq((TMBX MBXMEM *) pFoeInd, FOE_SERVICE) != 0 )
        {
            /* if the mailbox service could not be sent (or stored), the response will be
               stored in the variable pFoeSendStored and will be sent automatically
                from the mailbox handler (FOE_ContinueInd) when the send mailbox will be read
                the next time from the master */
            pFoeSendStored = (TMBX MBXMEM *) pFoeInd;
        }
    }

    return 0;
}


/////////////////////////////////////////////////////////////////////////////////////////
/**
 \param     pMbx      Pointer to the free mailbox buffer

 \brief    This function is called when the next mailbox fragment can be sent.
*////////////////////////////////////////////////////////////////////////////////////////

void FOE_ContinueInd(TMBX MBXMEM * pMbx)
{
    if ( pFoeSendStored )
    {
        /* send the stored FoE service which could not be sent before */
/*ECATCHANGE_START(V5.13) FOE1*/
        if (MBX_MailboxSendReq(pFoeSendStored, FOE_SERVICE) == 0)
        {
            pFoeSendStored = 0;
        }
    }
/*ECATCHANGE_END(V5.13) EOE1*/
}

/** @} */

