
#include "struct.h"
#include <sys/stat.h>
#include <conio.h>
#include <i86.h>

/*
	declare the calls 'Dev' will make into driver.
*/
int __far	Kick(struct output_buffer __far *obuf);
int __far	Stty(struct termios __far *tios); 
int __far	Ctrl(union ioctl_entry __far *ioc); 

#pragma aux (_dev_call) Kick;
#pragma aux (_dev_call) Stty;
#pragma aux (_dev_call) Ctrl;

#pragma off (check_stack);

extern struct dev_entry dev_table[];
extern struct driver_ctrl drv_ctrl;
extern int num_asyncs;

int idle[MAX_ASYNCS] = {1};

/*
 * TTO is called by the int handler everytime a TX interrupt is
 * received.
 *
 * Since this routine is "called" by either DEV, or the Int handler,
 * it will ALWAYS run at higher
 * priority than the driver task, so cannot be interrupted.
 */

pid_t tto(struct dev_entry *dev, struct output_buffer far *obuf)	{
	char far *p;
	unsigned c, i, n;
	int sig = 0;

	if(obuf->mode & OUTPUT_XOFF) {
		obuf->mode &= ~OUTPUT_XOFF;
		outp(dev->iobase, 0x13);
		return(0);
		}

	if(obuf->mode & OUTPUT_XON) {
		obuf->mode &= ~OUTPUT_XON;
		outp(dev->iobase, 0x11);
		return(0);
		}

	if(obuf->mode & OUTPUT_STOPPED) {
		dev->busy = 0;
		return(0);
		}

	if(obuf->mode & OUTPUT_STOPPED_HW) {
		dev->busy = 0;
		return(0);
		}

	if(obuf->discard) {
		/*
		 * Discard some (or all) data instead of sending it
		 */
		n = obuf->discard;
		obuf->discard -= n;
		obuf->size -= n;
		i = &obuf->buffer[obuf->length] - obuf->tail;
		if(i > n)
			obuf->tail += n;
		else
			obuf->tail = &obuf->buffer[n-i];
		}

	if(dev->state  ||  obuf->size > 0) {
		if(dev->state) {
			c = dev->state;
			dev->state = 0;
			}
		else {
			/*
			 * Get the next character to print
			 * from the output buffer
			 */
			p = obuf->tail;
			if(p >= &obuf->buffer[obuf->length-1])
				obuf->tail = &obuf->buffer[0];
			else
				++obuf->tail;
			--obuf->size;
			c = *p;
			}

		if(obuf->mode & OUTPUT_XNL) {
			if(c == '\l')
				dev->state = '\r';
			}
	
		/*
		 * print the character
		 */
		_disable();
		dev->tx_pending = 10;  /* Timeout */
		outp(dev->iobase, c);
		_enable();
		}
	else
		dev->busy = 0;
	
	/*
	 * If size of buffer drops below notification threshold,
	 * inform DEV of the new information
	 */
	if(obuf->size < obuf->bcount) {
		obuf->bcount = 0;
		obuf->action |= ACT_OUTPUT_READY;
		sig = drv_ctrl.hw_pid;
		}

	return(sig);
	}

/*
 * Kick is called by DEV everytime something "new" happens
 * to the output buffer
 *
 * It will set es:di to be the output buffer (1st register pointer)
 * It will also set ds to be the data segment of the driver being called
 *
 * Since this routine is "called" by DEV, it will ALWAYS run at higher
 * priority than the driver task, so cannot be interrupted.
 * If the interrupt handler has called it, then DEV cannot interrupt this
 * process either.
 */

int far Kick(struct output_buffer far *obuf)
	{
	register struct dev_entry *dev;

	dev = &dev_table[obuf->handle];

	if(obuf->mode & OUTPUT_HFLOW_OFF) {
		obuf->mode &= ~OUTPUT_HFLOW_OFF;
		/*
		 * turn off RTS
	     */
		dev->modem_control &= ~0x02;
		outp(dev->iobase+4, dev->modem_control);
		}

	if(obuf->mode & OUTPUT_HFLOW_ON) {
		obuf->mode &= ~OUTPUT_HFLOW_ON;
		/*
		 * turn on RTS
	 	 */
		dev->modem_control |= 0x02;
		outp(dev->iobase+4, dev->modem_control);
		}

	if(dev->busy)
		return(0);

	if((obuf->mode & (OUTPUT_XON|OUTPUT_XOFF)) != 0
			||  obuf->size > 0  &&  (obuf->mode & OUTPUT_STOPPED) == 0) {
		dev->busy = 1;
		tto(dev, obuf);
		}
	return(1);
	}

/*
 * STTY is called by DEV everytime something "new" happens
 * to the termios control parameters
 *
 * It will set es:di to be the termios buffer (1st register pointer)
 * It will also set ds to be the data segment of the driver being called
 *
 * Since this routine is "called" by DEV, it will ALWAYS run at higher
 * priority than the driver task, so cannot be interrupted.
 */

int far Stty(struct termios far *tios) {
	register struct dev_entry *dev;
	int index;
	unsigned short cflag;
	int status;

	cflag = tios->c_cflag;
	index = tios->handle;
	dev = &dev_table[index];
	dev->baud = tios->c_ospeed;
	/*
	 * Parities are: (stick:even:enable)
	 *   xx0 none
	 *   001 odd
	 *   011 even
	 *   101 mark
	 *   111 space
	 */
	dev->parity = (((~cflag) & PARODD) >> 8)
				| ((cflag & PARENB) >> 8)
				| ((cflag & PARSTK) >> 0);
	dev->stop_bits = ((cflag & CSTOPB) >> 6) + 1;
	dev->data_bits = ((cflag & CSIZE) >> 4) + 5;
	status = reset_async(index, 0);
	return(status);
	}

/*
 * Ctrl is called by DEV to indicate a high level
 * control function
 *
 * It will set es:di to be an ioctl control block (1st register pointer)
 * It will also set ds to be the data segment of the driver being called
 *
 * Since this routine is "called" by DEV, it will ALWAYS run at higher
 * priority than the driver task, so cannot be interrupted.
 */

int far Ctrl(union ioctl_entry far *ioc) {
	int type;
	unsigned unit;
	struct dev_entry *dev;

	type = ioc->type;
	ioc->status = IOCTL_ERROR;

	if(type == IOCTL_OPEN) { /* First Open */
		ioc->status = IOCTL_OK;
		return(0);
		}
	else if(type == IOCTL_CLOSE) { /* Last Close */
		/*
		 * Reset (rows,cols) to (0,0) on last close (ie: undefined)
		 */
		ioc->close.obuf->rows = 0;
		ioc->close.obuf->cols = 0;
		ioc->close_return.status = IOCTL_OK;
		return(0);
		}
	else if(type == IOCTL_CTL) { /* Ctl */
		unsigned char bits, mask, oldbits;

		unit = ioc->close.unit;
		if(unit > num_asyncs  ||  unit <= 0) {
			ioc->status = IOCTL_BAD_UNIT;
			return(0);
			}
		dev = &dev_table[unit-1];

		mask = ioc->user.data[4] & 0x03;
		bits = ioc->user.data[0];
		_disable();
		oldbits = inp( dev->iobase + 4 );
		dev->modem_control = (oldbits & ~mask & 0xE7) | (bits & mask) | 0x08;
		outp( dev->iobase + 4, dev->modem_control);
		_enable();
		ioc->user_return.data[0] = oldbits;

		mask = ioc->user.data[5] & 0x40;
		bits = ioc->user.data[1];
		_disable();
		oldbits = inp( dev->iobase + 3 );
		outp( dev->iobase + 3, (oldbits & ~mask) | (bits & mask));
		_enable();
		ioc->user_return.data[1] = oldbits;

		ioc->user_return.data[2] = dev->modem_status & 0xF0;

		ioc->user_return.data[3] = 0;
		ioc->user_return.nbytes = 4;
		ioc->user_return.status = IOCTL_OK;
		return(0);
		}
	else if(type == IOCTL_STARTBREAK) {
		unit = ioc->startbreak.unit;
		if(unit > num_asyncs  ||  unit <= 0) {
			ioc->status = IOCTL_BAD_UNIT;
			return(0);
			}
		dev = &dev_table[unit-1];
		outp( dev->iobase + 3, inp( dev->iobase + 3 ) | 0x40);
		ioc->status = IOCTL_OK;
		return(0);
		}
	else if(type == IOCTL_STOPBREAK) {
		unit = ioc->stopbreak.unit;
		if(unit > num_asyncs  ||  unit <= 0) {
			ioc->status = IOCTL_BAD_UNIT;
			return(0);
			}
		dev = &dev_table[unit-1];
		outp( dev->iobase + 3, inp( dev->iobase + 3 ) & ~0x40);
		ioc->status = IOCTL_OK;
		return(0);
		}
	else if(type == IOCTL_STARTDROP) {
		unit = ioc->startdrop.unit;
		if(unit > num_asyncs  ||  unit <= 0) {
			ioc->status = IOCTL_BAD_UNIT;
			return(0);
			}
		dev = &dev_table[unit-1];
		dev->modem_control &= ~0x01;
		outp(dev->iobase+4, dev->modem_control);
		ioc->status = IOCTL_OK;
		return(0);
		}
	else if(type == IOCTL_STOPDROP) {
		unit = ioc->stopdrop.unit;
		if(unit > num_asyncs  ||  unit <= 0) {
			ioc->status = IOCTL_BAD_UNIT;
			return(0);
			}
		dev = &dev_table[unit-1];
		dev->modem_control |= 0x01;
		outp(dev->iobase+4, dev->modem_control);
		ioc->status = IOCTL_OK;
		return(0);
		}
	return(-1);
	}

#pragma on (check_stack);
