/**HEADER********************************************************************
* 
* Copyright (c) 2008 Freescale Semiconductor;
* All Rights Reserved
*
* Copyright (c) 2004-2008 Embedded Access Inc.;
* All Rights Reserved
*
* Copyright (c) 1989-2008 ARC International;
* All Rights Reserved
*
*************************************************************************** 
*
* THIS SOFTWARE IS PROVIDED BY FREESCALE "AS IS" AND ANY EXPRESSED OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  
* IN NO EVENT SHALL FREESCALE OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
* THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************
*
* $FileName: ppphdlc.c$
* $Version : 3.0.7.0$
* $Date    : Jun-23-2009$
*
* Comments:
*
*   This file contains the PPPHDLC device functions needed
*   for communication.
*
*END************************************************************************/

#include <ppphdlc.h>
#include <fio.h>
#include <rtcs_in.h>
#include "fcs16.h"

PPPHDLC_OPT PPPHDLC_DEFAULT_OPTIONS = {
/* ACCM[]   */   {0xFFFFFFFFl, 0x00000000l, 0x00000000l, 0x60000000l,
                  0x00000000l, 0x00000000l, 0x00000000l, 0x00000000l},
/* PFC      */   FALSE,
/* ACFC     */   FALSE
};

/*
** A macro to test whether a character is flagged in the ACCM
*/

#define ACCM_ISSET(c,accm) ((accm)[(c)/PPPHDLC_BITS_IN_UINT32] & \
                           (1UL << ((c)%PPPHDLC_BITS_IN_UINT32)))


static uint_32 _iopcb_ppphdlc_open  (_iopcb_handle, void (_CODE_PTR_)(pointer), void (_CODE_PTR_)(pointer), pointer);
static uint_32 _iopcb_ppphdlc_close (_iopcb_handle);
static void    _iopcb_ppphdlc_write (_iopcb_handle, PCB_PTR, uint_32);
static PCB_PTR _iopcb_ppphdlc_read  (_iopcb_handle, uint_32);
static uint_32 _iopcb_ppphdlc_ioctl (_iopcb_handle, uint_32, pointer);

static void  PPPHDLC_putc (PPPHDLC_STRUCT_PTR, uchar, PPPHDLC_OPT_PTR);
static uchar PPPHDLC_getc (PPPHDLC_STRUCT_PTR, uint_32_ptr, uint_32_ptr, PPPHDLC_OPT_PTR);

static void    PPPHDLC_CCITT16_start (uint_16_ptr);
static void    PPPHDLC_CCITT16_calc  (uint_16_ptr, uchar);
static void    PPPHDLC_CCITT16_send  (PPPHDLC_STRUCT_PTR, PPPHDLC_OPT_PTR);
static boolean PPPHDLC_CCITT16_check (uint_16_ptr);


/*FUNCTION*-------------------------------------------------------------
*
*  Function Name  : _iopcb_ppphdlc_init
*  Returned Value : a HANDLE to be used by other functions
*  Comments       :
*      Sets some of the values of the ppphdlc structure and
*  returns the handle of the structure.
*
*END*-----------------------------------------------------------------*/

_iopcb_handle _iopcb_ppphdlc_init
   (
      FILE_PTR    device
         /* [IN] - the IO handle*/
   )
{  /* Body */
   PPPHDLC_STRUCT_PTR ppphdlc_ptr;
   _ppphdlc_partid    part_id;

   ppphdlc_ptr = PPPHDLC_memalloc(sizeof(PPPHDLC_STRUCT));
   if (!ppphdlc_ptr) {
      return NULL;
   } /* Endif */

   part_id = PPPHDLC_partcreate(sizeof(PCB)
                              + sizeof(PCB_FRAGMENT)
                              + PPPHDLC_FRAMESIZE_MAXDATA
                              + PPPHDLC_FRAMESIZE_FCS,
                                8, 0, 8, NULL, NULL);
   if (!part_id) {
      return NULL;
   } /* Endif */

   if (PPPHDLC_mutex_init(&ppphdlc_ptr->MUTEX) != PPPHDLC_OK) {
      return NULL;
   } /* Endif */

   PPPHDLC_memcopy(&PPPHDLC_DEFAULT_OPTIONS, &ppphdlc_ptr->SEND_OPT, sizeof(PPPHDLC_OPT));
   PPPHDLC_memcopy(&PPPHDLC_DEFAULT_OPTIONS, &ppphdlc_ptr->RECV_OPT, sizeof(PPPHDLC_OPT));

   ppphdlc_ptr->DEVICE  = device;
   ppphdlc_ptr->PART_ID = part_id;

   ppphdlc_ptr->PCB_TABLE.OPEN  = _iopcb_ppphdlc_open;
   ppphdlc_ptr->PCB_TABLE.CLOSE = _iopcb_ppphdlc_close;
   ppphdlc_ptr->PCB_TABLE.READ  = _iopcb_ppphdlc_read;
   ppphdlc_ptr->PCB_TABLE.WRITE = _iopcb_ppphdlc_write;
   ppphdlc_ptr->PCB_TABLE.IOCTL = _iopcb_ppphdlc_ioctl;

   return &ppphdlc_ptr->PCB_TABLE;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
*  Function Name  : _iopcb_ppphdlc_open
*  Returned Value : uint_32, an ERROR code
*  Comments       :
*      Sets some of the values of the ppphdlc structure and opens the link.
*
*END*-----------------------------------------------------------------*/

static uint_32 _iopcb_ppphdlc_open
   (
      _iopcb_handle       handle,
         /* [IN] - the IO handle*/
      void (_CODE_PTR_    up)(pointer),
         /* [IN] - function to put the link up */
      void (_CODE_PTR_    down)(pointer),
         /* [IN] - function to put the link down */
      pointer             param
         /* [IN] - for the up/down functions */
   )
{  /* Body */
   PPPHDLC_STRUCT_PTR ppphdlc_ptr = (PPPHDLC_STRUCT_PTR)((void _PTR_)handle);

   ppphdlc_ptr->UP      = up;
   ppphdlc_ptr->DOWN    = down;
   ppphdlc_ptr->PARAM   = param;

   /* open connection */
   if (ppphdlc_ptr->UP) {
      up(param);
   } /* Endif */

   return PPPHDLC_OK;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
*  Function Name  : _iopcb_ppphdlc_write
*  Returned Value :
*  Comments       :
*      Transmits the data
*
*END*-----------------------------------------------------------------*/

static void _iopcb_ppphdlc_write
   (
      _iopcb_handle      handle,
         /* [IN] - the handle */
      PCB_PTR            pcb_ptr,
         /* [IN] - the packet */
      uint_32            flags
         /* [IN] - the flags */
   )
{ /* Body */
   PPPHDLC_STRUCT_PTR ppphdlc_ptr = (PPPHDLC_STRUCT_PTR)((void _PTR_)handle);
   PPPHDLC_OPT        ppphdlc_opt, _PTR_ ppphdlc_opt_ptr;
   uint_32            length;
   uchar_ptr          fragment;
   uint_32            i = 0;

   PPPHDLC_mutex_lock(&ppphdlc_ptr->MUTEX);
   ppphdlc_opt = ppphdlc_ptr->SEND_OPT;
   PPPHDLC_mutex_unlock(&ppphdlc_ptr->MUTEX);

   if (flags & 1) {
      ppphdlc_opt_ptr = &PPPHDLC_DEFAULT_OPTIONS;
   } else {
      ppphdlc_opt_ptr = &ppphdlc_opt;
   } /* Endif */

   /* Send the HDLC flag sequence */
   fputc(PPPHDLC_FLAG, ppphdlc_ptr->DEVICE);
   PPPHDLC_CCITT16_start(&ppphdlc_ptr->FCS_SEND);

   /* Send the header */
   if (!ppphdlc_opt_ptr->ACFC) {
      PPPHDLC_putc(ppphdlc_ptr, PPPHDLC_ADDR, ppphdlc_opt_ptr);
      PPPHDLC_putc(ppphdlc_ptr, PPPHDLC_CTRL, ppphdlc_opt_ptr);
   } /* Endif */

   /* Send the packet */
   length = pcb_ptr->FRAG[i].LENGTH;
   fragment = pcb_ptr->FRAG[i].FRAGMENT;

   /* Compress the protocol field if possible */
   if (!ppphdlc_opt_ptr->PFC || *fragment) {
      PPPHDLC_putc(ppphdlc_ptr, *fragment, ppphdlc_opt_ptr);
   } /* Endif */
   fragment++;
   length--;

   while (length != 0)  {
      while (length != 0) {
         PPPHDLC_putc(ppphdlc_ptr, *fragment++, ppphdlc_opt_ptr);
         length--;
      } /* Endwhile */
      i++;
      length = pcb_ptr->FRAG[i].LENGTH;
      fragment = pcb_ptr->FRAG[i].FRAGMENT;
   } /* Endwhile */

   /* Send the checksum */
   PPPHDLC_CCITT16_send(ppphdlc_ptr, ppphdlc_opt_ptr);
   ppphdlc_ptr->STATS.COMMON.ST_TX_TOTAL++;

   /* Verify the checksum */
   if (!PPPHDLC_CCITT16_check(&ppphdlc_ptr->FCS_SEND))  {
      ppphdlc_ptr->STATS.COMMON.ST_TX_MISSED++;
   } /* Endif */

   fputc(PPPHDLC_FLAG, ppphdlc_ptr->DEVICE);
   PCB_free(pcb_ptr);

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
*  Function Name  : _iopcb_ppphdlc_read
*  Returned Value : PCB_PTR
*  Comments       :
*      Receives the data
*
*END*-----------------------------------------------------------------*/

static PCB_PTR _iopcb_ppphdlc_read
   (
      _iopcb_handle    handle,
         /* [IN] - the structure handle */
      uint_32          flags
         /* [IN] - flags */
   )
{  /* Body */
   PPPHDLC_STRUCT_PTR ppphdlc_ptr = (PPPHDLC_STRUCT_PTR)((void _PTR_)handle);
   PCB_FRAGMENT _PTR_ pcb_frag_ptr;
   PPPHDLC_OPT        ppphdlc_opt;
   PCB_PTR            pcb;
   uint_32            err;
   uchar_ptr          p;
   uint_16            data_len;
   register uchar     c;

   PPPHDLC_mutex_lock(&ppphdlc_ptr->MUTEX);
   ppphdlc_opt = ppphdlc_ptr->RECV_OPT;
   PPPHDLC_mutex_unlock(&ppphdlc_ptr->MUTEX);

   pcb = NULL;
   while (!pcb) {
      pcb = PPPHDLC_partalloc(ppphdlc_ptr->PART_ID);
      if (!pcb) {
         ppphdlc_ptr->STATS.COMMON.ST_RX_DISCARDED++;
         PPPHDLC_delay(1);
      } /* Endif */
   } /* Endwhile */

   pcb->FREE = (void (_CODE_PTR_)(PCB_PTR))PPPHDLC_partfree;
   pcb->PRIVATE = pcb;

   pcb_frag_ptr = pcb->FRAG;
   pcb_frag_ptr->FRAGMENT = (uchar_ptr)pcb + sizeof(PCB) + sizeof(PCB_FRAGMENT);
   pcb_frag_ptr++;
   pcb_frag_ptr->LENGTH = 0;
   pcb_frag_ptr->FRAGMENT = NULL;

restart:
   p = pcb->FRAG[0].FRAGMENT;
   PPPHDLC_CCITT16_start(&ppphdlc_ptr->FCS_RECV);

   c = PPPHDLC_getc(ppphdlc_ptr, &err, NULL, &ppphdlc_opt);
   if (err) goto restart;

   /* Receive the frame header */
   if (!ppphdlc_opt.ACFC && (c != PPPHDLC_ADDR)) {
      ppphdlc_ptr->STATS.ST_RX_BAD_ADDR++;
      while(fgetc(ppphdlc_ptr->DEVICE) != PPPHDLC_FLAG) {};
      goto restart;
   } /* Endif */

   /* If we got one, wait for control byte */
   if (c == PPPHDLC_ADDR) {
      c = PPPHDLC_getc(ppphdlc_ptr, &err, &ppphdlc_ptr->STATS.ST_RX_RUNT, &ppphdlc_opt);
      if (err) goto restart;

      if (c != PPPHDLC_CTRL) {
         ppphdlc_ptr->STATS.ST_RX_BAD_CTRL++;
         while(fgetc(ppphdlc_ptr->DEVICE) != PPPHDLC_FLAG) {};
         goto restart;
      } /* Endif */

      c = PPPHDLC_getc(ppphdlc_ptr, &err, &ppphdlc_ptr->STATS.ST_RX_RUNT, &ppphdlc_opt);
      if (err) goto restart;
   } /* Endif */

   data_len = 0;

   /* Decompress the protocol field if necessary */
   if (c & 1) {
      *p++ = 0;
      ++data_len;
   } /* Endif */

   /* We must recieve at least a frame check sequence */
   while (data_len < PPPHDLC_FRAMESIZE_FCS) {
      *p++ = c;
      ++data_len;
      c = PPPHDLC_getc(ppphdlc_ptr, &err, &ppphdlc_ptr->STATS.ST_RX_RUNT, &ppphdlc_opt);
      if (err) goto restart;
   } /* Endwhile */

   /* Receive data */
   for (;;) {
      *p++ = c;
      ++data_len;
      c = PPPHDLC_getc(ppphdlc_ptr, &err, NULL, &ppphdlc_opt);
      if (err == PPPHDLC_END) break;
      if (err == PPPHDLC_ABORT) goto restart;
       if (data_len >= (PPPHDLC_FRAMESIZE_MAXDATA + PPPHDLC_FRAMESIZE_FCS)) {
         ppphdlc_ptr->STATS.ST_RX_GIANT++;
         while(fgetc(ppphdlc_ptr->DEVICE) != PPPHDLC_FLAG) {};
         goto restart;
      } /* Endif */
   } /* Endfor */

   /* Validate the FCS */
   if (!PPPHDLC_CCITT16_check(&ppphdlc_ptr->FCS_RECV))  {
      ppphdlc_ptr->STATS.ST_RX_BAD_FCS++;
      goto restart;
   } /* Endif */

   pcb->FRAG[0].LENGTH = data_len - PPPHDLC_FRAMESIZE_FCS;
   return pcb;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
*  Function Name  : _iopcb_ppphdlc_close
*  Returned Value : uint_32, an ERROR code
*  Comments       :
*     Close link and free memory
*
*END*-----------------------------------------------------------------*/

static uint_32 _iopcb_ppphdlc_close
   (
      _iopcb_handle  handle
         /* [IN] - the structure handle */
   )
{  /* Body */
   PPPHDLC_STRUCT_PTR ppphdlc_ptr = (PPPHDLC_STRUCT_PTR)((void _PTR_)handle);

   /* close connection */
   if (ppphdlc_ptr->DOWN) {
      ppphdlc_ptr->DOWN(ppphdlc_ptr->PARAM);
   } /* Endif */

   /* Free memory */
   PPPHDLC_partdestroy(ppphdlc_ptr->PART_ID);
   PPPHDLC_mutex_destroy(&ppphdlc_ptr->MUTEX);
   PPPHDLC_memfree(ppphdlc_ptr);

   return PPPHDLC_OK;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
*  Function Name  : _iopcb_ppphdlc_ioctl
*  Returned Value : an ERROR code
*  Comments       :
*      Store and set the options
*
*END*-----------------------------------------------------------------*/

static uint_32 _iopcb_ppphdlc_ioctl
   (
      _iopcb_handle    handle,
         /* [IN] - the structure handle */
      uint_32          option,
         /* [IN] - options */
      pointer          value
         /* [IN] - option value */
   )
{  /* Body */
   PPPHDLC_STRUCT_PTR ppphdlc_ptr = (PPPHDLC_STRUCT_PTR)((void _PTR_)handle);

   PPPHDLC_mutex_lock(&ppphdlc_ptr->MUTEX);

   switch(option)  {
      case IOPCB_IOCTL_S_ACCM:
         ppphdlc_ptr->SEND_OPT.ACCM[0] = *(uint_32 _PTR_)value;
         break;
      case IOPCB_IOCTL_R_ACCM:
         ppphdlc_ptr->RECV_OPT.ACCM[0] = *(uint_32 _PTR_)value;
         break;
      case IOPCB_IOCTL_S_PFC:
         ppphdlc_ptr->SEND_OPT.PFC = *(boolean _PTR_)value;
         break;
      case IOPCB_IOCTL_R_PFC:
         ppphdlc_ptr->RECV_OPT.PFC = *(boolean _PTR_)value;
         break;
      case IOPCB_IOCTL_S_ACFC:
         ppphdlc_ptr->SEND_OPT.ACFC = *(boolean _PTR_)value;
         break;
      case IOPCB_IOCTL_R_ACFC:
         ppphdlc_ptr->RECV_OPT.ACFC = *(boolean _PTR_)value;
         break;
      case IOPCB_IOCTL_GET_IFTYPE:
         *(uint_32_ptr)value = IPIFTYPE_RS232;
         break;
   } /* Endswitch */

   PPPHDLC_mutex_unlock(&ppphdlc_ptr->MUTEX);

   return PPPHDLC_OK;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  PPPHDLC_putc
* Returned Value  :  None
* Comments        :
*     Outputs one character, escaped if necessary, and updates
*     the FCS.
*
*END*-----------------------------------------------------------------*/

static void PPPHDLC_putc
   (
      PPPHDLC_STRUCT_PTR  handle,
            /* [IN] - the ppphdlc structure */
      uchar               c,
            /* [IN] - character to send */
      PPPHDLC_OPT_PTR     opt
            /* [IN] - the options */
   )
{ /* Body */

   if (ACCM_ISSET(c, opt->ACCM)) {
      fputc(PPPHDLC_ESC, handle->DEVICE);
      fputc((char)c ^ 0x20, handle->DEVICE);
   } else {
      fputc((char)c, handle->DEVICE);
   } /* Endif */

   PPPHDLC_CCITT16_calc(&handle->FCS_SEND, c);

} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  PPPHDLC_getc
* Returned Value  :  character
* Comments        :
*     Retrieves one character and updates the FCS.
*
*END*-----------------------------------------------------------------*/

static uchar PPPHDLC_getc
   (
      PPPHDLC_STRUCT_PTR  handle,
            /* [IN] - the handle of the ppphdlc structure */
      uint_32_ptr         error,
            /* [OUT] - end of frame condition */
      uint_32_ptr         stat,
            /* [IN] - statistics counter to increment on flag character */
      PPPHDLC_OPT_PTR     opt
            /* [IN] - the options */
   )
{ /* Body */
   uchar c;
   boolean esc = FALSE;

   for (;;) {
      c = (uchar)fgetc(handle->DEVICE);

      /* The flag sequence ends a frame */
      if (c == PPPHDLC_FLAG) {
         if (esc) {
            handle->STATS.ST_RX_ABORTED++;
            *error = PPPHDLC_ABORT;
         } else {
            if (stat) (*stat)++;
            *error = PPPHDLC_END;
         } /* Endif */
         return 0;
      } /* Endif */

      /* Toss all flagged control characters */
      if (c < 0x20 && ACCM_ISSET(c, opt->ACCM)) {
         continue;
      } /* Endif */

      /* Escaped characters get bit 5 toggled */
      if (esc) {
         c ^= 0x20;
      } else if (c == PPPHDLC_ESC) {
         esc = TRUE;
         continue;
      } /* Endif */

      PPPHDLC_CCITT16_calc(&handle->FCS_RECV, c);
      *error = PPPHDLC_OK;
      return c;
   } /* Endfor */

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  PPPHDLC_CCITT16_start
* Returned Value  :  None
* Comments        :
*     Initializes the FCS_DATA for a CRC calculation.
*
*END*-----------------------------------------------------------------*/

static void PPPHDLC_CCITT16_start
   (
      uint_16_ptr        fcs
            /* [IN] -  fcs information */
   )
{ /* Body */
   *fcs = 0xFFFF;
} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  PPPHDLC_CCITT16_calc
* Returned Value  :  None
* Comments        :
*     Updates the CRC calculation given a new character.
*
*END*-----------------------------------------------------------------*/

static void PPPHDLC_CCITT16_calc
   (
      uint_16_ptr    fcs,
            /* [IN] - FCS information */
      uchar          c
            /* [IN] - character */
   )
{ /* Body */
   uint_16 crc = *fcs;

   crc = (crc >> 8) ^ fcstab[(crc ^ c) & 0xFF];
   *fcs = crc;

} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  PPPHDLC_CCITT16_send
* Returned Value  :  None
* Comments        :
*     Sends a CRC.
*
*END*-----------------------------------------------------------------*/

static void PPPHDLC_CCITT16_send
   (
      PPPHDLC_STRUCT_PTR   ppphdlc_ptr,
            /* [IN] - the handle of the ppphdlc structure */
      PPPHDLC_OPT_PTR      opt
            /* [IN] - the options */
   )
{ /* Body */
   uint_16 crc = ~ppphdlc_ptr->FCS_SEND & 0xFFFF;

   PPPHDLC_putc(ppphdlc_ptr,  crc      & 0xFF, opt);
   PPPHDLC_putc(ppphdlc_ptr, (crc>>=8) & 0xFF, opt);

} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  PPPHDLC_CCITT16_check
* Returned Value  :  TRUE if CRC is correct
* Comments        :
*     Validates a CRC calculation.
*
*END*-----------------------------------------------------------------*/

static boolean PPPHDLC_CCITT16_check
   (
      uint_16_ptr   fcs
            /* [IN] - FCS information */
   )
{ /* Body */

   return (*fcs == 0xF0B8);

} /* Endbody */


/* EOF */
