/*
 * Interdata 800 bpi mag tape driver
 *
 * This is a 'first attempt' at a driver, and should be thrown
 * away and rewritten.  The major known problems are:
 *      - it probably won't work for multiple transports
 *      - attempting a backspace from loadpoint will hang up the controller
 *        (this can be cleared manually by a reset/forward/reset/online
 *        sequence)
 *      - error checking, particularly for end-of-tape, is insufficient
 *
 * This has now advanced to being a second attempt, which may have
 * added improvements in the following areas
 *      - better checking for strange situations
 *              (like backspace or rewind from load point)
 *              (or write protected tapes)
 *      - more intelligent handling of EOT
 *      - better handling of write errs (leave some blank tape before retry)
 *
 *                              ... kre Oct 81
 *
 * 2/6/82 twg!day:
 * If no write-enable ring, and you attempt to write, tape positions
 * back a block.
 * The erase-forward on write errors can bomb out with large blocks
 * because you can run out of retries before you have spaced forward
 * past the bad spot.
 */

/* from dev/mt.c        2.4 (Melbourne) 81/10/07        */

#include "../h/local.h"

#ifdef  SCCS_ID
static char SCCS_ID [] = "@(#)mt.c        3.6      10:43:50 - 83/02/25 ";
#endif  SCCS_ID

#include "../h/param.h"
#include "../h/conf.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/buf.h"
#include "../h/selch.h"
#include "../h/tty.h"

/*  NMTERR is the max number of error retries.
 *  No error printouts if loudtape == NMTERR.
 *  Only fatal error printouts if loudtape == NMTERR -1.
 *  All error printouts if loudtape == NMTERR -1.
 *  It can be patched in the a.out or in core.
 */
#define NMTERR  8
int loudtape = NMTERR - 1;

extern int      nmt;            /* number of tape transports */
extern char     mtaddr[];       /* tape addresses */
extern int      mtselch;        /* selch number for tapes */

int     mtio(), mtscintr();

struct buf      mtab;

/* Values for m_status */
#define ISOPENED        01
#define ISDU            02
#define ISWRITING       04
#define ISBOT           010
#define ISEOT           020
#define ISWPROT         040
#define BACKWARDS       0100
#define EOTISOK         0200
#define ISWERR          0400

/*
 * The next bit of this (down to & including the struct tape definition,
 * but not the space allocation) should be in some include file somewhere.
 * Just for now (and probably forever) I can't be bothered.
 */
#define MTIOFUNC        (('m'<<8)|1)
#define MTIOCEOT        (('m'<<8)|2)
#define MTIOCGET        (('m'<<8)|3)

struct tape {
	int     m_status;               /* status of transport */
	int     m_blkno;                /* current block position in file */
	int     m_lastrec;              /* next record # at end of file */
	int     m_eot;                  /* number of blocks past EOT */
} tape[4];

/* Values for mtab.b_active */
#define INACTIVE 0      /* inactive */
#define SCOM     1
#define SSFOR    2      /* space forward */
#define SSREV    3      /* space reverse */
#define SIO      4      /* do read or write */
#define SBOT     5      /* write a filemark */

struct selchq   mtscq = {
	&mtio,
	&mtscintr,
	0,
	0
};

struct buf      rmtbuf, cmtbuf;
caddr_t mtendaddr;              /* end address from controller */

/*
 * Tape controller commands
 */
typedef int Tapecmd;
#define ENABLE  0x40
#define CLEAR   0x20
#define READ    0x21    /* Read a record */
#define WRITE   0x22    /* Write a record */
#define WF      0x30    /* Write filemark */
#define RW      0x38    /* Rewind */
#define FF      0x23    /* Forward space file */
#define BF      0x13    /* Back space file */
#define BR      0x11    /* Back space record */

#define OPEN    0xff

/*
 * Tape status
 */
#define ERR     0x80
#define EOF     0x40
#define EOT     0x20
#define NMTN    0x10
#define BSY     0x04
#define DU      0x01

mtopen(dev, flag)
{
	register struct tape *mt;
	register unit;
	extern short isp[];
	extern tapeint();

	mtab.b_flags |= B_TAPE;
	if ((unit=minor(dev)&03) >= nmt
	  || (mt = &tape[unit])->m_status & ISOPENED) {
		u.u_error = ENXIO;
		return;
	}
	/*
	 * the following line is an attempt to overcome a problem
	 * with our tape drive (all tape drives) that causes huge
	 * numbers of unwanted interrupts, flooding the system queue
	 * and stopping unix. Fix is to have low core immediate interrupt
	 * handler disable the device on first interrupt. We must
	 * enable it wherever we expect to get an interrupt
	 */
/*
 isp[mtaddr[unit]] = (short)tapeint;
*/
	mt->m_status = mt->m_blkno = 0;
	if (!(flag & 2)) /* open for writing */
		mt->m_status = ISWPROT;
	mt->m_lastrec = 1000000;

	mtcommand(dev, OPEN);
	if (mt->m_status & ISDU)
		u.u_error = ENXIO;

	if (u.u_error == 0)
		mt->m_status |= ISOPENED;
}

/* ARGSUSED */
mtclose(dev, flag)
register int dev;
{
	register struct tape *mt;
	register writing;

	mt = &tape[minor(dev)&03];
	writing = 0;

	if (mt->m_status&ISWRITING) {
		writing++;
		mtcommand(dev, WF);
		mtcommand(dev, WF);
	}
	if (minor(dev) & 04) {
		if (writing)
			mtcommand(dev, BR);
	} else
		mtcommand(dev, RW);
	mt->m_status = 0;
}

mtcommand(dev, command)
Tapecmd command;
{
	register struct buf *bp;

	trace(0x20<<16, "mtcommand", command);
	bp = &cmtbuf;
	spl5();
	while (bp->b_flags & B_BUSY) {
		bp->b_flags |= B_WANTED;
		sleep(bp, PRIBIO);
	}
	bp->b_flags |= B_BUSY | B_READ;
	spl0();

	bp->b_dev = dev;
	bp->b_blkno = (daddr_t) command;
	mtstrategy(bp);
	iowait(bp);

	if (bp->b_flags & B_WANTED)
		wakeup(bp);
	bp->b_flags = 0;
}

mtstrategy(abp)
struct buf *abp;
{
	register struct buf *bp;
	register struct tape *mt;
	register int *p;
	register daddr_t fsblkno;

	mt = &tape[minor((bp = abp)->b_dev)&03];
#ifdef  UCB_NKB
	fsblkno = dbtofsb(bp->b_blkno);
#else   UCB_NKB
	fsblkno = bp->b_blkno;
#endif  UCB_NKB

	if (bp != &cmtbuf) {
		p = &mt->m_lastrec;
		if (fsblkno > *p) {
			bp->b_flags |= B_ERROR;
			iodone(bp);
			return;
		}
		if (fsblkno == *p && (bp->b_flags&B_READ)) {
			bp->b_resid = bp->b_bcount;
			clrbuf(bp);
			iodone(bp);
			return;
		}
		if ((bp->b_flags&B_READ) == 0)
			*p = fsblkno + 1;
	} else {
		register cmd = (Tapecmd) bp->b_blkno;

		if ((cmd == BR || cmd == RW) && mt->m_status&ISBOT ||
		    (cmd == WF && mt->m_status&ISWPROT)) {
			if (cmd == WF)
				bp->b_flags |= B_ERROR;
			iodone(bp);
			return;
		}
	}
	bp->av_forw = 0;
	spl5();
	if (mtab.b_actf)
		mtab.b_actl->av_forw = bp;
	else
		mtab.b_actf = bp;
	mtab.b_actl = bp;
	if (mtab.b_active == INACTIVE)
		mtstart();
	spl0();
}

mtstart()
{
	register struct tape *mt;
	register struct buf *bp;
	register daddr_t fsblkno;

	while (bp = mtab.b_actf) {
#ifdef  UCB_NKB
		fsblkno = dbtofsb(bp->b_blkno);
#else   UCB_NKB
		fsblkno = bp->b_blkno;
#endif  UCB_NKB
		mt = &tape[minor(bp->b_dev)&03];
		if (bp != &cmtbuf && (mt->m_status&ISDU)) {
	abort:
			bp->b_flags |= B_ERROR;
			mtab.b_actf = bp->av_forw;
			mtab.b_active = INACTIVE;
			iodone(bp);
			continue;
		}
		mt->m_status &= ~ISWRITING;
		if (bp == &cmtbuf)
			mtab.b_active = SCOM;
		else if (fsblkno == mt->m_blkno) {
			if (mt->m_eot > NMTERR)
				goto abort;
			mtab.b_active = SIO;
			if ((bp->b_flags&B_READ) == 0) {
				if (mt->m_status & ISWPROT)
					goto abort;
				if (mt->m_eot && !(mt->m_status & EOTISOK)) {
					bp->b_error = ENOSPC;
					if (mt->m_lastrec > fsblkno)
						mt->m_lastrec = fsblkno;
					goto abort;
				}
				mt->m_status |= ISWRITING;
				/*
				 * According to the Interdata tape manual,
				 * when writing the first block it is necessary
				 * to write a file mark first and backspace
				 * over it.  If this is not done, the block
				 * is sometimes not written correctly.
				 *
				 * We also do this if we have had a write err
				 * to skip the bad spot
				 */
				if (mt->m_status&(ISBOT|ISWERR)) {
					mtab.b_active = SBOT;
					mt->m_status &= ~ISWERR;
				}
			}
		}
		else if (fsblkno > mt->m_blkno) {
			if (mt->m_eot > NMTERR)
				goto abort;
			mtab.b_active = SSFOR;
		} else
			mtab.b_active = SSREV;
		selchreq(mtselch, &mtscq, minor(bp->b_dev)&03);
		return;
	}
}

mtio(unit)
register  int     unit;
{
	register struct buf *bp;
	register addr;
	register stat;
	register struct tape *mt;

	trace(0x20<<16, "mtio", mtab.b_active);
	if ((bp = mtab.b_actf) == 0)
		return;

	unit = minor(bp->b_dev)&03;
	addr = mtaddr[unit];
	mt = &tape[unit];
	mt->m_status &= ~BACKWARDS;

	switch(mtab.b_active) {

	case SCOM:
		switch ((Tapecmd) bp->b_blkno) {

		case OPEN:
			oc(addr, CLEAR);
			oc(addr, ENABLE);
			if ((stat = ss(addr))&DU)
				mt->m_status |= ISDU;
			/*
			 * here we just "assume" that EOT when tape is opened
			 * means load point. This is not so disastrous, as we
			 * will fix it after first i/o. It does however, make
			 * it illegal to do a BR at this point.
			 */
			if (stat&EOT)
			{
				mt->m_status |= ISBOT;
				mt->m_eot = 0; /* reset eot flg at load pt */
			}
			mtab.b_active = INACTIVE;
			break;

		case RW:
			/*
			 * nb: we don't check ISBOT here, as we want to
			 * be able to rewind if the tape was opened at
			 * or past the EOT mark (which is incorrectly
			 * diagnosed as BOT).
			 */
			oc(addr, RW);
			stat = ss(addr);
			if (!(stat & BSY)) {
				if (!(stat & (DU|ERR))) {
					mt->m_status |= ISBOT;
					mt->m_blkno = 0;
					mt->m_eot = 0; /* reset eot flg */
				}
	fini:
				mtab.b_active = 0;
				mtab.b_actf = bp->av_forw;
				iodone(bp);
				mtstart();
			}
			mt->m_status |= BACKWARDS;
			break;

		case WF:
			oc(addr, WF);
			if (ss(addr) & NMTN)
				goto nowrit;            /* no write ring */
			break;

		case BR:
		case BF:
			if (mt->m_status & ISBOT)
				goto fini;
			oc(addr, (Tapecmd) bp->b_blkno);
			if (ss(addr) & NMTN)
				goto fini;
			mt->m_status |= BACKWARDS;
			break;

		case READ:
		case FF:
		/* shouldn't need default: as all possible cases are explicit */
		default:
			oc(addr, (Tapecmd) bp->b_blkno);
			if (ss(addr)&NMTN)
				goto fini;
			break;
		}

		trace(0x10<<16, "mtoc", bp->b_blkno);
		trace(0x10<<16, "status", ss(addr));
		mtab.b_actf = bp->av_forw;
		iodone(bp);
		selchfree(mtselch);
		return;

	case SSREV:
		if (mt->m_status & ISBOT)
			goto fini;
		oc(addr, BR);
		stat = ss(addr);
		trace(0x10<<16, "mtoc", BR);
		trace(0x10<<16, "status", stat);
		if (stat & NMTN)
			goto fini;
		mt->m_status |= BACKWARDS;
		return;

	case SBOT:
		oc(addr, WF);
		stat = ss(addr);
		trace(0x10<<16, "mtoc", WF);
		trace(0x10<<16, "status", stat);
		if (stat & NMTN) {              /* write protected */
	nowrit:
			bp->b_flags |= B_ERROR;
			mt->m_status |= ISWPROT;
			goto fini;
		}
		return;

	case SSFOR:
	case SIO:
		oc(mtselch, STOP);
		wdh(mtselch, bp->b_un.b_addr);
		wdh(mtselch, bp->b_un.b_addr + bp->b_bcount - 1);

		trace(0x10<<16, "mtrw", bp->b_un.b_addr);
		if ((bp->b_flags&B_READ) || mtab.b_active == SSFOR) {
			oc(addr, READ);
/*
			oc(mtselch, READ_GO);
*/
			sgo(mtselch, READ_GO, addr);
		} else {
			oc(addr, WRITE);
			/*
			 * the following test might be unnecessary, as
			 * contrary to statements in the P.E. tape manual,
			 * NMTN always appears to reset after a WRITE cmd
			 * If there is no write ring, it is indicated by
			 * the universal ERR bit, after the tape has actually
			 * gone through the motions of writing the block.
			 * People with weak hearts should be warned not
			 * to watch.
			 */
			if (ss(addr) & NMTN)
				goto nowrit;
/*
			oc(mtselch, GO);
*/
			sgo(mtselch, GO, addr);
		}
	}
}

mtscintr(dev, stat, unit)
{
	oc(mtselch, STOP);
	/*
	 * It would be nice to be able to do without the length rounding
	 * that goes on here so tapes with odd block lengths could be handled.
	 * But, selch's are dumb, don't act like the manual says, and it
	 * doesn't work. (NB: this is from with experience on an 8/32, it
	 * hasn't been tried on 3200 series machines)
	 * (it is ok for writing, so it could be done then, but that is
	 * not usually the case that bothers anyone)
	 */
	mtendaddr = (caddr_t) ((rdh(mtselch)+1) & ~01);
}

mtintr(dev, stat)
{
	register struct buf *bp;
	register struct tape *mt;
	register op;

	trace(0x10<<16, "interrupt", mtaddr[dev]);
	trace(0x10<<16, "status", stat);

	/*
	 * If NMTN status is not set, wait for another
	 * interrupt. NMTN should be the last bit to set.
	 *
	 * NB: we must enable interrupts again here, as low level code
	 * has disable them, If it hadn't, we might have been swamped
	 * with them by now.
	 */
	if ((stat&NMTN) == 0)
		return;

	op = mtab.b_active;
	mtab.b_active = INACTIVE;
	if ((bp = mtab.b_actf) == 0)
		return;

	mt = &tape[minor(bp->b_dev)&03];
	if (stat & EOT) {
		if (mt->m_status & BACKWARDS) {
			if ((mt->m_status & ISEOT) == 0) {
				mt->m_blkno = 0;
				mt->m_status |= ISBOT;
				mt->m_eot = 0;
			} else if (--mt->m_eot < 0)
				mt->m_eot = 0;
		} else {
			mt->m_status |= ISEOT;
			mt->m_eot++;
		}
	} else {
		mt->m_status &= ~(ISEOT|ISBOT);
		mt->m_eot = 0;
	}

	if (op == SCOM) {
		mtstart();
		return;
	}

	selchfree(mtselch);
	mt->m_status &= ~ISDU;
	if (stat&DU) {
		mt->m_status |= ISDU;
		mt->m_status &= ~ISWPROT;
		mtab.b_actf = bp->av_forw;
		bp->b_flags |= B_ERROR;
		iodone(bp);
		mtstart();
		return;
	}

	if (op == SSREV) {
		if ((mt->m_status & ISBOT) == 0)
			mt->m_blkno--;
	} else
		mt->m_blkno++;

	if ((stat&ERR) && op == SIO) {
		if (loudtape <= mtab.b_errcnt)
			deverror(bp, stat, dev);
		if (++mtab.b_errcnt < NMTERR) {
			if ((bp->b_flags & B_READ) == 0)
				mt->m_status |= ISWERR;
			mtab.b_active = SSREV;
			selchreq(mtselch, &mtscq, minor(bp->b_dev)&03);
			return;
		}
		bp->b_flags |= B_ERROR;
	}

	if (op == SIO || (stat&(EOF|EOT)) && op == SSFOR) {
		mtab.b_errcnt = 0;
		mtab.b_actf = bp->av_forw;
		bp->b_resid = stat&(EOF|EOT) ? bp->b_bcount :
			bp->b_bcount - (mtendaddr - bp->b_un.b_addr);
		iodone(bp);
	}

	mtstart();
}

/*
 * Raw magtape interface
 */
mtread(dev)
{
	mtseek(dev);
	physio(mtstrategy, &rmtbuf, dev, B_READ);
}

mtwrite(dev)
{
	mtseek(dev);
	physio(mtstrategy, &rmtbuf, dev, B_WRITE);
}

/*
 * Kludge to ignore seeks on raw mag tape by making block no. look right
 */
mtseek(dev)
{
	register struct tape *mt;

	mt = &tape[minor(dev)&03];
	mt->m_lastrec = (mt->m_blkno = u.u_offset>>BSHIFT) + 1;
}

/*
 * Ioctl call is used to issue commands to raw mag tape
 * First word is command function
 */

char mtcmds[8] = {              /* command functions */
	FF,                     /* 0 - forward space file */
	BF,                     /* 1 - back space file */
	0,                      /* 2 */
	WF,                     /* 3 - write file mark */
	READ,                   /* 4 - forward space record
				 *      (since there is no FR command, we simply
				 *      do a read without starting up the selch
				 *      and ignore the overrun error)
				 */
	BR,                     /* 5 - back space record */
	0,                      /* 6 */
	RW                      /* 7 - rewind */
};

/* ARGSUSED */
mtioctl(dev, cmd, addr, flag)
caddr_t addr;
{
	register fn, com;
	register struct tape *mt = &tape[minor(dev)&03];

	switch (cmd) {

	default:
		return(1);

	case TIOCSETP:                  /* for old timers */
	case MTIOFUNC:
		if ((fn = fuword(addr)) < 0 || fn > 7 || !(com = mtcmds[fn])) {
			if (fn == -1)
				u.u_error = EFAULT;
			else
				u.u_error = ENXIO;
			return(0);
		}
		if (com == WF && mt->m_status & ISWPROT) {
			u.u_error = EIO;
			return(0);
		}
		mtcommand(dev, com);
		return(0);

	case MTIOCEOT:
		mt->m_status |= EOTISOK;
		return(0);

	case MTIOCGET:
		if (copyout((caddr_t)mt, (caddr_t)addr, sizeof(struct tape)))
			u.u_error = EFAULT;
		return(0);
	}
}

/*
 * Dump all of memory to magtape in DUMPBLK-byte blocks
 */
#define DUMPBLK         8192
mtdump()
{
	extern char *memtop;
	register int selch, mt;
	register char *memp;

	/* Set up device addresses */
	selch = mtselch;
	mt = mtaddr[0];

	/*
	 * Write a filemark at the start of the tape & backspace over it
	 */
	oc(selch, STOP);
	oc(mt, CLEAR);
	oc(mt, RW);
	mtwait(mt);
	oc(mt, WF);
	mtwait(mt);
	oc(mt, BR);
	mtwait(mt);

	/*
	 * Write all memory to tape
	 */
	for (memp = (char *)0; memp < memtop; memp += DUMPBLK) {
		wdh(selch, memp);
		wdh(selch, memp + DUMPBLK - 1);
		oc(mt, WRITE);
		oc(selch, GO);
		while (ss(selch) & SELCHBSY)
			;
		oc(selch, STOP);
		mtwait(mt);
	}
	/*
	 * Write two filemarks end of tape and rewind
	 */
	oc(mt, WF);
	mtwait(mt);
	oc(mt, RW);
	mtwait(mt);
}

/*
 * Sense-status loop to wait for 'NO MOTION' status
 */
mtwait(addr)
{
	while ((ss(addr) & NMTN) == 0)
		;
}

