/*
 * 2.5-MB and 10-MB cartridge disk driver: Multi-disc version
 *	- overlapped seeks
 *	- seeks ordered by 'FSCAN' algorithm
 *	- dual-platter logical disks
 */

#include "../h/local.h"

#ifdef  SCCS_ID
static char SCCS_ID [] = "@(#)dsk.c        3.3      14:58:45 - 82/09/10 ";
#endif  SCCS_ID

#include "../h/param.h"
#include "../h/buf.h"
#include "../h/conf.h"
#include "../h/selch.h"
#include "../h/systm.h"

#define NBPC		24	/* number of blocks per cylinder */
#define NDSKCYL		(dsksize? 408 : 203)	/* no of cylinders per disk */
#define NDSKERR		10	/* number of error retries */

extern int	ndsk;		/* number of disks */
extern int	dskcntl;	/* disk controller address */
extern char	dskaddr[];	/* disk file addresses */
extern int	dskselch;	/* channel address for disk controller */
extern int	dsksize;	/* 0 => 2.5M,  1 => 10M */

int dskstart(), dskscintr(), dsknrdy();

struct buf	rdskbuf;	/* raw I/O buffer */
struct buf	dsktab;		/* major device table (per controller)
				 * b_active = controller busy flag
				 * b_actf = ptr to dsk table for last
				 *	accessed drive
				 */
struct dsk {		/* disk table (per drive) */
	char	dk_active;	/* drive busy flag */
	char	dk_errcnt;	/* error count (for recovery) */
	int	dk_cyl;		/* current cylinder position */
	int	dk_dir;		/* current direction of arm motion */
	struct buf *av_forw;	/* head of I/O queue for next pass */
	struct buf *dk_actf;	/* head of I/O queue for this pass */
	int	dk_junk[3];	/* kludge padding to power of two */
} dsk[4] = {
	{ 0, 0, -1 },
	{ 0, 0, -1 },
	{ 0, 0, -1 },
	{ 0, 0, -1 }
};


#define DSEEK	1
#define DIO	2
#define DNRDY	3

struct selchq dskscq {	/* channel queue entry */
	&dskstart,
	&dskscintr,
	0,
	0
};

/* Redefined buffer fields */

struct	{	/* kludge to be cleaned later */
	int	integ;
};
struct	{	/* as above */
	char	pad[2];
	char	hibyte, lobyte;
};

#define b_cyl		av_back.integ
#define b_sector	b_resid.hibyte
#define b_file		b_resid.lobyte

/* Disk file status & commands */

#define UNRCV		0x62	/* wrt chk|ill addr|seek inc */
#define ADDR_INTLK	0x10
#define WRT_PROT	0x80
#define NOT_READY	0x01

#define SEEK		0x42

/* Disk controller status & commands */

#define CNTL_UNRCV	0xe1	/* overrun|addr comp fail|def trk|data err */
#define OVERRUN		0x80
#define CYL_OV		0x10
#define IDLE		0x02

#define READ		0x01
#define WRITE		0x02

/*
 * Disk strategy routine
 *	- check block no. & translate to cylinder/head/sector
 *	- put buffer on queue for corresponding drive
 *	- if controller free, call dskstart() to start I/O
 */

dskstrategy(bp)
register struct buf	*bp;
{
	register struct dsk	*dp;
	register struct buf	*p1, *p2;
	register int		dev;
	register int		dn;

trace(1<<16, "dstrategy", bp);

	if ((minor(bp->b_dev)&07) >= ndsk) {
		bp->b_flags |= B_ERROR;
		bp->b_resid = bp->b_bcount;
		iodone(bp);
		return;
	}

	/*
	 * Calculate file/cylinder/head/sector
	 */
	bp->b_cyl = bp->b_blkno / NBPC;
	if ((bp->b_sector = (bp->b_blkno<<1) % (2*NBPC)) >= NBPC)
		bp->b_sector += 32-NBPC;

	/*
	 * Minor device n>=8 is interleaved cylinders of devices [n]-8, [n]-7
	 *	(where [n] is n rounded down to an even number)
	 */
	if ((dev = minor(bp->b_dev)) < 8)
		bp->b_file = dskaddr[dev];
	else {
		bp->b_file = (dskaddr[dev&06]) | (bp->b_cyl&01);
		bp->b_cyl >>= 1;
	}

	/*
	 * Block no. too high -- looks like EOF for raw read, error otherwise
	 */
	if (bp->b_cyl >= NDSKCYL) {
		if ((bp->b_flags&(B_PHYS|B_READ)) != (B_PHYS|B_READ))
			bp->b_flags |= B_ERROR;
		bp->b_resid = bp->b_bcount;
		iodone(bp);
		return;
	}

	/*
	 * Put buffer on queue for drive
	 */
	dp = &dsk[(bp->b_dev&07)>>dsksize];
	dn = dp - dsk;
	dk_numb[dn]++;
	dk_wds[dn] += (bp->b_bcount>>6);
	spl5();
		for (p1 = dp; (p2 = p1->av_forw) &&
			(dp->dk_dir? (p2->b_cyl <= bp->b_cyl)
			    : (p2->b_cyl >= bp->b_cyl) ); )
			p1 = p2;
		bp->av_forw = p2;
		p1->av_forw = bp;

		/*
		 * Start I/O
		 */
		if (!(dp->dk_active || dsktab.b_active))
			selchreq(dskselch, &dskscq, 0);
	spl0();
}

/*
 * Disk startup routine
 *	- start overlapped seeks on all drives
 *	- check status of finished seeks
 *	- start I/O transfer on 'next' drive (round robin)
 */

dskstart(unit)
{
	register struct dsk	*dp, *idp;
	register struct buf	*bp;
	register int		file, stat;

	idp = 0;
	if ((dp = dsktab.b_actf) == 0)
		dsktab.b_actf = dp = dsk;

trace(1<<16, "dstart", dp);
	do {		/* for each drive */
	/*
	 * Start overlapped seeks on all drives
	 */
		if (++dp >= &dsk[4])
			dp = dsk;

		while (!dp->dk_active) {
			if (!(bp = dp->dk_actf)) {
				if (!(bp = dp->av_forw))
					break;
				else {
					dp->dk_actf = bp;
					dp->av_forw = 0;
					dp->dk_dir = ~dp->dk_dir;
				}
			}
			file = bp->b_file;
			while ((stat = ss(file)) & ADDR_INTLK)
				;
			if (bp->b_cyl == dp->dk_cyl) {	/* seek done */
				if (idp)
					break;
				/* check status from seek */
				if (stat & ~WRT_PROT) {
					dskerror(dp, stat, file);
					dp->dk_cyl = -1;	/* force seek */
					continue;
				}
				idp = dp;
				break;
			}
			/* seek required */
			if (stat & NOT_READY) {
				dp->dk_active = DNRDY;
				timeout(&dsknrdy, dp, 1000);
				break;
			}
			if (stat & UNRCV) {
				dp->dk_errcnt = NDSKERR;	/* no retry */
				dskerror(dp, stat, file);
				continue;
			}
			dp->dk_active = DSEEK;
			dk_busy |= 1<<(dp-dsk);
			wh(file, bp->b_cyl);
			oc(file, SEEK);
trace(2<<16, "seek", file);
trace(2<<16, "cyl", bp->b_cyl);
			break;
		}
	} while (dp != dsktab.b_actf);

	/*
	 * Start next I/O transfer
	 */
	if (!idp)	/* no I/O to start */
		selchfree(dskselch);
	else {
		dsktab.b_actf = idp;
		bp = idp->dk_actf;
		file = bp->b_file;
		while (ss(file) & ADDR_INTLK)
			;
		oc(dskselch, STOP);
		wdh(dskselch, bp->b_un.b_addr);
		wdh(dskselch, bp->b_un.b_addr+bp->b_bcount-1);
		wh(file, bp->b_cyl);
		wd(dskcntl, bp->b_sector);
		if (bp->b_flags & B_READ) {
			oc(dskcntl, READ);
			oc(dskselch, READ_GO);
		} else {
			oc(dskcntl, WRITE);
			oc(dskselch, GO);
		}
trace(2<<16, "dio", file);
trace(2<<16, "sector", bp->b_sector);
		idp->dk_active = DIO;
		dk_busy |= 1<<(idp-dsk);
		dsktab.b_active++;
	}
}

/*
 * Disk not ready routine:
 *	- restart disk every 10 seconds until it comes ready again
 *	- (this should not be necessary: according the Interdata manual there
 *	  should be an interrupt when NRSRW drops)
 *
 * NOTE:
 *	This will not work when multi-level interrupts are implemented.  As 
 * this routine is called from the clock interrupt, it may be out of sync with
 * dskstart(), which will operate at a lower interrupt level.
 */
dsknrdy(dp)
struct dsk *dp;
{
	dp->dk_active = 0;
	if (dsktab.b_active == 0)
		selchreq(dskselch, &dskscq, 0);
}

/*
 * Disk interrupt routine
 *	- disk interrupts only after a seek
 *	- status will be checked by dskstart() when controller is free
 */

dskintr(dev, stat)
{
	register struct dsk	*dp;

trace(4<<16, "interrupt", dskaddr[dev]);
trace(4<<16, "status", stat);
	dp = &dsk[dev >> dsksize];

	if (dp->dk_active != DSEEK)
		return;
	dp->dk_active = 0;
	dk_busy &= ~(1<<(dp-dsk));

	dp->dk_cyl = dp->dk_actf->b_cyl;
	if (!dsktab.b_active)
		selchreq(dskselch, &dskscq, 0);
}

/*
 * Selch interrupt routine
 *	- selch interrupt will be followed by a controller interrupt
 *	  (unless OVERRUN status is set)
 */

dskscintr(dev, stat, unit)
{
	register struct dsk	*dp;

	if (!(dp = dsktab.b_actf))
		return;

	oc(dskselch, STOP);
	if ((stat = ss(dskcntl)) & OVERRUN) {
		dskerror(dp, stat, dskcntl);
		dsktab.b_active = dp->dk_active = 0;
		dk_busy &= ~(1<<(dp-dsk));
		dskstart(0);
	}
}

/*
 * Disk controller interrupt routine
 *	- check transfer status
 *	- on cylinder overflow, restart I/O on next cylinder
 *	- signal I/O done
 */

cntlintr(dev, stat)
register int	stat;
{
	register struct dsk	*dp;
	register struct buf	*bp;
	register int		resid;

trace(4<<16, "interrupt", dskcntl);
trace(4<<16, "status", stat);
	dsktab.b_active = 0;
	if (!(dp = dsktab.b_actf) || dp->dk_active != DIO)
		return;
	dp->dk_active = 0;
	dk_busy &= ~(1<<(dp-dsk));
	bp = dp->dk_actf;

	if (stat & CNTL_UNRCV) {
		dskerror(dp, stat, dskcntl);
		dp->dk_cyl = -1;		/* force seek */
		dskstart(0);
		return;
	}

	/* cylinder overflow */
	if ((stat & CYL_OV) == 0)
		bp->b_resid = 0;
	else {
		oc(dskselch, STOP);
		resid = (rdh(dskselch)-bp->b_un.b_addr+2) & ~0xff;
		bp->b_un.b_addr += resid;
		bp->b_bcount -= resid;
		bp->b_sector = 0;
trace(1<<16, "cylov", bp->b_cyl);
		if (minor(bp->b_dev) < 8)
			bp->b_cyl++;
		else {
			/*
			 * Dual-platter logical disk:  switch platters
			 */
			if (bp->b_file&01)
				bp->b_cyl++;
			bp->b_file ^= 01;
		}
		if (bp->b_cyl < NDSKCYL) {
			dskstart(0);
			return;
		} else {
			if ((bp->b_flags&(B_PHYS|B_READ)) != (B_PHYS|B_READ))
				bp->b_flags |= B_ERROR;
			bp->b_resid = resid;
		}
	}

	dp->dk_errcnt = 0;
	dp->dk_actf = bp->av_forw;
	iodone(bp);
	dskstart(0);
}

/*
 * Common error routine
 */
dskerror(dp, stat, dev)
register struct dsk	*dp;
{
	register struct buf	*bp;

	bp = dp->dk_actf;
	deverror(bp, stat, dev);
	if (++dp->dk_errcnt > NDSKERR) {
		dp->dk_errcnt = 0;
		bp->b_flags |= B_ERROR;
		dp->dk_actf = bp->av_forw;
		iodone(bp);
	}
}

/*
 * 'Raw' disk interface
 */

dskread(dev)
{
	physio(dskstrategy, &rdskbuf, dev, B_READ);
}

dskwrite(dev)
{
	physio(dskstrategy, &rdskbuf, dev, B_WRITE);
}

/*
 * Dump all of memory to disk at end of swap area
 */
dskdump()
{
	register selch, cntl, file;
	register cyl, len;
	register char *memp;
	int dcyl;

	/* Make sure this is the right driver */
	if (bdevsw[major(swapdev)].d_strategy != dskstrategy) {
		printf("Wrong dump\n");
		return;
	}
	/* Set up device addresses */
	selch = dskselch;
	cntl = dskcntl;
	file = dskaddr[minor(swapdev)&07];

	/* Dump from memory address 0 */
	memp = (char *)0;
	dcyl = cyl = (swplo + 1 + nswap - ((memtop+BSIZE)>>BSHIFT)) / NBPC;
	while ((len = memtop-memp) > 0) {
		/* Dump a cylinder at a time */
		if (len > NBPC*BSIZE)
			len = NBPC*BSIZE;
		oc(selch, STOP);
		wh(file, cyl);
		oc(file, SEEK);
		while (ss(file) != 0)
			;
		wdh(selch, memp);
		wdh(selch, memp + len - 1);
		wh(file, cyl);
		wd(cntl, 0);
		oc(cntl, WRITE);
		oc(selch, GO);
		while (ss(selch)&SELCHBSY)
			;
		oc(selch, STOP);
		while (ss(cntl) != IDLE)
			;
		memp += len;
		cyl++;
	}
	printf("Dumped to cyl %d\n", dcyl);
}
