V10/sys/io/te16.c

Compare this file to the similar file:
Show the results in this format:

/*
 * TM03/TE16 tape driver
 *
 * TODO:
 * multiple drives per formatter might work, but are untested
 */
#include "sys/param.h"
#include "sys/buf.h"
#include "sys/conf.h"
#include "sys/file.h"
#include "sys/user.h"
#include "sys/mbaddr.h"
#include "sys/mbsts.h"
#include "sys/subaddr.h"
#include "sys/mtio.h"
#include "sys/te16.h"

/*
 * hardware stuff
 */
struct	device {
	int	htcs1;		/* control status */
	int	htds;		/* drive status */
	int	hter;		/* error */
	int	htmr;		/* maintenance */
	int	htas;		/* attention status */
	int	htfc;		/* frame count */
	int	htdt;		/* drive type */
	int	htck;		/* nrzi check (crc) error character */
	int	htsn;		/* serial number */
	int	httc;		/* tape control */
};

/* htcs1 */
#define	HT_GO		000001		/* go bit */
#define	HT_REWOFFL	000002		/* rewind offline */
#define	HT_REW		000006		/* rewind */
#define	HT_DCLR		000010		/* drive clear */
#define	HT_ERASE	000024		/* erase */
#define	HT_WEOF		000026		/* write tape mark */
#define	HT_SFORW	000030		/* space forward */
#define	HT_SREV		000032		/* space reverse */
#define	HT_WCOM		000060		/* write forward */
#define	HT_RCOM		000070		/* read forward */

/* htds */
#define	HTDS_ATA	0100000		/* attention active */
#define	HTDS_ERR	0040000		/* composite error */
#define	HTDS_MOL	0010000		/* medium on line */
#define	HTDS_WRL	0004000		/* write lock */
#define	HTDS_EOT	0002000		/* end of tape */
#define	HTDS_DPR	0000400		/* drive present (always 1) */
#define	HTDS_DRY	0000200		/* drive ready */
#define	HTDS_SSC	0000100		/* slace status changed */
#define	HTDS_PES	0000040		/* phase-encoded status */
#define	HTDS_TM		0000004		/* tape mark */
#define	HTDS_BOT	0000002		/* beginning of tape */
#define	HTDS_SLA	0000001		/* slave attention */

/* hter */
#define	HTER_CORCRC	0100000		/* correctible data or ecc */
#define	HTER_UNS	0040000		/* unsafe */
#define	HTER_OPI	0020000		/* operation incomplete */
#define	HTER_DTE	0010000		/* drive timing error */
#define	HTER_NEF	0004000		/* non-executable function */
#define	HTER_CSITM	0002000		/* correctable skew/illegal tape mark */
#define	HTER_FCE	0001000		/* frame count error */
#define	HTER_NSG	0000400		/* non-standard gap */
#define	HTER_PEFLRC	0000200		/* format error or lrc error */
#define	HTER_DPAR	0000040		/* data parity error */
#define	HTER_FMT	0000020		/* format error */
#define	HTER_CPAR	0000010		/* control bus parity error */
#define	HTER_RMR	0000004		/* register modification refused */
#define	HTER_ILR	0000002		/* illegal register */
#define	HTER_ILF	0000001		/* illegal function */
#define	HTER_HARD \
	(HTER_UNS|HTER_OPI|HTER_NEF|HTER_DPAR|HTER_FMT|HTER_CPAR| \
	HTER_RMR|HTER_ILR|HTER_ILF)

/* htdt */
#define	HTDT_SPR	0002000		/* slave present */

/* httc */
#define	HTTC_800BPI	0001400		/* in bits 8-10, dens=1600 */
#define	HTTC_1600BPI	0002000		/* in bits 8-10, dens=800 */
#define	HTTC_PDP11	0000300		/* in bits 4-7, pdp11 normal format */

/*
 * system stuff
 */

int te16open(), te16close(), te16read(), te16write(), te16ioctl(), te16strategy();
struct cdevsw te16cdev = cdinit(te16open, te16close, te16read, te16write, te16ioctl);
struct bdevsw te16bdev = bdinit(te16open, te16close, te16strategy, B_TAPE);

/*
 * per-formatter stuff
 */

extern int tm03cnt;
extern struct tm03 tm03[];
extern struct mbaddr tm03addr[];

#define	T_BUSY	01		/* controller busy */
#define	NOUNIT	0377		/* no unit expected */

/*
 * per-drive stuff
 */

extern int te16cnt;
extern struct te16 te16[];
extern struct subaddr te16addr[];
extern struct buf rte16buf[];
extern struct buf cte16buf[];

/*
 * bits in minor device
 */
#define	TEUNIT(dev)	(minor(dev)&03)
#define	H_NOREWIND	04
#define	H_1600BPI	010

/*
 * sc_flags
 */
#define	H_BUSY	01	/* software drive busy */
#define	H_CMD	02	/* hardware command pending */
#define	H_WRITTEN 04	/* last operation was a write */
#define H_ERASED  010	/* last write retry was an erase gap */
#define	H_OPEN	020	/* device open */
#define	H_OFFLINE 040	/* device went offline; no io till close */
#define	H_WONLY	0100	/* write-only */

/*
 * scaling for block magtape blocks
 */
#define	INF	1000000L /* a block number that won't exist */
#define	TBLOCK	2	/* tape blocks per b_blkno block */
#define	TBSIZE	1024
#define	TBSHIFT	10

/*
 * overloads in struct buf; for te16cmd
 */
#define	b_repcnt  b_bcount
#define	b_command b_resid

te16open(dev, flag)
	dev_t dev;
	int flag;
{
	register int teunit;
	register struct te16 *sc;
	register struct subaddr *sa;
	register struct device *htaddr;
	register int dt;

	teunit = TEUNIT(dev);
	if (teunit >= te16cnt || (sc = &te16[teunit])->sc_flags & H_OPEN) {
		u.u_error = ENXIO;
		return;
	}
	sc->sc_flags |= H_OPEN;
	sa = &te16addr[teunit];
	if (tm03init(sa->ctl, sa->unit, teunit) == 0) {
		sc->sc_flags &=~ H_OPEN;
		u.u_error = ENXIO;
		return;
	}
	sc->sc_ctl = &tm03[sa->ctl];
	htaddr = sc->sc_ctl->cc_addr;
	sc->sc_dens = ((minor(dev)&H_1600BPI)?HTTC_1600BPI:HTTC_800BPI)|HTTC_PDP11|sa->unit;
	spl5();
	htaddr->httc = sc->sc_dens;
	/* delay here? */
	dt = htaddr->htdt;
	sc->sc_dsreg = htaddr->htds;
	spl0();
	if ((dt & HTDT_SPR) == 0) {
		printf("te16 %d absent\n", teunit);
		sc->sc_flags &=~ H_OPEN;
		u.u_error = ENODEV;
		return;
	}
	if ((sc->sc_dsreg & HTDS_MOL) == 0) {
		sc->sc_flags &=~ H_OPEN;
		u.u_error = EIO;
		return;
	}
	if ((flag&FWRITE) && (sc->sc_dsreg&HTDS_WRL)) {
		sc->sc_flags &=~ H_OPEN;
		u.u_error = EIO;
		return;
	}
	if ((sc->sc_dsreg & HTDS_BOT) == 0 && flag&FWRITE
	&&  ((minor(dev) & H_1600BPI) != 0) != ((sc->sc_dsreg & HTDS_PES) != 0)) {
		sc->sc_flags &=~ H_OPEN;
		u.u_error = EIO;
		return;
	}
	sc->sc_blkno = 0;
	sc->sc_nxrec = INF;
	if ((flag & (FREAD|FWRITE)) == FWRITE)
		sc->sc_flags |= H_WONLY;	/* stop block dev readahead */
}

tm03init(ctl, slave, dev)
register int ctl;
int slave, dev;
{
	register struct tm03 *cc;
	register int i;

	if (ctl < 0 || slave < 0 || ctl >= tm03cnt || slave >= TM03DRIVES)
		return (0);
	cc = &tm03[ctl];
	cc->cc_drives[slave] = dev;
	if (cc->cc_addr)
		return (1);
	if ((cc->cc_addr = (struct device *)mbaddr(&tm03addr[ctl])) == 0
	||  badaddr(&cc->cc_addr->htcs1, sizeof(long))
	||  (cc->cc_addr->htds & HTDS_DPR) == 0) {
		printf("tm03 %d absent\n", ctl);
		return (0);
	}
	cc->cc_mbaddr = &tm03addr[ctl];
	for (i = 0; i < TM03DRIVES; i++)
		if (i != slave)
			cc->cc_drives[i] = NOUNIT;
	/* any reset worth doing? */
	return (1);
}

te16close(dev)
	register dev_t dev;
{
	register struct te16 *sc = &te16[TEUNIT(dev)];

	if (sc->sc_flags & (H_WONLY|H_WRITTEN)) {
		te16cmd(dev, HT_WEOF, 1);
		te16cmd(dev, HT_WEOF, 1);
		te16cmd(dev, HT_SREV, 1);
	}
	if ((minor(dev)&H_NOREWIND) == 0)
		te16cmd(dev, HT_REW, 0);
	sc->sc_flags &= H_CMD|H_BUSY;
}

te16cmd(dev, com, count)
	dev_t dev;
	int com, count;
{
	register struct buf *bp;

	bp = &cte16buf[TEUNIT(dev)];
	(void) spl5();
	while (bp->b_flags&B_BUSY) {
		if(bp->b_repcnt == 0 && (bp->b_flags&B_DONE))
			break;
		bp->b_flags |= B_WANTED;
		sleep((caddr_t)bp, PRIBIO);
	}
	bp->b_flags = B_BUSY|B_READ;
	(void) spl0();
	bp->b_dev = dev;
	bp->b_command = com;
	bp->b_repcnt = count;
	bp->b_blkno = 0;
	te16strategy(bp);
	if (count == 0) {
		if (bp->b_flags & B_DONE)
			geterror(bp);
		return;
	}
	iowait(bp);
	if (bp->b_flags&B_WANTED)
		wakeup((caddr_t)bp);
	bp->b_flags &=~ B_BUSY;
}

te16strategy(bp)
	register struct buf *bp;
{
	register struct te16 *sc;

	sc = &te16[TEUNIT(bp->b_dev)];
	bp->av_forw = NULL;
	(void) spl5();
	if (sc->sc_actf == NULL)
		sc->sc_actf = bp;
	else
		sc->sc_actl->av_forw = bp;
	sc->sc_actl = bp;
	if ((sc->sc_flags & H_BUSY) == 0)
		te16start(sc);
	if (sc->sc_ctl->cc_actf && (sc->sc_ctl->cc_flags & T_BUSY) == 0)
		tm03start(sc->sc_ctl);
	(void) spl0();
}

te16start(sc)
register struct te16 *sc;
{
	register struct buf *bp;
	register struct device *htaddr;
	register struct tm03 *cc;
	register daddr_t bno;

	cc = sc->sc_ctl;
loop:
	if ((bp = sc->sc_actf) == 0)
		return;
	htaddr = cc->cc_addr;
	htaddr->httc = sc->sc_dens;
	if (htaddr->htds & HTDS_SSC)
		htaddr->htcs1 = HT_DCLR|HT_GO;
	sc->sc_dsreg = htaddr->htds;
	sc->sc_erreg = htaddr->hter;
	sc->sc_resid = htaddr->htfc;
	sc->sc_flags &= ~H_WRITTEN;
	if ((htaddr->htdt & HTDT_SPR) == 0 || (htaddr->htds & HTDS_MOL) == 0)
		if (sc->sc_flags & H_OPEN)
			sc->sc_flags |= H_OFFLINE;
	if (sc->sc_flags & H_OFFLINE) {
		bp->b_flags |= B_ERROR;
		goto done;
	}
	/*
	 * command, start it
	 */
	if (bp == &cte16buf[TEUNIT(bp->b_dev)]) {
		sc->sc_flags |= H_BUSY|H_CMD;
		htaddr->htfc = -bp->b_bcount;
		htaddr->htcs1 = bp->b_command|HT_GO;
		return;
	}
	/*
	 * transfer, see if position needed (wretched block magtape)
	 */
	bno = bp->b_blkno/TBLOCK;	
	if (bno > sc->sc_nxrec) {
		bp->b_error = ENXIO;
		bp->b_flags |= B_ERROR;
		goto done;
	}
	if (bp->b_flags & B_READ	/* at EOF, or really just writing? */
	&&  (bno == sc->sc_nxrec || sc->sc_flags & H_WONLY)) {
		bp->b_resid = bp->b_bcount;
		goto done;
	}
	if ((bp->b_flags&B_READ)==0)
		sc->sc_nxrec = bno + 1;
	if (sc->sc_blkno != bno) {	/* need position */
		sc->sc_flags |= H_BUSY|H_CMD;
		if (sc->sc_blkno < bno) {
			htaddr->htfc = sc->sc_blkno - bno;
			htaddr->htcs1 = HT_SFORW|HT_GO;
		} else {
			htaddr->htfc = bno - sc->sc_blkno;
			htaddr->htcs1 = HT_SREV|HT_GO;
		}
		return;
	}
	/*
	 * special pleading for `erase gap' write error recovery
	 * -- is this in the right place?  why bother?
	 */
	if ((bp->b_flags&B_READ) == 0) {
		if (cc->cc_errcnt) {
			if ((sc->sc_flags & H_ERASED) == 0) {
				sc->sc_flags |= H_ERASED|H_BUSY|H_CMD;
				htaddr->htcs1 = HT_ERASE|HT_GO;
				return;
			}
			sc->sc_flags &= ~H_ERASED;
		}
		if (htaddr->htds & HTDS_EOT) {		/* oops */
			bp->b_resid = bp->b_bcount;
done:
			sc->sc_actf = bp->av_forw;
			iodone(bp);
			goto loop;
		}
	}
	/*
	 * time for a transfer
	 */
	cc = sc->sc_ctl;
	sc->sc_next = NULL;
	if (cc->cc_actf == NULL)
		cc->cc_actf = sc;
	else
		cc->cc_actl->sc_next = sc;
	cc->cc_actl = sc;
	sc->sc_flags |= H_BUSY;
}

tm03start(cc)
register struct tm03 *cc;
{
	register struct te16 *sc;
	int tm03xfer();

	if ((sc = cc->cc_actf) == NULL)
		return;
	if (sc->sc_actf == NULL)
		panic("tm03start");
	cc->cc_flags |= T_BUSY;
	mbstart(cc->cc_mbaddr, sc->sc_actf, tm03xfer);
}

tm03xfer(bp)
register struct buf *bp;
{
	register struct te16 *sc;
	register struct device *htaddr;

	sc = &te16[TEUNIT(bp->b_dev)];
	htaddr = sc->sc_ctl->cc_addr;
	htaddr->httc = sc->sc_dens;
	htaddr->htfc = -bp->b_bcount;
	sc->sc_flags |= H_CMD;
	if (bp->b_flags & B_READ)
		htaddr->htcs1 = HT_RCOM|HT_GO;
	else
		htaddr->htcs1 = HT_WCOM|HT_GO;
}

/*
 * massbus interrupt
 * if a transfer was pending, assume it finished;
 * if attn set, something else finished too
 *
 * the loop to see which drives wanted attention is somewhat doubtful
 * and has not been tested with multiple drives
 */
tm030int(ctl, mbsr, mbbc, attn)
int ctl, mbsr, mbbc, attn;
{
	register struct tm03 *cc;
	register struct device *htaddr;
	register struct te16 *sc;
	register int i;

	cc = &tm03[ctl];
	if ((htaddr = cc->cc_addr) == NULL) {
		printf("tm03 %d: stray intr\n", ctl);
		return;
	}
	htaddr->htas = attn;		/* before we do anything */
	if (cc->cc_flags & T_BUSY)
		te16dtint(cc, mbsr);
	if (attn) {
		for (i = 0; i < TM03DRIVES; i++) {
			if (cc->cc_drives[i] == NOUNIT)
				continue;
			sc = &te16[cc->cc_drives[i]];
			htaddr->httc = sc->sc_dens;	/* select drive */
			if ((sc->sc_flags & H_CMD) == 0) {
				sc->sc_dsreg = htaddr->htds;	/* for BOT detection */
				wakeup((caddr_t)sc);
				if (htaddr->htds & HTDS_SSC)
					htaddr->htcs1 = HT_DCLR|HT_GO;
				continue;
			}
			if ((htaddr->htds & HTDS_DRY) == 0)
				continue;
			te16attn(sc);
		}
	}
	if (cc->cc_actf)
		tm03start(cc);
}

/*
 * error recovery is somewhat doubtful
 * particular specialties:
 * - short reads show up as frame count errors,
 *   so ignore them for the raw device
 * - reading at tape mark seems to produce a frame count error too
 * - quietly ignore CRC errors the drive corrected
 */
te16dtint(cc, mbsr)
register struct tm03 *cc;
int mbsr;
{
	register struct device *htaddr;
	register struct buf *bp;
	register struct te16 *sc;
	int ds, er, mbs;

	htaddr = cc->cc_addr;
	if ((sc = cc->cc_actf) == NULL)
		panic("te16dtint");
	sc->sc_flags &=~ H_CMD;
	bp = sc->sc_actf;
	ds = sc->sc_dsreg = htaddr->htds;
	er = sc->sc_erreg = htaddr->hter;
	sc->sc_resid = htaddr->htfc;
#if BABBLE
printf("dt: ds%o er%o fc%o mbs%o\n", ds, er, sc->sc_resid, mbsr);
printf("bp: bn%d bc%d fl%o\n", bp->b_blkno, bp->b_bcount, bp->b_flags);
#endif
	mbs = mbsr;
	if (er)
		htaddr->htcs1 = HT_DCLR|HT_GO;
	sc->sc_blkno++;
	if((bp->b_flags & B_READ) == 0)
		sc->sc_flags |= H_WRITTEN;
	if (bp == &rte16buf[TEUNIT(bp->b_dev)] || ds & HTDS_TM) {
		if (er & HTER_FCE) {
			er &=~ HTER_FCE;
			mbs &=~ (MBSR_DTABT|MBSR_MBEXC);
		}
	}
	if (er & HTER_HARD || (ds & HTDS_MOL) == 0 || mbs & MBSR_EBITS)
		bp->b_flags |= B_ERROR;
	else if (er) {				/* soft error */
		if (++cc->cc_errcnt >= 7)
			bp->b_flags |= B_ERROR;
		else
			cc->cc_flags &=~ T_BUSY;
	}
	if (bp->b_flags & B_ERROR)
		printf("te16 %d: hard error bn%d mbsr=%o er=%o ds=%o\n",
		  TEUNIT(bp->b_dev), bp->b_blkno/TBLOCK, mbsr, sc->sc_erreg, sc->sc_dsreg);
	if ((bp->b_flags & B_READ) == 0)
		bp->b_resid = -sc->sc_resid;
	else {
		if (ds & HTDS_TM) {
			bp->b_resid = bp->b_bcount;
			sc->sc_nxrec = bp->b_blkno/TBLOCK;
		} else if (sc->sc_resid > bp->b_bcount) {
			bp->b_flags |= B_ERROR;
			bp->b_error = ENOMEM;
		} else
			bp->b_resid = bp->b_bcount - sc->sc_resid;
	}
	if (cc->cc_flags & T_BUSY) {	/* we decided we were finished */
		sc->sc_actf = bp->av_forw;
		iodone(bp);
		cc->cc_actf = sc->sc_next;
		cc->cc_errcnt = 0;
		cc->cc_flags &=~ T_BUSY;
	}
	sc->sc_flags &=~ H_BUSY;
	if (sc->sc_actf)
		te16start(sc);
}

te16attn(sc)
register struct te16 *sc;
{
	register struct device *htaddr;
	register struct buf *bp;
	register struct tm03 *cc;
	register int er, ds;
	daddr_t bno;

	sc->sc_flags &=~ H_CMD;
	cc = sc->sc_ctl;
	htaddr = cc->cc_addr;
	ds = sc->sc_dsreg = htaddr->htds;
	er = sc->sc_erreg = htaddr->hter;
	sc->sc_resid = htaddr->htfc;
	if (er)
		htaddr->htcs1 = HT_DCLR|HT_GO;
	wakeup((caddr_t)sc);
	if ((bp = sc->sc_actf) == 0)
		return;
#if BABBLE
printf("at: ds%o er%o fc%o\n", sc->sc_dsreg, sc->sc_erreg, sc->sc_resid);
printf("bp: bn%d bc%d fl%o cm%o\n", bp->b_blkno, bp->b_bcount, bp->b_flags, bp->b_command);
#endif
	bno = bp->b_blkno/TBLOCK;
	if (bp == &cte16buf[TEUNIT(bp->b_dev)]) {
		switch ((int)bp->b_command) {
		case HT_REWOFFL:
			/* offline is on purpose; don't do anything special */
			ds |= HTDS_MOL;	
			break;
		case HT_SREV:
			/* if backspace file hit bot, its not an error */
		        if (er == (HTER_NEF|HTER_FCE) && ds&HTDS_BOT
			&&  bp->b_repcnt == INF)
				er &= ~HTER_NEF;
			break;
		}
		er &= ~HTER_FCE;
		if (er == 0)
			ds &= ~HTDS_ERR;
	}
	if ((ds & (HTDS_ERR|HTDS_MOL)) != HTDS_MOL) {
		if ((ds & HTDS_MOL) == 0 && sc->sc_flags & H_OPEN)
			sc->sc_flags |= H_OFFLINE;
		printf("te16 %d: hard error bn%d er%o ds%o\n",
		    TEUNIT(bp->b_dev), bno, sc->sc_erreg, sc->sc_dsreg);
		bp->b_flags |= B_ERROR;
	}
	if (bp == &cte16buf[TEUNIT(bp->b_dev)])
		bp->b_resid = -sc->sc_resid;
	else
		sc->sc_flags &=~ H_BUSY;	/* done seeking, now xfer */
	if ((ds & HTDS_TM) == 0)
		sc->sc_blkno = bno;
	else if (sc->sc_blkno > bno) {
		sc->sc_nxrec = bno - sc->sc_resid;
		sc->sc_blkno = sc->sc_nxrec;
	} else {
		sc->sc_blkno = bno + sc->sc_resid;
		sc->sc_nxrec = sc->sc_blkno - 1;
	}
	if (sc->sc_flags & H_BUSY) {
		sc->sc_actf = bp->av_forw;
		iodone(bp);
		sc->sc_flags &=~ H_BUSY;
	}
	if (sc->sc_actf)
		te16start(sc);
}

te16read(dev)
dev_t dev;
{

	te16phys(dev);
	physio(te16strategy, &rte16buf[TEUNIT(dev)], dev, B_READ, minphys);
}

te16write(dev)
dev_t dev;
{

	te16phys(dev);
	physio(te16strategy, &rte16buf[TEUNIT(dev)], dev, B_WRITE, minphys);
}

te16phys(dev)
dev_t dev;
{
	register struct te16 *sc;

	sc = &te16[TEUNIT(dev)];
	sc->sc_blkno = Lshift(u.u_offset, TBSHIFT);
	sc->sc_nxrec = sc->sc_blkno + 1;
}

/*ARGSUSED*/
te16ioctl(dev, cmd, addr, flag)
	dev_t dev;
	int cmd;
	caddr_t addr;
	int flag;
{
	register struct te16 *sc = &te16[TEUNIT(dev)];
	register struct buf *bp = &cte16buf[TEUNIT(dev)];
	register callcount;
	int fcount, htcmd;
	struct mtop mtop;

	switch (cmd) {
		case MTIOCTOP:
		if (copyin((caddr_t)addr, (caddr_t)&mtop, sizeof(mtop))) {
			u.u_error = EFAULT;
			return;
		}
		if (mtop.mt_count <= 0) {
			u.u_error = ENXIO;
			return;
		}
		switch(mtop.mt_op) {
		case MTWEOF:
			htcmd = HT_WEOF;
			callcount = mtop.mt_count;
			fcount = 1;
			break;
		case MTFSF: case MTBSF:
			htcmd = (mtop.mt_op == MTFSF) ? HT_SFORW : HT_SREV;
			callcount = mtop.mt_count;
			fcount = INF;
			break;
		case MTFSR: case MTBSR:
			htcmd = (mtop.mt_op == MTFSF) ? HT_SFORW : HT_SREV;
			callcount = 1;
			fcount = mtop.mt_count;
			break;
		case MTREW: case MTOFFL:
			te16cmd(dev, HT_REW, 1);
			if (mtop.mt_op == MTREW)
				return;
			spl5();
			while ((sc->sc_dsreg & HTDS_BOT) == 0)
				sleep((caddr_t)sc, PZERO);
			spl0();
			te16cmd(dev, HT_REWOFFL, 0);
			return;
		default:
			u.u_error = ENXIO;
			return;
		}
		while (--callcount >= 0) {
			te16cmd(dev, htcmd, fcount);
			if (bp->b_resid
			&&  (mtop.mt_op == MTFSR || mtop.mt_op == MTBSR)) {
				u.u_error = EIO;
				break;
			}
			if (u.u_error || sc->sc_dsreg&HTDS_BOT)
				break;
		}
		return;

	default:
		u.u_error = ENXIO;
		return;
	}
}