/* OS- and machine-dependent stuff for the 8250 asynch chip on a IBM-PC
 * Copyright 1991 Phil Karn, KA9Q
 *
 * 16550A support plus some statistics added mah@hpuviea.at 15/7/89
 *
 * CTS hardware flow control from dkstevens@ucdavis,
 * additional stats from delaroca@oac.ucla.edu added by karn 4/17/90
 * Feb '91      RLSD line control reorganized by Bill_Simpson@um.cc.umich.edu
 * Sep '91      All control signals reorganized by Bill Simpson
 * Apr '92	Control signals redone again by Phil Karn
 */
#ifdef MSDOS
#include "global.h"
#include <dos.h>
#include "mbuf.h"
#include "proc.h"
#include "iface.h"
#include "n8250.h"
#include "asy.h"
#include "devparam.h"
#include "hardware.h"
#include "kisspoll.h"


#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: n8250.c,v 1.6 1997/05/24 22:13:02 root Exp $";
#endif

#if 0 	/* was def TIPMAIL */
#include "cmdparse.h"
extern struct cmds Cmds[];
#endif


static int get_rlsd_asy (int dev, int new_rlsd);
static void asyrxint (struct asy *asyp, unsigned base);
static void asytxint (struct asy *asyp, unsigned base);
static void asymsint (struct asy *asyp);
static void asycom (struct asy *);
static void asy_monitor (int dev, void *p1, void *p2);
void pasy (struct asy *asyp);

struct asy *Asy;	/* allocated in main.c */
static unsigned char fifo_setup;
#ifdef DEBUGASY
static unsigned long ierints = 0;
#endif

static void asyint (int dev);
extern int arddec (volatile int *p);



/* Initialize asynch port "dev" */
int
asy_init (
int dev,
struct iface *ifp,
char *arg1,
char *arg2,
int16 bufsize,
int trigchar,
char monitor,
long speed,
int force,
int triglevel,
int polled
)
{
struct fifo *fp;
struct asy *ap;
int chain = 0;
char *ifn;
long interval;
unsigned int base;

	ap = &Asy[dev];
	ap->iface = ifp;
	ap->addr = htoi (arg1);
	ap->vec = htoi (arg2);
#ifdef POLLEDKISS
	ap->poller = NULL;
#endif
	ap->chain = chain;

	/* Set up receiver FIFO */
	fp = &ap->fifo;
	fp->buf = mallocw (bufsize);
	fp->bufsize = bufsize;
	fp->wp = fp->rp = fp->buf;
	fp->cnt = 0;
	fp->hiwat = 0;
	fp->overrun = 0;
	base = ap->addr;
	ap->trigchar = trigchar;

	/* Purge the receive data buffer */
	(void) inportb (base + RBR);

	/* Save original mask state, force off interrupts for now */
	if (ap->vec != -1) {
		ap->save.mask = getmask (ap->vec);
		(void) maskoff (ap->vec);
	}
	ap->save.lcr = inportb (base + LCR);
	ap->save.ier = inportb (base + IER);
	ap->save.mcr = inportb (base + MCR);
	ap->msr = ap->save.msr = inportb (base + MSR);
	ap->save.iir = inportb (base + IIR);

	/* save speed bytes */
	setbit (base + LCR, LCR_DLAB);
	ap->save.divl = inportb (base + DLL);
	ap->save.divh = inportb (base + DLM);
	clrbit (base + LCR, LCR_DLAB);

	/* save some information on the starting state */
	ap->cts_flow_control = (ap->save.msr & MSR_CTS) ? FOUND_UP : FOUND_DOWN;
	ap->rlsd_line_control = (ap->save.msr & MSR_RLSD) ? FOUND_UP : FOUND_DOWN;
	ap->dtr_usage = (ap->save.mcr & MCR_DTR) ? FOUND_UP : FOUND_DOWN;
	ap->rts_usage = (ap->save.mcr & MCR_RTS) ? FOUND_UP : FOUND_DOWN;

	/* Set interrupt vector to SIO handler */
	if (ap->vec != -1)	{
		LOCK_FUNCTION(asyint);
		LOCK_VARIABLE(Asy);
		(void) setvect (ap->vec, chain, asyint, dev);
	}

	/* Set line control register: 8 bits, no parity */
	outportb (base + LCR, LCR_8BITS);

	/* Set the fifo trigger level according to the user's wishes :-) - WG7J */
	if (triglevel == 1)
		fifo_setup = FIFO_SETUP1;
	else if (triglevel == 4)
		fifo_setup = FIFO_SETUP4;
	else if (triglevel == 8)
		fifo_setup = FIFO_SETUP8;
	else if (triglevel == 14)
		fifo_setup = FIFO_SETUP14;
	else
		fifo_setup = FIFO_SETUP4;	/* default to 4 */

	/* determine if 16550A, turn on FIFO mode and clear RX and TX FIFOs */
	outportb (base + FCR, FIFO_ENABLE);

	/* According to National ap note AN-493, the FIFO in the 16550 chip
	 * is broken and must not be used. To determine if this is a 16550A
	 * (which has a good FIFO implementation) check that both bits 7
	 * and 6 of the IIR are 1 after setting the fifo enable bit. If
	 * not, don't try to use the FIFO.
	 */

	if ((inportb (base + IIR) & IIR_FIFO_ENABLED) == IIR_FIFO_ENABLED || force) {
		ap->is_16550a = TRUE;
		outportb (base + FCR, fifo_setup);
	} else {
		/* Chip is not a 16550A. In case it's a 16550 (which has a
		 * broken FIFO), turn off the FIFO bit.
		 */
		outportb (base + FCR, 0);
		ap->is_16550a = FALSE;
	}

	/* Turn on receive interrupts and modem interrupts;
	 * leave transmit interrupts off until we actually send data.
	 */
	outportb (base + IER, IER_RLS | IER_DAV | IER_MS);

	/* Turn on 8250 master interrupt enable (connected to OUT2) */
	setbit (base + MCR, MCR_OUT2);

	/* Enable interrupt */
	if (ap->vec != -1)
		(void) maskon (ap->vec);

	(void) asy_speed (dev, speed);

	if (monitor) {
		/* set up to monitor line signals */
		ap->monitor = newproc (ifn = if_name (ifp, " monitor"),
				  256, asy_monitor, ifp->dev, ifp, NULL, 0);
		free (ifn);
		kwait (NULL);	/* let hooks get set up */
	}
	if ((ap->dtr_usage & FOUND_UP) && (ap->rlsd_line_control & FOUND_UP) || ap->monitor == NULL) {
		if (ifp->iostatus != NULL)
			(void) (*ifp->iostatus) (ifp, PARAM_UP, 0L);
	}
#ifdef POLLEDKISS
	if (polled) {
		/* Calculate the poll interval: some processing time +
		 * the packet duration for a mtu size packet.
		 */
		interval = (((long) ifp->mtu * 10000L) / speed);
		ap->poller = newproc (ifn = if_name (ifp, " poller"),
		   384, kiss_poller, ifp->xdev, (void *) interval, NULL, 0);
		free (ifn);
	}
#endif
	return 0;
}



int
asy_stop (struct iface *ifp)
{
unsigned base;
struct asy *ap;

	ap = &Asy[ifp->dev];

#ifdef POLLEDKISS
	if (ap->poller)
		killproc (ap->poller);
#endif
	if (ap->monitor != NULL) {
		ap->rlsd_line_control = IGNORED;
		killproc (ap->monitor);
		ap->monitor = NULL;
	}
	if (ap->iface == NULL)
		return -1;	/* Not allocated */
	ap->iface = NULL;

	base = ap->addr;

	(void) inportb (base + RBR);	/* Purge the receive data buffer */

	if (ap->is_16550a) {
		/* Purge hardware FIFOs and disable if we weren't already
		 * in FIFO mode when we entered. Apparently some
		 * other comm programs can't handle 16550s in
		 * FIFO mode; they expect 16450 compatibility mode.
		 */
		outportb (base + FCR, fifo_setup);
		if ((ap->save.iir & IIR_FIFO_ENABLED) != IIR_FIFO_ENABLED)
			outportb (base + FCR, 0);
	}
	/* Restore original interrupt vector and 8259 mask state */
	if (ap->vec != -1) {
		(void) freevect (ap->vec);
		(void) maskoff (ap->vec);
	}
	/* Restore speed regs */
	setbit (base + LCR, LCR_DLAB);
	outportb (base + DLL, ap->save.divl);	/* Low byte */
	outportb (base + DLM, ap->save.divh);	/* Hi byte */
	clrbit (base + LCR, LCR_DLAB);

	/* Restore control regs */
	outportb (base + LCR, ap->save.lcr);
	outportb (base + IER, ap->save.ier);
	outportb (base + MCR, ap->save.mcr);

	if (ap->save.mask)
		(void) maskon (ap->vec);
	else
		(void) maskoff (ap->vec);

	free (ap->fifo.buf);
	return 0;
}



/* Set asynch line speed */
int
asy_speed (int dev, int32 bps)
{
unsigned base;
long divisor;
struct asy *asyp;
int i_state;

	if (dev >= ASY_MAX)
		return -1;
	asyp = &Asy[dev];
	if (asyp->iface == NULL)
		return -1;

	if (bps == 0)
		return -1;
	asyp->speed = bps;

	base = asyp->addr;
	divisor = BAUDCLK / bps;

	i_state = disable ();

	/* Purge the receive data buffer */
	(void) inportb (base + RBR);
	if (asyp->is_16550a)	/* clear tx+rx fifos */
		outportb (base + FCR, FIFO_SETUP);

	/* Turn on divisor latch access bit */
	setbit (base + LCR, LCR_DLAB);

	/* Load the two bytes of the register */
	outportb (base + DLL, divisor);	/* Low byte */
	outportb (base + DLM, divisor >> 8);	/*lint !e704 * Hi byte */

	/* Turn off divisor latch access bit */
	clrbit (base + LCR, LCR_DLAB);

	restore (i_state);
	return 0;
}



/* Asynchronous line I/O control */
int32
asy_ioctl (struct iface * ifp, int cmd, int set, int32 val)
{
struct asy *ap = &Asy[ifp->dev];
uint base = ap->addr;

	switch (cmd) {
		case PARAM_SPEED:
			if (set)
				(void) asy_speed (ifp->dev, val);
			return ap->speed;
		case PARAM_DTR:
			if (set) {
				writebit (base + MCR, MCR_DTR, (int) val);
				ap->dtr_usage = (val) ? MOVED_UP : MOVED_DOWN;
			}
			return (inportb (base + MCR) & MCR_DTR) ? TRUE : FALSE;
		case PARAM_RTS:
			if (set) {
				writebit (base + MCR, MCR_RTS, (int) val);
				ap->rts_usage = (val) ? MOVED_UP : MOVED_DOWN;
			}
			return (inportb (base + MCR) & MCR_RTS) ? TRUE : FALSE;
		case PARAM_DOWN:
			clrbit (base + IER, (char) IER_DAV);
			clrbit (base + MCR, MCR_RTS);
			clrbit (base + MCR, MCR_DTR);
			ap->rts_usage = MOVED_DOWN;
			ap->dtr_usage = MOVED_DOWN;
			return FALSE;
		case PARAM_UP:
			setbit (base + IER, (char) IER_DAV);
			setbit (base + MCR, MCR_RTS);
			setbit (base + MCR, MCR_DTR);
			ap->rts_usage = MOVED_UP;
			ap->dtr_usage = MOVED_UP;
			return TRUE;
		case PARAM_BLIND:
			setbit (base + IER, (char) IER_DAV);
			ap->rlsd_line_control = IGNORED;

			if (ap->monitor != NULL) {
				killproc (ap->monitor);
				ap->monitor = NULL;
			}
			/* can't see what we are doing, so pretend we're up */
			if (ifp->iostatus != NULL) {
				(void) (*ifp->iostatus) (ifp, PARAM_UP, 0L);
			}
			return TRUE;
		default:
			return -1;
	}
}



#if 0
/* Open an asynch port for direct I/O, temporarily suspending any
 * packet-mode operations. Returns device number for asy_write and get_asy
 */
int
asy_open (char *name)
{
struct iface *ifp;
int dev;

	if ((ifp = if_lookup (name)) == NULL) {
		errno = ENODEV;
		return -1;
	}
	if ((dev = ifp->dev) >= ASY_MAX || Asy[dev].iface != ifp) {
		errno = EINVAL;
		return -1;
	}
	/* Suspend the packet drivers */
	suspend (ifp->rxproc);
	suspend (ifp->txproc);

	/* bring the line up (just in case) */
	if (ifp->ioctl != NULL)
		(void) (*ifp->ioctl) (ifp, PARAM_UP, TRUE, 0L);
	return dev;
}



int
asy_close (int dev)
{
struct iface *ifp;

	if (dev < 0 || dev >= ASY_MAX) {
		errno = EINVAL;
		return -1;
	}
	/* Resume the packet drivers */
	if ((ifp = Asy[dev].iface) == NULL) {
		errno = EINVAL;
		return -1;
	}
	resume (ifp->rxproc);
	resume (ifp->txproc);
	return 0;
}
#endif


/* Send a buffer on the serial transmitter and wait for completion */
static int
asy_write (int dev, const void *buf, unsigned short cnt)
{
struct dma *dp;
unsigned base;
struct asy *asyp;
char ier;
int tmp;
int i_state;
struct iface *ifp;

	if (dev < 0 || dev >= ASY_MAX)
		return -1;
	asyp = &Asy[dev];
	if ((ifp = asyp->iface) == NULL)
		return -1;

	base = asyp->addr;
	dp = &asyp->dma;

	if (dp->flags)
		return -1;	/* Already busy */

	dp->data = (uint8 *) buf;
	dp->cnt = cnt;
	dp->flags = 1;

	/* If CTS flow control is disabled or CTS is true,
	 * enable transmit interrupts here so we'll take an immediate
	 * interrupt to get things going. Otherwise let the
	 * modem control interrupt enable transmit interrupts
	 * when CTS comes up. If we do turn on TxE,
	 * "kick start" the transmitter interrupt routine, in case just
	 * setting the interrupt enable bit doesn't cause an interrupt
	 */

	if (asyp->cts_flow_control & MOVED_DOWN) {
		/* CTS flow control is enabled; let the modem control
		 * interrupt enable transmit interrupts if CTS is off
		 */
		ier = IER_MS;
		if (inportb (base + MSR) & MSR_CTS)
			ier |= IER_TxE;
	} else {
		/* Enable transmit interrupts; this will cause an immediate
		 * interrupt that will start things going
		 */
		ier = IER_TxE;
	}
	setbit (base + IER, ier);

	/* Wait for completion */
	for ( ; ; ) {
		i_state = disable ();
		tmp = dp->flags;
		restore (i_state);
		if (tmp == 0)
			break;
		kwait (&asyp->dma);
	}
	ifp->lastsent = secclock ();
	return cnt;
}



/* Blocking read from asynch line
 * Returns character or -1 if aborting
 */
int
get_asy (int dev)
{
struct fifo *fp;
uint8 c;

	if (dev < 0 || dev >= ASY_MAX) {
		errno = EINVAL;
		return -1;
	}

	fp = &Asy[dev].fifo;

	while (fp->cnt == 0)	{
		if (kwait (fp) != 0)
			return -1;
	}		

	(void) arddec ((volatile int *)&fp->cnt);

	c = *fp->rp++;
	if (fp->rp >= &fp->buf[fp->bufsize])
		fp->rp = fp->buf;

	return c;
}



/* Interrupt handler for 8250 asynch chip (called from asyvec.asm) */
static void
asyint (int dev)
{
#ifdef DEBUGASY
	ierints++;
#endif
	asycom (&Asy[dev]);
}



/* Common interrupt handler code for 8250/16550 port */
static void
asycom (struct asy *asyp)
{
unsigned base;
unsigned char iir;

	base = asyp->addr;
	while (((iir = inportb (base + IIR)) & IIR_IP) == 0) {
		switch (iir & IIR_ID_MASK) {
			case IIR_RLS:
				if (inportb (base + LSR) & LSR_OE)
					asyp->overrun++;
				break;
			case IIR_RDA:	/* Receiver interrupt */
				asyrxint (asyp, base);
				break;
			case IIR_THRE:	/* Transmit interrupt */
				asytxint (asyp, base);
				break;
			case IIR_MSTAT:	/* Modem status change */
				asymsint (asyp);
				asyp->msint_count++;
				break;
			default:
				break;
		}
		/* should happen at end of a single packet */
		if (iir & IIR_FIFO_TIMEOUT)
			asyp->fifotimeouts++;
	}
}



/* Process 8250 receiver interrupts */
static void
asyrxint (struct asy *asyp, unsigned base)
{
struct fifo *fp;
uint8 c, lsr;
int cnt = 0;
int trigseen = FALSE;

	asyp->rxints++;
	fp = &asyp->fifo;

	for ( ; ; ) {
		lsr = inportb (base + LSR);
		if (lsr & LSR_OE)
			asyp->overrun++;

		if (lsr & LSR_DR) {
			asyp->rxchar++;
			c = inportb (base + RBR);
			if (asyp->trigchar == -1 || asyp->trigchar == c)
				trigseen = TRUE;
			/* If buffer is full, we have no choice but
			 * to drop the character
			 */
			if (fp->cnt != fp->bufsize) {
				*fp->wp++ = c;
				if (fp->wp >= &fp->buf[fp->bufsize])
					/* Wrap around */
					fp->wp = fp->buf;
				fp->cnt++;
				if (fp->cnt > fp->hiwat)
					fp->hiwat = fp->cnt;
				cnt++;
			} else
				fp->overrun++;
		} else
			break;
	}
	if (cnt > asyp->rxhiwat)
		asyp->rxhiwat = cnt;
	if (trigseen)
		ksignal (fp, 1);
}



/* Handle 8250 transmitter interrupts */
static void
asytxint (struct asy *asyp, unsigned base)
{
struct dma *dp;
int count;
uint8 lsr;

	dp = &asyp->dma;
	asyp->txints++;
	if (!dp->flags || !(asyp->msr & MSR_CTS)) {
		/* These events "shouldn't happen". Either the
		 * transmitter is idle, in which case the transmit
		 * interrupts should have been disabled, or flow control
		 * is enabled but CTS is low, and interrupts should also
		 * have been disabled.
		 */
		clrbit (base + IER, IER_TxE);
		return;		/* Nothing to send */
	}
	lsr = inportb (base + LSR);
	if (lsr & LSR_OE)
		asyp->overrun++;

	if ((lsr & LSR_THRE) == 0)
		return;		/* Not really ready */

	/* If it's a 16550A, load up to 16 chars into the tx hw fifo
	 * at once. With an 8250, it can be one char at most.
	 */
	if (asyp->is_16550a) {
		count = (int) min (dp->cnt, OUTPUT_FIFO_SIZE);

		/* 16550A: LSR_THRE will drop after the first char loaded
		 * so we can't look at this bit to determine if the hw fifo is
		 * full. There seems to be no way to determine if the tx fifo
		 * is full (any clues?). So we should never get here while the
		 * fifo isn't empty yet.
		 */
		asyp->txchar += count;
		dp->cnt -= count;

		while (count-- != 0)
			outportb (base + THR, *dp->data++);
	} else {		/* 8250 */
		do {
			asyp->txchar++;
			outportb (base + THR, *dp->data++);
		} while (--dp->cnt != 0 && (inportb (base + LSR) & LSR_THRE));
	}
	if (dp->cnt == 0) {
		dp->flags = 0;
		/* Disable further transmit interrupts */
		clrbit (base + IER, IER_TxE);
		ksignal (&asyp->dma, 1);
	}
	return;
}



/* Handle 8250 modem status change interrupt */
static void
asymsint (struct asy *ap)
{
unsigned base = ap->addr;

	ap->msr = inportb(base+MSR);

	if ( ap->msr & MSR_CTS ) {
		switch ( ap->cts_flow_control ) {
			case FOUND_DOWN:
			case MOVED_DOWN:
				ap->cts_flow_control = MOVED_UP;

				/* CTS now asserted, enable Transmit interrupts */
				if(ap->dma.flags)
					setbit(base+IER,IER_TxE);
				break;
			default:
				break;
			
		};
	} else {
		switch ( ap->cts_flow_control ) {
			case FOUND_UP:
			case MOVED_UP:
				ap->cts_flow_control = MOVED_DOWN;

				/* CTS now dropped, disable Transmit interrupts */
				clrbit(base+IER,IER_TxE);
				break;
			default:
				break;
		};
	}

	if ( ap->msr & MSR_RLSD ) {
		switch ( ap->rlsd_line_control ) {
			case FOUND_DOWN:
			case MOVED_DOWN:
				ap->rlsd_line_control = MOVED_UP;

				/* RLSD just went up */
				ksignal( &(ap->rlsd_line_control), 1 );
				break;
			default:
				break;
		};
	} else {
		switch ( ap->rlsd_line_control ) {
			case FOUND_UP:
			case MOVED_UP:
				ap->rlsd_line_control = MOVED_DOWN;

				/* RLSD just went down */
				ksignal( &(ap->rlsd_line_control), 1 );
				break;
			default:
				break;
		};
	}

	if ( ap->msr & (MSR_TERI | MSR_RI) )
		(void) asy_ioctl( ap->iface, PARAM_UP, TRUE, 0L );
}

END_OF_FUNCTION(asyint)


/* Wait for a signal that the RLSD modem status has changed */
static int
get_rlsd_asy (dev, new_rlsd)
int dev;
int new_rlsd;
{
struct asy *ap = &Asy[dev];

	struct iface *ifp = ap->iface;
	int result;

	if ( ap->rlsd_line_control & IGNORED )
		return IGNORED;


	switch ( new_rlsd ) {
		case IGNORED:
			/* Just return the current value */
			return(ap->rlsd_line_control);

		case MOVED_DOWN:
			if ( !(ap->rlsd_line_control & FOUND_UP) ) {
				/* Already at requested value */
				return(new_rlsd);
			}
			break;
		case MOVED_UP:
			if ( ap->rlsd_line_control & FOUND_UP ) {
				/* Already at requested value */
				return(new_rlsd);
			}
			break;
		default:
			break;
	}

	/* Wait for state change to requested value */
	while (ap->rlsd_line_control != new_rlsd) {
		kpause(2L);
		kwait( &(ap->rlsd_line_control) );
	}

	if ( ap->rlsd_line_control & FOUND_UP )
		result = PARAM_UP;
	else /* DOWN or IGNORED */
		result = PARAM_DOWN;

	/* set our control signals to follow RLSD */
	if ( ifp->ioctl != NULL )
		(void) (*ifp->ioctl)( ifp, result, TRUE, 0L );
	if ( ifp->iostatus != NULL )
		(void) (*ifp->iostatus)( ifp, result, 0L );
	return(ap->rlsd_line_control);

}



/* Monitor RLSD signal, and report status changes */
static void
asy_monitor (dev, p1, p2)
int dev;
void *p1;
void *p2;
{
int save_rlsd = Asy[dev].rlsd_line_control;

	while (save_rlsd != IGNORED) {
		save_rlsd = get_rlsd_asy (dev, (save_rlsd ^ FOUND_UP) | MOVED_DOWN);
		kpause (2);
	}
	Asy[dev].monitor = NULL;
}



/* Poll the asynch input queues; called on every clock tick.
 * This helps limit the interrupt ring buffer occupancy when long
 * packets are being received.
 */
void
asytimer (void)
{
struct asy *asyp;
struct fifo *fp;
int i, i_state;

	for (i = 0; i < ASY_MAX; i++) {
		asyp = &Asy[i];
		if (asyp->iface == NULLIF)
			continue;

		fp = &asyp->fifo;
		if (fp->cnt != 0)
			ksignal (fp, 1);
		if (asyp->dma.flags && (inportb (asyp->addr + LSR) & LSR_THRE) &&
			(inportb(asyp->addr+IER) & IER_TxE))	{
				asyp->txto++;
				i_state = disable ();
				asytxint (asyp, asyp->addr);
				restore (i_state);
		}
#if 0 /* was def TIPMAIL */
		if (Tipsuspended[i].ifp)	{
			if (!carrier_detect(i))	{
				char buf[40];
				sprintf (buf, "start tip %s %s %d\n", Tipsuspended[i].ifp->name, (Tipsuspended[i].modem) ? "m" : "t", Tipsuspended[i].timeout);
				cmdparse(Cmds,buf,NULL);
				Tipsuspended[i].ifp = 0;
				Tipsuspended[i].modem = 0;
				Tipsuspended[i].timeout = 0;
			}
		}
#endif
	}
}



void
pasy (struct asy *asyp)
{
#ifdef DEBUGASY
int mcr, ier, iir;
#endif

	tprintf ("%s:", asyp->iface->name);
	if (asyp->is_16550a)
		tprintf (" [NS16550A]");
	if (asyp->trigchar != -1)
		tprintf (" [trigger 0x%02x]", asyp->trigchar);

	switch (asyp->cts_flow_control) {
		case MOVED_DOWN:
		case MOVED_UP:
			tputs (" [cts flow control]");
			break;
		default:
			break;
	};
	switch (asyp->rlsd_line_control) {
		case MOVED_DOWN:
		case MOVED_UP:
			tputs (" [rlsd line control]");
			break;
		case IGNORED:
			tputs (" [blind]");
			break;
		default:
			break;
	};

	tprintf (" %lu bps - bufsize %d\n", asyp->speed, asyp->fifo.bufsize);

#ifdef DEBUGASY
	ier = inportb (asyp->addr + IER);
	tprintf (" IE: int %lu  DAV %s  TxE %s  RLS %s  MS %s\n",
		ierints,
		(ier & IER_DAV) ? "On" : "Off",
		(ier & IER_TxE) ? "On" : "Off",
		(ier & IER_RLS) ? "On" : "Off",
		(ier & IER_MS) ? "On" : "Off");

	iir = inportb (asyp->addr + IIR);
	tprintf (" IR: int %s  MS %s  THRE %s  RDA %s  RLS %s\n",
		!(iir & IIR_IP) ? "On" : "Off",
		(((iir & IIR_ID_MASK) == IIR_MSTAT) && !(iir & IIR_IP)) ? "On" : "Off",
		((iir & IIR_ID_MASK) == IIR_THRE) ? "On" : "Off",
		((iir & IIR_ID_MASK) == IIR_RDA) ? "On" : "Off",
		((iir & IIR_ID_MASK) == IIR_RLS) ? "On" : "Off");

	mcr = inportb (asyp->addr + MCR);
	tprintf (" MC: int %lu  DTR %s  RTS %s  CTS %s  DSR %s  RI %s  CD %s\n",
		 asyp->msint_count,
		 (mcr & MCR_DTR) ? "On" : "Off",
		 (mcr & MCR_RTS) ? "On" : "Off",
		 (asyp->msr & MSR_CTS) ? "On" : "Off",
		 (asyp->msr & MSR_DSR) ? "On" : "Off",
		 (asyp->msr & MSR_RI) ? "On" : "Off",
		 (asyp->msr & MSR_RLSD) ? "On" : "Off");

#endif
	tprintf (" RX: int %lu  chars %lu  hw over %lu  hw hi %lu\n     ",
		 asyp->rxints, asyp->rxchar, asyp->overrun, asyp->rxhiwat);
	asyp->rxhiwat = 0;
	if (asyp->is_16550a)
		tprintf ("fifo TO %lu  ", asyp->fifotimeouts);
	tprintf ("sw over %lu  sw hi %u\n",
		 asyp->fifo.overrun, asyp->fifo.hiwat);
	asyp->fifo.hiwat = 0;

	tprintf (" TX: int %lu chars %lu THRE TO %lu%s\n",
		 asyp->txints, asyp->txchar, asyp->txto,
		 asyp->dma.flags ? " BUSY" : "");
}



/* Send a message on the specified serial line */
int
asy_send (dev, bpp)
int dev;
struct mbuf *bpp;
{
	if (dev < 0 || dev >= ASY_MAX)
		return -1;

	while (bpp != NULL) {
		/* Send the buffer */
		(void) asy_write (dev, bpp->data, bpp->cnt);
		/* Now do next buffer on chain */
		bpp = free_mbuf (bpp);
	}
	return 0;
}

#endif
