/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION  1986,1987,1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
/* $Header:st.c 12.0$ */
/* $ACIS:st.c 12.0$ */
/* $Source: /ibm/acis/usr/sys/caio/RCS/st.c,v $ */

#if !defined(lint) && !defined(NO_RCS_HDRS)
static char *rcsid = "$Header:st.c 12.0$";
#endif

/*	st.c	6.3	83/09/25	*/

#include "st.h"
#if NST > 0
/*
 * ST11/TE10 tape driver
 *
 * TODO:
 *	test driver with more than one slave
 *	test driver with more than one controller
 *	test reset code
 *	what happens if you offline tape during rewind?
 *	test using file system on tape
 */
#include "../machine/pte.h"

#include "../h/param.h"
#include "../h/systm.h"
#include "../h/buf.h"
#include "../h/dir.h"
#include "../h/conf.h"
#include "../h/user.h"
#include "../h/file.h"
#include "../h/map.h"
#include "../h/vm.h"
#include "../h/ioctl.h"
#include "../h/mtio.h"
#include "../h/cmap.h"
#include "../h/uio.h"
#include "../h/kernel.h"
#include "../machine/debug.h"

#include "../machineio/ioccvar.h"
#include "../machineio/streg.h"

/* Defines to set and get the MMU TCR */
#include        "../machine/mmu.h"
caddr_t real_buf_addr();

#define spl_tape()	_spl4()		/* cpu level 4 */
#define spl_high()	_spl2()		/* at cpu level 2 */

#define ST_CTLR_TIMEOUT	16		/* 16 seconds should be enough */
#define BDBTOFSB( X )	(X)		/* tape block = 512 bytes */
#define STBLOCK_SIZE	512
#define STTIMER_INC	5		/* number of seconds per timeout */
#define STTIMER_REW	(5 * 60)	/* for tape to rewind (2 is typical) */
#define STTIMER_REOF	(10 * 60)	/* for tape to forward space file */
#define STTIMER_SEEK	(1 * 60)	/* for tape to position */
#define STTIMER_NORMAL	60		/* for normal tape operation */

int	stdebug = 0;

/*
 * There is a cstbuf per tape controller.
 * It is used as the token to pass to the internal routines
 * to execute tape ioctls, and also acts as a lock on the slaves
 * on the controller, since there is only one per controller.
 * In particular, when the tape is rewinding on close we release
 * the user process but any further attempts to use the tape drive
 * before the rewind completes will hang waiting for cstbuf.
 */
struct	buf	cstbuf[NST];

/*
 * Raw tape operations use rstbuf.
 */
struct	buf	rstbuf[NST];

/*
 * Driver interface routines and variables.
 */
int	stprobe(), stslave(), stattach(), stint();
char	*stcmdprint();
char	*stccprint();
struct	iocc_ctlr *stminfo[NST];
struct	iocc_device *stdinfo[NST];
struct	buf stutab[NST];
int	printf();
int	uprintf();
int	strestart();

#define STADDR	0xf00001e8
/*struct stdevice *st = (struct stdevice *) STADDR;*/
struct stdevice *staddr = (struct stdevice *) STADDR;
/* write to 0xf00006f4 to reset interrupt */
char *int_reset = (char *) 0xf00006f4;

caddr_t ststd[] = { (caddr_t) STADDR, 0 };

struct iocc_driver stcdriver = { stprobe, stslave, stattach, 0,
	(caddr_t *) ststd, "st", stdinfo, "stc", stminfo, stint, 2 };

/* bits in minor device */
#define	STUNIT(dev)	0
#define	T_NOREWIND	04
#define	T_1600BPI	08

#define	INF	(daddr_t)1000000L

/*
 * Software state per tape transport.
 *
 * 1. A tape drive is a unique-open device; we refuse opens when it is already.
 * 2. We keep track of the current position on a block tape and seek
 *    before operations by forward/back spacing if necessary.
 * 3. We remember if we have been writing so we can write an EOF when
 *		we close.
 * 4. We remember the status registers so we can return them to the
 *		SENSE ioctl if we have been reading or writing (at which time it would
 *		be illegal to do an actual SENSE).
 */
struct	st_softc {
	short	sc_flags;	/* lock against multiple opens */
#define ST_OPEN		0x0001	/* device is open */
#define ST_HARDERR	0x0002	/* hard error - must close first */
#define ST_TIMERR		0x0004	/* error detected via timeout */
#define ST_GOTSTATUS 0x0008	/* stint() has read status bytes */
#define ST_READING	0x0010	/* streaming mode - read in progress */
#define ST_WRITING	0x0020	/* streaming mode - write in progress */
#define ST_STREAMING (ST_READING|ST_WRITING)
#define ST_FIL			0x0040	/* FILe mark reached part way thru last read
										 * should return 0 bytes on next read
										 */
#define ST_UNMOVED	0x0080	/* tape hasn't moved since the open - so
										 * we should ignore one FIL on a read
										 */
	short	sc_timo;	/* time until timeout expires */
	short	sc_lasttimo;	/* length of time of last timeout */
	short	sc_tact;	/* timeout is active */
	daddr_t	sc_blkno;	/* block number, relative to position at open */
	daddr_t	sc_nxrec;	/* position of end of tape, if known */
#define ST_TAPEMAX	999999
	long	sc_resid;	/* residual count - for partly completed ops */
	caddr_t st_physaddr;	/* the real memory address */
	int	sc_blkcnt;	/* # of blocks to transfer */
	daddr_t	sc_openblk;	/* block number, absolute position at open */
	char	sc_data[8];	/* data bytes returned from read status */
	char	sc_lastcmd;	/* last command */
	char	sc_fill[27];	/* fill to power of two */
} st_softc[NST];

#ifdef DEBUG
#define ST_TRACE
#define ST_MAX_TRACE (128 * 8)
char	st_tracebuff[ST_MAX_TRACE];	/* a trace buffer for debugging */
int	stsoftlen = sizeof (struct st_softc);
int	st_trace = 0;	/* subscript into trace buffer */
#endif

/*
 * States for ic->ic_tab.b_active, the per controller state flag.
 * This is used to sequence control in the driver.
 */
#define	SINACTIVE 0	/* "doing nothing" */
#define	SSEEK	1		/* seeking */
#define	SIO	2		/* doing seq i/o */
#define	SCOM	3		/* sending control command */
#define	SREW	4		/* sending a drive rewind */
#define	SWAIT	5		/* waiting for drive ready */

/*
 * Determine if there is a controller for
 * a st at address reg.  Our goal is to make the
 * device interrupt.
 */
/*ARGSUSED*/
stprobe(reg)
	caddr_t reg;
{
	DEBUGF( (stdebug&SHOW_INIT),
		printf("stprobe: entered\n"));

	return(PROBE_NOINT);
}

/*
 * Due to a design flaw, we cannot ascertain if the tape
 * exists or not unless it is on line - ie: unless a tape is
 * mounted. This is too servere a restriction to bear,
 * so all units are assumed to exist.
 */
/*ARGSUSED*/
stslave(iod, reg)
	struct iocc_device *iod;
	caddr_t reg;
{
	DEBUGF( (stdebug&SHOW_INIT),
		printf("stslave: entered\n"));

	return (1);
}

/*
 * Record attachment of the unit to the controller.
 */
/*ARGSUSED*/
stattach(iod)
	struct iocc_device *iod;
{
	DEBUGF( (stdebug&SHOW_INIT),
		printf("stattach: entered\n"));
	return (1);
}

int	sttimer();

int	stadapt_init = 0;	/* 0 means adapter needs to be reset */
int	stopen_timeout = 1000000;	/* one second */

/*
 * Open the device.  Tapes are unique open
 * devices, so we refuse if it is already open.
 * We also check that a tape is available, and
 * don't block waiting here; if you want to wait
 * for a tape you should timeout in user code.
 */
stopen(dev, flag)
	dev_t dev;
	int flag;
{
	register struct iocc_device *iod;
	register struct st_softc *sc;
	register int s;
	register struct stdevice *addr;
	int status;
	int rtn = 0;

	DEBUGF( (stdebug&SHOW_ORDWR),
		printf("stopen: unit=%d\n", STUNIT(dev)));

	if (STUNIT(dev) >= NST ||
		 (sc = &st_softc[STUNIT(dev)])->sc_flags & ST_OPEN ||
	    (iod = stdinfo[STUNIT(dev)]) == 0 || iod->iod_alive == 0) {
		return (ENXIO);
	}

	addr = (struct stdevice *) iod->iod_addr;

	DEBUGF( (stdebug&SHOW_ORDWR),
		printf("stopen: after ENXIO; sc=0x%x iod=0x%x\n", sc, iod));

	if (stadapt_init) {
		if (st_wait(dev)) {
			uprintf("st%d: not ready (hung rewinding?)\n", STUNIT(dev));
			rtn = EIO;
		} else {
			status = addr->stcs;
			DEBUGF( (stdebug&SHOW_ORDWR),
				printf("stopen: status=0x%b\n", status, STCFMT));
			if (!(status&(STC_ONL|STC_RDY))) {
				if (status == 0)
					uprintf("st%d: not ready (drive turned off?)\n", STUNIT(dev));
				else
					uprintf("st%d: not ready\n", STUNIT(dev));
				rtn = EIO;
			}
		}
	} else {
		register int timeout = 0;

		sc->sc_openblk = sc->sc_blkno = (daddr_t)0;
		if (!streset()) {
			/* now verify initialization went ok */
			timeout = stopen_timeout;
			while (!(sc->sc_flags&ST_GOTSTATUS) && --timeout) {
				DELAY(1);
			}
		}
		status = addr->stcs;
		DEBUGF( (stdebug&SHOW_ORDWR),
			printf("stopen: timeout=%d status=0x%b\n", timeout, status, STCFMT));
		if (!timeout) {
			if (status == 0x00)
				uprintf("st%d: not ready (drive turned off?)\n", STUNIT(dev));
			else
				uprintf("st%d: not ready\n", STUNIT(dev));
			rtn = EIO;
		}
	}
	if (rtn) {
		stadapt_init = 0;	/* try reset again - next time thru */
		return(rtn);
	}
	if (!(sc->sc_flags & ST_STREAMING)) {
		/*
		 * if we weren't streaming we update our status
		 * (if we were then we haven't changed tapes so
		 * it is safe to test WRP w/o the SENSE)
		 */
		stcommand(dev, ST_SENSE, 1);
	}
	if (flag&FWRITE && sc->sc_data[0] & ST0_WRP) {
		uprintf("st%d: write protected cartridge\n", STUNIT(dev));
		return(EIO);
	}

#ifdef ST_TRACE
	st_trace = 0;	/* restart tracing on good opens */
#endif

	sc->sc_flags &= ST_STREAMING;
	sc->sc_flags |= ST_OPEN|ST_UNMOVED;
	sc->sc_openblk += sc->sc_blkno;
	sc->sc_blkno = 0;
	sc->sc_nxrec = ST_TAPEMAX;
	s = spl_tape();
	if (sc->sc_tact == 0) {
		sc->sc_lasttimo = sc->sc_timo = 0;	/* no timeout in progress */
		sc->sc_tact = 1;
		timeout(sttimer, (caddr_t) (int) dev, STTIMER_INC*hz);
	}
	splx(s);
	DEBUGF((stdebug&SHOW_ORDWR), printf("stopen: done (openblk=%d)\n", sc->sc_openblk));
	return (0);
}

/*
 * Close tape device.
 *
 * If tape was open for writing or last operation was
 * a write, then write 2 EOFs.  Then backspace over the second
 * one if we aren't going to rewind.
 * Unless this is a non-rewinding special file, rewind the tape.
 * Make the tape available to others.
 */
stclose(dev, flag)
	register dev_t dev;
	register flag;
{
	register struct st_softc *sc = &st_softc[STUNIT(dev)];

	DEBUGF( (stdebug&SHOW_ORDWR), printf("stclose: entered\n"));

	if (flag == FWRITE || flag&FWRITE && sc->sc_flags&ST_WRITING) {

		DEBUGF( (stdebug&SHOW_ORDWR), printf("stclose: writing 2 EOFs\n"));
		
		stcommand(dev, ST_WEOF, 2);
		if ((minor(dev)&T_NOREWIND)) {

			DEBUGF( (stdebug&SHOW_ORDWR), printf("stclose: backspacing\n"));

			stcommand(dev, ST_SREV, 1);
		}
	}
	if ((minor(dev)&T_NOREWIND)) {
		sc->sc_flags &= ST_STREAMING;
	} else {

		DEBUGF( (stdebug&SHOW_ORDWR), printf("stclose: rewinding\n"));
		/*
		 * 0 count means don't hang waiting for rewind complete
		 * rather cstbuf stays busy until the operation completes
		 * preventing further opens from completing by
		 * preventing a ST_SENSE from completing.
		 */
		stcommand(dev, ST_TERMINATE, 0);
		sc->sc_flags = 0;
	}
}

/*
 * Execute a command on the tape drive
 * a specified number of times.
 * result  = 0	all ok
 * result != 0	error flag set
 */
stcommand(dev, com, count)
	dev_t dev;
	int com, count;
{
	register struct buf *bp;
	register int s;
	register struct st_softc *sc = &st_softc[STUNIT(dev)];
	register int result = 0;

	DEBUGF( (stdebug&SHOW_ENQ),
		printf("stcommand: cmd=0x%x(%s) count=%d\n", com,
			stcmdprint(com), count));

	if (sc->sc_flags & ST_HARDERR)
		return(1);			/* hard error - just return */
	bp = &cstbuf[STUNIT(dev)];
	s = spl_tape();
	while (bp->b_flags&B_BUSY) {
		/*
		 * This special check is because B_BUSY never
		 * gets cleared in the non-waiting rewind case.
		 */
		if (bp->b_bcount == 0 && (bp->b_flags&B_DONE))
			break;
		bp->b_flags |= B_WANTED;
		DEBUGF( (stdebug&SHOW_SLEEP),
			printf("stcommand: sleeping\n"));
		sleep((caddr_t)bp, PRIBIO);
	}
	if (sc->sc_flags & ST_HARDERR) {
		splx(s);
		return(1);			/* hard error - just return */
	}
	bp->b_flags = B_BUSY|B_READ;
	splx(s);
	bp->b_dev = dev;
	bp->b_bcount = count;
	bp->b_command = com;
	bp->b_blkno = 0;
	ststrategy(bp);
	/*
	 * In case of rewind from close, don't wait.
	 * This is the only case where count can be 0.
	 */
	if (count == 0)
		return(0);			/* pretend we are ok */
	iowait(bp);
	if (bp->b_flags&B_WANTED)
		wakeup((caddr_t)bp);
	result = bp->b_flags & B_ERROR;
	bp->b_flags &= B_ERROR;		/* B_ERROR, *not* ~B_ERROR */
	return(result);
}

/*
 * Queue a tape operation.
 */
ststrategy(bp)
	register struct buf *bp;
{
	register struct iocc_ctlr *ic;
	register struct buf *dp;
	register int s;

	DEBUGF( (stdebug&SHOW_ENQ),
		printf("ststrategy: entered bp=0x%x\n", bp));

	/*
	 * Put transfer at end of unit queue
	 */
	s = spl_tape();
	dp = &stutab[STUNIT(bp->b_dev)];
	bp->av_forw = NULL;
	ic = stdinfo[STUNIT(bp->b_dev)]->iod_mi;
	if (dp->b_actf == NULL) {
		dp->b_actf = bp;
		/*
		 * Transport not already active...
		 * put at end of controller queue.
		 */
		dp->b_forw = NULL;
		if (ic->ic_tab.b_actf == NULL)
			ic->ic_tab.b_actf = dp;
		else
			ic->ic_tab.b_actl->b_forw = dp;
		ic->ic_tab.b_actl = dp;
	} else {
		dp->b_actl->av_forw = bp;

		DEBUGF( (stdebug&SHOW_ENQ),
			printf("ststrategy: request queued; device busy\n"));
	}
	dp->b_actl = bp;

	/*
	 * If the controller is not busy, get
	 * it going.
	 */

	if (ic->ic_tab.b_active == SINACTIVE) {
		ststart(ic);
	}

	DEBUGF( (stdebug&SHOW_ENQ), printf("ststrategy: done\n"));
	splx(s);
}

/*	
 * Start activity on an st controller.
 */
ststart(ic)	/* CALLED FROM INTERRUPT LEVEL */
	register struct iocc_ctlr *ic;
{
	register struct buf *bp, *dp;
	register struct stdevice *addr;
	register struct st_softc *sc;
	int cmd;
	daddr_t blkno;

	DEBUGF( (stdebug&SHOW_DEQ),
		printf("ststart: staddr=0x%x ic_addr=0x%x\n", staddr, ic->ic_addr));

	/*
	 * Look for an idle transport on the controller.
	 */
loop:
	if ((dp = ic->ic_tab.b_actf) == NULL)
		return;
	if ((bp = dp->b_actf) == NULL) {
		ic->ic_tab.b_actf = dp->b_forw;
		goto loop;
	}

	DEBUGF( (stdebug&SHOW_DEQ),
		printf(" with bp->cmd=0x%x(%s) B_READ=%d bp=0x%x\n",
			bp->b_command, stcmdprint(bp->b_command), bp->b_flags&B_READ, bp));

	/* initialization needed for STUNLOAD */
#define NO_ADDR 0			  /* flag for no real address yet */
	st_softc[0].st_physaddr = NO_ADDR; /* mark as not translated */
	dp->b_un.b_addr = bp->b_un.b_addr;	/* set current address */

	/*
	 * Record pre-transfer status (e.g. for ST_SENSE)
	 */
	sc = &st_softc[STUNIT(bp->b_dev)];
	addr = (struct stdevice *)ic->ic_addr;

	if (sc->sc_flags & ST_HARDERR) {
		/*
		 * Have had a hard error on a non-raw tape
		 * or the tape unit is now unavailable
		 * (e.g. taken off line).
		 */
		bp->b_flags |= B_ERROR;
		DEBUGF( (stdebug&SHOW_DEQ), printf("ststart: hard error; dequeuing\n"));
		goto next;
	}

	/* control commands */
	if (bp == &cstbuf[STUNIT(bp->b_dev)]) {
		DEBUGF( (stdebug&SHOW_DEQ), printf("ststart: have control cmd\n"));
		/*
		 * Execute control operation with the specified count.
		 */
		/*
		 * Set next state; give time to complete
		 * rewind, or 10 seconds per iteration (minimum 60
		 * seconds and max 8 minutes) to complete other ops.
		 */
		ic->ic_tab.b_active = SCOM;
		sc->sc_lasttimo = sc->sc_timo = STTIMER_NORMAL;

		switch(cmd = bp->b_command) {
		case ST_SENSE:
			break;
		case ST_REW:
		case ST_RETENSION:
		case ST_ERASE:
		case ST_TERMINATE:
			ic->ic_tab.b_active = SREW;
			sc->sc_lasttimo = sc->sc_timo = STTIMER_REW;
 			sc->sc_blkcnt = 0;
			break;
		case ST_SREV:
			cmd = ST_ADAP_PASS1;
			sc->sc_blkcnt = ST_SREV;
			sc->sc_lasttimo = sc->sc_timo = STTIMER_SEEK;
			break;
		case ST_SFORW:
			sc->sc_blkcnt = 1;
			sc->sc_lasttimo = sc->sc_timo = STTIMER_SEEK;
			break;
		case ST_REOF:
			cmd = ST_SFORW;
			sc->sc_blkcnt = 1;
			sc->sc_lasttimo = sc->sc_timo = STTIMER_REOF;
			break;
		/* if Writing EOF's, then load the number of EOF's to write */
		case ST_WEOF:
			sc->sc_blkcnt = 1;
			break;
		}
	} else {
		/*
		 * The following checks handle boundary cases for operation
		 * on non-raw tapes.  On raw tapes the initialization of
		 * sc->sc_nxrec by stphys causes them to be skipped normally
		 * (except in the case of retries).
		 */
		if (BDBTOFSB(bp->b_blkno + sc->sc_openblk) > sc->sc_nxrec) {
			/*
			 * Can't read past known end-of-file.
			 */
			bp->b_flags |= B_ERROR;
			bp->b_error = ENXIO;
			DEBUGF( (stdebug&SHOW_DEQ), printf("ststart: past EOF; b_blkno=%d sc_openblk=%d sc_nxrec=%d\n", bp->b_blkno, sc->sc_openblk, sc->sc_nxrec));
			goto next;
		}
		if (bp->b_flags&B_READ &&
		    (BDBTOFSB(bp->b_blkno + sc->sc_openblk) == sc->sc_nxrec ||
		     sc->sc_flags&ST_FIL)) {
			/*
			 * Reading at end of file returns 0 bytes.
			 */
			sc->sc_flags &= ~ST_FIL;
			sc->sc_resid = bp->b_bcount;
			clrbuf(bp);
			bp->b_resid = sc->sc_resid;
			DEBUGF( (stdebug&SHOW_DEQ), printf("ststart: at EOF; b_blkno=%d sc_openblk=%d sc_nxrec=%d b_resid=%d\n", bp->b_blkno, sc->sc_openblk, sc->sc_nxrec, bp->b_resid));
			goto next;
		}
	
		if (!(bp->b_flags&B_READ)) {
			/*
			 * Writing sets EOF
			 */
			sc->sc_nxrec = BDBTOFSB(bp->b_blkno + sc->sc_openblk) +
				       bp->b_bcount/STBLOCK_SIZE;
		}
	
		DEBUGF( (stdebug&SHOW_ENQ), 
			printf("ststart: checking position; blkno=%d BDBTOFSB()=%d\n", sc->sc_blkno, BDBTOFSB(bp->b_blkno)));
	
		/*
		 * If in the correct place to do the data transfer,
		 * set up all the adapter register and start the operation.
		 */
		if (BDBTOFSB(blkno = sc->sc_blkno) == BDBTOFSB(bp->b_blkno)) {
			if (bp->b_flags & B_READ) {
				cmd = ST_RCOM;
				DEBUGF( (stdebug&SHOW_ENQ),
					printf("ststart: ST_RCOM\n"));
			} else {
				cmd = ST_WCOM;
				DEBUGF( (stdebug&SHOW_ENQ),
					printf("ststart: ST_WCOM\n"));
			}
			ic->ic_tab.b_active = SIO;
			sc->sc_lasttimo = sc->sc_timo = STTIMER_SEEK;	/* premature, but should serve */
			sc->sc_blkcnt = howmany(bp->b_bcount, STBLOCK_SIZE);
			DEBUGF( (stdebug&SHOW_DEQ),
				printf("ststart: pos ok; blkcnt=%d cmd=0x%x blkno=%d\n",
					sc->sc_blkcnt, cmd, blkno));
			sc->sc_lasttimo = sc->sc_timo = STTIMER_NORMAL;
		} else {
			register int at = BDBTOFSB(sc->sc_openblk + blkno),
				     to = BDBTOFSB(sc->sc_openblk+bp->b_blkno);
			/*
			 * Tape positioned incorrectly;
			 * seek forwards or backwards to the correct spot.
			 * This happens for raw tapes only on error retries.
			 */
			ic->ic_tab.b_active = SSEEK;
			sc->sc_lasttimo = sc->sc_timo = STTIMER_SEEK;
			if (at <= to) {
				cmd = bp->b_command = ST_SFORW;
				sc->sc_blkcnt = 1;
				DEBUGF( (stdebug&SHOW_DEQ), printf("ststart: ST_SFORW for 1 block; at relative %d going to %d\n", BDBTOFSB(blkno), BDBTOFSB(bp->b_blkno)));
			/*
			 * The formulas (derived from testing) used to decide
			 * whether to backspace or rewind and forward space are:
			 *	seconds to backspace = 3.8 * (at - to)
			 *	seconds to rew/forw = 3.2 + (.006 * (at + to))
			 * Multiply these by 500 and divide by three to get:
			 */
			} else if (633 * (at - to) > 533 + at + to) {
				sc->sc_lasttimo = sc->sc_timo = STTIMER_REW;
				cmd = bp->b_command = ST_TERMINATE;
 				sc->sc_blkcnt = 0;
				DEBUGF( (stdebug&SHOW_DEQ), printf("ststart: ST_REW %d blocks; at relative %d going to %d\n", at, BDBTOFSB(blkno), BDBTOFSB(bp->b_blkno)));
			} else {
				bp->b_command = ST_SREV;
				cmd = ST_ADAP_PASS1;
				sc->sc_blkcnt = ST_SREV;
				DEBUGF( (stdebug&SHOW_DEQ), printf("ststart: ST_SREV for 1 block; at relative %d going to %d\n", BDBTOFSB(blkno), BDBTOFSB(bp->b_blkno)));
			}
		}
	}
	sc->sc_lastcmd = cmd;

	if (sc->sc_lastcmd != ST_SENSE) {
		sc->sc_flags &= ~ST_FIL;
	}

	/*
	 * do the command in sc->sc_lastcmd.
	 */

	DEBUGF( (stdebug&SHOW_ENQ),
		printf("ststart: cmd=0x%x(%s) b_count=%d\n", sc->sc_lastcmd,
			stcmdprint(sc->sc_lastcmd), bp->b_bcount));

	addr->stcmd = sc->sc_lastcmd;

	return;

next:
	DEBUGF( (stdebug&SHOW_DEQ), printf("ststart: at next label\n"));
	/*
	 * Done with this operation due to error or
	 * the fact that it doesn't do anything.
	 * Dequeue the transfer and continue processing this slave.
	 */
	ic->ic_tab.b_errcnt = 0;
	dp->b_actf = bp->av_forw;
	iodone(bp);
	sc->sc_timo = 0;		/* kill timeout */
	ic->ic_tab.b_active = SINACTIVE;		/* not active */
	goto loop;

/*
 * come here if the tape controller is not ready. 
 * bump an error counter and if it is not ready after several retries then
 * we will give up and indicate an error
 * this mechanism is used because although the tape drive has completed
 * the previous operation it is not ready to accept the next operation
 * (as indicated by READY), in many cases, until over a second later!
 */
notready:
	if (++ic->ic_tab.b_errcnt > ST_CTLR_TIMEOUT * 16) {
		printf("st%d: controller not ready\n", STUNIT(bp->b_dev));
		sc->sc_flags |= ST_HARDERR;
		bp->b_flags |= B_ERROR;
		ic->ic_tab.b_active = SINACTIVE;		/* not active */
		goto next;
	}
	DEBUGF( (stdebug&SHOW_TIMER),
		printf("ststart: not ready; timeout started\n"));
	timeout(strestart, ic, hz / 16);	/* check again in 1/16 second */
	ic->ic_tab.b_active = SWAIT;		/* waiting for ready */
	return;
}

strestart(ic)
	register struct iocc_ctlr *ic;
{
	register int s = spl_tape();

	DEBUGF( (stdebug&SHOW_TIMER),
		printf("strestart: calling ststart\n"));
	ststart(ic);
	splx(s);
}

/*
 * ST interrupt routine.
 */
stint(stnum)	/* CALLED FROM INTERRUPT LEVEL */
	int stnum;
{
	struct buf *dp;
	register struct buf *bp = NULL;
	register struct iocc_ctlr *ic = stminfo[0];
	register struct stdevice *addr = (struct stdevice *)stdinfo[0]->iod_addr;
	register struct st_softc *sc = &st_softc[0];
	register state;
	unsigned char rc, status;
	int		leftover = 0;
	static	cmd_rejected = 0;	/* boolean state flag */
	static	sense_exception = 0;	/* boolean state flag */

	status = addr->stcs;
	rc = addr->stdata;

#ifdef ST_TRACE
	{ register int t = st_trace;
	st_tracebuff[t] = sc->sc_lastcmd ? sc->sc_lastcmd : ST_CMD_AA;
	st_tracebuff[t+1] = status;
	st_tracebuff[t+2] = rc;
	st_tracebuff[t+3] = time.tv_sec;
	st_trace = t+4 & ST_MAX_TRACE-1;
	}
#endif

	DEBUGF( (stdebug&SHOW_INTR),
		printf("stint: entered status=0x%b rc=0x%x(%s) lastcmd=0x%x(%s)\n",
			status, STCFMT, rc, stccprint(rc), sc->sc_lastcmd,
			stcmdprint(sc->sc_lastcmd)));

	if ((status & (STC_INT|STC_IBF)) == 0 && stnum != -1) {
		return(1);		/* apparently not our interrupt */
	}

	if ((dp = ic->ic_tab.b_actf) != NULL) {
		bp = dp->b_actf;
	}

	DEBUGF( (stdebug&SHOW_INTR), printf("stint: switch(rc) bp=0x%x cmd_rejected=%d sense_exception=%d sc_blkno=%d sc_openblk=%d\n", bp, cmd_rejected, sense_exception, sc->sc_blkno, sc->sc_openblk));

	switch (rc) {
	case CC_DONE:
		sc->sc_flags &= ~ST_UNMOVED;
		switch (sc->sc_lastcmd) {
			case ST_REW:
			case ST_ERASE:
			case ST_RETENSION:
			case ST_TERMINATE:
 				sc->sc_blkno =  -sc->sc_openblk;
				/* we'll be done after updating our copy of status bytes */
				addr->stcmd = ST_SENSE;
				break;
			default: /* all done - see if anything else to do */
				/* TODO (check blkcnt to see if really all done) */
				goto opdoop;
		}
		break;
	case CC_LD_COUNT:
		DEBUGF( (stdebug&SHOW_COUNT),
			printf("stint: CC_LD_COUNT %d\n", sc->sc_blkcnt));

		/* we can only do 256 blocks at a time */
		if (sc->sc_blkcnt > 255) {
			addr->stdata = 0;		/* 0 actually does 256 */
			sc->sc_blkcnt -= 256;
		} else {
			addr->stdata = sc->sc_blkcnt;
			sc->sc_blkcnt = 0;
		}

		switch (sc->sc_lastcmd) {
			case ST_WCOM:
				sc->sc_flags |= ST_WRITING;
				stunload(bp, dp, 0, staddr);
				sc->sc_blkno++;
				break;
			case ST_RCOM:
			case ST_SFORW:
				sc->sc_flags |= ST_READING;
				break;
			case ST_WEOF:
				sc->sc_flags &= ~ST_STREAMING;
				break;
			case ST_ADAP_PASS1:
			case ST_ADAP_PASS2:
				break;
			case ST_REOF:
			default:
				printf("st: unexpected CC_LD_COUNT lastcmd=0x%x(%s)\n",
					sc->sc_lastcmd, stcmdprint(sc->sc_lastcmd));
				break;
		}
		if (sc->sc_blkcnt != 0) {
			/* TODO - test - does this always work? */
			bp->b_bcount = sc->sc_blkcnt;
			goto opcont;
		}
		break;
	case CC_POR_AA:
		addr->stcmd = ST_CMD_AA; /* hand shake */
		break;
	case CC_RD_STATUS:
	{
		int cmd_rej = cmd_rejected;
		int sense_xcep = sense_exception;

		cmd_rejected = 0;	
		sense_exception = 0;
		if (!stget_status()) {
			int need_retry = 0;	/* boolean "should we retry operation?" */

			sc->sc_flags |= ST_GOTSTATUS;	/* alerts stopen initialization ok */
			if (!sense_xcep)
				goto opdoop;
			/* The order we check conditions below *is* significant */
			if (sc->sc_data[0] & ST0_CNI) {
				if (cmd_rej && ic->ic_tab.b_errcnt == 0) {
					need_retry = 1;
					ic->ic_tab.b_errcnt++;
				} else {
					sc->sc_flags |= ST_HARDERR;
				}
			} else if (sc->sc_data[0] & ST0_EOM) {
				sc->sc_flags |= ST_HARDERR;
			} else if (sc->sc_data[0] & ST0_FIL) {
				if (sc->sc_lastcmd == ST_RCOM) {
					if (cmd_rej && ic->ic_tab.b_errcnt == 0 &&
						 sc->sc_flags&ST_UNMOVED) {
						need_retry = 1;
						ic->ic_tab.b_errcnt++;
					} else {
						sc->sc_nxrec = BDBTOFSB(bp->b_blkno + sc->sc_openblk);
						leftover = bp->b_bcount - (sc->sc_blkno+sc->sc_openblk-sc->sc_nxrec << DEV_BSHIFT);
						if (leftover < bp->b_bcount) { /* partial read */
							sc->sc_flags |= ST_FIL;	/* force EOF on next read */
						}
					}
				} else if (bp->b_command == ST_REOF) {
					bp->b_bcount--;
					sc->sc_blkno--;
				} else if (sc->sc_lastcmd == ST_SFORW) {
					/*
					 * We move 1 block at a time.  (If we go
					 * more we can't tell where we are if we
					 * hit a filemark!)  Thus we can set
					 * leftover to a boolean.
					 */
					leftover = cmd_rej;
					/*
					 * The following if allows us to use
					 * rew followed by forw to backspace.
					 */
					if (sc->sc_blkno <= 0 &&
					    ic->ic_tab.b_active == SSEEK) {
						sc->sc_blkno--;
					} else {
						bp->b_flags |= B_ERROR;
						bp->b_error = EIO;
					}
				} else if (sc->sc_lastcmd == ST_ADAP_PASS1 &&
					   bp->b_command == ST_SREV) {
					/*
					 * Backspace always 1 at a time too.
					 */
					leftover = cmd_rej;
					/*
					 * Circumvent hardware bugs
					 * in backspace positioning.
					 */
					if (++ic->ic_tab.b_errcnt == 1)
						need_retry = 1;
					if (cmd_rej) {
						sc->sc_blkno++;
						bp->b_bcount++;
					} else {
						bp->b_flags |= B_ERROR;
						bp->b_error = EIO;
					}
				} else if (!cmd_rej) {	/* "can't happen" */
					printf("st: unexpected FIL; unrejected lastcmd=0x%x(%s)\n",
						sc->sc_lastcmd, stcmdprint(sc->sc_lastcmd));
					sc->sc_flags |= ST_HARDERR;
				} else switch (sc->sc_lastcmd) {
					case ST_REW:
					case ST_ERASE:
					case ST_RETENSION:
					case ST_TERMINATE:
						need_retry = 1;
						break;
					case ST_WCOM:
						if (++ic->ic_tab.b_errcnt == 1) {
							need_retry = 1;
							break;
						} /* else fall through for error */
					default:	/* "can't happen" */
						printf("st: unexpected FIL; rejected lastcmd=0x%x(%s)\n",
							sc->sc_lastcmd, stcmdprint(sc->sc_lastcmd));
						sc->sc_flags |= ST_HARDERR;
						break;
				}
			} else if (sc->sc_data[0] & ST0_WRP) {
				if (sc->sc_lastcmd == ST_ERASE || sc->sc_lastcmd == ST_WCOM ||
					 sc->sc_lastcmd == ST_WEOF) {
					/* "can't happen" due to check in stopen() */
					sc->sc_flags |= ST_HARDERR;
				} /* else ignore it */
			} else if (sc->sc_data[1] & ST1_POR) {
				/* ignore it */
			} else if (sc->sc_data[0] & (ST0_USL|ST0_UDA|ST0_BNL) ||
						  sc->sc_data[1] & (ST1_ILL|ST1_NTD|ST1_BUS)) {
				sc->sc_flags |= ST_HARDERR;
			} /* else ignore it */
			if (sc->sc_flags & ST_HARDERR) {
				stextendstatus(printf);
				if (bp != NULL) {
					bp->b_flags |= B_ERROR;
					/* make leftover non zero (and possibly even correct!) */
					/* TODO (buggy?) */
					leftover = (sc->sc_blkcnt && sc->sc_blkcnt < bp->b_bcount) ?
								  sc->sc_blkcnt : bp->b_bcount ? bp->b_bcount : 1;
				}
				goto opdone;
			}
			if (need_retry)
				goto opcont;
			goto opdoop;
		} 
		/* we're in sad shape if stget_status() failed */
		printf("st: stget_status() failed; stcs=0x%b lastcmd=0x%x(%s)\n",
			status, STCFMT, sc->sc_lastcmd, stcmdprint(sc->sc_lastcmd));
		if (stadapt_init)
			(void)streset();	/* init adapter (handshake) */
		break;
	}
	case CC_READ: 
	case CC_WRITE:
		stunload(bp, dp, 0, staddr);
		sc->sc_blkno++;
		sc->sc_timo = sc->sc_lasttimo;	/* reset timeout */
		break;
	case CC_READ_LAST:
		sc->sc_flags &= ~ST_UNMOVED;
		stunload(bp, dp, 0, staddr);
		sc->sc_blkno++;
		goto opdoop;
	case CC_POR:
	case CC_CMD_REJ_EXCEPT:
	case CC_EXCEPTION:
		cmd_rejected = (rc == CC_CMD_REJ_EXCEPT); /* for later analysis */
		sense_exception = (rc != CC_POR);
		addr->stcmd = ST_SENSE;
		/* continue - analysis will be done above (case CC_RD_STATUS) */
		break;
	case CC_NOTREADY:
	case CC_READY:
	case CC_PARITY:
	case CC_DIRECTION:
	case CC_FPLS:
	case CC_ROSERR:
	case CC_RAMERR:
	case CC_FPLSERR:
	case CC_DRVERR:
	case CC_BUSERR:
		printf("st: apparent adapter/drive failure rc=0x%x(%s)\n",
			rc, stccprint(rc));
		if (stadapt_init)
			(void)streset();	/* init adapter (handshake) */
		break;
	case CC_CMD_REJ_INVAL:
	case CC_REJ_SEQUENCE:
		/* TODO (reset is overkill, above 2 need only HARDERR) */
	default:
		printf("st: unknown condition code rc=0x%x(%s)\n",
			rc, stccprint(rc));
		if (stadapt_init)
			(void)streset();	/* init adapter (handshake) */
		break;
	}
		goto opreset; /* reset interrupt and continue */

opdoop:
	/*
	 * An command completed... record status
	 * and see if we need to continue a multi command operation
	 */
	sc->sc_timo = 0;
	state = ic->ic_tab.b_active;
	ic->ic_tab.b_active = SINACTIVE;

	/*
	 * Advance tape control FSM.
	 */
	DEBUGF( (stdebug&SHOW_INTR),
		printf("stint: switch(state=%d) sc_blkno=%d\n", state, sc->sc_blkno));

	switch (state) {

	case SSEEK:
		if (bp != NULL) {
			if (bp->b_flags & B_ERROR) {
				/* we never even got transfer started! */
				leftover = bp->b_bcount;
				goto opdone;
			}
			switch (bp->b_command) {
				case ST_SFORW:
					sc->sc_blkno += 2;
				case ST_SREV:
					sc->sc_blkno--;
				case ST_TERMINATE:
					goto opcont;
			}
		}
		printf("st: SSEEK assertion failed\n");
		break;
	case SCOM:
		/*
		 * For forward/backward space record update current position.
		 */
		if (bp != NULL && bp == &cstbuf[STUNIT(bp->b_dev)]) {
			switch (bp->b_command) {
				case ST_SFORW:
					sc->sc_blkno += 2;
				case ST_SREV:
					sc->sc_blkno--;
					bp->b_bcount--;
					if (bp->b_flags & B_ERROR) {	/* non HARD errors come here */
						leftover += bp->b_bcount;
					} else if (bp->b_bcount > 0)
						goto opcont;
					break;
				case ST_WEOF:
					if (--bp->b_bcount > 0)
						goto opcont;
					break;
				case ST_REOF:
					sc->sc_blkno++;
					if (bp->b_bcount > 0)
						goto opcont;
			}
			goto opdone;
		}
		printf("st: SCOM assertion failed\n");
		break;
	case SINACTIVE:
	case SIO: /* (read/write increments block number) */
	case SREW:
	case SWAIT:
		break;

	default:
		printf("st: unknown state 0x%x\n", state);
		break;
	}
			
opdone:
	/*
	 * Reset error count and remove
	 * from device queue.
	 */
	sc->sc_timo = 0;	/* cancel timer */
	ic->ic_tab.b_errcnt = 0;
	ic->ic_tab.b_active = SINACTIVE;	/* no longer active */
	if (bp != NULL) {
		dp->b_actf = bp->av_forw;
		sc->sc_resid = bp->b_resid = leftover;
		iodone(bp);
		/*
		 * Circulate slave to end of controller
		 * queue to give other slaves a chance.
		 */
		ic->ic_tab.b_actf = dp->b_forw;
		if (dp->b_actf) {
			dp->b_forw = NULL;
			if (ic->ic_tab.b_actf == NULL)
				ic->ic_tab.b_actf = dp;
			else
				ic->ic_tab.b_actl->b_forw = dp;
			ic->ic_tab.b_actl = dp;
		}
	}
	if (ic->ic_tab.b_actf == 0) {
		DEBUGF( (stdebug&SHOW_INTR), printf("stint: exiting at opdone\n"));
		goto exit0;
	}

opcont:
	sc->sc_timo = 0;		/* kill timeout */
	ststart(ic);
	DEBUGF( (stdebug&SHOW_INTR), printf("stint: exiting at opcont\n"));
	goto exit0;

opreset:
	DEBUGF( (stdebug&SHOW_INTR), printf("stint: exiting at opreset\n"));
exit0:
	*int_reset = 0xff;
	return(0);
}

char *
stccprint(cc)	/* CALLED FROM INTERRUPT LEVEL */
register int	cc;
{
	switch(cc) {
		case CC_DONE:				return("DONE");
		case CC_LD_COUNT:			return("LD_COUNT");
		case CC_POR_AA:			return("POR_AA");
		case CC_RD_STATUS:		return("RD_STATUS");
		case CC_READ:				return("READ");
		case CC_WRITE:				return("WRITE");
		case CC_READ_LAST:		return("READ_LAST");
		case CC_POR:				return("POR");
		case CC_CMD_REJ_EXCEPT:	return("CMD_REJ_EXCEPT");
		case CC_EXCEPTION:		return("EXCEPTION");
		case CC_NOTREADY:			return("NOTREADY");
		case CC_READY:				return("READY");
		case CC_PARITY:			return("PARITY");
		case CC_DIRECTION:		return("DIRECTION");
		case CC_FPLS:				return("FPLS");
		case CC_ROSERR:			return("ROSERR");
		case CC_RAMERR:			return("RAMERR");
		case CC_FPLSERR:			return("FPLSERR");
		case CC_DRVERR:			return("DRVERR");
		case CC_BUSERR:			return("BUSERR");
		case CC_CMD_REJ_INVAL:	return("CMD_REJ_INVAL");
		case CC_REJ_SEQUENCE:	return("REJ_SEQUENCE");
		default:						return("unknown");
	}
}

sttimer(dev)
	int dev;
{
	register struct st_softc *sc = &st_softc[STUNIT(dev)];
	register short x;

	if (sc->sc_timo > 0 && (sc->sc_timo -= STTIMER_INC) <= 0) {
		printf("st%d: lost interrupt; lastcmd=0x%x(%s)\n", STUNIT(dev),
			sc->sc_lastcmd, stcmdprint(sc->sc_lastcmd));
		sc->sc_timo = sc->sc_lasttimo;		/* reset the timer */
		x = spl_tape();
		/* set error flag */
	/*XXX*/	sc->sc_flags |= ST_HARDERR | ST_TIMERR;
		stint(-1);				/* flag as timer interrupt */
		(void) splx(x);
	}

	if ((sc->sc_flags&ST_OPEN) == 0 && (stutab[0].b_active == 0)) 
		sc->sc_tact = 0; 	/* timer not active */
	else
		timeout(sttimer, (caddr_t)dev, STTIMER_INC*hz);
}

/*ARGSUSED*/
stread(dev, uio)
	dev_t dev;
	struct uio *uio;
{
	int errno;

	DEBUGF( (stdebug&SHOW_ORDWR),
		printf("stread: entered\n"));

	if ((errno = stphys(dev, uio)) == 0) {
		errno = physio(ststrategy, &rstbuf[STUNIT(dev)], dev, B_READ, minphys, uio);
		DEBUGF( (stdebug&SHOW_ORDWR),
			printf("stread: exited with b_resid=%d\n",
				rstbuf[STUNIT(dev)].b_resid));
	}
	return (errno);
}

stwrite(dev, uio)
	dev_t dev;
	struct uio *uio;
{
	int errno;
	DEBUGF( (stdebug&SHOW_ORDWR),
		printf("stwrite: entered\n"));

	errno = stphys(dev, uio);
	if (errno)
		return (errno);
	return (physio(ststrategy, &rstbuf[STUNIT(dev)], dev, B_WRITE, minphys, uio));
}

/*
 * Check that a raw device exists.
 * If it does, set up sc_blkno and sc_nxrec
 * so that the tape will appear positioned correctly.
 */
stphys(dev, uio)
	dev_t dev;
	struct uio *uio;
{
	register struct st_softc *sc;
	register struct iocc_device *iod;
	DEBUGF( (stdebug&SHOW_ORDWR),
		printf("stphys: entered\n"));

	if (STUNIT(dev) >= NST || (iod = stdinfo[STUNIT(dev)]) == 0 ||
		 iod->iod_alive == 0) {
		return (ENXIO);
	}
	sc = &st_softc[STUNIT(dev)];
	sc->sc_blkno = BDBTOFSB(btodb(uio->uio_offset));
	sc->sc_nxrec = ST_TAPEMAX;
	return (0);
}

/*ARGSUSED*/
stioctl(dev, cmd, data, flag)
	caddr_t data;
	dev_t dev;
{
	register struct st_softc *sc = &st_softc[STUNIT(dev)];
	register struct buf *bp = &cstbuf[STUNIT(dev)];
	register callcount;
	int fcount;
	struct mtop *mtop;
	struct mtget *mtget;
	/* we depend on the values and order of the MT codes here */
/*XXX*/	static stops[] =
	   {ST_WEOF,ST_REOF,ST_SBAD,ST_SFORW,ST_SREV,ST_TERMINATE,ST_OFFL,ST_SENSE,ST_ERASE,ST_RETENSION};
	DEBUGF( (stdebug&SHOW_ORDWR),
		printf("stioctl: entered\n"));

	switch (cmd) {

	case MTIOCTOP:	/* tape operation */
		mtop = (struct mtop *)data;
		switch (mtop->mt_op) {

		case MTWEOF:
		case MTFSR:
		case MTFSF:
		case MTBSF:
		case MTBSR:
			callcount = 1;
			fcount = mtop->mt_count;
			break;

		case MTERASE:
		case MTREW: case MTOFFL: case MTNOP: case MTRETENSION:
			callcount = 1;
			fcount = 1;
			break;

		default:
			return (ENXIO);
		}
		if (callcount <= 0 || fcount <= 0)
			return (EINVAL);
		DEBUGF(stdebug & SHOW_ENQ, 
			printf("cmd=0x%x fcount=%d callcount=%d\n", stops[mtop->mt_op], fcount, callcount));
		while (--callcount >= 0) {
			if (stcommand(dev, stops[mtop->mt_op], fcount))
				return(EIO);
			if ((mtop->mt_op == MTFSR || mtop->mt_op == MTBSR) &&
			    bp->b_resid)
				return (EIO);
			if (bp->b_flags&B_ERROR)
				break;
		}
		return (geterror(bp));

	case MTIOCGET:
		mtget = (struct mtget *)data;
		mtget->mt_dsreg = 0;
		mtget->mt_erreg = sc->sc_data[0] + (sc->sc_data[1] << 8);
		mtget->mt_resid = sc->sc_resid;
		mtget->mt_type = MT_ISST;
		break;

	default:
		return (ENXIO);
	}
	return (0);
}

stget_status()	/* CALLED FROM INTERRUPT LEVEL */
{
	register struct st_softc *sc = &st_softc[0];
	int i, x;
	int timeout;
	register struct stdevice *addr;

	addr = (struct stdevice *)stdinfo[0]->iod_addr;

	DEBUGF( (stdebug&SHOW_GO), printf("stget_status entered\n"));

	/* read 8 data bytes from port */
	for (i=0; i<8; i++) {
		/* spin until data ready to read*/
		timeout = 10000;
		while ( (((x=addr->stcs) & STC_OBF) == 0) && (--timeout)) {
			DELAY(1);
		}
		if (!timeout) {
			x=x;	/* for lint and picky compilers */
			printf("st: getstatus timeout occurred\n");
			break;
		}

		sc->sc_data[i] = addr->stdata;

		if (timeout) {
			DEBUGF( (stdebug&SHOW_DATA),
				printf("stget_status: data%d = 0x%x\n", i, sc->sc_data[i]));
		}
	}

	DEBUGF((stdebug & SHOW_REGS), stextendstatus(printf));

	return(!timeout);
}

/*
 * print extended status information
 */
stextendstatus(fn)	/* CALLED FROM INTERRUPT LEVEL */
int (*fn)();
{
	register struct st_softc *sc = &st_softc[0];
		
	(*fn)("st: extended status: status0=%b status1=%b\n",
		sc->sc_data[1], ST1FMT, sc->sc_data[0], ST0FMT);
}

/*
 * load/unload the adapter buffer
 * called for write/read when a data transfer is required.
 * adunload is called to transfer as much data as exists in
 * the current page, and then the next page address is obtained
 * and the process continues.
 */
stunload(bp, dp, unit, staddr)	/* CALLED FROM INTERRUPT LEVEL */
	register struct buf *bp, *dp;
	register int unit;
	struct stdevice *staddr;
{
/* count remaining = end-address - current address */
	register unsigned int stcnt = (bp->b_un.b_addr + bp->b_bcount) - dp->b_un.b_addr;
					  /* note bp != dp */
	register caddr_t addr = st_softc[STUNIT(bp->b_dev)].st_physaddr; /* the real memory address */
/*
 * following calculation yields the number of bytes left on this page
 * it will give zero when at the start of a page which skips the first
 * adunload.
 */
	register int nleft = PGOFSET - (((int)dp->b_un.b_addr - 1) & PGOFSET);
	register int flag = bp->b_flags & B_READ; /* 0=write, non-zero = read */

	if (bp == NULL || dp == NULL) {
		printf("st: stunload NULL input pointer(s)!!!\n");
		return(1);
	}

	if (stcnt > STBLOCK_SIZE)
		stcnt = STBLOCK_SIZE; /* actual amount to transfer */
	if (nleft > stcnt)
		nleft = stcnt;
	if (nleft) {
		if (addr == NO_ADDR) {
			addr = real_buf_addr(bp, dp->b_un.b_addr); /* first time */
			if (addr == (caddr_t) -1) {
				printf("st: stunload real_buf_addr1 failed bp=0x%x dp->addr=0x%x\n",
					bp, dp->b_un.b_addr);
				bp->b_flags |= B_ERROR;
				bp->b_error = ENXIO;
				iodone(bp);
				return(1);
			}
		}
		DEBUGF( (stdebug&SHOW_DATA),
			printf("stunload:xad1 addr=0x%x staddr=0x%x nleft=0x%x flag=0x%x\n",
				addr, staddr, nleft, flag));

		xadunload(addr, staddr, nleft, flag);
		addr += nleft;
		dp->b_un.b_addr += nleft;
	}
	DEBUGF( (stdebug&SHOW_DATA),
		printf("stunload: nleft=0x%x ", stcnt - nleft));

	if (nleft = stcnt - nleft) {	  /* if more to transfer then we are now at a
					     page boundary */
		addr = real_buf_addr(bp, dp->b_un.b_addr);
		if (addr == (caddr_t) -1) {
			printf("st: stunload real_buf_addr2 failed bp=0x%x dp->addr=0x%x\n",
				bp, dp->b_un.b_addr);
			bp->b_flags |= B_ERROR;
			bp->b_error = ENXIO;
			iodone(bp);
			return(1);
		}
		DEBUGF( (stdebug&SHOW_DATA),
			printf("stunload:xad2 addr=0x%x staddr=0x%x nleft=0x%x flag=0x%x\n",
				addr, staddr, nleft, flag));

		xadunload(addr, staddr, nleft, flag);
		dp->b_un.b_addr += nleft;
		addr += nleft;
	}
	DEBUGF( (stdebug&SHOW_DATA),
		printf("stunload: nleft=0x%x ", STBLOCK_SIZE - stcnt));

	if ((nleft = STBLOCK_SIZE - stcnt)) {
		DEBUGF( (stdebug&SHOW_DATA),
			printf("stunload:xad3 addr=0x%x staddr=0x%x nleft=0x%x flag=0x%x\n",
				addr, staddr, nleft, flag));

		xadunload((caddr_t)0, staddr, nleft, flag); /* partial read */
	}
	st_softc[STUNIT(bp->b_dev)].st_physaddr = addr; /* updated real address */

	DEBUGF( (stdebug&SHOW_DATA),
		printf("stunload: addr=%x\n", addr));
}


char *
stcmdprint(cmd)	/* CALLED FROM INTERRUPT LEVEL */
register int cmd;
{
	switch (cmd) {
	case ST_RESET:		return("RESET");
	case ST_SELECT:		return("SELECT");
	case ST_REW:		return("REWIND");
	case ST_ERASE:		return("ERASE");
	case ST_RETENSION:	return("RETENSION");
	case ST_SENSE:		return("SENSE");
	case ST_WCOM:		return("WRITE");
	case ST_WEOF:		return("WEOF");
	case ST_RCOM:		return("READ");
	case ST_REOF:		return("REOF");
	case ST_SFORW:		return("SFORW");
	case ST_TERMINATE:	return("TERMINATE");
	case ST_DST1:		return("DST1");
	case ST_DST2:		return("DST2");
	case ST_DST3:		return("DST3");
	case ST_ADAP_PASS1:	return("PASS1");
	case ST_ADAP_PASS2:	return("PASS2");
	case ST_CMD_AA:		return("CMD_AA");
	default:		return("unknown");
	}
}

/*
 * during open - wait for a pending op to finish if one is going on 
 * this sleep is interruptable.
 * we will give up if we exceed the maximum rewind time 
 * (in case pending rewind is hung)
 * Returns 0 if all seems aok.
 */
int
st_wait(dev)
{
	register int timer = 0;
	register struct iocc_ctlr *ic = stdinfo[STUNIT(dev)]->iod_mi;

	while (ic->ic_tab.b_active != SINACTIVE) {
		if (++timer > 120)
			break;
		DEBUGF( stdebug & SHOW_SLEEP, printf("st_wait: sleeping on lbolt\n"));
		sleep(&lbolt, PZERO+1);
	}
	DEBUGF( stdebug & SHOW_SLEEP, printf("st_wait: sleep timer=0x%x\n", timer));
	return(timer > 120);
}

int
streset() 	/* CALLED FROM INTERRUPT LEVEL */
{
	register struct stdevice *addr = (struct stdevice *)stdinfo[0]->iod_addr;
	register int timeout = 10000;

	addr->stcs = STC_RESET;	/* reset, leaving interrupts disabled */
	*int_reset = 0xff;
	DELAY(10);
	addr->stcs = STC_ENABLE|STC_RESET;
	addr->stcs = STC_ENABLE|STC_NORESET|STC_PARITY;	/* add parity checking */
	while (addr->stcs & STC_BSY && --timeout) {
		DELAY(10);
	}
	if (timeout) {
		stadapt_init++;
		return(0);
	} else {
		stadapt_init = 0;
		printf("st: streset busy timeout\n");
		return(1);
	}
}

#endif
