4.4BSD/usr/src/sys/vax/mba/mt.c

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

/*-
 * Copyright (c) 1982, 1986 The Regents of the University of California.
 * All rights reserved.
 *
 * This module is believed to contain source code proprietary to AT&T.
 * Use and redistribution is subject to the Berkeley Software License
 * Agreement and your Software Agreement with AT&T (Western Electric).
 *
 *	@(#)mt.c	7.10 (Berkeley) 5/8/91
 */

#include "mu.h"
#if NMT > 0
/*
 * TM78/TU78 tape driver
 *
 *	Original author - ?
 *	Most error recovery bug fixes - ggs (ulysses!ggs)
 *	`read reverse' error recovery - ggs (ulysses!ggs)
 *
 * OPTIONS:
 *	MTLERRM - Long error message text - twd, Brown University
 *
 * TODO:
 *	Add odd byte count kludge from VMS driver (?)
 *	Write dump routine
 */

#include "sys/param.h"
#include "sys/systm.h"
#include "sys/buf.h"
#include "sys/conf.h"
#include "sys/file.h"
#include "sys/user.h"
#include "sys/proc.h"
#include "sys/map.h"
#include "sys/ioctl.h"
#include "sys/mtio.h"
#include "sys/cmap.h"
#include "sys/tty.h"
#include "sys/syslog.h"

#include "../include/pte.h"
#include "../include/cpu.h"
#include "mbareg.h"
#include "mbavar.h"
#include "mtreg.h"

#define MTTIMEOUT	10000		/* loop limit for controller test */
#define	INF		1000000L	/* a block number that won't exist */
#define MASKREG(r)	((r) & 0xffff)	/* the control registers have 16 bits */

/* Bits for sc_flags */

#define	H_WRITTEN	01		/* last operation was a write */
#define H_EOT		02		/* end of tape encountered */
#define H_IEOT		04		/* ignore EOT condition */

int	mt_do_readrev = 1;

/* Per unit status information */

struct	mu_softc {
	char	sc_openf;	/* unit is open if != 0 */
	char	sc_flags;	/* state flags */
	daddr_t	sc_blkno;	/* current physical block number */
	daddr_t	sc_nxrec;	/* firewall input block number */
	u_short	sc_erreg;	/* copy of mter or mtner */
	u_short	sc_dsreg;	/* copy of mtds */
	short	sc_resid;	/* residual function count for ioctl */
	short	sc_dens;	/* density code - MT_GCR or zero */
	int	sc_i_mtas;	/* mtas at slave attach time */
	int	sc_i_mtner;	/* mtner at slave attach time */
	int	sc_i_mtds;	/* mtds at slave attach time */
	caddr_t	sc_ctty;	/* record user's tty for errors */
	int	sc_blks;	/* number of I/O operations since open */
	int	sc_softerrs;	/* number of soft I/O errors since open */
} mu_softc[NMU];

struct	buf	cmtbuf[NMT];		/* tape command buffer structures */

struct	mba_device *mtinfo[NMT];	/* unit to ctlr structures */
struct	mba_slave *muinfo[NMU];		/* unit to slave structures */

char	mtds_bits[] = MTDS_BITS;	/* mtds bit names for error messages */
short	mttypes[] = { MBDT_TU78, 0 };

int	mtattach(), mtslave(), mtustart(), mtstart(), mtndtint(), mtdtint();
struct	mba_driver mtdriver =
	{ mtattach, mtslave, mtustart, mtstart, mtdtint, mtndtint,
	  mttypes, "mt", "mu", mtinfo };

/* Bits in minor device */
#define	MUUNIT(dev)	(minor(dev)&03)
#define	H_NOREWIND	04
#define	H_6250BPI	010

#define MTUNIT(dev)	(muinfo[MUUNIT(dev)]->ms_ctlr)

void	mtcreset();

/*ARGSUSED*/
mtattach(mi)
	struct mba_device *mi;
{

	/* void */
}

mtslave(mi, ms, sn)
	struct mba_device *mi;
	struct mba_slave *ms;
	int sn;
{
	register struct mu_softc *sc = &mu_softc[ms->ms_unit];
	register struct mtdevice *mtaddr = (struct mtdevice *)mi->mi_drv;
	int s = spl5(), rtn = 0, i;

	/*
	 * Just in case the controller is ill, reset it.  Then issue
	 * a sense operation and wait about a second for it to respond.
	 */
	mtcreset(mtaddr);
	mtaddr->mtas = -1;
	mtaddr->mtncs[sn] = MT_SENSE|MT_GO;
	for (i = MTTIMEOUT; i > 0; i--) {
		DELAY(50);
		if (MASKREG(mtaddr->mtas) != 0)
			break;
	}
	sc->sc_i_mtas = mtaddr->mtas;
	sc->sc_i_mtner = mtaddr->mtner;
	sc->sc_i_mtds = mtaddr->mtds;

	/*
	 * If no response, whimper.  If wrong response, call it an
	 * unsolicited interrupt and use mtndtint to log and correct.
	 * Otherwise, note whether this slave exists.
	 */
	if (i <= 0)
		printf("mt: controller hung\n");
	else if ((mtaddr->mtner & MTER_INTCODE) != MTER_DONE)
		(void) mtndtint(mi);
	else if (mtaddr->mtds & MTDS_PRES) {
		muinfo[ms->ms_unit] = ms;
		rtn = 1;
	}

	/* cancel the interrupt, then wait a little while for it to go away */
	mtaddr->mtas = mtaddr->mtas;
	DELAY(10);
	splx(s);
	return (rtn);
}

mtopen(dev, flag)
	dev_t dev;
	int flag;
{
	register int muunit;
	register struct mu_softc *sc;
	register struct mba_slave *ms;

	muunit = MUUNIT(dev);
	if (muunit >= NMU || (ms = muinfo[muunit]) == NULL ||
	    ms->ms_alive == 0 || mtinfo[ms->ms_ctlr]->mi_alive == 0)
		return (ENXIO);
	if ((sc = &mu_softc[muunit])->sc_openf)
		return (EBUSY);
	sc->sc_openf = 1;
	sc->sc_dens = (minor(dev) & H_6250BPI) ? MT_GCR : 0;
	mtcommand(dev, MT_SENSE, 1);
	if ((sc->sc_dsreg & MTDS_ONL) == 0) {
		uprintf("mu%d: not online\n", muunit);
		sc->sc_openf = 0;
		return (EIO);
	}
	if ((sc->sc_dsreg & MTDS_AVAIL) == 0) {
		uprintf("mu%d: not online (port selector)\n", muunit);
		sc->sc_openf = 0;
		return (EIO);
	}
	if ((flag & FWRITE) && (sc->sc_dsreg & MTDS_FPT)) {
		uprintf("mu%d: no write ring\n", muunit);
		sc->sc_openf = 0;
		return (EIO);
	}
	if ((sc->sc_dsreg & MTDS_BOT) == 0 && (flag & FWRITE) &&
	    (sc->sc_dens == MT_GCR) != ((sc->sc_dsreg & MTDS_PE) == 0)) {
		uprintf("mu%d: can't change density in mid-tape\n", muunit);
		sc->sc_openf = 0;
		return (EIO);
	}
	sc->sc_blkno = (daddr_t)0;

	/*
	 * Since cooked I/O may do a read-ahead before a write, trash
	 * on a tape can make the first write fail.  Suppress the first
	 * read-ahead unless definitely doing read-write.
	 */
	sc->sc_nxrec = ((flag & (FTRUNC | FWRITE)) == (FTRUNC | FWRITE)) ?
	    (daddr_t)0 : (daddr_t)INF;
	sc->sc_flags = 0;
	sc->sc_blks = 0;
	sc->sc_softerrs = 0;
	sc->sc_ctty = (caddr_t)(u.u_procp->p_flag&SCTTY ? 
			u.u_procp->p_session->s_ttyp : 0);
	return (0);
}

mtclose(dev, flag)
	register dev_t dev;
	register int flag;
{
	register struct mu_softc *sc = &mu_softc[MUUNIT(dev)];

	if ((flag & (FREAD | FWRITE)) == FWRITE ||
	    ((flag & FWRITE) && (sc->sc_flags & H_WRITTEN)))
		mtcommand(dev, MT_CLS|sc->sc_dens, 1);
	if ((minor(dev) & H_NOREWIND) == 0)
		mtcommand(dev, MT_REW, 0);
	if (sc->sc_blks > 100 && sc->sc_softerrs > sc->sc_blks / 100)
		log(LOG_INFO, "mu%d: %d soft errors in %d blocks\n",
		    MUUNIT(dev), sc->sc_softerrs, sc->sc_blks);
	sc->sc_openf = 0;
	return (0);
}

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

	bp = &cmtbuf[MTUNIT(dev)];
	s = 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;
	splx(s);
	bp->b_dev = dev;
	bp->b_command = com;
	bp->b_repcnt = count;
	bp->b_blkno = 0;
	bp->b_error = 0;
	mtstrategy(bp);
	if (count == 0)
		return;
	biowait(bp);
	if (bp->b_flags & B_WANTED)
		wakeup((caddr_t)bp);
	bp->b_flags &= B_ERROR;
}

mtstrategy(bp)
	register struct buf *bp;
{
	register struct buf *dp;
	struct mba_device *mi = mtinfo[MTUNIT(bp->b_dev)];
	int s;

	/*
	 * If this is a data transfer operation, set the resid to a
	 * default value (EOF) to simplify getting it right during
	 * error recovery or bail out.
	 */
	if (bp != &cmtbuf[MTUNIT(bp->b_dev)])
		bp->b_resid = bp->b_bcount;

	/*
	 * Link this request onto the end of the queue for this
	 * controller, then start I/O if not already active.
	 */
	bp->av_forw = NULL;
	dp = &mi->mi_tab;
	s = spl5();
	if (dp->b_actf == NULL)
		dp->b_actf = bp;
	else
		dp->b_actl->av_forw = bp;
	dp->b_actl = bp;
	if (dp->b_active == 0)
		mbustart(mi);
	splx(s);
}

mtustart(mi)
	register struct mba_device *mi;
{
	register struct mtdevice *mtaddr = (struct mtdevice *)mi->mi_drv;
	register struct buf *bp = mi->mi_tab.b_actf;
	register struct mu_softc *sc = &mu_softc[MUUNIT(bp->b_dev)];
	daddr_t blkno;
	int count;

	if (sc->sc_openf < 0) {
		bp->b_flags |= B_ERROR;
		return (MBU_NEXT);
	}
	if (bp != &cmtbuf[MTUNIT(bp->b_dev)]) {
		/*
		 * Data transfer.  If write at end of tape,
		 * signal "no space" unless suppressed
		 * by MTIOCIEOT.
		 */
		if ((sc->sc_flags & (H_EOT | H_IEOT)) == H_EOT &&
		    (bp->b_flags & B_READ) == 0) {
			bp->b_flags |= B_ERROR;
			bp->b_error = ENOSPC;
			return (MBU_NEXT);
		}

		if (bp->b_flags & B_RAW) {
			/* raw transfer; never seek */
			sc->sc_blkno = bdbtofsb(bp->b_blkno);
			sc->sc_nxrec = sc->sc_blkno + 1;
		} else {
			/* seek beyond end of file */
			if (bdbtofsb(bp->b_blkno) > sc->sc_nxrec) {
				bp->b_flags |= B_ERROR;
				bp->b_error = ENXIO;
				return (MBU_NEXT);
			}

			/*
			 * This should be end of file, but the buffer
			 * system wants a one-block look-ahead.  Humor it.
			 */
			if (bdbtofsb(bp->b_blkno) == sc->sc_nxrec &&
			    bp->b_flags & B_READ) {
				bp->b_resid = bp->b_bcount;
				clrbuf(bp);
				return (MBU_NEXT);
			}

			/* If writing, mark the next block invalid. */
			if ((bp->b_flags & B_READ) == 0)
				sc->sc_nxrec = bdbtofsb(bp->b_blkno) + 1;
		}
	} else {
		/* It's a command, do it now. */
		mtaddr->mtncs[MUUNIT(bp->b_dev)] =
			(bp->b_repcnt<<8)|bp->b_command|MT_GO;
		return (MBU_STARTED);
	}

	/*
	 * If raw I/O, or if the tape is positioned correctly for
	 * cooked I/O, set the byte count, unit number and repeat count
	 * then tell the MASSBUS to proceed.  Note that a negative
	 * bcount tells mbstart to map the buffer for "read backwards".
	 */
	if ((blkno = sc->sc_blkno) == bdbtofsb(bp->b_blkno)) {
		if (mi->mi_tab.b_errcnt == 2) {
			mtaddr->mtbc = -bp->b_bcount;
			mtaddr->mtca = MUUNIT(bp->b_dev);
		} else {
			mtaddr->mtbc = bp->b_bcount;
			mtaddr->mtca = (1<<2)|MUUNIT(bp->b_dev);
		}
		return (MBU_DODATA);
	}

	/* Issue skip operations to position the next block for cooked I/O. */

	if (blkno < bdbtofsb(bp->b_blkno))
		count = bdbtofsb(bp->b_blkno) - blkno;
	else
		count = blkno - bdbtofsb(bp->b_blkno);
	if ((unsigned)count > 0377)
		count = 0377;
	mtaddr->mtncs[MUUNIT(bp->b_dev)] = count | MT_SFORW|MT_GO;
	return (MBU_STARTED);
}

mtstart(mi)
	register struct mba_device *mi;
{
	register struct buf *bp = mi->mi_tab.b_actf;
	register struct mu_softc *sc = &mu_softc[MUUNIT(bp->b_dev)];

	if (bp->b_flags & B_READ)
		if (mi->mi_tab.b_errcnt == 2)
			return (MT_READREV|MT_GO);
		else
			return (MT_READ|MT_GO);
	else
		return (MT_WRITE|sc->sc_dens|MT_GO);
}

mtdtint(mi, mbsr)
	register struct mba_device *mi;
	int mbsr;
{
	register struct mtdevice *mtaddr = (struct mtdevice *)mi->mi_drv;
	register struct buf *bp = mi->mi_tab.b_actf;
	register struct mu_softc *sc;
	register int er;

	/* I'M still NOT SURE IF THIS SHOULD ALWAYS BE THE CASE SO FOR NOW... */
	if ((mtaddr->mtca & 3) != MUUNIT(bp->b_dev)) {
		printf("mt: wrong unit!\n");
		mtaddr->mtca = MUUNIT(bp->b_dev);
	}

	er = MASKREG(mtaddr->mter);
	sc = &mu_softc[MUUNIT(bp->b_dev)];
	sc->sc_erreg = er;
	if (bp->b_flags & B_READ)
		sc->sc_flags &= ~H_WRITTEN;
	else
		sc->sc_flags |= H_WRITTEN;
	switch (er & MTER_INTCODE) {

	case MTER_EOT:
		sc->sc_flags |= H_EOT;
		/* fall into MTER_DONE */

	case MTER_DONE:
		sc->sc_blkno++;
		if (mi->mi_tab.b_errcnt == 2) {
			bp->b_bcount = bp->b_resid;
			bp->b_resid -= MASKREG(mtaddr->mtbc);
			if (bp->b_resid > 0 && (bp->b_flags & B_RAW) == 0)
				bp->b_flags |= B_ERROR;
		} else
			bp->b_resid = 0;
		break;

	case MTER_SHRTREC:
		sc->sc_blkno++;
		bp->b_bcount = bp->b_resid;
		bp->b_resid -= MASKREG(mtaddr->mtbc);
		if ((bp->b_flags & B_RAW) == 0)
			bp->b_flags |= B_ERROR;
		break;

	case MTER_RETRY:
		/*
		 * Simple re-try.  Since resid is always a copy of the
		 * original byte count, use it to restore the count.
		 */
		mi->mi_tab.b_errcnt = 1;
		bp->b_bcount = bp->b_resid;
		return (MBD_RETRY);

	case MTER_RDOPP:
		/*
		 * The controller just decided to read it backwards.
		 * If the controller returns a byte count of zero,
		 * change it to 1, since zero encodes 65536, which
		 * isn't quite what we had in mind.  The byte count
		 * may be larger than the size of the input buffer, so
		 * limit the count to the buffer size.  After
		 * making the byte count reasonable, set bcount to the
		 * negative of the controller's version of the byte
		 * count so that the start address for the transfer is
		 * set up correctly.
		 */
		if (mt_do_readrev) {
			mi->mi_tab.b_errcnt = 2;
			if ((bp->b_bcount = MASKREG(mtaddr->mtbc)) == 0)
				bp->b_bcount = 1;
			if (bp->b_bcount > bp->b_resid)
				bp->b_bcount = bp->b_resid;
			bp->b_bcount = -(bp->b_bcount);
			return(MBD_RETRY);
		} else if (MASKREG(mtaddr->mtbc) <= bp->b_resid) {
			sc->sc_blkno++;
			bp->b_bcount = bp->b_resid;
			bp->b_resid -= MASKREG(mtaddr->mtbc);
			bp->b_flags |= B_ERROR;
			break;
		}
		bp->b_flags |= B_ERROR;
		/* fall into MTER_LONGREC */

	case MTER_LONGREC:
		sc->sc_blkno++;
		bp->b_bcount = bp->b_resid;
		bp->b_resid = 0;
		bp->b_error = ENOMEM;
		bp->b_flags |= B_ERROR;
		break;

	case MTER_NOTCAP:
		printf("mu%d: blank tape\n", MUUNIT(bp->b_dev));
		goto err;

	case MTER_TM:
		/*
		 * End of file.  Since the default byte count has
		 * already been set, just count the block and proceed.
		 */
		sc->sc_blkno++;
	err:
		sc->sc_nxrec = bdbtofsb(bp->b_blkno);
		break;

	case MTER_OFFLINE:
		if (sc->sc_openf > 0) {
			sc->sc_openf = -1;
			tprintf(sc->sc_ctty, "mu%d: offline\n",
			    MUUNIT(bp->b_dev));
		}
		bp->b_flags |= B_ERROR;
		break;

	case MTER_NOTAVL:
		if (sc->sc_openf > 0) {
			sc->sc_openf = -1;
			tprintf(sc->sc_ctty, "mu%d: offline (port selector)\n",
			    MUUNIT(bp->b_dev));
		}
		bp->b_flags |= B_ERROR;
		break;

	case MTER_FPT:
		tprintf(sc->sc_ctty, "mu%d: no write ring\n",
		    MUUNIT(bp->b_dev));
		bp->b_flags |= B_ERROR;
		break;

	case MTER_UNREAD:
		sc->sc_blkno++;
		bp->b_bcount = bp->b_resid;
		bp->b_resid -= MIN(MASKREG(mtaddr->mtbc), bp->b_bcount);

		/* code 010 means a garbage record, nothing serious. */
		if ((er & MTER_FAILCODE) == (010 << MTER_FSHIFT)) {
			tprintf(sc->sc_ctty,
			    "mu%d: rn=%d bn=%d unreadable record\n",
			    MUUNIT(bp->b_dev), sc->sc_blkno, bp->b_blkno);
			bp->b_flags |= B_ERROR;
			break;
		}

		/*
		 * Anything else might be a hardware problem,
		 * fall into the error report.
		 */

	default:
		/*
		 * The bits in sc->sc_dsreg are from the last sense
		 * command.  To get the most recent copy, you have to
		 * do a sense at interrupt level, which requires nested
		 * error processing.  This is a bit messy, so leave
		 * well enough alone.
		 */
		tprintf(sc->sc_ctty, "\
mu%d: hard error (data transfer) rn=%d bn=%d mbsr=%b er=0%o ds=%b\n",
		    MUUNIT(bp->b_dev), sc->sc_blkno, bp->b_blkno,
		    mbsr, mbsr_bits, er,
		    MASKREG(sc->sc_dsreg), mtds_bits);
#ifdef MTLERRM
		mtintfail(er);
#endif
		bp->b_flags |= B_ERROR;

		/*
		 * The TM78 manual says to reset the controller after
		 * TM fault B or MASSBUS fault.
		 */
		if ((er & MTER_INTCODE) == MTER_TMFLTB ||
		    (er & MTER_INTCODE) == MTER_MBFLT)
			mtcreset(mtaddr);
	}

	/*
	 * Just in case some strange error slipped through (drive off
	 * line during read-reverse error recovery comes to mind), make
	 * sure the byte count is reasonable.
	 */
	if (bp->b_bcount < 0)
		bp->b_bcount = bp->b_resid;

	if ((bp->b_flags & B_ERROR) == 0) {
		/* this counts reverse reads as soft errors */
		sc->sc_blks++;
		if (mi->mi_tab.b_errcnt) /* alternatively, if == 1 */
			sc->sc_softerrs++;
	}
	return (MBD_DONE);
}

mtndtint(mi)
	register struct mba_device *mi;
{
	register struct mtdevice *mtaddr = (struct mtdevice *)mi->mi_drv;
	register struct buf *bp = mi->mi_tab.b_actf;
	register struct mu_softc *sc;
	register int er, fc;
	int unit;

	unit = (mtaddr->mtner >> 8) & 3;
	er = MASKREG(mtaddr->mtner);
	sc = &mu_softc[unit];
	sc->sc_erreg = er;

	/* Check for unsolicited interrupts. */
	if (bp == NULL || unit != MUUNIT(bp->b_dev)) {
		if ((er & MTER_INTCODE) == MTER_ONLINE)
			return (MBN_SKIP);

		printf("mu%d: stray intr (non data transfer) er=0%o ds=%b\n",
		    unit, er, MASKREG(sc->sc_dsreg), mtds_bits);
#ifdef MTLERRM
		mtintfail(er);
#endif
		if ((er & MTER_INTCODE) == MTER_TMFLTB ||
		    (er & MTER_INTCODE) == MTER_MBFLT) {
			/*
			 * Reset the controller, then set error status
			 * if there was anything active when the fault
			 * occurred.  This may shoot an innocent
			 * bystander, but it's better than letting
			 * an error slip through.
			 */
			mtcreset(mtaddr);
			if (bp != NULL) {
				bp->b_flags |= B_ERROR;
				return (MBN_DONE);
			}
		}
		return (MBN_SKIP);
	}

	fc = (mtaddr->mtncs[unit] >> 8) & 0xff;
	sc->sc_resid = fc;

	/*
	 * Clear the "written" flag after any operation that changes
	 * the position of the tape.
	 */
	if (bp != &cmtbuf[MTUNIT(bp->b_dev)] || bp->b_command != MT_SENSE)
		sc->sc_flags &= ~H_WRITTEN;

	switch (er & MTER_INTCODE) {

	case MTER_EOT:
		sc->sc_flags |= H_EOT;
		/* fall into MTER_DONE */

	case MTER_DONE:
		/* If this is a command buffer, just update the status.	*/
		if (bp == &cmtbuf[MTUNIT(bp->b_dev)]) {
	done:
			if (bp->b_command == MT_SENSE)
				sc->sc_dsreg = MASKREG(mtaddr->mtds);
			return (MBN_DONE);
		}

		/*
		 * It's not a command buffer, must be a cooked I/O
		 * skip operation (perhaps a shaky assumption, but it
		 * wasn't my idea).
		 */
		if ((fc = bdbtofsb(bp->b_blkno) - sc->sc_blkno) < 0)
			sc->sc_blkno -= MIN(0377, -fc);
		else
			sc->sc_blkno += MIN(0377, fc);
		return (MBN_RETRY);

	case MTER_ONLINE:		/* ddj -- shouldn't happen but did */
	case MTER_RWDING:
		return (MBN_SKIP);	/* ignore "rewind started" interrupt */

	case MTER_NOTCAP:
		tprintf(sc->sc_ctty, "mu%d: blank tape\n", MUUNIT(bp->b_dev));
		bp->b_flags |= B_ERROR;
		return (MBN_DONE);

	case MTER_TM:
	case MTER_LEOT:
		/*
		 * For an ioctl skip operation, count a tape mark as
		 * a record.  If there's anything left to do, update
		 * the repeat count and re-start the command.
		 */
		if (bp == &cmtbuf[MTUNIT(bp->b_dev)]) {
			if ((sc->sc_resid = bp->b_repcnt = fc - 1) == 0)
				return (MBN_DONE);
			else
				return (MBN_RETRY);
		} else {
			/*
			 * Cooked I/O again.  Just update the books and
			 * wait for someone else to return end of file or
			 * complain about a bad seek.
			 */
			if (sc->sc_blkno > bdbtofsb(bp->b_blkno)) {
				sc->sc_nxrec = bdbtofsb(bp->b_blkno) + fc - 1;
				sc->sc_blkno = sc->sc_nxrec;
			} else {
				sc->sc_nxrec = bdbtofsb(bp->b_blkno) - fc;
				sc->sc_blkno = sc->sc_nxrec + 1;
			}
		}
		return (MBN_RETRY);

	case MTER_FPT:
		tprintf(sc->sc_ctty, "mu%d: no write ring\n",
		    MUUNIT(bp->b_dev));
		bp->b_flags |= B_ERROR;
		return (MBN_DONE);

	case MTER_OFFLINE:
		/* If `off line' was intentional, don't complain. */
		if (bp == &cmtbuf[MTUNIT(bp->b_dev)] &&
		    bp->b_command == MT_UNLOAD)
			return(MBN_DONE);
		if (sc->sc_openf > 0) {
			sc->sc_openf = -1;
			tprintf(sc->sc_ctty, "mu%d: offline\n",
			    MUUNIT(bp->b_dev));
		}
		bp->b_flags |= B_ERROR;
		return (MBN_DONE);

	case MTER_NOTAVL:
		if (sc->sc_openf > 0) {
			sc->sc_openf = -1;
			tprintf(sc->sc_ctty, "mu%d: offline (port selector)\n",
			    MUUNIT(bp->b_dev));
		}
		bp->b_flags |= B_ERROR;
		return (MBN_DONE);

	case MTER_BOT:
		if (bp == &cmtbuf[MTUNIT(bp->b_dev)])
			goto done;
		/* fall through */

	default:
		tprintf(sc->sc_ctty, "\
mu%d: hard error (non data transfer) rn=%d bn=%d er=0%o ds=%b\n",
		    MUUNIT(bp->b_dev), sc->sc_blkno, bp->b_blkno,
		    er, MASKREG(sc->sc_dsreg), mtds_bits);
#ifdef MTLERRM
		mtintfail(er);
#endif
		if ((er & MTER_INTCODE) == MTER_TMFLTB ||
		    (er & MTER_INTCODE) == MTER_MBFLT)
			mtcreset(mtaddr);	/* reset the controller */
		bp->b_flags |= B_ERROR;
		return (MBN_DONE);
	}
	/* NOTREACHED */
}

void
mtcreset(mtaddr)
	register struct mtdevice *mtaddr;
{
	register int i;

	mtaddr->mtid = MTID_CLR;		/* reset the TM78 */
	DELAY(200);
	for (i = MTTIMEOUT; i > 0; i--) {
		DELAY(50);			/* don't nag */
		if ((mtaddr->mtid & MTID_RDY) != 0)
			return;			/* exit when ready */
	}
	printf("mt: controller hung\n");
}

/*ARGSUSED*/
mtioctl(dev, cmd, data, flag)
	dev_t dev;
	int cmd;
	caddr_t data;
	int flag;
{
	register struct mu_softc *sc = &mu_softc[MUUNIT(dev)];
	register struct buf *bp = &cmtbuf[MTUNIT(dev)];
	register struct mtop *mtop;
	register struct mtget *mtget;
	int callcount, fcount, error = 0;
	int op;

	/* We depend on the values and order of the MT codes here. */

	static mtops[] =
	{MT_WTM,MT_SFORWF,MT_SREVF,MT_SFORW,MT_SREV,MT_REW,MT_UNLOAD,MT_SENSE};

	switch (cmd) {

	/* tape operation */

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

		case MTWEOF:
			callcount = mtop->mt_count;
			fcount = 1;
			break;

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

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

		case MTREW: case MTOFFL:
			callcount = 1;
			fcount = 1;
			break;

		default:
			return (ENXIO);
		}
		if (callcount <= 0 || fcount <= 0)
			return (EINVAL);
		op = mtops[mtop->mt_op];
		if (op == MT_WTM)
			op |= sc->sc_dens;
		while (--callcount >= 0) {
			register int n, fc = fcount;

			do {
				n = MIN(fc, 0xff);
				mtcommand(dev, op, n);
				n -= sc->sc_resid;
				fc -= n;
				switch (mtop->mt_op) {

				case MTWEOF:
					sc->sc_blkno += (daddr_t)n;
					sc->sc_nxrec = sc->sc_blkno - 1;
					break;

				case MTOFFL:
				case MTREW:
				case MTFSF:
					sc->sc_blkno = (daddr_t)0;
					sc->sc_nxrec = (daddr_t)INF;
					break;

				case MTBSF:
					if (sc->sc_resid) {
						sc->sc_blkno = (daddr_t)0;
						sc->sc_nxrec = (daddr_t)INF;
					} else {
						sc->sc_blkno = (daddr_t)(-1);
						sc->sc_nxrec = (daddr_t)(-1);
					}
					break;

				case MTFSR:
					sc->sc_blkno += (daddr_t)n;
					break;

				case MTBSR:
					sc->sc_blkno -= (daddr_t)n;
					break;
				}
				if (sc->sc_resid)
					break;
			} while (fc);
			if (fc) {
				sc->sc_resid = callcount + fc;
				if (mtop->mt_op == MTFSR ||
				    mtop->mt_op == MTBSR)
					return (EIO);
				break;
			}
			if (bp->b_flags & B_ERROR)
				break;
		}
		if (bp->b_flags&B_ERROR)
			if ((error = bp->b_error)==0)
				return (EIO);
		return (error);

	/* tape status */
	case MTIOCGET:
		mtget = (struct mtget *)data;
		mtget->mt_erreg = sc->sc_erreg;
		mtget->mt_resid = sc->sc_resid;
		mtcommand(dev, MT_SENSE, 1);	/* update drive status */
		mtget->mt_dsreg = sc->sc_dsreg;
		mtget->mt_type = MT_ISMT;
		break;

	/* ignore EOT condition */
	case MTIOCIEOT:
		sc->sc_flags |= H_IEOT;
		break;

	/* enable EOT condition */
	case MTIOCEEOT:
		sc->sc_flags &= ~H_IEOT;
		break;

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

#define	DBSIZE	20

mtdump()
{
	register struct mba_device *mi;
	register struct mba_regs *mp;
	int blk, num;
	int start;

	start = 0;
	num = maxfree;
#define	phys(a,b)		((b)((int)(a)&0x7fffffff))
	if (mtinfo[0] == 0)
		return (ENXIO);
	mi = phys(mtinfo[0], struct mba_device *);
	mp = phys(mi->mi_hd, struct mba_hd *)->mh_physmba;
	mp->mba_cr = MBCR_IE;
#if lint
	blk = 0; num = blk; start = num; blk = start;
	return (0);
#endif
#ifdef notyet
	mtaddr = (struct mtdevice *)&mp->mba_drv[mi->mi_drive];
	mtaddr->mttc = MTTC_PDP11|MTTC_1600BPI;
	mtaddr->mtcs1 = MT_DCLR|MT_GO;
	while (num > 0) {
		blk = num > DBSIZE ? DBSIZE : num;
		mtdwrite(start, blk, mtaddr, mp);
		start += blk;
		num -= blk;
	}
	mteof(mtaddr);
	mteof(mtaddr);
	mtwait(mtaddr);
	if (mtaddr->mtds&MTDS_ERR)
		return (EIO);
	mtaddr->mtcs1 = MT_REW|MT_GO;
	return (0);
}

mtdwrite(dbuf, num, mtaddr, mp)
	register dbuf, num;
	register struct mtdevice *mtaddr;
	struct mba_regs *mp;
{
	register struct pte *io;
	register int i;

	mtwait(mtaddr);
	io = mp->mba_map;
	for (i = 0; i < num; i++)
		*(int *)io++ = dbuf++ | PG_V;
	mtaddr->mtfc = -(num*NBPG);
	mp->mba_sr = -1;
	mp->mba_bcr = -(num*NBPG);
	mp->mba_var = 0;
	mtaddr->mtcs1 = MT_WCOM|MT_GO;
}

mtwait(mtaddr)
	struct mtdevice *mtaddr;
{
	register s;

	do
		s = mtaddr->mtds;
	while ((s & MTDS_DRY) == 0);
}

mteof(mtaddr)
	struct mtdevice *mtaddr;
{

	mtwait(mtaddr);
	mtaddr->mtcs1 = MT_WEOF|MT_GO;
#endif notyet
}

#ifdef MTLERRM
/*
 * Failure messages for each failure code, per interrupt code.
 * Each table ends with a code of -1 as a default.
 */
struct fmesg {
	int	f_code;
	char	*f_mesg;
};

static char unclass[] = "unclassified failure code";

/* MTER_BOT */
static struct fmesg botmsg[] = {
	01,	"tape was at BOT",
	02,	"BOT seen after tape started",
	03,	"ARA ID detected",
	-1,	unclass
};

/* MTER_NOTRDY */
static struct fmesg notrdymsg[] = {
	01,	"TU on-line but not ready",
	02,	"fatal error has occurred",
	03,	"access allowed but not ready",
	-1,	unclass
};

/* MTER_NOTCAP */
static struct fmesg notcapmsg[] = {
	01,	"no record found within 25 feet",
	02,	"ID burst neither PE nor GCR",
	03,	"ARA ID not found",
	04,	"no gap found after ID burst",
	-1,	unclass
};

/* MTER_LONGREC */
static struct fmesg longrecmsg[] = {
	00,	"extended sense data not found",
	01,	"extended sense data updated",
	-1,	unclass
};

/* MTER_UNREAD, MTER_ERROR, MTER_EOTERR, MTER_BADTAPE */
static struct fmesg code22msg[] = {
	01,	"GCR write error",
	02,	"GCR read error",
	03,	"PE read error",
	04,	"PE write error",
	05,	"at least 1 bit set in ECCSTA",
	06,	"PE write error",
	07,	"GCR write error",
	010,	"RSTAT contains bad code",
	011,	"PE write error",
	012,	"MASSBUS parity error",
	013,	"invalid data transferred",
	-1,	unclass
};

/* MTER_TMFLTA */
static struct fmesg tmfltamsg[] = {
	01,	"illegal command code",
	02,	"DT command issued when NDT command active",
	03,	"WMC error",
	04,	"RUN not received from MASSBUS controller",
	05,	"mismatch in command read - function routine",
	06,	"ECC ROM parity error",
	07,	"XMC ROM parity error",
	010,	"mismatch in command read - ID burst command",
	011,	"mismatch in command read - verify ARA burst command",
	012,	"mismatch in command read - verify ARA ID command",
	013,	"mismatch in command read - verify gap command",
	014,	"mismatch in command read - read id burst command",
	015,	"mismatch in command read - verify ARA ID command",
	016,	"mismatch in command read - verify gap command",
	017,	"mismatch in command read - find gap command",
	020,	"WMC LEFT failed to set",
	021,	"XL PE set in INTSTA register",
	022,	"XMC DONE did not set",
	023,	"WMC ROM PE or RD PE set in WMCERR register",
	-1,	unclass
};

/* MTER_TUFLTA */
static struct fmesg tufltamsg[] = {
	01,	"TU status parity error",
	02,	"TU command parity error",
	03,	"rewinding tape went offline",
	04,	"tape went not ready during DSE",
	05,	"TU CMD status changed during DSE",
	06,	"TU never came up to speed",
	07,	"TU velocity changed",
	010,	"TU CMD did not load correctly to start tape motion",
	011,	"TU CMD did not load correctly to set drive density",
	012,	"TU CMD did not load correctly to start tape motion to write BOT ID",
	013,	"TU CMD did not load correctly to backup tape to BOT after failing to write BOT ID",
	014,	"failed to write density ID burst",
	015,	"failed to write ARA burst",
	016,	"failed to write ARA ID",
	017,	"ARA error bit set in MTA status B register",
	021,	"could not find a gap after ID code was written correctly",
	022,	"TU CMD did not load correctly to start tape motion to read ID burst",
	023,	"timeout looking for BOT after detecting ARA ID burst",
	024,	"failed to write tape mark",
	025,	"tape never came up to speed while trying to reposition for retry of writing tape mark",
	026,	"TU CMD did not load correctly to start tape motion in erase gap routine",
	027,	"could not detect a gap in in erase gap routine",
	030,	"could not detect a gap after writing record",
	031,	"read path terminated before entire record was written",
	032,	"could not find a gap after writing record and read path terminated early",
	033,	"TU CMD did not load correctly to backup for retry of write tape mark",
	034,	"TU velocity changed after up to speed while trying to reposition for retry of writing tape mark",
	035,	"TU CMD did not load correctly to backup to retry a load of BOT ID",
	036,	"timeout looking for BOT after failing to write BOT ID",
	037,	"TU velocity changed while writing PE gap before starting to write record",
	040,	"TU CMD did not load correctly to set PE tape density at start of write BOT ID burst",
	041,	"TU CMD did not load correctly to set GCR tape density after writing Density ID",
	042,	"TU CMD did not load correctly to set PE tape density at start of read from BOT",
	043,	"TU CMD did not load correctly to set GCR tape density after reading a GCR Density ID burst",
};

/* MTER_TMFLTB */
static char inlinetest[] = "inline test failed";
static struct fmesg tmfltbmsg[] = {
	00,	"RST0 interrupt occurred with TM RDY set",
	01,	"power failed to interrupt",
	02,	"unknown interrupt on channel 5.5",
	03,	"unknown interrupt on channel 6.5",
	04,	"unknown interrupt on channel 7",
	05,	"unknown interrupt on channel 7.5",
	06,	"CAS contention retry count expired",
	07,	"CAS contention error not retryable",
	010,	"queue error, could not find queue entry",
	011,	"queue entry already full",
	012,	"8085 ROM parity error",
	013,	inlinetest,
	013,	inlinetest,
	014,	inlinetest,
	015,	inlinetest,
	016,	inlinetest,
	017,	inlinetest,
	020,	inlinetest,
	021,	inlinetest,
	022,	inlinetest,
	023,	inlinetest,
	024,	inlinetest,
	025,	inlinetest,
	026,	inlinetest,
	027,	inlinetest,
	030,	inlinetest,
	031,	inlinetest,
	032,	inlinetest,
	033,	inlinetest,
	034,	inlinetest,
	035,	inlinetest,
	036,	inlinetest,
	037,	inlinetest,
	040,	inlinetest,
	041,	inlinetest,
	042,	inlinetest,
	043,	inlinetest,
	044,	inlinetest,
	045,	inlinetest,
	046,	inlinetest,
	047,	inlinetest,
	050,	inlinetest,
	051,	inlinetest,
	052,	inlinetest,
	053,	inlinetest,
	054,	inlinetest,
	055,	inlinetest,
	056,	inlinetest,
	057,	inlinetest,
	-1,	unclass
};

/* MTER_MBFLT */
static struct fmesg mbfltmsg[] = {
	01,	"control bus parity error",
	02,	"illegal register referenced",
	-1,	unclass
};

/*
 * MTER_LEOT, MTER_RWDING, NTER_NOTAVL, MTER_NONEX, MTER_KEYFAIL,
 * and default: no failure message.
 */
static struct fmesg nullmsg[] = {
	-1,	""
};

/*
 * Interrupt code table.
 */
static struct errmsg {
	int	e_code;
	char	*e_mesg;
	struct	fmesg *e_fmesg;
} errmsg[] = {
	MTER_BOT,	"unexpected BOT",	botmsg,
	MTER_LEOT,	"unexpected LEOT",	nullmsg,
	MTER_RWDING,	"tape rewinding",	nullmsg,
	MTER_NOTRDY,	"drive not ready",	notrdymsg,
	MTER_NOTAVL,	"drive not available",	nullmsg,
	MTER_NONEX,	"unit does not exist",	nullmsg,
	MTER_NOTCAP,	"not capable",		notcapmsg,
	MTER_LONGREC,	"long record",		longrecmsg,
	MTER_UNREAD,	"unreadable record",	code22msg,
	MTER_ERROR,	"error",		code22msg,
	MTER_EOTERR,	"EOT error",		code22msg,
	MTER_BADTAPE,	"tape position lost",	code22msg,
	MTER_TMFLTA,	"TM fault A",		tmfltamsg,
	MTER_TUFLTA,	"TU fault A",		tufltamsg,
	MTER_TMFLTB,	"TM fault B",		tmfltbmsg,
	MTER_MBFLT,	"MB fault",		mbfltmsg,
	MTER_KEYFAIL,	"keypad entry error",	nullmsg,
	-1,		"unclassified error",	nullmsg
};

/*
 * Decode an interrupt-time failure.
 */
mtintfail(erreg)
	int erreg;
{
	register struct errmsg *e;
	register struct fmesg *f;
	register int ecode, fcode;

	ecode = erreg & MTER_INTCODE;
	fcode = (erreg & MTER_FAILCODE) >> MTER_FSHIFT;
	for (e = errmsg; e->e_code >= 0; e++)
		if (e->e_code == ecode)
			break;
	for (f = e->e_fmesg; f->f_code >= 0; f++)
		if (f->f_code == fcode)
			break;
	printf("    interrupt code = 0%o <%s>\n", ecode, e->e_mesg);
	printf("    failure code = 0%o <%s>\n", fcode, f->f_mesg);
}
#endif /* MTLERRM */
#endif /* NMT > 0 */