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


/*
 * 40-mb disk driver
 */ 

extern int	nd40;
extern char	d40addr[];
extern int	d40selch;
extern int	d40cntl;
extern struct dskmap d40map[];

#define NDSKERR	10		/* number of error retries */

int d40start(), d40scintr();

struct buf d40tab;
struct buf rd40buf;

struct selchq d40scq {
	&d40start,
	&d40scintr,
	0
};


		/*** CAUTION: offsets of fields in following struct are
				not to be tampered with			***/
struct dsk {
	char dk_active;		/* disc active flag */ 
	char dk_errcnt;		/* error count		*/ 
	char dk_prep;		/* i/o preparatory flag */ 
	short dk_cyl;		/* current cylinder pos'n */ 
	short dk_dir;		/* current arm direction */ 
	struct buf *dk_actf;	/* head of current i/o queue */ 
	struct buf *av_forw;	/* head of pending i/o queue */ 
} d40[4];

#define	drivemap(dev)		&d40[minor(dev) >> 3]

#define	b_dskad			b_resid

struct dsk_head_locn {		/* fields of b_dskad */
	short b_cyl;			/* cylinder number */ 
	char b_head;			/* head number */ 
	char b_sect;			/* sector number */ 
};


#define	d40file(bp)	d40addr[minor(bp->b_dev)>>3]
#define	d40cyl(bp)	bp->b_dskad.b_cyl
#define	d40head(bp)	bp->b_dskad.b_head
#define d40sect(bp)	bp->b_dskad.b_sect

#define	DSEEK		1
#define	DIO		2


/* disk file status & commands */ 

#define	WRT_PROT	0x80
#define	UNRCV		0x19
#define	DSK_JUNK	0x24	/* irrelevant bits */
#define	NOT_READY	0x08

#define	SEEK		0x42

/* disk controller status & commands */ 

#define	CNTL_UNRCV	0x61
#define	CYL_OV		0x10
#define	IDLE		0x02

#define	WRITE		0x42
#define	READ		0x41
#define	SETHEAD		0x20
#define	SETCYL		0x10
#define	RSTGATN		0x08
#define	RSTHEAD		0x04

/*
 *     disc size info
 */ 

#define	SECTORS		20	/* sectors per track */
#define	HEADS		20	/* tracks per cylinder */
#define	PERTRACK	200	/* # blocks per cylinder  */



/*
 *	disk strategy routine
 */ 
d40strategy(bp)
	register struct buf *bp;
{
	register struct dsk *dp;
	register struct buf *p1, *p2;
	register int i;

	bp->b_resid = bp->b_bcount;

	if(minor(bp->b_dev) >= nd40<<3) {
		bp->b_flags |= B_ERROR;
		iodone(bp);
		return;
	}

	/*
	 * Block no. too high -- looks like EOF for raw read, error otherwise
	 */ 
	if(bp->b_blkno >= d40map[minor(bp->b_dev)&7].dm_nblk) {
		if((bp->b_flags & (B_PHYS|B_READ)) != (B_PHYS|B_READ))
			bp->b_flags |= B_ERROR;
		iodone(bp);
		return;
	}

	/* Calculate cylinder : head : sector */ 

	i = (bp->b_blkno + d40map[minor(bp->b_dev)&07].dm_baddr) * 2;

	bp->b_dskad.b_sect = i % SECTORS;
	i /= SECTORS;
	bp->b_dskad.b_head = i % HEADS;
	bp->b_dskad.b_cyl = i / HEADS;


	dp = drivemap(bp->b_dev);

	spl5();

	for(p1 = dp; (p2 = p1->av_forw) &&
		      ( (dp->dk_dir) ? (d40cyl(p2) <= d40cyl(bp) ) :
				       (d40cyl(p2) >= d40cyl(bp) ) ); )
		p1 = p2;

	bp->av_forw = p2;
	p1->av_forw = bp;

	if(!(dp->dk_active || d40tab.b_active))
		selchreq(d40selch, &d40scq);

	spl0();
}

/*
 * start next disk i/o operation
 *	- check disk status
 *	- initiate seek
 */ 

d40start()
{
	register struct dsk *dp, *idp;
	register struct buf *bp;
	register int scmd, ccmd, stat, file, ead;

	idp = 0;
	if((dp = d40tab.b_actf) == 0)	/* get active drive */ 
		d40tab.b_actf = dp = d40;	/* if none, start at first */ 


	do {
		if(++dp >= &d40[4])
			dp = d40;	/* round-robin	*/ 
		while(!dp->dk_active) {	/* while disc not active */ 
			if(!(bp = dp->dk_actf)) {	/* if active q empty */ 
				if(!(bp = dp->av_forw)) /* if pending q empty */ 
					break;
				else {
					dp->dk_actf = bp;	/* swap queues	*/ 
					dp->av_forw = 0;
					dp->dk_dir = ~dp->dk_dir;	/* change arm dir'n  */ 
				}
			}

			file = d40file(bp);
			stat = ss(file);
			if(dp->dk_cyl == d40cyl(bp)) {	/* seek done  */ 

				if(idp)
					break;

				if (stat & ~(WRT_PROT|DSK_JUNK)) {
					if (stat & UNRCV)
						dp->dk_errcnt = NDSKERR;
					d40error(dp, stat, file);
					continue;
				}
				if (stat & WRT_PROT && !(bp->b_flags & B_READ)) {
					dp->dk_errcnt = 0;
					dp->dk_actf = bp->av_forw;
					d40tab.b_active = 0;
					bp->b_flags |= B_ERROR;
					bp->b_resid = bp->b_bcount;	/* ?? */
					iodone(bp);
					continue;
				}

				idp = dp;
				break;
			}

			/* seek required */ 

			if (stat & UNRCV) {
				dp->dk_errcnt = NDSKERR;	/* no retries */ 
				d40error(dp, stat, file);
				continue;
			}

			dp->dk_active = DSEEK;

			wh(file, d40cyl(bp));
			oc(file, SETCYL);
			while(!(ss( d40cntl )&IDLE))
				;

			d40prep(dp);
			oc(file, SEEK);

			break;

		}
	} while(dp != d40tab.b_actf);

	/* start next i/o : */ 
	if(!idp) {
		selchfree(d40selch);
		return;
	}

	d40tab.b_actf = dp = idp;
	bp = idp->dk_actf;

	if(!dp->dk_prep)
			d40prep(dp);
	else
		ss(d40file(bp));

	ead = bp->b_un.b_addr+bp->b_bcount-1;

	if(bp->b_flags & B_READ) {
		scmd = READ_GO;
		ccmd = READ;
	} else {
		scmd = GO;
		ccmd = WRITE;
	}

	trace(010<<16, "dcmd", ccmd);
	trace(010<<16, "sector", d40sect(bp));
	trace(010<<16, "head", d40head(bp));

	oc(d40selch, STOP);

	trace(010<<16, "bufstart", bp->b_un.b_addr);
	trace(010<<16, "bufend", ead);

	wdh(d40selch, bp->b_un.b_addr);
	wdh(d40selch, ead);

	wd(d40cntl, d40sect(bp));
	wh(d40cntl, (d40head(bp)<<10) | d40cyl(bp));

	oc(d40cntl, ccmd);
	oc(d40selch, scmd);

	idp->dk_active = DIO;
	d40tab.b_active++;
	dp->dk_prep = 0;
}

/*
 * disk interrupt routine
 *	-disk interrupts only after a seek (I hope)
 */ 

d40intr(dev, stat)
{
	register struct buf *bp;
	register struct dsk *dp;


	trace(020<<16, "status", stat);

	dp = &d40[dev];

	if(dp->dk_active != DSEEK)
		return;

	trace(020<<16, "interrupt", d40addr[dev]);

	dp->dk_active = 0;
	dp->dk_cyl = d40cyl(dp->dk_actf);
	if(!d40tab.b_active)
		selchreq(d40selch, &d40scq);
}

/*
 * selch interrupt routine
 *	-selch interrupt will be followed by a controller interrupt
 */ 

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

	if(!(dp = d40tab.b_actf))
		return;
	oc(d40selch, STOP);
}

/*
 * disk controller interrupt routine
 *	-on cylinder overflow start i/o on next cylinder
 *	-check ending status, signal i/o done
 */ 

d40cintr(dev, stat)
{
	register struct buf *bp;
	register struct dsk *dp;
	register i, resid;


	trace(020<<16, "interrupt", d40cntl);
	trace(020<<16, "status", stat);

	d40tab.b_active = 0;
	if(!(dp = d40tab.b_actf) || dp->dk_active != DIO)
		return;

	dp->dk_active = 0;
	bp = dp->dk_actf;

	if(stat&CNTL_UNRCV) {
		d40error(dp, stat, d40cntl);
		d40start();
		return;
	}

	/*
	 * Cylinder overflow
	 */ 
	if(!(stat&CYL_OV)) {
		bp->b_resid = 0;
	} else {
		oc(d40selch, STOP);
		resid = (rdh(d40selch) - bp->b_un.b_addr + 2) & ~0377;
		trace(020<<16, "cyl ov", bp->b_un.b_addr);

		bp->b_un.b_addr += resid;
		bp->b_bcount -= resid;

		bp->b_dskad.b_sect = 0;
		bp->b_dskad.b_head = 0;

		i = minor(bp->b_dev) & 07;
		if(++bp->b_dskad.b_cyl < (d40map[i].dm_baddr+d40map[i].dm_nblk)/PERTRACK) {
			d40start();
			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;
	d40tab.b_active = 0;
	iodone(bp);
	d40start();
}

d40error(dp, stat, addr)
	register struct dsk *dp;
{
	register struct buf *bp;

	bp = dp->dk_actf;
	deverror(bp, stat, addr);
	dp->dk_cyl = -1;		/* force a seek next I/O */

	if(++dp->dk_errcnt > NDSKERR) {
		dp->dk_errcnt = 0;
		bp->b_flags |= B_ERROR;
		bp->b_resid = bp->b_bcount;		/** ?? needed or not ?? **/
		dp->dk_actf = bp->av_forw;
		iodone(bp);
	}
}

/*
 * 'Raw' disc interface
 */ 
d40read(dev)
{
	physio(d40strategy, &rd40buf, dev, B_READ);
}

d40write(dev)
{
	physio(d40strategy, &rd40buf, dev, B_WRITE);
}


/*
 *	prepare for I/O - done before seek if any is needed
 */

d40prep(dp)
	struct dsk *dp;
{
	register struct buf *bp;
	register file, cntrl;

	bp = dp->dk_actf;
	file = d40file(bp);
	cntrl = d40cntl;

	oc(file, RSTGATN);
	while(!(ss(cntrl)&IDLE))
		;
	oc(file, RSTHEAD);
	while(!(ss(cntrl)&IDLE))
		;
	wh(file, d40head(bp));
	oc(file, SETHEAD);
	while(!(ss(cntrl)&IDLE))
		;
	dp->dk_prep = 1;
}
