/*
 * 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 [] = "@(#)mta.c        3.2      14:44:58 - 83/04/11 ";
#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 loudatape == NMTERR.
 *  Only fatal error printouts if loudatape == NMTERR -1.
 *  All error printouts if loudatape == NMTERR -1.
 *  It can be patched in the a.out or in core.
 */
#define NMTERR  8
int loudatape = NMTERR - 1;

extern int      nmta;            /* number of tape transports */
extern char     mtaaddr[];       /* tape addresses */
extern int      mtaselch;        /* selch number for tapes */

int     mtaio(), mtascintr();

struct buf      mtaab;

/* 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 mtaab.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   mtascq = {
	&mtaio,
	&mtascintr,
	0,
	0
};

struct buf      rmtabuf, cmtabuf;
caddr_t mtaendaddr;              /* 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

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

	mtaab.b_flags |= B_TAPE;
	if ((unit=minor(dev)&03) >= nmta
	  || (mta = &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[mtaaddr[unit]] = (short)tapeint;
*/
	mta->m_status = mta->m_blkno = 0;
	if (!(flag & 2)) /* open for writing */
		mta->m_status = ISWPROT;
	mta->m_lastrec = 1000000;

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

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

/* ARGSUSED */
mtaclose(dev, flag)
register int dev;
{
	register struct tape *mta;
	register writing;

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

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

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

	trace(0x20<<16, "mtacommand", command);
	bp = &cmtabuf;
	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;
	mtastrategy(bp);
	iowait(bp);

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

mtastrategy(abp)
struct buf *abp;
{
	register struct buf *bp;
	register struct tape *mta;
	register int *p;
	register daddr_t fsblkno;

	mta = &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 != &cmtabuf) {
		p = &mta->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) && mta->m_status&ISBOT ||
		    (cmd == WF && mta->m_status&ISWPROT)) {
			if (cmd == WF)
				bp->b_flags |= B_ERROR;
			iodone(bp);
			return;
		}
	}
	bp->av_forw = 0;
	spl5();
	if (mtaab.b_actf)
		mtaab.b_actl->av_forw = bp;
	else
		mtaab.b_actf = bp;
	mtaab.b_actl = bp;
	if (mtaab.b_active == INACTIVE)
		mtastart();
	spl0();
}

mtastart()
{
	register struct tape *mta;
	register struct buf *bp;
	register daddr_t fsblkno;

	while (bp = mtaab.b_actf) {
#ifdef  UCB_NKB
		fsblkno = dbtofsb(bp->b_blkno);
#else   UCB_NKB
		fsblkno = bp->b_blkno;
#endif  UCB_NKB
		mta = &tape[minor(bp->b_dev)&03];
		if (bp != &cmtabuf && (mta->m_status&ISDU)) {
	abort:
			bp->b_flags |= B_ERROR;
			mtaab.b_actf = bp->av_forw;
			mtaab.b_active = INACTIVE;
			iodone(bp);
			continue;
		}
		mta->m_status &= ~ISWRITING;
		if (bp == &cmtabuf)
			mtaab.b_active = SCOM;
		else if (fsblkno == mta->m_blkno) {
			if (mta->m_eot > NMTERR)
				goto abort;
			mtaab.b_active = SIO;
			if ((bp->b_flags&B_READ) == 0) {
				if (mta->m_status & ISWPROT)
					goto abort;
				if (mta->m_eot && !(mta->m_status & EOTISOK)) {
					bp->b_error = ENOSPC;
					if (mta->m_lastrec > fsblkno)
						mta->m_lastrec = fsblkno;
					goto abort;
				}
				mta->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 (mta->m_status&(ISBOT|ISWERR)) {
					mtaab.b_active = SBOT;
					mta->m_status &= ~ISWERR;
				}
			}
		}
		else if (fsblkno > mta->m_blkno) {
			if (mta->m_eot > NMTERR)
				goto abort;
			mtaab.b_active = SSFOR;
		} else
			mtaab.b_active = SSREV;
		selchreq(mtaselch, &mtascq, minor(bp->b_dev)&03);
		return;
	}
}

mtaio(unit)
register  int     unit;
{
	register struct buf *bp;
	register addr;
	register stat;
	register struct tape *mta;

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

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

	switch(mtaab.b_active) {

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

		case OPEN:
			oc(addr, CLEAR);
			oc(addr, ENABLE);
			if ((stat = ss(addr))&DU)
				mta->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)
			{
				mta->m_status |= ISBOT;
				mta->m_eot = 0; /* reset eot flg at load pt */
			}
			mtaab.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))) {
					mta->m_status |= ISBOT;
					mta->m_blkno = 0;
					mta->m_eot = 0; /* reset eot flg */
				}
	fini:
				mtaab.b_active = 0;
				mtaab.b_actf = bp->av_forw;
				iodone(bp);
				mtastart();
			}
			mta->m_status |= BACKWARDS;
			break;

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

		case BR:
		case BF:
			if (mta->m_status & ISBOT)
				goto fini;
			oc(addr, (Tapecmd) bp->b_blkno);
			if (ss(addr) & NMTN)
				goto fini;
			mta->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, "mtaoc", bp->b_blkno);
		trace(0x10<<16, "status", ss(addr));
		mtaab.b_actf = bp->av_forw;
		iodone(bp);
		selchfree(mtaselch);
		return;

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

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

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

		trace(0x10<<16, "mtarw", bp->b_un.b_addr);
		if ((bp->b_flags&B_READ) || mtaab.b_active == SSFOR) {
			oc(addr, READ);
/*
			oc(mtaselch, READ_GO);
*/
			sgo(mtaselch, 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(mtaselch, GO);
*/
			sgo(mtaselch, GO, addr);
		}
	}
}

mtascintr(dev, stat, unit)
{
	oc(mtaselch, 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)
	 */
	mtaendaddr = (caddr_t) ((rdh(mtaselch)+1) & ~01);
}

mtaintr(dev, stat)
{
	register struct buf *bp;
	register struct tape *mta;
	register op;

	trace(0x10<<16, "interrupt", mtaaddr[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 = mtaab.b_active;
	mtaab.b_active = INACTIVE;
	if ((bp = mtaab.b_actf) == 0)
		return;

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

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

	selchfree(mtaselch);
	mta->m_status &= ~ISDU;
	if (stat&DU) {
		mta->m_status |= ISDU;
		mta->m_status &= ~ISWPROT;
		mtaab.b_actf = bp->av_forw;
		bp->b_flags |= B_ERROR;
		iodone(bp);
		mtastart();
		return;
	}

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

	if ((stat&ERR) && op == SIO) {
		if (loudatape <= mtaab.b_errcnt)
			deverror(bp, stat, dev);
		if (++mtaab.b_errcnt < NMTERR) {
			if ((bp->b_flags & B_READ) == 0)
				mta->m_status |= ISWERR;
			mtaab.b_active = SSREV;
			selchreq(mtaselch, &mtascq, minor(bp->b_dev)&03);
			return;
		}
		bp->b_flags |= B_ERROR;
	}

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

	mtastart();
}

/*
 * Raw magtape interface
 */
mtaread(dev)
{
	mtaseek(dev);
	physio(mtastrategy, &rmtabuf, dev, B_READ);
}

mtawrite(dev)
{
	mtaseek(dev);
	physio(mtastrategy, &rmtabuf, dev, B_WRITE);
}

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

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

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

char mtacmds[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 */
mtaioctl(dev, cmd, addr, flag)
caddr_t addr;
{
	register fn, com;
	register struct tape *mta = &tape[minor(dev)&03];

	switch (cmd) {

	default:
		return(1);

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

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

	case MTIOCGET:
		if (copyout((caddr_t)mta, (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
mtadump()
{
	extern char *memtop;
	register int selch, mta;
	register char *memp;

	/* Set up device addresses */
	selch = mtaselch;
	mta = mtaaddr[0];

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

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

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

