/*
 * TJU77/TWU77/TJE16/TWE16 tape driver
 */

/*
 *	SCCS id	@(#)ht.c	2.1 (Berkeley)	8/5/83
 */

#include "ht.h"
#if	NHT > 0
#include "param.h"
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/conf.h>
#include <sys/dir.h>
#include <sys/file.h>
#include <sys/user.h>
#include <sys/htreg.h>
#ifdef	HT_IOCTL
#include <sys/mtio.h>
#endif

struct	buf	httab;
struct	buf	rhtbuf;
struct	buf	chtbuf;

struct	htdevice	*HTADDR;

#define	INF	32760

struct	softc	{
	char	sc_openf;
	char	sc_lastiow;
	daddr_t	sc_blkno;
	daddr_t	sc_nxrec;
	u_short	sc_erreg;
	u_short	sc_fsreg;
#ifdef	HT_IOCTL
	short	sc_resid;
#endif
}	tu_softc[NHT];


#define	SIO	1
#define	SSFOR	2
#define	SSREV	3
#define	SRETRY	4
#define	SCOM	5
#define	SOK	6

#define	TUUNIT(dev)	(minor(dev) & 077)

/* bits in minor device */
#define	H_800BPI	0100
#define	H_NOREWIND	0200

htattach(addr, unit)
register struct htdevice *addr;
{
	/*
	 * This driver supports only one controller.
	 */
	if (unit != 0)
		return(0);
	if ((addr != (struct htdevice *) NULL) && (fioword(addr) != -1)) {
		HTADDR = addr;
#if	PDP11 == 70 || PDP11 == GENERIC
		if (fioword(&(addr->htbae)) != -1)
			httab.b_flags |= B_RH70;
#endif
		return(1);
	}
	HTADDR = (struct hpdevice *) NULL;
	return(0);
}

htopen(dev, flag)
dev_t	dev;
{
	register ds;
	register htunit = TUUNIT(dev);
	register struct tu_softc *sc = &tu_softc[htunit];

	httab.b_flags |= B_TAPE;
	htunit = minor(dev) & 077;
	if (HTADDR == (struct htdevice *) NULL || htunit >= NHT) {
		u.u_error = ENXIO;
		return;
	}
	else
		if (sc->sc_openf) {
			u.u_error = EBUSY;
			return;
		}
	sc->sc_blkno = (daddr_t) 0;
	sc->sc_nxrec = (daddr_t) INF;
	sc->sc_lastiow = 0;
	ds = htcommand(dev, HT_SENSE, 1);
	if ((ds & HTFS_MOL) == 0) {
		uprintf("tu%d: not online\n", htunit);
		u.u_error = EIO;
		return;
	}
	if ((flag & FWRITE) && (ds & HTFS_WRL)) {
		uprintf("tu%d: no write ring\n", htunit);
		u.u_error = EIO;
		return;
	}
	if (u.u_error == 0)
		sc->sc_openf++;
}

htclose(dev, flag)
dev_t	dev;
{
	register struct tu_softc *sc = &tu_softc[TUUNIT(dev)];

	if (flag == FWRITE || ((flag & FWRITE) && sc->sc_lastiow)) {
		htcommand(dev, HT_WEOF, 1);
		htcommand(dev, HT_WEOF, 1);
		htcommand(dev, HT_SREV, 1);
	}
	if ((minor(dev) & H_NOREWIND) == 0)
		htcommand(dev, HT_REW, 1);
	sc->sc_openf = 0;
}

/*ARGSUSED*/
htcommand(dev, com, count)
u_short	count;
dev_t	dev;
{
	register s;
	register struct	buf *bp;

	bp = &chtbuf;
	s = spl5();
	while(bp->b_flags & B_BUSY) {
		/*
		 * This special check is because B_BUSY never
		 * gets cleared in the non-waiting rewind case.
		 */
		if (bp->b_repcnt == 0 && (bp->b_flags & B_DONE))
			break;
		bp->b_flags |= B_WANTED;
		sleep((caddr_t) bp, PRIBIO);
	}
	bp->b_flags = B_BUSY | B_READ;
	splx(s);
	bp->b_dev = dev;
#ifdef	HT_IOCTL
	if (com == HT_SFORW || com == HT_SREV)
		bp->b_repcnt = count;
#endif
	bp->b_command = com;
	bp->b_blkno = (daddr_t) 0;
	htstrategy(bp);
	/*
	 * In case of rewind from close, don't wait.
	 * This is the only case where count can be 0.
	 */
	if (count == 0)
		return;
	iowait(bp);
	if(bp->b_flags & B_WANTED)
		wakeup((caddr_t)bp);
	bp->b_flags &= B_ERROR;
	return (bp->b_resid);
}

htstrategy(bp)
register struct	buf *bp;
{
	int s;
	register daddr_t *p;
	register struct tu_softc *sc = &tu_softc[TUUNIT(bp->b_dev)];

	if(bp != &chtbuf) {
#ifdef	UNIBUS_MAP
		if ((httab.b_flags & B_RH70) == 0)
			mapalloc(bp);
#endif
		p = &sc->sc_nxrec;
		if(dbtofsb(bp->b_blkno) > *p) {
			bp->b_flags |= B_ERROR;
			bp->b_error = ENXIO;
			iodone(bp);
			return;
		}
		if(dbtofsb(bp->b_blkno) == *p && bp->b_flags & B_READ) {
			bp->b_resid = bp->b_bcount;
			iodone(bp);
			return;
		}
		if ((bp->b_flags & B_READ) == 0) {
			*p = dbtofsb(bp->b_blkno) + 1;
			sc->sc_lastiow = 1;
		}
	}
	bp->av_forw = NULL;
	s = spl5();
	if (httab.b_actf == NULL)
		httab.b_actf = bp;
	else
		httab.b_actl->av_forw = bp;
	httab.b_actl = bp;
	if (httab.b_active == 0)
		htstart();
	splx(s);
}

htstart()
{
	register struct buf *bp;
	register den;
	int htunit;
	daddr_t	blkno;
	register struct softc *sc;

    loop:
	if ((bp = httab.b_actf) == NULL)
		return;
	htunit = minor(bp->b_dev) & 0177;
	sc = &tu_softc[TUUNIT(bp->b_dev)];
	sc->sc_erreg = HTADDR->hter;
	sc->sc_fsreg = HTADDR->htfs;
#ifdef	HT_IOCTL
	sc->sc_resid = HTADDR->htfc;
#endif
	HTADDR->htcs2 = ((htunit >> 3) & 07);
	den = HTTC_1600BPI | HTTC_PDP11 | (htunit & 07);
	if (htunit & H_800BPI)
		den = HTTC_800BPI | HTTC_PDP11 | (htunit & 07);
	if ((HTADDR->httc & 03777) != den)
		HTADDR->httc = den;
	if (HTADDR->htcs2 & HTCS2_NEF || (HTADDR->htfs & HTFS_MOL) == 0)
		goto abort;
	htunit &= 077;
	blkno = sc->sc_blkno;
	if (bp == &chtbuf) {
		if (bp->b_command == HT_SENSE) {
			bp->b_resid = HTADDR->htfs;
			goto next;
		}
		httab.b_active = SCOM;
		HTADDR->htfc = 0;
		HTADDR->htcs1 = bp->b_command | HT_IE | HT_GO;
		return;
	}
	if (sc->sc_openf < 0 || dbtofsb(bp->b_blkno) > sc->sc_nxrec)
		goto abort;
	if (blkno == dbtofsb(bp->b_blkno)) {
		httab.b_active = SIO;
		HTADDR->htba = bp->b_un.b_addr;
#if	PDP11 == 70 || PDP11 == GENERIC
		if(httab.b_flags & B_RH70)
			HTADDR->htbae = bp->b_xmem;
#endif
		HTADDR->htfc = -bp->b_bcount;
		HTADDR->htwc = -(bp->b_bcount >> 1);
		den = ((bp->b_xmem & 3) << 8) | HT_IE | HT_GO;
		if(bp->b_flags & B_READ)
			den |= HT_RCOM;
		else {
			if(HTADDR->htfs & HTFS_EOT) {
				bp->b_resid = bp->b_bcount;
				goto next;
			}
			den |= HT_WCOM;
		}
		HTADDR->htcs1 = den;
	} else {
		if (blkno < dbtofsb(bp->b_blkno)) {
			httab.b_active = SSFOR;
			HTADDR->htfc = blkno - dbtofsb(bp->b_blkno);
			HTADDR->htcs1 = HT_SFORW | HT_IE | HT_GO;
		} else {
			httab.b_active = SSREV;
			HTADDR->htfc = dbtofsb(bp->b_blkno) - blkno;
			HTADDR->htcs1 = HT_SREV | HT_IE | HT_GO;
		}
	}
	return;

    abort:
	bp->b_flags |= B_ERROR;

    next:
	httab.b_actf = bp->av_forw;
	iodone(bp);
	goto loop;
}

htintr()
{
	register struct buf *bp;
	register state;
	int err, htunit;
	register struct softc *sc;

	if ((bp = httab.b_actf) == NULL)
		return;
	htunit = TUUNIT(bp->b_dev);
	state = httab.b_active;
	httab.b_active = 0;
	sc = &tu_softc[htunit];
	sc->sc_erreg = HTADDR->hter;
	sc->sc_fsreg = HTADDR->htfs;
#ifdef	HT_IOCTL
	sc->sc_resid = HTADDR->htfc;
#endif
	if (HTADDR->htcs1 & HT_TRE) {
		err = HTADDR->hter;
		if (HTADDR->htcs2 & HTCS2_ERR || (err & HTER_HARD))
			state = 0;
		if (bp == &rhtbuf)
			err &= ~HTER_FCE;
		if ((bp->b_flags & B_READ) && (HTADDR->htfs & HTFS_PES))
			err &= ~(HTER_CSITM | HTER_CORCRC);
		if ((HTADDR->htfs & HTFS_MOL) == 0) {
			if(sc->sc_openf)
				sc->sc_openf = -1;
		}
		else
			if (HTADDR->htfs & HTFS_TM) {
				HTADDR->htwc = -(bp->b_bcount >> 1);
				sc->sc_nxrec = dbtofsb(bp->b_blkno);
				state = SOK;
			}
			else
				if (state && err == 0)
					state = SOK;
		if (httab.b_errcnt > 4)
#ifdef	UCB_DEVERR
			printf("tu%d: hard error bn %D er=%b ds=%b\n",
			       htunit, bp->b_blkno,
			       sc->sc_erreg, HTER_BITS,
			       sc->sc_fsreg, HTFS_BITS);
#else
			deverror(bp, sc->sc_erreg, sc->sc_fsreg);
#endif
		htinit();
		if (state == SIO && ++httab.b_errcnt < 10) {
			httab.b_active = SRETRY;
			sc->sc_blkno++;
			HTADDR->htfc = -1;
			HTADDR->htcs1 = HT_SREV | HT_IE | HT_GO;
			return;
		}
		if (state != SOK) {
			bp->b_flags |= B_ERROR;
			state = SIO;
		}
	} else
		if (HTADDR->htcs1 & HT_SC)
			if(HTADDR->htfs & HTFS_ERR)
				htinit();

	switch (state) {
		case SIO:
		case SOK:
			sc->sc_blkno++;

		case SCOM:
			httab.b_errcnt = 0;
			httab.b_actf = bp->av_forw;
			iodone(bp);
			bp->b_resid = -(HTADDR->htwc << 1);
			break;

		case SRETRY:
			if((bp->b_flags & B_READ) == 0) {
				httab.b_active = SSFOR;
				HTADDR->htcs1 = HT_ERASE | HT_IE | HT_GO;
				return;
			}

		case SSFOR:
		case SSREV:
			if(HTADDR->htfs & HTFS_TM) {
				if(state == SSREV) {
					sc->sc_nxrec = dbtofsb(bp->b_blkno) - HTADDR->htfc;
					sc->sc_blkno = sc->sc_nxrec;
				} else
					{
					sc->sc_nxrec = dbtofsb(bp->b_blkno) + HTADDR->htfc - 1;
					sc->sc_blkno = sc->sc_nxrec + 1;
				}
			} else
				sc->sc_blkno = dbtofsb(bp->b_blkno);
			break;

		default:
			return;
	}
	htstart();
}

htinit()
{
	register ocs2;
	register omttc;
	
	omttc = HTADDR->httc & 03777;	/* preserve old slave select, dens, format */
	ocs2 = HTADDR->htcs2 & 07;	/* preserve old unit */

	HTADDR->htcs2 = HTCS2_CLR;
	HTADDR->htcs2 = ocs2;
	HTADDR->httc = omttc;
	HTADDR->htcs1 = HT_DCLR | HT_GO;
}

htread(dev)
register dev_t	dev;
{
	htphys(dev);
	bphysio(htstrategy, &rhtbuf, dev, B_READ);
}

htwrite(dev)
register dev_t	dev;
{
	htphys(dev);
	bphysio(htstrategy, &rhtbuf, dev, B_WRITE);
}

htphys(dev)
dev_t dev;
{
	daddr_t a;
	register struct tu_softc *sc = &tu_softc[TUUNIT(dev)];

	a = dbtofsb(u.u_offset >> 9);
	sc->sc_blkno = a;
	sc->sc_nxrec = a + 1;
}

#ifdef	HT_IOCTL

/*ARGSUSED*/
htioctl(dev, cmd, addr, flag)
dev_t	dev;
caddr_t	addr;
{
	register struct buf *bp = &chtbuf;
	register struct softc *sc = &tu_softc[minor(dev)&07];
	register callcount;
	int	fcount;
	struct	mtop mtop;
	struct	mtget mtget;
	/* we depend on the values and order of the MT codes here */
	static	htops[] = {HT_WEOF, HT_SFORW, HT_SREV, HT_SFORW,
		HT_SREV, HT_REW, HT_REWOFFL, HT_SENSE};

	switch (cmd)	{

		case MTIOCTOP:
			if (copyin(addr, (caddr_t) &mtop, sizeof(mtop))) {
				u.u_error = EFAULT;
				return;
			}
			switch(mtop.mt_op) {
				case MTWEOF:
					callcount = mtop.mt_count;
					fcount = 1;
					break;
				case MTFSF:
				case MTBSF:
					callcount = mtop.mt_count;
					fcount = INF;
					break;
				case MTFSR:
				case MTBSR:
					callcount = 1;
					fcount = mtop.mt_count;
					break;
				case MTREW:
				case MTOFFL:
				case MTNOP:
					callcount = 1;
					fcount = 1;
					break;
				default:
					u.u_error = ENXIO;
					return;
			}
			if (callcount <= 0 || fcount <= 0) {
				u.u_error = ENXIO;
				return;
			}
			while (--callcount >= 0) {
				htcommand(dev, htops[mtop.mt_op], fcount);
				if ((mtop.mt_op == MTFSR || mtop.mt_op == MTBSR)
				    && bp->b_resid) {
					u.u_error = EIO;
					break;
				}
				if ((bp->b_flags & B_ERROR)
				    || sc->sc_fsreg & HTFS_BOT)
					break;
			}
			geterror(bp);
			return;
		case MTIOCGET:
			mtget.mt_erreg = sc->sc_erreg;
			mtget.mt_dsreg = sc->sc_fsreg;
			mtget.mt_resid = sc->sc_resid;
			mtget.mt_type = MT_ISHT;
			if (copyout((caddr_t) &mtget, addr, sizeof(mtget)))
				u.u_error = EFAULT;
			return;
		default:
			u.u_error = ENXIO;
	}
}
#endif	HT_IOCTL
#endif	NHT
