/* ser.c */

/* heavily borrowed from my program to control the ic-735 via rs-232 */
#include <exec/types.h>
#include <exec/exec.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>
#include <graphics/text.h>
#include <devices/serial.h>
#include <ctype.h>
#include <libraries/dos.h>
#include "mb.h"
#include "amiga.h"

extern short debug;
extern char *speed;
extern struct Window *mywindow;
extern long keybit,readbit,windbit,timerbit;
extern char tmpstr[],optflags[];
extern short done_flag;

/*   */
long getbits;
ULONG class;
USHORT code;

/* msg structure for GetMsg() */
extern struct IntuiMessage *NewMessage;

/* declarations for the serial stuff */
struct IOExtSer *Ser_Read;
unsigned char rs_in[2];
struct IOExtSer *Ser_Write;
unsigned char rs_out[2];
int serflags;
short tapr_flag = 0;    /* Using tapr upgrade? */
long BaudRate = 9600L;

struct timeRequest {
   struct IORequest tr_node;
   struct timeval tr_time;
};
extern struct timeRequest timermsg;

/* defaults to "serial.device" but user can change it if they have a
   multi-serial card
*/
extern UBYTE *ser_dev_name;
extern long unit;          /* defaults to zero, but can be changed */
initser()
{
   long error;
   if(ser_dev_name == 0L)ser_dev_name = (UBYTE *)SERIALNAME;
   Ser_Read = (struct IOExtSer *)AllocMem
                  ((long)sizeof(*Ser_Read),
                   (long)MEMF_PUBLIC|MEMF_CLEAR);
   serflags = Ser_Read->io_SerFlags =
                    SERF_SHARED | SERF_XDISABLED | SERF_7WIRE;
   Ser_Read->IOSer.io_Message.mn_ReplyPort =  CreatePort(0L,0L);
   if(OpenDevice(ser_dev_name,unit,(struct IORequest *)Ser_Read,0L))   {
      puts("Cant open Read device\n");
      DeletePort(Ser_Read->IOSer.io_Message.mn_ReplyPort);
      FreeMem(Ser_Read,(long)sizeof(*Ser_Read));
      return(1);
   }
   Ser_Read->IOSer.io_Command = CMD_READ;
   Ser_Read->IOSer.io_Length = 1L;
   Ser_Read->IOSer.io_Data = (APTR) &rs_in[0];

   Ser_Write = (struct IOExtSer *)AllocMem
                             ((long)sizeof(*Ser_Write),
                              (long)MEMF_PUBLIC|MEMF_CLEAR);
   Ser_Write->io_SerFlags = SERF_SHARED | SERF_XDISABLED | SERF_7WIRE;
   Ser_Write->IOSer.io_Message.mn_ReplyPort = CreatePort(0L,0L);
   if(OpenDevice(ser_dev_name,unit,(struct IORequest *)Ser_Write,0L))   {
      puts("Cant open Write device\n");
getout:
      CloseDevice((struct IORequest *)Ser_Read);
      DeletePort(Ser_Write->IOSer.io_Message.mn_ReplyPort);
      FreeMem(Ser_Write,(long)sizeof(*Ser_Write));
      DeletePort(Ser_Read->IOSer.io_Message.mn_ReplyPort);
      FreeMem(Ser_Read,(long)sizeof(*Ser_Read));
      return(1);
   }
   Ser_Read->io_TermArray.TermArray0 = 0x0L;
   Ser_Read->io_TermArray.TermArray1 = 0x0L;
   Ser_Write->IOSer.io_Command = CMD_WRITE;
   Ser_Write->IOSer.io_Length = 1L;
   Ser_Write->IOSer.io_Data = (APTR) &rs_out[0];
   Ser_Read->IOSer.io_Command = SDCMD_SETPARAMS;
   Ser_Read->io_SerFlags =  SERF_SHARED | SERF_XDISABLED | SERF_7WIRE;
   if((error = DoIO((struct IORequest *)Ser_Read)) != 0) {
      sprintf(tmpstr,"error 0 #%ld from setcommand in seric.c\n",error);
      ttputs(tmpstr);
      goto getout;
   }
   /* Set the break time which is required if the TNC is used in transparent
      mode. It is probably only necessary to do this once for Ser_Read but
      I do Ser_Write as well just in case. The default appears to be 
      250000.
   */
   Ser_Read->io_BrkTime = 500000L;
   Ser_Write->io_BrkTime = 500000L;
   Ser_Read->io_Baud = BaudRate;
   if((error = DoIO((struct IORequest *)Ser_Read)) != 0) {
      sprintf(tmpstr,"error 1 #%ld from setcommand in seric.c\n",error);
      ttputs(tmpstr);
      goto getout;
   }
   Ser_Write->IOSer.io_Command = SDCMD_SETPARAMS;
   if((error = DoIO((struct IORequest *)Ser_Write)) != 0) {
      sprintf(tmpstr,"error W #%ld from setcommand in seric.c\n",error);
      ttputs(tmpstr);
      goto getout;
   }
   Ser_Read->io_ReadLen = 8;
   Ser_Read->io_WriteLen = 8;
   if((error = DoIO((struct IORequest *)Ser_Read)) != 0) {
      sprintf(tmpstr,"error 2 #%ld from setcommand in seric.c\n",error);
      ttputs(tmpstr);
      goto getout;
   }
   Ser_Read->io_RBufLen = 4096L;
   if((error = DoIO((struct IORequest *)Ser_Read)) != 0) {
      sprintf(tmpstr,"error 3 #%ld from setcommand in seric.c\n",error);
      ttputs(tmpstr);
      goto getout;
   }
   Ser_Read->IOSer.io_Command = CMD_READ;

   BeginIO((struct IORequest *)Ser_Read);

   readbit = (1L << Ser_Read->IOSer.io_Message.mn_ReplyPort->mp_SigBit);
   return(0);
}

cleanser()
{
   AbortIO((struct IORequest *)Ser_Read);
   AbortIO((struct IORequest *)Ser_Write);
   CloseDevice((struct IORequest *)Ser_Read);
   CloseDevice((struct IORequest *)Ser_Write);
   DeletePort(Ser_Write->IOSer.io_Message.mn_ReplyPort);
   FreeMem(Ser_Write,(long)sizeof(*Ser_Write));
   DeletePort(Ser_Read->IOSer.io_Message.mn_ReplyPort);
   FreeMem(Ser_Read,(long)sizeof(*Ser_Read));
}

/*
   Read one char from the serial port.
   If rflag is non-null then don't start the next read IO.
   This is only used in YAPP and is turned off when it is done.
*/
short rflag = 0;
extern long serfirst;
unsigned char rdchar()
{
   unsigned char c;
   long waitbits;

   waitbits = windbit | readbit;
   /* There might already be an unread char at the serial port */
   serfirst = 0L;
   if(CheckIO((struct IORequest *)Ser_Read)) {
      Wait(readbit);
      serfirst |= readbit;
   }
   while(1)  {
      if(serfirst) {
         getbits = serfirst;
         serfirst = 0L;
      }
      else {
         getbits = Wait(waitbits);
      }
      if(windbit & getbits) {
         while(NewMessage = (struct IntuiMessage *)GetMsg(mywindow->UserPort)) {
            class = NewMessage->Class;
            code = NewMessage->Code;
            switch( class ) {
            case CLOSEWINDOW:
               if(done_flag)done(1);
               break;
            default:
               ReplyMsg((struct Message *) NewMessage );
            }   /* end of switch (class) */
         }
      }
      if(readbit & getbits) {
         WaitIO((struct IORequest *)Ser_Read);
         c = rs_in[0];
         if(!(port->flags & p_trans) & tapr_flag)c &= 0177;
         Ser_Read->IOSer.io_Command = CMD_READ;
         Ser_Read->IOSer.io_Length = 1L;
         Ser_Read->IOSer.io_Data = (APTR) &rs_in[0];
         if(!rflag)BeginIO((struct IORequest *)Ser_Read);
         return(c);
      }
   }
}
/* send character to the serial port */
schar(ch)
int ch;
{

   Ser_Write->IOSer.io_Command = CMD_WRITE;
   Ser_Write->IOSer.io_Length = 1L;
   Ser_Write->IOSer.io_Data = (APTR) &rs_out[0];
   rs_out[0] = ch;
   DoIO((struct IORequest *)Ser_Write);
   WaitIO((struct IORequest *)Ser_Write);
}

/* Allows program to Check Carrier Detect and/or DSR */
getserstat()
{
   Ser_Write->IOSer.io_Command = SDCMD_QUERY;
   DoIO((struct IORequest *)Ser_Write);
   return(Ser_Write->io_Status);
}

/* send the command string in s */
sends(s)
unsigned char *s;
{
   register unsigned char *p;
   register short i;

   i = 0;
   p = s;
   /* Count the number of chars in the string */
   while(*p++)i++;
   if(i == 0)return;
   Ser_Write->IOSer.io_Command = CMD_WRITE;
   Ser_Write->IOSer.io_Length = i;
   Ser_Write->IOSer.io_Data = (APTR) s;
   DoIO((struct IORequest *)Ser_Write);
   WaitIO((struct IORequest *)Ser_Write);
}
extern short block_cancel;
sendblock(s,l)
unsigned char *s;
int l;
{
   register char ch;
   extern unsigned char ichar;
   extern struct IOStdReq *Con_Read;

   Ser_Write->IOSer.io_Command = CMD_WRITE;
   Ser_Write->IOSer.io_Length = l;
   Ser_Write->IOSer.io_Data = (APTR) s;
   DoIO((struct IORequest *)Ser_Write);
   WaitIO((struct IORequest *)Ser_Write);
   if(CheckIO((struct IORequest *)Con_Read)) {
      Wait(keybit);
      WaitIO((struct IORequest *)Con_Read);
      ch = ichar;
      Con_Read->io_Data = (APTR)&ichar;
      Con_Read->io_Command = CMD_READ;
      Con_Read->io_Length = 1;
      SendIO((struct IORequest *)Con_Read);
      if(ch == '\033') {
         ttputs("sendblock\n");
      }
      if(ch == achar) {
         block_cancel = 1;
         port->mode = forced;
         restart();
         return(1);
      }
   }
   return(0);
}
breakport()
{
   Ser_Write->IOSer.io_Command = SDCMD_BREAK;
   DoIO((struct IORequest *)Ser_Write);
   WaitIO((struct IORequest *)Ser_Write);
   Delay(20L);
}
rdblock(p,l)
char *p;
int l;
{
   register int i;

   /* Negative length indicates read a null-terminated string of maximum
      length -l including the null.
   */
   if(l < 0) {

      /* Looking for a null-terminated string is not supported by the
         ibmser.device for the GoldenGate II card and, as Dan Babcock
         who wrote ibmser.device has told me, it is an inherently
         unreliable way to read because the null byte could get lost.
         So use EOFMODE and ask for the maximum permissible length.
      */
      Ser_Read->io_SerFlags |= SERF_EOFMODE;
      Ser_Read->IOSer.io_Command = CMD_READ;
      Ser_Read->IOSer.io_Length = (long) -l;
      Ser_Read->IOSer.io_Data = (APTR) p;
      BeginIO((struct IORequest *)Ser_Read);
   }
   else {
      Ser_Read->IOSer.io_Command = CMD_READ;
      Ser_Read->IOSer.io_Length = (long)l;
      Ser_Read->IOSer.io_Data = (APTR) p;
      BeginIO((struct IORequest *)Ser_Read);
   }
   i = rdwait();
   if(l < 0) {
      Ser_Read->io_SerFlags &= ~SERF_EOFMODE;
   }
   return(i == 0);
}
short restart_done = 0;
restart()
{
   if(restart_done)return;
   rflag = 0;
/* If I/O is still in progress then clobber it */
   if(CheckIO((struct IORequest *)Ser_Read) == 0) {
      AbortIO((struct IORequest *)Ser_Read);
      Wait(readbit);
      WaitIO((struct IORequest *)Ser_Read);
   }
   Ser_Read->io_SerFlags &= ~SERF_EOFMODE;
   Ser_Read->IOSer.io_Command = CMD_READ;
   Ser_Read->IOSer.io_Length = 1L;
   Ser_Read->IOSer.io_Data = (APTR) &rs_in[0];
   BeginIO((struct IORequest *)Ser_Read);
   restart_done = 1;
}

rdwait()
{
   long waitbits;
   short time_out;
   register char ch;
   extern unsigned char ichar;
   extern struct IOStdReq *Con_Read;

   /* Set the timeout timer to the connect time value */
   time_out = port->ctime;
   waitbits = windbit | readbit | keybit | timerbit;
   settimer(1);   /* set timeout timer to look for CD */
   while(1)  {
      getbits = Wait(waitbits);
      if(windbit & getbits) {
         while(NewMessage = (struct IntuiMessage *)GetMsg(mywindow->UserPort)) {
            class = NewMessage->Class;
            code = NewMessage->Code;
            switch( class ) {
            case CLOSEWINDOW:
               if(done_flag)done(1);
               break;
            default:
               ReplyMsg((struct Message *) NewMessage );
            }   /* end of switch (class) */
         }
      }
      if(keybit & getbits) {
         WaitIO((struct IORequest *)Con_Read);
         ch = ichar;
         Con_Read->io_Data = (APTR)&ichar;
         Con_Read->io_Command = CMD_READ;
         Con_Read->io_Length = 1;
         SendIO((struct IORequest *)Con_Read);
         if(ch == '\033') {
            ttputs("rdwait\n");
         }
         if(ch == achar) {
            port->mode = forced;
            block_cancel = 1;
            restart();
            return(0);
         }
      }
      if(readbit & getbits) {
         WaitIO((struct IORequest *)Ser_Read);
         return((short)Ser_Read->IOSer.io_Actual);
      }
      if(timerbit & getbits) {
         WaitIO((struct IORequest *)&timermsg);
         if(getserstat() & 0x20) {
            block_cancel = 1;
            port->mode = discon;
            restart();
            return(0);
         }
         if(time_out-- == 0) {
            block_cancel = 1;
            port->mode = discon;
            restart();
            return(0);
         }
         settimer(1);
      }
   }
}
