/*

dart.c - Serial I/O emulator

Contributed by Bill Brendling.

*/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include "dart.h"
#include "common.h"
#include "diag.h"

static	 byte  ivec;
static	 int   iflags;
static struct
	{
	int		fdIn;
	int		fdOut;
	int		reg;
	BOOLEAN	rxint;
	byte	wr1;
	byte	wr3;
	byte	wr4;
	byte	wr5;
	byte	rr0;
	byte	rr1;
	byte	rx;
	} channel[2];

#define	 RR0_RX_AVAILABLE	  0x01
#define	 RR0_INT_PENDING	  0x02
#define	 RR0_TX_EMPTY		  0x04
#define	 RR0_DCD			  0x08
#define	 RR0_RI				  0x10
#define	 RR0_CTS			  0x20
#define	 RR0_BREAK			  0x80

#define	 RR1_ALL_SENT		  0x01
#define	 RR1_PARITY_ERROR	  0x10
#define	 RR1_RX_OVERRUN		  0x20
#define	 RR1_FRAMING_ERROR	  0x40

#define	 INT_SPECIAL		  0
#define	 INT_RX_AVAIL		  1
#define	 INT_TX_EMPTY		  2
#define	 INT_EXT_CHG		  3

static void set_int (int ch, int type)
	{
	iflags	|= ( 1 << ( 4 * ch + type ) );
	channel[0].rr0	 |=	0x02;
	diag_message (DIAG_DART_CFG, "DART Channel %d: Set interrupt %d: iflags = 0x%02x", ch, type, iflags);
	}

static void clear_int (int ch, int type)
	{
	iflags	&= ~ ( 1 << ( 4 * ch + type ) );
	if ( iflags == 0 )	channel[0].rr0 &= 0xfd;
	diag_message (DIAG_DART_CFG, "DART Channel %d: Clear interrupt %d: iflags = 0x%02x", ch, type, iflags);
	}

static void dart_rx (int ch, byte value)
	{
	diag_message (DIAG_DART_DATA, "DART Channel %d: Received character = 0x%02x", ch, value);
	channel[ch].rx	 =	value;
	channel[ch].rr0	 |=	RR0_RX_AVAILABLE;
	if ( ( (channel[ch].wr1 & 0x18) == 0x08 ) && ( channel[ch].rxint ) )
		{
		set_int (ch, INT_RX_AVAIL);
		channel[ch].rxint  =  FALSE;
		}
	if ( channel[ch].wr1 & 0x10 )
		{
		set_int (ch, INT_RX_AVAIL);
		}
	}

static void dart_pump (void)
	{
	int	 ch;
	int	 status;
	byte value;
	for ( ch = 0; ch < 2; ++ch )
		{
		if ( ( ! ( channel[ch].rr0 & RR0_RX_AVAILABLE ) ) && ( channel[ch].fdIn >= 0 ) )
			{
			status	 =	read (channel[ch].fdIn, &value, 1);
			if ( status == 1 )
				{
				dart_rx (ch, value);
				}
			else if ( ( status == -1 ) && ( errno != EAGAIN ) && ( errno != EWOULDBLOCK ) )
				{
				fatal ("Read error %d on DART Channel %d", errno, ch);
				}
			}
		}
	}

void dart_out (word port, byte value)
	{
	int	 ch	=  port & 0x01;
	diag_message (DIAG_DART_PORTS, "Write to DART port 0x%02x: 0x%02x", port, value);
	if ( port & 0x02 )
		{
		switch ( channel[ch].reg )
			{
			case  0:
			{
			channel[ch].reg	  =	 value & 0x07;
			diag_message (DIAG_DART_CFG, "DART Channel %d WR0: Select register %d", ch, channel[ch]);
			switch ( ( value >> 3 ) & 0x07 )
				{
				case 0x00:
				{
				diag_message (DIAG_DART_CFG, "DART Channel %d Command: Null Code", ch);
				break;
				}
				case 0x01:
				{
				diag_message (DIAG_DART_CFG, "DART Channel %d Command: Not used", ch);
				break;
				}
				case 0x02:
				{
				diag_message (DIAG_DART_CFG, "DART Channel %d Command: Reset Ext/Status Interrupts", ch);
				clear_int (ch, INT_SPECIAL);
				break;
				}
				case 0x03:
				{
				diag_message (DIAG_DART_CFG, "DART Channel %d Command: Channel reset", ch);
				channel[ch].reg	 =	0;
				channel[ch].rr0	 =	RR0_TX_EMPTY;
				channel[ch].rr1	 =	RR1_ALL_SENT;
				iflags	=  0;
				break;
				}
				case 0x04:
				{
				diag_message (DIAG_DART_CFG, "DART Channel %d Command: Enable Int on next Rx character", ch);
				channel[ch].rxint	=  TRUE;
				break;
				}
				case 0x05:
				{
				diag_message (DIAG_DART_CFG, "DART Channel %d Command: Reset Tx Int Pending", ch);
				clear_int (ch, INT_TX_EMPTY);
				break;
				}
				case 0x06:
				{
				diag_message (DIAG_DART_CFG, "DART Channel %d Command: Error Reset", ch);
				channel[ch].rr1	 &=	0x01;
				break;
				}
				case 0x07:
				{
				diag_message (DIAG_DART_CFG, "DART Channel %d Command: Return from Int", ch);
				break;
				}
				}
			break;
			}
			case  1:
			{
			channel[ch].wr1	  =	 value;
			if ( channel[ch].wr1 & 0x01 ) diag_message (DIAG_DART_CFG, "DART Channel %d WR1: Ext Int Enable", ch);
			if ( channel[ch].wr1 & 0x02 ) diag_message (DIAG_DART_CFG, "DART Channel %d WR1: Tx Int Enable", ch);
			if ( channel[ch].wr1 & 0x20 ) diag_message (DIAG_DART_CFG, "DART Channel %d WR1: Wait/Ready on R/T", ch);
			if ( channel[ch].wr1 & 0x40 ) diag_message (DIAG_DART_CFG, "DART Channel %d WR1: Wait/Ready Function", ch);
			if ( channel[ch].wr1 & 0x80 ) diag_message (DIAG_DART_CFG, "DART Channel %d WR1: Wait/Ready Enable", ch);
			if ( ( channel[ch].wr1 & 0x18 ) == 0x00 )
				diag_message (DIAG_DART_CFG, "DART Channel %d WR1: Rx Int Disable", ch);
			if ( ( channel[ch].wr1 & 0x18 ) == 0x08 )
				diag_message (DIAG_DART_CFG, "DART Channel %d WR1: Rx Int on first character", ch);
			if ( ( channel[ch].wr1 & 0x18 ) == 0x10 )
				diag_message (DIAG_DART_CFG, "DART Channel %d WR1: Int on all Rx characters (parity affects vector)", ch);
			if ( ( channel[ch].wr1 & 0x18 ) == 0x18 )
				diag_message (DIAG_DART_CFG, "DART Channel %d WR1: Int on all Rx characters (parity does not affect vector)", ch);
			channel[ch].reg	  =	 0;
			break;
			}
			case  2:
			{
			ivec  =	 value;
			diag_message (DIAG_DART_CFG, "DART Channel %d WR2: Interrupt vector = 0x%02x", ch, ivec);
			channel[ch].reg	  =	 0;
			break;
			}
			case  3:
			{
			channel[ch].wr3	  =	 value;
			if ( channel[ch].wr3 & 0x01 ) diag_message (DIAG_DART_CFG, "DART Channel %d WR3: Rx Enable", ch);
			if ( channel[ch].wr3 & 0x20 ) diag_message (DIAG_DART_CFG, "DART Channel %d WR3: Auto enables", ch);
			if ( ( channel[ch].wr3 & 0xC8 ) == 0x00 )
				diag_message (DIAG_DART_CFG, "DART Channel %d WR3: Rx 5 bits/character", ch);
			if ( ( channel[ch].wr3 & 0xC8 ) == 0x40 )
				diag_message (DIAG_DART_CFG, "DART Channel %d WR3: Rx 7 bits/character", ch);
			if ( ( channel[ch].wr3 & 0xC8 ) == 0x80 )
				diag_message (DIAG_DART_CFG, "DART Channel %d WR3: Rx 6 bits/character", ch);
			if ( ( channel[ch].wr3 & 0xC8 ) == 0xC0 )
				diag_message (DIAG_DART_CFG, "DART Channel %d WR3: Rx 8 bits/character", ch);
			channel[ch].reg	  =	 0;
			break;
			}
			case  4:
			{
			channel[ch].wr4	  =	 value;
			if ( channel[ch].wr4 & 0x01 ) diag_message (DIAG_DART_CFG, "DART Channel %d WR4: Parity Enable", ch);
			if ( channel[ch].wr4 & 0x02 ) diag_message (DIAG_DART_CFG, "DART Channel %d WR4: Parity Even", ch);
			if ( ( channel[ch].wr4 & 0x0C ) == 0x00 )
				diag_message (DIAG_DART_CFG, "DART Channel %d WR4: Invalid stop bits", ch);
			if ( ( channel[ch].wr4 & 0x0C ) == 0x04 )
				diag_message (DIAG_DART_CFG, "DART Channel %d WR4: 1 stop bit/character", ch);
			if ( ( channel[ch].wr4 & 0x0C ) == 0x08 )
				diag_message (DIAG_DART_CFG, "DART Channel %d WR4: 1 1/2 stop bits/character", ch);
			if ( ( channel[ch].wr4 & 0x0C ) == 0x0C )
				diag_message (DIAG_DART_CFG, "DART Channel %d WR4: 2 stop bits/character", ch);
			if ( ( channel[ch].wr4 & 0xC0 ) == 0x00 )
				diag_message (DIAG_DART_CFG, "DART Channel %d WR4: x1 clock mode", ch);
			if ( ( channel[ch].wr4 & 0xC0 ) == 0x40 )
				diag_message (DIAG_DART_CFG, "DART Channel %d WR4: x16 clock mode", ch);
			if ( ( channel[ch].wr4 & 0xC0 ) == 0x80 )
				diag_message (DIAG_DART_CFG, "DART Channel %d WR4: x32 clock mode", ch);
			if ( ( channel[ch].wr4 & 0xC0 ) == 0xC0 )
				diag_message (DIAG_DART_CFG, "DART Channel %d WR4: x64 clock mode", ch);
			channel[ch].reg	  =	 0;
			break;
			}
			case  5:
			{
			channel[ch].wr5	  =	 value;
			if ( channel[ch].wr5 & 0x02 ) diag_message (DIAG_DART_CFG, "DART Channel %d WR5: RTS", ch);
			if ( channel[ch].wr5 & 0x08 ) diag_message (DIAG_DART_CFG, "DART Channel %d WR5: Tx Enable", ch);
			if ( channel[ch].wr5 & 0x10 ) diag_message (DIAG_DART_CFG, "DART Channel %d WR5: Send Break", ch);
			if ( channel[ch].wr5 & 0x80 ) diag_message (DIAG_DART_CFG, "DART Channel %d WR5: DTR", ch);
			channel[ch].reg	  =	 0;
			break;
			}
			default:
			{
			fatal ("Write to ivalid DART register");
			break;
			}
			}
		}
	else
		{
		diag_message (DIAG_DART_DATA, "DART Channel %d: Transmit 0x%02x", ch, value);
		// For test purposes assume sent immediately.
		if ( channel[ch].fdOut >= 0 )  write (channel[0].fdOut, &value, 1);
		if ( channel[ch].wr1 & 0x02 )  set_int (ch, INT_TX_EMPTY);
		channel[ch].rr0	|= 0x04;
		channel[ch].rr1	|= 0x01;
		// Loopback test.
		// dart_rx (ch, value);
		}
	dart_pump ();
	}

byte dart_in (word port)
	{
	int	ch	=  port & 0x01;
	byte value =  0;
	diag_message (DIAG_DART_PORTS, "Read from DART port 0x%02x", port);
	if ( port & 0x02 )
		{
		switch ( channel[ch].reg )
			{
			case  0:
			{
			value =	 channel[ch].rr0;
			if ( value & RR0_RX_AVAILABLE )	 diag_message (DIAG_DART_CFG, "DART Channel %d RR0: Rx Character Available", ch);
			if ( value & RR0_INT_PENDING )	 diag_message (DIAG_DART_CFG, "DART Channel %d RR0: Interrupt Pending", ch);
			if ( value & RR0_TX_EMPTY )		 diag_message (DIAG_DART_CFG, "DART Channel %d RR0: Transmit Empty", ch);
			if ( value & RR0_DCD )			 diag_message (DIAG_DART_CFG, "DART Channel %d RR0: Data Carrier Detect", ch);
			if ( value & RR0_RI )		  	 diag_message (DIAG_DART_CFG, "DART Channel %d RR0: Ring Indicator", ch);
			if ( value & RR0_CTS )			 diag_message (DIAG_DART_CFG, "DART Channel %d RR0: Clear to Send", ch);
			if ( value & RR0_BREAK )		 diag_message (DIAG_DART_CFG, "DART Channel %d RR0: Break", ch);
			break;
			}
			case  1:
			{
			value =	 channel[ch].rr1;
			if ( value & RR1_ALL_SENT )		 diag_message (DIAG_DART_CFG, "DART Channel %d RR1: All Sent", ch);
			if ( value & RR1_PARITY_ERROR )	 diag_message (DIAG_DART_CFG, "DART Channel %d RR1: Parity Error", ch);
			if ( value & RR1_RX_OVERRUN )	 diag_message (DIAG_DART_CFG, "DART Channel %d RR1: RX Overrun", ch);
			if ( value & RR1_FRAMING_ERROR ) diag_message (DIAG_DART_CFG, "DART Channel %d RR1: Framing Error", ch);
			break;
			}
			case  2:
			{
			value =	 ivec;
			diag_message (DIAG_DART_CFG, "DART Channel %d RR2: Interrupt Vector = 0x%02x", ch, value);
			break;
			}
			default:
			{
			fatal ("Read from invalid DART register");
			break;
			}
			}
		}
	else
		{
		value  =  channel[ch].rx;
		channel[ch].rr0	&= ~RR0_RX_AVAILABLE;
		clear_int (ch, INT_RX_AVAIL);
		diag_message (DIAG_DART_DATA, "DART Channel %d: Read data = 0x%02x", ch, value);
		}
	dart_pump ();
	return	 value;
	}

int dart_int_pending (void)
	{
	int	 ipend =  -1;
	dart_pump ();
	if ( channel[1].wr1 & 0x04 )
		{
		if ( iflags & 0x01 )		ipend	=	( ivec & 0xf1 ) | 0x0E;
		else if ( iflags & 0x02 )	ipend	=	( ivec & 0xf1 ) | 0x0C;
		else if ( iflags & 0x04 )	ipend	=	( ivec & 0xf1 ) | 0x08;
		else if ( iflags & 0x08 )	ipend	=	( ivec & 0xf1 ) | 0x0A;
		else if ( iflags & 0x10 )	ipend	=	( ivec & 0xf1 ) | 0x06;
		else if ( iflags & 0x20 )	ipend	=	( ivec & 0xf1 ) | 0x04;
		else if ( iflags & 0x40 )	ipend	=	( ivec & 0xf1 ) | 0x00;
		else if ( iflags & 0x80 )	ipend	=	( ivec & 0xf1 ) | 0x02;
		}
	else
		{
		if ( iflags != 0 ) ipend	=	ivec;
		}
	if ( ipend > -1 )
		diag_message (DIAG_DART_CFG, "DART Interrupts: Flags = 0x%02x, base = 0x%02x, vector = 0x%02x",
			iflags, ivec, ipend);
	return	ipend;
	}

void dart_init (void)
	{
	memset (&channel, 0, sizeof (channel));
	channel[0].fdIn	 =	-1;
	channel[0].fdOut =	-1;
	channel[1].fdIn	 =	-1;
	channel[1].fdOut =	-1;
	}

void dart_read (int ch, const char *psFile)
	{
	if ( channel[ch].fdIn >= 0 )
		{
		close (channel[ch].fdIn);
		channel[ch].fdIn	=  -1;
		}
	if ( ( psFile != NULL ) && ( psFile[0] != '\0' ) )
		{
		channel[ch].fdIn   =  open (psFile, O_RDONLY | O_NONBLOCK);
		if ( channel[ch].fdIn == -1 )
			fatal ("Error %d when opening file \"%s\" for read on DART channel %d.", errno, psFile, ch);
		}
	}

void dart_write (int ch, const char *psFile)
	{
	if ( channel[ch].fdOut >= 0 )
		{
		close (channel[ch].fdOut);
		channel[ch].fdOut	=  -1;
		}
	if ( ( psFile != NULL ) && ( psFile[0] != '\0' ) )
		{
		channel[ch].fdOut   =  creat (psFile, 0777);
		if ( channel[ch].fdOut == -1 )
			fatal ("Error %d when opening file \"%s\" for write on DART channel %d.", errno, psFile, ch);
		}
	}

void dart_term (void)
	{
	dart_read (0, NULL);
	dart_write (0, NULL);
	dart_read (1, NULL);
	dart_write (1, NULL);
	}
