#
/*
 * TU16 Tape Driver
 *
 * Handles one TM02 controller, up to 4 TU16 slave transports
 * minor device classes:
 * bits 0,1: slave select
 * bit 2 off: rewind on close; on: position after first TM
 * bit 3 off: 800 bpi; on: 1600 bpi
 */

#include "../head/param.h"
#include "../head/systm.h"
#include "../head/buf.h"
#include "../head/bufx.h"
#include "../head/conf.h"
#include "../head/file.h"
#include "../head/user.h"
#include "../head/userx.h"
#include "../head/elog.h"

#define NHT 4
#define	HTADDR	0172440

struct htregs {
	int	htcs1, htwc, htba, htfc;
	int	htcs2, htds, hter, htas;
	int	htck, htdb, htmr, htdt;
	int	htsn, httc, htbae, htcs3;
};

struct	devtab	httab;
struct	buf	rhtbuf, chtbuf;

struct	iostat	htstat[NHT];
struct	errtab	htetab { etabinit(E_BLK|E_RH70,NHT,HT,htstat) };

char	h_openf[NHT];
int	h_den[NHT];
char	*h_blkno[NHT], *h_nxrec[NHT];

#define	GO	01
#define	NOP	0
#define	WEOF	026
#define	SFORW	030
#define	SREV	032
#define	ERASE	024
#define	REW	06
#define	DCLR	010
#define P800	01300		/* 800 + pdp11 mode */
#define	P1600	02300		/* 1600 + pdp11 mode */
#define	IENABLE	0100
#define	RDY	0200
#define	TMARK	04
#define	DRY	0200
#define EOT	02000
#define CS	02000
#define COR	0100000
#define PES	040
#define WRL	04000
#define MOL	010000
#define PIP	020000
#define ERR	040000
#define FCE	01000
#define	TRE	040000
#define HARD	064023	/* UNS|OPI|NEF|FMT|RMR|ILR|ILF */

#define	SSEEK	1
#define	SIO	2
#define SABORT	3
#define SRETRY	4
#define SCOM	5
#define SOK	6
#define SERR	7

htopen(dev, flag)
{
	register unit, ds, i;

	unit = dev&03;
	if (unit >= NHT || h_openf[unit]) {
		u.u_error = ENXIO;
		return;
	}
	h_den[unit] = (dev&010 ? P1600 : P800)|unit;
	flag =& FWRITE;
	for(i = 0; i < 75; i++) {	/* max 5 min wait on rewind */
		h_blkno[unit] = 0;
		h_nxrec[unit] = -1;
		ds = hcommand(unit, NOP);
		if ((ds&MOL)==0 || (flag && (ds&WRL)) || h_openf[unit]) {
			u.u_error = ENXIO;
			return;
		}
		if((ds&PIP)==0) {
			if (u.u_error==0)
				h_openf[unit]++;
			return;
		}
		sleep(&lbolt,-1);
	}
	u.u_error = ENXIO;
}

htclose(dev, flag)
{
	register int unit;

	unit = dev&03;
	flag =& FWRITE;
	if (flag) {
		hcommand(unit, WEOF);
		hcommand(unit, WEOF);
	}
	if (dev&04) {
		if (flag)
			hcommand(unit, SREV); else
			hcommand(unit, NOP);
	} else
		hcommand(unit, REW);
	h_openf[unit] = 0;
}

hcommand(unit, com)
{
	register struct buf *bp;

	bp = &chtbuf;
	spl5();
	while(bp->b_flags&B_BUSY) {
		bp->b_flags =| B_WANTED;
		sleep(bp, PRIBIO);
	}
	spl0();
	bp->b_dev = unit;
	bp->b_resid = com;
	bp->b_blkno = 0;
	bp->b_flags = B_BUSY|B_READ;
	htstrategy(bp);
	iowait(bp);
	if(bp->b_flags&B_WANTED)
		wakeup(bp);
	bp->b_flags = 0;
	return(bp->b_resid);
}

htstrategy(abp)
struct buf *abp;
{
	register struct buf *bp;
	register char **p;

	bp = abp;
	p = &h_nxrec[bp->b_dev&03];
	if (*p < bp->b_blkno || (*p == bp->b_blkno && bp->b_flags&B_READ)) {
		if (bp->b_flags&B_READ)
			bp->b_resid = bp->b_wcount;
		else {
			bp->b_flags =| B_ERROR;
			bp->b_error = ENXIO;
		}
		iodone(bp);
		return;
	}
	if ((bp->b_flags&B_READ)==0)
		*p = bp->b_blkno + 1;
	bp->av_forw = 0;
	spl5();
	if (httab.d_actf==0)
		httab.d_actf = bp;
	else
		httab.d_actl->av_forw = bp;
	httab.d_actl = bp;
	if (httab.d_active==0)
		htstart();
	spl0();
}

htstart()
{
	register struct buf *bp;
	register int unit;
	register char *blkno;

    loop:
	if ((bp = httab.d_actf) == 0)
		return;
	unit = bp->b_dev&03;
	HTADDR->htcs2 = 0;
	if((HTADDR->httc&03777)!=h_den[unit])
		HTADDR->httc = h_den[unit];
	blkno = h_blkno[unit];
	if ((HTADDR->htds&MOL)==0)
		goto abort;
	htetab.e_aunit = &htstat[unit];
	if (bp == &chtbuf) {
		if (bp->b_resid==NOP) {
			bp->b_resid = HTADDR->htds;
			goto next;
		}
		httab.d_active = SCOM;
		htetab.e_aunit->io_misc++;
		HTADDR->htfc = 0;
		HTADDR->htcs1 = bp->b_resid|IENABLE|GO;
		return;
	}
	if (h_openf[unit] < 0)
		goto abort;
	if (blkno == bp->b_blkno) {
		httab.d_active = SIO;
		htetab.e_aunit->io_ops++;
		blkacty =| (1<<HT);
		rhstart(bp, &HTADDR->htfc, bp->b_wcount<<1, &HTADDR->htbae);
	} else {
		httab.d_active = SSEEK;
		htetab.e_aunit->io_misc++;
		if (blkno < bp->b_blkno) {
			HTADDR->htfc = blkno - bp->b_blkno;
			HTADDR->htcs1 = SFORW|IENABLE|GO;
		} else {
			HTADDR->htfc = bp->b_blkno - blkno;
			HTADDR->htcs1 = SREV|IENABLE|GO;
		}
	}
	return;
    abort:
	bp->b_flags =| B_ERROR;
    next:
	httab.d_actf = bp->av_forw;
	iodone(bp);
	goto loop;
}

htintr()
{
	register struct buf *bp;
	register int unit, state;
	struct htregs htregs[0];
	int	err;

	if ((bp = httab.d_actf)==0)
		return;
	blkacty =& ~(1<<HT);
	unit = bp->b_dev&03;
	state = httab.d_active;
	httab.d_active = 0;
	if (HTADDR->htcs1&TRE) {
		err = HTADDR->hter;
		if (HTADDR->htcs2.hibyte>0 || err&HARD)
			state = SERR;
		if (bp == &rhtbuf) {
			err =& ~FCE;
		}
		if ((bp->b_flags&B_READ) && (HTADDR->htds&PES))
			err =& ~(CS|COR);
		if(HTADDR->htds&EOT) {
			h_openf[unit] = -1;
		}
		else if(HTADDR->htds&TMARK) {
			state = 0;
			HTADDR->htwc = bp->b_wcount;
			if(bp != &rhtbuf)
				h_openf[unit] = -1;
		     }
		else if(state != SERR && err == 0)
			state = SOK;
		else
			fmtblk(&htetab,HTADDR,sizeof(htregs[0])/2,bp);
		HTADDR->htcs1 = TRE|DCLR|GO;
		htetab.e_aunit->io_misc++;
		h_blkno[unit]++;
		if (state==SIO && httab.d_errcnt < 10) {
			if(httab.d_errcnt++ == 0)
				logerr(&htetab,E_FIRST);
			htetab.e_aunit->io_misc++;
			httab.d_active = SRETRY;
			HTADDR->htfc = -1;
			HTADDR->htcs1 = SREV|IENABLE|GO;
			return;
		}
		if (state!=SOK) {
			if(state)
				bp->b_flags =| B_ERROR;
			state = SABORT;
		}
	} else if (HTADDR->htcs1 < 0) {	/* SC */
		if (HTADDR->htds&ERR) {
			fmtblk(&htetab,HTADDR,sizeof(htregs[0])/2,bp);
			HTADDR->htcs1 = DCLR|GO;
			htetab.e_aunit->io_misc++;
		}
	}
	switch(state) {
	case SIO:
	case SOK:
		h_blkno[unit]++;
	case SABORT:
	case SCOM:
		if(htetab.e_emsg != NULL)
			logerr(&htetab,E_RETRY);
		httab.d_errcnt = 0;
		httab.d_actf = bp->av_forw;
		bp->b_resid = HTADDR->htwc;
		iodone(bp);
		break;
	case SRETRY:
		if((bp->b_flags&B_READ)==0) {
			htetab.e_aunit->io_misc++;
			httab.d_active = SSEEK;
			HTADDR->htcs1 = ERASE|IENABLE|GO;
			return;
		}
	case SSEEK:
		h_blkno[unit] = bp->b_blkno;
		break;
	default:
		return;
	}
	htstart();
}

htread(dev)
{
	htphys(dev);
	physio(htstrategy, &rhtbuf, dev, B_READ, 0);
}

htwrite(dev)
{
	htphys(dev);
	physio(htstrategy, &rhtbuf, dev, B_WRITE, 0);
}

htphys(dev)
{
	register unit, a;

	unit = dev&03;
	a = lshift(u.u_offset, -9);
	h_blkno[unit] = a;
	h_nxrec[unit] = ++a;
}
