/*
 * 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
 *
 */

#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"

#define	NMTERR	10		/* number of error retries */

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

int	mtio(), mtscintr();

struct buf	mttab;

#define	ISOPENED	01
#define	ISDU		02
#define	ISWRITING	04
#define	ISBOT		010

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_pad;			/* kludge padding to power of 2 */
} tape[4];

#define	SCOM	1
#define	SSFOR	2
#define	SSREV	3
#define	SIO	4
#define	SBOT	5

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

struct buf	rmtbuf, cmtbuf;
int	mtead;

/*
 * Tape controller commands
 */
#define	ENABLE	0x40
#define	CLEAR	0x20
#define	READ	0x21
#define	WRITE	0x22
#define	WF	0x30
#define	RW	0x38
#define	FF	0x23
#define	BF	0x13
#define	BR	0x11

#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;

	mttab.b_flags |= B_TAPE;
	if ((unit=minor(dev)&03) >= nmt
	  || (mt = &tape[unit])->m_status & ISOPENED) {
		u.u_error = ENXIO;
		return;
	}
	mt->m_status = mt->m_blkno = 0;
	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;
}

mtclose(dev, flag)
{
	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)
{
	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 = 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;

	if ((bp = abp) != &cmtbuf) {
		mt = &tape[minor(bp->b_dev)&03];
		p = &mt->m_lastrec;
		if (bp->b_blkno > *p) {
			bp->b_flags |= B_ERROR;
			iodone(bp);
			return;
		}
		if (bp->b_blkno == *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 = bp->b_blkno + 1;
	}

	bp->av_forw = 0;
	spl5();
	if (mttab.b_actf)
		mttab.b_actl->av_forw = bp;
	else
		mttab.b_actf = bp;
	mttab.b_actl = bp;
	if (mttab.b_active == 0)
		mtstart();
	spl0();
}

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

	while (bp = mttab.b_actf) {
		mt = &tape[minor(bp->b_dev)&03];
		if (bp != &cmtbuf && (mt->m_status&ISDU)) {
			bp->b_flags |= B_ERROR;
			mttab.b_actf = bp->av_forw;
			iodone(bp);
			continue;
		}
		mt->m_status &= ~ISWRITING;
		if (bp == &cmtbuf)
			mttab.b_active = SCOM;
		else if (bp->b_blkno == mt->m_blkno) {
			mttab.b_active = SIO;
			if ((bp->b_flags&B_READ) == 0) {
				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.
				 */
				if (mt->m_status&ISBOT)
					mttab.b_active = SBOT;
			}
		}
		else if (bp->b_blkno > mt->m_blkno)
			mttab.b_active = SSFOR;
		else
			mttab.b_active = SSREV;
		selchreq(mtselch, &mtscq);
		return;
	}
}

mtio()
{
	register struct buf *bp;
	register addr;
	register stat;

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

	addr = mtaddr[minor(bp->b_dev)&03];

	switch(mttab.b_active) {

	case SCOM:
		if (bp->b_blkno == OPEN) {
			register struct tape *mt;

			mt = &tape[minor(bp->b_dev)&03];
			oc(addr, CLEAR);
			oc(addr, ENABLE);
			if ((stat = ss(addr))&DU)
				mt->m_status |= ISDU;
			if (stat&EOT)
				mt->m_status |= ISBOT;
			mttab.b_active = 0;
		} else
			oc(addr, bp->b_blkno);
		trace(0x10<<16, "mtoc", bp->b_blkno);
		trace(0x10<<16, "status", ss(addr));
		mttab.b_actf = bp->av_forw;
		iodone(bp);
		selchfree(mtselch);
		return;

	case SSREV:
		oc(addr, BR);
		trace(0x10<<16, "mtoc", BR);
		trace(0x10<<16, "status", ss(addr));
		return;

	case SBOT:
		oc(addr, WF);
		trace(0x10<<16, "mtoc", WF);
		trace(0x10<<16, "status", ss(addr));
		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) || mttab.b_active == SSFOR) {
			oc(addr, READ);
			oc(mtselch, READ_GO);
		} else {
			oc(addr, WRITE);
			oc(mtselch, GO);
		}
	}
}

mtscintr(dev, stat)
{
	oc(mtselch, STOP);
	mtead = (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.
	 */
	if ((stat&NMTN) == 0)
		return;

	op = mttab.b_active;
	mttab.b_active = 0;
	if ((bp = mttab.b_actf) == 0)
		return;
	if (op == SCOM) {
		mtstart();
		return;
	}

	selchfree(mtselch);
	mt = &tape[minor(bp->b_dev)&03];
	mt->m_status &= ~(ISBOT|ISDU);
	if (stat&DU) {
		mt->m_status |= ISDU;
		mttab.b_actf = bp->av_forw;
		bp->b_flags |= B_ERROR;
		iodone(bp);
		mtstart();
		return;
	}

	if (op == SSREV)
		mt->m_blkno--;
	else
		mt->m_blkno++;

	if ((stat&ERR) && op == SIO) {
		deverror(bp, stat, dev);
		if (++mttab.b_errcnt < NMTERR) {
			mttab.b_active = SSREV;
			selchreq(mtselch, &mtscq);
			return;
		}
		printf("Solid error\n");
		bp->b_flags |= B_ERROR;
	}

	if (op == SIO || (stat&(EOF|EOT)) && op == SSFOR) {
		mttab.b_errcnt = 0;
		mttab.b_actf = bp->av_forw;
		bp->b_resid = stat&(EOF|EOT) ? bp->b_bcount :
			bp->b_bcount - (mtead - 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 */
};

mtioctl(dev, cmd, addr, flag)
caddr_t addr;
{
	register fn, com;

	if (cmd != TIOCSETP)
		return(1);
	if ((fn = fuword(addr)) < 0 || fn > 7 || !(com = mtcmds[fn])) {
		u.u_error = ENXIO;
		return(0);
	}
	mtcommand(dev, com);
	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)
		;
}
