static char rcsid[] = "$Header: smoct.c,v 820.1 86/12/04 19:56:30 root Exp $";
static char sccsid[]="%W% %Y% %Q% %G%";

/************************************************************************
*									*
*				Copyright 1985				*
*			VALID LOGIC SYSTEMS INCORPORATED		*
*									*
*	This listing contains confidential proprietary information	*
*	which is not to be disclosed to unauthorized persons without	*
*	written consent of an officer of Valid Logic Systems 		*
*	Incorporated.							*
*									*
*	The copyright notice appearing above is included to provide	*
*	statutory protection in the event of unauthorized or 		*
*	unintentional public disclosure.				*
*									*
************************************************************************/

/*
 * Central Data High Performance Intelligent Terminal Controller MB1031
 */

#include "smoct.h"
#if NSMOCT > 0
#include <setjmp.h>
#include "../h/param.h"
#include "../h/ioctl.h"
#include "../h/tty.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/conf.h"
#include "../h/file.h"
#include "../h/kernel.h"
#include "../h/uio.h"
#include "../s32/vectors.h"
#include "../s32/cpu.h"
#include "../s32dev/mbvar.h"
#include "../s32dev/screg.h"
#include "../s32dev/smoctreg.h"

int smoctreset(), smoctprobe(), smoctattach();
struct mb_device *smoctinfo[NSMOCT];
u_short smoctstd[] = { IOPORTBASE, 0 };
struct mb_driver smoctdriver =
    { smoctreset, smoctprobe, 0, smoctattach, smoctstd, "smoct", smoctinfo };
u_short smoct_ignore = 0;	/* for debugging: don't probe if 1 */

/* map stty to Signetics 2661 speeds -- use same table as sc driver */
extern char sc_baud[];
#define SCBAUDSIZE 16

char smoct_db[ULTOMINOR(NSMOCT,0)] = {
	B9600, B9600, B9600, B9600, B9600, B9600, B9600, B9600,
	B9600, B9600, B9600, B9600, B9600, B9600, B9600, B9600,
	B9600, B9600, B9600, B9600, B9600, B9600, B9600, B9600,
	B9600, B9600, B9600, B9600, B9600, B9600, B9600, B9600
};
struct tty smoct_tty[ULTOMINOR(NSMOCT,0)];


int	smoct_basepage;	/* Lowest page of DMA stuff with V_NONCACHED
			   or'd in if 68020, so we don't have to keep
			   recalculating it each time */

enum intstatflag { STATOFF, STATON, FORCEINTR };

u_char smoct_chk = 0;	/* if (smoct_chk & (1 << unit)) then smoctinit() has
			   been called already for unit */
u_char smoct_up = 0;	/* if (smoct_up & (1 << unit)) then unit is
			   properly initialized by smoctinit() */
int smoct_vpage = 0;	/* Smoct_vpage and smoct_vpage + 1 are two
			   pages of virtual address space that we map
			   to the physical 8K structure for one unit
			   and one line.  Smoct_vpage = kvalloc(2);
			   see smoctinit().
			 */
u_short smoctintparam = ((1 << 3) | 5) << 8;

#define	TIMESTATS
#undef	BUFSTATS

#ifdef BUFSTATS
u_char	smoctinstat[IBUFSIZE];	/* stats for input buffer usage */
u_char	smoctoutstat[OBUFSIZE]; /* stats for output buffer usage */
int inpintcnt, outintcnt;	/* count interrupts */
#endif BUFSTATS

#ifdef TIMESTATS
#define TIMEBUFSIZE 512
unsigned long	smoct_timebuf[TIMEBUFSIZE],	*smoct_time,
		*smoct_tend = &smoct_timebuf[TIMEBUFSIZE];
u_long	tzero;
extern 	struct timeval	time;
#define LONGTIME(ch) (((ch)<<26)|(((time.tv_sec-tzero)&0x3F)<<20)|time.tv_usec)
#define MARKTIME(ch) if (smoct_time < smoct_tend) *smoct_time++ = LONGTIME(ch)
#define DECODETIME(ch,usec,t) ((ch)=(((t)>>26)|0x40),\
				usec=((((t>>20)&0x3F)*1000000)+(t&0xFFFFF)))
#endif TIMESTATS



#define RDYWAIT (chipType == CHIPTYPE_68020 ? 8000000 : 2000000)
#define STATWAIT (chipType == CHIPTYPE_68020 ? 8000000 : 2000000)


#define CDBGSTART	0x10000000
#define PRINTFS		0x01000000
#define PRSPURSTAT	0x02000000
#define RESETDBG	0x00100000
#define CHGPARAM	0x00020000
#define REPTSTATS	0x00010000
#define ONEOUTBUF	0x00004000
/*
** Using multiple buffers drops characters the first time the port is 
** used. It may also be responsible for spurious interrupt problems,
** therefore we are defaulting to one buffer. -- chrisw 12/4/86
**
** There is an inherent danger in setting the default in 'smoctdbg' since
** someone may clobber this variable while setting other debug flags in
** 'adb', but we want to an easy way to switch back to multiple buffers 
** in the field. -- chrisw
*/
int smoctdbg = ONEOUTBUF;

/*ARGSUSED*/
smoctreset(reg)
	caddr_t reg;
{
	label_t *saved_jb;
extern	label_t *nofault;
	label_t jb;
	register line;
	int unit = IOPORTTOUNIT(reg);
	int oldvpageminor, min;

	if (smoct_ignore)
		return;
	if (smoctdbg & PRINTFS) printf("smoctreset(%x)\n", reg);

	/* protect from bus errors */
	saved_jb = nofault;
	nofault = &jb;
	if (setjmp(&jb)) {
		nofault = saved_jb;
		if (smoctdbg & PRINTFS)
			printf("smoctreset chokes on bus error\n", reg);
		return -1;		/* urp, fault */
	}

	if (smoctinit(reg, unit)) {
		nofault = saved_jb;
		printf("smoctinit called from smoctreset fails\n");
		return -1;
	}
	for (line = 0, min = ULTOMINOR(unit, 0);
	    line < NSMOCTLINES;
	    line++, min++) {
#ifdef	OLD_VM
		oldvpageminor	= smoctsetvpage(min);
#endif	OLD_VM

		smoctcmdexec(unit, line, CLRINT, 0);

#ifdef	OLD_VM
		smoctsetvpage(oldvpageminor);
#endif	OLD_VM
	}
	nofault = saved_jb;
}

/*ARGSUSED*/
smoctprobe(reg, intr, unit)
	caddr_t reg;
	int (*intr)();
{
	label_t *saved_jb;
extern	label_t *nofault;
	label_t jb;
	register i;
	int smoctintr();
	int	oldvpageminor, min;
	register struct smoct_param *spp =
		&smoct_param[ULTOMINOR(unit, 0)];
	u_char	*intsrc;
	int ret = 0;

	if (smoct_ignore)	/* for debugging, pretend it isn't there */
		return 0;
	if (!(0 <= unit || unit < NSMOCT)) {
		printf("smoctprobe(%x,%x,%x) -- bad unit number\n",
			reg, intr, unit);
		return 0;
	}
	if (smoctdbg & PRINTFS)
		printf("smoctprobe(%x,%x,%x)\n", reg, intr, unit);

	/* protect from bus errors */
	saved_jb = nofault;
	nofault = &jb;
	if (setjmp(&jb)) {
		if (smoctdbg & PRINTFS)
			printf("smoctprobe chokes on bus error\n", reg);
		goto end;		/* urp, fault */
	}
	if (intr != smoctintr)
		goto end;
	for (i = 0; i < NSMOCT; i++)
		if (smoctinfo[i] && smoctinfo[i]->md_alive &&
		    smoctinfo[i]->md_addr == reg)
			goto end;

	if (smoctinit(reg, unit))
		goto end;

	/* Board seems to be there, let's make it interrupt */
	min = ULTOMINOR(unit,0);
#ifdef	OLD_VM
	oldvpageminor	= smoctsetvpage(min);
#endif	OLD_VM

	/* force status change interrupt */
	smoctsetstat(spp, unit, 0, FORCEINTR);

#ifdef	M68020
	flushWriteQueue();
#endif	M68020
	/* Interrupt should happen somewhere in here. */

	/*
	 * Since the status change interrupt was forced using line 0 above,
	 * we can use the smoct pointer below to poll.
	 */
	/* ...poll for status change... */
	intsrc = &(smoct->so_intsrc);
	for (i = STATWAIT; --i >= 0 && ((*intsrc & STATCHG) == 0);)
		;
	if (i < 0) {
		printf("smoctprobe: poll fails\n");
		goto fixvpage;
	} else {
		if (smoctdbg & PRINTFS) printf("smoctprobe: poll succeeds\n");
	}

	/* Restore to normal -- turn off status change interrupts */
	smoctsetstat(spp, unit, 0, STATOFF);

	/* Clear the interrupt */
	if (smoctcmdexec(unit, 0, CLRINT, 0))
		goto fixvpage;

	ret = sizeof (struct smoct);	/* successful termination */
fixvpage:
#ifdef	OLD_VM
	smoctsetvpage(oldvpageminor);
#endif	OLD_VM
end:
	nofault = saved_jb;
	return ret;
}

/*ARGUSED*/
smoctattach(md)
	struct mb_device *md;
{
	register line;
	int unit = md->md_unit;
	register struct smoct_param *spp =
		&smoct_param[ULTOMINOR(md->md_unit, 0)];
	register struct tty *tp =
		&smoct_tty[ULTOMINOR(md->md_unit, 0)];

	for (line = 0; line < NSMOCTLINES; line++, spp++, tp++)
	{
		spp->sp_ucmdreg = SCCR_NORMAL;
		smoctcmdreg(unit, line);

	}
}

/*ARGSUSED*/
smoctopen(dev, flag)
	dev_t dev;
{
	int line = MINORTOLINE(minor(dev));
	int unit = MINORTOUNIT(minor(dev));
	register struct tty *tp = &smoct_tty[minor(dev)];
	register struct smoct_param *spp = &smoct_param[minor(dev)];
#ifdef	OLD_VM
	int oldvpageminor;
#else	OLD_VM
	register struct smoct *sp = smoct + line;
#endif	OLD_VM
	int error;
	int smoctstart();
	int smoctintr();


	if (unit >= NSMOCT || smoctinfo[unit] == 0 || !smoctinfo[unit]->md_alive)
		return ENXIO;
	if (tp->t_state & TS_XCLUDE && u.u_uid != 0)
		return EBUSY;
	if ((tp->t_state & (TS_ISOPEN|TS_WOPEN)) == 0) {
		tp->t_oproc = smoctstart;
		tp->t_flags = ECHO|XTABS|CRMOD|CRTBS|CRTERA|CRTKIL|CTLECH
			    | EVENP|ODDP;	
		ttychars(tp);
		tp->t_ispeed = tp->t_ospeed = smoct_db[minor(dev)];
		tp->t_line = NTTYDISC;

		spp->sp_ucmdreg = SCCR_DTR|SCCR_RTS|SCCR_RXEN|SCCR_TXEN;
		smoctcmdreg(unit, line);

		smoctparam(tp);

		/* statistics stuff */
#ifdef	BUFSTATS
		inpintcnt = outintcnt = 0;
		smoctclrstat(smoctinstat, IBUFSIZE);
		smoctclrstat(smoctoutstat, OBUFSIZE);
#endif	BUFSTATS
#ifdef	TIMESTATS
		tzero = time.tv_sec;
		smoct_time = &smoct_timebuf[0];
#endif	TIMESTATS
	}

	/* set up to interrupt if DSR or DCD go on */
	(void) spl5();
#ifdef	OLD_VM
	oldvpageminor	= smoctsetvpage(minor(dev));
	if (smoct->so_usartstat & (SCSR_DCD | SCSR_DSR))
		tp->t_state |= TS_CARR_ON;
	smoctsetstat(spp, unit, line, STATON);
	smoctsetvpage(oldvpageminor);
#else	OLD_VM
	if (sp->so_usartstat & (SCSR_DCD | SCSR_DSR))
		tp->t_state |= TS_CARR_ON;
	smoctsetstat(spp, unit, line, STATON);
#endif	OLD_VM
	(void) spl0();

	(void) spl5();
	while ((tp->t_state & TS_CARR_ON) == 0) {
		tp->t_state |= TS_WOPEN;
		sleep((caddr_t)&tp->t_rawq, TTIPRI);
	}
	error = (*linesw[tp->t_line].l_open)(dev, tp);
	(void) spl0();
	return error;
}

smoctclose(dev)
	dev_t dev;
{
	int line = MINORTOLINE(minor(dev));
	int unit = MINORTOUNIT(minor(dev));
#ifdef	OLD_VM
	int oldvpageminor;
#endif	OLD_VM
	register struct smoct_param *spp = &smoct_param[minor(dev)];
	register struct tty *tp = &smoct_tty[minor(dev)];
	int error;

	(*linesw[tp->t_line].l_close)(tp);

	spp->sp_ucmdreg &= ~SCCR_BRK;
	smoctcmdreg(unit, line);

	error = ttyclose(tp);			/* drain output first */

	spp->sp_ucmdreg &= ~(SCCR_DTR|SCCR_RTS|SCCR_RXEN|SCCR_TXEN);
	smoctcmdreg(unit, line);

	/* shut off status change interrupts */
#ifdef	OLD_VM
	oldvpageminor	= smoctsetvpage(minor(dev));
#endif	OLD_VM
	smoctsetstat(spp, unit, line, STATOFF);
#ifdef	OLD_VM
	smoctsetvpage(oldvpageminor);
#endif	OLD_VM
	if (smoctdbg & REPTSTATS) {
#ifdef	BUFSTATS
		printf("input buffer usage: %d interrupts\n", inpintcnt);
		smoctrptstat(smoctinstat, IBUFSIZE);
		printf("output buffer usage: %d interrupts\n", outintcnt);
		smoctrptstat(smoctoutstat, OBUFSIZE);
#endif	BUFSTATS
#ifdef	TIMESTATS
		smoctdumptimes();
#endif	TIMESTATS
	}

	return error;
}

smoctread(dev, uio)
	dev_t dev;
	struct uio *uio;
{
	register struct tty *tp = &smoct_tty[minor(dev)];
	int r, s, save;

	r = (*linesw[tp->t_line].l_read)(tp, uio);
	MARKTIME('r');

	/* Go poll the board to see if there is another chunk
	   available, now that we have cleared some out.  This is mostly
	   important for the time after we have gotten the last bit of
	   data and want to clear out the buffer.
	 */
	s = spl2();	/* shut out smoctintr */
#ifdef	OLD_VM
	save = smoctsetvpage(minor(dev));
	smoctchkibuf(tp);
#else	OLD_VM
	smoctchkibuf(tp, smoct + MINORTOLINE(minor(dev)));
#endif	OLD_VM
#ifdef	OLD_VM
	smoctsetvpage(save);
#endif	OLD_VM
	splx(s);

	return	r;
}

smoctwrite(dev, uio)
	dev_t dev;
	struct uio *uio;
{
	register struct tty *tp = &smoct_tty[minor(dev)];
	int r;

	r = (*linesw[tp->t_line].l_write)(tp, uio);
	MARKTIME('w');
	return	r;
}

smoctintr(dev)
dev_t dev;
{
	/*
	 *  The usual "dev" argument isn't valid since there is only one
	 *  interrupt generated by the eight UART channels.  Must
	 *  read smoct->so_portintstat on line 0 to find out which is the
	 *  interrupter.
	 */
	register u_char portmask;
	u_char mask;
	register int line;
	register struct tty *tp;
        register struct smoct_param *spp;
#ifdef	OLD_VM
	int oldvpageminor;
#else	OLD_VM
	register struct smoct *sp;
#endif	OLD_VM
	int unit = 0;			/* BROKEN */
	int min = ULTOMINOR(unit,0);

#ifdef	OLD_VM
	oldvpageminor	= smoctsetvpage(min);
#endif	OLD_VM
	portmask = smoct->so_portintstat;		/* line 0 only */
	if (portmask == 0) return 0;		/* sorry, wrong number */

#ifdef	OLD_VM
	for (line = 0, mask = 1,
		tp = &smoct_tty[min], spp = &smoct_param[min];
		portmask != 0;
		line++, min++, tp++, spp++, mask <<= 1) {
#else	OLD_VM
	for (line = 0, mask = 1, sp = smoct,
		tp = &smoct_tty[min], spp = &smoct_param[min];
		portmask != 0;
		sp++, line++, min++, tp++, spp++, mask <<= 1) {

	    if (portmask & mask) {	/* found */
		register u_char intsrc;
#ifdef	OLD_VM
		smoctsetvpage(min);
		intsrc = smoct->so_intsrc;
#else	OLD_VM
		intsrc = sp->so_intsrc;
#endif	OLD_VM

		portmask &= ~mask;	/* cross it off */

		if (smoctdbg & PRINTFS)
		    printf("smoct%d intr type %x state %x USART status %x\n",
				min, intsrc, tp->t_state, smoct->so_usartstat);

		if (intsrc & OUTPDONE) {
		    /* output done/ready for output */

#ifdef	BUFSTATS
		    outintcnt++;
#endif	BUFSTATS
		    /*
		     * This really is a bogus sanity check
		     */
#ifdef	LATER
		    if ((tp->t_state & TS_BUSY) == 0) {
			printf("smoct%d: spurious output interrupt\n", min);
		    } else {
#endif	LATER
			tp->t_state &= ~TS_BUSY;
			/*
			 * There was no real documentation on how multiple
			 * buffer operation worked so we are going back
			 * to single buffer operation in light of the
			 * spurious output interrupt problems.
			 */
			switch (spp->sp_state & BUFSTATE) {
			    case BUF0USED:
			    case BUF0USED | BUF0THEN1:
			    case BUF0USED | BUF1USED | BUF0THEN1:
				spp->sp_state &= ~BUF0USED;	break;
			    case BUF1USED:
			    case BUF1USED | BUF0THEN1:
			    case BUF0USED | BUF1USED:
				if (smoctdbg & ONEOUTBUF) { /* One buffer is the default now */
				    spp->sp_state &= ~BUF0USED;	break;
				}
				spp->sp_state &= ~BUF1USED;	break;
			    default:
				printf("smoct%d: bad output state\n", min);
				break;
			}
			(*linesw[tp->t_line].l_start)(tp);
		    }
#ifdef	LATER
		}
#endif	LATER

		if (intsrc & INPUT) { /* input ready */
#ifdef	BUFSTATS
		    inpintcnt++;
#endif	BUFSTATS
#ifdef	TIMESTATS
		    MARKTIME('i');
#endif	TIMESTATS
#ifdef	OLD_VM
		    smoctchkibuf(tp);
#else	OLD_VM
		    smoctchkibuf(tp, sp);
#endif	OLD_VM
		}

		if (intsrc & STATCHG) {
		    /* status change -- DSR or DCD changed */
		    register u_char stat;

#ifdef	OLD_VM
		    stat = smoct->so_usartstat;
#else	OLD_VM
		    stat = sp->so_usartstat;
#endif	OLD_VM
		    if (stat & spp->sp_intstatb1) {
			if (smoctdbg & PRINTFS)
			    printf("DCD or DSR turned on, line %d stat %x\n",
				line, stat);

			if ((tp->t_state & TS_CARR_ON) == 0) {
				/*
				 * NOTE: if wakeup done first race exists
				 */
				tp->t_state |= TS_CARR_ON;
				wakeup((caddr_t)&tp->t_rawq);
			}
		    } else if ((~stat) & spp->sp_intstatb0) {
			if (smoctdbg & PRINTFS)
			    printf("DCD or DSR turned off, line %d stat %x\n",
				line, stat);

			if (tp->t_state & TS_CARR_ON &&
			    (tp->t_flags & NOHANG) == 0) {
				if (tp->t_state&TS_ISOPEN) {
					gsignal(tp->t_pgrp, SIGHUP);
					gsignal(tp->t_pgrp, SIGCONT);
				}
				tp->t_state &= ~TS_CARR_ON;
			}
		    } else if (smoctdbg & PRSPURSTAT) {
			/* This happens pretty frequently:  it changes
			   and then changes back before we can catch it.  */
			printf("smoctintr: spurious status change\n");
		    }

		    smoctsetstat(spp, unit, line, STATON);
		}

		if (intsrc & PARERRINT) {
		    /* parity error */
		    /* [What do we do about this?] */
		    printf("smoctintr: parity error interrupt\n");
		}

	    /*
	     * I believe that we should not clear the interrupt until all
	     * ports are serviced ... therefore I am moving the following
	     * two lines of code - ELP 11/04/1986
	     */
#ifdef	DONT_CLEAR_INTERRUPTS_HERE
		/* clear interrupt ONLY at the end! */
		smoctcmdexec(unit, line, CLRINT, 0);
#endif	DONT_CLEAR_INTERRUPTS_HERE

	    }
	}
	/* clear interrupt ONLY at the end! */
	smoctcmdexec(unit, 0, CLRINT, 0);
#ifdef	OLD_VM
	smoctsetvpage(oldvpageminor);
#endif	OLD_VM
	return(1);	/* always reschedule */
}

/*
 *  Check the input buffer and get a chunk of data out if there is
 *  some in there.  Must use smoctsetvpage() to select the right line
 *  before calling this routine!
 */
#ifdef	OLD_VM
smoctchkibuf(tp)
#else	OLD_VM
smoctchkibuf(tp, sp)
register struct smoct *sp;
#endif	OLD_VM
struct tty *tp;
{
	register u_short empindex, filindex;

	MARKTIME('b');
#ifdef	OLD_VM
	empindex = SWAPSHORT(smoct->so_iempptr);
#else	OLD_VM
	empindex = SWAPSHORT(sp->so_iempptr);
#endif	OLD_VM
	/* Note that so_ifilptr may move during this loop
	   if board is still getting more input.  If it is
	   not moving then the loop will make one
	   iteration if emp < fil and one or two if emp > fil.

	   Break out early if we hit a line feed -- the
	   line discipline l_rint routines will not take
	   more than a line before they have to wake up
	   readers.
	 */
	while (empindex !=
#ifdef	OLD_VM
	      (filindex = SWAPSHORT(smoct->so_ifilptr))) {
#else	OLD_VM
	      (filindex = SWAPSHORT(sp->so_ifilptr))) {
#endif	OLD_VM
	    register char *r;
	    char *smoctemptyibuf();

#ifdef	OLD_VM
	    if (empindex < filindex) {
		/* empindex to filindex */
		if (r = smoctemptyibuf(
		    &(smoct->so_ibuf[empindex]),
		    &(smoct->so_ibuf[filindex]),
		    &(smoct->so_ebuf[empindex]), tp)) {
		    smoct->so_iempptr =
			SWAPSHORT(r - &(smoct->so_ibuf[0]));
		    break;
		}
		smoct->so_iempptr = SWAPSHORT(empindex = filindex);
	    } else {	/* empindex > filindex */
		/* empindex to end of buffer, reset to 0 */
		if (r = smoctemptyibuf(
		    &(smoct->so_ibuf[empindex]),
		    &(smoct->so_ebuf[0]),
		    &(smoct->so_ebuf[empindex]), tp)) {
		    smoct->so_iempptr =
			SWAPSHORT(r - &(smoct->so_ibuf[0]));
		    break;
		}
		smoct->so_iempptr = empindex = 0;
	    }
#else	OLD_VM
	    if (empindex < filindex) {
		/* empindex to filindex */
		if (r = smoctemptyibuf(
		    &(sp->so_ibuf[empindex]),
		    &(sp->so_ibuf[filindex]),
		    &(sp->so_ebuf[empindex]), tp)) {
		    sp->so_iempptr =
			SWAPSHORT(r - &(sp->so_ibuf[0]));
		    break;
		}
		sp->so_iempptr = SWAPSHORT(empindex = filindex);
	    } else {	/* empindex > filindex */
		/* empindex to end of buffer, reset to 0 */
		if (r = smoctemptyibuf(
		    &(sp->so_ibuf[empindex]),
		    &(sp->so_ebuf[0]),
		    &(sp->so_ebuf[empindex]), tp)) {
		    sp->so_iempptr =
			SWAPSHORT(r - &(sp->so_ibuf[0]));
		    break;
		}
		sp->so_iempptr = empindex = 0;
	    }
#endif	OLD_VM
	}
}

/*
 *  Clear a contiguous series of bytes from the board input buffer.
 *  It is an error to call this routine if charp >= stopp; we assume this
 *  test has already been done.
 *
 *  Return charp if we hit a '\n' and had to stop, 0 if we got to the end.
 *  The tty line discipline l_rint routines (ttyinput, bkinput) cannot
 *  handle more than a line at a time without waking up the reader to
 *  clean out the queue.
 */
char *
smoctemptyibuf(charp,stopp,errp,tp)
register char *charp, *stopp, *errp;
register struct tty *tp;
{
	register u_char e, c;
	char *startp = charp;
	u_char bad;
	int (*l_rint)() = linesw[tp->t_line].l_rint;

	bad = tp->t_flags & RAW ? tp->t_intrc : 0;
	do {
		if ((e = *XORBIT0(errp++)) == 0) {
			if (e & FRAME) {
			    /* frame error */
			    c = bad;
			} else {
			    /* some other error -- I don't know
				what to do */
			    c = bad;
			}
			charp++;
		} else
			c = *XORBIT0(charp++);

		/* for efficiency we calculate l_rint outside of the
		   loop as it does not change */
		(*l_rint)(c, tp);

		if (c == '\n') {
#ifdef	BUFSTATS
			smoctinstat[charp-startp]++;
#endif	BUFSTATS
			return (charp);
		}
	} while (charp < stopp);
#ifdef	BUFSTATS
	smoctinstat[stopp-startp]++;
#endif	BUFSTATS
	return (0);
}

smoctstart(tp)
	register struct tty *tp;
{
	enum smoct_cmd cmd;
	register i;
	register int line = MINORTOLINE(minor(tp->t_dev));
	register int unit = MINORTOUNIT(minor(tp->t_dev));
	register n;
	register char *obuf;
	register char *p;
	register struct smoct_param *spp = &smoct_param[minor(tp->t_dev)];
#ifdef	OLD_VM
	int oldvpageminor;
#else	OLD_VM
	register struct smoct *sp = smoct + line;
#endif	OLD_VM
	int s;
	int onebusy;

	int ttrstrt();

	s = spl2();	/* keep out smoctintr -- mutex smoct_vpage */
	/* Note that we don't leave on TS_BUSY -- with this board we
	   can try to load up another output buffer. */
	if (tp->t_state & (TS_TIMEOUT|TS_TTSTOP)) {
		splx(s);
		return;
	}
	if ((onebusy = (tp->t_state & TS_BUSY)) && 
		    (spp->sp_state & BUF0USED) && (spp->sp_state & BUF1USED)) {
		splx(s);
		return;
	}
	if (tp->t_outq.c_cc <= TTLOWAT(tp)) {
		if (tp->t_state & TS_ASLEEP) {
			tp->t_state &= ~TS_ASLEEP;
			wakeup((caddr_t)&tp->t_outq);
		}
		if (tp->t_wsel) {
			selwakeup(tp->t_wsel, tp->t_state & TS_WCOLL);
			tp->t_wsel = 0;
			tp->t_state &= ~TS_WCOLL;
		}
	}
	if (tp->t_outq.c_cc == 0) {
		splx(s);
		return;
	}
	if (tp->t_flags & (RAW|LITOUT))
		n = ndqb(&tp->t_outq, 0);
	else {
		n = ndqb(&tp->t_outq, 0x80);
		if (n == 0) {
			if (!onebusy) {
				register char c = getc(&tp->t_outq);
				timeout(ttrstrt, (caddr_t)tp, (c & 0x7f) + 6);
				tp->t_state |= TS_TIMEOUT;/* next call must be
							     from ttrstrt() */
			}
			splx(s);
			return;
		}
	}

	if ((spp->sp_state & BUF0USED) && (spp->sp_state & BUF1USED))
		if (onebusy) {
			splx(s);
			return;
		} else
/* Preston thinking out loud here */
	/* Perhaps instead of onebusy we need to resample
           tp->t_state & TS_BUSY.  Or is it possible for that to get
	   turned off after we read it above?  Perhaps not.
	   
	   What happens if we change this panic() into a printf()?
	   It thinks BUF1USED is false in the code below.  Don't do that.
	   
	   What happens if we change this panic() in { printf(); return }?
	   It will either (a) right itself or (b) get jammed and print
	   that message over and over, or else hang.  Probably the latter.
	   Yes, the latter.  Throw away a buffer. -- PG 28 May 86
	 */
		{
			if (smoctdbg & PRINTFS)
				printf("smoctstart -- BUSY off, no bufs\n");
			spp->sp_state &= ~BUF1USED;
		}
	/* 
	   If this doesn't work try flushing one of the buffers.  That may
	   be difficult if the command has already been sent to the board.
	 */


	if (spp->sp_state & BUF0USED) {
		spp->sp_sendblk1 = n;
#ifdef	OLD_VM
		obuf = &(smoct->so_obuf1[0]);
#else	OLD_VM
		obuf = &(sp->so_obuf1[0]);
#endif	OLD_VM
		cmd = SENDBLK1;
		spp->sp_state |= (BUF1USED | BUF0THEN1);
		MARKTIME('S');
	} else {
		spp->sp_sendblk0 = n;
#ifdef	OLD_VM
		obuf = &(smoct->so_obuf0[0]);
#else	OLD_VM
		obuf = &(sp->so_obuf0[0]);
#endif	OLD_VM
		cmd = SENDBLK0;
		spp->sp_state |= BUF0USED;
		spp->sp_state &= ~BUF0THEN1;	/* this is 1 then 0 */
		MARKTIME('s');
	}

#ifdef	OLD_VM
	oldvpageminor	= smoctsetvpage(minor(tp->t_dev));
#endif	OLD_VM

	/*
	 * Moved this up from below because a race exists between the return 
	 * from smoctcmdexec and the time you get an interrupt.  If this
	 * happens before the TS_BUSY bit is set then a "spurious output
	 * interrupt" message magically appears on the console.
	 *
	 * ELP - 11/07/1986
	 */
	tp->t_state |= TS_BUSY;		/* next call must be from smoctintr() */

	/* load buffer */
	for (i = n; i > 0; i--)
		*XORBIT0(obuf++) = getc(&tp->t_outq);

	smoctcmdexec(unit, line, spp->sp_lastocmd = cmd, n);

#ifdef	OLD_VM
	smoctsetvpage(oldvpageminor);
#endif	OLD_VM
#ifdef	BUFSTATS
	smoctoutstat[n]++;
#endif	BUFSTATS

#ifdef	LATER
	tp->t_state |= TS_BUSY;		/* next call must be from smoctintr() */
#endif	LATER
	splx(s);
}	

/*ARGSUSED*/
smoctstop(tp, flag)
	register struct tty *tp;
{
	int s = spl5();

	if (tp->t_state & TS_BUSY) {
		if ((tp->t_state & TS_TTSTOP) == 0)
			tp->t_state |= TS_FLUSH;
	}
	splx(s);
}

smoctioctl(dev, cmd, data, flag)
	caddr_t data;
	dev_t dev;
{
	int line = MINORTOLINE(minor(dev));
	int unit = MINORTOUNIT(minor(dev));
	register struct tty *tp = smoct_tty + line;
	register i;
	int error;

	error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag);
	if (error >= 0)
		return error;
	error = ttioctl(tp, cmd, data, flag);
	if (error > 0)
		return error;
	if (error == 0) {
		switch (cmd) {
		case TIOCSETN:
		case TIOCSETP:
		case TIOCSET:
		case TIOCBIS:
		case TIOCBIC:
		case TIOCLSET:
		case TIOCLBIS:
		case TIOCLBIC:
			if (tp->t_ospeed == BDEFAULT)
				tp->t_ispeed = tp->t_ospeed = smoct_db[minor(dev)];
			smoctparam(tp);
			break;
		}
		return 0;
	}
	switch (cmd) {
	case TIOCSBRK:
		smoct_param[minor(dev)].sp_ucmdreg |= SCCR_BRK;
		smoctcmdreg(unit, line);
		break;
	case TIOCCBRK:
		smoct_param[minor(dev)].sp_ucmdreg &= ~SCCR_BRK;
		smoctcmdreg(unit, line);
		break;
	case TIOCSDTR:
		smoct_param[minor(dev)].sp_ucmdreg |= SCCR_DTR;
		smoctcmdreg(unit, line);
		break;
	case TIOCCDTR:
		smoct_param[minor(dev)].sp_ucmdreg &= ~SCCR_DTR;
		smoctcmdreg(unit, line);
		break;
	case TIOCMODG:
		i = TIOCM_LE;
		if (smoct_param[minor(dev)].sp_ucmdreg & SCCR_DTR)
			i |= TIOCM_DTR;
		if (smoct_param[minor(dev)].sp_ucmdreg & SCCR_RTS)
			i |= TIOCM_RTS;
		if (smoct_param[minor(dev)].sp_ucmdreg & SCSR_DSR)
			i |= TIOCM_CTS|TIOCM_DSR;
		if (smoct_param[minor(dev)].sp_ucmdreg & SCSR_DCD)
			i |= TIOCM_CAR;
		*(int *)data = i;
		break;
	case TIOCMODS:
		spl7();
		i = *(int *)data;
		smoct_param[minor(dev)].sp_ucmdreg = smoct_param[minor(dev)].sp_ucmdreg &
			(SCCR_RXEN|SCCR_TXEN) |
			(i & TIOCM_DTR ? SCCR_DTR : 0) |
			(i & TIOCM_RTS ? SCCR_RTS : 0);
		smoctcmdreg(unit, line);
		spl0();
		break;
	case TIOCMGET:		/* not supported */
	case TIOCMSET:
	case TIOCMBIS:
	case TIOCMBIC:
	default:
		return ENOTTY;
	}
	return 0;
}

smoctparam(tp)
	register struct tty *tp;
{
	register i;
	register line = MINORTOLINE(minor(tp->t_dev));
	int unit = MINORTOUNIT(minor(tp->t_dev));
	register struct smoct_param *spp = &smoct_param[minor(tp->t_dev)];
	int oldvpageminor;
	u_short newmodreg;
	int s = spl5();

	i = SCMR1_NORMAL;
	switch (tp->t_ospeed) {
	case B110:
		i |= SCMR1_STOP2;
		break;
	case B134:
		i |= SCMR1_STOP1_5;
		break;
	default:
		i |= SCMR1_STOP1;
		break;
	}
	if (tp->t_flags&(RAW|LITOUT) || (tp->t_flags&ANYP) == 0)
		i |= SCMR1_LEN8;
	else {
		/* both EVENP|ODDP is not supported */
		i |= SCMR1_LEN7|SCMR1_PEN;
		if (tp->t_flags & EVENP)
			i |= SCMR1_PEVEN;
	}
	newmodreg = (i << 8)				/* mr1 */
		| (SCMR2_NORMAL | sc_baud[tp->t_ospeed]);	/* mr2 */

	smoctmodreg(unit, line, spp, newmodreg);
	splx(s);
}

/* Board-specific routines start here.   These all could be statics but I
   want the name to be recognizable in the kernel debugger.
 */
/* set up the whole board; return non-zero if anything fails */
smoctinit(reg, unit)
caddr_t reg;
int unit;
{
	register line;
	register int min;
	register int timer;
	register struct smoct_param *spp;
	register u_char *ioregister;
#ifndef	OLD_VM
	register int vpage;
#endif	OLD_VM
	int oldvpageminor;
	int csr;
	int retry = 0;

	if (smoct_up & (1 << unit)) return 0;/* we've done it already */
	if (smoct_chk & (1 << unit)) return 1; /* Board failed initialization*/
	smoct_chk |= (1 << unit);

	if (smoctdbg & (PRINTFS | CDBGSTART) && unit == 0) {
		printf("smoctinit calls cdebugger:\n");
		cdebugger();
	}

#ifdef	OLD_VM
	if (smoct_vpage == 0) smoct_vpage = kvalloc(2);
#else	OLD_VM
	if (smoct_vpage == 0) smoct_vpage = kvalloc(16);
#endif	OLD_VM

	smoct_ioport[unit] = (u_char *) reg;
kickme:
	/* First write to the I/O register wakes up board and does not
	   represent a command */

	*(smoct_ioport[unit]) = 0x0;	

	flushWriteQueue ();

	/* wait until board is ready: low bit is ready bit */
	/* We also wait in smoctcmdexec to make sure board is ready -- chrisw */
	ioregister = smoct_ioport[unit];
	for (timer = RDYWAIT; --timer >= 0 && ((*ioregister & 0x01) == 0);)
		;
	if (timer < 0) {		/* no answer, try again */
		if (retry == 0) {
			retry ++;
			goto kickme;
		}
		printf ("smoctinit: board never went ready\n");
		return 1;
	}
	if (smoctdbg & RESETDBG)
		printf ("smoctinit: board appears to be ready!\n");

	SETSMOCTBASEPAGE;

#ifdef	OLD_VM
	/* initialize page so it points to minor 0.  See smoctsetvpage(). */
	setpagemap(smoct_vpage,	MINORTOPHYPG(0));
	setpagemap(smoct_vpage + 1,	MINORTOPHYPG(0) + 1);
#else	OLD_VM
	/* initialize page so it points to minor 0.  See smoctsetvpage(). */
	for (vpage = 0; vpage < 16; vpage++) 
		setpagemap(smoct_vpage + vpage,	MINORTOPHYPG(0) + vpage);
#endif	OLD_VM

#ifdef	OLD_VM
	/* This smoct pointer never moves; the virtual address never changes,
	 * but the physical address changes each time we refer to a different
	 * line.  See smoctsetvpage().
	 */
#else	OLD_VM
	/*
	 * The smoct pointer is the base of the eight port data structures
	 * housed in the smoct board.  We must use the line variable to pick
	 * the port we are using.
	 */
#endif	OLD_VM
	smoct  = (struct smoct *) (smoct_vpage << pageshift);


        min = ULTOMINOR(unit, 0);
	for (line = 0, spp = &smoct_param[min];
		line < NSMOCTLINES;
		line++, min++, spp++)
	{
#ifdef	OLD_VM
		/* Most of the defaults are OK.  (All the defaults are
		   0; therefore there is no need to initialize most of
		   the fields of smoct_param[].)
		 */
		oldvpageminor	= smoctsetvpage(min);
#endif	OLD_VM


		/* set mode registers */
		spp->sp_umodreg = 	((SCMR1_NORMAL | SCMR1_STOP1 |
					SCMR1_LEN7 | SCMR1_PEN) << 8)
					| (SCMR2_NORMAL | sc_baud[B9600]);
		if (smoctcmdexec(unit, line, UMODREG, spp->sp_umodreg)) {
			if (retry == 0 && line == 0) {
				retry ++;
				goto kickme;
			} else if (line)
				printf ("Set mode register failed line = %d\n",
					line);
			printf("smoct%d: Set mode register failed!\n", unit);
			return 1;
		}

		/* set command register */
		spp->sp_ucmdreg = SCCR_NORMAL;
		if (smoctcmdexec(unit, line, UCMDREG, spp->sp_ucmdreg)) {
			printf("smoct%d: Set command register failed!\n", unit);
			return 1;
		}

		/* these params get set up in board on first open */
		spp->sp_inpdly = 0x40;		/* determined empirically */
		spp->sp_inphiwat = 512;
		if (smoctdbg & ONEOUTBUF)
			spp->sp_state = BUF1USED;

		/* Set up board level interrupts to Multibus level 5 --
		   this is not the level we use in 68000 but the actual
		   Multibus level -- I got it from the strapping on the
		   board.  Bit 3 of upper byte (1 << 3) means interrupt;
		   low byte is vector (unused).  We use non-bus-vectored
		   interrupts; if we want bus-vectored interrupts we have
		   to turn on bit 7 (1 << 7) of high byte and put something
		   meaningful in low byte for the vector.
		 */
		spp->sp_intoutp =
		spp->sp_intinp =
		spp->sp_intstat =
		spp->sp_intpar =
			smoctintparam;

		if (smoctcmdexec(unit, line, INTINP, spp->sp_intinp)) {
			printf("smoct%d: Set input interrupt level failed!\n", unit);
			return 1;
		}

		if (smoctcmdexec(unit, line, INTOUTP, spp->sp_intoutp)) {
			printf("smoct%d: Set output interrupt level failed!\n", unit);
			return 1;
		}

		if (smoctcmdexec(unit, line, INTSTAT, spp->sp_intstat)) {
			printf("smoct%d: Set status interrupt level failed!\n", unit);
			return 1;
		}

		if (smoctcmdexec(unit, line, INTPAR, spp->sp_intpar)) {
			printf("smoct%d: Set parity interrupt level register failed!\n", unit);
			return 1;
		}

		/* clear the status change interrupt generated by this mess */
		if (smoctcmdexec(unit, line, CLRINT, 0)) {
			printf("smoct%d: Clear interrupt failed!\n", unit);
			return 1;
		}

#ifdef	OLD_VM
		smoctsetvpage(oldvpageminor);
#endif	OLD_VM
	}
	smoct_up |= (1 << unit);
	return 0;
}

/*
 *	Execute a command on this line.  Return error status.
 *
 *	Be careful with this code!  It must still work right (return error)
 *	even if the board is not there!
 */
int
smoctcmdexec(unit, line, cmd, param)
int unit, line;
enum smoct_cmd cmd;
u_short param;
{
	register i;
#ifndef	OLD_VM
	register struct smoct *sp = smoct + line;
#endif	OLD_VM
	register u_char *ioreg = smoct_ioport[unit];

#ifdef	OLD_VM
	smoct->so_cmd = (u_char) cmd;
	smoct->so_cmddata = SWAPSHORT(param);
	smoct->so_cmdstat = 0xff;
#else	OLD_VM
	sp->so_cmd = (u_char) cmd;
	sp->so_cmddata = SWAPSHORT(param);
	sp->so_cmdstat = 0xff;
#endif	OLD_VM

	/* wait until board is ready for command: low bit is ready bit */
	/*
	** This is redundant since we now wait in smoctinit() -- chrisw
	*/
	for (i = RDYWAIT; --i >= 0 && ((*ioreg & 0x01) == 0);)
		;
	if (i < 0) {		/* no answer, give up */
		printf ("smoctcmdexec: board not ready\n");
		return 1;
	}

	*ioreg = BITFLAG(line);

	flushWriteQueue();

	/* board does the command */
#ifdef	OLD_VM
	for (i = STATWAIT; --i >= 0 && (smoct->so_cmdstat == 0xff);)
		;
#else	OLD_VM
	for (i = STATWAIT; --i >= 0 && (sp->so_cmdstat == 0xff);)
		;
#endif	OLD_VM
	if (smoctdbg & RESETDBG)
	{	/* Go for "the long count".  This is for debugging only. */
		int trial = 1;
		while (i < 0 && trial < 20) {
			printf("smoct: failed try #%d\n", trial++);
			for (i = STATWAIT; --i >= 0 && (sp->so_cmdstat == 0xff);)
				;
		}
	}
	if (i < 0) {		/* no answer, give up */
		if (smoctdbg & RESETDBG) {
			printf ("smoctcmdexec: unit=0x%x, line=0x%x, cmd=0x%x, param=0x%x\n", 
			unit, line, cmd, param);
			printf ("smoct=0x%x, sp=0x%x, ioreg=0x%x, cmdstat=0x%x \n", 
			smoct, sp, ioreg, sp->so_cmdstat);
			printf ("sp pte is 0x%x\n", 
			(u_short) getpagemap ((u_long)sp >> pageshift));
			printf ("smoctcmdexec: command never completed\n");
		}
		return 1;
	}

#ifdef	OLD_VM
	return (smoct->so_cmdstat);  /* 0 == good, 1 == bad */
#else	OLD_VM
	return (sp->so_cmdstat);  /* 0 == good, 1 == bad */
#endif	OLD_VM
}

/*
 * Set the UART command register; return status.
 * This routine is just a convenience:  don't call it if the caller
 * is doing smoctsetvpage() itself; instead just call smoctcmdexec()
 * directly.
 */
int
smoctcmdreg(unit, line)
int line;
{
	int min = ULTOMINOR(unit,line);
	struct smoct_param *spp = &smoct_param[min];
	int	oldvpageminor;
	int	r;

#ifdef	OLD_VM
	oldvpageminor	= smoctsetvpage(min);
#endif	OLD_VM

	if (r = smoctcmdexec(unit, line, UCMDREG, spp->sp_ucmdreg))
		printf("smoctcmdreg: bad status return\n");

#ifdef	OLD_VM
	smoctsetvpage(oldvpageminor);
#endif	OLD_VM
	return r;
}

/*
 *	Set mode registers.  This clears some other parameters of the board
 *	so that we have to restore them.  See pp. 24-25 of reference manual.
 */
int
smoctmodreg(unit, line, spp, newmodreg)
int unit;
int line;
struct smoct_param *spp;
u_short newmodreg;
{
#ifdef	OLD_VM
	int oldvpageminor;
#else	OLD_VM
	register struct smoct *sp = smoct + line;
#endif	OLD_VM
	int min;

	min = ULTOMINOR(unit, line);
	if (smoctdbg & CHGPARAM || newmodreg != spp->sp_umodreg) {
		if (smoctdbg & PRINTFS || smoctdbg & CHGPARAM)
			printf("smoct%d reset mode registers\n", min);
#ifdef	OLD_VM
		oldvpageminor	= smoctsetvpage(min);
#endif	OLD_VM
		if (smoctdbg & CHGPARAM) {
			printf("smoct = %x, spp = %x\n", smoct, spp);
			cdebugger();
			printf("\ndelay = %x, hiwat = %x\n",
				spp->sp_inpdly, spp->sp_inphiwat);
		}

		/* Doing UMODREG clears the filling and emptying pointers
		   which throws off all our bookkeeping. */
#ifdef	OLD_VM
		if ((smoct->so_ifilptr != smoct->so_iempptr) 
		   && (smoctdbg & PRINTFS))
#else	OLD_VM
		if ((sp->so_ifilptr != sp->so_iempptr) 
		   && (smoctdbg & PRINTFS))
#endif	OLD_VM
		{
			printf("smoctmodreg -- WARNING: input chars dropped\n");
		}
		spp->sp_umodreg = newmodreg;
		smoctcmdexec(unit, line, UMODREG,	spp->sp_umodreg);
		smoctcmdexec(unit, line, XONMASKEN,	spp->sp_xonmasken);
		smoctcmdexec(unit, line, XONCHARS,	spp->sp_xonchars);
		smoctcmdexec(unit, line, INPDLY,	spp->sp_inpdly);
		smoctcmdexec(unit, line, INPHIWAT,	spp->sp_inphiwat);
		smoctcmdexec(unit, line, INTSTATB0,	spp->sp_intstatb0);
		smoctcmdexec(unit, line, INTSTATB1,	spp->sp_intstatb1);
#ifdef	OLD_VM
		smoctsetvpage(oldvpageminor);
#endif	OLD_VM
	}
	return 0;
}

#ifdef SWAB
/* swap bytes on low-order word of parameter */
asm("	.text");
asm("	.globl swab");
asm("swab:");
	asm("	clrl	d0");
	asm("	movw	sp@(6),d0");
	asm("	rorw	#8,d0");
	asm("	rts");
#endif SWAB

/*  Set up the status change masks; we assume smoctsetpage is done by
 *  the caller.  This is really just a big macro.
 */
smoctsetstat(spp, unit, line, flag)
register struct smoct_param *spp;
int unit, line;
enum intstatflag flag;
{
#ifndef	OLD_VM
	register struct smoct *sp = smoct + line;
#endif	OLD_VM

#ifdef	OLD_VM
	u_char stat = smoct->so_usartstat;
#else	OLD_VM
	u_char stat = sp->so_usartstat;
#endif	OLD_VM
	switch (flag) {
		case STATOFF:
			/* shut up */
			spp->sp_intstatb0 =  0;
			spp->sp_intstatb1 =  0;
			break;
		case STATON:
			/* set to notify of DSR or DCD change */
			spp->sp_intstatb0 =  stat & (SCSR_DSR | SCSR_DCD);
			spp->sp_intstatb1 = ~stat & (SCSR_DSR | SCSR_DCD);
			break;
		case FORCEINTR:
			/* force a match on DCD bit */
			spp->sp_intstatb0 =  stat ^ SCSR_DCD;
			spp->sp_intstatb1 = ~stat ^ SCSR_DCD;
			break;
		default:	break;
	}
	smoctcmdexec(unit, line, INTSTATB0, spp->sp_intstatb0);
	smoctcmdexec(unit, line, INTSTATB1, spp->sp_intstatb1);
}
smoctdummy(){}

#ifdef	OLD_VM
/*
 *  Set virtual pages for minor device and return old minor.
 */
int
smoctsetvpage(min)
int min;
{
	static int vpageminor;
	register int old;
	register int phypg;

	old = vpageminor;
	if (min != vpageminor) {
		vpageminor = min;
		phypg = MINORTOPHYPG(min);
		setpagemap(smoct_vpage,	phypg);
		setpagemap(smoct_vpage + 1,	phypg + 1);
	}
	return (old);
}
#endif	OLD_VM

#ifdef	BUFSTATS
smoctclrstat(statbuf, len)
u_char statbuf[];
int len;
{
	int i;
	u_char	*p;
	p = statbuf;
	for (i = 0; i < len; i++) *p++ = 0;
}



smoctrptstat(statbuf, len)
u_char statbuf[];
int len;
{
	int i, bufs, chars;
	int mode, modecnt, min, max;
	register u_char *p;

	mode = modecnt = chars = bufs = 0;
	max = 0;	min = len;
	p = statbuf;
	for (i = 0; i < len; i++, p++) {
		if (*p > 0) {
			/*	printf("%d\tX %d,\t", i, *p);	*/
			chars += (*p) * i;
			bufs += *p;
			if (*p > modecnt) {
				modecnt = *p;
				mode = i;
			}
			if (min > i) min = i;
			if (max < i) max = i;
		}
	}
	if (chars == 0) {
		printf("\nstat table empty\n");
	} else {
		printf("\n%d chars, %d bufs, min %d, median %d, mode %d, max %d\n",
			chars, bufs, min, chars / bufs, mode, max);
	}
}
#endif	BUFSTATS

#ifdef	TIMESTATS
/* Show delta times in microseconds between events */
smoctdumptimes()
{
	unsigned long *st;
	unsigned int this, last;
	unsigned int diff, difftotal;
	unsigned int diffrtotal, diffrcnt;
	unsigned int diffwtotal, diffwcnt;
	unsigned int lastr, lastw;
	char ch;

	difftotal = last = lastr = lastw = 0;
	diffrtotal = diffrcnt = 0;
	diffwtotal = diffwcnt = 0;
	for (st = &smoct_timebuf[0]; st != smoct_time; st++) {
		DECODETIME(ch,this,*st);
		if (this < last) this += 64000000;  /* overflows in 64 sec. */
		printf("%d", diff = this - last); putchar(ch,0);
		if (last > 0)	difftotal += diff;
		if (ch == 'r') {
			if (lastr > 0) {	/* not the first time */
				diffrtotal += this - lastr;
				diffrcnt++;
			}
			lastr = this;
		} else if (ch == 'w') {
			if (lastw > 0) {	/* not the first time */
				diffwtotal += this - lastw;
				diffwcnt++;
			}
			lastw = this;
		}
		last = this;
	}
	printf("\nDuration of test period %d usec.\n", difftotal);
	if (diffrcnt > 0)
		printf("Mean time between reads %d usec (%d samples).\n",
			diffrtotal / diffrcnt, diffrcnt);
	if (diffwcnt > 0)
		printf("Mean time between writes %d usec (%d samples).\n",
			diffwtotal / diffwcnt, diffwcnt);
}
#endif	TIMESTATS

#ifdef notdef
/* 	The routines after this point are toys for testing and
 *	debugging.  I didn't want to throw them out; they might
 *	be useful someday.
 */


/* Run through I/O page, raping and looting. */
u_char *sfaddr;	
/*ARGSUSED*/
smoctfindem(reg)
	caddr_t reg;
{
	label_t *saved_jb;
extern	label_t *nofault;
	label_t jb;

	if (smoct_ignore)
		return;
	printf("smoctfindem(%x)\n", reg);

	sfaddr = (u_char *) MBIO_VA;
	saved_jb = nofault;
	nofault = &jb;
	if (setjmp(&jb)) {
		sfaddr++;
	}
	for (; sfaddr < (u_char *) (MBIO_VA + 1000); sfaddr++)
	{
		register u_char trash;
		register i;
		trash = *sfaddr;
		printf("read %x ok\n", sfaddr);
		for (i = 100000; i > 0; --i);
	}


	sfaddr = (u_char *) MBIO_VA;
	if (setjmp(&jb)) {
		sfaddr++;
	}
	for (; sfaddr < (u_char *) (MBIO_VA + 1000); sfaddr++)
	{
		register i;
		*sfaddr = (u_char) 0;
		printf("wrote %x ok\n", sfaddr);
		for (i = 100000; i > 0; --i);
	}
	nofault = saved_jb;
}

/*	Dump status information.
 */
int
smoctdump(unit, line)
{
	int min, oldvpageminor;

	min = ULTOMINOR(unit,line);
	oldvpageminor	= smoctsetvpage(min);

	printf("smoct (%x, %x):\n", unit, line);
	printf("\tso_cmdstat = %x\n",	smoct->so_cmdstat);
	printf("\tso_usartstat = %x\n",	smoct->so_usartstat);
	printf("\tso_ifilptr = %x\n",	smoct->so_ifilptr);
	printf("\tso_iempptr = %x\n",	smoct->so_iempptr);
	printf("\tso_rcvovfl = %x\n",	smoct->so_rcvovfl);
	printf("\tso_intsrc = %x\n",	smoct->so_intsrc);
	printf("\tso_obusy = %x\n",	smoct->so_obusy);
	printf("\tso_ostopped = %x\n",	smoct->so_ostopped);
	printf("\tso_parerr = %x\n",	smoct->so_parerr);
	printf("\tso_firmrev = %x\n",	smoct->so_firmrev);
	printf("\tso_portintstat = %x\n",	smoct->so_portintstat);

	smoctsetvpage(oldvpageminor);
}

/*	Throw some junk data out port (0,0) in order to prod it into
 *	doing something.
 */
int
smoctsendblk()
{
	int msglen = 8;
	int min, oldvpageminor, i, r;
	enum smoct_cmd cmd;
	register char *obuf, *p;

	min = 0;
	oldvpageminor	= smoctsetvpage(min);
	obuf = &(smoct->so_obuf0[0]);
	cmd = SENDBLK0;
	p = "Hi, Mom!";

	/* load buffer */
	for (i = msglen; i > 0; i--)
		*XORBIT0(obuf++) = *p++;

	r = smoctcmdexec(0, 0, cmd, msglen);

	smoctsetvpage(oldvpageminor);

	return r;
}

/*	Throw some junk data out port (0,0) in order to prod it into
 *	doing something.
 */
int
smoctsendimmed()
{
	int msglen = 8;
	int min, oldvpageminor, i, r;
	register struct smoct_param *spp = &smoct_param[0];
	register char *p;

	min = 0;
	oldvpageminor	= smoctsetvpage(min);
	p = "Hi, Mom!";

	smoctcmdexec(0, 0, UCMDREG, (SCCR_RTS | SCCR_DTR | SCCR_TXEN));

	for (i = msglen; i > 0; i--)
		smoctcmdexec(0, 0, SENDIMMED, *p++);

	/* restore to the standard thing */
	smoctcmdexec(0, 0, UCMDREG, spp->sp_ucmdreg);

	smoctsetvpage(oldvpageminor);
}
#endif notdef
#endif
