#
/*
 * TM tape driver
 * Conventions:
 *	minor devices 0-3 rewind
 *	minor devices 4-7 no rewind
 */

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

struct tmregs {
	int tmer;
	int tmcs;
	int tmbc;
	int tmba;
	int tmdb;
	int tmrd;
};

#define NTM	4
#define	TMADDR	0172520

struct	devtab	tmtab;
struct	buf	rtmbuf;
struct	buf	ctmbuf;

struct	iostat	tmstat[NTM];
struct	errtab	tmetab { etabinit(E_BLK,NTM,TM,tmstat) };

char	t_openf[NTM];
char	*t_blkno[NTM];
char	*t_nxrec[NTM];

#define	GO	01
#define	RCOM	02
#define	WCOM	04
#define	WEOF	06
#define	SFORW	010
#define	SREV	012
#define	WIRG	014
#define	REW	016
#define	DENS	060000		/* 9-channel */
#define	IENABLE	0100
#define	CRDY	0200
#define	TUR	1
#define	WRL	4
#define	SELR	0100
#define GSD	010000
#define	HARD	0102200	/* ILC, EOT, NXM */
#define	EOF	0040000

#define NOP	0
#define	SSEEK	01
#define	SIO	02
#define SBACK	04
#define	TCMD	010
#define SBAD	020

tmopen(dev, flag)
{
	register int unit, ds, i;

	unit = dev.d_minor&03;
	if(unit >= NTM || t_openf[unit]) {
		u.u_error = ENXIO;
		return;
	}
	flag =& FWRITE;
	for(i = 0; i < 75; i++) {	/* only wait 5 min for TUR */
		t_blkno[unit] = 0;
		t_nxrec[unit] = 65535L;
		ds = tcommand(unit, NOP);
		if((ds&SELR)==0 || (flag && (ds&WRL)) || t_openf[unit]) {
			u.u_error = ENXIO;
			return;
		}
		if(ds&TUR) {
			if(u.u_error == 0)
				t_openf[unit]++;
			return;
		}
		sleep(&lbolt, -1);
	}
	u.u_error = ENXIO;
}

tmclose(dev, flag)
{
	register int unit;

	unit = dev.d_minor&03;
	flag =& FWRITE;
	if (flag){
		tcommand(unit, WEOF);
		tcommand(unit, WEOF);
	}
	if ((dev.d_minor&04) == 0)
		tcommand(unit, REW);
	else if(flag)
		tcommand(unit, SREV);
	t_openf[unit] = 0;
}

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

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

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

	bp = abp;
	if(bp->b_flags&B_PHYS)
		mapalloc(bp);
	p = &t_nxrec[bp->b_dev.d_minor&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 (tmtab.d_actf==0)
		tmtab.d_actf = bp;
	else
		tmtab.d_actl->av_forw = bp;
	tmtab.d_actl = bp;
	if (tmtab.d_active==0)
		tmstart();
	spl0();
}

tmstart()
{
	register struct buf *bp;
	register int com, unit;
	char *blkno;

    loop:
	if ((bp = tmtab.d_actf) == 0)
		return;
	unit = bp->b_dev.d_minor&03;
	com = (unit<<8);
	TMADDR->tmcs = com;	/* gives unit time to be selected */
	blkno = t_blkno[unit];
	tmetab.e_aunit = &tmstat[unit];
	if(bp == &ctmbuf) {
		if(bp->b_resid == NOP) {
			bp->b_resid = TMADDR->tmer;
			tmtab.d_actf = bp->av_forw;
			iodone(bp);
			goto loop;
		}
		tmtab.d_active = TCMD;
		tmetab.e_aunit->io_misc++;
		TMADDR->tmbc = 0;
		TMADDR->tmcs = com|bp->b_resid|DENS|IENABLE|GO;
		return;
	}
	if (t_openf[unit] < 0 || (TMADDR->tmcs & CRDY)==0) {
		if((t_openf[unit] != -2) || (bp->b_flags&B_READ)) {
			if (t_openf[unit] == -2)
				bp->b_resid = bp->b_wcount;
			else
				bp->b_flags =| B_ERROR;
			tmtab.d_actf = bp->av_forw;
			iodone(bp);
			goto loop;
		}
	}
	com =| DENS |  ((bp->b_xmem & 03)<<4) | IENABLE ;
	if (blkno != bp->b_blkno) {
		tmtab.d_active = SSEEK;
		if (blkno < bp->b_blkno) {
			com =| SFORW|GO;
			TMADDR->tmbc = blkno - bp->b_blkno;
		} else {
			com =| SREV|GO;
			TMADDR->tmbc = bp->b_blkno - blkno;
			if(t_openf[unit] == -2)
				tmtab.d_active = SBACK;
		}
		tmetab.e_aunit->io_misc++;
	}
	else {
		tmtab.d_active = SIO;
		tmetab.e_aunit->io_ops++;
		blkacty =| (1<<TM);
		TMADDR->tmbc = bp->b_wcount << 1;
		TMADDR->tmba = bp->b_addr;		/* core address */
		com =| ((bp->b_flags&B_READ)? RCOM|GO:
		    ((tmtab.d_errcnt)? WIRG|GO: WCOM|GO));
	}
	TMADDR->tmcs = com;
}

tmintr()
{
	register struct buf *bp;
	register int unit, state;
	struct tmregs tmregs[0];

	if ((bp = tmtab.d_actf)==0)
		return;
	blkacty =& ~(1<<TM);
	state = tmtab.d_active;
	unit = bp->b_dev.d_minor&03;
	if (TMADDR->tmcs < 0) {		/* error bit */
		while(TMADDR->tmrd & GSD) ;	/* wait for gap shutdown */
		if(state&(SSEEK|TCMD) || TMADDR->tmer&HARD) {
			t_openf[unit] = -1;
			state = SBAD;
		}
		else if((TMADDR->tmer&EOF) == 0) {	/* soft errors */
			if (tmtab.d_errcnt < 10) {
				fmtblk(&tmetab,TMADDR,sizeof(tmregs[0])/2,bp);
				if(tmtab.d_errcnt++ == 0)
					logerr(&tmetab,E_FIRST);
				t_blkno[unit]++;
				tmtab.d_active = 0;
				tmstart();
				return;
			}
			else
				state = SBAD;
		}
		else if(bp != &rtmbuf) {		/* EOF */
			if(state&SBACK)
				t_openf[unit] = 1;
			else
				t_openf[unit] = -2;
		}
		if(state&SBAD) {
			bp->b_flags =| B_ERROR;
			fmtblk(&tmetab,TMADDR,sizeof(tmregs[0])/2,bp);
		}
		else
			TMADDR->tmbc = bp->b_wcount << 1;
	}
	if(state&(SIO|TCMD|SBAD)) {
		if(tmetab.e_emsg != NULL)
			logerr(&tmetab,E_RETRY);
		tmtab.d_errcnt = 0;
		t_blkno[unit]++;
		tmtab.d_actf = bp->av_forw;
		tmtab.d_active = 0;
		bp->b_resid = TMADDR->tmbc >> 1;
		iodone(bp);
	} else
		t_blkno[unit] = bp->b_blkno;
	tmstart();
}

tmread(dev)
{
	tmphys(dev);
	physio(tmstrategy, &rtmbuf, dev, B_READ, 0);
}

tmwrite(dev)
{
	tmphys(dev);
	physio(tmstrategy, &rtmbuf, dev, B_WRITE, 0);
}

tmphys(dev)
{
	register unit, a;

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