Coherent4.2.10/conf/asy/src/asy.c

Compare this file to the similar file:
Show the results in this format:

/* $Header: /ker/conf/asy/src/RCS/asy.c,v 1.1 93/09/14 18:30:31 nigel Exp $ */
/*
 * Purpose:	8250-family async port device driver
 *
 *	Devices are named /dev/asy[00..31]{fpl}
 *	Minor number bit assignments:
 *		x... ....	1 for NO modem control, "l"
 *		.x.. ....	1 for polled operation (no irq service), "p"
 *		..x. ....	1 for RTS/CTS flow control, "f"
 *		...x xxxx	channel number - 0..31
 *
 * $Log:	asy.c,v $
 * Revision 1.1  93/09/14  18:30:31  nigel
 * First "conf"-system revision
 */

#include <sys/coherent.h>

#include <sys/errno.h>
#include <sys/stat.h>
#include <kernel/v_types.h>
#include <termio.h>
#include <poll.h>

#include <kernel/trace.h>
#include <sys/uproc.h>
#include <sys/proc.h>
#include <sys/tty.h>
#include <sys/con.h>
#include <sys/devices.h>
#include <sys/sched.h>
#include <sys/asy.h>
#include <sys/ins8250.h>
#include <sys/poll_clk.h>

#define ASY_CLOSE_SECS  10

#define	IEN_USE_MSI	(IE_RxI | IE_TxI | IE_LSI | IE_MSI)
#define	IEN_NO_MSI	(IE_RxI | IE_TxI | IE_LSI)

#define CTLQ	0021
#define CTLS	0023

#define NUM_IRQ		16	/* PC allows irq numbers 0..15		*/
#define BPB		8	/* 8 bits per byte			*/
#define DTRTMOUT  	3	/* DTR seconds for close		*/
#define LOOP_LIMIT	100	/* safety valve on irq loops		*/

/*
 * For rawin silo (see poll_clk.h), use last element of si_buf to count
 * the number of characters in the silo.
 */
#if 0
#define SILO_CHAR_COUNT	si_buf[SI_BUFSIZ-1]
#else
#define SILO_CHAR_COUNT	si_cnt
#endif

#define SILO_HIGH_MARK	(SI_BUFSIZ-SI_BUFSIZ/4)
#define SILO_LOW_MARK	(SI_BUFSIZ/4)
#define MAX_SILO_INDEX	(SI_BUFSIZ-2)
#define MAX_SILO_CHARS	(SI_BUFSIZ-1)

#define RAWIN_FLUSH(in_silo)	{ \
  in_silo->si_ox = in_silo->si_ix; \
  in_silo->SILO_CHAR_COUNT = 0; }
#define RAWOUT_FLUSH(out_silo)	{ out_silo->si_ox = out_silo->si_ix; }
#define channel(dev)	(dev & 0x1F)

#define IEN	((a0->a_nms)?IEN_NO_MSI:IEN_USE_MSI)

#define NW_OUTSILO	1	/* bits in need_wake[] entries		*/

typedef void (* VPTR)();	/* pointer to function returning void	*/
typedef int (* FPTR)();		/* pointer to function returning int	*/


void asy_putchar();

/*
 * Configuration functions (local).
 */

static void cinit();

/*
 * Support functions (local).
 */
static void add_irq();
static void asy_irq();
static int asy_send();
static void asybreak();
static void asyclock();
static void asycycle();
static void asydump();
static int asyintr();
static void asyparam();
static void asysph();
static void asyspr();
static void asystart();
static void endbrk();
static void irqdummy();
static void upd_irq1();

static int p1(),p2(),p3(),p4();


extern int albaud[], alp_rate[];

/*
 * When asypatch runs, it checks whether its internal value for
 * ASY_VERSION matches this driver's value, so as to prevent the patch
 * utility and the driver from getting out of phase.
 */
int ASY_VER = ASY_VERSION;
int ASY_HPCL = 1;
int ASY_NUM = 0;
int ASYGP_NUM = 0;
asy0_t asy0[MAX_ASY] = {
	{ 0 }
};
asy_gp_t asy_gp[MAX_ASYGP] = {
	{ 0 }
};

static asy1_t * asy1;		/* unused entries have type US_NONE	*/
static short dummy_port;	/* used only during driver startup	*/
static int poll_divisor;	/* set by asyspr(), read by asyclk()	*/
static char pptbl [MAX_ASY];	/* channel numbers of polled ports	*/
static int ppnum;		/* number of channels in pptbl		*/

/*
 * irq0[x] and irq1[x] are lists for irq number x.
 * irq0 has nodes that may possibly cause an irq.
 * irq1 contains nodes for active devices.
 * Whenever a device becomes active or inactive, irq1 is rebuilt from irq0.
 *
 * nodespace is an array of available nodes used in making the lists.
 * nextnode points to the next free node.
 * Nodes are taken from nodespace only during driver load.
 */

static FPTR ptbl [PT_MAX] = { asyintr,p1,p2,p3,p4 };
static struct irqnode *irq0[NUM_IRQ], *irq1[NUM_IRQ];
static struct irqnode nodespace[MAX_ASY];
static char need_wake[MAX_ASY];
static char nextnode;
static int initialized;	/* for asy_putchar() */

/*
 * asyload()
 */

static void
asyload()
{
	int	s, chan;
	asy0_t	*a0;
	asy1_t	*a1;
	TTY	*tp;
	short	port;
	char	irq;
	char	speed;
	char	g;
	char	sense_ct = 0;

	/*
	 * Allocate space for asy structs.  Possible error return.
	 */
	asy1 = (asy1_t *)kalloc(ASY_NUM * sizeof(asy1_t));
	if (asy1 == 0) {
		printf("asyload: can't allocate space for %d async devices\n",
		  ASY_NUM);
		return;
	}
	kclear(asy1, ASY_NUM*sizeof(asy1_t));

	/*
	 * For each non-null port:
	 *	sense chip type
	 *	write baud rate to sgtty/termio structs
	 *	disable port interrupts
	 *	hang up port
	 *	set default baud rate (also resets UART)
	 *	hook "start" function into line discipline module
	 *	hook "param" function into line discipline module
	 *	hook CS into line discipline module
	 * 	if port uses irq
	 *		if not in a port group
	 *			add to irq list
	 */
	for (chan = 0; chan < ASY_NUM; chan++) {
		a0 = asy0 + chan;
		a1 = asy1 + chan;
		tp = &a1->a_tty;
		speed = a0->a_speed;
		tp->t_sgttyb.sg_ispeed = tp->t_sgttyb.sg_ospeed = speed;
		tp->t_dispeed = tp->t_dospeed = speed;
		port = a0->a_port;

		/*
		 * A port address of zero means a skipped entry in the table.
		 * In this case a1->a_ut keeps its initial value of US_NONE.
		 */

		if (port) {
			dummy_port = port;
			/*
			 * uart_sense() prints port info.
			 * Do this four times per line.
			 */

			a1->a_ut = uart_sense(port);
			sense_ct++;
			if ((sense_ct & 1) == 0)
				putchar('\n');
			else
				putchar('\t');

			s = sphi();
			outb(port+MCR, 0);
			outb(port+LCR, LC_DLAB);
			outb(port+DLL, albaud[speed]);
			outb(port+DLH, albaud[speed] >> 8);
			outb(port+LCR, LC_CS8);
			tp->t_start = asystart;
			/* leave tp->t_param at 0 */
			tp->t_ddp = (char *) chan;
			spl(s);

			if (a0->a_irqno && a0->a_asy_gp == NO_ASYGP)
				add_irq (a0->a_irqno, asyintr, chan);
		}
	}
	if (sense_ct & 1)
		putchar('\n');

	/*
	 * for each port group
	 *	add group to irq list
	 */

	for (g = 0 ; g < ASYGP_NUM ; g ++)
		add_irq (asy_gp [g].irq, ptbl [asy_gp [g].gp_type], g);

	/*
	 * Attach irq routines.
	 */

	for (irq = 0 ; irq < NUM_IRQ ; irq ++) {
		if (irq0 [irq])
			setivec (irq, asy_irq);
	}
}

/*
 * asyunload()
 */

static void
asyunload()
{
	char chan, irq;

	/*
	 * for each channel
	 *	disable UART interrupts
	 *	hangup port
	 *	cancel timer
	 */

	for (chan = 0; chan < ASY_NUM; chan++) {
		asy0_t * a0 = asy0 + chan;
		asy1_t * a1 = asy1 + chan;
		short port = a0->a_port;
		TTY *tp = &a1->a_tty;

		outb(port+IER, 0);
		outb(port+MCR, 0);

		timeout (tp->t_rawtim, 0, NULL, 0);
	}

	/*
	 * for each irq
	 *	if irq routine was attached
	 *		detach it
	 */

	for (irq = 0 ; irq < NUM_IRQ ; irq ++)
		if (irq0 [irq])
			clrivec(irq);

	/*
	 * Deallocate dynamic asy storage.
	 */
	if (asy1)
		kfree (asy1);
}

/*
 * asyopen()
 */

static void
asyopen(dev, mode)
dev_t dev;
int mode;
{
	int	s;
	char	msr, mcr;
	char	chan = channel(dev);
	asy0_t	*a0 = asy0 + chan;
	asy1_t	*a1 = asy1 + chan;
	TTY	*tp = &a1->a_tty;
	short	port = a0->a_port;

	if (a1->a_ut == US_NONE) { /* chip not found */
		set_user_error (ENXIO);
		goto bad_open;
	}

	if ((tp->t_flags & T_EXCL) != 0 && ! super()) {
		set_user_error (ENODEV);
		goto bad_open;
	}

#if 0
	if (drvl[major(dev)].d_time != 0) {	/* Modem settling */
		set_user_error (EBUSY);
		goto bad_open;
	}
#endif

	/*
	 * Can't open for hardware flow control if modem status
	 * interrupts are disallowed.
	 */
	if (a0->a_nms && (dev & CFLOW) != 0) {
		set_user_error (ENXIO);
		goto bad_open;
	}

	/*
	 * Can't open a polled port if another driver is using polling.
	 */
	if (dev & CPOLL && poll_owner & ~ POLL_ASY) {
		set_user_error (EBUSY);
		goto bad_open;
	}

	/*
	 * Can't have both com[13] or both com[24] IRQ at once.
	 */
	if (!(dev & CPOLL) && a0->a_ixc) {
		struct irqnode *np = irq1[a0->a_irqno];
		while (np) {
			if (np->func != ptbl[0] || np->arg != chan) {
				set_user_error (EBUSY);
				goto bad_open;
			}
			np = np->next_actv;
		}
	}

	/*
	 * If port already in use, are new and old open modes compatible?
	 */
	if (a1->a_in_use) {
		int oldmode = 0, newmode = 0; /* mctl:1 irq:2 flow:4 */

		if (a1->a_modc)
			oldmode += 1;
		if (a1->a_irq)
			oldmode += 2;
		if (a1->a_flc)
			oldmode += 4;
		if ((dev & NMODC) == 0)
			newmode += 1;
		if ((dev & CPOLL) == 0)
			newmode += 2;
		if (dev & CFLOW)
			newmode += 4;
		if (oldmode != newmode) {
			set_user_error (EBUSY);
			goto bad_open;
		}
	}

	/*
	 * Sleep here if another process is opening or closing the port.
	 * This can happen if:
	 *   another process is trying a first open and awaiting CD;
	 *   another process is closing the port after losing CD;
	 *   a remote process opened the port, spawned a daemon,
	 *     and disconnected, and the daemon ignored SIGHUP and is
	 *     improperly keeping the port open.
	 * Don't try to set tp->t_flags before this sleep!  During
	 *   the sleep, ttclose() may be called and clear the flags.
	 */
	while (a1->a_in_use &&
	       (a1->a_hcls || ((dev & NMODC) == 0 &&
			       (inb (port + MSR) & MS_RLSD) == 0))) {

		if (x_sleep ((char *) & tp->t_open, pritty, slpriSigCatch,
			     "asyblk") == PROCESS_SIGNALLED) {
			set_user_error (EINTR);
			goto bad_open;
		}
	}


	/*
	 * If channel not in use, mark it as such.
	 */

	if (a1->a_in_use == 0) {
		/*
		 * Save modes for this open attempt to avoid future conflicts.
		 * Then start asycycle() for this port.
		 */
		if (dev & NMODC) {
			tp->t_flags &= ~T_MODC;
			a1->a_modc = 0;
		} else {
			tp->t_flags |= T_MODC;
			a1->a_modc = 1;
		}
		if (dev & CPOLL)
			a1->a_irq = 0;
		else
			a1->a_irq = 1;
		if (dev & CFLOW) {
			tp->t_flags |= T_CFLOW;
			a1->a_flc = 1;
		} else {
			tp->t_flags &= ~T_CFLOW;
			a1->a_flc = 0;
		}
	}
	a1->a_in_use++;

	/*
	 * From here, error exit is bad_open_u.
	 */

	if (tp->t_open == 0) {        /* not already open */
		silo_t	* in_silo = &a1->a_in;

		if (!(dev & CPOLL)) {
			upd_irq1(a0->a_irqno);
			a1->a_has_irq = 1;
		}

		/*
		 * Need to start cycling to scan for CD.
		 */
		asycycle(chan);

		s = sphi();
		/*
		 * Raise basic modem control lines even if modem
		 * control hasn't been specified.
		 * MC_OUT2 turns on NON-open-collector IRQ line from the UART.
		 * since we can't have two UART's on same IRQ with MC_OUT2 on
		 */
		mcr = MC_RTS | MC_DTR;
		if (dev & CPOLL) {
			outb(port+MCR, mcr);
		} else {
			outb(port+MCR, mcr | a0->a_outs);
			outb(port+IER, IEN);
		}

		if ((dev & NMODC) == 0) {	/* want modem control? */
			tp->t_flags |= T_HOPEN | T_STOP;
			for (;;) {	/* wait for carrier */
				msr = inb(port+MSR);
				/*
				 * If carrier detect present
				 *   if port not already open
				 *     break out of loop and finish first open
				 *   else
				 *     do second (or third, etc.) open
				 */
				if (msr & MS_RLSD)
					break;

				/* wait for carrier */
				if (x_sleep ((char *) & tp->t_open, pritty,
					     slpriSigCatch, "need CD")
				    == PROCESS_SIGNALLED) {
					outb(port + MCR, 0);
			    		outb(port + IER, 0);
					set_user_error (EINTR);
					tp->t_flags &= ~(T_HOPEN | T_STOP);
					spl(s);
					goto bad_open_u;
				}
			}

			/*
			 * Mark that we are no longer hanging in open.
			 * Allow output over the port unless hardware flow
			 * control says not to.
			 */
			tp->t_flags &= ~T_HOPEN;
			tp->t_flags &= ~T_STOP;
			if (!(tp->t_flags & T_CFLOW) || (msr & MS_CTS))
				a1->a_ohlt = 0;
			else
				a1->a_ohlt = 1;

			/*
			 * Awaken any other opens on same device.
			 */
			wakeup((char *)(&tp->t_open));
		}
		if (ASY_HPCL)
			tp->t_flags |= T_HPCL;
		ttopen(tp);				/* stty inits */
		tp->t_flags |= T_CARR;

		asyparam(tp);	/* gimmick: do this while t_open is zero */

		/*
		 * TO DO: flush UART input register(s).
		 */

		spl(s);

		/*
		 * Turn on polling for the port.
		 */
		if (dev & CPOLL) {
			a1->a_poll = 1;
			asyspr();
		}
	} /* end of first-open case */

	tp->t_open++;

	ttsetgrp(tp, dev, mode);

	return;

bad_open_u:
	a1->a_in_use--;
	wakeup((char *)(&tp->t_open));
bad_open:
	return;
}

/*******
*
* asyflagset
*
* Set a flag to 1 and send a wakeup.  Used from itimeout ().
*******/
static void
asyflagset (a1)
asy1_t * a1;
{
	silo_t	* out_silo = &a1->a_out;

	wakeup((char *)out_silo);
	a1->a_clto = 1;
}

/*
 * asyclose()
 */
static void
asyclose(dev, mode)
dev_t dev;
int mode;
{
	int	chan = channel(dev);
	asy0_t	*a0 = asy0 + chan;
	asy1_t	*a1 = asy1 + chan;
	TTY	*tp = &a1->a_tty;
	silo_t	* out_silo = &a1->a_out;
	silo_t	* in_silo = &a1->a_in;
	int	flags, maj;
	int	s;
	short	port = a0->a_port;
	char	lsr;

	tp->t_open --;

	if (tp->t_open)
		goto not_last_close;
	s = sphi();

	a1->a_hcls = 1;			/* disallow reopen til done closing */
	flags = tp->t_flags;		/* save flags - ttclose zeroes them */
	ttclose(tp);

	/*
	 * Wait for output silo and UART xmit buffer to empty.
	 * Allow signal to break the sleep.
	 */
	a1->a_clto = 0;
	itimeout ((__tfuncp_t) asyflagset, (__VOID__ *) a1,
	  ASY_CLOSE_SECS * HZ, pltimeout);

	for (;;) {
		int chipEmpty = 0, siloEmpty = 0;

		lsr = inb(port + LSR);
		chipEmpty = (lsr & LS_TxIDLE);
		siloEmpty = (out_silo->si_ix == out_silo->si_ox);

		/* If all pending output is done, we can close. */
		if (chipEmpty && siloEmpty)
			break;

		/* Only wait so long for output to finish. */
		if (a1->a_clto)
			break;

		need_wake[chan] |= NW_OUTSILO;
		if (x_sleep ((char *) out_silo, pritty, slpriSigCatch,
			     "asyclose") == PROCESS_SIGNALLED) {
			RAWOUT_FLUSH(out_silo);
			break;
		}
	}
	need_wake[chan] &= ~NW_OUTSILO;

	/*
	 * If not hanging in open
	 */
	if ((flags & T_HOPEN) == 0) {
		/*
		 * Disable interrupts.
		 */
		outb(port+IER, 0);
		outb(port+MCR, inb(port+MCR) & ~MC_OUTS);
	}

	/*
	 * If hupcls
	 */

	if (flags & T_HPCL) {

		/*
		 * Hangup port - drop DTR and RTS.
		 */
		outb(port+MCR, inb(port+MCR) & MC_OUTS);

		/*
		 * Hold dtr low for timeout
		 */
		maj = major(dev);
		drvl[maj].d_time = 1;

		x_sleep ((char *) & drvl [maj].d_time, pritty, slpriNoSig,
			 "drop DTR");
		drvl[maj].d_time = 0;
	}

	a1->a_poll = 0;
	asyspr();
	RAWIN_FLUSH(in_silo);
	a1->a_hcls = 0;		/* allow reopen - done closing */
	wakeup((char *)(&tp->t_open));
	spl(s);
	a1->a_in_use--;

	if (!(dev & CPOLL))
		upd_irq1(a0->a_irqno);
	return;

not_last_close:
	a1->a_in_use--;
	wakeup((char *)(&tp->t_open));
	return;
}

/*
 * asyread()
 */
static void
asyread(dev, iop)
dev_t dev;
register IO * iop;
{
	int 	chan = channel(dev);
	asy1_t	*a1 = asy1 + chan;
	TTY	*tp = &a1->a_tty;

	ttread(tp, iop);
}

/*
 * asytimer()
 */
static void
asytimer(dev)
dev_t dev;
{
	if (++drvl[major(dev)].d_time > DTRTMOUT)
		wakeup((char *)&drvl[major(dev)].d_time);
}

/*
 * asywrite()
 */
static void
asywrite(dev, iop)
dev_t dev;
register IO * iop;
{
	int	chan = channel(dev);
	asy0_t	*a0 = asy0 + chan;
	asy1_t	*a1 = asy1 + chan;
	TTY	*tp = &a1->a_tty;
	short	port = a0->a_port;
	register int c;

	/*
	 * Treat user writes through tty driver.
	 */
	if (iop->io_seg != IOSYS) {
		ttwrite(tp, iop);
		return;
	}

	/*
	 * Treat kernel writes by blocking on transmit buffer.
	 */
	while ((c = iogetc(iop)) >= 0) {
		/*
		 * Wait until transmit buffer is empty.
		 * Check twice to prevent critical race with interrupt handler.
		 */
		for (;;) {
			if (inb(port+LSR) & LS_TxRDY)
				if (inb(port+LSR) & LS_TxRDY)
					break;
		}

		/*
		 * Output the next character.
		 */
		outb(port+DREG, c);
	}
}

/*
 * asyioctl()
 */

static void
asyioctl(dev, com, vec)
dev_t	dev;
int	com;
struct sgttyb *vec;
{
	int	chan = channel(dev);
	asy0_t	*a0 = asy0 + chan;
	asy1_t	*a1 = asy1 + chan;
	TTY	*tp = &a1->a_tty;
	int	s;
	int	temp;
	silo_t	*out_silo = &a1->a_out;
	silo_t	*in_silo = &a1->a_in;
	short	port = a0->a_port;
	unsigned char	msr, mcr, lcr, ier;
	char	do_ttioctl = 0;
	char	do_asyparam = 0;

	s = sphi();
	ier = inb(port+IER);
	mcr = inb(port+MCR);		/* get current MCR register status */
	lcr = inb(port+LCR);		/* get current LCR register status */

	/*
	 * If command will drain output, do the drain now
	 * before calling ttioctl().
	 * Don't do this for 286 kernel:  we're running out of code space.
	 */

	switch(com) {

	case TCSETAW:
	case TCSETAF:
	case TCSBRK:
	case TIOCSETP:
		/*
		 * Wait for output silo and UART xmit buffer to empty.
		 * Allow signal to break the sleep.
		 */
		for (;;) {
			if (! ttoutp (tp) &&
			    out_silo->si_ix == out_silo->si_ox &&
			    (inb (port + LSR) & LS_TxIDLE) != 0)
				break;
			need_wake[chan] |= NW_OUTSILO;

			if (x_sleep ((char *) out_silo, pritty, slpriSigCatch,
				     "asydrain") == PROCESS_SIGNALLED)
				break;
		}

		need_wake [chan] &= ~NW_OUTSILO;
	}

	switch(com) {
	case TIOCSBRK:			/* set BREAK */
		outb (port + LCR, lcr | LC_SBRK);
		break;

	case TIOCCBRK:			/* clear BREAK */
		outb (port + LCR, lcr & ~ LC_SBRK);
		break;

	case TIOCSDTR:			/* set DTR */
		outb (port + MCR, mcr | MC_DTR);
		break;

	case TIOCCDTR:			/* clear DTR */
		outb (port + MCR, mcr & ~ MC_DTR);
		break;

	case TIOCSRTS:			/* set RTS */
		outb (port + MCR, mcr | MC_RTS);
		break;

	case TIOCCRTS:			/* clear RTS */
		outb (port + MCR, mcr & ~ MC_RTS);
		break;

	case TIOCRSPEED:		/* set "raw" I/O speed divisor */
		outb (port + LCR, lcr | LC_DLAB);  /* set speed latch bit */
		outb (port + DLL, (unsigned) vec);
		outb (port + DLH, (unsigned) vec >> 8);
		outb (port + LCR, lcr);       /* reset latch bit */
		break;

	case TIOCWORDL:		/* set word length and stop bits */
		outb (port + LCR, ((lcr & ~ 0x7) | ((unsigned) vec & 0x7)));
		break;

	case TIOCRMSR:		/* get CTS/DSR/RI/RLSD (MSR) */
		msr = inb (port + MSR);
		temp = msr >> 4;
		kucopy (& temp, (unsigned *) vec, sizeof (unsigned));
		break;

	case TIOCFLUSH:		/* Flush silos here, queues in tty.c */
		RAWIN_FLUSH (in_silo);
		RAWOUT_FLUSH (out_silo);
		do_ttioctl = 1;
		break;

		/*
		 * If port parameters change, plan to call asyparam().
		 * Need to check now before structs are updated.
		 */
	case TCSETA:
	case TCSETAW:
	case TCSETAF:
		{
			struct	termio	trm;

			ukcopy (vec, & trm, sizeof (struct termio));
			if (trm.c_cflag != tp->t_termio.c_cflag)
				do_asyparam = 1;
		}
		do_ttioctl = 1;
		break;

	case TIOCSETP:
	case TIOCSETN:
		{
			struct	sgttyb	sg;

			ukcopy(vec, &sg, sizeof(struct sgttyb));
			if (sg.sg_ispeed != tp->t_sgttyb.sg_ispeed ||
			   ((sg.sg_flags ^ tp->t_sgttyb.sg_flags) & ANYP) != 0)
				do_asyparam = 1;
		}
		do_ttioctl = 1;
		break;

	default:
		do_ttioctl = 1;
	}

	outb (port + IER, ier);
	if (do_ttioctl)
		ttioctl (tp, com, vec);
	spl (s);
	if (do_asyparam)
		asyparam (tp);

	/*
	 * Things to be done after calling ttioctl().
	 */
	switch(com) {
	case TCSBRK:
		/*
		 * Send 0.25 second break:
		 * 1.  Turn on break level.
		 * 2.  Set timer to turn off break level 0.25 sec later.
		 * 3.  Sleep till timer expires.
		 * 4.  Turn off break level.
		 */

		outb (port + LCR, lcr | LC_SBRK);
		a1->a_brk = 1;
		timeout (& tp->t_sbrk, HZ / 4, endbrk, chan);

		while (a1->a_brk)
			x_sleep (a1, pritty, slpriNoSig, "asybreak");

		outb (port + LCR, lcr & ~ LC_SBRK);
	}
}


/*
 * Turn off the break level.
 * Called from timeout after ioctl(fd, TCSBRK, 0).
 */

void
endbrk(chan)
int chan;
{
	asy1_t	*a1 = asy1 + chan;

	a1->a_brk = 0;
	wakeup (a1);
}


/*
 * asyparam()
 */

static void
asyparam(tp)
TTY * tp;
{
	int	chan = (int)tp->t_ddp;
	asy0_t	*a0 = asy0 + chan;
	asy1_t	*a1 = asy1 + chan;
	short	port = a0->a_port;
	int	s;
	int	write_baud=1, write_lcr=1;
	unsigned char	mcr, newlcr, speed, oldSpeed;
	unsigned short cflag = tp->t_termio.c_cflag;

	speed = cflag & CBAUD;
	switch (cflag & CSIZE) {
	case CS5:  newlcr = LC_CS5;  break;
	case CS6:  newlcr = LC_CS6;  break;
	case CS7:  newlcr = LC_CS7;  break;
	case CS8:  newlcr = LC_CS8;  break;
	}
	if (cflag & CSTOPB)
		newlcr |= LC_STOPB;
	if (cflag & PARENB) {
		newlcr |= LC_PARENB;
		if ((cflag & PARODD) == 0)
			newlcr |= LC_PAREVEN;
	}
#if 0
	/*
	 * Bad 286 code...kill it soon! -Louis
	 */

	speed = tp->t_sgttyb.sg_ispeed;

	switch (tp->t_sgttyb.sg_flags & (EVENP | ODDP | RAW)) {

	case ODDP:
		newlcr = LC_CS7 | LC_PARENB;
		break;

	case EVENP:
		newlcr = LC_CS7 | LC_PARENB | LC_PAREVEN;
		break;

	default:
		newlcr = LC_CS8;
		break;
	}
#endif

	/*
	 * Don't bang on the UART needlessly.
	 * Writing baud rate resets the port, which loses characters.
	 * You want this on first open, NOT on later opens.
	 */

	oldSpeed = a0->a_speed;

	if (speed == oldSpeed && tp->t_open) {
		write_baud = 0;
		if (newlcr == a1->a_lcr) {
			write_lcr = 0;
		}
	}
	a0->a_speed = speed;
	a1->a_lcr = newlcr;

	if (write_lcr) {
		char ier_save;
		s = sphi();
		ier_save = inb(port+IER);
		if (write_baud) {
			if (speed) {
				short divisor = albaud [speed];

				if (oldSpeed == 0) {
					/* if previous baud rate was zero,
					 * need to go off hook. */
					mcr = inb(port+MCR) | (MC_RTS | MC_DTR);
					outb(port+MCR, mcr);
				}
				outb(port+LCR, LC_DLAB);
				outb(port+DLL, divisor);
				outb(port+DLH, divisor >> 8);
			} else {
				/* Baud rate of zero means hang up. */
				mcr = inb(port+MCR) & ~(MC_RTS | MC_DTR);
				outb(port+MCR, mcr);
			}
		}
		outb(port+LCR, newlcr);
		if (a1->a_ut == US_16550A)
			outb(port+FCR, FC_ENABLE | FC_Rx_RST | FC_Rx_08);
		outb(port+IER, ier_save);
		spl(s);
	}
	if (write_baud)
		asyspr ();
}

/*
 * asystart()
 */
static void
asystart(tp)
TTY * tp;
{
	int	chan = (int)tp->t_ddp;
	asy0_t	*a0 = asy0 + chan;
	asy1_t	*a1 = asy1 + chan;
	short	port = a0->a_port;
	int	s;
	int	need_xmit = 1;	/* True if should start sending data now. */
	silo_t	*out_silo = &a1->a_out;
	char	lsr;

	/*
	 * Read line status register AFTER disabling interrupts.
	 */

	s = sphi ();
	lsr = inb (port + LSR);

	/*
	 * Process break indication.
	 * NOTE: Break indication cleared when line status register was read.
	 */
	if (lsr & LS_BREAK)
		defer (asybreak, chan);

	/*
	 * If no output data, it may be time to finish closing the port;
	 * but won't need another xmit interrupt.
	 */
	if (out_silo->si_ix == out_silo->si_ox) {
		if (need_wake[chan] & NW_OUTSILO) {
			need_wake[chan] &= ~NW_OUTSILO;
			wakeup((char *)out_silo);
		}
		need_xmit = 0;
	}

	/*
	 * Do nothing if output is stopped.
	 */
	if (tp->t_flags & T_STOP)
		need_xmit = 0;
	if (a1->a_ohlt)
		need_xmit = 0;

	/*
	 * Start data transmission by writing to UART xmit reg.
	 */
	if ((lsr & LS_TxRDY) && need_xmit) {
		int xmit_count;
		xmit_count = (a1->a_ut == US_16550A)?16:1;
		asy_send(out_silo, port+DREG, xmit_count);
	}
	spl(s);
}

/*
 * asypoll()
 */
static int
asypoll(dev, ev, msec)
dev_t dev;
int ev;
int msec;
{
	int	chan = channel(dev);
	asy1_t	*a1 = asy1 + chan;
	TTY	*tp = & a1->a_tty;

	return ttpoll (tp, ev, msec);
}

/*
 * asycycle()
 *
 * Do a wakeup of any sleeping asy's at regular intervals.
 */
static void
asycycle(chan)
int chan;
{
	asy0_t	*a0 = asy0 + chan;
	asy1_t	*a1 = asy1 + chan;
	TTY	*tp = &a1->a_tty;
	short	port = a0->a_port;
	int	s;
	char	msr, mcr;
	silo_t	*out_silo = &a1->a_out;
	silo_t	*in_silo = &a1->a_in;
	int	n, ch;
	int	do_start = 1;
	unsigned char	iir;

	/*
	 * Check Carrier Detect (RLSD).
	 *
	 * Modem status interrupts were not enabled due to 8250 hardware bug.
	 * Enabling modem status and receive interrupts may cause lockup
	 * on older parts.
	 */
	if (tp->t_flags & T_MODC) {

		/*
		 * Get status
		 */
		msr = inb(port+MSR);

		/*
		 * Carrier changed.
		 */
		if ((msr & MS_RLSD) && !(tp->t_flags & T_CARR)) {
			/*
			 * Carrier is on - wakeup open.
			 */
			s = sphi();
			tp->t_flags |= T_CARR;
			spl(s);
			wakeup((char *)(&tp->t_open));
		}

		if (!(msr & MS_RLSD) && (tp->t_flags & T_CARR)) {
			s = sphi();
			RAWIN_FLUSH(in_silo);
			RAWOUT_FLUSH(out_silo);
			tp->t_flags &= ~T_CARR;
			spl(s);
			tthup(tp);
		}
	}

	/*
	 * Empty raw input buffer.
	 *
	 * The line discipline module (tty.c) will set T_ISTOP true when the
	 * tt input queue is nearly full (tp->t_iq.cq_cc >= IHILIM), and make
	 * T_ISTOP false when it's ready for more input.
	 *
	 * When T_ISTOP is true, ttin() simply discards the character passed.
	 */
	if (!(tp->t_flags & T_ISTOP)) {
		while (in_silo->SILO_CHAR_COUNT > 0) {
			s = sphi();
			ttin(tp, in_silo->si_buf[in_silo->si_ox]);
			if (in_silo->si_ox < MAX_SILO_INDEX)
				in_silo->si_ox++;
			else
				in_silo->si_ox = 0;
			in_silo->SILO_CHAR_COUNT--;
			spl(s);
		}
	}

	/*
	 * Hardware flow control.
	 *	Check CTS to see if we need to halt output.
	 *	(MS_INTR should have done this - repeat code here to be sure)
	 *	Check input silo to see if we need to raise RTS.
	 */
	if (tp->t_flags & T_CFLOW) {

		/*
		 * Get status
		 */
		msr = inb(port+MSR);
		s = sphi();
		if (msr & MS_CTS)
			a1->a_ohlt = 0;
		else
			a1->a_ohlt = 1;
		spl(s);

		/*
		 * If using hardware flow control, see if we need to drop RTS.
		 */
		if ((tp->t_flags & T_CFLOW)
		&& (in_silo->SILO_CHAR_COUNT > SILO_HIGH_MARK)) {
			s = sphi();
			mcr = inb(port+MCR);
			if (mcr & MC_RTS) {
				outb(port+MCR, mcr & ~MC_RTS);
			}
			spl(s);
		}

		/*
		 * If input silo below low mark, assert RTS.
		 */
		if (in_silo->SILO_CHAR_COUNT <= SILO_LOW_MARK) {
			s = sphi();
			mcr = inb(port+MCR);
			if ((mcr & MC_RTS) == 0) {
				outb(port+MCR, mcr | MC_RTS);
			}
			spl(s);
		}
	}

	/*
	 * Calculate free output slot count.
	 */

	n = sizeof(out_silo->si_buf) - 1;
	n += out_silo->si_ox - out_silo->si_ix;
	n %= sizeof(out_silo->si_buf);

	/*
	 * Fill raw output buffer.
	 */
	for (;;) {
		if (--n < 0)
			break;
		s = sphi();
		ch = ttout(tp);
		spl(s);
		if (ch < 0)
			break;

		s = sphi();
		out_silo->si_buf[out_silo->si_ix] = ch;
		if (out_silo->si_ix >= sizeof(out_silo->si_buf) - 1)
			out_silo->si_ix = 0;
		else
			out_silo->si_ix++;
		spl(s);
	}

	/*
	 * if port has an interrupt pending (probably missed an irq)
	 *	the following two loops should not be merged
	 *	- need ALL port irq's inactive at once
	 *	for each port on this irq line (use irq1 for this)
	 *		disable interrupts (clear IER)
	 *	for each port on this irq line
	 *		restore interrupts
	 */

	if (a1->a_has_irq && ((iir = inb (port + IIR)) & 1) == 0) {
		struct irqnode	*ip;
		asy_gp_t	*gp;
		int	s;
		short	p;
		char	c, slot;

		do_start = 0;
		s = sphi ();
		ip = irq1 [a0->a_irqno];
		while(ip) {
			if (ip->func == asyintr) {
				p = ip->arg;
				outb (p + IER, 0);
			} else {
				gp = asy_gp + ip->arg;
				for (slot = 0; slot < MAX_SLOTS; slot++) {
					if ((c = gp->chan_list [slot]) <
					    MAX_ASY) {
						p = asy0 [c].a_port;
						outb (p + IER, 0);
					}
				}
			}
			ip = ip->next_actv;
		}
		/*
		 * Now, all ports on the offending irq line have irq off.
		 */
		ip = irq1 [a0->a_irqno];
		while (ip) {
			if (ip->func == asyintr) {
				p = ip->arg;
				outb (p + IER, IEN);
			} else {
				gp = asy_gp + ip->arg;
				for (slot = 0; slot < MAX_SLOTS; slot++) {
					if ((c = gp->chan_list [slot]) <
					    MAX_ASY){
						p = asy0 [c].a_port;
						outb (p + IER, IEN);
					}
				}
			}
			ip = ip->next_actv;
		}
		spl (s);
	}

	if (do_start)
		ttstart (tp);

	/*
	 * Schedule next cycle.
	 */
	if (a1->a_in_use)
		timeout (& tp->t_rawtim, HZ / 10, asycycle, chan);
}

/*
 * irqdummy()
 *
 * Suppress interrupts that may occur during chip sensing.
 */
static void
irqdummy()
{
	/*
	 * Try to clear all pending interrupts.
	 */
	inb(dummy_port+IIR);
	inb(dummy_port+LSR);
	inb(dummy_port+MSR);
	inb(dummy_port+DREG);
}

/*
 * add_irq()
 *
 * Given channel number, add port info to irq0 list.
 */
static void
add_irq(irq, func, arg)
int irq;
int (*func)();
int arg;
{
	struct irqnode * np;

	/*
	 * Sanity check.
	 */
	if (irq <= 0 || irq >= NUM_IRQ)
		return;

	if (nextnode < MAX_ASY) {
		np = nodespace + nextnode++;
		np->func = func;
		np->arg = arg;
		np->next = irq0[irq];
		irq0[irq] = np;
	} else {
		printf("asy: too many irq nodes (%d)\n", nextnode);
	}
}


/*
 * asy_irq()
 *
 * Given pointer to node list, service async interrupt.
 */

static void
asy_irq (level)
int level;
{
	struct irqnode * ip = irq1 [level];
	int		doit;

	do {
		struct irqnode * here = ip;

		doit = 0;

		while (here != NULL) {
			doit |= (* here->func) (here->arg);
			here = here->next_actv;
		}
	} while (doit);
}


/*
 * upd_irq1()
 *
 * Given an irq number, rebuild the links for active devices.
 */
static void
upd_irq1(irq)
int irq;
{
	struct irqnode	*np;
	asy1_t	*a1;
	int	chan;
	int	s;

	/*
	 * Sanity check.
	 */
	if (irq <= 0 || irq >= NUM_IRQ)
		return;

	/*
	 * For each node in the irq0 list
	 *	if node is for irq status port
	 *		for each channel using the status port
	 *			if channel in use, in irq mode
	 *				add node to irq1 list
	 *				skip rest of channels for this node
	 *	else - node is for simple UART
	 *		if channel in use, in irq mode
	 *			add node to irq1 list
	 */
	s = sphi();
	np = irq0[irq];
	irq1[irq] = 0;
	while (np) {
		if (np->func != asyintr) {
			char ix, loop = 1;
			asy_gp_t *gp = asy_gp + np->arg;

			for (ix = 0; ix < MAX_SLOTS && loop; ix++) {
				if ((chan = gp->chan_list[ix]) < MAX_ASY) {
					a1 = asy1 + chan;
					if (a1->a_in_use && a1->a_irq) {
						np->next_actv = irq1[irq];
						irq1[irq] = np;
						loop = 0;
					}
				}
			}
		} else {
			a1 = asy1 + np->arg;
			if (a1->a_in_use && a1->a_irq) {
				np->next_actv = irq1[irq];
				irq1[irq] = np;
			}
		}
		np = np->next;
	}
	spl(s);
}

/*
 * asybreak()
 */
static void
asybreak(chan)
int chan;
{
	int	s;
	asy1_t	*a1 = asy1 + chan;
	silo_t	*out_silo = &a1->a_out;
	silo_t	*in_silo = &a1->a_in;
	TTY	*tp = &a1->a_tty;

	s = sphi();
	RAWIN_FLUSH(in_silo);
	RAWOUT_FLUSH(out_silo);
	spl(s);
	ttsignal(tp, SIGINT);
}

/*
 * asyintr()
 *
 * Handle interrupt for a single channel.
 */
static int
asyintr(chan)
int chan;
{
	asy0_t	*a0 = asy0 + chan;
	asy1_t	*a1 = asy1 + chan;
	TTY	*tp = &a1->a_tty;
	silo_t	*out_silo = &a1->a_out;
	silo_t	*in_silo = &a1->a_in;
	int	c, xmit_count;
	int	ret = 0;
	short	port = a0->a_port;
	unsigned char	msr, lsr;

	if (chan >= MAX_ASY) {
		printf("asy: irq on channel %d\n", chan);
		return 0;
	}

rescan:
	switch (inb(port+IIR) & 0x07) {

	case LS_INTR:
		ret = 1;
		lsr = inb(port + LSR);
		if (lsr & LS_BREAK)
			defer(asybreak, chan);
		goto rescan;

	case Rx_INTR:
		ret = 1;
		c = inb(port+DREG);
		if (tp->t_open == 0)
			goto rescan;
		/*
		 * Must recognize XOFF quickly to avoid transmit overrun.
		 * Recognize XON here as well to avoid race conditions.
		 */
		if (_IS_IXON_MODE (tp)) {
			/*
			 * XON.
			 */

			if (_IS_START_CHAR (tp, c) ||
			    (_IS_IXANY_MODE (tp) &&
			     (tp->t_flags & T_STOP) != 0)) {
				tp->t_flags &= ~(T_STOP | T_XSTOP);
				goto rescan;
			}

			/*
			 * XOFF.
			 */

			if (_IS_STOP_CHAR (tp, c)) {
				tp->t_flags |= T_STOP;
				goto rescan;
			}
		}

		/*
		 * Save char in raw input buffer.
		 */
		if (in_silo->SILO_CHAR_COUNT < MAX_SILO_CHARS) {
			in_silo->si_buf[in_silo->si_ix] = c;
			if (in_silo->si_ix < MAX_SILO_INDEX)
				in_silo->si_ix ++;
			else
				in_silo->si_ix = 0;
			in_silo->SILO_CHAR_COUNT ++;
		}

		/*
		 * If using hardware flow control, see if we need to drop RTS.
		 */
		if ((tp->t_flags & T_CFLOW) != 0 &&
		    in_silo->SILO_CHAR_COUNT > SILO_HIGH_MARK) {
			unsigned char mcr = inb (port + MCR);
			if (mcr & MC_RTS) {
				outb(port+MCR, mcr & ~MC_RTS);
			}
		}
		goto rescan;

	case Tx_INTR:
		ret = 1;
		/*
		 * Do nothing if output is stopped.
		 */
		if (tp->t_flags & T_STOP) {
			goto rescan;
		}
		if (a1->a_ohlt)
			goto rescan;

		/*
		 * Transmit next char in raw output buffer.
		 */
		xmit_count = (a1->a_ut == US_16550A)?16:1;
		asy_send(out_silo, port+DREG, xmit_count);
		goto rescan;

	case MS_INTR:
		ret = 1;
		/*
		 * Get status (and clear interrupt).
		 */
		msr = inb(port+MSR);

		/*
		 * Hardware flow control.
		 *	Check CTS to see if we need to halt output.
		 */
		if (tp->t_flags & T_CFLOW) {
			if (msr & MS_CTS)
				a1->a_ohlt = 0;
			else
				a1->a_ohlt = 1;
		}

		goto rescan;
	default:
		return ret;
	} /* endswitch */
}

/*
 * asyclk()
 *
 * Called every time T0 interrupts.- if it returns 0,
 * the usual system timer interrupt stuff is done.
 * Poll all pollable ports.
 */
static int
asyclk()
{
	static	int count;
	int	ix;

	for (ix = 0; ix < ppnum; ix++)
		asysph(pptbl[ix]);

	count++;
	if (count >= poll_divisor)
		count = 0;
	return count;
}

/*
 * asyspr()
 *
 * asyspr is called when a port is opened or closed or changes speed
 * It sets the polling rate only as fast as needed, and shuts off polling
 * whenever possible.
 * It updates the links in irq1[0], which lists polled-mode ports.
 */
static void
asyspr()
{
	asy0_t	*a0;
	asy1_t	*a1;
	int	chan;
	int	s;
	int	ix, max_rate, port_rate;

	/*
	 * Rebuild table of pollable ports.
	 */
	s = sphi();
	ppnum = 0;
	for (chan = 0; chan < ASY_NUM; chan++) {
		a1 = asy1 + chan;
		if (a1->a_poll)
			pptbl[ppnum++] = chan;
	}
	spl(s);

	/*
	 * If another driver has the polling clock, do nothing.
	 */
	if (poll_owner & ~ POLL_ASY)
		return;

	/*
	 * Find highest valid polling rate in units of HZ/10.
	 * If using FIFO chip, can poll at 1/16 the usual rate.
	 */
	max_rate = 0;
	for (ix = 0; ix < ppnum; ix++) {
		chan = pptbl[ix];
		a0 = asy0 + chan;
		a1 = asy1 + chan;
		port_rate = alp_rate[a0->a_speed];
		if (a1->a_ut == US_16550A) {
			port_rate /= 16;
			if (port_rate % HZ)
				port_rate += HZ - (port_rate % HZ);
		}
		if (max_rate < port_rate)
			max_rate = port_rate;
	}

	/*
	 * if max_rate is not current rate, adjust the system clock
	 */
	if (max_rate != poll_rate) {
		poll_rate = max_rate;
		poll_divisor = poll_rate/HZ;  /* used in asyclk() */
		altclk_out();		/* stop previous polling */
		poll_owner &= ~ POLL_ASY;
		if (poll_rate) {  /* resume polling at new rate if needed */
			poll_owner |= POLL_ASY;
			altclk_in(poll_rate, asyclk);
		}
	}
}

/*
 * asysph()
 *
 * Serial polling handler.
 */
static void
asysph(chan)
int chan;
{
	asy0_t	*a0 = asy0 + chan;
	asy1_t	*a1 = asy1 + chan;
	TTY	*tp = &a1->a_tty;
	silo_t	*out_silo = &a1->a_out;
	silo_t	*in_silo = &a1->a_in;
	int	c, xmit_count;
	short	port = a0->a_port;
	char	lsr;

	/*
	 * Check for received break first.
	 * This status is wiped out on reading the LSR.
	 */
	lsr = inb(port + LSR);
	if (lsr & LS_BREAK)
		defer(asybreak, chan);

	/*
	 * Handle all incoming characters.
	 */
	for (;;) {
		lsr = inb(port + LSR);
		if ((lsr & LS_RxRDY) == 0)
			break;
		c = inb(port+DREG);
		if (tp->t_open == 0)
			continue;
		/*
		 * Must recognize XOFF quickly to avoid transmit overrun.
		 * Recognize XON here as well to avoid race conditions.
		 */
		if (_IS_IXON_MODE (tp)) {
			/*
			 * XOFF.
			 */
			if (_IS_STOP_CHAR (tp, c)) {
				tp->t_flags |= T_STOP;
				continue;
			}

			/*
			 * XON.
			 */
			if (_IS_START_CHAR (tp, c)) {
				tp->t_flags &= ~T_STOP;
				continue;
			}
		}

		/*
		 * Save char in raw input buffer.
		 */
		if (in_silo->SILO_CHAR_COUNT < MAX_SILO_CHARS) {
			in_silo->si_buf[in_silo->si_ix] = c;
			if (in_silo->si_ix < MAX_SILO_INDEX)
				in_silo->si_ix++;
			else
				in_silo->si_ix = 0;
			in_silo->SILO_CHAR_COUNT++;
		}

		/*
		 * If using hardware flow control, see if we need to drop RTS.
		 */
		if ((tp->t_flags & T_CFLOW)
		  && (in_silo->SILO_CHAR_COUNT > SILO_HIGH_MARK)) {
			unsigned char mcr = inb(port+MCR);
			if (mcr & MC_RTS) {
				outb(port+MCR, mcr & ~MC_RTS);
			}
		}
	}

	/*
	 * Handle outgoing characters.
	 * Do nothing if output is stopped.
	 */
	lsr = inb(port + LSR);
	if ((lsr & LS_TxRDY)
	  && !(tp->t_flags & T_STOP)
	  && !(a1->a_ohlt)) {
		/*
		 * Transmit next char in raw output buffer.
		 */
		xmit_count = (a1->a_ut == US_16550A)?16:1;
		asy_send(out_silo, port+DREG, xmit_count);
	}

	/*
	 * Hardware flow control.
	 *	Check CTS to see if we need to halt output.
	 */
	if (tp->t_flags & T_CFLOW) {
		if (inb(port+MSR) & MS_CTS)
			a1->a_ohlt = 0;
		else
			a1->a_ohlt = 1;
	}
}

/*
 * asy_send()
 *
 * Write to xmit data register of the UART.
 * Assume all checking about whether it's time to send has been done already.
 * Called by time-critical IRQ and polling routines!
 *
 * "rawout" is the output silo for the TTY struct supplying data to the port.
 * "dreg" is the i/o address of the UART xmit data register.
 * "xmit_count" is the max number of chars we can write (16 for FIFO parts).
 */
static int
asy_send(rawout, dreg, xmit_count)
register silo_t * rawout;
int dreg, xmit_count;
{
	/*
	 * Transmit next chars in raw output buffer.
	 */
	for (;(rawout->si_ix != rawout->si_ox) && xmit_count; xmit_count--) {
		outb(dreg, rawout->si_buf[rawout->si_ox]);
		/*
		 * Adjust raw output buffer output index.
		 */
		if (++rawout->si_ox >= sizeof(rawout->si_buf))
			rawout->si_ox = 0;
	}
	return xmit_count;
}

/*
 * p1()
 *
 * Interrupt handler for Comtrol-type port groups.
 * Status register has 1 in bit positions for interrupting ports.
 */
static int
p1(g)
int g;
{
	asy_gp_t	*gp = asy_gp + g;
	short		port = gp->stat_port;
	unsigned char	status, index, chan;
	int		safety = LOOP_LIMIT;
	int		ret = 0;

#if 0 /* DEBUG */
static int pxstat[2][8];
	int ci;
	int change_found=0;

	for (ci=0; ci<1; ci++) {
		index = inb(port+ci);
		outb(port+ci, 0);
		if (index != pxstat[g][ci]) {
			if (!change_found) {
				change_found = 1;
				printf("<%d:", g);
			} else
				putchar(' ');
			printf("%x:%x", port+ci, index);
			pxstat[g][ci] = index;
		}
	}
	if (change_found)
		putchar('>');
	for (ci=0; ci<8; ci++)
		asyintr(4+ci);
	putchar('.');
	return 0;
#endif /* DEBUG */

	/*
	 * while any port is active
	 *	call simple interrupt handler for active channel
	 */
	while (status = inb(port)) {
		ret = 1;
		index = 0;
		if (status & 0xf0) {
			status &= 0xf0;
			index +=4;
		} else
			status &= 0x0f;
		if (status & 0xcc) {
			status &= 0xcc;
			index +=2;
		} else
			status &= 0x33;
		if (status & 0xaa)
			index++;
		chan = gp->chan_list[index];
		asyintr(chan);
		if (safety-- == 0) {
			printf("asy: p1 runaway - status %x\n", status);
			break;
		}
	}

	return ret;
}

/*
 * p2()
 *
 * Interrupt handler for Arnet-type port groups.
 * Status register has 0 in bit positions for interrupting ports.
 */
static int
p2(g)
int g;
{
	asy_gp_t	*gp = asy_gp + g;
	short		port = gp->stat_port;
	unsigned char	status, index, chan;
	int		safety = LOOP_LIMIT;
	int		ret = 0;

	/*
	 * while any port is active
	 *	call simple interrupt handler for active channel
	 */
	while (status = ~inb(port)) {
		ret = 1;
		index = 0;
		if (status & 0xf0) {
			status &= 0xf0;
			index +=4;
		} else
			status &= 0x0f;
		if (status & 0xcc) {
			status &= 0xcc;
			index +=2;
		} else
			status &= 0x33;
		if (status & 0xaa)
			index++;
		chan = gp->chan_list[index];
		asyintr(chan);
		if (safety-- == 0) {
			printf("asy: p2 runaway - status %x\n", status);
			break;
		}
	}
	return ret;
}

/*
 * p3()
 *
 * Interrupt handler for GTEK-type port groups.
 */
static int
p3(g)
int g;
{
	asy_gp_t	*gp = asy_gp + g;
	short		port = gp->stat_port;
	unsigned char	index, chan;

	/*
	 * Call simple interrupt handler for active channel.
	 */
	index = inb(port) & 7;
	chan = gp->chan_list[index];
	return asyintr(chan);
}

/*
 * p4()
 *
 * Interrupt handler for DigiBoard-type port groups.
 */
static int
p4(g)
int g;
{
	asy_gp_t	*gp = asy_gp + g;
	short		port = gp->stat_port;
	unsigned char	index, chan;
	int		ret = 0;
	int		safety = LOOP_LIMIT;

	/*
	 * Status register has slot number for active port,
	 * or 0xFF if no port is active.
	 */

	for (;;) {
		index = inb(port);
		if (index == 0xFF)
			break;
		if (safety-- == 0) {
			printf("asy: p4 runaway - status %x\n", index);
			break;
		}
		ret = 1;
		chan = gp->chan_list[index&0xF];
		asyintr(chan);
	}
	return ret;
}

#ifdef TRACER
void
asydump(chan, tag)
int chan;
char *tag;
{
	asy0_t	*a0 = asy0 + chan;
	asy1_t	*a1 = asy1 + chan;
	TTY	*tp = &a1->a_tty;

	printf("ch=%d  %s\n", chan, tag);
	printf("port=%x  irqno=%x  speed=%d  ",
	  a0->a_port, a0->a_irqno, a0->a_speed);
	printf("outs=%x  gp=%d  xcl=%d\n",
	  a0->a_outs, a0->a_asy_gp, a0->a_ixc);
	printf("in_use=%d  lcr=%x  irq=%d  has_irq=%d  ",
	  a1->a_in_use, a1->a_lcr, a1->a_irq, a1->a_has_irq);
	printf("hop=%d  hcl=%d  ", a1->a_hopn, a1->a_hcls);
	printf("opn=%d  ier=%x\n", tp->t_open, inb(a0->a_port+IER));
}
#endif

/*
 * Configuration table (export data).
 */
CON asycon ={
	DFCHR|DFPOL,			/* Flags */
	ASY_MAJOR,			/* Major index */
	asyopen,			/* Open */
	asyclose,			/* Close */
	NULL,				/* Block */
	asyread,			/* Read */
	asywrite,			/* Write */
	asyioctl,			/* Ioctl */
	NULL,				/* Powerfail */
	asytimer,			/* Timeout */
	asyload,			/* Load */
	asyunload,			/* Unload */
	asypoll				/* Poll */
};