
#define ERROR (-1)     /* don't change these 2 because they have to */
#define TIMEOUT (-2)   /* be the same as the #defines in sz.c and rz.c */

#define SERBUFSIZE 1024L

/*
 *   Serial IO functions
 *
 *	based on routines by A.Livshits & J.M.Forgeas
 *
 *   Extensively modified for Amiga zmodem by Frank Harper
 */

#include <stdio.h>
#include <ctype.h>
#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/memory.h>
#include <exec/io.h>
#include <exec/errors.h>
#include <exec/ports.h>
#include <devices/timer.h>
#include <devices/serial.h>
#include <libraries/dos.h>
#include "intuition/intuition.h"

extern int Lleft;			 /* number of chars in input buffer */
extern UBYTE  SerOpen;			 /* True if Serial port is open */
static UBYTE TimeOpen;
extern struct IOExtSer *InSer, *OutSer;
extern struct MsgPort  *InSerPort, *OutSerPort;

extern ULONG  InSerSigMask, OutSerSigMask; /* Masks for Wait & WaitIO */
extern ULONG  IOTimeSigMask;

extern struct timerequest *IOTime;
extern struct MsgPort *TimerPort;
extern struct Window *Win;
struct MsgPort *CreatePort();
void *CreateExtIO(),*AllocMem();

static UBYTE  RecSerBuf[SERBUFSIZE];	/* Serial input buffer */
static UBYTE Previous=FALSE;		/* True if asynchronous serial
					   write is pending */


extern int Enable_Abort = 0;		/* No ^C allowed (Manx) */

 /*
  * Init. serial port
  *
  * bps: bits per second (or Baud rate) between 110 and 29,200
  *	if this parameter is -1 then the device is opened with
  *	no changes in bps,len, or stop.
  *
  * len: lenght of each word sent and received (7 or 8)
  * stop: number of stop bits 1 or 2
  * flags: used to set other parameters
  *
  * returns pointer to IOExtSer structure, so caller can determine what
  * the serial parameters in use actually are. In case of failure
  * return NULL.
  */

struct IOExtSer *SetSer(bps,len,stop,flags)
ULONG bps,len,stop,flags;
{

   if( bps!=-1 && (bps<110 || bps>29200 || len<7 || len>8 ||
		    stop<1 || stop>2)) return NULL;

   InSer = (struct IOExtSer *) AllocMem((long)sizeof(*InSer),MEMF_PUBLIC|MEMF_CLEAR);
   if(InSer==NULL) return NULL;

   OutSer = (struct IOExtSer *) AllocMem((long)sizeof(*OutSer),MEMF_PUBLIC|MEMF_CLEAR);
   if( OutSer==NULL) return NULL;

   InSerPort = InSer->IOSer.io_Message.mn_ReplyPort = CreatePort(0L,0L);
   if(InSerPort==NULL) return NULL;

   InSerSigMask = 1 << (InSerPort->mp_SigBit);
   InSer->io_SerFlags = SERF_SHARED&flags;

   if(OpenDevice(SERIALNAME,NULL,InSer,NULL)) {
       fprintf(stderr,"Error opening serial device\n");
       return NULL;
   }
   if( bps!=-1 ) {
     InSer->io_SerFlags = flags;
     InSer->io_Baud	= bps;
     InSer->io_ReadLen	= InSer->io_WriteLen = len;
     InSer->io_StopBits = stop;
     InSer->io_CtlChar	= 0x11130000L;
     InSer->io_RBufLen	= SERBUFSIZE;
     InSer->io_BrkTime	= 250000L;
     InSer->IOSer.io_Command = SDCMD_SETPARAMS;
     if( DoIO(InSer)!=0 ) {
	 fprintf(stderr,"Error setting serial port parameters\n");
	 return NULL;
     }
   }
   CopyMem(InSer,OutSer,sizeof(struct IOExtSer));
   OutSerPort = OutSer->IOSer.io_Message.mn_ReplyPort = CreatePort(0L,0L);
   if(OutSerPort==NULL) return NULL;
   OutSerSigMask = 1 << (OutSerPort->mp_SigBit);
   SerOpen=1;
   return InSer;
}

 /*
  * Close serial port
  */

void  CloseSer()
{
    if(Previous) {
	Wait(OutSerSigMask);
	GetMsg(OutSerPort);
    }
    if(SerOpen) {
	CloseDevice(InSer);
	SerOpen=0;
    }
    if(OutSerPort) {
	DeletePort(OutSerPort);
	OutSerPort=NULL;
    }
    if(InSerPort)  {
	DeletePort(InSerPort);
	InSerPort=NULL;
    }
    if(OutSer) {
	FreeMem(OutSer,(long)sizeof(*OutSer));
	OutSer=NULL;
    }
    if(InSer) {
	FreeMem(InSer,(long)sizeof(*InSer));
	InSer=NULL;
    }
}

 /*
  * Write asynchronously the len characters (up to SERBUFSIZE) pointed
  * at by c.
  */

int LWriteSer(c,len)
UBYTE  *c;
int len;
{

   ULONG  IntuiMask,mask;
   static UBYTE buf[SERBUFSIZE];

    if (len==0) return (0);
    if (len > SERBUFSIZE || SerOpen==0) return ERROR;
    if (Previous) {
	IntuiMask = 1 << (Win->UserPort->mp_SigBit);
	mask = Wait(OutSerSigMask | IntuiMask);
	if ((mask & IntuiMask) && CheckQuit()) {
	    KillIO( (struct IOStdReq *)OutSer,mask);
	    Previous=FALSE;
	    bibi(1);
	}
	GetMsg(OutSerPort);
    }
    Previous = TRUE;
    CopyMem(c,buf,len);
    OutSer->IOSer.io_Data = (APTR)buf;
    OutSer->IOSer.io_Length = (ULONG)len;
    OutSer->IOSer.io_Command = CMD_WRITE;
    SendIO(OutSer);
    return (0);
}

 /*
  * Send an asynchronous read request for n characters
  * put result in buf
  */

SendSer(n,buf)
int n;
char *buf;
{
    if( SerOpen==0 ) {
	fprintf(stderr,"Read attempt on closed Serial Port\n");
	return ERROR;
    }
    InSer->IOSer.io_Data = (APTR)buf;
    InSer->IOSer.io_Length = n;
    InSer->IOSer.io_Command = CMD_READ;
    SendIO(InSer);
    return (0);
}

 /*
  * Purge any characters waiting in serial port queue
  *
  */

PurgeSer()
{
    if( SerOpen!=0 ) {
     InSer->IOSer.io_Command = CMD_CLEAR;
     if ( DoIO(InSer)!=0) fprintf(stderr,"can't purge serport\n");
    }
}

 /*
  * Initialize timer device
  */

int SetTimer()

{
    int err;

    TimerPort =  CreatePort(0,0);
    if (TimerPort==NULL) return ERROR;
    IOTimeSigMask = 1 << (TimerPort->mp_SigBit);
    IOTime   = (struct timerequest *) CreateExtIO(TimerPort,sizeof(struct timerequest));
    if (IOTime==NULL) return ERROR;
    if( (err=OpenDevice(TIMERNAME,UNIT_VBLANK,IOTime,0))!=0)
	 TimeOpen=0;
    else TimeOpen=1;
    return err;
}

 /*
  * Close timer device
  */

void CloseTimer()
{
    if( TimeOpen ) {
	CloseDevice(IOTime);
	TimeOpen=0;
    }
    if(IOTime) {
	DeleteExtIO(IOTime,sizeof(struct timerequest));
	IOTime=NULL;
    }
    if(TimerPort) {
	DeletePort(TimerPort);
	TimerPort=NULL;
    }
}

/* send a break to the host */

void SendBreak()
{
    if ( SerOpen ) {
	InSer->IOSer.io_Command = SDCMD_BREAK;
	if( DoIO(InSer)!=0) fprintf(stderr,"SendBreak:error sending break\n");
    }
}

 /*
  * Read a character from serial port. Timeout is in tenths of seconds.
  *
  */
int ReadSer(timeout)
int timeout;
{
    int ch;
    ULONG mask,IntuiMask;
    static UBYTE *NextChar;
    int Quit;

  /*  if(CheckQuit()) bibi(1);*/
    if( !SerOpen) return ERROR;
    if( Lleft>0 ) {
	--Lleft;
	return (*NextChar++);
     }
    IOTime->tr_time.tv_secs = timeout/10;
    IOTime->tr_time.tv_micro = 100000*(timeout%10);
    IOTime->tr_node.io_Command = TR_ADDREQUEST;
    SendIO(IOTime);
    SendSer(1,RecSerBuf);
    IntuiMask = 1 << (Win->UserPort->mp_SigBit);
    mask=Wait( InSerSigMask | IOTimeSigMask | IntuiMask);
    if (CheckQuit()) {
	KillIO(IOTime,mask);
	KillIO( (struct IOStdReq *)InSer,mask);
	bibi(1);
    }
    else if ((mask&InSerSigMask)!=0) {
	    KillIO(IOTime,mask);
	    GetMsg(InSerPort);
	    ch=RecSerBuf[0];
	    if( (Lleft=CharsWaiting())>0){/* find out how many characters are
					     waiting */
		InSer->IOSer.io_Data = (APTR)RecSerBuf;
		InSer->IOSer.io_Length = Lleft<SERBUFSIZE?Lleft:SERBUFSIZE;
		InSer->IOSer.io_Command = CMD_READ;
		DoIO(InSer);
		NextChar=RecSerBuf;
	     }
	    return(ch);
    }
    else  { /* it was the timer signal */
	    KillIO( (struct IOStdReq *)InSer,mask);
	    WaitIO(IOTime);
	    return TIMEOUT;
    }
}

 /*
  * Determine the number of characters waiting in the serial.device
  * input buffer
  *
  */

CharsWaiting()
{
    if( SerOpen ) {
	    InSer->IOSer.io_Command = SDCMD_QUERY;
	    if( DoIO(InSer)!=0) fprintf(stderr,"CharsWaiting: IO error\n");
	    return (int)(InSer->IOSer.io_Actual);
    }
}

 /*
  * If IO isn't done Abort it
  * in any case GetMsg it
  *
  */

KillIO(Req,Mask)
struct IOStdReq *Req;
ULONG Mask;
{
  ULONG SigMask;
  struct MsgPort  *SigPort;

    SigPort=Req->io_Message.mn_ReplyPort;
    SigMask=1 << (SigPort->mp_SigBit);
    if( (Mask & SigMask)==0 ) {
	    AbortIO(Req);
	    Wait(SigMask);
    }
    GetMsg(SigPort);
}
