/* $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: st.c 1.95 90/10/22 $";

/*
 * st.c
 * 	Systech MTI 800/1600/1650 Driver  (STREAMS version)
 */

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

#include "sys/sysmacros.h"
#include "sys/types.h"
#include "sys/mutex.h"
#include "sys/devsw.h"
#include "sys/param.h"
#include "sys/signal.h"
#include "sys/conf.h"
#include "sys/ioctl.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/errno.h"
#include "sys/gate.h"
#include "sys/pte.h"
#include "sys/vmparam.h"
#include "sys/plocal.h"
#include "sys/intctl.h"
#include "sys/hwparam.h"

#include "sys/clock.h"
#include "sys/slic.h"
#include "sys/systm.h"
#include "sys/stropts.h"
#include "sys/strid.h"
#include "sys/cmn_err.h"
#include "sys/termio.h"
#include "sys/mbad.h"
#include "sys/st.h"
#include "sys/mbad_device.h"
#include "sys/st_device.h"
#include "sys/debug.h"

#include "sys/mbad_ioconf.h"
#include "sys/autoconf.h"


#ifdef DEBUG
int	st_debug = 0;	/* 0=none,1=little,2=more,3=lots,4=all */
#define STDEBUG(x)	if (st_debug) CPRINTF("x")
#else
#define STDEBUG(x)
#endif /* DEBUG */

void st_init(), st_intr();
int ststart(), st_probe(), stboot();
int     st_timeout(), st_breaktime(), st_delaytime();

/* Length of responses, commands, and config commands */
static unchar resplen[] =   { 3,2,2,0,0,0,0,2,6,6,0,0,5,0,0,0 };
static unchar cmdlen[] =    { 1,1,1,1,2,2,1,5,6,7,1,1,6,1,1,0 };
static unchar configlen[] = { 5,8,0,9,3,5,3,3,5,2,0,0,0,0,0,0 };

/* Mapping of timer constants to ms. */
static short timer_ms[] = { 530,353,241,197,177,88,44,22,15,13,11,7,5,4,3,1 };

/* Table for configuring input and output buffer sizes */
static ushort bufsz_tbl[] = {
	IS, OS, IS, OS, IS, OS, IS, OS, IS, OS, IS, OS, IS, OS, IS, OS,
	IS, OS, IS, OS, IS, OS, IS, OS, IS, OS, IS, OS, IS, OS, IS, OS,
};

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

/*
 * MBAd mapping registers needed for Systech output buffer size 
 * depends of the size of the input and output buffers.  Since
 * each MBAd mapping register maps 1k of address space, and the
 * input and output buffer sizes should always be less than 1k, we should only
 * need 32 mapping registers (2 for each port: 1 for input, 1 for output.)
 * The streams buffers (used for output dma) are already aligned on boundaries
 * of their size (i.e. 4096 bytes buffers are aligned on 4096 byte boundaries,
 * etc.) and the input buffers are aligned on 1024 byte boundaries.
 * Maps 0 to 15 are reserved for input ports 0 to 15.  Maps 16 to 31 are
 * reserved for output ports 0 to 15.
 */

#define STMAPSREQ 32 

static struct module_info stm_info = {
/*  module ID    module name   min pkt size    max pkt siz   hiwat   lowat  */
     STRID_ST,      "st",          0,            INFPSZ,      150,    50
};

static int stopen(), stclose(), stwsrv();

static struct qinit strinit = {
	NULL,  NULL,  stopen,  stclose,  NULL,  &stm_info,  NULL
};

static struct qinit stwinit = {
	putq,  stwsrv,  NULL,  NULL,  NULL, &stm_info,  NULL
};

struct streamtab stinfo = { &strinit,  &stwinit,  NULL,  NULL  };

struct	stinfo	**st_info;	/* per board data */
int	stbase;			/* base interrupt vector */
int	nst;			/* number of configured boards */
struct	stinfo *st_timer;	/* systech providing timer */
bool_t	st_timer_on;		/* true when timer is on */
int	st_timer_ms;		/* measured frequency of timer in ms */

extern  int	staddwait;	/* additional time to wait for output drain */
extern	int	sttimerrate;	/* frequency at which sttimer() runs */
extern  int	stcflags;	/* initial value for tp->t_cflags */
extern	struct	stlh stlh[];	/* block input low/high water */
extern	int	sttimerave;	/* how fast rate changes */
extern	int	stvdwait;	/* number of loops to wait for valid data */
extern	unchar	stprintoverflow;/* non-zero prints message on input overflow */
extern	unchar	stprintalocfail;/* non-zero prints message on allocb fail */
extern	unchar	stprintupstreamblkd;/* non-zero prints mssg on upstream blkd */
extern	int	stfifotimeout;	/* timeout count on fifo */
extern  int	putnextctl2();	/* Routine defined in ld0.c */
extern  uint	bufcall();
extern	putbqx();

extern struct devops stdevops;
extern int st_c_major;

extern int st_flags;
extern unchar st_bin;

/*
 * Probe for the existance of a board.
 * NB: The read following the write is necessary 
 * to sync up with the hardware write buffer.
 */
int
st_probe(dev)
	struct gen_device *dev;
{
	volatile struct stdevice *staddr;
	staddr = (struct stdevice *) dev->devreg[0];
	staddr->stie = 0;				/* Mask Interrupts */
	return((staddr->ststat & ST_ERR) == 0);		/* Errors? */
}

void st_init( driver )
	struct driver_module *driver;
{
	(void) mbad_boot_mbad_device( driver, stboot, st_flags, st_bin);
}

/*
 * Allocate driver data structures
 */
stboot(n, md)
	int n;				/* configured boards */
	struct mbad_dev *md;		/* multibus descriptor */
{
	volatile struct stdevice *staddr;
	register struct stinfo *ip;
	register int line;
	register struct strtty *tp;
	register int board;
	unchar	mask;
	int	nbi = 0;
	struct dev *dp;

	stbase = md->md_vector;
	if (n) {
		nst = n;
		st_info = (struct stinfo **)calloc(sizeof(struct stinfo *)*nst);
	}

	/*
	 * For each configured board ...
	 *   allocate info data structure (for live boards)
	 *   For each line ...
	 *     set it up
	 */
	for (board = 0; board < nst; board++,md++) {
		st_info[board] = NULL;
		if (md->md_alive == 0)
			continue;
		ip = st_info[board] =
		    (struct stinfo *) calloc(sizeof(struct stinfo));
		staddr = (struct stdevice *) md->md_csr;
		ip->st_addr = staddr;
		init_lock(&ip->st_lock);
		ip->st_cflags = md->md_flags;
		ip->st_size = 0;
		ip->st_vector = md->md_vector;
		ip->st_mblevel = md->md_level;
		ip->st_mbdesc = md->md_desc;
		ip->st_response_index = 0;
		ip->st_response_length = 0;
		ip->st_basemap = md->md_basemap;
		if (md->md_nmaps < STMAPSREQ) {
			cmn_err(CE_WARN, "st%d: %d maps alloc'd, need %d, deconfigured\n",
			board, md->md_nmaps, STMAPSREQ);
			/*
			 *+ The driver requires a given number of MULTIBUS mapping
			 *+ registers in order to communicate with the Systech
			 *+ board.  Too few mapping registers were allocated.
			 *+ Corrective action: edit the system.std file to request
			 *+ the proper number of maps, then rebuild the kernel.
			 */
			continue;
		}
		/*
		 * Detect revision of firmware
		 */
		if (st_wait(staddr, ST_READY)) {
			cmn_err(CE_WARN,"st%d not responding (not ready)\n", board);
			/*
			 *+ The Systech board is not responding to the driver.
			 *+ The user will not be allowed to access the device.
			 */
			continue;
		}
		staddr->stcmd = ST_RBDATA;		/* illegal on old FW */
		staddr->stgo = 1;
		if (st_wait(staddr, ST_READY)) {
			cmn_err(CE_WARN, "st%d not responding (fw test)\n", board);
			/*
			 *+ The Systech board is not responding to the driver.
			 *+ The user will not be allowed to access the device.
			 */
			continue;
		}
		if (staddr->ststat & ST_ERR) {
			staddr->stcmd = ST_RERR;
			staddr->stgo = 1;
			if (st_wait(staddr, ST_READY) || st_flush(staddr)) {
				cmn_err(CE_WARN, "st%d not responding (bad rerr).\n", board);
				/*
				 *+ The Systech board is not responding to the driver.
				 *+ The user will not be allowed to access the
				 *+ device.  
				 */
				continue;
			}
		} else {
			unsigned int mb_addr; 
			ip->st_cflags |= STF_BI;
			/*
			 * Borrow the last mbad map register to
			 * configure input/output buffers.
			 */
			mb_addr = mbad_physmap(md->md_desc, MB_MAPS-2,
				(caddr_t) bufsz_tbl, sizeof(bufsz_tbl), 2);
			staddr->stcmd = ST_CONFIG;
			staddr->stcmd = STC_BUFSIZ;
			staddr->stcmd = mb_addr;
			staddr->stcmd = mb_addr >> 8;
			staddr->stcmd = mb_addr >> 16;
			staddr->stgo = 1;
			if (st_wait(staddr, ST_READY)) {
				cmn_err(CE_WARN, "st%d not responding (config)\n", board);
				/*
				 *+ The Systech board is not responding to the driver.
				 *+ The user will not be allowed to access the
				 *+ device. 
				 */
				continue;
			}
			if (staddr->ststat & ST_ERR) {
				cmn_err(CE_WARN, "st%d bad bufsz_tbl config.\n", board);
				/*
				 *+ The Systech board responded to the "configure
				 *+ buffer sizes" command with an error.
				 */
				staddr->stcmd = ST_RERR;
				staddr->stgo = 1;
				if (st_wait(staddr, ST_READY) 
				  || st_flush(staddr)) {
					cmn_err(CE_WARN, "st%d not responding (bad config)\n", board);
					/*
					  *+ The Systech board is not responding to the driver.
					  *+ The user will not be allowed to access the
					  *+ device.
				 	 */
					continue;
				}
			}
		}

		/*
		 * Init each line on a board.
		 * Any errors here (other than MTI-800 detect) cause us to
		 * disable the entire board.
		 */
		for (tp = ip->st_strtty,line = 0; line < 16; tp++,line++) {
			/*
			 * Turn off each usart modem control outputs
			 * (RTS, DTR) and disable usart xmit and receive.
			 */
			staddr->stcmd = (ST_WCMD | line);
			staddr->stcmd = 0;
			staddr->stgo = 1;
			/* no resp expected */
			if (st_wait(staddr, ST_READY))
				break;
			/*
			 * A command error will occur on non-existent lines.
			 * Use this fact to detect 8 line MTI-800 boards.
			 */
			if (staddr->ststat & ST_ERR) {
				staddr->stcmd = ST_RERR;
				staddr->stgo = 1;
				(void) st_wait(staddr, ST_READY);
				(void) st_flush(staddr);
				break;
			}
			init_lock(&tp->t_ttylock);
			init_sema(&tp->t_rawqwait, 0);
			tp->t_cmask = DCD;		/* Carrier to watch */
			tp->t_bufcallid = 0;
			/*
			 * Configure block output
			 */
			staddr->stcmd = (ST_CONFIG | line);
			staddr->stcmd = STC_OUTPUT;
			staddr->stcmd = 1;
			staddr->stgo = 1;
			/* no resp expected */
			if (st_wait(staddr, ST_READY))
				break;
			/*
			 * Configure single char input (unless block input).
			 */
			if ((ip->st_cflags & STF_BI) == 0) {
				staddr->stcmd = (ST_ESCI | line);
				staddr->stgo = 1;
				/* no resp expected */
				if (st_wait(staddr, ST_READY))
					break;
			}
			/*
			 * Configure modem status changes to be reported.
			 */
			staddr->stcmd = (ST_CONFIG | line);
			staddr->stcmd = STC_MODEM;
			staddr->stcmd = 1;
			staddr->stgo = 1;
			/* no resp expected */
			if (st_wait(staddr, ST_READY))
				break;
			
			/* 
			 * Allocate an input buffer for this line and compute 
			 * its mbad addr.  Make sure the buffer is aligned
			 * on a 1024-byte boundary.
			 */	
			callocrnd(MB_MRSIZE);
			ip->st_inbufs[line].st_buf = calloc(IS);
			ip->st_inbufs[line].st_blockin = 0;
			ip->st_inbufs[line].st_mbaddr = 
				mbad_physmap(ip->st_mbdesc, (int)(ip->st_basemap + line),
					     ip->st_inbufs[line].st_buf, IS, 1);
		}
		if (line != 8 && line != 16) {
			cmn_err(CE_WARN, "st%d: unusable, line %d bad\n", board, line);
			/*
			 *+ The Systech board has one or more unusable lines, 
			 *+ making the board inaccessible to the user.
			 */
			continue;
		}
		ip->st_size = line;
		for (line = 0; line < ip->st_size; line++) {
			dp = (struct dev *)calloc(sizeof(struct dev));
			dp->d_dev = makedev(st_c_major, (board << 4) | (line & 0x0f));
			dp->d_ops = &stdevops;
			if (devsw_add(dp) != 0) {
				cmn_err(CE_WARN,
				"st%d: failure to add dev line %d to devsw\n",
				board,line);
				/*
				 *+ The kernel encountered a problem when it
				 *+ attempted to add a tty
				 *+ 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 device already
				 *+ belongs to another device.
			 	*/
			}
		}
		if (ip->st_cflags & STF_BI)
			++nbi;
		ip->st_addr->stie = STI_RA;	/* interrupt enable mask */
		mask = 1 << ip->st_mblevel;
		sendsoft(ip->st_mbdesc->mb_slicaddr, mask);
	}
	if (nst && nbi)
		st_picktimer();
}

/*
 * Pick a board to use to time out block
 * input commands.  The actual timer accuracy
 * varies greatly between boards so we look for
 * the one nearest the desired timeout.
 */
st_picktimer()
{
	register struct stinfo *ip;
	volatile struct stdevice *staddr;
	register struct cpuslic *sl = va_slic;
	int near = 0x7fffffff;
	unchar reload_value;
	int i, n, time_in_ms;

	/* 
	 * setup SLIC timer for 10 ms.
	 */
	sl->sl_tctl = 0;
	reload_value = (sys_clock_rate * 1000000) / (SL_TIMERDIV * 100) - 1;
	sl->sl_trv = reload_value;

	for (i = 0; i < nst; i++) {
		if ((ip = st_info[i]) == NULL || ip->st_size == 0)
			continue;
		staddr = st_info[i]->st_addr;

		/* load board timer */
		if (st_wait(staddr, ST_READY))
			continue;
		staddr->stcmd = ST_CONFIG | sttimerrate;
		staddr->stcmd = STC_TIMER;
		staddr->stgo = 1;
		if (st_wait(staddr, ST_READY))
			continue;
		n = staddr->stclrtim;		/* clear timer */
		while ((staddr->ststat & ST_TIMER) == 0)
			/* void */;
		n = staddr->stclrtim;		/* clear timer */

		/* load slic timer */
		sl->sl_tcont = reload_value;

		/*
		 * Loop until timer on board ticks again
		 * and check slic timer to see how much
		 * time has gone by.
		 */
		time_in_ms = 0;
		while ((staddr->ststat & ST_TIMER) == 0) {
			if (sl->sl_tcont == 0) {
				time_in_ms += 10;	/* wrap every 10ms */
				while (sl->sl_tcont == 0)
					/* spin */;
			}
		}
		time_in_ms += (reload_value - sl->sl_tcont) / 
			((sys_clock_rate * 1000000) / (SL_TIMERDIV * 1000));
		n = time_in_ms - timer_ms[sttimerrate];
		if (n < 0)
			n = -n;
		if (n < near) {
			st_timer = st_info[i];
			st_timer_ms = time_in_ms;
			near = n;
		}
	}
	/*
	 * If the timer picked is more than 
	 * 10 percent out of spec, scale the values
	 */
	if ((10*near) > timer_ms[sttimerrate]) {
		for (i=0; i < 16; i++) {
			stlh[i].st_high = (stlh[i].st_high * st_timer_ms) / 
						timer_ms[sttimerrate];
			stlh[i].st_low = (stlh[i].st_low * st_timer_ms) / 
						timer_ms[sttimerrate];
		}
	}
}

/*
 * Flush a response from the response fifo.
 * Return 0 for success, 1 if timeout occurs.
 * Called only if a response is expected.
 * Called from stboot() only.
 */
static int
st_flush(staddr)
	register struct stdevice *staddr;
{
	register int n, temp;

	if (st_wait(staddr, ST_RA))
		return (1);
	temp = staddr->stcra;
	temp = staddr->stresp;
	n = resplen[temp >> 4];
	while (--n > 0) {
		if (st_wait(staddr, ST_VD))
			return (1);
		/* discard the response itself */
		temp = staddr->stresp;
	}
	return (0);
}

/*
 * Open a Systech line.
 */
static
stopen(rd_q, dev, flag, sflag)
	queue_t *rd_q;
	dev_t dev;
	int flag, sflag;
{
	register struct strtty *tp;
	register struct stinfo *ip;
	register int line = STLINE(dev);
	register int board = STBOARD(dev);
	spl_t s;
	int addr;
	unchar firstopen;

	STDEBUG(OPEN);
	if ( st_info == NULL || board >= nst || (ip = st_info[board]) == NULL
	  || line >= ip->st_size) {
		u.u_error = ENXIO;
		return (OPENFAIL);
	}
	
	/* Check for non-driver open */
	if (sflag != DRVROPEN)
		return (OPENFAIL);

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

	REQ_CTTY();	/* inform the stream head that we could be a */
			/* controlling terminal 		     */

	firstopen = 0;				/* initialize */
	/*
	 * If this is first open, initialize state to default.
	 */
	if ((tp->t_state & ISOPEN) == 0) {
		firstopen = 1;			/* We are the 1st to open tty*/

		tp->msg = NULL;			/* set up streams pointers */ 
		tp->t_dev = dev;
		tp->qptr = WR(rd_q);
		rd_q->q_ptr = (char *)tp;
		WR(rd_q)->q_ptr = (char *)tp;
		
		tp->t_state |= ISOPEN;
		tp->t_cflags = stcflags;	/* Initial value for cflags */
		tp->t_bufcallid = 0;
		(void) st_param(ip, line);	/* setup default baud rate */
		
		if (ip->st_cflags & STF_BI) {
			if (!ip->st_inbufs[line].st_blockin) {
				/*
			 	* Block input will be used on input so
			 	* start up an input operation.
			 	*/
				ip->st_inbufs[line].st_blockin = 1;
				st_cmd(ip, ST_CONFIG|line, STC_INPUT|0x08, 1);
				addr = ip->st_inbufs[line].st_mbaddr;
				st_cmd(ip, ST_BLKIN|line, addr, addr>>8, addr>>16, 1, 0);
				tp->blkin_pending = 1;
			}
			/*
			 * Start up timer to handle block input timouts
			 * No mutex because losing the race is harmless
			 */
			if (!st_timer_on) {
				st_timer_on = 1;
				st_timer->st_addr->stie = (STI_RA|STI_TIMER);
			}
		}
		/* Turn on port lines, tx and (if wanted) rx  */	
		if (tp->t_cflags & CREAD) {
			st_cmd(ip, ST_WCMD|line, RTS|DTR|RXEN|TXEN);
			tp->t_wcmd = RTS|DTR|RXEN|TXEN;
		} else { 
			st_cmd(ip, ST_WCMD|line, RTS|DTR|TXEN);
			tp->t_wcmd = RTS|DTR|TXEN;
		}
	}
	
	if (tp->t_cflags & CLOCAL)
		tp->t_state |= CARR_ON;
	else
		tp->t_state &= ~CARR_ON;

	/* Wait for carrier, if required */ 
	if (!(flag & FNDELAY)) {	
		st_cmd(ip, ST_RSTAT|line);
		while ((tp->t_state & CARR_ON) == 0) {
			tp->t_state |= 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.
			 */
			if (p_sema_v_lock(&tp->t_rawqwait, PZERO+1 | PCATCH,
					  &tp->t_ttylock) == PCATCH) {
				if (firstopen) {
					tp->t_state = 0;
					tp->qptr = NULL;
					rd_q->q_ptr = NULL;
					WR(rd_q)->q_ptr = NULL;
				}
				return(OPENFAIL);
			}
			s = p_lock(&tp->t_ttylock, SPLSTR);
		}
		tp->t_state &= ~WOPEN;
	} 
	tp->t_dev =dev;
	v_lock(&tp->t_ttylock, s);
	return(OPENOK);
}

/*
 * Close a Systech line.
 */
static
stclose(rd_q, flag)
	queue_t *rd_q;
	int flag;
{
	register struct strtty *tp = (struct strtty *) rd_q->q_ptr; 	
	register struct stinfo *ip = st_info[STBOARD(tp->t_dev)];
	register int line = STLINE(tp->t_dev);
	short wait, numq;
	spl_t s;
	uint	bid;
	int addr;

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

	/* 
	 * If the carrier is still active and NDELAY is not set, 
	 * try to process all queued output.
	 */
	if ((tp->t_state & CARR_ON) && ((flag & FNDELAY) == 0)) {
		tp->t_wcmd &= ~RXEN;
		st_cmd(ip, ST_WCMD | line, tp->t_wcmd);   /* disable rx */
		/*
		 * If data is being transmitted or data is still enqueued on the write 
		 * queue, attempt to wait for this data to drain.
		 */
		if ((numq = qsize(WR(rd_q))) || (tp->t_state & BUSY)) {
			tp->t_state |= WCLOSE;
			tp->t_state &= ~DRAINTIME;
			wait = ((((numq * ST_AVGCNT) + STMAXOUT) * 
				stbaud_time[(tp->t_cflags & CBAUD)])/1000) +
				staddwait;
			tp->t_drainid = timeout(st_timeout, (caddr_t) rd_q, wait * HZ);
			switch (p_sema_v_lock(&tp->t_rawqwait, PZERO+1 | PCATCH, &tp->t_ttylock)) {
			case PCATCH:
				untimeout(tp->t_drainid);
				s = p_lock(&tp->t_ttylock, SPLSTR);	/* SIGNALLED */
				tp->t_state &= ~WCLOSE;  
				st_cmd(ip, ST_ABORTOUT | line);
				break;
			default:
				untimeout(tp->t_drainid);
				s = p_lock(&tp->t_ttylock, SPLSTR);
				if (tp->t_state & DRAINTIME)
					st_cmd(ip, ST_ABORTOUT | line);  /* DRAIN TIMEOUT */
			}
		} 
	} else {
		/* 
		 * If the carrier is off, or NDELAY is set, 
		 * kill the current output.
		 */
		st_cmd(ip, ST_ABORTOUT | line);
	}

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

	if (tp->t_cflags & HUPCL) {
		st_cmd(ip, ST_WCMD|line, RERR);
		tp->t_wcmd = 0;
	}
	tp->t_state &= ~ISOPEN;
	bid = tp->t_bufcallid;
	tp->t_bufcallid = 1;		/* prevent further buff calls */
	v_lock(&tp->t_ttylock, s);
	if (bid) {
		(void) unbufcall(bid);
	}

	/*
	 * wait for all put and server activity to finish.
	 */
	disable_procs(rd_q);

	untimeout(tp->t_breakid);
	untimeout(tp->t_delayid);

	(void)p_lock(&tp->t_ttylock, SPLSTR);

	/*
	 * If we bufcalled and didn't get to start another block input, 
	 * start one now.
	 */
	if (!(tp->blkin_pending)) {
		addr = ip->st_inbufs[line].st_mbaddr;
		st_cmd(ip, ST_BLKIN|line, addr, addr>>8, addr>>16, 1, 0);
		tp->blkin_pending = 0;
	}

	tp->t_state  = 0;		/* clear streams flags and ptrs */
	freemsg(tp->msg);
	tp->msg = NULL;
	rd_q->q_ptr = NULL;		/* disconnect stream pointers */
	WR(rd_q)->q_ptr = NULL;
	tp->qptr = NULL;
	v_lock(&tp->t_ttylock, s);
}

/*
 * Systech interrupt service routine.
 */
void st_intr(vector)
	int vector;
{
	volatile struct stdevice *staddr;
	register unchar *p;
	register int n, h;
	register int temp;
	register int board = vector - stbase;
	int nresponses;
	struct stinfo *ip;

	if ((unsigned)board >= nst || (ip = st_info[board]) == NULL) {
		cmn_err(CE_NOTE, "st%d: unknown interrupt, vector %d\n", board, vector);
		/*
		 *+ An interrupt was received from unknown source.  The interrupt
		 *+ is ignored.  
		 */
		return;
	}
	staddr = ip->st_addr;
	temp = staddr->stcra;
	p = ip->st_response;
	n = ip->st_response_index;
	h = ip->st_response_length;
	for (nresponses = 0; nresponses < 100; ) {
		if (ip == st_timer && (staddr->ststat & ST_TIMER)) {
			temp = staddr->stclrtim;	/* clear timer */
			sttimer();
		}
		temp = 0;
		while ((staddr->ststat & ST_VD) == 0) {	/* valid data? */
			if (++temp > stvdwait)
				goto out;
		}
		p[n] = staddr->stresp;			/* read response byte */
		if (n == 0) {
			if ((h = resplen[p[0]>>4]) == 0) {
				cmn_err(CE_NOTE, "st%d: response 0x%x", board, p[0]);
				/*
				 *+ The Systech board responded to a driver command
				 *+ with an unexpected response length.  The response
				 *+ is discarded.
				 */
				continue;
			}
		}
		if (++n >= h) {
			n = 0;
			++nresponses;
			if (stresponse(ip, board, 0, 0))
				break;
		}
	}
	/*
	 * Done when no responses in response queue,
	 * processed 100 responses, or board is taking
	 * too long to put bytes into the response fifo.
	 * Save where we are so if we have a partial
	 * response, we can resume later.
	 */
out:
	ip->st_response_index = n;
	ip->st_response_length = h;
	mbad_reenable(ip->st_mbdesc, ip->st_mblevel);
}


/*
 * bufcall interface to stresponse
 */
stresp(tp)
	register struct strtty *tp;
{
	int board = STBOARD(tp->t_dev);
	tp->t_bufcallid = 0;
	(void) stresponse(st_info[board], board, STLINE(tp->t_dev), 1);
}


/*
 * Process a response from a Systech board.
 */
static int
stresponse(ip, board, bcline, caller)
	register struct stinfo *ip;
	int board, bcline, caller;
{
	register unchar *buf;
	int line;
	register struct strtty *tp;
	register caddr_t cp;
	register int n;
	mblk_t   *bp;
	int cnt, addr;
	spl_t s;
	static int stupstreamblkdcnt = 0;

	if (caller == 0) {
		buf = ip->st_response;
		line = buf[0] & 0x0f;
		tp = &ip->st_strtty[line];
	} else {
		line = bcline;
		tp = &ip->st_strtty[line];
		buf = tp->bfc_response;
	}
	s = p_lock(&tp->t_ttylock, SPLSTR);

	switch (buf[0] & 0xf0) {

	case ST_ESCI:			/* Single Char Input */
		if (tp->t_state & ISOPEN) {
			l.cnt.v_rcvint++;	/* Bump statistics counter */
			if (stprintoverflow && (buf[1] & OE))
				cmn_err(CE_WARN, "st%d: silo overflow on line %d (esci)\n", board, line);
				/*
				 *+ A Systech input silo overflowed; 
				 *+ the Systech lost data.  
				 */
			l.cnt.v_rawch++;	/* Bump statistics counter */	

			if (buf[1] & FE) { 	/* framing error */
				if (!putnextctl1(RD(tp->qptr), M_CTL, MC_FRAME_ERR)) {
					cmn_err(CE_WARN, "st%d: line %d framing error report failed\n",
						board, line);
					/*
					 *+ An attempt to send a M_CTL message upstream
					 *+ to report a framing error on a Systech port
					 *+ failed.  
					 */
				}
				v_lock(&tp->t_ttylock, s);
				return(0);

			} else if (buf[1] & PE) {	/* parity error */

				if (!putnextctl2(RD(tp->qptr), M_CTL, MC_PARITY_ERR, (int)buf[2])) {
					cmn_err(CE_WARN, "st%d: line %d parity error report failed (esci)\n",
						board, line);
					/*
					 *+ An attempt to send a M_CTL message 
					 *+ upstream to report a parity error
					 *+ on a Systech port failed.
					 */
				}
				v_lock(&tp->t_ttylock, s);
				return(0);

			} else {	/* normal data */

				if (!canputnext(RD(tp->qptr))) {
					if (stprintupstreamblkd)
						if (0 == stupstreamblkdcnt++ % 100)
							cmn_err(CE_WARN, "st%d: line %d unable to deliver incoming data byte\n",
								board, line);
							/*
							 *+ A driver attempt to send incoming
							 *+ data upstream failed due to
							 *+ upstream flow control.
							 *+ The kernel should be tuned by
							 *+ increasing the value of TTYBUF_LATENCY
							 *+ in the ld0 config file.
							 */
				} else if ((bp = allocb(1, BPRI_MED)) == NULL) {
					if (stprintalocfail)
						cmn_err(CE_WARN, "st%d: line %d allocb failed on SCI\n", board, line);
						/*
						 *+ A driver attempt to allocate a
						 *+ stream buffer for incoming data
						 *+ failed.  
						 */
				} else {
					*bp->b_wptr++ = buf[2];
					putnext(RD(tp->qptr), bp);
				}
				v_lock(&tp->t_ttylock, s);
				return(0);
			}
		}
		break;

	case ST_BLKOUT:		/* Block Output */
		if (tp->t_state & ISOPEN) {
			l.cnt.v_xmtint++;	/* Bump statistics counter */
			if (buf[2] & STE_MEMERR) {
				cmn_err(CE_NOTE,"st%d: block output mem error, line %d\n",
					board, line);
				/*
				  *+ The Systech board reported a block output memory
				  *+ address error.  Corrective action:  check the
				 *+ Systech board for old revision hardware.
				 *+ If the hardware is old, upgrade it.
			 	 */
				 st_unexp(buf);
			}
			tp->t_state &= ~BUSY;		/* Not busy anymore */ 
			/*
			 * If the close routine did an ABORTOUT command, and
			 * set tp->msg to NULL, don't try to access this
			 * non-existant message!
		 	 */ 
			if ((bp = tp->msg) != NULL) {
				if (!(buf[2] & STE_MEMERR)) {      		/* Don't move ptrs on MEMERR */
					if ((bp->b_wptr - bp->b_rptr) > STMAXOUT)     /* Check for residue */
						bp->b_rptr += STMAXOUT;
					else {
						bp = tp->msg->b_cont;		/* Get next msg block */
						tp->msg->b_cont = NULL;		/* Break link */
						freeb(tp->msg);			/* Free last message */
						tp->msg = bp;			/* This is the new msg*/
					}
				}
			/* If processing an M_DELAY, do not start a new output */
				if (tp->msg != NULL) {
					ASSERT_DEBUG(!(tp->t_state & TXDELAY), "stresponse");
					if ((tp->t_state & SUSPOUT) == 0) {
						ststart(tp, tp->msg);
						if (!(tp->t_state & BUSY))
							qenable(tp->qptr);
					}
				} else
					qenable(tp->qptr);
			}
		}
		break;
	
	case ST_BLKIN:		/* block input */
		tp->blkin_pending = 0;
		if (tp->t_state & ISOPEN) {
			l.cnt.v_rcvint++;	/* Bump statistics counter */
			cnt = buf[3] | (buf[4] << 8);
			if (stprintoverflow && (buf[1] & OE))
				cmn_err(CE_WARN, "st%d: silo overflow on line %d (blkin)\n",
					board, line);
				/*
				 *+ A Systech input silo overflowed; 
				 *+ the Systech lost data.  
				 */
			if (cnt < 0 || cnt > IS) {
				cmn_err(CE_WARN, "st%d: block input count %d line %d\n",
					board, cnt, line);
				/*
				 *+ The input block size from the Systech board is 
				 *+ out of range.  The data is discarded.  
				 */
				st_unexp(buf);
				cnt = 0;
			}
			/* 
			 * If we wanted to flush the hw input buffer, the
			 * DISCARDIN flag will be set.  Set count to zero
			 * to discard the input data.
			 */
			if (tp->t_flags & DISCARDIN) {
				tp->t_flags &= ~DISCARDIN;
				cnt = 0;
			}
			l.cnt.v_rawch += cnt;	/* Add to statistics counter */
			if (buf[2] & STE_MEMERR) {
				cmn_err(CE_NOTE, "st%d: block input memory error, line %d",
					board, line);
				/*
				 *+ The Systech board reported a block input memory
				 *+ address error.  
				 */
				 st_unexp(buf);
			}

			if (cnt > 0) {
				cp = ip->st_inbufs[line].st_buf;
				if (!canputnext(RD(tp->qptr))) {
					if (stprintupstreamblkd)
						if (0 == stupstreamblkdcnt++ % 100)
							cmn_err(CE_WARN, "st%d: line %d unable to deliver %d incoming bytes\n",
								board, line, cnt);
							/*
							 *+ A driver attempt to send incoming
							 *+ data upstream failed due to
							 *+ upstream flow control.
							 *+ The kernel should be tuned by
							 *+ increasing the value of TTYBUF_LATENCY
							 *+ in the ld0 config file.
							 */
				} else if ((bp = allocb(cnt, BPRI_MED)) == NULL) {
					if (stprintalocfail)
						cmn_err(CE_WARN, "st%d: line %d unable to allocb %d bytes\n",
							board, line, cnt);
						/*
						  *+ A driver attempt to allocate a
						  *+ stream buffer for incoming data
						  *+ failed.  
					 	 */
						
					/* Save response buffer in tp for later use. */
					for (n = 0; n < 5; n++)
						tp->bfc_response[n] = buf[n];
					/*
					 * drop RTS (via MC_STOPI) and buffcall
					 */
					if (tp->t_bufcallid == 0) {
						tp->t_bufcallid = bufcall( (uint) cnt, BPRI_MED, stresp, tp );
					}
					(void)putnextctl1(RD(tp->qptr), M_CTL,
								     MC_STOPI);
					v_lock(&tp->t_ttylock, s);
					return(1);
				} else {
					ip->st_chars[line] += cnt;
					bcopy(cp,(char *)bp->b_wptr, (unsigned)cnt);
					bp->b_wptr += cnt;
					putnext(RD(tp->qptr), bp);
				} 
			}
			if ((buf[1] & FE) && !(tp->t_flags & DISCARDIN)) {
				if (!putnextctl1(RD(tp->qptr), M_CTL, MC_FRAME_ERR)) {
					cmn_err(CE_WARN, "st%d: line %d frame error report failed\n",
						board, line);
					/*
					 *+ A driver attempt to report a Systech frame
					 *+ error upstream failed. 
					 */
				}
			} else {
				if (buf[1] & PE) {
					if (!putnextctl2(RD(tp->qptr), M_CTL, MC_PARITY_ERR, (int) buf[5])) {
						cmn_err(CE_WARN, "st%d: line %d parity error report failed (blkin)\n",
							board, line);
						/*
						 *+ A driver attempt to report a Systech parity
						 *+ error upstream failed.
					  	 */
					}
				}
			}
		}
		n = 1;
		if (ip->st_mode[line] == FAST) 
			n = STMAXIN; 
		else { 
			cnt = stlh[(tp->t_cflags & CBAUD)].st_high;
			if (ip->st_chars[line]> cnt) { 
				ip->st_rate[line] = (cnt << 1);
				ip->st_mode[line] = FAST;
				n = STMAXIN;
			}
		}
		addr = ip->st_inbufs[line].st_mbaddr;
		st_cmd(ip, ST_BLKIN|line, addr, addr>>8, addr>>16, n, n>>8);
		tp->blkin_pending = 1;
		break;

	case ST_RERR:		/* Command Error */
		cmn_err(CE_WARN, "st%d: command error", board);
		/*
		 *+ The Systech board reported a command error.  
	 	*/
		st_unexp(buf);
		break;

	case STC_MODEM:		/* modem bits changed */
		l.cnt.v_mdmint++;
		/*FALLTHROUGH*/
	case ST_RSTAT:		/* read modem status */
		if (tp->t_state & ISOPEN) {
			if (buf[1] & tp->t_cmask) {
				tp->t_state |= CARR_ON;
				vall_sema(&tp->t_rawqwait);	/* wake every body up */
			} else if (!(tp->t_cflags & CLOCAL)) {
					tp->t_state &= ~CARR_ON;
					if (!(tp->t_state & WOPEN)) {
	        				if (!putnextctl(RD(tp->qptr), M_HANGUP))
							cmn_err(CE_WARN, "st%d: line %d carrier drop report failed\n", board, line);
							/*
							   *+ A driver attempt to report a carrier drop
							   *+ upstream failed.
			 		 		 */
					}
			}
		}
		break;
		
	case ST_ABORTOUT:
	case ST_ABORTIN:
		break;
	
	default:
		cmn_err(CE_NOTE, "st%d: unexpected", board);
		/*
		  *+ An unexpected response was received from the Systech board.  
	 	 */
		st_unexp(buf);
		break;
	}
	v_lock(&tp->t_ttylock, s);
	return(0);
}

/*
 * stmsg - Received message handler
 */
stmsg(wr_q, mp)
	queue_t  *wr_q;
	mblk_t *mp;
{
	struct strtty *tp = (struct strtty *) wr_q->q_ptr;
	spl_t tplock;
	int line = STLINE(tp->t_dev);
	int board = STBOARD(tp->t_dev);
	struct stinfo *ip = st_info[board];

	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 & TXBREAK) {
					v_lock(&tp->t_ttylock, tplock);
					return(PRIPUTBACK);
				}
				tp->t_state |= SUSPOUT;
				st_cmd(ip, ST_SUSPEND | line);
				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 & TXBREAK) {
					v_lock(&tp->t_ttylock, tplock);
					return(PRIPUTBACK);
				}
				if (tp->t_state & SUSPOUT) {
					tp->t_state &= ~SUSPOUT;
					st_cmd(ip, ST_RESUME | line);
				}
				if ((tp->t_state & (TXDELAY | BUSY)) == 0) {
					if (tp->msg)
						ststart(tp, tp->msg);
				}
				v_lock(&tp->t_ttylock, tplock);
				break;
	
			case MC_STOPI:		/* Suspend 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 & TXBREAK) {
					v_lock(&tp->t_ttylock, tplock);
					return(PRIPUTBACK);
				}
				st_cmd(ip, ST_OUT | line, *++mp->b_rptr);
				tp->t_wcmd &= ~RTS;
				st_cmd(ip, ST_WCMD|line, tp->t_wcmd);
				v_lock(&tp->t_ttylock, tplock);
				break;

			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 & TXBREAK) {
					v_lock(&tp->t_ttylock, tplock);
					return(PRIPUTBACK);
				}
				tp->t_wcmd |= RTS;
				st_cmd(ip, ST_WCMD|line, tp->t_wcmd);
				st_cmd(ip, ST_OUT | line, *++mp->b_rptr);
				v_lock(&tp->t_ttylock, tplock);
				break;
			default:
				cmn_err(CE_WARN, "st%d: M_PCCTL message received with bad subtype\n",board); 
				/*
				 *+ A driver write put routine received a stream
				 *+ message of type M_PCCTL, but the message subtype
				 *+ is illegal.  
				 */
			}
		} else {
			cmn_err(CE_WARN, "st%d: M_PCCTL message received with NULL data ptr\n",board);
			/*
			 *+ A 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_IOCTL:				/* Non-priority 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 & TXBREAK) {
			v_lock(&tp->t_ttylock, tplock);
			return(STDPUTBACK);
		}
		if (st_doioctl(tp, mp)) {
			v_lock(&tp->t_ttylock, tplock);
			return(STDPUTBACK);
		} else 
			v_lock(&tp->t_ttylock, tplock);
		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 & TXBREAK) {
			v_lock(&tp->t_ttylock, tplock);
			return(PRIPUTBACK);
		}
		if (st_doioctl(tp, mp)) {
			v_lock(&tp->t_ttylock, tplock);
			return(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 & TXBREAK) {
			v_lock(&tp->t_ttylock, tplock);
			return(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);
				st_cmd(ip, ST_ABORTOUT | line);
				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);
				*mp->b_rptr &= ~FLUSHW;
				qreply(wr_q, mp);
				break;
			}
			else 
				freemsg(mp);
		} else {
			cmn_err(CE_WARN, "st%d: M_FLUSH message received with NULL data ptr\n",board);
			/*
			 *+ A 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_DELAY:				/* Delay output for n ticks */
	case M_DATA:				/* Std. data */
		tplock = p_lock(&tp->t_ttylock, SPLSTR);
		if ((tp->t_state & (TXBREAK | BUSY | TXDELAY | SUSPOUT)) == 0) {
			ststart(tp, mp);
			v_lock(&tp->t_ttylock, tplock);
		} else {
			v_lock(&tp->t_ttylock, tplock);
			return(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 stmsg()
 * returns a non-zero status, put the message back on the
 * queue for later processing.
 */
static
stwsrv(wr_q)
queue_t *wr_q;
{
	spl_t	tplock;
	mblk_t *bp;
	struct strtty *tp;
	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, "stwsrv:bad wr_q passed to routine\n");
		/*
		 *+ The STREAMS system passed a bad write queue
		 *+ pointer to this service routine.
		 */
		return;
	}
	tp = (struct strtty *) wr_q->q_ptr;
	if (tp == NULL) {
		cmn_err(CE_WARN, "stwsrv: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 = stmsg(wr_q, bp)) == STDPUTBACK) {
			putbq(wr_q, bp);
			return;
		} else if (stat == PRIPUTBACK) {
				putbqx(wr_q, bp);
				return;
			}
 	}
	tplock = p_lock(&tp->t_ttylock, SPLSTR);
	if (tp->t_state & WCLOSE)
		v_sema(&tp->t_rawqwait);
	v_lock(&tp->t_ttylock, tplock);
}

/*
 * st_doioctl - process M_IOCTL and M_PCIOCTL messages.  
 * 
 * Assumes tp is locked at spl s.  
 * Process message by calling st_param() to set up hardware
 * parameters.  Acknowlege message and send it back upstream
 * if all goes well.  Returns zero is all is well.  Returns 1
 * if the output is busy and we must wait for quiescent output.
 */
st_doioctl(tp, mp)
	struct strtty *tp;
	mblk_t *mp;
{
	struct iocblk *iocp;
	struct termio *tptr;
	struct termios *tsptr;
	queue_t *wr_q;
	int line = STLINE(tp->t_dev);
	int board = STBOARD(tp->t_dev);
	short oldcflags;
	register struct stinfo *ip = st_info[board];
	mblk_t *bp;

	STDEBUG(DOIOCTL);
	wr_q = tp->qptr;
	if (mp->b_rptr == NULL) {
		cmn_err(CE_WARN, "st%d: IOCTL message received with NULL data ptr\n",board);
		/*
		 *+ A 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);
	}
	iocp = (struct iocblk *) mp->b_rptr;
	switch (iocp->ioc_cmd) {
	case TCSETAF:			/* Set params with read flush */
		if (tp->t_state & BUSY)
			return(1);	
		tptr = (struct termio *) mp->b_cont->b_rptr;
		oldcflags = tp->t_cflags;	/* Save old flags */
		tp->t_cflags = tptr->c_cflag;

		if ((iocp->ioc_error = st_param(ip, line)) == 0) {
			if (tp->t_cflags & CLOCAL)
				tp->t_state |= CARR_ON;
		} else {
			tp->t_cflags = oldcflags;      /* Put old flags back*/
		}
		
		mp->b_datap->db_type = M_IOCACK;
		iocp->ioc_count = 0;
		if (!putnextctl1(RD(wr_q), M_FLUSH, FLUSHR)) {
			cmn_err(CE_WARN, "st%d: line %d TCSETAF flush msg failed\n", board, line);
			/*
			 *+ A 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 */
		if (tp->t_state & BUSY)
			return(1);	
		tsptr = (struct termios *) mp->b_cont->b_rptr;
		oldcflags = tp->t_cflags;	/* Save old flags */
		tp->t_cflags = tsptr->c_cflag;

		if ((iocp->ioc_error = st_param(ip, line)) == 0) {
			if (tp->t_cflags & CLOCAL)
				tp->t_state |= CARR_ON;
		} else {
			tp->t_cflags = oldcflags;      /* Put old flags back*/
		}

		mp->b_datap->db_type = M_IOCACK;
		iocp->ioc_count = 0;
		if (!putnextctl1(RD(wr_q), M_FLUSH, FLUSHR)) {
			cmn_err(CE_WARN, "st%d: line %d TCSETPF flush msg failed\n", board, line);
			/*
			 *+ A driver attempt to send a read flush message upstream
			 *+ after processing a TCSETPF message failed.
			 */
		}
		qreply(wr_q, mp);
		break;

	case TCSBRK:				/* Do 0.25 secs of BREAK */
		/* Wait if the output port is still busy */
		if (tp->t_state & BUSY)
			return(1);
		if (*(int *) mp->b_cont->b_rptr == 0) {
			st_cmd(ip, ST_WCMD|line, tp->t_wcmd|SBRK);
			tp->t_state |= TXBREAK;
			tp->t_breakid = timeout(st_breaktime, (caddr_t) tp, HZ >> 2);
		}
		mp->b_datap->db_type = M_IOCACK;
		iocp->ioc_count = 0;
		qreply(wr_q, mp);
		break;

	case TCSETAW:
		if (tp->t_state & BUSY)
			return(1);	
	case TCSETA:				/* Set parameters */
		tptr = (struct termio *) mp->b_cont->b_rptr;
		oldcflags = tp->t_cflags;	/* Save old flags */
		tp->t_cflags = tptr->c_cflag;

		if ((iocp->ioc_error = st_param(ip, line)) == 0) {
			if (tp->t_cflags & CLOCAL)
				tp->t_state |= CARR_ON;
		} else {
			tp->t_cflags = oldcflags;      /* Put old flags back*/
		}

		mp->b_datap->db_type = M_IOCACK;
		iocp->ioc_count = 0;
		qreply(wr_q, mp);
		break;

	case TCSETPW:
		if (tp->t_state & BUSY)
			return(1);
	case TCSETP:				/* Set parameters */
		tsptr = (struct termios *) mp->b_cont->b_rptr;
		oldcflags = tp->t_cflags;	/* Save old flags */
		tp->t_cflags = tsptr->c_cflag;
		if ((iocp->ioc_error = st_param(ip, line)) == 0) {
			if (tp->t_cflags & CLOCAL)
				tp->t_state |= CARR_ON;
		} else {
			tp->t_cflags = oldcflags;      /* Put old flags back*/
		}

		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, "st%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((char *)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, "st%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, "st%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( (char *)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, "st%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 ST command bits for setting asynchronous
 * baud rates.  '-1' is the signal to turn off the DTR and RTS signals.
 * '-2' indicates that a baud rate is not available on the ST.
 */

int st_mrates[] = {
	-1,			/* B0	  (hangup)	*/
	STB_M50,		/* B50			*/
	STB_M75,		/* B75			*/
	STB_M110,		/* B110			*/
	STB_M134,		/* B134			*/
	STB_M150,		/* B150			*/
	-2,			/* B200	  (not avail.)	*/
	STB_M300,		/* B300			*/
	STB_M600,		/* B600			*/
	STB_M1200,		/* B1200		*/
	STB_M1800,		/* B1800		*/
	STB_M2400,		/* B2400		*/
	STB_M4800,		/* B4800		*/
	STB_M9600,		/* B9600		*/
	STB_M19200,		/* EXTA                 */
	-2			/* EXTB	  (not used)	*/
};

/*
 * st_param - set line parameters on the hardware.
 * Assumes tp is locked.  Set parameters based on bits
 * set or cleared in tp->t_cflags.
 */
static int
st_param(ip, line)
	register struct stinfo *ip;
	int line;
{
	struct strtty *tp = &ip->st_strtty[line];
	register short flags = tp->t_cflags;
	register int speed = st_mrates[(flags & CBAUD)];
	int stopbits;
	unchar par, len;

	STDEBUG(PARAM);
	if (speed == -2)
		return (EINVAL);
	if (speed == -1) {
		st_cmd(ip, ST_WCMD|line, RXEN|TXEN);
		tp->t_wcmd &= ~(RTS | DTR);
		return (0);
	}
	switch (flags & CSIZE) {
	case CS5:
		len = STB_BITS5;
		break;
	case CS6:
		len = STB_BITS6;
		break;
	case CS7:
		len = STB_BITS7;
		break;
	default:				/* default to 8 bits */
		len = STB_BITS8;
	}
	stopbits = (flags & CSTOPB) ? STB_MSTOP2 : STB_MSTOP1;
	if (flags & PARENB)
		if (flags & PARODD)
			par = STB_ODD_PARITY;
		else	
			par = STB_EVEN_PARITY;
	else
		par = STB_NO_PARITY;
	
	st_cmd(ip, ST_CONFIG|line, STC_ASYNC, (CCA2|stopbits|par|len),
		(CCA3|speed), CCA4);
	return (0);
}

/*
 * Called with streams message of type M_DELAY or M_DATA.
 * If the message type is M_DATA, data is sent to the output port.
 * Assumes that the output port is not busy and that the output
 * data stream is not suspended.  Assumes that tp is locked.
 */
ststart(tp, bp)
	struct strtty *tp;
	mblk_t *bp;
{
	register int addr;
	register int nch;
	register struct stinfo *ip = st_info[STBOARD(tp->t_dev)]; 
	unchar line = STLINE(tp->t_dev);
	int board = STBOARD(tp->t_dev);
	
	STDEBUG(STSTART);
	switch (bp->b_datap->db_type) {

	case M_DELAY:
		if (bp->b_rptr != NULL) {
			ASSERT_DEBUG(tp->t_delayid == 0, "ststart");
			tp->t_state |= TXDELAY;
			tp->t_delayid = timeout(st_delaytime,
						(caddr_t) tp,
						*(int *) bp->b_rptr);
			freemsg(bp);
			return;
		} else {
			cmn_err(CE_WARN, "st%d: M_DELAY message received with NULL data ptr\n",board);
			/*
			 *+ A 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:
		tp->msg = bp;
		if (bp->b_rptr >= bp->b_wptr)  {
			bp = tp->msg->b_cont;
			tp->msg->b_cont = NULL;
			freeb(tp->msg);
			tp->msg = bp;
			if (tp->msg == NULL)
				return;
		}

		/*
	 	* If the number of bytes left in the message block is
	 	* greater than the max output size (OS), send the max
	 	* number of bytes and the rest will be sent later.
	 	*/
		nch = bp->b_wptr - bp->b_rptr;
		if (nch > STMAXOUT)
			nch = STMAXOUT;
		addr = mbad_physmap(ip->st_mbdesc, (int) (ip->st_basemap + line + 16), 
			    	    (caddr_t) bp->b_rptr, (unsigned) nch, 1);
		st_cmd(ip, ST_BLKOUT|line, addr, addr>>8, addr>>16, nch, nch>>8);
		l.cnt.v_outch += nch;			/* Statistics */
		tp->t_state |= BUSY;
	     	break;
	}
}

/*
 * Write command to the command fifo.
 * Mutex the board with 'st_lock'.
 */
/*VARARGS1*/
/*ARGSUSED*/
st_cmd(ip, a, b, c, d, e, f, g)
	struct stinfo *ip;
{
	volatile struct stdevice *staddr = ip->st_addr;
	register int *p = &a;
	register int n;
	int board;
	spl_t s;

	n = ((a & 0xf0) == ST_CONFIG) ? configlen[b>>4] : cmdlen[a>>4];
#ifdef DEBUG
	if (st_debug > 2) {
		int i;
		CPRINTF("st_cmd: ");
		for(i=0; i<n; i++) CPRINTF("0x%x ", p[i] & 0xff);
		CPRINTF("\n");
	}
#endif /* DEBUG */
	s = p_lock(&ip->st_lock, SPLSTR);
	/*
	 * Wait for board to become ready to accept cmds.
	 */
	if (st_wait(staddr, ST_READY)) {
		board = ip->st_vector - stbase;
		cmn_err(CE_WARN, "st%d: cmd fifo timeout (cmd wait)\n", board);
		/*
		 *+ The Systech board was not ready to accept commands after
		 *+ driver wait.  
		 */
		v_lock(&ip->st_lock, s);
		return;
	}
	if (staddr->ststat & ST_ERR) {
		/*
		 * Previous command was a command error so clear it.
		 */
		staddr->stcmd = ST_RERR;
		staddr->stgo = 1;
		if (st_wait(staddr, ST_READY)) {
			board = ip->st_vector - stbase;
			cmn_err(CE_WARN, "st%d: cmd fifo timeout (bad rerr) \n", board);
			/*
			 *+ The Systech board was not ready to accept commands
			 *+ after driver wait.  
			 */
			v_lock(&ip->st_lock, s);
			return;
		}
	}
	/*
	 * Load 'n' command bytes into the cmd fifo.
	 * Then return without waiting.
	 */
	while (n-- > 0)
		staddr->stcmd = (unchar) *p++;
	ip->st_addr->stgo = 1;
	v_lock(&ip->st_lock, s);
}

/*
 * Print out an unexpected response.
 */
st_unexp(buf)
	register unchar *buf;
{
	register int n = resplen[*buf>>4];

	CPRINTF(" response = ");
	while (n-- > 0)
		CPRINTF(" 0x%x", *buf++);
	CPRINTF("\n");
}

/*
 * Timer routine.
 * Called from stintr() to abort lines in FAST mode that have not
 * received any data since last timer tick.
 * Changes the mode from FAST to SLOW if rate slows enough.
 */
sttimer()
{
	register struct stinfo *ip;
	register int i, board;
	int stlow;

	for (board = 0; board < nst; board++) {
		ip = st_info[board];
		if (ip == NULL || (ip->st_cflags & STF_BI) == 0)
			continue;
		for (i = 0; i < ip->st_size; i++) {
			if (ip->st_mode[i] == FAST) {
				/* 
				 * ave = (sttimerave-1)*ave+chars)
				 *         ---------------------
				 *		sttimerave
				 * Asymptotic to chars^s
				 */
			        ave(ip->st_rate[i],ip->st_chars[i],sttimerave);
				stlow = stlh[(ip->st_strtty[i].t_cflags & CBAUD)].st_low;
				if (ip->st_rate[i] < stlow)
					ip->st_mode[i] = SLOW;
				if (ip->st_mode[i] == SLOW || !ip->st_chars[i])
					st_cmd(ip, ST_RBDATA|i);
			}
			ip->st_chars[i] = 0;
		}
	}
}

/*
 * Wait for a bit to be set in the status register.
 * Return 0 for success, 1 if timeout occurs.
 */
static int
st_wait(staddr, bit)
	volatile struct stdevice *staddr;
	register int bit;
{
	register int temp;

	temp = stfifotimeout;
	while ((staddr->ststat & bit) == 0) {
		DELAY(50);
		if (temp-- <= 0)
			return (1);
	}
	return (0);
}

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

	s = p_lock(&tp->t_ttylock, SPLSTR);
	if (tp->t_state & WCLOSE) {
		if (qsize(WR(rd_q)))
			tp->t_state |= DRAINTIME;
		v_sema(&tp->t_rawqwait);
	}
	v_lock(&tp->t_ttylock, s);
}
/*
 * Called when timer expires when waiting 0.25sec for break output.
 */
st_breaktime(tp)
	struct strtty *tp;
{
	struct stinfo *ip = st_info[STBOARD(tp->t_dev)];
	unchar line = STLINE(tp->t_dev);
	spl_t s;

	s = p_lock(&tp->t_ttylock, SPLSTR);
	tp->t_state &= ~TXBREAK;
	st_cmd(ip, ST_WCMD|line, tp->t_wcmd);		/* Turn break off */
	tp->t_breakid = 0;
	qenable(tp->qptr);
	v_lock(&tp->t_ttylock, s);
}

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

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