/* @(#)rm.c	1.1 */
/*
 * DRWM05 R2 Driver (CDC 9766)
 *
 * John Buck, PINY 7/83
 *
 * Modified from bells system-V hp driver, enhancments
 * to error reporting, and recovery added, as well as
 * fixes for emulex controller caveats.
 */

#include "sys/param.h"
#include "sys/types.h"
#include "sys/sysmacros.h"
#include "sys/dir.h"
#include "sys/signal.h"
#include "sys/user.h"
#include "sys/errno.h"
#include "sys/buf.h"
#include "sys/elog.h"
#include "sys/iobuf.h"
#include "sys/systm.h"

struct	device
{
	int	rmcs1;		/* Control and Status register 1 */
	int	rmwc;		/* Word count register */
	caddr_t	rmba;		/* UNIBUS address register */
	int	rmda;		/* Desired address register */
	int	rmcs2;		/* Control and Status register 2*/
	int	rmds;		/* Drive Status */
	int	rmer1;		/* Error register 1 */
	int	rmas;		/* Attention Summary */
	int	rmla;		/* Look ahead */
	int	rmdb;		/* Data buffer */
	int	rmmr;		/* Maintenance register */
	int	rmdt;		/* Drive type */
	int	rmsn;		/* Serial number */
	int	rmof;		/* Offset register */
	int	rmdc;		/* Desired Cylinder address register*/
	int	rmcc;		/* Current Cylinder */
	int	rmer2;		/* Error register 2 */
	int	rmer3;		/* Error register 3 */
	int	rmec1;		/* Burst error bit position */
	int	rmec2;		/* Burst error bit pattern */
	int	rmbae;		/* 11/70 bus extension */
	int	rmcs3;
};

/*
 * Number of drives (units) -- makes rmtimer() work better/faster
 */
#define	NRM	2

/*
 * Micro-offsetting constants
 */
#define	P400	020
#define	M400	0220
#define	P800	040
#define	M800	0240
#define	P1200	060
#define	M1200	0260

/*
 * Table of micro offsets for error recovery/retry
 */
int	rm_offset[16] =
{
	P400, M400, P400, M400,
	P800, M800, P800, M800,
	P1200, M1200, P1200, M1200,
	0, 0, 0, 0,
};

/*
 * Save slots for the last/current cylinder/sector positions
 * Makes CE's happy when the drives go bananas.
 */
short	cur_cyl[NRM], cur_da[NRM];
short	last_cyl[NRM], last_da[NRM];

/*
 * Names of the RP06 registers (RM05 looks alot like the RP06)
 * Used for printing out the register values on an error (CE's love this)
 */
char *regname[20] = {	/* There are 20 RP registers */
	"CS1", "WC", "BA", "DA", "CS2", "DS", "ER1", "AS", "LA", "DB",
	"MR", "DT", "SN", "OF", "DC", "CC", "ER2", "ER3", "EC1", "EC2"
};

struct device *rm_addr;

struct	size
{
	daddr_t	nblocks;
	int	cyloff;
} rm_sizes[8];

struct	iotime	rmstat[NRM];
struct	iobuf	rmtab = tabinit(RM0,0);
#define	sa(X)	tabinit(RM0,&rmstat[X].ios)
struct	iobuf	rmutab[NRM] = {
	sa(0), sa(1)	/* sa(2), sa(...), sa(7) (for more drives) */
};

/*
 * Magic numbers inherent to the drives
 */
#define	NSECT	32
#define	NTRAC	19
#define	SDIST	3
#define	RDIST	7
#define	GDIST	1

#define	GO	01
#define	RECAL	06
#define	DCLR	010
#define	RELEASE	012
#define	OFFSET	014
#define	RTC	016
#define	PRESET	020
#define	SEARCH	030
#define	WCOM	060
#define	RCOM	070

#define	ERR	040000	/* ds - Error */
#define	MOL	010000	/* ds - Medium online */
#define	VV	0100	/* ds - volume valid */
#define	DRY	0200	/* ds - drive ready  -- 9766 uses this for all */
#define	SC	0100000	/* cs1 - Special condition */
#define	TRE	040000	/* cs1 - transfer error */
#define	DVA	04000	/* cs1 - drive available */
#define	RDY	0200	/* cs1 - Ready */
#define	IE	0100	/* cs1 - interrupt enable */
#define	NED	010000	/* cs2 - non-existent device */
#define	CTLCLR	040	/* cs2 - init */
#define	WLE	04000	/* er1 - Write lock error */
#define	DCK	0100000	/* er1 - Data check */
#define	FMT22	010000	/* of - 16 bit /word format */
#define	ECI	04000	/* of - ecc inhibit */

#define	wtime	b_flags
#define	WOK	0
#define	WABORT	1
#define	WNED	2
#define	WMOL	4
#define	WERR	30

#define	acts	io_s1
#define	qcnt	io_s2

#define	trksec	av_back
#define	cylin	b_resid

#define	rh70	(cputype == 70)

rmopen(dev)
{
	register struct iobuf *dp;

	if ((rmtab.b_flags&B_ONCE)==0) {
		rmtab.b_flags |= B_ONCE;
		rmtimer();
	}
	dp = &rmutab[dev>>3];
	dp->io_addr = (physadr)rm_addr;
	dp->io_nreg = rh70?NDEVREG:NDEVREG-2;
}

rmclose(dev)
{
}

rmstrategy(bp)
register struct buf *bp;
{
	register struct iobuf *dp;
	register struct buf *ap;
	daddr_t	last;
	int	unit;
	int	co;

	unit = minor(bp->b_dev);
	last = rm_sizes[unit&07].nblocks;
	co = rm_sizes[unit&07].cyloff;
	unit >>= 3;
	dp = &rmutab[unit];
	if (bp->b_blkno < 0 || bp->b_blkno >= last) {
		if (bp->b_blkno == last && bp->b_flags&B_READ)
			bp->b_resid = bp->b_bcount;
		else {
			bp->b_flags |= B_ERROR;
			bp->b_error = ENXIO;
		}
		iodone(bp);
		return;
	}
	bp->av_forw = NULL;
	bp->b_start = lbolt;
	bp->cylin = bp->b_blkno/(NSECT*NTRAC) + co;
	co = bp->b_blkno%(NSECT*NTRAC);
	*((int *)(&bp->trksec)) = ((co/NSECT)<<8)+(co%NSECT);
	rmstat[unit].io_cnt++;
	rmstat[unit].io_bcnt += btoc(bp->b_bcount);
	spl6();
	dp->qcnt++;
	if (dp->b_actf == NULL) {
		dp->b_actf = bp;
		dp->b_actl = bp;
		dp->acts = (int)bp;
	} else {
		register struct buf *cp;
		if (((int)rmstat[unit].io_cnt&07) == 0)
			dp->acts = (int)dp->b_actl;
		for (ap = (struct buf *)dp->acts; cp = ap->av_forw; ap = cp) {
			int s1, s2;

			if ((s1 = ap->cylin - bp->cylin)<0) s1 = -s1;
			if ((s2 = ap->cylin - cp->cylin)<0) s2 = -s2;
			if (s1 < s2)
				break;
		}
		ap->av_forw = bp;
		if ((bp->av_forw = cp) == NULL)
			rmutab[unit].b_actl = bp;
	}
	if (dp->b_active == 0) {
		rmustart(unit);
		if (rmtab.b_active == 0 && rmtab.b_actf != NULL)
			rmstart();
	}
	spl0();
}

rmustart(unit)
{
	register struct buf *bp;
	register struct iobuf *dp;
	register struct device *rp;
	int	sn, dsn;

	dp = &rmutab[unit];
	rp = rm_addr;
	lobyte(rp->rmcs2) = unit;
	lobyte(rp->rmcs1) = IE;
	rp->rmas = 1<<unit;
	bp = dp->b_actf;
	if ((rp->rmds&MOL) == 0) {
		if (dp->wtime!=WOK)
			return;
		if (bp == NULL)
			dp->wtime = WOK;
		else if (rp->rmcs2&NED)
			dp->wtime = WNED;
		else if (!pwr_act)
			dp->wtime = WMOL;
		else
			dp->wtime = WERR;
		return;
	} else if (rp->rmds&ERR) {
		fmtberr(dp,(bp==NULL)?0:rm_sizes[minor(bp->b_dev)&07].cyloff);
		lobyte(rp->rmcs1) = IE|DCLR|GO;
		rmwait();
		if (rp->rmds&ERR || ++dp->b_errcnt > 28) {
			prrmreg(unit);
			dp->wtime = WERR;
			rp->rmas = 1<<unit;
			return;
		}
	}
	dp->wtime = WOK;
	if ((rp->rmds&VV) == 0) {
		lobyte(rp->rmcs1) = PRESET|GO;
		rp->rmof = FMT22;
		printf("CDC Drive %d online\n", unit);
		rmwait();
	}
	if (bp == NULL) {
		lobyte(rp->rmcs1) = IE|RELEASE|GO;
		return;
	}
	if ((dp->b_errcnt&07) == 04) {
		dp->b_errcnt++;
		lobyte(rp->rmcs1) = RECAL|GO;
		last_cyl[unit] = last_da[unit] = 0;
		rmwait();
		return;
	}
	if (dp->b_active == 0)
		dp->io_start = lbolt;
	dp->b_active++;
	sn = (int)bp->trksec&0377;
	dsn = (sn<<2) - (rp->rmla>>4) - GDIST;
	if (dsn<=0) dsn += NSECT*4;
	if (((bp->cylin - rp->rmcc) || dsn > RDIST*4) &&
	    dp->b_active<3) {
		rp->rmdc = bp->cylin;
		sn -= SDIST;
		if (sn<0) sn += NSECT;
		rp->rmda = sn;
	/* sector search, track ignored, lookahead SDIST */
		lobyte(rp->rmcs1) = IE|SEARCH|GO;
		if (dp->b_active == 1) {
			rmstat[unit].ios.io_misc++;
		}
	} else {
		dp->b_forw = NULL;
		if (rmtab.b_actf == NULL)
			rmtab.b_actf = (struct buf *)dp;
		else
			rmtab.b_actl->b_forw = (struct buf *)dp;
		rmtab.b_actl = (struct buf *)dp;
	}
}

rmstart()
{
	register struct buf *bp;
	register struct iobuf *dp;
	register struct device *rp;
	int unit;

    loop:
	if ((dp = (struct iobuf *)rmtab.b_actf) == NULL)
		return;
	if ((bp = dp->b_actf) == NULL) {
		rmtab.b_actf = dp->b_forw;
		goto loop;
	}
	rp = rm_addr;
	unit = minor(bp->b_dev)>>3;
	rp->rmcs2 = unit;
	rmtab.b_active++;
	if(rmtab.b_errcnt >= 16) {
		rp->rmof = rm_offset[rmtab.b_errcnt & 017] | FMT22;
		rp->rmcs1 = OFFSET|GO;
		rmwait();
	}
	cur_cyl[unit] = rp->rmdc = bp->cylin;
	cur_da[unit] = rp->rmda = (int)bp->trksec;
	rp->rmba = loword(bp->b_paddr);
	if (rh70)
		rp->rmbae = hiword(bp->b_paddr);
	rp->rmwc = -(bp->b_bcount>>1);
	blkacty |= (1<<RM0);
	if (bp->b_flags & B_READ)
		unit = ((hiword(bp->b_paddr)&3)<<8)|IE|RCOM|GO;
	else
		unit = ((hiword(bp->b_paddr)&3)<<8)|IE|WCOM|GO;
	if (pwr_act >= 0)
		rp->rmcs1 = unit;
}

rmintr()
{
	register struct buf *bp;
	register unit;
	register struct device *rp;
	struct iobuf *dp;
	int	as, cs1;

	rp = rm_addr;
	as = rp->rmas&0377;
	cs1 = rp->rmcs1;
	if (rmtab.b_active) {	/* data transfer underway */
		blkacty &= ~(1<<RM0);
		dp = (struct iobuf *)rmtab.b_actf;
		bp = dp->b_actf;
		unit = minor(bp->b_dev);
		lobyte(rp->rmcs2) = unit>>3;
		if (cs1 & TRE) {		/* error bit */
			if (++dp->b_errcnt>28)
				bp->b_flags |= B_ERROR;
			else if (rp->rmer1&WLE) {
				bp->b_flags |= B_ERROR;
				rmprint(bp->b_dev, "\nWrite lock error");
			} else {
				rmtab.b_active = 0;
			}
			fmtberr(dp,rm_sizes[unit&07].cyloff);
			if (dp->b_errcnt > 2){
				prrmreg(unit);
				prcom(rmprint, bp, rp->rmer1, rp->rmcs2);
			}
			if (rp->rmer1 == DCK) {
				if (rmecc(rp, bp))
					return;
			}
			rp->rmcs1 = TRE|IE|DCLR|GO;
			while((rp->rmcs1 & RDY) == 0)
				;
		}
		unit >>= 3;
		if (rmtab.b_active) {
			if (dp->io_erec)
				logberr(dp,bp->b_flags&B_ERROR);
			if (dp->b_errcnt){
				rp->rmcs1 = RTC|GO;
				rmwait();
			}
			rmtab.b_active = 0;
			dp->b_active = 0;
			dp->b_errcnt = 0;
			dp->b_actf = bp->av_forw;
			bp->b_resid = (-rp->rmwc)<<1;
			last_cyl[unit] = cur_cyl[unit];
			last_da[unit] = cur_da[unit];
			dp->qcnt--;
			if (bp == (struct buf *)dp->acts)
				dp->acts = (int)dp->b_actf;
			rp->rmcs1 = IE|RELEASE|GO;
			rmstat[unit].io_resp += lbolt - bp->b_start;
			rmstat[unit].io_act += lbolt - dp->io_start;
			iodone(bp);
		}
		rmtab.b_actf = dp->b_forw;
		if (dp->b_actf)
			rmustart(unit);
		as &= ~(1<<unit);
	} else {
		if (as == 0)
			rp->rmcs1 = IE;
		hibyte(rp->rmcs1) = TRE>>8;
	}
	for (unit=0; as; unit++)
		if (as&(1<<unit)) {
			as &= ~(1<<unit);
			rmustart(unit);
		}
	if (rmtab.b_actf != NULL)
		rmstart();
}

rmread(dev)
{
	if (physck(rm_sizes[dev&07].nblocks, B_READ))
		physio(rmstrategy, 0, dev, B_READ);
}

rmwrite(dev)
{
	if (physck(rm_sizes[dev&07].nblocks, B_WRITE))
		physio(rmstrategy, 0, dev, B_WRITE);
}

rmecc(rp, bp)
register struct device *rp;
register struct buf *bp;
{
	register i;
	int	b, n;

	if (rp->rmec2 == 0) {
		rp->rmof = FMT22;
		return(0);
	}
	i = rp->rmec1 - 1;
	n = i&017;
	i = (i&~017)>>3;
	b = ((rp->rmwc + (bp->b_bcount>>1) - 1)>>8)&0377;
	i += b<<BSHIFT;
	if ( i < bp->b_bcount)
		biput(bp, i, biget(bp, i)^(rp->rmec2<<n));
	i += 2;
	if ( i < bp->b_bcount)
		biput(bp, i, biget(bp, i)^(rp->rmec2>>(16-n)));
	rmtab.b_active++;
	if (rp->rmwc) {
		i = (int)bp->trksec;
		i = NSECT*(i>>8) + (i&0377) + b + 1;
		if (i >= NSECT*NTRAC) {
			i -= NSECT*NTRAC;
			rp->rmdc = bp->cylin + 1;
		} else
			rp->rmdc = bp->cylin;
		rp->rmda = ((i/NSECT)<<8) + (i%NSECT);
		i = (rp->rmcs1&~RDY)|IE|GO;
		rp->rmcs1 = TRE|IE|DCLR|GO;
		rmwait();
		if (pwr_act >= 0)
			rp->rmcs1 = i;
		return(1);
	} else
		return(0);
}

rmtimer()
{
	register unit;
	register struct iobuf *dp;
	register struct buf *bp;

	for (unit=0; unit < NRM; unit++) {
		dp = &rmutab[unit];
		if (dp->wtime == WOK)
			continue;
		rmustart(unit);
		if (dp->wtime == WOK)
			continue;
		if (dp->wtime == WABORT) {
			if (dp->io_erec)
				logberr(dp, B_ERROR);
			while (bp = dp->b_actf) {
				bp->b_flags |= B_ERROR;
				dp->b_actf = bp->av_forw;
				iodone(bp);
			}
			dp->b_active = 0;
			dp->b_errcnt = 0;
			dp->qcnt = 0;
			printf("CDC drive %d not available\n", unit);
		}
		dp->wtime--;
	}
	if (rmtab.b_active == 0 && rmtab.b_actf != NULL)
		rmstart();
	timeout(rmtimer, 0, 15*HZ);
}

rmclr()
{
	register unit;

	rm_addr->rmcs2 = CTLCLR;
	rmtab.b_active = 0;
	rmtab.b_actf = NULL;
	for (unit = 0; unit < NRM; unit++)
		rmustart(unit);
	rmstart();
}

rmprint(dev, str)
char *str;
{
	printf("%s on CDC drive %d, slice %d\n", str, (dev>>3)&7, dev&7);
}

prrmreg(unit)
int	unit;
{
	register int *rp;
	register short i, j;

	rp = (int *)0177570;
	if((unsigned int)(*rp) < 010)
		return;		/* Skip the big messages if csw < 8 */

	rp = rm_addr;
	printf("CDC Drive %d registers:\n", unit);
	for(j = 0; j < 20; j += 10){
		for(i = 0; i < 10; i++)
			printf("RP%s\t", regname[j + i]);
		printf("\n");
		for(i = 0; i < 10; i++)
			printf("%o\t", rp[j + i]);
		printf("\n\n");
	}
	printf("Prev. Cyl. = %d\t\tPrev. Trk. = %d\t\tPrev. Sec. = %d\n\n",
		last_cyl[unit], (last_da[unit] >> 8) & 037,
		last_da[unit] & 037);
}

rmwait()
{
	register struct device *rp;

	rp = rm_addr;
	while((rp->rmds & DRY) == 0)
		;
}
