/*
	serial device interface for Atari ST

	v0.00 01.01.93 DFN Initial Version
*/

#include <stdio.h>
#include <stdlib.h>
#include <dos.h>		
#include <sys/types.h>
#include <sys/stat.h>
#include <osbind.h>

#include "global.h"
#include "config.h"
#include "mbuf.h"
#include "internet.h"
#include "iface.h"
#include "st.h"
#include "cmdparse.h"
#include "asy.h"
#include "st_asy.h"
#include "devparam.h"

struct asy Asy[ASY_MAX];
void __asy_rxover(void);
void __asy_txover(void);
void __stdargs asy_tx(int dev, void *p1, void *p2);
void asy_flush(int dev);

void		(*rxover)();
void		(*txover)();
void     int_rxover(void);
void		int_txover(void);

/* Interface list header */

int Nasy = 0;										/* number of async devices 			*/

int TX_pwait = 0;

/*
	asy_init - Initialize async port "dev"
*/

int asy_init(int dev,
				struct iface *iface,
				char	*addr,
				char	*vec,
				int16 bufsize,
				int	trigchar,
				char	monitor,
				long	speed)
{
	char   *ifn;
	char   *bufp_in, *bufp_out;
	struct iorec *ip;
	struct asy *asy;

	Nasy++;
	asy = &Asy[dev];

	asy->iface 			= iface;					/* link up to interface structure */
	asy->rxchar 		= 0;
	asy->txchar 		= 0;
	asy->rxover       = 0;
	asy->txover			= 0;
	asy->mode 			= trigchar;
	asy->input_active = 0;
	asy->device_name 	= strdup(addr);
	asy->unit 			= atoi(vec);
	asy->buflen 		= bufsize;
	asy->vec 			= atoi(vec);			/* dev to resend bytes to */
	asy->addr 			= 1;

	/*
		force user to allocate more memory than he already has.
	   If no memory is allocated, asy_stop() may behave funny...
	*/
	
	if (bufsize <= 256)	/* only allocate a bigger buffer */
		return -1;

	if ((bufp_in = malloc(bufsize)) == NULLCHAR){
		printf("asy_init(%d): no memory for rx buffer \n", dev);
		return -1;
	}

	if ((bufp_out = malloc(bufsize)) == NULLCHAR){
		printf("asy_init(%d): no memory for tx buffer \n", dev);
		return -1;
	}

	/* Save original IOREC values */

	ip = Iorec((asy->addr)-1);	/* Iorec wants AUX: = 0, MIDI = 2 */

	Jdisint(12);					/* disable RS-232 interrupt */

	asy->in = ip;
	memcpy(&asy->oldin, ip, sizeof(struct iorec));
	
	if (asy->addr == RS232) {	/* increase RS-232 transmit buffer? 		*/
		ip++;
		asy->out = ip;
		memcpy(&asy->oldout,ip,sizeof(struct iorec));
	}

/* Set up receiver FIFO 																	*/
	
	asy->in->ibuf 		= bufp_in;						/* buffer pointer				*/
	asy->in->ibufsiz 	= bufsize;						/* size							*/
	asy->in->ibufhd 	= asy->in->ibuftl = 0;		/* head & tail indices		*/
	asy->in->ibuflow	= bufsize / 4;					/* low water mark				*/
	asy->in->ibufhi 	= bufsize - (bufsize / 4);	/* high water mark			*/
#if 0
	asy->out->ibuf 	= bufp_out;						/* buffer pointer				*/
	asy->out->ibufsiz = bufsize;						/* size							*/
	asy->out->ibufhd 	= asy->out->ibuftl = 0;    /* head & tail indices		*/
	asy->out->ibuflow = bufsize / 4;             /* low water mark          */
	asy->out->ibufhi 	= bufsize - (bufsize / 4);	/* high water mark			*/
#endif
	txover = Setexc(73, int_txover);			/* link in overrun handlers		*/
	rxover = Setexc(75, int_rxover);

	Jenabint(12);									/* enable RS-232 interrupts 		*/
	Offgibit(0xEF);								/* raise DTR							*/
	
	asy->input_len 		= 0;					/* clear input buffer 				*/
	asy->input_active 	= 0;
	asy->serial_open 		= 1;
	asy->output_active 	= 0;

	asy_speed(dev, speed);
	
	iface->txproc = newproc( ifn = if_name(iface," tx"),  /* tx thread      */
			256, asy_tx, dev, iface, NULL, 0);

	return (int)Nasy;
}

/*****************************************************************************
	asy_stop  - restore old iorec and frees memory allocated to the RS-232
					buffers.  Cycle DTR to force modem to drop the line. 
*****************************************************************************/

int asy_stop(struct iface *iface)
{
	struct asy *asy;
	
	asy = &Asy[iface->dev];

#ifdef DEBUG
	printf("asy_stop: iface=0x%lx dev=%d \n", iface, iface->dev);
	fflush(stdout);
#endif

	(void)Jdisint(12);		/* disable RS-232 interrupts */

	Setexc(73, txover);							/* restore overrun handlers		*/
	Setexc(75, rxover);		

	free(asy->in->ibuf);		/* free the buffer */

	/* Restore old iorecs */
	
	memcpy(asy->in, &asy->oldin, sizeof(struct iorec));
	
	if (asy->addr == RS232)	{
		int32	t;										/* temporary timer variable		*/

		memcpy(asy->out, &asy->oldout, sizeof(struct iorec));
  
		Ongibit(0x10);									/* drop DTR, so modem drops line */	
		t = 500 + msclock();                   /* get time + 500ms              */
		while (t > msclock())						/* wait                          */
			;
		Offgibit(0xEF);                        /* raise DTR                     */
	}
	
	(void)Jenabint(12);								/* enable RS-232 interrupts */ 

	Nasy--;
	return 0;
}

/*****************************************************************************
	asy_speed - Set async line speed
*****************************************************************************/
int asy_speed(int dev, long speed)
{
	int baud; 										/* int result; */
	struct	asy *asy;

	asy = &Asy[dev];
	
	if (speed <= 0 || dev >= Nasy)
		return -1;

	asy->speed = speed;			/* shouldn't this be done in slip.c? */

	switch (speed) {
		case 300:
			baud = 9;	/* how slow can you get? :-) */
			break;
		case 1200:
			baud = 7;
			break;
		case 2400:
			baud = 4;
			break;
		case 4800:
			baud = 2;
			break;
		case 9600:
			baud = 1;
			break;
		case 19200:
			baud = 0;
			break;
		case 38400:
			Rsconf(0,2,0x08,-1,-1,-1);
			Xbtimer(3,1,8,(void *)-1);
			asy_flush(dev);
			return 0;
		default:
			printf("asy_speed: unknown RS-232 speed (%d).\n", speed);
			return -1;
	}
/*	(void) Rsconf(baud,0,0x88,-1,-1,-1); */	/* no flow control 	*/
	(void) Rsconf(baud,2,0x88,-1,-1,-1);		/* RTS/CTS enabled	*/
	asy_flush(dev);

	return 0;
}

/*****************************************************************************
	asy_flush - flush the input buffer of device dev (either aux: or midi)
   May be useful, because setting the baudrate causes an 0x7f to be sent.
*****************************************************************************/

void asy_flush(int dev)
{
	int st_dev;
	long c;					/* Bconin returns a long */
	struct asy *asy = &Asy[dev];

	st_dev = asy->addr;

	while (Bconstat(st_dev) == -1) {			/* at least 1 char available		*/
		c = Bconin(st_dev);						/* read character						*/
	}
}

/*****************************************************************************
	asy_ioctl - Async line control
*****************************************************************************/

int32 asy_ioctl (struct iface *iface, int cmd, int set, int32 val)
{
	int32	retval;
	struct asy *asy;
	
	asy = &Asy[iface->dev];

	switch (cmd) {
		case PARAM_SPEED:
			if (set)
				asy_speed(iface->dev, val);
			return Asy[iface->dev].speed;

		case PARAM_DTR:
			if (set) {
				val ?  Offgibit(0xEF) : Ongibit(0x10);
				asy->dtr_usage = (val) ? MOVED_UP : MOVED_DOWN;
			}
			retval = Giaccess(0, 0x0E) & 0x10; 		/* get DTR status				*/
			tprintf("DTR is %s \n", retval ? "DOWN" : "UP");
			return retval ? 0 : 1;

		case PARAM_RTS:
			if (set) {
				val ? Offgibit(0xF7) : Ongibit(0x08);
				asy->rts_usage = (val) ? MOVED_UP : MOVED_DOWN;
			}
			retval = Giaccess(0, 0x0E) & 0x08;		/* get RTS status				*/
			tprintf("RTS is %s \n", retval ? "DOWN" : "UP");
			return retval ? 0 : 1;
						
		case PARAM_UP:
			Offgibit(0xF7);						/* raise RTS							*/
			Offgibit(0xEF);						/* raise DTR							*/
			asy->rts_usage = MOVED_UP;
			asy->dtr_usage = MOVED_UP;
			return TRUE;

		case PARAM_DOWN:
			Ongibit(0x08);							/* drop RTS								*/
			Ongibit(0x10);							/* drop DTR								*/
			asy->rts_usage = MOVED_DOWN;
			asy->dtr_usage = MOVED_DOWN;
			return FALSE;
		}


	return FALSE;
}

/***********************************************************************
	asy_rxover - RS232 receiver overrun interrupt handler
***********************************************************************/

void __saveds asy_rxover(void)
{
	struct asy *asy = &Asy[0];

	asy->rxover = asy->rxover + 1;
}
/***********************************************************************
	asy_txover - RS232 transmitter overrun interrupt handler
***********************************************************************/

void __saveds asy_txover(void)
{
	struct asy *asy = &Asy[0];

	asy->txover = asy->txover + 1;
}
 

 
/*****************************************************************************
	get_asy - Receive character from asynch line, called from slip.c
				 Blocks if no character available.  Returns -1 if pwait is
				 interrupted.
*****************************************************************************/
 
int get_asy(int dev)
{
 	struct asy *asy = &Asy[dev];
	
	while (Bconstat(asy->addr) == 0)			/* 0 - no char, -1 char available	*/
		if (pwait(NULL) != 0)					/* giveup the CPU if no char 			*/
			return -1;								/* signal received						*/
			
	asy->input_len--;
	asy->rxchar++;									/* total number of chars received 	*/

	return Bconin(asy->addr);					/* return i/p character					*/

}

/*****************************************************************************
	asy_len - get length of async input queue
	          returns 1 if characters are available, else 0
*****************************************************************************/

int asy_len(int dev)
{
 	struct asy *asy = &Asy[dev];
	
	if (Bconstat(asy->addr) == 0)			/* 0 - no char, -1 char available	*/
		return 0;								/* no characters waiting				*/
	else
		return 1;								/* 1 or more characters waiting		*/	
}
 
/*****************************************************************************
	asy_send - queue an mbuf to the async device, called from slip.c
*****************************************************************************/

int asy_send(int dev, struct mbuf *bp)
{
	if (dev < 0 || dev >= Nasy)
		return -1;

	enqueue(&Asy[dev].sndq, bp);

	return 0;
}

/*****************************************************************************
	asy_tx - send mbufs to the serial device
*****************************************************************************/
 
void __stdargs asy_tx(int dev, void *p1, void *p2)
{
	int st_dev;
	unsigned short i = 0;
	struct mbuf *bp;
	struct asy *asy = &Asy[dev];

	st_dev = asy->addr;
	
	for( ; ;) {
		while (asy->sndq == NULLBUF)
			pwait(&asy->sndq);

		bp = dequeue(&asy->sndq);

		if (bp != NULLBUF) {
			for (i = 0; i < bp->cnt; ++i) {
				while (Bcostat(st_dev) == 0) {		/* if serial device not ready		 */
					TX_pwait += 1;							/* inc the wait count             */
					pwait(NULL);							/*    giveup the CPU					 */
				}
				(void)Bconout(st_dev, bp->data[i]);	/* xmit byte 1= AUX: 3=MIDI:		 */
   		}
			asy->txchar += bp->cnt;						/* increase count of tx chars     */
		}
		bp = free_mbuf(bp);								/* do next buf on chain		 */
	}
}

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

static void asyinfo(struct asy *asy, int verbose)
{
	short	tx_head, tx_tail;							
	short rx_head, rx_tail;
	short chars_in, chars_out;

	tx_head = asy->out->ibufhd;					/* transmitter head pointer	*/
	tx_tail = asy->out->ibuftl;					/* transmiter tail pointer		*/

	rx_head = asy->in->ibufhd;						/* receiver head pointer		*/
	rx_tail = asy->in->ibuftl;						/* receiver tail pointer		*/
	
	if ((chars_in  = rx_head - rx_tail) < 0)
		chars_in = asy->in->ibufsiz - rx_tail + rx_head;

	if ((chars_out = tx_head - tx_tail) < 0)
		chars_out = asy->out->ibufsiz - tx_tail + tx_head;

	tprintf("%s: %8d baud (%s unit %d)\n",
		asy->iface->name, asy->speed, asy->device_name, asy->unit);
		
	tprintf(" RX: %8lu chars %8lu o/r %6d in buf (H=%d T=%d B=%d)\n",
				asy->rxchar, asy->rxover, chars_in,
				asy->in->ibufhd, asy->in->ibuftl, asy->in->ibufsiz);
				
	tprintf(" TX: %8lu chars %8lu o/r %6d in buf (H=%d T=%d B=%d)\n",
				asy->txchar, asy->txover, chars_out,
				asy->out->ibufhd, asy->out->ibuftl, asy->out->ibufsiz); 
	tprintf("     pwait = %d \n", TX_pwait);
}

/*****************************************************************************
	doasystat - 
*****************************************************************************/

int doasystat(int argc, char *argv[], void *envp)
{
	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);
}
