/*
 *
 *  Copyright © 1987, 1989, 1990
 *  Louis A. Mamakos, WA3YMH
 *
 *  This work, or any derivations thereof, may be used for non-commercial
 *  purposes only.  So there.
 */

/*
 *  This module support an interface to an Amiga device driver, usually
 *  "serial.device", to be used in KISS/SLIP/NRS mode.  The device and unit
 *  to be used are specified on the "attach" command and passed in.
 */

#include <exec/types.h>
#include <exec/devices.h>
#include <exec/io.h>
#include <exec/errors.h>
#ifdef	LATTICE
#include <proto/exec.h>
#endif
#include "global.h"
#include "config.h"
#include "hardware.h"
#include "mbuf.h"
#include "iface.h"
#include "proc.h"
#include "cmdparse.h"
#include "asy.h"
#include "slip.h"
#include "commands.h"
#ifdef	NRS
#include "nrs.h"
#endif

#include "amiga/serdev.h"

struct asy Asy[ASY_MAX];
int Nasy = 0;

/* Initialize asynch port "dev" */

/*
 *  We will make the bold and rash assumption that the asy link will be used
 *  for slip and slip-like stuff.  That is, we assume that there is an
 *  an end-of-frame character that we can have the serial.device driver look
 *  for.  Thus, we can fire up a single I/O request and have the whole frame
 *  come back at once.  If we're in NET/ROM serial mode, we'll choose end of
 *  frame character appropriately.
 */
int
asy_init(dev, iface, device, unit, bufsize, trigchar, cts)
int dev;
struct iface *iface;
char *device, *unit;	/* attach args for device driver name and unit */
unsigned bufsize;
int trigchar; /*MARCPPP: was char*/
char cts; /*MARCPPP: was int*/
{
	char serinitstr[1];
	int serinitlen;
	register struct asy *asy;

	serinitstr[0] = FR_END;	/* initialize initialization string to a */
	serinitlen = 1;		/* frame end to flush receiver */

	asy = &Asy[dev];

/*printf("Asy_init!\n");
fflush(stdout);*/
	if ((asy->serinp = CreatePort(0L, 0L)) == NULL)
		clean("Can't create serial input port");

	if ((asy->seroutp = CreatePort(0L, 0L)) == NULL)
		clean("Can't create serial output port");


	if (bufsize < 576) {
		bufsize = 576;
		tprintf("asy_init: bufsize too small.. set to %d\n", bufsize);
	}
	if (bufsize > 20480) {
		bufsize = 20480;
		tprintf("asy_init: bufsize too large.. set to %d\n", bufsize);
	}

	/*
	 * Open serial device.
	 */
	asy->serin.io_SerFlags = SERF_XDISABLED | SERF_RAD_BOOGIE;  /* ? */
	asy->serin.io_Status = 0;
	asy->serin.io_RBufLen = bufsize;
	asy->serin.IOSer.io_Command = 0;
	asy->speed = asy->serin.io_Baud = 2400;	/* default speed */
	asy->iface = iface;	/* link up to interface structure */
	asy->rxchar = 0;
	asy->txchar = 0;

	asy->mode = trigchar;
	asy->in_sigbit = 1L << asy->serinp->mp_SigBit;
	asy->out_sigbit = 1L << asy->seroutp->mp_SigBit;
	asy->input_active = 0;
	asy->device_name = strdup(device);
	asy->unit = atoi(unit);
/*printf("bef asy opendev\n");*/
fflush(stdout);
	if (OpenDevice(asy->device_name, asy->unit,
			(struct IORequest *)  &asy->serin, 0L) != 0) {
		tprintf("Can't open %s unit %d, sorry\n", device, asy->unit);
		switch (asy->serin.IOSer.io_Error) {
		case IOERR_OPENFAIL:
			tprintf(" - open failure; device driver not present or "
				"device already\nin use?\n");
			break;

		case SerErr_DevBusy:
			tprintf(" - device/unit is busy\n");
			break;
		}
		clean("Can't open serial device");
	}
	asy->serin.IOSer.io_Message.mn_ReplyPort = asy->serinp;
	asy->serout = asy->serin;
	asy->serout.IOSer.io_Message.mn_ReplyPort = asy->seroutp;
	asy->buflen = bufsize;

	/* alloc input buffer */
/*printf("aft asy opendev\n");*/
fflush(stdout);
	if((asy->input_buffer = malloc(asy->buflen)) == NULL)
		clean("Can't allocate serial input buf");

	asy->serin.IOSer.io_Command = CMD_RESET;
	DoIO((struct IORequest *) &asy->serin);
	if (asy->serin.IOSer.io_Error)
		printf("Bad I/O status %d on RESET\n",
				asy->serin.IOSer.io_Error);


	asy->serin.io_TermArray.TermArray0 =
		asy->serin.io_TermArray.TermArray1 = (ULONG)asy->mode << 24 |
			(ULONG)asy->mode << 16 | (ULONG)asy->mode << 8 |
			asy->mode;

	asy->serin.IOSer.io_Data = (APTR) asy->input_buffer;
	asy->serin.IOSer.io_Length = asy->buflen;
	asy->input_len = 0;		/* clear input buffer */
	asy->serin.io_SerFlags = SERF_XDISABLED|SERF_EOFMODE|SERF_RAD_BOOGIE;
	asy->serin.IOSer.io_Flags = 0;
	asy->serin.IOSer.io_Command = CMD_READ;
	SendIO((struct IORequest *) &asy->serin);
	asy->input_active = 1;

	asy->serout.IOSer.io_Data = (APTR) serinitstr;
	asy->serout.IOSer.io_Length = serinitlen;
	asy->serout.IOSer.io_Command = CMD_WRITE;
	asy->serout.IOSer.io_Flags = 0;
	asy->serout.io_SerFlags = SERF_XDISABLED | SERF_RAD_BOOGIE;
	SendIO((struct IORequest *) &asy->serout);
	asy->serial_open = 1;
	asy->output_active = 1;

	/* re-compute Wait() mask with new device */
	buildwaitmask();

	return (int)Nasy;
}

int
asy_stop(iface)
struct iface *iface;
{
	register struct asy *asy;

	if (iface->dev >= ASY_MAX) {
		fprintf(stderr, "asy_stop: bad dev %d\n", iface->dev);
		return 0;
	}

	asy = &Asy[iface->dev];
	AbortIO((struct IORequest *) &asy->serin);
	AbortIO((struct IOReqeust *) &asy->serout);
	CloseDevice((struct IORequest *) &asy->serin);
	DeletePort(asy->serinp);
	DeletePort(asy->seroutp);
	free(asy->input_buffer);	/* release buffer */
	free(asy->device_name);
	buildwaitmask();
	return 0;
}

/* Asynchronous line I/O control */
int
asy_ioctl(iface, argc, argv)
	struct iface *iface;
	int argc;
	char *argv[];
{
	if (argc < 1) {
		printf("%d\r\n", Asy[iface->dev].speed);
		return 0;
	}
	return asy_speed(iface->dev, atoi(argv[0]));
}

/* Set asynch line speed */
int
asy_speed(dev,speed)
int dev;
int speed;
{
	struct asy *asy;
	int oldspeed;

	asy = &Asy[dev];
	if (asy->serial_open == 0)
		return -1;

	if (asy->input_active) {
		AbortIO((struct IORequest *) &asy->serin);
		WaitIO((struct IORequest *) &asy->serin);
		(void) GetMsg(asy->serinp);
	}
	oldspeed = asy->serin.io_Baud;
	asy->speed = asy->serin.io_Baud = speed;
	asy->serin.io_ReadLen = 8;
	asy->serin.io_WriteLen = 8;
	asy->serin.io_StopBits = 1;
	asy->serin.io_RBufLen = asy->buflen;
	asy->serin.io_TermArray.TermArray0 =
		asy->serin.io_TermArray.TermArray1 = (ULONG)asy->mode << 24 |
			(ULONG)asy->mode << 16 | (ULONG)asy->mode << 8 |
			asy->mode;


	asy->serin.IOSer.io_Command = SDCMD_SETPARAMS;
	asy->serin.IOSer.io_Flags = 0;
	DoIO((struct IORequest *) &asy->serin);
	if (asy->serin.IOSer.io_Error)
		printf("Bad I/O status %d on SETPARAMS\n",
					asy->serin.IOSer.io_Error);

	asy->serin.IOSer.io_Command = CMD_READ;
	asy->serin.IOSer.io_Data = (APTR) asy->input_buffer;
	asy->serin.IOSer.io_Length = asy->buflen;
	asy->input_len = 0;		/* clear input buffer */
	asy->serin.io_SerFlags = SERF_XDISABLED|SERF_EOFMODE|SERF_RAD_BOOGIE;
	asy->serin.IOSer.io_Flags = 0;	/* no quick I/O */
	SendIO((struct IORequest *) &asy->serin);
	asy->input_active = 1;

	doasystat(0, 0, NULL);	/*******/
	return 0;
}

/*
 *  This is the general plan:
 *
 *  Output:
 *	Output is actually started on the hardware by asy_output() below.  It
 *	is passed a device, buffer and byte count.  It checks the device not
 *	busy, WaitIOs on the previous I/O and then builds and submits the
 *	new I/O request.  It also marks the output as busy.
 *
 *	From the top, asy_send() queues an mbuf to the end of the output queue
 *	associated with the device.  This action will wake up the associated
 *	process which may begin output as described above.
 *
 *	The output process is in a loop in the asy_tx() function, waiting for
 *	mbufs to be queued to this interface.  It removes the mbuf from the
 *	queue, sends it on the idle transmitter, and waits for the I/O to
 *	complete.  It continues to send each buffer on the chain until it
 *	runs out, when it then waits for a new buffer to be queued.
 */

/* Send a buffer to serial transmitter */
static void
asy_output(dev,buf,cnt)
int dev;
char *buf;
unsigned short cnt;
{
	register struct asy *asy = &Asy[dev];

	if (dev >= Nasy)
		return;

	if (asy->output_active)
		return;		/* output already in progress */

	WaitIO((struct IORequest *) &asy->serout);
	(void) GetMsg(asy->seroutp);
	asy->serout.IOSer.io_Data = (APTR) buf;
	asy->serout.IOSer.io_Length = cnt;
	asy->serout.io_SerFlags = SERF_XDISABLED | SERF_RAD_BOOGIE;
	asy->serout.IOSer.io_Flags = 0;	/* no quick I/O */
	asy->serout.IOSer.io_Command = CMD_WRITE;
	asy->txchar += cnt;
	asy->output_active = 1;
	SendIO((struct IORequest *) &asy->serout);
}


/*
 * Receive characters from asynch line
 * Returns count of characters read
 */
int
get_asy(int dev)
{
 	register struct asy *asy = &Asy[dev];
	unsigned char c;

/*printf("GET_ASY\n");
fflush(stdout);*/
	while (asy->input_len == 0)	{  /* if buffer is empty.. */
		if (!asy->input_active) {
			asy->serin.IOSer.io_Command = CMD_READ;
			asy->serin.IOSer.io_Data =
				(APTR) asy->input_buffer;
			asy->serin.IOSer.io_Length = asy->buflen;
			asy->serin.io_SerFlags = 
			    SERF_XDISABLED|SERF_EOFMODE|SERF_RAD_BOOGIE;
			asy->serin.IOSer.io_Flags = 0; /* no quick I/O */
/*printf("SET_ASY: 1SENDIO()\n");
fflush(stdout);*/
			SendIO((struct IORequest *) &asy->serin);
/*printf("SET_ASY: 1AFTSENDIO()\n");
fflush(stdout);*/
			asy->input_active = 1;
		}
/*printf("SET_ASY: pwait()\n");
fflush(stdout);*/
		pwait(&asy->input_len);
/*printf("SET_ASY: AFT pwait()\n");
fflush(stdout);*/
	}

	asy->input_len--;
	asy->rxchar++;
	c = *asy->input_p++;

	if (asy->input_len == 0 && !asy->input_active) {
		/* if buffer is now empty, start up another input for 
		   next time */
		asy->serin.IOSer.io_Command = CMD_READ;
		asy->serin.IOSer.io_Data = (APTR) asy->input_buffer;
		asy->serin.IOSer.io_Length = asy->buflen;
		asy->serin.io_SerFlags = 
			SERF_XDISABLED|SERF_EOFMODE|SERF_RAD_BOOGIE;
		asy->serin.IOSer.io_Flags = 0;	/* no quick I/O */
/*printf("SET_ASY: 2SENDIO()\n");
fflush(stdout);*/
		SendIO((struct IORequest *) &asy->serin);
/*printf("SET_ASY: 2AFTSENDIO()\n");
fflush(stdout);*/
		asy->input_active = 1;
	}
/*printf("SET_ASY: byebye\n");
fflush(stdout);*/
	return c;
}

int
stxrdy(dev)
int dev;
{
	return Asy[dev].output_active == 0;
}

static void
asyinfo(struct asy *asy, int verbose) {
	tprintf("%s: rxchar %lu txchar %lu %d baud (%s unit %d)\n",
		asy->iface->name, asy->rxchar, asy->txchar,
		asy->speed, asy->device_name, asy->unit);

	if (verbose) {
		tprintf("\tVersion %d/%d: %s\n",
			asy->serin.IOSer.io_Device->dd_Library.lib_Version,
			asy->serin.IOSer.io_Device->dd_Library.lib_Revision,
			asy->serin.IOSer.io_Device->dd_Library.lib_IdString);
	}
}

int
doasystat(argc, argv, envp)
	int argc;
	char *argv[];
	void *envp;
{
	register struct asy *asy;
	int i;

	if (!Nasy) {
		tprintf("No asy interfaces attached.\n");
		return -1;
	}

	if (argc> 1) {
		for (i = 1; i < argc; i++) {
			struct asy *dev = NULL;

			for (asy = &Asy[0]; asy < &Asy[Nasy]; asy++) {
				if (strcmp(asy->iface->name, argv[i]) == 0) {
					dev = asy;
					break;
				}
			}
			if (dev)
				asyinfo(dev, 1);
			else
				tprintf("Unknown device '%s'\n", argv[i]);
		}
	} else 
		/* just dump them all out */
		for (asy = &Asy[0]; asy < &Asy[Nasy]; asy++)
			asyinfo(asy, 0);
}

int
asy_send(int dev, struct mbuf *bp)
{
/*printf("ASY_SEND\n"); fflush(stdout); getchar();*/
	if (dev < 0 || dev >= Nasy)
		return -1;
	enqueue(&Asy[dev].sndq, bp);
	return 0;
}

void
asy_tx(int dev, void *p1, void *p2)
{
	register struct mbuf *bp;
	struct asy *asyp;

	asyp = &Asy[dev];

	for(;;) {
		while(asyp->sndq == NULLBUF)
			pwait(&asyp->sndq);

/*printf("asy_tx [wakeup]\n"); fflush(stdout); getchar();*/

		bp = dequeue(&asyp->sndq);

		while(bp != NULLBUF) {
			/* Start the transmitter */
			asy_output(dev, bp->data, bp->cnt);
			/* Wait for completion */	
			while(asyp->output_active)
				pwait(asyp);
			/* Now do next buffer on chain */
			bp = free_mbuf(bp);
		}
	}
}

/*
 *  These routines get called from the main Amiga event handler.  When
 *  the Wait() in amiga.c returns, and the serial I/O bit is set, then this
 *  routine is called to handle and serial.device (or similar device) related
 *  events.
 */

/*
 *  Check to see if the current input serial I/O has completed.
 */
void
check_serin(asy)
	register struct asy *asy;
{
	register int error;

	if (CheckIO((struct IORequest *) &asy->serin) == NULL) {
#ifdef	notdef
		continue;
#else
		return;
#endif
	}
	if (error = WaitIO((struct IORequest *) &asy->serin)) {
		printf("(SERIN) WaitIO returns %d\n", error);
fflush(stdout);
	}
	(void) GetMsg(asy->serinp);
	if (asy->serin.IOSer.io_Error) {
		printf("Bad I/O stat %d (SERIN)\n",
			asy->serin.IOSer.io_Error);
fflush(stdout);
	}
	asy->input_active = 0;
	/*
	 * if, due to an error from the serial.device we didn't receive
	 * any input, then the get_asy() routine will post another input
	 * request
	 */

	/* input has completed.  fill in state variables */
	asy->input_len = asy->serin.IOSer.io_Actual;
	asy->input_p = asy->input_buffer;
	psignal(&asy->input_len, 1);
}

/*
 *  Check to see if the current output serial I/O request have completed or not.
 */
void
check_serout(asy)
	struct asy *asy;
{
	if (asy->output_active == 0)
		return;		/* shouldn't happen */

	if (CheckIO((struct IORequest *) &asy->serout) != NULL) {
		/* This buffer is done.  Poke those who want to be prodded */
		asy->output_active = 0;
		psignal(asy, 1);
	}
}
