//////////////////////////////////////////////////////////////////////
// UCSB - Electrical and Computer Engineering Dept.
//
// IOL_UART (IOL Uart Class)
//
// Version  When      Who                    Why
// -------  ----      ---                    ---
// 1.0      02/01/00  Daryl Fortney          Initial Design
//
// This class supports serial port access under UNIX & DOS platforms.
//
// Copyright 2000 UCSB.  All rights reserved.
//////////////////////////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////////////////////////
#ifndef IOL_UART_CPP
#define IOL_UART_CPP

// Dependencies
#include <iol/uart/uart.hpp>

// Global Structures
#ifdef IOL_DOS_DIRECT
void far *UART_ISR[4]={UART_ISR0,UART_ISR1,UART_ISR2,UART_ISR3};
UART_DATA UD[4] = {
	{0,0x03F8,0x0C,0x10,0,0,0,0,0,0,0,0,0},
	{0,0x02F8,0x0B,0x08,0,0,0,0,0,0,0,0,0},
	{0,0x03E8,0x0C,0x10,0,0,0,0,0,0,0,0,0},
	{0,0x02E8,0x0B,0x08,0,0,0,0,0,0,0,0,0}
};
#endif

#ifdef IOL_DOS_CTI
union REGS inregs, outregs;
struct SREGS segregs;
#endif

#ifdef IOL_WINDOWS
HANDLE  UD[4];
#endif

char UART_NAME[UART_PORTS][16]={  
	UART_1,UART_2,UART_3,UART_4,
	UART_5,UART_6,UART_7,UART_8
};

//////////////////////////////////////////////////////////////////////
// Maintenance Functions
//////////////////////////////////////////////////////////////////////

//
// Print(): Simply prints the UART Attributes to CERR.
//
void	IOL_UART::Print(void){
#ifdef IOL_UART_PRINT_FLOWS
cerr<<"IOL_UART("<<this<<")::Print()"<<endl;
#endif

	#ifdef IOL_UART_PRINT_PRINT
	cerr<<"IOL_UART: ";

	// Check For Open UART
	if(STATUS==IOL_OPEN){
		cerr<<"Id = "<<ID<<" Mode = "<<hex<<MODE<<dec<<endl
			<<"========================================"<<endl;
		#ifdef IOL_DOS_DIRECT
		cerr<<"Control          R  = "<<UD[PORT].CONTROL<<endl
			<<"TxLength         R  = "<<UD[PORT].TXL<<" Bytes"<<endl
			<<"RxLength         R  = "<<UD[PORT].RXL<<" Bytes"<<endl;
		#endif
		#ifdef IOL_WINDOWS
		DCB				d;
		GetCommState(UD[PORT],&d);

		cerr<<"BaudRate         RW = "<<(long)d.BaudRate<<endl
			<<"WordLength       RW = "<<(long)d.ByteSize<<endl
			<<"Parity           RW = "<<(long)d.Parity<<endl
			<<"StopBits         RW = "<<(long)d.StopBits<<endl
			<<"Binary           R  = "<<(long)d.fBinary<<endl
			<<"ParityCheck      R  = "<<(long)d.fParity<<endl
			<<"OutX             R  = "<<(long)d.fOutX<<endl
			<<"InX              R  = "<<(long)d.fInX<<endl
			<<"XonChar          R  = "<<(long)d.XonChar<<endl
			<<"XoffChar         R  = "<<(long)d.XoffChar<<endl;
		#endif
		cerr<<"Timeout          RW = "<<Timeout()<<" 1/10 Sec."<<endl;
	}else
		cerr<<"CLOSED"<<endl;
	#endif
}

//////////////////////////////////////////////////////////////////////
// Initialization Functions
//////////////////////////////////////////////////////////////////////

//
// Open(): Simply opens the UART.
//
BOOL	IOL_UART::Open(char *id,long mode){
#ifdef IOL_UART_PRINT_FLOWS
cerr<<"IOL_UART("<<this<<")::Open("<<id<<","<<hex<<mode<<dec<<")"<<endl;
#endif

	// Make Sure It Is Closed First
	Close();

	// Extract port # from "/dev/ttyd#" id
	if(id==NULL)	
		return FALSE;
	PORT=id[9]-'1';

	// Detect UART Type
	if((TYPE=Detect()) == NONE){
		#ifdef IOL_UART_PRINT_ERRORS
		cerr<<"ERROR: UART No Serial Port Detected"<<endl;
		#endif
		return FALSE;
	}

	// Validate Port Selection
	if(PORT<0 || PORT>7){
		#ifdef IOL_UART_PRINT_ERRORS
		cerr<<"ERROR: UART Illegal Port Specified"<<endl;
		#endif
		return FALSE;
	}

	// Open Port 
	#ifdef IOL_DOS_DIRECT
	sprintf(ID,"COM%ld",PORT+1);
	
	// Enable FIFO For Type 16550A UART
	if(TYPE==T16550A)	
		Set(FCR,FCR_ENABLE);
	else 
		Set(FCR,FCR_DISABLE);

	// Allocate Tx&Rx FIFOs
	UD[PORT].TX = new volatile char[UART_SIZE];
	UD[PORT].RX = new volatile char[UART_SIZE];

	// Save Old UART Interrupt Vector
	// Install New UART Interrupt Vector
	#ifdef IOL_DOS_BC
	UD[PORT].OLD_ISR = getvect(UD[PORT].IRQ);
	setvect(UD[PORT].IRQ,(void interrupt (*)(...))UART_ISR[PORT]);
	#else
	UD[PORT].OLD_ISR = (void interrupt (*)(...))getvect(UD[PORT].IRQ);
	setvect(UD[PORT].IRQ,UART_ISR[PORT]);
	#endif

	// Unmask ICU Interrupts & Set IER & MSR
	Set(MCR,0x0F);
	Set(IER,IER_RX);
	Get(LSR);
	Get(0x00);
	outp(0x21,inp(0x21)&~UD[PORT].IMR);
	#endif

	#ifdef IOL_DOS_CTI
	sprintf(ID,"CTI_COM%ld",PORT+1);
	#endif

	#ifdef IOL_UNIX
	sprintf(ID,"%s",id);
	UD[PORT]=open(ID,O_RDWR);
	if(&UD[PORT]==NULL){
		#ifdef IOL_UART_PRINT_ERRORS
		cerr<<"ERROR: UART Failure Opening Port"<<endl;
		#endif
		return FALSE;
	}
	#endif

	#ifdef IOL_WINDOWS
	// Open Port
	sprintf(ID,"COM%ld",PORT+1);
	UD[PORT]=CreateFile(ID,(GENERIC_READ|GENERIC_WRITE),0,0,
							OPEN_EXISTING,FILE_ATTRIBUTE_SYSTEM,NULL);
	if(UD[PORT]==INVALID_HANDLE_VALUE){
		#ifdef IOL_UART_PRINT_ERRORS
		cerr<<"ERROR: UART Failure Opening Port"<<endl;
		#endif
		return FALSE;
	}
	#endif

	// Set Communications Mode
	Mode(mode);

	// Set Timeouts
	Timeout(10);

	// Flush UART Buffers
	Flush();

	// Update Port Status
	STATUS=IOL_OPEN;

	return TRUE;
}

//
// Close(): Simply closes the UART.
//
BOOL	IOL_UART::Close(void){

	// Check For Not Open
	if(STATUS==IOL_CLOSED) 
		return TRUE;

	#ifdef IOL_DOS_DIRECT
	// Mask ICU Interrupts & Clear IER
	Set(IER,0x00);
	Set(MCR,0x00);
	outp(0x21,inp(0x21)|UD[PORT].IMR);

	// Restore Old Interrupt Vector
	#ifdef IOL_DOS_BC
	setvect(UD[PORT].IRQ, UD[PORT].OLD_ISR);
	#else
	setvect(UD[PORT].IRQ, (void interrupt (*)())UD[PORT].OLD_ISR);
	#endif

	// Free RxTx Buffers
	delete[] (void *) UD[PORT].RX;
	delete[] (void *) UD[PORT].TX;
	#endif

	#ifdef IOL_UNIX
	close(UD[PORT]);
	#endif

	#ifdef IOL_WINDOWS
	CloseHandle(UD[PORT]);
	#endif

	// Update Port Status
	STATUS=IOL_CLOSED;

	return TRUE;
}

//
// Detect(): Returns the Detected UART Type.
//
long	IOL_UART::Detect(void){
#ifdef IOL_UART_PRINT_FLOWS
cerr<<"IOL_UART("<<this<<")::Detect()"<<endl;
#endif

	#ifdef IOL_DOS_DIRECT
	// Just Look Around & Guess
	Set(LCR,0x1B);
	if(Get(LCR)!=0x1B)		
		return NONE;
	Set(LCR,0x03);
	if(Get(LCR)!=0x03)		
		return NONE;
	Set(SCRATCH,0x55);
	if(Get(SCRATCH)!=0x55)	
		return T8250;
	Set(SCRATCH,0xAA);
	if(Get(SCRATCH)!=(char)0xAA)	
		return T16450;
	Set(FCR,0x01);
	if(!(Get(FCR)&0x80))	
		return T16450;
	if(!(Get(FCR)&0x40))	
		return T16550;

	return T16550A;
	#else
	return THIDDEN;
	#endif
}

//////////////////////////////////////////////////////////////////////
// Attribute Functions
//////////////////////////////////////////////////////////////////////

//
// Parameter(num): Returns the Parameter value.
//
long	IOL_UART::Parameter(long num){
#ifdef IOL_UART_PRINT_FLOWS
cerr<<"IOL_UART("<<this<<")::Parameter("<<num<<")"<<endl;
#endif

	num=num; // only one parameter supported
	#ifdef IOL_UART_CHECKS
	if(num>TIMEOUT){
		#ifdef IOL_UART_PRINT_ERRORS
		cerr<<"IOL ERROR: IOL_UART("<<this<<")::"<<"Parameter("<<num<<")"<<endl;
		#endif
		throw(IOL_ERROR);
	}
	#endif

	return TO;
}

//
// Parameter(num,value): Sets the Parameter value.
//
long	IOL_UART::Parameter(long num,long value){
#ifdef IOL_UART_PRINT_FLOWS
cerr<<"IOL_UART("<<this<<")::Parameter("<<num<<","<<value<<")"<<endl;
#endif

	num=num; // only one parameter supported

	#ifdef IOL_UART_CHECKS
	if(num>TIMEOUT){
		#ifdef IOL_UART_PRINT_ERRORS
		cerr<<"IOL ERROR: IOL_UART("<<this<<")::Parameter("<<num<<","<<value<<")"<<endl;
		#endif
		throw(IOL_ERROR);
	}
	#endif

	if(value>=0) TO=value;

	#ifdef IOL_UNIX
	struct termio	c;
	ioctl(UD[PORT],TCGETA,&c);
	c.c_cc[VMIN]  = 0;
	c.c_cc[VTIME] = (unsigned char)TO;
	ioctl(UD[PORT],TCSETA,&c);
	#endif

	#ifdef IOL_WINDOWS
	COMMTIMEOUTS	c;
	c.ReadIntervalTimeout=TO;
    c.ReadTotalTimeoutMultiplier=c.WriteTotalTimeoutMultiplier=0;
	c.ReadTotalTimeoutConstant=c.WriteTotalTimeoutConstant=TO;
	SetCommTimeouts(UD[PORT],&c);
	#endif

	return TO;
}

//
// Mode(mode): Sets the UART Mode.
//
long	IOL_UART::Mode(long mode){
#ifdef IOL_UART_PRINT_FLOWS
cerr<<"IOL_UART("<<this<<")::Mode("<<hex<<mode<<dec<<")"<<endl;
#endif

	long	add;
	long	value;

	#ifdef	IOL_DOS_CTI
	inregs.x.cx = 0;
	inregs.h.ah = 0x04;
	inregs.x.dx = PORT;
	#endif

	#ifdef IOL_UNIX
	struct termio c;
	ioctl(UD[PORT],TCGETA,&c);
	c.c_cflag &= (~(CBAUD | CSIZE | CSTOPB | PARENB | PARODD));
	#endif

	#ifdef IOL_WINDOWS
	DCB	c;
	GetCommState(UD[PORT],&c);
	#endif

	// Set Baud Rate
	add=0;
	switch(mode&UART_B){
		case UART_300B:		value=300;		add=UART_300B; break;
		case UART_600B:		value=600;		add=UART_600B; break;
		case UART_1200B:	value=1200;		add=UART_1200B;	break;
		case UART_2400B:	value=2400;		add=UART_2400B;	break;
		case UART_4800B:	value=4800;		add=UART_4800B;	break;
		case UART_9600B:	value=9600;		add=UART_9600B;	break;
		case UART_19200B:	value=19200;	add=UART_19200B; break;
		case UART_38400B:	value=38400;	add=UART_38400B; break;
		default:			break;
	}
	if(add){
		#ifdef IOL_DOS_DIRECT
		Or(LCR, 0x80);
		outpw(UD[PORT].BASE,115200L/value);
		And(LCR, 0x7F);
		#endif

		#ifdef IOL_DOS_CTI
		inregs.x.bx = value;
		inregs.x.cx = (value >> 16);
		#endif

		#ifdef IOL_UNIX
		if (add==UART_9600B)	c.c_cflag |=  B9600;
		#endif

		#ifdef IOL_WINDOWS
		c.BaudRate=value;
		#endif

		MODE=(MODE&~UART_B)|add;
	}

	// Set Wordlength
	add=0;
	switch(mode&UART_W){
		case UART_5W:		value=5;		add=UART_5W; break;
		case UART_6W:		value=6;		add=UART_6W; break;
		case UART_7W:		value=7;		add=UART_7W; break;
		case UART_8W:		value=8;		add=UART_8W; break;
		default:			break;
	}
	if(add){
		#ifdef IOL_DOS_DIRECT
		And(LCR,0xfc);
		Or(LCR,value-5);
		#endif

		#ifdef IOL_DOS_CTI
		if(value==7) inregs.h.al &= 0;
		else if(value==8) inregs.h.al |= 0x01;
		inregs.h.al |= 0x02;
		#endif

		#ifdef IOL_UNIX
		if(add==UART_7W)	c.c_cflag |= CS7;
		else				c.c_cflag |= CS8;
		#endif

		#ifdef IOL_WINDOWS
		c.ByteSize=(unsigned char)value&0xff;
		#endif

		MODE=(MODE&~UART_W)|add;
	}

	// Set Parity
	add=0;
	switch(mode&UART_P){
		case UART_NP:		value=0;		add=UART_NP; break;
		case UART_OP:		value=1;		add=UART_OP; break;
		case UART_EP:		value=2;		add=UART_EP; break;
		case UART_MP:		value=3;		add=UART_MP; break;
		case UART_SP:		value=4;		add=UART_SP; break;
		default:			break;
	}
	if(add){
		#ifdef IOL_DOS_DIRECT
		And(LCR,0xc7);
		if(value) Or(LCR,16*value-8);
		#endif

		#ifdef IOL_DOS_CTI
		if(value==0) inregs.h.al &= 0xe7;
		if(value==1) inregs.h.al |= 0x08;
		if(value==2) inregs.h.al |= 0x18;
		#endif

		#ifdef IOL_UNIX
		if(add==UART_EP)	c.c_cflag |= PARENB;
		#endif

		#ifdef IOL_WINDOWS
		c.Parity=(unsigned char)value&0xff;
		#endif

		MODE=(MODE&~UART_P)|add;
	}

	// Set Stopbits
	add=0;
	switch(mode&UART_S){
		case UART_1S:		value=1;		add=UART_1S; break;
		case UART_2S:		value=2;		add=UART_2S; break;
		default:			break;
	}
	if(add){
		#ifdef IOL_DOS_DIRECT
		And(LCR,0xfb);
		Or(LCR,4*(value-1));
		#endif

		#ifdef IOL_DOS_CTI
		if(value==1) inregs.h.al &= 0xfb;
		else if (value==2) inregs.h.al |= 0x04;
		#endif

		#ifdef IOL_WINDOWS
		c.StopBits=2*(value-1);
		#endif

		MODE=(MODE&~UART_S)|add;
	}

	#ifdef	IOL_DOS_CTI
	int86x(0x14,&inregs,&outregs,&segregs);
	#endif

	#ifdef IOL_UNIX
	c.c_iflag &= ~ICRNL;
	c.c_lflag &= ~ICANON;
	c.c_lflag &= ~ECHO;
	ioctl(UD[PORT],TCSETA,&c);
	#endif

	#ifdef IOL_WINDOWS
	SetCommState(UD[PORT],&c);
	#endif

	return MODE;
}

//////////////////////////////////////////////////////////////////////
// Tx & Rx Functions
//////////////////////////////////////////////////////////////////////

//
// Tx(): Transmits Data over serial port.
//
long	IOL_UART::Tx(BYTE* pdata,long num,long until){
#ifdef IOL_UART_PRINT_FLOWS
cerr<<"IOL_UART("<<this<<")::Tx(pdata,"<<num<<","<<until<<")"<<endl;
#endif

	// Must be Open
	if(STATUS==IOL_CLOSED) 
		return 0;

	// TxLength Request
	long	n;
	if(pdata==NULL || num<=0){
		n=0;

		#ifdef IOL_DOS_DIRECT
		n=UD[PORT].TXL;
		#endif

		return n;
	}

	// Tx Data
	BYTE* blk=pdata;
	for(n=0;n<num;n++,blk++){

		// Timeout If Tx Buffer Full
		#ifdef IOL_DOS_DIRECT
		long to=TO;
		while (UD[PORT].TXL>=UART_SIZE)
			if(to){
				Delay(1);
				if(--to == 0)	return n;
			}

		UD[PORT].TXL++;
		UD[PORT].TX[UD[PORT].TXH++] = *blk;
		if(UD[PORT].TXH>=UART_SIZE) 
			UD[PORT].TXH=0;
		#endif

		#ifdef IOL_DOS_CTI
		inregs.h.ah = 0x01;
		inregs.h.al = *blk;
		inregs.x.dx = PORT;
		int86x(	0x14, &inregs, &outregs, &segregs );
		#endif

		#ifdef IOL_UNIX
		if(TIMEOUT==0)
			while(write(UD[PORT],blk,1)==0);
		else
			if(write(UD[PORT],blk,1)==0) break;
		#endif

		#ifdef IOL_WINDOWS
		unsigned long	m;
		WriteFile(UD[PORT],blk,1,&m,NULL);
		if(m==0) break;
		#endif

		// Check For Until & Including
		if(until>=0)
			if(*blk==until){
				n++;
				break;
			}
	}

	#ifdef IOL_DOS_DIRECT
	// Enable Tx Interrupts
	Or(IER,IER_TX);
	#endif

	return n;
}

//
// Rx(): Receives Data over serial port.
//
long	IOL_UART::Rx(BYTE* pdata,long num,long until){
#ifdef IOL_UART_PRINT_FLOWS
cerr<<"IOL_UART("<<this<<")::Rx(pdata,"<<num<<","<<until<<")"<<endl;
#endif

	// Must be Open
	if(STATUS==IOL_CLOSED) 
		return 0;

	// RxLength Request
	long	n;
	if(pdata==NULL || num<=0){
		n=1;

		#ifdef IOL_DOS_DIRECT
		n=UD[PORT].RXL;
		#endif

		#ifdef IOL_DOS_CTI
		inregs.h.ah = 0x0a;
		inregs.x.dx = PORT;
		int86x( 0x14, &inregs, &outregs, &segregs);
		n=(long)(outregs.x.ax);
		#endif

		return n;
	}

	// Rx Data
	BYTE*	blk=pdata;
	for(n=0;n<num;n++,blk++){

		// Timeout If Rx Buffer Empty
		#ifdef IOL_DOS_DIRECT
		long to=TO;
		while(UD[PORT].RXL==0)
			if(to){
				Delay(1);
				if(--to == 0) return n;
			}

		*blk = UD[PORT].RX[UD[PORT].RXT];
		UD[PORT].RXL--;
		UD[PORT].RXT++;
		if(UD[PORT].RXT>=UART_SIZE) 
			UD[PORT].RXT=0;
		#endif

		#ifdef IOL_DOS_CTI
		long to=TO;
		while(Rx()==0)
			if(to){
				Delay(1);
				if(--to == 0) return n;
			}
		inregs.h.ah = 0x02;
		inregs.x.dx = PORT;
		int86x(	0x14, &inregs, &outregs, &segregs );
		*blk=outregs.h.al;
		#endif

		#ifdef IOL_UNIX
		if(TO==0)
			while(read(UD[PORT],blk,1)==0);
		else
			if(read(UD[PORT],blk,1)==0) break;
		#endif

		#ifdef IOL_WINDOWS
		unsigned long	m;
		ReadFile(UD[PORT],blk,1,&m,NULL);
		if(m==0) break;
		#endif

		// Check For Until & Including
		if(*blk==until){
			n++;
			break;
		}
	}

	return n;
}

//
// TxBlocking(): Transmits Data over serial port.
//
long	IOL_UART::TxBlocking(BYTE* pdata,long num,long until){
	unsigned long	m;
	WORD	curr=0;
	
	do{
		m=Tx(&pdata[curr],num-curr,until);
		curr+=m;
	}while(curr<num);

	return num;
}

//
// RxBlocking(): Receives Data over serial port.
//
long	IOL_UART::RxBlocking(BYTE* pdata,long num,long until){
	unsigned long	m;
	WORD	curr=0;
	
	do{
		m=Rx(&pdata[curr],num-curr,until);
		curr+=m;
	}while(curr<num);

	return num;
}

// Sends a Null Terminated String
long	IOL_UART::TxS(TEXT string){
	return Tx(string,strlen(string)+1);
}

// Receives a Null Terminated String
long	IOL_UART::RxS(TEXT string,long num){
	return Rx(string,num,0);
}

#ifdef IOL_DOS_DIRECT
///////////////////////////////////////////////////////////////////////////
// UART Interrupt Service Routines
///////////////////////////////////////////////////////////////////////////
void far interrupt UART_ISR0(){	UART_HANDLER(0); }
void far interrupt UART_ISR1(){	UART_HANDLER(1); }
void far interrupt UART_ISR2(){	UART_HANDLER(2); }
void far interrupt UART_ISR3(){	UART_HANDLER(3); }

void UART_HANDLER(const int PORT){

	while(inp(UD[PORT].BASE+IOL_UART::IIR)!=IOL_UART::IIR_NO){

		// Get Line Status
		UD[PORT].CONTROL=inp(UD[PORT].BASE+IOL_UART::LSR);

		// Rx Data
		if(UD[PORT].CONTROL&IOL_UART::LSR_RX){
			UD[PORT].RXL++;
			UD[PORT].RX[UD[PORT].RXH++] = inp(UD[PORT].BASE);
			if(UD[PORT].RXH>=UART_SIZE) 
				UD[PORT].RXH=0;
		}

		// Tx Data
		if(UD[PORT].CONTROL&IOL_UART::LSR_TX && UD[PORT].TXL){
			UD[PORT].TXL--;
			outp(UD[PORT].BASE,UD[PORT].TX[UD[PORT].TXT++]);
			if(UD[PORT].TXT>=UART_SIZE) 
				UD[PORT].TXT=0;

			// Disable Tx if Empty
			if(UD[PORT].TXL==0)
				outp(UD[PORT].BASE+IOL_UART::IER,IOL_UART::IER_RX);
		}
	}

	// Reset PIC
	outp(0x20,0x20);
}
#endif

#ifdef IOL_DOS_CTI
void	IOL_UART::RxFlush(void){

	inregs.h.ah = 0x09;
	inregs.x.dx = PORT;
	int86x( 0x14, &inregs, &outregs, &segregs);
}
#endif

#endif
