/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.

#ident "$Header: xs.c 1.87 90/09/14 $";

/*
 * xs.c
 * 	Xylogics 781 Driver  (STREAMS version)
 */

/* $Log:	xs.c,v $
 */

#include "sys/types.h"
#include "sys/mutex.h"
#include "sys/slic.h"
#include "sys/devsw.h"
#include "sys/param.h"
#include "sys/signal.h"
#include "sys/conf.h"
#include "sys/ioctl.h"
#include "sys/systm.h"
#include "sys/timer.h"
#include "sys/user.h"
#include "sys/proc.h"
#include "sys/file.h"
#include "sys/vmmac.h"
#include "sys/vmmeter.h"
#include "sys/vmsystm.h"
#include "sys/uio.h"
#include "sys/session.h"
#include "sys/stream.h"
#include "sys/stropts.h"
#include "sys/termio.h"
#include "sys/debug.h"
#include "sys/sysmacros.h"
#include "sys/pte.h"
#include "sys/vmparam.h"
#include "sys/plocal.h"
#include "sys/cmn_err.h"
#include "sys/strid.h"
#include "sys/errno.h"
#include "sys/intctl.h"
#include "sys/ssm_vme.h"
#include "sys/ssm.h"
#include "sys/hwparam.h"
#include "sys/autoconf.h"
#include "sys/xs.h"
#include "sys/xs_device.h"


int	xsstart(), xs_probe();
int	xs_timeout(), xs_delaytime(), xs_breaktime();
void xs_init(), xs_intr();

static struct module_info xsm_info = {STRID_XS, "xs", 0, INFPSZ, 150, 50};

/* Table of ms-per-byte serial output rate (indexed by baud B0-EXTA) */
static unchar xsbaud_time[] = {
	0, 200, 143, 91, 77, 67, 50, 33, 17, 8, 6, 4, 2, 1, 1 };

static int xsopen(), xsclose(), xsrsrv(), xswsrv();

static struct qinit xsrinit = {
	NULL,  xsrsrv,  xsopen,  xsclose,  NULL,  &xsm_info,  NULL
};

static struct qinit xswinit = {
	putq,  xswsrv,  NULL,  NULL,  NULL, &xsm_info,  NULL
};

struct streamtab xsinfo = { &xsrinit,  &xswinit,  NULL,  NULL  };

struct	xsinfo	**xs_info;	/* per board data */
int	xsbase;			/* base VME interrupt vector */
int	nxs;			/* number of configured boards */
int	xs_vme_basevec = 0;	/* VME interrupt base counter */

#ifdef DEBUG
int     dumpcmd = 0;		/* set to 1 to dump xs_cmd command */
#endif

extern	unchar	xsprintoverflow;/* non-zero prints message on input overflow */
extern	unchar	xsprintalocfail;/* non-zero prints message on allocb fail */
extern  int	putnextctl2();  /* defined in ld0.c */
extern  putbqx();
extern	int 	xscflags;	/* Initial cflags value */
extern	int	xsaddwait;
extern  unchar	xs_bin;
extern 	uint	bufcall();
extern	int	xsbreaktime;	/* Break duration in milliseconds */
extern	uint	xstimeout;	/* ARCP timeout factor */

/*
 * Flavours
 */
#define DPRINTF1 if (str_debug(STRID_XS, 1))printf
#define DPRINTF2 if (str_debug(STRID_XS, 2))printf
#define DPRINTF3 if (str_debug(STRID_XS, 3))printf
#define DPRINTF4 if (str_debug(STRID_XS, 4))printf

extern int xs_c_major;
extern struct devops xsdevops;

/*
 * xs_probe - see if a 781 is installed
 * returns 1 if device probes present.  If the 781 is not present,
 * an NMI will be generated and we will be interrupted from this
 * routine.
 */
int
xs_probe(dev)
	struct gen_device *dev;
{
	hardware_781 *xs;
	unchar a;

	xs = (hardware_781 *) dev->devreg[0];
	a = xs->cntl_status;		/* Attempt to read the board */
#ifdef lint
	CPRINTF("xs_probe: board status is 0x%x\n",a);
#endif
	return(1);			/* Success! */
}

/*
 * Initialize 781 hardware and driver structures
 */
void
xs_init(driver)
	struct driver_module *driver;
{
	hardware_781 *xsaddr;
	register struct xsinfo *ip;
	register int line;
	register struct xsstrtty *tp;
	register int board;
	int stat, size, vector, level, xsbaseflag = 0;
	int xstime;
	int ndevs = driver->numdev;  	/* Number of boards */
	struct gen_device *devp;
	ulong temp;
	unchar xssetupok, xsvecfound;
	unchar *ptr;
	uparms_781 *up;
	struct dev *dp;

	if (!ndevs)
		return;
	else
		xs_info = (struct xsinfo **)calloc(sizeof(struct xsinfo *) * ndevs);
	if (!xs_info) {
		cmn_err(CE_WARN, "xs: No memory for info structure; deconfigured\n");
		/*
		 *+ The driver could not allocate system memory for an information
		 *+ structure.
		 */
		return;
	}
	nxs = 0;
	/*
	 * For each configured board ...
	 *   allocate info data structure (for live boards)
	 *   set up interrupt lines for the board
	 *   For each line ...
	 *     initialize uart parameters
	 */
	devp = driver->devices;
	for (board = 0; board < ndevs; board++, devp++) {
		xs_info[board] = NULL;
		if (!devp->driver)
			continue;

		ip = xs_info[board] =
		    (struct xsinfo *) calloc(sizeof(struct xsinfo));
		if (!ip) {
			cmn_err(CE_WARN, "xs%d: No memory for info structure; deconfigured\n", board);
			/*
			 *+ The driver could not allocate system memory for an information
			 *+ structure.
		 	 */
			continue;
		}
		ip->dev_ptr = devp;
		xsaddr =
		    (hardware_781 *) devp->devreg[0];
		ip->xs_addr = xsaddr;
		init_lock(&ip->xs_lock);

		/* Allocate IDB for this board */
		ptr = (unchar *) calloc(sizeof(struct xs_idb) + 1);
		if (!ptr) {
			cmn_err(CE_WARN, "xs%d: No memory for 781 IDB\n", board);
			/*
			 *+ The driver could not allocate system memory for an IDB.
		 	 */
			continue;
		}
		if (((ulong)ptr & 1) != 0)
			ptr++;
		ip->idb = (idb_781 *)ptr;

		/* Allocate output buffers for this board */
		ptr = (unchar *) calloc(sizeof(struct outbuf_array) + 1);
		if (!ptr) {
			cmn_err(CE_WARN, "xs%d: No memory for 781 output buffers\n", board);
			/*
			 *+ The driver could not allocate system memory for 781
			 *+ output buffers.
		 	 */
			continue;
		}
		if (((ulong)ptr & 1) != 0)
			ptr++;
		ip->xs_outbufs = (outbuf_array_781 *)ptr;

		/* Allocate input buffers for this board */
		ptr = (unchar *) calloc(sizeof(struct inbuf_array) + 1);
		if (!ptr) {
			cmn_err(CE_WARN, "xs%d: No memory for 781 input buffers\n");
			/*
			 *+ The driver could not allocate system memory for 781
			 *+ input buffers.
		 	 */
			continue;
		}
		if (((ulong)ptr & 1) != 0)
			ptr++;
		ip->xs_inbufs = (inbuf_array_781 *)ptr;

		/* Set up VME mapping for input buffers */
		size = sizeof(struct inbuf_array);
		if (devp->bus->dma_setup)
			temp = (*devp->bus->dma_setup) (devp, (long) size, ip->xs_inbufs, 1);
		else
			temp = -1;
		if (temp == -1) {
			cmn_err(CE_WARN, "xs%d: Cannot setup VME maps for input buffers\n", board);
			/*
			 *+ The driver could not set up VME maps for input buffers.
			 */
			 continue;
		}
		if (temp < size) {
			cmn_err(CE_WARN,"xs%d: Mapped size is less than inbuf size\n", board);
			/*
			 *+ The driver could not set up VME maps for input buffers.
			 */
			continue;
	        }
		/*
		 * devp->bus_map_addr is the VME address of the inbuf struct.
		 * It was written there by the dma_setup routine.
		 */
		temp = (long) devp->bus_map_addr;

		/* Load VME address of input buffers into idb */
		ip->idb->icba_hi = stovs(temp >> 16);
		ip->idb->icba_lo = stovs(temp);
		ip->idb->inbuf_len = stovs(IBUFSIZE);

		/* Set up VME mapping for output buffers */
		size = sizeof(struct outbuf_array);
		if (devp->bus->dma_setup)
			temp = (*devp->bus->dma_setup) (devp, (long) size, ip->xs_outbufs, 1);
		else
			temp = -1;
		if (temp == -1) {
			cmn_err(CE_WARN, "xs%d: Cannot setup VME maps for output buffers\n", board);
			/*
			 *+ The driver could not set up VME maps for output buffers.
			 */
			 continue;
		}
		if (temp < size) {
			cmn_err(CE_WARN,"xs%d: Mapped size is less than outbuf size\n", board);
			/*
			 *+ The driver could not set up VME maps for input buffers.
			 */
			continue;
	        }
		/*
		 * devp->bus_map_addr is the VME address of the outbuf struct.
		 * It was written there by the dma_setup routine.
		 */
		temp = (long) devp->bus_map_addr;

		/* Load VME address of output buffers into idb */
		ip->idb->ocrba_hi = stovs(temp >> 16);
		ip->idb->ocrba_lo = stovs(temp);
		ip->idb->outbuf_len = stovs(OBUFSIZE);

		/* Set up VME mapping for idb structure */
		size = sizeof(struct xs_idb);
		if (devp->bus->dma_setup)
			temp = (*devp->bus->dma_setup) (devp, (long) size, ip->idb, 1);
		else
			temp = -1;
		if (temp == -1) {
			cmn_err(CE_WARN, "xs%d: Cannot setup VME maps for idb\n", board);
			/*
			 *+ The driver could not set up VME maps for IDB.
			 */
			 continue;
		}
		if (temp < size) {
			cmn_err(CE_WARN,"xs%d: Mapped size is less than idb size\n", board);
			/*
			 *+ The driver could not set up VME maps for IDB.
			 */
			continue;
	        }
		/*
		 * devp->bus_map_addr is the VME address of the outbuf struct.
		 * It was written there by dma_setup routine.
		 */
		temp = (long) devp->bus_map_addr;

		/* Load idb VME address into 781 hardware registers */
		ip->xs_addr->idb_addr3 = (ushort)(temp >> 24);
		ip->xs_addr->idb_addr2 = (ushort)(temp >> 16);
		ip->xs_addr->idb_addr1 = (ushort)(temp >> 8);
		ip->xs_addr->idb_addr0 = (ushort)(temp);
		ip->xs_addr->idb_addr_mod = XS_DMA_AMOD;

		if (driver->intr)
			vector = ivecall(xs_bin);

		if (!xsbaseflag) {
			xsbaseflag++;
			xsbase = vector;
		}
		ivecinit(xs_bin, vector, driver->intr);

		ip->idb->addr_mod = stovs(XS_DMA_AMOD);

		level = devp->config[0].busint;
		ip->idb->inp_intlev = stovs(level);
		ip->idb->out_intlev = stovs(level);
		ip->idb->stat_intlev= stovs(level);

		/*
		 * Spin through integers 0 to 127 looking for an unused
		 * VME interrupt vector to use for this 781 board.  If
		 * all vectors are in use, or the setup function returns
		 * an error or the interrupt setup routine goes away,
		 * skip the init of this board.  Else, set up the interrupt
		 * vector for this board.
		 */
		xssetupok = TRUE;
		xsvecfound = FALSE;
		while (xssetupok && !xsvecfound && xs_vme_basevec < 128) {
			if (*devp->bus->intr_setup)
				stat = (*devp->bus->intr_setup)
		       	       	       (devp->bus, level, xs_vme_basevec, xs_bin,
					vector, TMPOS_GROUP | SL_GROUP);
			else {
				xssetupok = FALSE;
				stat = 0;
				continue;
			}
			if (stat == BUS_INTVEC_BAD) {
				xs_vme_basevec++;
				continue;
			}
			if (stat != BUS_INT_OK) {
				xssetupok = FALSE;
			 	continue;
			}
			ip->idb->inp_intvec  = stovs(xs_vme_basevec);
			ip->idb->out_intvec  = stovs(xs_vme_basevec);
			ip->idb->stat_intvec = stovs(xs_vme_basevec);
			xsvecfound = TRUE;
			xs_vme_basevec++;
		}
		if (!xssetupok) {
			cmn_err(CE_WARN, "xs%d: bad vme intr map, stat %d\n",
				board, stat);
			/*
			  *+ While the ts driver was attempting to initialize the interrupt
			  *+ mapping for a 781 board, an unspecified error
			  *+ occurred.  The device is not configured.  If the error
			  *+ is due to a duplicate interrupt, this error
			  *+ message will have been preceded by another
			  *+ error message.  Otherwise, the error resulted because the
			  *+ bus owner is not a VME.
		 	  */
			continue;
		}
		if ((xs_vme_basevec > 127) && !xsvecfound) {
			cmn_err(CE_WARN, "xs%d: out of VME interrupt vectors",
				board);
			/*
			 *+ All 128 VME interrupt vectors have been allocated
			 *+ to other VME boards.
		 	 */
			continue;
		}
		/* Init the output buffers and uart parameter block */
		for (line = 0; line < 16; line++) {
			ip->xs_outbufs->obufs[line].get = stovs(1);
			ip->xs_outbufs->obufs[line].put = 0;
			up = &ip->idb->uart_parms[line];
			up->serial_modes =
				XS_STOP1 | XS_BITS8 | XS_NO_PARITY | XS_HW_FLOW;
			up->baud_rates = XSB_9600 << 12 | XSB_9600 << 8;
			up->line_state = 0;
			up->line_mask = XSLINE_DSR | XSLINE_CTS | XSLINE_DCD;
 			up->input_timeout = stovs(xsrxtime);
 			up->input_min = stovs(xsinputmin);
			up->xoff_char = 0;
			up->xon_char = 0;
			up->low_water = stovs(xslowwater);
			up->break_time = stovs(xsbreaktime/5);
		}

		/* Reset the board */
		ip->xs_addr->cntl_status = XSCR_MRST;
		xstime = calc_delay(xstimeout);
		while ((xstime-- > 0) && (ip->xs_addr->cntl_status & XSSR_RSTA) != 0);
		if (ip->xs_addr->cntl_status & XSSR_RSTA) {
			cmn_err(CE_WARN, "xs%d: Board failed reset, code %d, deconfigured\n",
			       board, ip->xs_addr->cntl_status);
			/*
			 *+ The Xylogics 781 board failed module reset.
			 */
			continue;
		}

		/*
		 * Init line-specific items.
		 */
		for (tp = ip->xs_strtty,line = 0; line < 16; tp++,line++) {
			init_lock(&tp->t_ttylock);
			init_sema(&tp->t_rawqwait, 0);
			tp->t_cmask = XSLINE_DCD;
			tp->t_bufcallid = 0;
			dp = (struct dev *)calloc(sizeof(struct dev));
			dp->d_dev = makedev(xs_c_major, (board << 4) | (line & 0x0f));
			dp->d_ops = &xsdevops;
			if (devsw_add(dp) != 0) {
				cmn_err(CE_WARN,
				"xs%d: failure to add line %d dev to devsw\n",
				 board, line);
				/*
				 *+ The kernel encountered a problem when it
				 *+ tried to add a console
				 *+ dev to the devsw data structure.  This is
				 *+ due to a driver that is adding a dev to
				 *+ devsw; the major/minor number for the dev
				 *+ already belongs to another dev.
				 */
			}
		}
		nxs++;		/* One more good board */
	}
}

/*
 * Open a Xylogics 781 line.
 */
static
xsopen(rd_q, dev, flag, sflag)
	queue_t *rd_q;
	dev_t dev;
	int flag, sflag;
{
	register struct xsstrtty *tp;
	struct xsinfo *ip;
	struct xs_hardware xsregs;
	register int line = XSLINE(dev);
	int board = XSBOARD(dev);
	spl_t s;
	unchar firstopen;

#ifdef DEBUG
	DPRINTF1("xsopen: start, line %d board %d\n",line,board);
#endif
	if (xs_info == NULL || board >= nxs || (ip = xs_info[board]) == NULL) {
		u.u_error = ENXIO;
		return (OPENFAIL);
	}

	/* Check for non-driver open */
	if (sflag != DRVROPEN)
		return (OPENFAIL);

	tp = &ip->xs_strtty[line];
	s = p_lock(&tp->t_ttylock, SPLSTR);

	REQ_CTTY();	/* inform stream head that we can be a ctty */

	firstopen = 0;
	XS_CLEAR(xsregs);		 	/* Start with clean hw */
	/*
	 * If this is first open, initialize state to default.
	 */
	if ((tp->t_state & XS_ISOPEN) == 0) {
		firstopen = 1;
		tp->msg = NULL;			/* set up streams pointers */
		tp->t_dev = dev;
		tp->qptr = WR(rd_q);
		tp->t_bufcallid = 0;
		tp->t_delayid = 0;
		tp->t_breakid = 0;
		tp->t_flags = 0;
		tp->t_flow = 0;
		ip->boardnum = board;
		rd_q->q_ptr = (char *)tp;
		WR(rd_q)->q_ptr = (char *)tp;

		tp->t_state |= XS_ISOPEN;
		tp->t_cflags = xscflags;	/* Initial value for cflags */

		/* Do a read flush to discard any transient data */
		xsregs.port = line;
		xsregs.cmd_report = XSCR_FLUSH;
		xs_cmd(ip, &xsregs);
		XS_CLEAR(xsregs);

		/* If we cannot set the initial line parameters, clear */
		/* everything before we exit.			       */
		if (xs_param(&ip->idb->uart_parms[line], tp)) {
			tp->t_state = 0;
			tp->qptr = NULL;
			rd_q->q_ptr = NULL;
			WR(rd_q)->q_ptr = NULL;
			v_lock(&tp->t_ttylock, s);
			return(OPENFAIL);
		}

		/* Init input and output buffer pointers for this port */
		ip->xs_outbufs->obufs[line].get = stovs(1);
		ip->xs_outbufs->obufs[line].put = 0;
		ip->xs_inbufs->ibufs[line].count = 0;

		if (tp->t_cflags & CREAD) {
			/* Enable RX interrupt, set read bit for 781 */
			ip->idb->uart_parms[line].line_mask |= XS_IENABLE;
			if (line >= 0 && line <= 7)
				xsregs.low_rd_active |= 1 << line;
			else
				xsregs.hi_rd_active |=  1 << (line - 8);

			xsregs.cmd_report = XSCR_RAC;
		} else
			ip->idb->uart_parms[line].line_mask &= ~XS_IENABLE;

		ip->idb->uart_parms[line].line_mask &= ~tp->t_cmask;
		ip->idb->uart_parms[line].line_mask &= ~XS_UNI;
		ip->idb->uart_parms[line].line_mask |= XS_MCU;

		if (tp->t_cflags & CBAUD)
			ip->idb->uart_parms[line].line_state |= XSLINE_DTR;

		/* Execute UART_INIT and read enables (if any) */
		xsregs.port = line;
		xsregs.cmd_report |= XSCR_UINIT;
		xs_cmd(ip, &xsregs);
	}
	if (tp->t_cflags & CLOCAL)
		tp->t_state |= XS_CARR_ON;
	else
		tp->t_state &= ~XS_CARR_ON;


	/* Wait for carrier, if required. */
	if (!(flag & FNDELAY)) {
		XS_CLEAR(xsregs);
		xsregs.cmd_report = XSCR_MGET;
		xsregs.port = line;
		xs_cmd(ip, &xsregs);
		(void) xs_wait(ip);
		if (ip->dev_ptr->bus->dma_flush)
			(*ip->dev_ptr->bus->dma_flush) (ip->dev_ptr);
		if (ip->idb->uart_parms[line].line_state & tp->t_cmask)
			tp->t_state |= XS_CARR_ON;
		while ((tp->t_state & XS_CARR_ON) == 0) {
			tp->t_state |= XS_WOPEN;
			/*
			 * Waiting for carrier.  If we get signalled out of the
			 * wait, and we are the first openers of this tty line,
			 * clean up and return an error code.  If we are not
			 * the first openers, just return the error code.
			 */
#ifdef DEBUG
			DPRINTF2("xsopen: waiting for carrier\n");
#endif
			if (p_sema_v_lock(&tp->t_rawqwait, PZERO+1 | PCATCH,
					  &tp->t_ttylock) == PCATCH) {
				if (firstopen) {
					tp->t_state = 0;
					tp->t_flags = 0;
					tp->qptr = NULL;
					rd_q->q_ptr = NULL;
					WR(rd_q)->q_ptr = NULL;
				}
				return(OPENFAIL);
			}
			s = p_lock(&tp->t_ttylock, SPLSTR);
		}
#ifdef DEBUG
		DPRINTF2("xsopen: got carrier\n");
#endif
	}
	tp->t_state &= ~XS_WOPEN;
	tp->t_dev =dev;
	v_lock(&tp->t_ttylock, s);

#ifdef DEBUG
	DPRINTF1("xsopen: leaving\n");
#endif
	return(OPENOK);
}

/*
 * Close a Xylogics 781 line.
 */
static
xsclose(rd_q, flag)
	queue_t *rd_q;
	int flag;
{
	register struct xsstrtty *tp = (struct xsstrtty *) rd_q->q_ptr;
	register struct xsinfo *ip = xs_info[XSBOARD(tp->t_dev)];
	register int line = XSLINE(tp->t_dev);
	struct xs_hardware xsregs;
	spl_t s;
	int curmsg;
	short wait, numq;
	uint	bid;
	int	drainid;

	s = p_lock(&tp->t_ttylock, SPLSTR);
#ifdef DEBUG
	DPRINTF1("xsclose: start, line %d\n",line);
#endif

	XS_CLEAR(xsregs);

	/*
	 * If the carrier is still active and NDELAY is not set,
	 * try to process all queued output.
	 */
	if ((tp->t_state & XS_CARR_ON) && ((flag & FNDELAY) == 0)) {
		tp->t_flags |= XS_DISCARDIN;	/* logically disable input */
		if ((numq = qsize(WR(rd_q))) || xs_drain(ip, line)) {
			/*
			 * We really don't know how much data is currently in
			 * the output buffer unless we were to count each byte
			 * as it went on the buffer and subtracted when we think
			 * they were off.  So, set curmsg to the size of the
			 * output buffer as a "worst case" option.
			 */
			curmsg = OBUFSIZE;
			tp->t_state |= XS_WCLOSE;
			tp->t_state &= ~XS_DRAINTIME;
			wait = ((((numq * XS_AVGCNT) + curmsg) *
				xsbaud_time[(tp->t_cflags & CBAUD)])/1000) +
				xsaddwait;
#ifdef DEBUG
			DPRINTF2("xsclose: will wait %d secs for drain\n",wait);
#endif
			drainid = timeout(xs_timeout, (caddr_t) rd_q, wait * HZ);
			switch (p_sema_v_lock(&tp->t_rawqwait, PZERO+1 | PCATCH, &tp->t_ttylock)) {
			case PCATCH:
				untimeout(drainid);
				s = p_lock(&tp->t_ttylock, SPLSTR);	/* SIGNALLED */
				tp->t_state &= ~XS_WCLOSE;
				break;
			default:
				untimeout(drainid);
				s = p_lock(&tp->t_ttylock, SPLSTR);
			}
			/*
			 * the 781 has a bug where it may have an internal
			 * pointer that allows old data in its onboard buffer to
			 * be transmitted again - RESET clears the problem
			 */
			xsregs.cmd_report = XSCR_RESET;
			xsregs.port = line;
			xs_cmd(ip, &xsregs);
		}
	} else {
		/*
		 * If the carrier is off or NDELAY is set,
		 * kill the current output.
		 */
		xsregs.cmd_report = XSCR_RESET;
		xsregs.port = line;
		xs_cmd(ip, &xsregs);
	}

	/* in all close cases, release all allocated messages */
	flushq(WR(rd_q), FLUSHALL);
	freemsg(tp->msg);
	tp->msg = NULL;

	(void) xs_wait(ip);
	ip->xs_outbufs->obufs[line].get = stovs(1);
	ip->xs_outbufs->obufs[line].put = 0;
	ip->idb->uart_parms[line].line_mask &= ~XS_IENABLE;
	ip->idb->uart_parms[line].line_mask |= tp->t_cmask;
	if (tp->t_cflags & HUPCL)
		ip->idb->uart_parms[line].line_state &= ~XSLINE_DTR;
	XS_CLEAR(xsregs);
	xsregs.cmd_report = XSCR_UINIT;
	xsregs.port = line;
	xs_cmd(ip, &xsregs);

	/*
	 * unbufcall() must be called without tp->t_ttylock held.
	 * Note the current bufcallid value and deregister it.
	 */
	tp->t_state &= ~XS_ISOPEN;
	if (bid=tp->t_bufcallid) {
		tp->t_bufcallid = 1;	/* prevent further bufcalls */
		v_lock(&tp->t_ttylock, s);
		(void) unbufcall(bid);
	} else
		v_lock(&tp->t_ttylock, s);

	/*
	 * wait for all put and server activity to finish.
	 * interrupt activity has already been disabled by the
	 * WCLOSE state flag.
	 */
	disable_procs(rd_q);

	untimeout(tp->t_delayid);	/* cancel any pending timeouts */
	untimeout(tp->t_breakid);	/* cancel any pending timeouts */
	(void)p_lock(&tp->t_ttylock, SPLSTR);

	tp->t_state = 0;		/* clear streams flags and ptrs */
	tp->t_flags = 0;

	rd_q->q_ptr = NULL;		/* disconnect stream pointers */
	WR(rd_q)->q_ptr = NULL;
	tp->qptr = NULL;

#ifdef DEBUG
	DPRINTF1("xsclose: leaving\n");
#endif

	v_lock(&tp->t_ttylock, s);
}

/*
 * xsrecover - bufcall recovery interface.
 */
xsrecover(tp)
	struct xsstrtty *tp;
{
	mblk_t	*mptr;
	int line = XSLINE(tp->t_dev);
	int board = XSBOARD(tp->t_dev);
	struct xsinfo *ip = xs_info[board];
	struct xs_hardware xsregs;
	int cnt;
	spl_t s;

	s = p_lock(&tp->t_ttylock, SPLSTR);

	if (((tp->t_state & XS_ISOPEN) == 0) || (tp->t_state & XS_WCLOSE)) {
		v_lock(&tp->t_ttylock, s);
		return;
	}
	XS_CLEAR(xsregs);
	cnt = vtoss(ip->xs_inbufs->ibufs[line].count);
	/*
	 * If the input buffer count has somehow been corrupted from the
	 * time the 781 interrupted us until now, discard the data
	 * and restart the read anyway.
	 */
	if (cnt < 0 || cnt > IBUFSIZE) {
		cmn_err(CE_WARN, "xs%d: illegal block input count %d line %d rec\n",
  					board, cnt, line);
		/*
		 *+ The input block size from the 781 board is
		 *+ out of range.  Data discarded.
	 	 */
		if (line <= 7)
			xsregs.low_rd_active |= 1 << line;
		else
			xsregs.hi_rd_active |= 1 << (line - 8);
		tp->t_flags &= ~XS_RDBLOCKED;
		xsregs.port = line;
		xsregs.cmd_report = XSCR_RAC;
		xs_cmd(ip, &xsregs);
		v_lock(&tp->t_ttylock, s);
		return;
	}
	/*
	 * If the count is ok, try to get a streams buffer for the input
	 * data.  If we *still* cannot get a buffer, the only thing to do
	 * is bufcall again.  Otherwise, load the buffer with input data and
	 * send it upstream.
	 */
	if ((mptr = allocb(cnt, BPRI_MED)) == NULL)
		tp->t_bufcallid = bufcall((uint) cnt, BPRI_MED, xsrecover, tp);
	else {
		tp->t_bufcallid = 0;
		bcopy(&ip->xs_inbufs->ibufs[line].data[0],
		      (char *) mptr->b_wptr, (unsigned) cnt);
		mptr->b_wptr += cnt;
		putq(RD(tp->qptr), mptr);
	}
	v_lock(&tp->t_ttylock, s);
}

/*
 * Xylogics 781 interrupt service routine.
 */
void
xs_intr(vector)
	int vector;
{
	register struct xsinfo *ip;
	register int board = vector - xsbase;
	register ushort status;
	register ushort mask;
	ushort report, rcode;
	struct xsstrtty *tp;
	int cnt, line;
	spl_t s;
	mblk_t	*mptr;
	struct xs_hardware xsregs;
	unchar err_data;

	if ((unsigned) board >= nxs || (ip = xs_info[board]) == NULL) {
		cmn_err(CE_NOTE, "xs%d: unknown interrupt, vector %d\n", board, vector);
		/*
		 *+ An interrupt was received from an unknown source.  The interrupt
		 *+ is ignored.
		 */
		return;
	}
	XS_LOAD(ip, xsregs);				/* Get hw regs info */
	if (ip->dev_ptr->bus->dma_flush)
		(*ip->dev_ptr->bus->dma_flush) (ip->dev_ptr);

	err_data = 0;
	report = xsregs.cmd_report;
	rcode = report & XSCR_RMASK;

	/*
	 * if this is a FRAME or PARITY error, setup a dummy read complete
	 * for the line with the error
	 */
	if (((report & XSCR_RAC) == 0) &&
	    ((rcode == (ushort)XSCR_PARITY) ||
	    (rcode == (ushort)XSCR_FRAME))) {
		report |= XSCR_RAC;
		line = xsregs.port;
		if (line <= 7)
			xsregs.low_rd_active |= 1 << line;
		else
			xsregs.hi_rd_active |= 1 << (line - 8);
	}

	/* Check for read-completes */
	if (report & XSCR_RAC) {
		status = xsregs.low_rd_active;
		l.cnt.v_rcvint++;
		mask = 0x01;
		for (line = 0; line <= 15; line++, mask <<= 1) {
			if (line == 8) {
				status = xsregs.hi_rd_active;
				mask = 0x01;
			}
			if (status & mask) {
				tp = &ip->xs_strtty[line];
				s = p_lock(&tp->t_ttylock, SPLSTR);

				if (((tp->t_state & XS_ISOPEN) == 0) ||
				    (tp->t_state & XS_WCLOSE) ||
				    (tp->t_flags & XS_DISCARDIN)) {
					if (line <= 7)
						xsregs.low_rd_active &= ~mask;
					else
						xsregs.hi_rd_active &= ~mask;
					v_lock(&tp->t_ttylock, s);
					continue;
				}

				cnt = vtoss(ip->xs_inbufs->ibufs[line].count);
				if (cnt < 0 || cnt > IBUFSIZE) {
					cmn_err(CE_WARN, "xs%d: illegal block input count %d line %d\n",
						board, cnt, line);
					/*
					 *+ The input block size from the 781 board is
					 *+ out of range.  Data discarded.
				 	 */
					cnt = 0;
				}
				l.cnt.v_rawch += cnt;
				/*
				 * If this port reported a parity error, flag it:
				 * we will use the last byte of received data as
				 * the parity error culprit. Remember to clean up
				 * the dummy read active bit.
				 */
				if (((rcode == (ushort)XSCR_PARITY) ||
				    (rcode == (ushort)XSCR_FRAME)) &&
				    (line == xsregs.port)) {
					if (cnt > 0) {
						err_data = 1;
						cnt--;
					}
				}
				if (cnt > 0) {
					if ((mptr = allocb(cnt, BPRI_MED)) == NULL) {
						if (xsprintalocfail)
							cmn_err(CE_WARN, "xs%d: line %d unable to allocb %d bytes\n",
								board, line, cnt);
							/*
							 *+ A driver attempt to allocate a
							 *+ stream buffer for incoming data
							 *+ failed.
							 */
						/*
					 	 * bufcall to wait for buffer
					 	 */
						if (line <= 7)
							xsregs.low_rd_active &= ~mask;
						else
							xsregs.hi_rd_active &= ~mask;

						tp->t_flags |= XS_RDBLOCKED;

						if (tp->t_bufcallid == 0)
							tp->t_bufcallid = bufcall((uint) cnt, BPRI_MED, xsrecover, tp);
						(void)putnextctl1(RD(tp->qptr), M_CTL, MC_STOPI);
					} else {
						bcopy(&ip->xs_inbufs->ibufs[line].data[0],
					      		(char *)mptr->b_wptr,
							(unsigned) cnt);
						mptr->b_wptr += cnt;

						/*
						 *  Put the message upstream if possible.  Otherwise,
						 *  put the message on the local queue, block
						 *  interrupts on the correct line and set a flag
						 *  to indicate the we are blocked.  The message on
						 *  the local queue will be processed by the service
						 *  procedure, xsrsrv().
						 */
						if (canputnext(RD(tp->qptr))) {
							putnext(RD(tp->qptr), mptr);
						} else {

							putq(RD(tp->qptr), mptr);

							if (line <= 7)
								xsregs.low_rd_active &= ~mask;
							else
								xsregs.hi_rd_active &= ~mask;

							tp->t_flags |= XS_RDBLOCKED;
						}
					}
				}
				v_lock(&tp->t_ttylock, s);
			}
		}
		xsregs.cmd_report = XSCR_RAC;	/* Restart reads */
	}

	/* Come here if a write operation passed */
	/* the low-water mark of the write queue */
	if ((report & XSCR_WAC) || (report & XSCR_SYNC)) {
		l.cnt.v_xmtint++;
		status = xsregs.low_wr_active;
		mask =0x01;
		for (line = 0; line < 16; line++, mask <<= 1) {
			if (line == 8) {
				status = xsregs.hi_wr_active;
				mask = 0x01;
			}
			if (status & mask) {
				tp = &ip->xs_strtty[line];
				s = p_lock(&tp->t_ttylock, SPLSTR);

				if ((tp->t_state & XS_ISOPEN) == 0) {
					v_lock(&tp->t_ttylock, s);
					continue;
				}
				tp->t_state &= ~XS_TX_FULL; /* not full anymore */

				/*
				 * if a flow control request was defered, send it now
				 */
				if (tp->t_flags & XS_FLOW) {
					tp->t_flags &= ~XS_FLOW;
					bcopy((caddr_t) &tp->t_flow, XS_PUT_ADDR(ip, line), 1);
					if (ip->dev_ptr->bus->dma_flush)
						(*ip->dev_ptr->bus->dma_flush) (ip->dev_ptr);
					if (xs_bufptr_move(ip, line, 1)) {
						if (line <= 7)
							xsregs.low_wr_active |= 1 << line;
						else
							xsregs.hi_wr_active |= 1 << (line - 8);
						xsregs.cmd_report = XSCR_WAC;
					}
					xs_cmd(ip, &xsregs);
				}

				/*
				 * If this is a SYNC interrupt it is for
				 * one of two wait for drain requests: in
				 * any event, the output buffer is EMPTY
				 */
				if (report & XSCR_SYNC) {
					/*
					 * clear the output buffer busy bit
					 * and the SYNC request flag
					 */
					tp->t_state &= ~XS_TX_BUSY;
					tp->t_flags &= ~XS_SYNCREQ;
					/*
					 * Case 1: A received ioctl needs to wait for
					 * all data to be transmitted from the 781.
					 * Enable the write queue and clear the flag.
					 * The next queue message should be the ioctl.
					 */
					if (tp->t_state & XS_WIOCTL) {
						tp->t_state &= ~XS_WIOCTL;
						qenable(tp->qptr);
					}

					/*
					 * Case 2: A line is waiting for close.  All of
					 * the data is out of the write queue, but up to
					 * 1k of it may still be on the 781.  This SYNC
					 * interrupt indicates that all of the data has
					 * left the 781 buffers.  Wake up the close
					 * routine that is sleeping.
					 */
					else if (tp->t_state & XS_WCLOSE) {
						tp->t_state &= ~XS_WCLOSE;
						v_sema(&tp->t_rawqwait);
					}
					v_lock(&tp->t_ttylock, s);
					continue;
				}

				/*
				 * If we are still sending a data message
				 * out to the 781, call xsstart to put
				 * more data on the output queue.
				 */
				if (tp->msg != NULL)
					xsstart(tp, tp->msg);
				/*
				 * all messages have been sent, so enable the queue
				 */
				else
					qenable(tp->qptr);
				/*
				 * try to queue a SYNC request if there are closes or
				 * ioctls waiting
				 */
				if ((tp->t_state & (XS_WCLOSE | XS_WIOCTL)) &&
				    (!(report & XSCR_SYNC)))
					(void)xs_drain(ip,line);

				v_lock(&tp->t_ttylock, s);
			}
		}
	}
	/*
	 * Check for other errors reported by 781.
	 * All error reports are associated with a port on this 781
	 * board, except for the Internal Queue Overflow message.
	 * This is a board-level error message.
	 */
	if (rcode) {
		if (rcode != XSCR_INTQOVF) {
			line = xsregs.port;
			tp = &ip->xs_strtty[line];
			s = p_lock(&tp->t_ttylock, SPLSTR);
			if (tp->t_state & XS_ISOPEN) {
				switch (rcode) {
					case XSCR_PARITY:	/* Parity Error */
#ifdef DEBUG
						DPRINTF2("xsintr: parity err, line %d\n",line);
#endif
						if (!err_data) {
							if (!putnextctl1(RD(tp->qptr), M_CTL, MC_PARITY_ERR))
								cmn_err(CE_WARN, "xs%d: line %d parity error report failed\n",
									board, line);
								/*
								 *+ A driver attempt to report a parity
								 *+ error upstream failed.
				 		 	 	 */
						} else {
							if (!putnextctl2(RD(tp->qptr), M_CTL, MC_PARITY_ERR,
								ip->xs_inbufs->ibufs[line].data[cnt]))
							cmn_err(CE_WARN, "xs%d: line %d parity error/data report failed\n",
								board, line);
							/*
							 *+ A driver attempt to report a parity
							 *+ error and associated data upstream failed.
				 		 	 */
						}
						break;
					case XSCR_FRAME:	/* Framing Error */
#ifdef DEBUG
						DPRINTF2("xsintr: frame error line %d\n",line);
#endif
						if (!err_data) {
							if (!putnextctl1(RD(tp->qptr), M_CTL, MC_FRAME_ERR))
								cmn_err(CE_WARN, "xs%d: line %d framing error report failed\n",
									board, line);
								/*
								 *+ A driver attempt to report a framing
								 *+ error upstream failed.
						 	 	 */
						} else {
							if (!putnextctl2(RD(tp->qptr), M_CTL, MC_FRAME_ERR,
								ip->xs_inbufs->ibufs[line].data[cnt]))
								cmn_err(CE_WARN, "xs%d: line %d framing error report failed\n",
									board, line);
								/*
								 *+ A driver attempt to report a framing
								 *+ error upstream failed.
						 	 	 */
						}
						break;

					case XSCR_INOVRR:	/* Input overrun */
						cmn_err(CE_WARN, "xs%d: Input overrun, line %d.\n", board, line);
						/*
						 *+ The Xylogics 781 board encountered an input
						 *+ overrun condition.
					 	 */
						break;
					case XSCR_ACCERR:	/* Data access error */
						cmn_err(CE_WARN, "xs%d: 781 VME data access error, line %d.\n", board, line);
						/*
						 *+ The Xylogics 781 board encountered a data
						 *+ access error condition.
					         */
						break;
					case XSCR_ACCTIME:	/* Data access timeout error */
						cmn_err(CE_WARN, "xs%d: 781 VME data access timeout error, line %d.\n", board, line);
						/*
						 *+ The Xylogics 781 board encountered a data
						 *+ access timeout error condition.
					 	 */
						break;
					case XSCR_UARTOVF:	/* Uart overflow */
						if (xsprintoverflow)
							cmn_err(CE_WARN, "xs%d: 781 octart overflow, line %d.\n", board, line);
							/*
							 *+ The Xylogics 781 board encountered a uart
							 *+ overflow condition.
					 	 	 */
						break;
					case XSCR_CMDERR:	/* Command Error */
						cmn_err(CE_WARN, "xs%d: Command error, line %d\n",board, line);
						/*
						 *+ The Xylogics 781 board encountered a command
						 *+ error condition.
					 	 */
						break;

					case XSCR_MODEM:	/* modem lines changed */

						if (tp->t_cflags & CLOCAL)
							break;
						if (ip->idb->uart_parms[line].line_state & tp->t_cmask) {
							tp->t_state |= XS_CARR_ON;
							if (tp->t_state & (XS_WOPEN | XS_WCLOSE)) {
								tp->t_state &= ~XS_WCLOSE;
								vall_sema(&tp->t_rawqwait);
							}
						} else {
							tp->t_state &= ~XS_CARR_ON;
							if (!(tp->t_state & XS_WOPEN)) {
			        				if (!putnextctl(RD(tp->qptr), M_HANGUP))
									cmn_err(CE_WARN, "xs%d: M_HANGUP report failure, line %d\n",
										board, line);
									/*
									 *+ An attempt to send a M_CTL message upstream
									 *+ to report a carrier drop on a 781 port
									 *+ failed.
						 		 	 */
							}
						}
						break;

					default:
						cmn_err(CE_WARN, "xs%d: unexpected report code 0x%x\n", board, rcode);
						/*
						 *+ An unexpected report code was received from
						 *+ the Xylogics 781 board.
					 	 */
						break;
				}
			}
			v_lock(&tp->t_ttylock, s);
		} else { 	/* Internal Queue Overflow */
			cmn_err(CE_WARN, "xs%d: Internal Queue Overflow\n", board);
			/*
			 *+ The Xylogics 781 board encountered an internal queue overflow
			 *+ condition.  This situation occurs only if the host
			 *+ system does not respond to a 781 interrupt for a long time.
		 	 */
		}
	}
	if (xsregs.cmd_report == XSCR_RAC) {
		/*
		 * Clear write active bits.  781 looks at them
		 * even though the WAC bit is not set.
		 */
		xsregs.low_wr_active = 0;
		xsregs.hi_wr_active = 0;
		xs_cmd(ip, &xsregs);
	}
	ip->xs_addr->cntl_status = XSCR_CRS;		/* Clear interrupt */
}

/*
 * xsrsrv - Read service procedure.  Takes a message
 * off the read queue and processes it, if the driver is able.
 */
static
xsrsrv(rd_q)
        queue_t *rd_q;

{
	spl_t tplock;
	mblk_t *bp;
	struct xsstrtty *tp;
	int line;
	struct xs_hardware xsregs;
	register struct xsinfo *ip;

	/*
	 * Make sure there is something pointed to by
	 * rd_q.  If there isn't, return.
	 */
	if (rd_q == NULL) {
		cmn_err(CE_WARN, "xsrsrv: bad rd_q passed to routine\n");
	        /*
		 *+ The STREAMS system passed a bad read queue
		 *+ pointer to this service routine.
		 */
	        return;
	}

	tp = (struct xsstrtty *)rd_q->q_ptr;
	if (tp == NULL) {
		cmn_err(CE_WARN, "xsrsrv: bad rd_q passed to routine: bad tp\n");
		/*
		 *+ The STREAMS system passed a bad read queue
		 *+ pointer to this service routine.
		 */
		return;
	}

	/*
	 *  Process the message on the queue.  There
	 *  should only be one, but we'll be safe and
	 *  use a while loop.
	 */
	while ((bp = getq(rd_q)) != NULL) {

		if (canputnext(rd_q)) {
			putnext(rd_q, bp);
		} else {
			putbq(rd_q, bp);
			return;
		}
	}

	/*
	 *  Clear the blocked flag and reenable interrupts
	 *  on the correct line.
	 */
	tplock = p_lock(&tp->t_ttylock, SPLSTR);
	if (tp->t_flags & XS_RDBLOCKED) {
		XS_CLEAR(xsregs);
		tp->t_flags &= ~XS_RDBLOCKED;

		line = XSLINE(tp->t_dev);
		ip = xs_info[XSBOARD(tp->t_dev)];

		xsregs.port = line;
		xsregs.cmd_report = XSCR_RAC;

		if (line >= 0 && line <= 7)
			xsregs.low_rd_active |= 1 << line;
		else
			xsregs.hi_rd_active |= 1 << (line - 8) ;

		xs_cmd(ip, &xsregs);
	}
	v_lock(&tp->t_ttylock, tplock);
}

/*
 * xsmsg - Received message handler.
 *
 */
xsmsg(wr_q, mp)
	queue_t  *wr_q;
	mblk_t *mp;
{
	struct xsstrtty *tp = (struct xsstrtty *) wr_q->q_ptr;
	spl_t tplock;
	int line = XSLINE(tp->t_dev);
	int board = XSBOARD(tp->t_dev);
	struct xsinfo *ip;
	struct xs_hardware xsregs;

	if ((unsigned) board >= nxs || (ip = xs_info[board]) == NULL) {
		cmn_err(CE_NOTE, "xs%d: xsmsg rec'd bad wr_q ptr\n", board);
		/*
		 *+ The write queue pointer received was false or
		 *+ trashed, as the board value is out of range.
		 */
		freemsg(mp);
		return(0);
	}
	XS_CLEAR(xsregs);
	switch (mp->b_datap->db_type) {
	case M_PCCTL:
		if (mp->b_rptr != NULL) {
			switch (*mp->b_rptr) {
			case MC_STOP:		/* Suspend data to device */
				tplock = p_lock(&tp->t_ttylock, SPLSTR);
				/*
				 * If we are sending a break out of this
				 * port, wait until it is done before doing
				 * anything else on this port.
				 */
				if (tp->t_state & XS_TXBREAK) {
					v_lock(&tp->t_ttylock, tplock);
					return(XS_PRIPUTBACK);
				}
				tp->t_state |= XS_SUSPOUT;
				xsregs.cmd_report = XSCR_XOFF;
				xsregs.port = line;
				xs_cmd(ip, &xsregs);
				v_lock(&tp->t_ttylock, tplock);
				break;

			case MC_START:		/* Unsuspend data to device */
				tplock = p_lock(&tp->t_ttylock, SPLSTR);
				/*
				 * If we are sending a break out of this
				 * port, wait until it is done before doing
				 * anything else on this port.
				 */
				if (tp->t_state & XS_TXBREAK) {
					v_lock(&tp->t_ttylock, tplock);
					return(XS_PRIPUTBACK);
				}
				if (tp->t_state & XS_SUSPOUT) {
					tp->t_state &= ~XS_SUSPOUT;
					xsregs.cmd_report = XSCR_XON;
					xsregs.port = line;
					xs_cmd(ip, &xsregs);
				}
				v_lock(&tp->t_ttylock, tplock);
				break;

			case MC_STOPI:		/* Suspend data from device */
			case MC_STARTI:		/* Unsuspend data from device */
				tplock = p_lock(&tp->t_ttylock, SPLSTR);
				/*
				 * If we are sending a break out of this
				 * port, wait until it is done before doing
				 * anything else on this port.
				 */
				if (tp->t_state & XS_TXBREAK) {
					v_lock(&tp->t_ttylock, tplock);
					return(XS_PRIPUTBACK);
				}
				/*
				 * if the output buffer is full, save the flow control
				 * character and set the XS_FLOW flag so the interrupt
				 * routine will transmit the character when there is
				 * space in the output buffer: this is done rather than
				 * putting the message back on the queue to prevent
				 * deadlock
				 */
				if (ip->dev_ptr->bus->dma_flush)
					(*ip->dev_ptr->bus->dma_flush) (ip->dev_ptr);
				if (XS_OUTBUF_FULL(ip, line)) {
					tp->t_flags |= XS_FLOW;
					tp->t_flow = *(++mp->b_rptr);
					v_lock(&tp->t_ttylock, tplock);
					break;
				}
				tp->t_flags &= ~XS_FLOW;
				bcopy((caddr_t) ++mp->b_rptr, XS_PUT_ADDR(ip, line), 1);
				if (ip->dev_ptr->bus->dma_flush)
					(*ip->dev_ptr->bus->dma_flush) (ip->dev_ptr);
				if (xs_bufptr_move(ip, line, 1)) {
					if (line <= 7)
						xsregs.low_wr_active |= 1 << line;
					else
						xsregs.hi_wr_active |= 1 << (line - 8);
					xsregs.cmd_report = XSCR_WAC;
				}
				xs_cmd(ip, &xsregs);
				v_lock(&tp->t_ttylock, tplock);
				break;

			default:
				cmn_err(CE_WARN, "xs%d: M_PCCTL message received with bad subtype\n",board);
				/*
				 *+ The driver write put routine received a stream
				 *+ message of type M_PCCTL, but the message subtype
				 *+ is illegal.
				 */
			}
		} else {
			cmn_err(CE_WARN, "xs%d: M_PCCTL message received with NULL data ptr\n",board);
			/*
			 *+ The driver write put routine received a stream
			 *+ message of type M_PCCTL, but the message did
			 *+ not include a data block containing a subcommand and
			 *+ its parameters.
			 */
		}
		freemsg(mp);
		break;

	case M_PCIOCTL:				/* IOCTL to be done *now* */
		tplock = p_lock(&tp->t_ttylock, SPLSTR);
		/*
		 * If we are sending a break out of this
		 * port, wait until it is done before doing
		 * anything else on this port.
		 */
		if (tp->t_state & XS_TXBREAK) {
			v_lock(&tp->t_ttylock, tplock);
			return(XS_PRIPUTBACK);
		}
		if (xs_doioctl(tp, mp)) {
			v_lock(&tp->t_ttylock, tplock);
			return(XS_PRIPUTBACK);
		} else {
			v_lock(&tp->t_ttylock, tplock);
			break;
		}
	case M_FLUSH:				/* Flush data queue(s) */
		/*
		 * If we are sending a break out of this
		 * port, wait until it is done before doing
		 * anything else on this port.
		 */
		tplock = p_lock(&tp->t_ttylock, SPLSTR);
		if (tp->t_state & XS_TXBREAK) {
			v_lock(&tp->t_ttylock, tplock);
			return(XS_STDPUTBACK);
		}
		v_lock(&tp->t_ttylock, tplock);
		if (mp->b_rptr != NULL) {
			if (*mp->b_rptr & FLUSHW) {
				tplock = p_lock(&tp->t_ttylock, SPLSTR);
				flushq(wr_q, FLUSHDATA);
				/* tell 781 to stop output and flush */
				xsregs.cmd_report = XSCR_RESET;
				xsregs.port = line;
				xs_cmd(ip, &xsregs);
				(void) xs_wait(ip);
				ip->xs_outbufs->obufs[line].get = stovs(1);
				ip->xs_outbufs->obufs[line].put = 0;
				tp->t_state &= ~(XS_TX_FULL | XS_DATASUSP);
				if (tp->msg) {
					freemsg(tp->msg);
					tp->msg = NULL;
				}
				v_lock(&tp->t_ttylock, tplock);
			}
			if (*mp->b_rptr & FLUSHR) {
				flushq(RD(wr_q), FLUSHDATA);
				/* tell 781 to discard any buffered data*/
				xsregs.cmd_report = XSCR_FLUSH;
				xsregs.port = line;
				xs_cmd(ip, &xsregs);
				*mp->b_rptr &= ~FLUSHW;
				qreply(wr_q, mp);
				break;
			}
			else
				freemsg(mp);
		} else {
			cmn_err(CE_WARN, "xs%d: M_FLUSH message received with NULL data ptr\n",board);
			/*
			 *+ The driver write put routine received a stream
			 *+ message of type M_FLUSH, but the message did
			 *+ not include a data block indicating the
			 *+ type of flush to perform.
			 */
			freemsg(mp);
		}
		break;

	case M_IOCTL:
		tplock = p_lock(&tp->t_ttylock, SPLSTR);
		/*
		 * If we are sending a break out of this
		 * port, wait until it is done before doing
		 * anything else on this port.
		 */
		if (tp->t_state & XS_TXBREAK) {
			v_lock(&tp->t_ttylock, tplock);
			return(XS_STDPUTBACK);
		}
		if (xs_doioctl(tp, mp)) {
			v_lock(&tp->t_ttylock, tplock);
			return(XS_STDPUTBACK);
		} else {
			v_lock(&tp->t_ttylock, tplock);
			break;
		}

	case M_DELAY:				/* Delay output for n ticks */
	case M_DATA:				/* Std. data or ioctl messages */
		tplock = p_lock(&tp->t_ttylock, SPLSTR);
		if ((tp->t_state & (XS_TXBREAK | XS_TX_FULL | XS_TXDELAY |
				XS_SUSPOUT | XS_WIOCTL)) == 0) {
			if (mp->b_datap->db_type == M_DATA)
				tp->t_state &= ~XS_DATASUSP;
			xsstart(tp, mp);
			v_lock(&tp->t_ttylock, tplock);
		} else {
			if (mp->b_datap->db_type == M_DATA)
				tp->t_state |= XS_DATASUSP;
			v_lock(&tp->t_ttylock, tplock);
			return(XS_STDPUTBACK);
		}
		/*NOTREACHED*/
		break;
	default:
		freemsg(mp);
	}
	return(0);
}


/*
 * Service routine takes messages off write queue and
 * processes them, if the driver is able to.  If xsmsg()
 * returns a non-zero status, put the message back on the
 * queue for later processing.
 */
static
xswsrv(wr_q)
queue_t *wr_q;
{
	mblk_t *bp;
	struct xsstrtty *tp = (struct xsstrtty *) wr_q->q_ptr;
	int stat;

	/*
	 * Make sure there is something pointed to by
	 * wr_q->q_ptr.  If there isn't, return.
	 */
	if (wr_q == NULL) {
		cmn_err(CE_WARN, "xswsrv:bad wr_q passed to routine\n");
		/*
		 *+ The STREAMS system passed a bad write queue
		 *+ pointer to this service routine.
		 */
		return;
	}
	tp = (struct xsstrtty *) wr_q->q_ptr;
	if (tp == NULL) {
		cmn_err(CE_WARN, "xswsrv:bad wr_q passed to routine- bad tp\n");
		/*
		 *+ The STREAMS system passed a bad write queue
		 *+ pointer to this service routine.
		 */
		return;
	}
	while ((bp = getq(wr_q)) != NULL) {
		if ((stat = xsmsg(wr_q, bp)) == XS_STDPUTBACK) {
			putbq(wr_q, bp);
			return;
		} else if (stat == XS_PRIPUTBACK) {
			  putbqx(wr_q, bp);
			  return;
		}
 	}
}

/*
 * xs_doioctl - process M_IOCTL messages.
 *
 * Assumes tp is locked.
 * Process message by calling xs_param() to set up hardware
 * parameters.  Acknowlege message and send it back upstream.
 */
xs_doioctl(tp, mp)
	struct xsstrtty *tp;
	mblk_t *mp;
{
	struct iocblk *iocp;
	struct termio *tptr;
	struct termios *tsptr;
	mblk_t *bp;
	struct xs_hardware xsregs;
	queue_t *wr_q;
	ushort oldcflags;
	int line = XSLINE(tp->t_dev);
	int board = XSBOARD(tp->t_dev);
	register struct xsinfo *ip = xs_info[board];

	wr_q = (queue_t *) tp->qptr;
	if (mp->b_rptr == NULL) {
		cmn_err(CE_WARN, "xs%d: IOCTL message received with NULL data ptr\n",board);
		/*
		 *+ The driver write put routine received a stream
		 *+ message of type M_IOCTL or M_PCIOCTL, but the message did
		 *+ not include a data block containing subcommands and
		 *+ parameters.
		 */
		mp->b_datap->db_type = M_IOCNAK;
		qreply(wr_q, mp);
		return(0);
	}
	XS_CLEAR(xsregs);
	iocp = (struct iocblk *) mp->b_rptr;
	switch (iocp->ioc_cmd) {
	case TCSETAF:			       /* Set params with read flush */
		/*
		 * Set line parameters but wait until all current output
		 * is drained before doing anything.
		 */
		if (xs_drain(ip,line)) {
			tp->t_state |= XS_WIOCTL;
			return(1);
		}

		xsregs.cmd_report = XSCR_FLUSH;
		xsregs.port = line;
		xs_cmd(ip, &xsregs);
		tptr = (struct termio *) mp->b_cont->b_rptr;
		if (tp->t_cflags != tptr->c_cflag) {
			oldcflags = tp->t_cflags;	/* save old flags */
			tp->t_cflags = tptr->c_cflag;
			if ((iocp->ioc_error = xs_param(&ip->idb->uart_parms[line], tp)) != 0)
				tp->t_cflags = oldcflags;	/* put old flags back */
			else {
				if (tp->t_cflags & CLOCAL)
					tp->t_state |= XS_CARR_ON;
				XS_CLEAR(xsregs);
				xsregs.cmd_report = XSCR_UINIT;
				xsregs.port = line;
				xs_cmd(ip, &xsregs);
			}
		}
		if (iocp->ioc_error)
			mp->b_datap->db_type = M_IOCNAK;
		else
			mp->b_datap->db_type = M_IOCACK;
		iocp->ioc_count = 0;
		if (!putnextctl1(RD(wr_q), M_FLUSH, FLUSHR)) {
			cmn_err(CE_WARN, "xs%d: line %d TCSETAF flush msg failed\n", board, line);
			/*
			 *+ The driver attempt to send a read flush message upstream
			 *+ after processing a TCSETAF message failed.
			 */
		}
		qreply(wr_q, mp);
		break;

	case TCSETPF:			       /* Set params with read flush */
		/*
		 * Set line parameters but wait until all current output
		 * is drained before doing anything.
		 */
		if (xs_drain(ip, line)) {
			tp->t_state |= XS_WIOCTL;
			return(1);
		}

		xsregs.cmd_report = XSCR_FLUSH;
		xsregs.port = line;
		xs_cmd(ip, &xsregs);
		tsptr = (struct termios *) mp->b_cont->b_rptr;
		if (tp->t_cflags != tsptr->c_cflag) {
			oldcflags = tp->t_cflags;	/* Save old flags */
			tp->t_cflags = tsptr->c_cflag;
			if ((iocp->ioc_error = xs_param(&ip->idb->uart_parms[line], tp)) != 0)
				tp->t_cflags = oldcflags;	/* put old flags back */
			else {
				if (tp->t_cflags & CLOCAL)
					tp->t_state |= XS_CARR_ON;
				XS_CLEAR(xsregs);
				xsregs.cmd_report = XSCR_UINIT;
				xsregs.port = line;
				xs_cmd(ip, &xsregs);
			}
		}
		if (iocp->ioc_error)
			mp->b_datap->db_type = M_IOCNAK;
		else
			mp->b_datap->db_type = M_IOCACK;
		iocp->ioc_count = 0;
		if (!putnextctl1(RD(wr_q), M_FLUSH, FLUSHR)) {
			cmn_err(CE_WARN, "xs%d: line %d TCSETPF flush msg failed\n", board, line);
			/*
			 *+ The driver attempt to send a read flush message upstream
			 *+ after processing a TCSETPF message failed.
			 */
		}
		qreply(wr_q, mp);
		break;

	case TCSBRK:
		/*
		 * Send break but wait until all current output
		 * is drained before doing anything.
		 */
		if (xs_drain(ip, line)) {
			tp->t_state |= XS_WIOCTL;
			return (1);
		}
		if (*(int *) mp->b_cont->b_rptr == 0) {
			tp->t_state |= XS_TXBREAK;
			xsregs.cmd_report = XSCR_BREAKON;
			xsregs.port = line;
			xs_cmd(ip, &xsregs);
			tp->t_breakid = timeout(xs_breaktime, (caddr_t) tp,
				(xsbreaktime * HZ) / 1000);
		}
		mp->b_datap->db_type = M_IOCACK;
		iocp->ioc_count = 0;
		qreply(wr_q, mp);
		break;

	case TCSETAW:
		/*
		 * Set line parameters but wait until all current output
		 * is drained before doing anything.
		 */
		if (xs_drain(ip, line)) {
			tp->t_state |= XS_WIOCTL;
			return(1);
		}
		/*FALLTHROUGH*/
	case TCSETA:				/* Set parameters */
		tptr = (struct termio *) mp->b_cont->b_rptr;
		if (tp->t_cflags != tptr->c_cflag) {
			oldcflags = tp->t_cflags;	/* Save old flags */
			tp->t_cflags = tptr->c_cflag;
			if ((iocp->ioc_error = xs_param(&ip->idb->uart_parms[line], tp)) != 0)
				tp->t_cflags = oldcflags;	/* put old flags back */
			else {
				if (tp->t_cflags & CLOCAL)
					tp->t_state |= XS_CARR_ON;
				XS_CLEAR(xsregs);
				xsregs.cmd_report = XSCR_UINIT;
				xsregs.port = line;
				xs_cmd(ip, &xsregs);
			}
		}
		if (iocp->ioc_error)
			mp->b_datap->db_type = M_IOCNAK;
		else
			mp->b_datap->db_type = M_IOCACK;
		iocp->ioc_count = 0;
		qreply(wr_q, mp);
		break;

	case TCSETPW:
		/*
		 * Set line parameters but wait until all current output
		 * is drained before doing anything.
		 */
		if (xs_drain(ip, line)) {
			tp->t_state |= XS_WIOCTL;
			return(1);
		}
		/*FALLTHROUGH*/
	case TCSETP:				/* Set parameters */
		tsptr = (struct termios *) mp->b_cont->b_rptr;
		if (tp->t_cflags != tsptr->c_cflag) {
			oldcflags = tp->t_cflags;	/* Save old flags */
			tp->t_cflags = tsptr->c_cflag;
			if ((iocp->ioc_error = xs_param(&ip->idb->uart_parms[line], tp)) != 0)
				tp->t_cflags = oldcflags;	/* put old flags back */
			else {
				if (tp->t_cflags & CLOCAL)
					tp->t_state |= XS_CARR_ON;
				XS_CLEAR(xsregs);
				xsregs.cmd_report = XSCR_UINIT;
				xsregs.port = line;
				xs_cmd(ip, &xsregs);
			}
		}
		if (iocp->ioc_error)
			mp->b_datap->db_type = M_IOCNAK;
		else
			mp->b_datap->db_type = M_IOCACK;
		iocp->ioc_count = 0;
		qreply(wr_q, mp);
		break;

	case TCGETA:					/* Get parameters */
		if (mp->b_cont == NULL) {
			if ((bp = allocb(sizeof(struct termio), BPRI_MED)) == NULL) {
				cmn_err(CE_WARN, "xs%d: line %d TCGETA data block allocb failed\n",
					board, line);
				/*
				 *+ An attempt to allocate a streams data buffer for
				 *+ TCGETA parameters failed.
				 */
				mp->b_datap->db_type = M_IOCNAK;
				qreply(wr_q, mp);
				break;
			}
			/* clear unset fields */
			bzero((caddr_t)  bp->b_rptr, sizeof(struct termio));
			mp->b_cont = bp;			/* Link data buffer */
		} else
			if (mp->b_cont->b_datap->db_lim -
			    mp->b_cont->b_datap->db_base < sizeof(struct termio)) {
				cmn_err(CE_WARN, "xs%d: line %d TCGETA recvd data block bad size\n",
					board, line);
				/*
				 *+ A TCGETA ioctl with a preallocated buffer
				 *+ does not have enough allocated space to
				 *+ store the requested information.
				 */
				mp->b_datap->db_type = M_IOCNAK;
				qreply(wr_q, mp);
				break;
			}
		tptr = (struct termio *) mp->b_cont->b_wptr;    /* Cast data buffer */
		tptr->c_cflag = tp->t_cflags;		/* Load cflags */
		mp->b_cont->b_wptr += sizeof(struct termio);
		iocp->ioc_count = sizeof(struct termio);
		mp->b_datap->db_type = M_IOCACK;
		qreply(wr_q, mp);
		break;

	case TCGETP:					/* Get parameters */
		if (mp->b_cont == NULL) {
			if ((bp = allocb(sizeof(struct termios), BPRI_MED)) == NULL) {
				cmn_err(CE_WARN, "xs%d: line %d TCGETP data block allocb failed\n",
					board, line);
				/*
				 *+ An attempt to allocate a streams data buffer for
				 *+ TCGETP parameters failed.
				 */
				mp->b_datap->db_type = M_IOCNAK;
				qreply(wr_q, mp);
				break;
			}
			/* clear unset fields */
			bzero((caddr_t) bp->b_rptr, sizeof(struct termios));
			mp->b_cont = bp;			/* Link data buffer */
		} else
			if (mp->b_cont->b_datap->db_lim -
			    mp->b_cont->b_datap->db_base < sizeof(struct termios)) {
				cmn_err(CE_WARN, "xs%d: line %d TCGETP recvd data block bad size\n",
					board, line);
				/*
				 *+ A TCGETP ioctl with a preallocated buffer
				 *+ does not have enough allocated space to
				 *+ store the requested information.
				 */
				mp->b_datap->db_type = M_IOCNAK;
				qreply(wr_q, mp);
				break;
			}
		tsptr = (struct termios *) mp->b_cont->b_wptr;    /* Cast data buffer */
		tsptr->c_cflag = tp->t_cflags;		/* Load cflags */
		mp->b_cont->b_wptr += sizeof(struct termios);
		iocp->ioc_count = sizeof(struct termios);
		mp->b_datap->db_type = M_IOCACK;
		qreply(wr_q, mp);
		break;

	default:
		/* nak the ioctl */
		mp->b_datap->db_type = M_IOCNAK;
		qreply(wr_q, mp);
	}
	return(0);
}

/*
 * This table defines the XS command bits for setting asynchronous
 * baud rates.  '-1' is the signal to turn off the DTR* output.
 * '-2' indicates that a baud rate is not available on the 781.
 */

int xs_mrates[] = {
	-1,			/* B0	  (hangup)	*/
	XSB_50,			/* B50			*/
	XSB_75,			/* B75			*/
	XSB_110,		/* B110			*/
	-2,			/* B134.5 (not avail.)	*/
	XSB_150,		/* B150			*/
	-2,			/* B200	  (not avail.)	*/
	XSB_300,		/* B300			*/
	XSB_600,		/* B600			*/
	XSB_1200,		/* B1200		*/
	XSB_1800,		/* B1800		*/
	XSB_2400,		/* B2400		*/
	XSB_4800,		/* B4800		*/
	XSB_9600,		/* B9600		*/
	XSB_19200,		/* B19200		*/
	-2			/* EXTB	  (not used)	*/
};

/*
 * xs_param - interpret cflags and load modem structure
 *	      with the appropriate info.
 *
 * Assumes tp is locked.  If baud rate is illegal, an error status
 * is returned.
 */
int
xs_param(modem, tp)
	struct uparms *modem;
	struct xsstrtty *tp;
{
	ushort par, len, stopb;
	short cflags = tp->t_cflags;
	register int speed = xs_mrates[(cflags & CBAUD)];

	if (speed == -2) {				/* Can't do EXTB */
		cmn_err(CE_NOTE, "xs%d: Xylogics 781 illegal baud rate requested, line %d\n",
			XSBOARD(tp->t_dev), XSLINE(tp->t_dev));
		/*
		 *+ Xylogics 781 mux boards do not support EXTB/38,400,
		 *+ 134.5, or 200 baud.
		 */
		return (EINVAL);
	}
	if (speed == -1) {				/* Hang up */
		modem->line_state &= ~XSLINE_DTR;
		return(0);
	}
	switch (cflags & CSIZE) {
	case CS5:
		len = XS_BITS5;
		break;
	case CS6:
		len = XS_BITS6;
		break;
	case CS7:
		len = XS_BITS7;
		break;
	default:				/* default to 8 bits */
		len = XS_BITS8;
	}
	stopb = (cflags & CSTOPB) ? XS_STOP2 : XS_STOP1;
	if (cflags & PARENB)
		if (cflags & PARODD)
			par = XS_ODD_PARITY;
		else
			par = XS_EVEN_PARITY;
	else
		par = XS_NO_PARITY;

	modem->input_timeout = stovs(xsrxtime);
	modem->xoff_char = 0;
	modem->xon_char  = 0;
	modem->input_min = stovs(xsinputmin);
	modem->break_time = stovs(xsbreaktime/5);
	modem->low_water = stovs(xslowwater);
	modem->serial_modes = par | len | stopb | XS_HW_FLOW;
	modem->baud_rates = speed << 12 | speed << 8;
	return(0);
}

/*
 * Try to put as much data as possible on the specified 781 line's
 * output queue.  Compute the amount of free space in the
 * output queue (allowing wrap).  Then attempt to fill that number of
 * free spaces.  If the output queue goes empty when data is written
 * to the output queue, restart the write operation after the data is
 * written.  ASSUMES TP LOCKED.
 * NOTE: tp->t_state will have its XS_TX_FULL flag set if the output queue
 *	 is completely full.  This flag will not be set if the message
 *	 passed to this function will fit into the output queue with space
 *	 remaining.
 */
xsstart(tp, bp)
	register struct xsstrtty *tp;
	mblk_t *bp;
{
	int board = XSBOARD(tp->t_dev);
	struct xsinfo *ip = xs_info[board];
	struct xs_hardware xsregs;
	int count, nch, xfercnt;
	unchar newdata, empty, moremsgs;
	queue_t *wr_q = tp->qptr;
	int line = XSLINE(tp->t_dev);
	mblk_t *mp;

	switch (bp->b_datap->db_type) {

	case M_DELAY:
		if (bp->b_rptr != NULL) {
			ASSERT_DEBUG(tp->t_delayid == 0, "xsstart delay");
			tp->t_state |= XS_TXDELAY;
			tp->t_delayid = timeout(xs_delaytime,
						(caddr_t) tp,
						*(int *) bp->b_rptr);
			freemsg(bp);
			return;
		} else {
			cmn_err(CE_WARN, "xs%d: M_DELAY message received with NULL data ptr\n",board);
			/*
			 *+ The driver write put routine received a stream
			 *+ message of type M_DELAY, but the message did
			 *+ not include a data block containing the
			 *+ amount of time to delay.
			 */
			freemsg(bp);
			return;
		}
		/*NOTREACHED*/
		break;

	case M_DATA:

#ifdef DEBUG
		DPRINTF3("xsstart: M_DATA handler start\n");
#endif
		newdata = FALSE;			/* init state */
		empty = FALSE;
		/*
		 * Since the output buffer might still be draining when
		 * we get to the point, the best thing to do is to take
		 * a "snapshot" of the buffer at this time and come up
		 * with the number of characters that will fit in the
		 * buffer.  It may go empty while we are getting around
		 * to filling it.  If this happens, we should note it
		 * since we will be checking for empty many times during
		 * the fill process.
		 */
		moremsgs = TRUE;
		while ((count = xs_contig_space(ip, line)) && moremsgs) {
#ifdef DEBUG
			DPRINTF3("xsstart: in 1st loop, count is %d\n",count);
#endif
			while (count) {
				nch = bp->b_wptr - bp->b_rptr;
				while (nch == 0) {	/* If msg block empty, get next one */
					mp = bp;
					bp = mp->b_cont;
					mp->b_cont = NULL;
					freeb(mp);
					if (bp == NULL) {
						moremsgs = FALSE;
						count = 0;
						break;
					}
					nch = bp->b_wptr - bp->b_rptr;
				}
				if (count > 0) {
					xfercnt = min((uint) count, (uint) nch);
					newdata = TRUE;
#ifdef DEBUG
					DPRINTF3("xsstart: addr of put[0]=0x%x\n",&ip->xs_outbufs->obufs[line].buffer[0]);
					DPRINTF3("xsstart: bcopy to 0x%x, %d chars\n",XS_PUT_ADDR(ip,line),xfercnt);
#endif
					bcopy((caddr_t) bp->b_rptr,
						XS_PUT_ADDR(ip, line),
						(unsigned) xfercnt);
					if (ip->dev_ptr->bus->dma_flush)
						(*ip->dev_ptr->bus->dma_flush) (ip->dev_ptr);
					if (xs_bufptr_move(ip, line, xfercnt))
						empty = 1;
					bp->b_rptr += xfercnt;
					count -= xfercnt;
				}
			}
		}
#ifdef DEBUG
		if (empty)
			DPRINTF3("xsstart: out of loops, buf needs restart\n");
		else
			DPRINTF3("xsstart: out of loops, buf doesn't need a restart\n");
#endif

		/* We have loaded the outbuf.  If bp is NULL, we have fit the
		 * entire message into the outbuf with space left.  If bp is
		 * not null, and data is left in the message, the outbuf must
		 * be full.  Set the busy flag and point tp->msg to the message
		 * for later loading.  If bp is not null and no data is left
		 * in the message, the message just filled the outbuf.  Set
		 * tp->msg to null.
		 */
#ifdef DEBUG
		DPRINTF3("xsstart: outbuf is loaded, bp is 0x%x\n",bp);
#endif
		if (bp == NULL) {
			tp->t_state &= ~XS_TX_FULL;
			tp->msg = NULL;
#ifdef DEBUG
			DPRINTF3("xsstart:bp is null, qenable\n");
#endif
			qenable(wr_q);
		} else {
			tp->t_state |= XS_TX_FULL;
			if ((bp->b_wptr - bp->b_rptr) == 0) {
				if (bp->b_cont != NULL) {
					mp = bp->b_cont;
					bp->b_cont = NULL;
					freeb(bp);
					tp->msg = mp;
				} else {
					freeb(bp);
					tp->msg = NULL;
				}
			} else
				tp->msg = bp;
		}
#ifdef DEBUG
		DPRINTF3("xsstart: after load, PUT is now %d\n",vtoss(XS_PUT(ip,line)));
		DPRINTF3("xsstart: after load, tp->msg is 0x%x\n",tp->msg);
#endif

		/*
		 * If the output buffer was previously empty
		 * and we added more data, tell the 781 about it.
	 	 */
		if (newdata && empty) {
#ifdef DEBUG
			DPRINTF3("xsstart: telling 781 about em=to=full outbuf\n");
#endif
			tp->t_state |= XS_TX_BUSY;
			XS_CLEAR(xsregs);
			xsregs.cmd_report = XSCR_WAC;
			if (line >= 0 && line <= 7)
				xsregs.low_wr_active |= 1 << line;
			else
				xsregs.hi_wr_active  |= 1 << (line - 8);
			xs_cmd(ip, &xsregs);
		}
	}
#ifdef DEBUG
	DPRINTF3("xsstart: leaving\n");
#endif
}

/*
 * xs_cmd - send a command to the 781
 *
 * Wait for ARCP bit in 781 status register
 * to be cleared.  Move the input values to
 * the hardware registers and then hit the ARC bit
 * to interrupt the 781.  Do not wait for command
 * completion.
 *
 * REMEMBER- Until the ARCP bit is cleared, this command
 * will not be latched into the 781!
 */
xs_cmd(ip, xsregs)
	struct xsinfo *ip;
	struct xs_hardware *xsregs;
{
	spl_t	s;

	s = p_lock(&ip->xs_lock, SPLSTR);
	if (xs_wait(ip)) {
		cmn_err(CE_WARN, "xs%d: 781 timeout waiting for ARCP to clear\n",ip->boardnum);
		/*
		 *+ A Xylogics 781 board did not signal, within a reasonable
		 *+ amount of time, that it was ready for a new command.
		 *+ You may need to increase the value of xstimeout in
		 *+ xs_space.c.
		 */
		 v_lock(&ip->xs_lock, s);
		 return;
	}

#ifdef DEBUG
	if (dumpcmd)
		xs_dumpcmd(xsregs, ip);
#endif
	ip->xs_addr->low_rd_active = xsregs->low_rd_active;
	ip->xs_addr->hi_rd_active  = xsregs->hi_rd_active;
	ip->xs_addr->low_wr_active = xsregs->low_wr_active;
	ip->xs_addr->hi_wr_active  = xsregs->hi_wr_active;
	ip->xs_addr->cmd_report    = xsregs->cmd_report;
	ip->xs_addr->port          = xsregs->port;
	ip->xs_addr->cntl_status = XSCR_ARC;
	v_lock(&ip->xs_lock, s);
}

/*
 * Wait for ARCP bit in 781 status register
 * to be cleared.   Return 1 if the operation
 * takes longer that expected.  Else return 0.
 */
xs_wait(ip)
	struct xsinfo *ip;
{
	register int temp;
	temp = calc_delay(xstimeout);
	while ((ip->xs_addr->cntl_status & XSSR_ARCP) != 0)
		if (temp-- <= 0)
			return (1);
	return (0);
}

/*
 * Called when timer expires after number of machine ticks as specified in
 * an M_DELAY message.
 */
xs_delaytime(tp)
	struct xsstrtty *tp;
{
	spl_t s;

	s = p_lock(&tp->t_ttylock, SPLSTR);
	tp->t_state &= ~XS_TXDELAY;
	tp->t_delayid = 0;
	qenable(tp->qptr);
	v_lock(&tp->t_ttylock, s);
}

/*
 * Called when timer expires after a break interval is started.
 */
xs_breaktime(tp)
	struct xsstrtty *tp;
{
	spl_t s;
	struct xs_hardware xsregs;
	struct xsinfo *ip = xs_info[XSBOARD(tp->t_dev)];

	XS_CLEAR(xsregs);
	s = p_lock(&tp->t_ttylock, SPLSTR);
	tp->t_state &= ~XS_TXBREAK;
	tp->t_breakid = 0;
	xsregs.cmd_report = XSCR_BREAKOFF;
	xsregs.port = XSLINE(tp->t_dev);
	xs_cmd(ip, &xsregs);
	qenable(tp->qptr);
	v_lock(&tp->t_ttylock, s);
}

/*
 * Called when timer expires waiting on output to drain during close.
 * Set appropriate flags and wakeup sleeping process which is waiting to
 * close.
 */
xs_timeout(rd_q)
	queue_t *rd_q;
{
	spl_t s;
	struct xsstrtty *tp = (struct xsstrtty *) rd_q->q_ptr;

	s = p_lock(&tp->t_ttylock, SPLSTR);
	if (tp->t_state & XS_WCLOSE) {
		if (qsize(WR(rd_q)))
			tp->t_state |= XS_DRAINTIME;
		tp->t_state &= ~XS_WCLOSE;
		v_sema(&tp->t_rawqwait);
	}
	v_lock(&tp->t_ttylock, s);
}

#ifdef DEBUG
/*
 * xs_dumpcmd - dump xs register values and uart parms (diagnostic)
 */
xs_dumpcmd(xsregs, ip)
	struct xs_hardware *xsregs;
	struct xsinfo *ip;
{
	struct uparms *up;

	CPRINTF("xs_dumpcmd: command-----------------------------\n");
	CPRINTF("xs_dumpcmd: contents of command registers:\n");
	CPRINTF("xs_dumpcmd: low_rd = 0x%x  hi_rd = 0x%x\n",
		xsregs->low_rd_active, xsregs->hi_rd_active);
	CPRINTF("xs_dumpcmd: low_wr = 0x%x  hi_wr = 0x%x\n",
		xsregs->low_wr_active, xsregs->hi_wr_active);
	CPRINTF("xs_dumpcmd: cmd_report = 0x%x  port = 0x%x\n",
		xsregs->cmd_report, xsregs->port);
	if (((int) xsregs->port >= 0) && ((int) xsregs->port <= 15)) {
		up = &ip->idb->uart_parms[xsregs->port];
		CPRINTF("xs_dumpcmd: smodes = 0x%x   bauds = 0x%x\n",
			up->serial_modes, up->baud_rates);
		CPRINTF("xs_dumpcmd: lstate = 0x%x  lmask = 0x%x\n",
			up->line_state,up->line_mask);
		CPRINTF("xs_dumpcmd: intout = 0x%x  inmin = 0x%x\n",
			up->input_timeout, up->input_min);
		CPRINTF("xs_dumpcmd: xoff = 0x%x  xon = 0x%x\n",
			up->xoff_char, up->xon_char);
		CPRINTF("xs_dumpcmd: lowat = 0x%x  bktime = 0x%x\n",
			up->low_water, up->break_time);
	}
	CPRINTF("xs_dumpcmd:end----------------------------------\n");
}
#endif

/*
 * xs_contig_space- return the number of contiguous (non-wrapped) bytes
 *		    available in an output buffer.
 *
 */
int
xs_contig_space(ip, line)
	struct xsinfo *ip;
	int line;
{
	int put, get;

	if (ip->dev_ptr->bus->dma_flush)
		(*ip->dev_ptr->bus->dma_flush) (ip->dev_ptr);
	if (XS_OUTBUF_FULL(ip, line))
		return(0);

	put = vtoss(XS_PUT(ip,line));
	put = (put + 1) & OBUFSIZEDEC;
	get = vtoss(XS_GET(ip,line));

	if (put >= get) {
		if (get == 0)
			return(OBUFSIZEDEC - put);
		else
			return(OBUFSIZE - put);
	} else
		return(get - 1 - put);
}

/*
 * xs_bufptr_move- move an outbufs PUT pointer
 *
 */
int
xs_bufptr_move(ip, line, count)
	struct xsinfo *ip;
	int line, count;
{
	short oldput = vtoss(XS_PUT(ip, line));
	int empty = 0;
	short temp_put;

	if (XS_OUTBUF_EMPTY(ip, line))
		empty = 1;
	temp_put = (vtoss(XS_PUT(ip,line)) + count) & OBUFSIZEDEC;
	XS_PUT(ip,line) = stovs(temp_put);
	if (ip->dev_ptr->bus->dma_flush)
		(*ip->dev_ptr->bus->dma_flush) (ip->dev_ptr);
	if (vtoss(XS_GET(ip,line)) == ((oldput + 1) & OBUFSIZEDEC))
		empty = 1;
	return(empty);
}

/*
 * xs_drain - set the output buffer to drain: return 1 if the buffer
 * is draining and 0 if it is empty.
 */
int
xs_drain (ip, line)
	struct xsinfo	*ip;
	int		line;
{
	struct xsstrtty *tp;
	struct xs_hardware xsregs;

	tp = &ip->xs_strtty[line];

	/*
	 * if data is suspended on the write queue, or a break is in
	 * progress, return 1
	 */
	if (tp->t_state & (XS_TXBREAK | XS_DATASUSP))
		return (1);

	/*
	 * If there are no remaining messages, request a synchronous
	 * write request so the 781 will only generate an interrupt
	 * when the output buffer is empty (instead of when the
	 * output buffer low water mark is reached)
	 */
	if (tp->msg == NULL) {
		/*
		 * if the output buffer is not busy, return 0
		 */
		if ((tp->t_state & XS_TX_BUSY) == 0) {
			/*
			 * the 781 has a bug where it may have an internal
			 * pointer that allows old data in its onboard buffer to
			 * be transmitted again - RESET clears the problem
			 */
			XS_CLEAR(xsregs);
			xsregs.cmd_report = XSCR_RESET;
			xsregs.port = line;
			xs_cmd(ip, &xsregs);
			return (0);
		}
		/*
		 * schedule a SYNC request
		 */
		if ((tp->t_flags & XS_SYNCREQ) == 0) {
			tp->t_flags |= XS_SYNCREQ;
			XS_CLEAR(xsregs);
 			xsregs.cmd_report = XSCR_SYNC;
			if (line <= 7)
				xsregs.low_wr_active = 1 << line;
			else
				xsregs.hi_wr_active = 1 << (line - 8);
			xs_cmd(ip,&xsregs);
		}
	}
	return (1);
}
