/* mdio.c - bmodem i/o routines for the Amiga/Aztec C */
/*
	by David Betz, BYTE Magazine/BIX
*/

#include <stdio.h>
#include <setjmp.h>
#include <exec/exec.h>
#include <libraries/dos.h>
#include <devices/serial.h>
#include <devices/timer.h>
#include <functions.h>

/* definitions */
#define IBSIZE		512		/* input buffer size */

/* return status codes */
#define DT_TIME	-1

/* local variables */
static int readOpen = FALSE;
static struct IOExtSer *readReq = NULL;
static int timerOpen = FALSE;
static struct timerequest *timerReq = NULL;
static int writeOpen = FALSE;
static struct IOExtSer *writeReq = NULL;
static int inputOpen = FALSE;
static unsigned char inbuf[IBSIZE],*inptr;
static int incnt;

/* external variables */
extern jmp_buf xfr_error;

/* md_init - common initialization routine */
md_init(baud)
  long baud;
{
    /* create a read request block */
    readReq = (struct IOExtSer *)
        AllocMem((long)sizeof(struct IOExtSer),MEMF_PUBLIC|MEMF_CLEAR);
    if (readReq == NULL)
	cleanup("can't create read request block");
    readReq->io_SerFlags = SERF_SHARED;

    /* create a reply port for the read request block */
    readReq->IOSer.io_Message.mn_ReplyPort = CreatePort("ReadReply",0L);
    if (readReq->IOSer.io_Message.mn_ReplyPort == NULL)
	cleanup("can't create read reply port");

    /* open the serial device - input */
    if (OpenDevice("serial.device",0L,readReq,0L))
	cleanup("can't open serial device for input");
    readOpen = TRUE;

    /* set the baud rate and character size */
    readReq->IOSer.io_Command = SDCMD_SETPARAMS;
    readReq->io_CtlChar = 0x13150506L;
    readReq->io_SerFlags = SERF_XDISABLED;
    readReq->io_ExtFlags = 0;
    readReq->io_Baud = baud;
    readReq->io_ReadLen = 8;
    readReq->io_WriteLen = 8;
    readReq->io_StopBits = 1;
    DoIO(readReq);

    /* create a timer request block */
    timerReq = (struct timerequest *)
        AllocMem((long)sizeof(struct timerequest),MEMF_PUBLIC|MEMF_CLEAR);
    if (timerReq == NULL)
	cleanup("can't create timer request block");

    /* create a reply port for the timer request block */
    timerReq->tr_node.io_Message.mn_ReplyPort = CreatePort("TimerReply",0L);
    if (timerReq->tr_node.io_Message.mn_ReplyPort == NULL)
	cleanup("can't create timer reply port");

    /* open the timer device */
    if (OpenDevice("timer.device",UNIT_VBLANK,timerReq,0L))
	cleanup("can't open timer device");
    timerOpen = TRUE;

    /* setup for a timer request */
    timerReq->tr_node.io_Command = TR_ADDREQUEST;

    /* create a write request block */
    writeReq = (struct IOExtSer *)
    	AllocMem((long)sizeof(struct IOExtSer),MEMF_PUBLIC|MEMF_CLEAR);
    if (writeReq == NULL)
	cleanup("can't create write request block");
    writeReq->io_SerFlags = SERF_SHARED;

    /* create a reply port for the write request block */
    writeReq->IOSer.io_Message.mn_ReplyPort = CreatePort("WriteReply",0L);
    if (writeReq->IOSer.io_Message.mn_ReplyPort == NULL)
	cleanup("can't create write reply port");

    /* open the serial device - output */
    if (OpenDevice("serial.device",0L,writeReq,0L))
	cleanup("can't open serial device for output");
    writeOpen = TRUE;

    /* input buffer starts out empty */
    incnt = 0;
}

/* cleanup - cleanup and exit after an error */
cleanup(msg)
  char *msg;
{
    fprintf(stderr,"error: %s\n",msg);
    md_done();
    exit(1);
}

/* md_done - terminate modem communications */
md_done()
{
    /* delete the read reply port */
    if (readReq != NULL && readReq->IOSer.io_Message.mn_ReplyPort)
	DeletePort(readReq->IOSer.io_Message.mn_ReplyPort);

    /* close the read request and free the memory */
    if (readOpen)
	CloseDevice(readReq);
    if (readReq)
	FreeMem(readReq,(long)sizeof(struct IOExtSer));

    /* delete the timer reply port */
    if (timerReq != NULL && timerReq->tr_node.io_Message.mn_ReplyPort)
	DeletePort(timerReq->tr_node.io_Message.mn_ReplyPort);

    /* close the timer request and free the memory */
    if (timerOpen)
	CloseDevice(timerReq);
    if (timerReq)
	FreeMem(timerReq,(long)sizeof(struct timerequest));

    /* delete the write reply port */
    if (writeReq != NULL && writeReq->IOSer.io_Message.mn_ReplyPort)
	DeletePort(writeReq->IOSer.io_Message.mn_ReplyPort);

    /* close the write request and free the memory */
    if (writeOpen)
	CloseDevice(writeReq);
    if (writeReq)
	FreeMem(writeReq,(long)sizeof(struct IOExtSer));
}

/* md_get - get a character with timeout */
int md_get(tmo)
  int tmo;
{
    long mask,rmask;

    /* check for a buffered character */
    if (--incnt >= 0)
	return (*inptr++);

    /* check for buffered characters */
    if (GetBufferedCharacters() > 0) {
	--incnt;
	return (*inptr++);
    }

    /* set the timeout timer */
    timerReq->tr_time.tv_secs = tmo;
    timerReq->tr_time.tv_micro = 0;
    SendIO(timerReq);

    /* setup to read a single character */
    SetupRead();

    /* compute the signal mask */
    mask = (1L << readReq->IOSer.io_Message.mn_ReplyPort->mp_SigBit)
         | (1L << timerReq->tr_node.io_Message.mn_ReplyPort->mp_SigBit)
		 | SIGBREAKF_CTRL_C;

    /* wait for a character or a timeout */
    for (;;) {

	/* wait for the read request to complete or timeout */
	rmask = Wait(mask);

	/* check for the abort signal */
	if (rmask & SIGBREAKF_CTRL_C) {
	    AbortIO(timerReq);	/* abort the timer request */
	    WaitIO(timerReq);
	    AbortIO(readReq);	/* abort the read request */
	    WaitIO(readReq);
	    longjmp(xfr_error,1);
	}

	/* check for timer completion */
	if (CheckIO(timerReq)) {
	    WaitIO(timerReq);	/* dequeue the request */
	    AbortIO(readReq);	/* abort the read request */
	    WaitIO(readReq);	/* and wait for abort to complete */
	    return (DT_TIME);	/* return a timeout indication */
	}

	/* check for read completion */
	if (CheckIO(readReq)) {
	    WaitIO(readReq);	/* dequeue the request */
	    if (readReq->IOSer.io_Actual == 1L) {
		AbortIO(timerReq);	/* abort the timer request */
		WaitIO(timerReq);	/* and wait for abort to complete */
		return (inbuf[0]);	/* return the character */
	    }
	    SetupRead();	/* setup another read */
	}
    }
}

/* md_iflush - flush the input buffer */
md_iflush(flg)
  int flg;
{
    int cnt;
    incnt = 0;	/* clear the input buffer */
    if (flg) {	/* flush the line */
	log("flushing input buffer");
	for (cnt = 0; md_get(1) != DT_TIME; ++cnt)
	    ;
	log("flushed %d bytes",cnt);
    }
}

/* md_put - output a character to the modem */
md_put(ch)
  int ch;
{
    char buf[1];
    buf[0] = ch;
    writeReq->IOSer.io_Command = CMD_WRITE;
    writeReq->IOSer.io_Data = (APTR)&buf[0];
    writeReq->IOSer.io_Length = (LONG)1;
    DoIO(writeReq);
}

/* md_write - write a packet to the modem */
int md_write(buf,len)
  char *buf;
  int len;
{
    writeReq->IOSer.io_Command = CMD_WRITE;
    writeReq->IOSer.io_Data = (APTR)&buf[0];
    writeReq->IOSer.io_Length = (LONG)len;
    DoIO(writeReq);
}

/* GetBufferedCharacters - get characters already buffered */
static int GetBufferedCharacters()
{
    /* do a status query */
    readReq->IOSer.io_Command = SDCMD_QUERY;
    DoIO(readReq);

    /* read the characters that are in the input buffer */
    if (readReq->IOSer.io_Actual > 0L) {

	/* compute the number of character to read */
	if ((incnt = (int)readReq->IOSer.io_Actual) > IBSIZE)
	    incnt = IBSIZE;
	inptr = inbuf;

	/* get the buffered characters */
	readReq->IOSer.io_Command = CMD_READ;
	readReq->IOSer.io_Length = (LONG)incnt;
	readReq->IOSer.io_Data = (APTR)&inbuf[0];
	DoIO(readReq);
    }

    /* return the number of characters read */
    return (incnt = (int)readReq->IOSer.io_Actual);
}

/* SetupRead - setup to read a single character */
static SetupRead()
{
    readReq->IOSer.io_Command = CMD_READ;
    readReq->IOSer.io_Length = (LONG)1;
    readReq->IOSer.io_Data = (APTR)&inbuf[0];
    BeginIO(readReq);
}

/* log - log an error */
log(fmt,a1,a2,a3,a4,a5,a6,a7,a8)
  char *fmt;
{
/*
    extern int blknum;
    fprintf(stderr,fmt,a1,a2,a3,a4,a5,a6,a7,a8);
    fprintf(stderr,", blknum=%d\n",blknum);
*/
}
