/*
 * Licensed under the GNU General Public License version 2 with exceptions. See
 * LICENSE file in the project root for full license information
 */

/** \file
 * \brief
 * Servo over EtherCAT (SoE) Module.
 */

#include <stdio.h>
#include <string.h>
#include "osal.h"
#include "oshw.h"
#include "ethercattype.h"
#include "ethercatbase.h"
#include "ethercatmain.h"
#include "ethercatsoe.h"

#define EC_SOE_MAX_DRIVES 8

/** SoE (Servo over EtherCAT) mailbox structure */
PACKED_BEGIN
typedef struct PACKED
{
   ec_mbxheadert MbxHeader;
   uint8         opCode         :3;
   uint8         incomplete     :1;
   uint8         error          :1;
   uint8         driveNo        :3;
   uint8         elementflags;
   union
   {
      uint16     idn;
      uint16     fragmentsleft;
   };
} ec_SoEt;
PACKED_END

/** Report SoE error.
 *
 * @param[in]  context        = context struct
 * @param[in]  Slave      = Slave number
 * @param[in]  idn        = IDN that generated error
 * @param[in]  Error      = Error code, see EtherCAT documentation for list
 */
void ecx_SoEerror(ecx_contextt *context, uint16 Slave, uint16 idn, uint16 Error)
{
   ec_errort Ec;

   memset(&Ec, 0, sizeof(Ec));
   Ec.Time = osal_current_time();
   Ec.Slave = Slave;
   Ec.Index = idn;
   Ec.SubIdx = 0;
   *(context->ecaterror) = TRUE;
   Ec.Etype = EC_ERR_TYPE_SOE_ERROR;
   Ec.ErrorCode = Error;
   ecx_pusherror(context, &Ec);
}

/** SoE read, blocking.
 *
 * The IDN object of the selected slave and DriveNo is read. If a response
 * is larger than the mailbox size then the response is segmented. The function
 * will combine all segments and copy them to the parameter buffer.
 *
 * @param[in]  context        = context struct
 * @param[in]  slave         = Slave number
 * @param[in]  driveNo       = Drive number in slave
 * @param[in]  elementflags  = Flags to select what properties of IDN are to be transferred.
 * @param[in]  idn           = IDN.
 * @param[in,out] psize      = Size in bytes of parameter buffer, returns bytes read from SoE.
 * @param[out] p             = Pointer to parameter buffer
 * @param[in]  timeout       = Timeout in us, standard is EC_TIMEOUTRXM
 * @return Workcounter from last slave response
 */
int ecx_SoEread(ecx_contextt *context, uint16 slave, uint8 driveNo, uint8 elementflags, uint16 idn, int *psize, void *p, int timeout)
{
   ec_SoEt *SoEp, *aSoEp;
   int totalsize, framedatasize;
   int wkc;
   uint8 *bp;
   uint8 *mp;
   uint16 *errorcode;
   ec_mbxbuft MbxIn, MbxOut;
   uint8 cnt;
   boolean NotLast;

   ec_clearmbx(&MbxIn);
   /* Empty slave out mailbox if something is in. Timeout set to 0 */
   wkc = ecx_mbxreceive(context, slave, (ec_mbxbuft *)&MbxIn, 0);
   ec_clearmbx(&MbxOut);
   aSoEp = (ec_SoEt *)&MbxIn;
   SoEp = (ec_SoEt *)&MbxOut;
   SoEp->MbxHeader.length = htoes(sizeof(ec_SoEt) - sizeof(ec_mbxheadert));
   SoEp->MbxHeader.address = htoes(0x0000);
   SoEp->MbxHeader.priority = 0x00;
   /* get new mailbox count value, used as session handle */
   cnt = ec_nextmbxcnt(context->slavelist[slave].mbx_cnt);
   context->slavelist[slave].mbx_cnt = cnt;
   SoEp->MbxHeader.mbxtype = ECT_MBXT_SOE + MBX_HDR_SET_CNT(cnt); /* SoE */
   SoEp->opCode = ECT_SOE_READREQ;
   SoEp->incomplete = 0;
   SoEp->error = 0;
   SoEp->driveNo = driveNo;
   SoEp->elementflags = elementflags;
   SoEp->idn = htoes(idn);
   totalsize = 0;
   bp = p;
   mp = (uint8 *)&MbxIn + sizeof(ec_SoEt);
   NotLast = TRUE;
   /* send SoE request to slave */
   wkc = ecx_mbxsend(context, slave, (ec_mbxbuft *)&MbxOut, EC_TIMEOUTTXM);
   if (wkc > 0) /* succeeded to place mailbox in slave ? */
   {
      while (NotLast)
      {
         /* clean mailboxbuffer */
         ec_clearmbx(&MbxIn);
         /* read slave response */
         wkc = ecx_mbxreceive(context, slave, (ec_mbxbuft *)&MbxIn, timeout);
         if (wkc > 0) /* succeeded to read slave response ? */
         {
            /* slave response should be SoE, ReadRes */
            if (((aSoEp->MbxHeader.mbxtype & 0x0f) == ECT_MBXT_SOE) &&
                (aSoEp->opCode == ECT_SOE_READRES) &&
                (aSoEp->error == 0) &&
                (aSoEp->driveNo == driveNo) &&
                (aSoEp->elementflags == elementflags))
            {
               framedatasize = etohs(aSoEp->MbxHeader.length) - sizeof(ec_SoEt)  + sizeof(ec_mbxheadert);
               totalsize += framedatasize;
               /* Does parameter fit in parameter buffer ? */
               if (totalsize <= *psize)
               {
                  /* copy parameter data in parameter buffer */
                  memcpy(bp, mp, framedatasize);
                  /* increment buffer pointer */
                  bp += framedatasize;
               }
               else
               {
                  framedatasize -= totalsize - *psize;
                  totalsize = *psize;
                  /* copy parameter data in parameter buffer */
                  if (framedatasize > 0) memcpy(bp, mp, framedatasize);
               }

               if (!aSoEp->incomplete)
               {
                  NotLast = FALSE;
                  *psize = totalsize;
               }
            }
            /* other slave response */
            else
            {
               NotLast = FALSE;
               if (((aSoEp->MbxHeader.mbxtype & 0x0f) == ECT_MBXT_SOE) &&
                   (aSoEp->opCode == ECT_SOE_READRES) &&
                   (aSoEp->error == 1))
               {
                  mp = (uint8 *)&MbxIn + (etohs(aSoEp->MbxHeader.length) + sizeof(ec_mbxheadert) - sizeof(uint16));
                  errorcode = (uint16 *)mp;
                  ecx_SoEerror(context, slave, idn, *errorcode);
               }
               else
               {
                  ecx_packeterror(context, slave, idn, 0, 1); /* Unexpected frame returned */
               }
               wkc = 0;
            }
         }
         else
         {
            NotLast = FALSE;
            ecx_packeterror(context, slave, idn, 0, 4); /* no response */
         }
      }
   }
   return wkc;
}

/** SoE write, blocking.
 *
 * The IDN object of the selected slave and DriveNo is written. If a response
 * is larger than the mailbox size then the response is segmented.
 *
 * @param[in]  context        = context struct
 * @param[in]  slave         = Slave number
 * @param[in]  driveNo       = Drive number in slave
 * @param[in]  elementflags  = Flags to select what properties of IDN are to be transferred.
 * @param[in]  idn           = IDN.
 * @param[in]  psize         = Size in bytes of parameter buffer.
 * @param[out] p             = Pointer to parameter buffer
 * @param[in]  timeout       = Timeout in us, standard is EC_TIMEOUTRXM
 * @return Workcounter from last slave response
 */
int ecx_SoEwrite(ecx_contextt *context, uint16 slave, uint8 driveNo, uint8 elementflags, uint16 idn, int psize, void *p, int timeout)
{
   ec_SoEt *SoEp, *aSoEp;
   int framedatasize, maxdata;
   int wkc;
   uint8 *mp;
   uint8 *hp;
   uint16 *errorcode;
   ec_mbxbuft MbxIn, MbxOut;
   uint8 cnt;
   boolean NotLast;

   ec_clearmbx(&MbxIn);
   /* Empty slave out mailbox if something is in. Timeout set to 0 */
   wkc = ecx_mbxreceive(context, slave, (ec_mbxbuft *)&MbxIn, 0);
   ec_clearmbx(&MbxOut);
   aSoEp = (ec_SoEt *)&MbxIn;
   SoEp = (ec_SoEt *)&MbxOut;
   SoEp->MbxHeader.address = htoes(0x0000);
   SoEp->MbxHeader.priority = 0x00;
   SoEp->opCode = ECT_SOE_WRITEREQ;
   SoEp->error = 0;
   SoEp->driveNo = driveNo;
   SoEp->elementflags = elementflags;
   hp = p;
   mp = (uint8 *)&MbxOut + sizeof(ec_SoEt);
   maxdata = context->slavelist[slave].mbx_l - sizeof(ec_SoEt);
   NotLast = TRUE;
   while (NotLast)
   {
      framedatasize = psize;
      NotLast = FALSE;
      SoEp->idn = htoes(idn);
      SoEp->incomplete = 0;
      if (framedatasize > maxdata)
      {
         framedatasize = maxdata;  /*  segmented transfer needed  */
         NotLast = TRUE;
         SoEp->incomplete = 1;
         SoEp->fragmentsleft = (uint16)(psize / maxdata);
      }
      SoEp->MbxHeader.length = htoes((uint16)(sizeof(ec_SoEt) - sizeof(ec_mbxheadert) + framedatasize));
      /* get new mailbox counter, used for session handle */
      cnt = ec_nextmbxcnt(context->slavelist[slave].mbx_cnt);
      context->slavelist[slave].mbx_cnt = cnt;
      SoEp->MbxHeader.mbxtype = ECT_MBXT_SOE + MBX_HDR_SET_CNT(cnt); /* SoE */
      /* copy parameter data to mailbox */
      memcpy(mp, hp, framedatasize);
      hp += framedatasize;
      psize -= framedatasize;
      /* send SoE request to slave */
      wkc = ecx_mbxsend(context, slave, (ec_mbxbuft *)&MbxOut, EC_TIMEOUTTXM);
      if (wkc > 0) /* succeeded to place mailbox in slave ? */
      {
         if (!NotLast || !ecx_mbxempty(context, slave, timeout))
         {
            /* clean mailboxbuffer */
            ec_clearmbx(&MbxIn);
            /* read slave response */
            wkc = ecx_mbxreceive(context, slave, (ec_mbxbuft *)&MbxIn, timeout);
            if (wkc > 0) /* succeeded to read slave response ? */
            {
               NotLast = FALSE;
               /* slave response should be SoE, WriteRes */
               if (((aSoEp->MbxHeader.mbxtype & 0x0f) == ECT_MBXT_SOE) &&
                   (aSoEp->opCode == ECT_SOE_WRITERES) &&
                   (aSoEp->error == 0) &&
                   (aSoEp->driveNo == driveNo) &&
                   (aSoEp->elementflags == elementflags))
               {
                  /* SoE write succeeded */
               }
               /* other slave response */
               else
               {
                  if (((aSoEp->MbxHeader.mbxtype & 0x0f) == ECT_MBXT_SOE) &&
                      (aSoEp->opCode == ECT_SOE_READRES) &&
                      (aSoEp->error == 1))
                  {
                     mp = (uint8 *)&MbxIn + (etohs(aSoEp->MbxHeader.length) + sizeof(ec_mbxheadert) - sizeof(uint16));
                     errorcode = (uint16 *)mp;
                     ecx_SoEerror(context, slave, idn, *errorcode);
                  }
                  else
                  {
                     ecx_packeterror(context, slave, idn, 0, 1); /* Unexpected frame returned */
                  }
                  wkc = 0;
               }
            }
            else
            {
               ecx_packeterror(context, slave, idn, 0, 4); /* no response */
            }
         }
      }
   }
   return wkc;
}

/** SoE read AT and MTD mapping.
 *
 * SoE has standard indexes defined for mapping. This function
 * tries to read them and collect a full input and output mapping size
 * of designated slave.
 *
 * @param[in]  context = context struct
 * @param[in]  slave   = Slave number
 * @param[out] Osize   = Size in bits of output mapping (MTD) found
 * @param[out] Isize   = Size in bits of input mapping (AT) found
 * @return >0 if mapping successful.
 */
int ecx_readIDNmap(ecx_contextt *context, uint16 slave, uint32 *Osize, uint32 *Isize)
{
   int retVal = 0;
   int   wkc;
   int psize;
   uint8 driveNr;
   uint16 entries, itemcount;
   ec_SoEmappingt     SoEmapping;
   ec_SoEattributet   SoEattribute;

   *Isize = 0;
   *Osize = 0;
   for(driveNr = 0; driveNr < EC_SOE_MAX_DRIVES; driveNr++)
   {
      psize = sizeof(SoEmapping);
      /* read output mapping via SoE */
      wkc = ecx_SoEread(context, slave, driveNr, EC_SOE_VALUE_B, EC_IDN_MDTCONFIG, &psize, &SoEmapping, EC_TIMEOUTRXM);
      if ((wkc > 0) && (psize >= 4) && ((entries = etohs(SoEmapping.currentlength) / 2) > 0) && (entries <= EC_SOE_MAXMAPPING))
      {
         /* command word (uint16) is always mapped but not in list */
         *Osize += 16;
         for (itemcount = 0 ; itemcount < entries ; itemcount++)
         {
            psize = sizeof(SoEattribute);
            /* read attribute of each IDN in mapping list */
            wkc = ecx_SoEread(context, slave, driveNr, EC_SOE_ATTRIBUTE_B, SoEmapping.idn[itemcount], &psize, &SoEattribute, EC_TIMEOUTRXM);
            if ((wkc > 0) && (!SoEattribute.list))
            {
               /* length : 0 = 8bit, 1 = 16bit .... */
               *Osize += (int)8 << SoEattribute.length;
            }
         }
      }
      psize = sizeof(SoEmapping);
      /* read input mapping via SoE */
      wkc = ecx_SoEread(context, slave, driveNr, EC_SOE_VALUE_B, EC_IDN_ATCONFIG, &psize, &SoEmapping, EC_TIMEOUTRXM);
      if ((wkc > 0) && (psize >= 4) && ((entries = etohs(SoEmapping.currentlength) / 2) > 0) && (entries <= EC_SOE_MAXMAPPING))
      {
         /* status word (uint16) is always mapped but not in list */
         *Isize += 16;
         for (itemcount = 0 ; itemcount < entries ; itemcount++)
         {
            psize = sizeof(SoEattribute);
            /* read attribute of each IDN in mapping list */
            wkc = ecx_SoEread(context, slave, driveNr, EC_SOE_ATTRIBUTE_B, SoEmapping.idn[itemcount], &psize, &SoEattribute, EC_TIMEOUTRXM);
            if ((wkc > 0) && (!SoEattribute.list))
            {
               /* length : 0 = 8bit, 1 = 16bit .... */
               *Isize += (int)8 << SoEattribute.length;
            }
         }
      }
   }

   /* found some I/O bits ? */
   if ((*Isize > 0) || (*Osize > 0))
   {
      retVal = 1;
   }
   return retVal;
}

#ifdef EC_VER1
int ec_SoEread(uint16 slave, uint8 driveNo, uint8 elementflags, uint16 idn, int *psize, void *p, int timeout)
{
   return ecx_SoEread(&ecx_context, slave, driveNo, elementflags, idn, psize, p, timeout);
}

int ec_SoEwrite(uint16 slave, uint8 driveNo, uint8 elementflags, uint16 idn, int psize, void *p, int timeout)
{
   return ecx_SoEwrite(&ecx_context, slave, driveNo, elementflags, idn, psize, p, timeout);
}

int ec_readIDNmap(uint16 slave, uint32 *Osize, uint32 *Isize)
{
   return ecx_readIDNmap(&ecx_context, slave, Osize, Isize);
}
#endif
