/* 
 * Copyright (C) 1995 Advanced RISC Machines Limited. All rights reserved.
 * 
 * This software may be freely used, copied, modified, and distributed
 * provided that the above copyright notice is preserved in all copies of the
 * software.
 */

/* -*-C-*-
 *
 * $Revision: 1.4 $
 *     $Date: 1999/01/28 03:50:16 $
 *
 *
 * serdrv.c - Synchronous Serial Driver for Angel.
 *            This is nice and simple just to get something going.
 */

#ifdef __hpux
#  define _POSIX_SOURCE 1
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "crc.h"
#include "devices.h"
#include "buffers.h"
#include "rxtx.h"
#include "hostchan.h"
#include "params.h"
#include "logging.h"

#ifdef COMPILING_ON_WINDOWS
#  undef   ERROR
#  undef   IGNORE
#  include <windows.h>
#  include "angeldll.h"
#  include "comb_api.h"
#else
#  ifdef __hpux
#    define _TERMIOS_INCLUDED
#    include <sys/termio.h>
#    undef _TERMIOS_INCLUDED
#  else
#    include <termios.h>
#  endif
#  include "unixcomm.h"
#endif

#ifndef UNUSED
#  define UNUSED(x) (x = x)      /* Silence compiler warnings */
#endif
 
#define MAXREADSIZE 512
#define MAXWRITESIZE 512

#define SERIAL_FC_SET  ((1<<serial_XON)|(1<<serial_XOFF))
#define SERIAL_CTL_SET ((1<<serial_STX)|(1<<serial_ETX)|(1<<serial_ESC))
#define SERIAL_ESC_SET (SERIAL_FC_SET|SERIAL_CTL_SET)

static const struct re_config config = {
    serial_STX, serial_ETX, serial_ESC, /* self-explanatory?               */
    SERIAL_FC_SET,                      /* set of flow-control characters  */
    SERIAL_ESC_SET,                     /* set of characters to be escaped */
    NULL /* serial_flow_control */, NULL  ,    /* what to do with FC chars */
    angel_DD_RxEng_BufferAlloc, NULL                /* how to get a buffer */
};

static struct re_state rxstate;

typedef struct writestate {
  unsigned int wbindex;
  /*  static te_status testatus;*/
  unsigned char writebuf[MAXWRITESIZE];
  struct te_state txstate;
} writestate;

static struct writestate wstate;

/*
 * The set of parameter options supported by the device
 */
static unsigned int baud_options[] = {
#ifdef __hpux
    115200, 57600, 
#endif
    38400, 19200, 9600
};

static ParameterList param_list[] = {
    { AP_BAUD_RATE,
      sizeof(baud_options)/sizeof(unsigned int),
      baud_options }
};

static const ParameterOptions serial_options = {
    sizeof(param_list)/sizeof(ParameterList), param_list };

/* 
 * The default parameter config for the device
 */
static Parameter param_default[] = {
    { AP_BAUD_RATE, 9600 }
};

static ParameterConfig serial_defaults = {
    sizeof(param_default)/sizeof(Parameter), param_default };

/*
 * The user-modified options for the device
 */
static unsigned int user_baud_options[sizeof(baud_options)/sizeof(unsigned)];

static ParameterList param_user_list[] = {
    { AP_BAUD_RATE,
      sizeof(user_baud_options)/sizeof(unsigned),
      user_baud_options }
};

static ParameterOptions user_options = {
    sizeof(param_user_list)/sizeof(ParameterList), param_user_list };

static bool user_options_set;

/* forward declarations */
static int serial_reset( void );
static int serial_set_params( const ParameterConfig *config );
static int SerialMatch(const char *name, const char *arg);

static void process_baud_rate( unsigned int target_baud_rate )
{
    const ParameterList *full_list;
    ParameterList       *user_list;

    /* create subset of full options */
    full_list = Angel_FindParamList( &serial_options, AP_BAUD_RATE );
    user_list = Angel_FindParamList( &user_options,   AP_BAUD_RATE );

    if ( full_list != NULL && user_list != NULL )
    {
        unsigned int i, j;
        unsigned int def_baud = 0;

        /* find lower or equal to */
        for ( i = 0; i < full_list->num_options; ++i )
           if ( target_baud_rate >= full_list->option[i] )
           {
               /* copy remaining */
               for ( j = 0; j < (full_list->num_options - i); ++j )
                  user_list->option[j] = full_list->option[i+j];
               user_list->num_options = j;

               /* check this is not the default */
               Angel_FindParam( AP_BAUD_RATE, &serial_defaults, &def_baud );
               if ( (j == 1) && (user_list->option[0] == def_baud) )
               {
#ifdef DEBUG
                   printf( "user selected default\n" );
#endif
               }
               else
               {
                   user_options_set = TRUE;
#ifdef DEBUG
                   printf( "user options are: " );
                   for ( j = 0; j < user_list->num_options; ++j )
                      printf( "%u ", user_list->option[j] );
                   printf( "\n" );
#endif
               }

               break;   /* out of i loop */
           }
                
#ifdef DEBUG
        if ( i >= full_list->num_options )
           printf( "couldn't match baud rate %u\n", target_baud_rate );
#endif
    }
#ifdef DEBUG
    else
       printf( "failed to find lists\n" );
#endif
}

static int SerialOpen(const char *name, const char *arg)
{
    const char *port_name = name;

#ifdef DEBUG
    printf("SerialOpen: name %s arg %s\n", name, arg ? arg : "<NULL>");
#endif

#ifdef COMPILING_ON_WINDOWS
    if (IsOpenSerial()) return -1;
#else
    if (Unix_IsSerialInUse()) return -1;
#endif

#ifdef COMPILING_ON_WINDOWS
    if (SerialMatch(name, arg) != adp_ok)
        return adp_failed;
#else
    port_name = Unix_MatchValidSerialDevice(port_name);
# ifdef DEBUG
    printf("translated port to %s\n", port_name == 0 ? "NULL" : port_name);
# endif
    if (port_name == 0) return adp_failed;
#endif

    user_options_set = FALSE;

    /* interpret and store the arguments */
    if ( arg != NULL )
    {
        unsigned int target_baud_rate;
        target_baud_rate = (unsigned int)strtoul(arg, NULL, 10);
        if (target_baud_rate > 0)
        {
#ifdef DEBUG
            printf( "user selected baud rate %u\n", target_baud_rate );
#endif
            process_baud_rate( target_baud_rate );
        }
#ifdef DEBUG
        else
           printf( "could not understand baud rate %s\n", arg );
#endif
    }

#ifdef COMPILING_ON_WINDOWS
    {
        int port = IsValidDevice(name);
        if (OpenSerial(port, FALSE) != COM_OK)
            return -1;
    }
#else
    if (Unix_OpenSerial(port_name) < 0)
      return -1;
#endif

    serial_reset();

#if defined(__unix) || defined(__CYGWIN32__)
    Unix_ioctlNonBlocking();
#endif

    Angel_RxEngineInit(&config, &rxstate);
    /*
     * DANGER!: passing in NULL as the packet is ok for now as it is just
     * IGNOREd but this may well change
     */
    Angel_TxEngineInit(&config, NULL, &wstate.txstate); 
    return 0;
}

static int SerialMatch(const char *name, const char *arg)
{
    UNUSED(arg);
#ifdef COMPILING_ON_WINDOWS
    if (IsValidDevice(name) == COM_DEVICENOTVALID)
        return -1;
    else
        return 0;
#else
    return Unix_MatchValidSerialDevice(name) == 0 ? -1 : 0;
#endif
}

static void SerialClose(void)
{
#ifdef DO_TRACE
    printf("SerialClose()\n");
#endif

#ifdef COMPILING_ON_WINDOWS
    CloseSerial();
#else
    Unix_CloseSerial();
#endif
}

static int SerialRead(DriverCall *dc, bool block) {
  static unsigned char readbuf[MAXREADSIZE];
  static int rbindex=0;

  int nread;
  int read_errno;
  int c=0;
  re_status restatus;
  int ret_code = -1;            /* assume bad packet or error */

  /* must not overflow buffer and must start after the existing data */
#ifdef COMPILING_ON_WINDOWS
  {
    BOOL dummy = FALSE;
    nread = BytesInRXBufferSerial();

    if (nread > MAXREADSIZE - rbindex)
      nread = MAXREADSIZE - rbindex;

    if ((read_errno = ReadSerial(readbuf+rbindex, nread, &dummy)) == COM_READFAIL)
    {
        MessageBox(GetFocus(), "Read error\n", "Angel", MB_OK | MB_ICONSTOP);
        return -1;   /* SJ - This really needs to return a value, which is picked up in */
                     /*      DevSW_Read as meaning stop debugger but don't kill. */
    }
    else if (pfnProgressCallback != NULL && read_errno == COM_OK)
    {
      progressInfo.nRead += nread;
      (*pfnProgressCallback)(&progressInfo);
    }
  }
#else
  nread = Unix_ReadSerial(readbuf+rbindex, MAXREADSIZE-rbindex, block);
  read_errno = errno;
#endif

  if ((nread > 0) || (rbindex > 0)) {

#ifdef DO_TRACE
    printf("[%d@%d] ", nread, rbindex);
#endif

    if (nread>0)
       rbindex = rbindex+nread;

    do {
      restatus = Angel_RxEngine(readbuf[c], &(dc->dc_packet), &rxstate);
#ifdef DO_TRACE
      printf("<%02X ",readbuf[c]);
      if (!(++c % 16))
          printf("\n");
#else
      c++;
#endif
    } while (c<rbindex &&
             ((restatus == RS_IN_PKT) || (restatus == RS_WAIT_PKT)));

#ifdef DO_TRACE
   if (c % 16)
        printf("\n");
#endif

    switch(restatus) {
      
      case RS_GOOD_PKT:
        ret_code = 1;
        /* fall through to: */

      case RS_BAD_PKT:
        /*
         * We now need to shuffle any left over data down to the
         * beginning of our private buffer ready to be used 
         *for the next packet 
         */
#ifdef DO_TRACE
        printf("SerialRead() processed %d, moving down %d\n", c, rbindex-c);
#endif
        if (c != rbindex) memmove((char *) readbuf, (char *) (readbuf+c),
                                  rbindex-c);
        rbindex -= c;
        break;

      case RS_IN_PKT:
      case RS_WAIT_PKT:
        rbindex = 0;            /* will have processed all we had */
        ret_code = 0;
        break;

      default:
#ifdef DEBUG
        printf("Bad re_status in serialRead()\n");
#endif
        break;
    }
  } else if (nread == 0)
    ret_code = 0;               /* nothing to read */
  else if (read_errno == ERRNO_FOR_BLOCKED_IO) /* nread < 0 */
    ret_code = 0;

#ifdef DEBUG
  if ((nread<0) && (read_errno!=ERRNO_FOR_BLOCKED_IO))
    perror("read() error in serialRead()");
#endif

  return ret_code;
}


static int SerialWrite(DriverCall *dc) {
  int nwritten = 0;
  te_status testatus = TS_IN_PKT;

  if (dc->dc_context == NULL) {
    Angel_TxEngineInit(&config, &(dc->dc_packet), &(wstate.txstate));
    wstate.wbindex = 0;
    dc->dc_context = &wstate;
  }

  while ((testatus == TS_IN_PKT) && (wstate.wbindex < MAXWRITESIZE))
  {
    /* send the raw data through the tx engine to escape and encapsulate */
    testatus = Angel_TxEngine(&(dc->dc_packet), &(wstate.txstate),
                              &(wstate.writebuf)[wstate.wbindex]);
    if (testatus != TS_IDLE) wstate.wbindex++;
  }

  if (testatus == TS_IDLE) {
#ifdef DEBUG
    printf("SerialWrite: testatus is TS_IDLE during preprocessing\n");
#endif
  }

#ifdef DO_TRACE
  { 
    int i = 0;

    while (i<wstate.wbindex)
    {
        printf(">%02X ",wstate.writebuf[i]);

        if (!(++i % 16))
            printf("\n");
    }
    if (i % 16)
        printf("\n");
  }
#endif

#ifdef COMPILING_ON_WINDOWS
  if (WriteSerial(wstate.writebuf, wstate.wbindex) == COM_OK)
  {
    nwritten = wstate.wbindex;
    if (pfnProgressCallback != NULL)
    {
      progressInfo.nWritten += nwritten;
      (*pfnProgressCallback)(&progressInfo);
    }
  }
  else
  {
      MessageBox(GetFocus(), "Write error\n", "Angel", MB_OK | MB_ICONSTOP);
      return -1;   /* SJ - This really needs to return a value, which is picked up in */
                   /*      DevSW_Read as meaning stop debugger but don't kill. */
  }
#else
  nwritten = Unix_WriteSerial(wstate.writebuf, wstate.wbindex);

  if (nwritten < 0) {
    nwritten=0;
  }
#endif

#ifdef DEBUG
  if (nwritten > 0)
    printf("Wrote %#04x bytes\n", nwritten);
#endif

  if ((unsigned) nwritten == wstate.wbindex && 
      (testatus == TS_DONE_PKT || testatus == TS_IDLE)) {

    /* finished sending the packet */

#ifdef DEBUG
    printf("SerialWrite: calling Angel_TxEngineInit after sending packet (len=%i)\n",wstate.wbindex);
#endif
    testatus = TS_IN_PKT;
    wstate.wbindex = 0;
    return 1;
  }
  else {
#ifdef DEBUG
    printf("SerialWrite: Wrote part of packet wbindex=%i, nwritten=%i\n",
           wstate.wbindex, nwritten);
#endif
   
    /*
     *  still some data left to send shuffle whats left down and reset
     * the ptr
     */
    memmove((char *) wstate.writebuf, (char *) (wstate.writebuf+nwritten),
            wstate.wbindex-nwritten);
    wstate.wbindex -= nwritten;
    return 0;
  }
  return -1;
}


static int serial_reset( void )
{
#ifdef DEBUG
    printf( "serial_reset\n" );
#endif

#ifdef COMPILING_ON_WINDOWS
    FlushSerial();
#else
    Unix_ResetSerial();
#endif

    return serial_set_params( &serial_defaults );
}


static int find_baud_rate( unsigned int *speed )
{
    static struct {
          unsigned int baud;
          int termiosValue;
    } possibleBaudRates[] = {
#if defined(__hpux)
        {115200,_B115200}, {57600,_B57600},
#endif
#ifdef COMPILING_ON_WINDOWS
        {38400,CBR_38400}, {19200,CBR_19200}, {9600, CBR_9600}, {0,0}
#else
        {38400,B38400}, {19200,B19200}, {9600, B9600}, {0,0}
#endif
    };
    unsigned int i;

    /* look for lower or matching -- will always terminate at 0 end marker */
    for ( i = 0; possibleBaudRates[i].baud > *speed; ++i )
       /* do nothing */ ;

    if ( possibleBaudRates[i].baud > 0 )
       *speed = possibleBaudRates[i].baud;

    return possibleBaudRates[i].termiosValue;
}


static int serial_set_params( const ParameterConfig *config )
{
    unsigned int speed;
    int termios_value;

#ifdef DEBUG
    printf( "serial_set_params\n" );
#endif

    if ( ! Angel_FindParam( AP_BAUD_RATE, config, &speed ) )
    {
#ifdef DEBUG
        printf( "speed not found in config\n" );
#endif
        return DE_OKAY;
    }

    termios_value = find_baud_rate( &speed );
    if ( termios_value == 0 )
    {
#ifdef DEBUG
        printf( "speed not valid: %u\n", speed );
#endif
        return DE_OKAY;
    }

#ifdef DEBUG
    printf( "setting speed to %u\n", speed );
#endif

#ifdef COMPILING_ON_WINDOWS
    SetBaudRate((WORD)termios_value);
#else
    Unix_SetSerialBaudRate(termios_value);
#endif

    return DE_OKAY;
}


static int serial_get_user_params( ParameterOptions **p_options )
{
#ifdef DEBUG
    printf( "serial_get_user_params\n" );
#endif

    if ( user_options_set )
    {
        *p_options = &user_options;
    }
    else
    {
        *p_options = NULL;
    }

    return DE_OKAY;
}


static int serial_get_default_params( ParameterConfig **p_config )
{
#ifdef DEBUG
    printf( "serial_get_default_params\n" );
#endif

    *p_config = (ParameterConfig *) &serial_defaults;
    return DE_OKAY;
}


static int SerialIoctl(const int opcode, void *args) {

    int ret_code;

#ifdef DEBUG
    printf( "SerialIoctl: op %d arg %p\n", opcode, args ? args : "<NULL>");
#endif

    switch (opcode)
    {
       case DC_RESET:         
           ret_code = serial_reset();
           break;

       case DC_SET_PARAMS:     
           ret_code = serial_set_params((const ParameterConfig *)args);
           break;

       case DC_GET_USER_PARAMS:     
           ret_code = serial_get_user_params((ParameterOptions **)args);
           break;

       case DC_GET_DEFAULT_PARAMS:
           ret_code = serial_get_default_params((ParameterConfig **)args);
           break;

       default:               
           ret_code = DE_BAD_OP;
           break;
    }

  return ret_code;
}

DeviceDescr angel_SerialDevice = {
    "SERIAL",
    SerialOpen,
    SerialMatch,
    SerialClose,
    SerialRead,
    SerialWrite,
    SerialIoctl
};
