/*********************************  port.c  *******************************
 * $Source: /home/cvs/ESP/gen2/software/msp430/dwarves/port.c,v $
 *  Copyright (C) 2005 MBARI
 *
 *  MBARI Proprietary Information. All rights reserved.
 * $Id: port.c,v 1.2 2005/12/13 03:13:59 brent Exp $
 *
 * Serial Port pass through for dwarves
 *
 * Note:  Does not implemenent much of the SerialReadErr reply message
 *        Only break errors are indicated, and no direct indication
 *        is given of the end of a break condition.
 *
 *****************************************************************************/

#include "msp430x16x.h"  //register and I/O definitions
#include "parser.h"
#include "espmsg.h"
#include "clocks.h"
#include "dwarfser.h"
#include "espdwarf.h"

#include "port.h"


#define defMaxChunk 120   //default value for maxChunk below
#define absMaxChunk 120   //absolute maximum chunk size allowed
//don't increase absMaxChunk without considering additional stack size required

/******* Globals *******/

byte portReqTag;          //tag that node wants us to associate with our data

byte portFlowReqTag;        //tag associated with outbound flow control
static byte portFlowReqSrc; //source of currently active flow controlled write

static byte maxChunk;     //max # of bytes / chunk
static byte fullChunk;    //forward immediately when # of bytes exceeds this
static byte trigger;      //byte value that triggers immediate forwarding
static byte reqSrc;       //I2C node address requesting serial port forwarding
                      //MSB of requester set indicates that terminator is valid   

static byte *breakPt;     //point in input fifo at which last break occured
static byte *scanCursor;  //last byte in input scanned for terminator

/********* Code ********/

//configure the specified serial channel
//reply with error code
void doSerialConfigureRequest(unsigned src,unsigned tag,unsigned reqLen)
{
  fifo *evIn = node.events;
  unsigned result = 0xee00;  //force 0 to be output on wrong/missing channel
  beginReply (serialConfigure, tag, cursor);
  cursor = bufAddByte (cursor, serialConfigureCmd);
  if (reqLen >= sizeof(struct serialConfigureRequest)-1 ) {
    unsigned channel = FIFOreadByte(evIn);
    if (channel) {
      reportErr ("SerialConfigure for invalid port #%u", channel);
    }else{  //channel == 0
      unsigned options = FIFOreadByte(evIn);
      byte parity = options & serialParity;
      uint32 baudRate = FIFOread24(evIn);
      
      result = options & (serialDTRenable | serialDCDenable); //unsupported
      if ((options & serialBits) >= serialBits6)
        result |= serialBadWidth;
      if (parity == serialParity)
        result |= serialParity;
      if (baudRate > MAXBAUDRATE || baudRate < MINBAUDRATE)
        result |= serialBadBaud;
      if (!result) {
        byte u1ctl = SWRST;
        uint16 mclksPerBaud = MCLKRATE / baudRate;
        if (options & serialStopBits) u1ctl |= SPB;
        if ((options & serialBits) == serialBits8) u1ctl |= CHAR;
        if (parity) {
          u1ctl |= PENA;
          if (parity == serialParityEven) u1ctl |= PEV;
        }
        { //(busy-)wait a while for output buffers to clear (but don't hang!)
          unsigned count=5;
          unsigned start = MCLKnow;
          do
            MCLKpoll(start, 60000) if (FIFOempty (debugPort.output)) break;
          while (count--);
        }
        _DINT();
        FIFOclear (debugPort.output);
        U1CTL = u1ctl;           //reset USART & update parity and char width
        UBR01 = mclksPerBaud;    //update baud clock divisor
        UBR11 = mclksPerBaud >> 8;
        if (options & serialRTSenable)
          dwarfEnableRTS();
        else
          dwarfDisableRTS();
        _EINT();    
        UCTL1 &= ~SWRST;        // Re(start) USART
        IE2 |= URXIE1|UTXIE1;   //unmask USART1 interrupts
      }
    }
  }else{
    reportErr ("Truncated SerialConfigure");
  } 
  if (result) cursor = bufAddByte (cursor, result);
  writeNode (src, serialConfigure, cursor - serialConfigure);  
}


//reply with current serial port configuration
void doSerialGetConfigRequest(unsigned src,unsigned tag,unsigned reqLen)
{
  unsigned channel = 0;  //if no channel specified, respond for all
  beginReply (serialGetConfig, tag, cursor);
  cursor = bufAddByte (cursor, serialGetConfigCmd);  
  if (reqLen)
    channel = FIFOreadByte(node.events);
  if (!channel) {
    byte options = 0;
    uint16 clksPerBaud = (UBR11 << 8) | UBR01;
    byte ctl = U1CTL;
    if (!(ctl & CHAR)) options |= serialBits7;
    if (ctl & PENA)
      options |= ctl & PEV ? serialParityEven : serialParityOdd;
    if (ctl & SPB)
      options |= serialStopBits;
    if (dwarfUsingRTS())
      options |= serialRTSenable;
    cursor = bufAddByte (cursor, options);
    cursor = bufAdd24 (cursor, MCLKRATE / clksPerBaud);
  }
  writeNode (src, serialGetConfig, cursor - serialGetConfig);
}


//reply with information about our one available serial channel
void doSerialInfoRequest(unsigned src,unsigned tag,unsigned reqLen)
{
  unsigned channel = 0;  //if no channel specified, respond for all ;-)
  beginReply (serialInfo, tag, cursor);
  cursor = bufAddByte (cursor, serialInfoCmd);
  if (reqLen)
    channel = FIFOreadByte(node.events);
  if (!channel) {
    cursor = bufAddByte (cursor,   //port capabilities
      serialBits7 | serialParity | serialStopBits | serialRTSenable );
      cursor = bufAdd24 (cursor, MAXBAUDRATE);
      cursor = bufAdd24 (cursor, MINBAUDRATE);
      cursor = bufAdd16 (cursor, FIFOsize(debugPort.input));
      cursor = bufAdd16 (cursor, FIFOsize(debugPort.output));
  }
  writeNode (src, serialInfo, cursor - serialInfo);
}


//write the requested string and reply with the # of bytes written
void doSerialWriteRequest(unsigned src,unsigned tag,unsigned reqLen)
{
  fifo *evIn = node.events;
  beginReply (serialWrite, tag, cursor);
  cursor = bufAddByte (cursor, serialWriteCmd);
  if (reqLen) {
    unsigned channel = FIFOreadByte(evIn);
    if (channel & 0x7f) {
      reportErr ("SerialWrite to invalid port #%u", channel);
    }else{
      if (validTag(portFlowReqTag) && portFlowReqTag != tag) {
        beginReply (serialWrite, portFlowReqTag, ptr);
        ptr = bufAddByte (ptr, serialWriteCmd);
        writeNode (portFlowReqSrc, serialWrite, ptr - serialWrite);
      }
      {
        fifo *serOut = debugPort.output;
        size_t freeBytes = FIFOfree(serOut);
        size_t len;
        _DINT();
        len=reqLen-1;  //fill delay slot
        endBRK();
        _EINT();
        portFlowReqTag = 0;
        if (len > freeBytes) {  //no room at the inn...
          cursor = bufAddByte (cursor, 0);
          cursor = bufAddByte (cursor, len = freeBytes);
          if (channel & 0x80) { //enable flow control
            portFlowReqTag = tag;
            portFlowReqSrc = src;
          }
        }else //the whole string fits
          cursor = bufAddByte (cursor, freeBytes - len);
        while (len) {
          FIFOwriteByte(serOut, FIFOreadByte(evIn));
          --len;
        }
        parserKickstart (&debugPort);
      }
    }
  }else
    reportErr ("SerialWrite missing serial port #");
  writeNode (src, serialWrite, cursor - serialWrite);
}


//resume writing after flow shutdown
void resumeSerialWriteFlow(size_t bytesFree)
{
  beginReply (serialWrite, portFlowReqTag, cursor);
  cursor = bufAddByte(cursor, serialWriteCmd);
  if (bytesFree > 250)
    bytesFree=250;
  cursor = bufAddByte(cursor, bytesFree);
  writeNode (portFlowReqSrc, serialWrite, cursor - serialWrite);
  portFlowReqTag = 0;
}



//flush serial buffers and/or assert RS-232 break condition
void doSerialFlushMsg(unsigned msgLen)
{
  fifo *evIn = node.events;
  if (msgLen) {
    unsigned channel = FIFOreadByte(evIn);
    if (channel) {
      reportErr ("SerialFlush on invalid port #%u", channel);
    }else{
      unsigned opMask = 0;
      if (msgLen >= 2) opMask = FIFOreadByte(evIn);
      _DINT();
      if (opMask & serialFlushRead) {
        FIFOclear (debugPort.input);
        breakPt = NULL;
      }
      if (opMask & serialFlushWrite)
        FIFOclear (debugPort.output);
      if (opMask & serialAssertBreak)
        startBRK();
      _EINT();
      if (opMask & serialCancelFlow)
        portFlowReqTag = 0;
    }
  }else
    reportErr ("SerialFlush missing serial port #");
}


//forward received bytes to the requester
void doSerialReadRequest(unsigned src,unsigned tag,unsigned reqLen)
{
  fifo *evIn = node.events;
  if (reqLen) {
    unsigned channel = FIFOreadByte(evIn);
    if (channel) {
      reportErr ("SerialRead from invalid port #%u", channel);
    }else{
      unsigned maxBytes = 0;
      unsigned minBytes = 40;
      unsigned maxAge = 4;
      if (reqLen >= 2) {
        maxBytes = FIFOreadByte(evIn);
        if (reqLen >= 3) {
          minBytes = FIFOreadByte(evIn);
          if (reqLen >= 5) {
            maxAge = FIFOread16(evIn);
            if (reqLen >= 6) {
              trigger = FIFOreadByte(evIn);
              src |= 0x80;  //flag that a terminator was specified
            }
          }
        }
      }
      if (forwardingSerialPort() && portReqTag != tag) { //abort to original req
        beginReply (serialReadErr, portReqTag, cursor);
        cursor = bufAddByte(cursor, serialReadErrCmd);
        writeNode (reqSrc & 0x7f, serialReadErr, cursor - serialReadErr);
      }
      cancelSerialForwarding();  //cancel any active request
      if (maxBytes) {
        if (maxBytes > absMaxChunk)
          maxBytes = absMaxChunk;  
        maxChunk = maxBytes;
        if (minBytes > maxBytes)
          minBytes = maxBytes;
        reportErr("Forwarding input to node #%u ...", src & 0x7f);
        fullChunk = minBytes;
        reqSrc = src;
        portReqTag = tag;
        itimer[portTimer].period = maxAge;
      }else
        reportErr ("Canceled input forwarding");
    }
  }
}


//forward first chunkLen bytes in serial port input fifo to the I2C output
//We send break errors, but all others are ignored
static void forwardChunk (fifo *serIn, size_t chunkLen)
{
  unsigned src = reqSrc & 0x7f;
  beginVarReply (serialRead, portReqTag, cursor, absMaxChunk);
  cursor = bufAddByte (cursor, serialReadCmd);
  if (breakPt) {  //is there an RS-232 break event in the input queue?
    size_t brkOffset = FIFOdelta(serIn, breakPt, FIFOhead(serIn));
    if (brkOffset <= chunkLen) { //it's in the chunk we're about to send
      byte *rewind = cursor;     //to reuse already allocated reply buffer
      breakPt = NULL;
      if (brkOffset) {  //send chunk up to break error
        parserGetBlock (&debugPort, &cursor, brkOffset);
        writeNode (src, serialRead, cursor - serialRead);
      }
      rewind[-1] = serialReadErrCmd;  //send break error with no data bytes
      *rewind = serialBreakErr;
      writeNode (src, serialRead, rewind+1 - serialRead);
      if (!(chunkLen -= brkOffset))  
        return; //no data follows break
      cursor = rewind;  //else, send data that followed the break error
      rewind[-1] = serialReadCmd;     //switch back to a normal data response
    }
  }
  parserGetBlock (&debugPort, &cursor, chunkLen);
  writeNode (src, serialRead, cursor - serialRead);
  if (!FIFOempty(serIn))  //(re)-start timeout on any leftover chunk
    itimer[portTimer].countdown = itimer[portTimer].period;
}    


static void forwardOldData (struct timer *array, 
                              unsigned index, struct timer *timer)
//forward data that has been hanging around in our local input fifo too long
{
  fifo *serIn = debugPort.input;
  timer->countdown = 0;  //prevent reentry
  {
    size_t chunkLen = FIFOlen(serIn);
    if (chunkLen) {
      if (chunkLen > maxChunk)
        chunkLen = maxChunk;
      forwardChunk (serIn, chunkLen);
    }
  } 
}


static void missedOldDataTimeout (struct timer *array, 
                              unsigned index, struct timer *timer)
//nop -- serves to remember a missed timeout
{
}


static void dontForwardOldData (struct timer *array, 
                              unsigned index, struct timer *timer)
//just remember that we missed a timeout
{
  timer->action = missedOldDataTimeout;
}


//scan ahead for terminator in input queue
//factored out of forwardSerial to elimenate stack ops from it
//enter with interrupts disabled, they are enabled on exit
void fwdTerminator (byte *head, byte *end)
{
  struct timer *timer = itimer+portTimer;  
  byte *term = NULL;
  _EINT();  //got to because scanning might take a while
  timer->action=dontForwardOldData;  //avoid race with age timer
  do {  //peek ahead for trigger/terminator byte in our input queue
    if (parserGetByteAhead(&debugPort, &scanCursor) == trigger) 
      term = scanCursor;
  } while (scanCursor != end); 
  if (term) {
    fifo *serIn = debugPort.input;
    size_t lineLen = FIFOdelta (serIn, scanCursor, head);
    forwardChunk (serIn, lineLen);
  }else{  //no terminator, check for missed timeout while scanning
    MASK_TIMER_INTERRUPTS();
    if (timer->action == missedOldDataTimeout) {
      UNMASK_TIMER_INTERRUPTS();    //simulate time-out to catch up
      forwardOldData(itimer, portTimer, timer);
    }
    UNMASK_TIMER_INTERRUPTS();
  }
  timer->action = forwardOldData;
} 

   
//forward bytes just received to requester
//(call with interrupts disabled)
//return true interrupts were enabled
boolean forwardSerial(void)
{
  fifo *serIn = debugPort.input;
  size_t chunkLen = FIFOlen(serIn);
  if (chunkLen) {
    struct timer *timer = itimer+portTimer;
    if (chunkLen >= fullChunk) {
      timer->countdown = 0;  //prevent timer from expiring in forwardChunk
      _EINT();  //got to because forwarding might take a while
      if (chunkLen > maxChunk)
        chunkLen = maxChunk;
      forwardChunk (serIn, chunkLen);
      return true;         
    }else{
      if (!timer->countdown)  //ensure we're counting down to age data
        timer->countdown = timer->period;
          
      if (reqSrc & 0x80) {  //there was a terminator specified  
        byte *head = FIFOhead(serIn);
        byte *end = FIFOadvance (serIn, head, chunkLen);
        if (!scanCursor)
          scanCursor = head;
        if (scanCursor != end) { //new bytes received
          fwdTerminator (head, end);  //enables interrupts
          return true;
        }
      }
      
    } 
  }
  return false;
}



//cancel port forwarding
void cancelSerialForwarding(void)
{
  struct timer *timer = itimer+portTimer;
  timer->countdown = 0;
  timer->period = 0; 
  timer->action = forwardOldData;
  scanCursor = NULL;
  portReqTag = 0;  //no forwarding active
}


//initialize -- cancel port forwarding
void initSerialPort(void)
{
  breakPt = NULL;
  portFlowReqTag = 0;
  cancelSerialForwarding();
}


//record serial port error
//returns true if system should reset
boolean recordSerialPortErr (byte usartErr)
{
  if (usartErr & BRK)
    breakPt = FIFOtail(debugPort.input);
  return false;  //never reset on serial errors while forwarding is active
}



