#include <conio.h>
#include <sys/_devdrvr.h>
#include <sys/trace.h>
#include <sys/tracecod.h>
#include "struct.h"

/*
 * Stack checking MUST be disabled in the following routines
 */

#pragma off (check_stack);

extern pid_t (far *tti)(int, int, unsigned);
extern unsigned tti_ds;

extern struct dev_entry *dev_list[];
extern struct driver_ctrl drv_ctrl;
extern int num_asyncs;

/*
 * Compensate for a "bug" in the 8250/450/550 which misses a Tx interrupt if
 * a Rx or Line status int occurs at the "same time". Re-setting the IER
 * will cause the Tx interrupt to be re-generated if THRE is now set.
 * We only do this work if QNX is "waiting" for a Tx interrupt when a
 * Rx or line status interrupt is received.
 *
 * Note: That a high level timeout on Tx interrupts is also used to recover
 * from "other" hardware bugs which cause some UARTs (especially CMOS parts)
 * to lose Tx interrupts.
 */

static void test_tx(struct dev_entry *dev, unsigned char lsr) {
	if(dev->tx_pending == 0)
		return;
	if((lsr & 0x20) == 0)
		return;
	outp(dev->iobase+1, 0x0f);
	return;
	}

/*
 * Process data in a line status register
 */

static pid_t process_lsr(struct dev_entry *dev, unsigned char lsr) {
	unsigned key = 0;

	if((lsr & 0x1e) == 0)
		return(0);

	/*
	 * Read whatever input data happens to be in the
	 * buffer (to "eat" the spurious data associated
	 * with break, parity error, etc.)
	 */
	key = inp(dev->iobase);

	if(lsr & 0x10)	{
		Trace1( (long)_TRACE_DEV_SER_HUP+1, _TRACE_TRANSIENT, dev->iobase );
		key |= RCV_BREAK;
		}
	else {
		if(lsr & 0x04) {
			key |= RCV_PARITY;
			Trace1( (long)_TRACE_DEV_SER_PARITY, _TRACE_TRANSIENT, dev->iobase );
			}
		if(lsr & 0x08) {
			key |= RCV_FRAME;
			Trace1( (long)_TRACE_DEV_SER_FRAME, _TRACE_TRANSIENT, dev->iobase );
			}
		}

	if(lsr & 0x02) {
		key |= RCV_OVERRUN;
		Trace1( (long)_TRACE_DEV_SER_OVERRUN, _TRACE_TRANSIENT, dev->iobase );
		}
	return( (*tti) (key, dev->tty, tti_ds) );
	}

/************************************************************************
 * ASYNC interrupt handler                                              *
 ************************************************************************/

pid_t far async_irq0()  { return(async_int(0));  }
pid_t far async_irq1()  { return(async_int(1));  }
pid_t far async_irq2()  { return(async_int(2));  }
pid_t far async_irq3()  { return(async_int(3));  }
pid_t far async_irq4()  { return(async_int(4));  }
pid_t far async_irq5()  { return(async_int(5));  }
pid_t far async_irq6()  { return(async_int(6));  }
pid_t far async_irq7()  { return(async_int(7));  }
pid_t far async_irq8()  { return(async_int(8));  }
pid_t far async_irq9()  { return(async_int(9));  }
pid_t far async_irq10() { return(async_int(10)); }
pid_t far async_irq11() { return(async_int(11)); }
pid_t far async_irq12() { return(async_int(12)); }
pid_t far async_irq13() { return(async_int(13)); }
pid_t far async_irq14() { return(async_int(14)); }
pid_t far async_irq15() { return(async_int(15)); }


pid_t async_int(int irq) {
	pid_t status = 0;
	int nothing;
	struct dev_entry *dev;
	unsigned char iir;
	unsigned char lsr;
	unsigned char msr;

	while(1) {
		nothing = 1;
		for(dev = dev_list[irq]; dev != 0; dev = dev->link) {
			if(dev->iobase == 0)
				continue;
			iir = inp(dev->iobase+2);
			if((iir & 0x01) != 0)
				continue;
			/*
			 * Interrupt pending
			 */
			nothing = 0;
			iir &= 7;
			if(iir == 4) {
				/*
				 * Input Interrupt
				 */
				while(1) {
					status |= (*tti) (inp(dev->iobase), dev->tty, tti_ds);
					++dev->rx_count;
					lsr = inp(dev->iobase+5);
					status |= process_lsr(dev, lsr);
					if((lsr & 0x01) == 0) {
						test_tx(dev, lsr);
						break;
						}
					}
				}
			else if(iir == 2) {
				/*
				 * Output Interrupt
				 */
				dev->tx_pending = 0;
				status |= tto(dev, dev->obuf);
				++dev->tx_count;
				}
			else if(iir == 0) {
				/*
				 * Modem change
				 */
				msr = inp(dev->iobase+6);
				dev->modem_status = (dev->modem_status & 0x0F) | (msr & 0xF0);

				if(dev->mode & MODE_PKT) /* Special mode for custom hardware */
					status |= (*tti) (RCV_PKT_SPECIAL, dev->tty, tti_ds);

				if((msr & 0x08) != 0  &&  (dev->mode & MODE_NOHUP) == 0) {
					if(msr & 0x80) {
						status |= (*tti) (RCV_CARRIER, dev->tty, tti_ds);
						Trace1( _TRACE_DEV_SER_CARRIER, _TRACE_EXPECTED, dev->iobase );
						}
					else {
						status |= (*tti) (RCV_HNGUP, dev->tty, tti_ds);
						Trace1( _TRACE_DEV_SER_HUP, _TRACE_EXPECTED, dev->iobase );
						}
					}
					

				if(dev->mode & MODE_SPLIT) {
					if(msr & 0x01) {
						/*
						 * Require "ONLY" CTS for hardware flow control
						 */
						if(msr & 0x10)
							status |= (*tti) (RCV_HW_ON, dev->tty, tti_ds);
						else
							status |= (*tti) (RCV_HW_OFF, dev->tty, tti_ds);
						}
					}
				else {
					if(msr & 0x03) {
						/*
						 * Require "BOTH" DSR and CTS to be on
						 * before we will transmit in HFLOW mode
						 */
						if((msr & 0x30) == 0x30)
							status |= (*tti) (RCV_HW_ON, dev->tty, tti_ds);
						else
							status |= (*tti) (RCV_HW_OFF, dev->tty, tti_ds);
						}
					}
				}
			else {
				/*
				 * Line status change
				 */
				lsr = inp(dev->iobase+5);
				status |= process_lsr(dev, lsr);
				test_tx(dev, lsr);
				}
			}
		if(nothing)
			break;
		}
	return(status);
	}

#pragma on (check_stack);
