V7M/sys/dev/hk.c

Compare this file to the similar file:
Show the results in this format:

/*
 * UNIX/v7m RK06/7 disk driver
 * Fred Canter 6/19/81
 *
 * This driver supports, overlapped seek,
 * interleaved file systems, head offset positioning,
 * and full ECC recovery on block and raw I/O transfers.
 *
 * Thanks to Jerry Brenner for 95% of this driver !
 */

#include "../h/param.h"
#include "../h/systm.h"
#include "../h/buf.h"
#include "../h/conf.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/seg.h"

/*
 * Define REGDUMP to get a dump of all the
 * RK06/7 device registers on an error.
 */
/*#define	REGDUMP	1	*/

#define HKEOFF	      1       /* if 1 then enable offset recovery */

/*
 *
 *	The following  structure defines the order of the Control
 *	and Status registers for the RK06/07
 *
 */
struct device
{
	int	hkcs1;	/* Control and Status register 1 */
	int	hkwc;	/* Word Count Register */
	caddr_t hkba;	/* Memory Address Register */
	int	hkda;	/* Track Sector Address Register */
	int	hkcs2;	/* Control And Status Register 2 */
	int	hkds;	/* Drive Status Register */
	int	hkerr;	/* Error Register */
	union{
		int	w; /* High byte is Attention Summary */
		char c[2]; /* Low is Offset Reg. */
	}hkas;
	int	hkdc;	/* Desired Cylinder Register */
	int	dum;	/* Dummy for unused location */
	int	hkdb;	/* Data Buffer */
	int	hkmr1;	/* Maintenance Register 1 */
	int	hkec1;	/* ECC Position Register */
	int	hkec2;	/* ECC Pattern Register */
	int	hkmr2;	/* Maint. Register 2 */
	int	hkmr3;	/* Maint. Register 3 */
};

#define	HKADDR	((struct device *)0177440)	/* RK6/711 address */
#define NHK	2	/* number of drives on system */
#define NSECT	22	/* number of sectors per pack */
#define NTRAC	3	/* number of tracks per cylinder */
#define NUMTRY 28	/* max retry count */

/*
 *
 *	The structure hk_sizes defines the logical separation of a disk pack
 *	into mountable file systems.
 *
 *	The file systems are named in numeric order.
 *
 *		the first entry in hk_sizes describes file system hk0
 *		the next hk1  , etc.
 *
 *	The nblocks entry specifies the number of blocks (512 bytes) in
 *	the file system.
 *
 *	The cyloff entry specifies the offset+1 from the physical beginning
 *	of the pack to the beginning of the file system in cylinders
 *
 */
struct	size
{
	daddr_t	nblocks;
	int	cyloff;
} hk_sizes[8] =
{
	9636,	0,	/* cyl   0-145, root on rk06/7		*/
	8910,	146,	/* cyl 146-280, swap on rk06/7		*/
	8514,	281,	/* cyl 281-409, rest of rk06 pack	*/
	26598,	411,	/* cyl 411-813, rest of rk07 pack	*/
	0,	0,	/* spare, for avoiding bad blocks	*/
	0,	0,	/* spare, for avoiding bad blocks	*/
	27060,	0,	/* cyl   0-409, all of rk06 pack	*/
	53724,	0,	/* cyl   0-813, all of rk07 back	*/
};

/*
 *
 * The following definitions specify the offset values
 * used during error recovery
 *
 */
#ifdef HKEOFF
#define P25	01	/* +25 Rk06, +12.5 Rk07 */
#define M25	0201	/* -25 RK06, -12.5 RK07 */
#define	P200	010	/* +200 RK06, +100 RK07 */
#define M200	0210	/* -200, RK06, -100 RK07 */
#define P400	020	/* +400  RK06 , +200  RK07 */
#define M400	0220	/* -400  RK06 , -200  RK07 */
#define P800	040	/* +800  RK06 , +400 RK07 */
#define M800	0240	/* -800  RK06 , -400  RK07 */
#define P1200	060	/* +1200  RK06 , +600  RK07 */
#define M1200	0260	/* -1200  RK06 , -600 Rk07 */
/*
 *
 *	The following array defines the order in which the offset values defined above
 *	are used during error recovery.
 *
 */
int	hk_offset[16] =
{
	P25, M25, P200, M200,
	P400, M400, P400, M400,
	P800, M800, P800, M800,
	P1200, M1200, P1200, M1200,
};
#endif

/*
*	Control and Status bit definitions for  hkcs1
*/
#define GO	01	/* GO bit */
#define SELECT	01	/* Select Function */
#define PAKACK	02	/* Pack Acknowledge Function */
#define DCLR	04	/* Drive Clear Function */
#define UNLOAD	06	/* Unload Heads Function */
#define STRSPN	010	/* Start Spindle Function */
#define RECAL	012	/* Recalibrate Function */
#define OFFSET	014	/* Offset Function */
#define SEEK	016	/* Seek Function */
#define RCOM	020	/* Read Command */
#define WCOM	022	/* Write Command */
#define RHDR	024	/* Read Header	*/
#define WHDR	026	/* Write Header */
#define WCHK	030	/* Write Check Function */
#define IEI	0100	/* Interrupt Inable bit */
#define CRDY	0200	/* Controller Ready bit */
#define SEL7	02000	/* Select RK07 bit */
#define CTO	04000	/* Controller Time Out bit */
#define CFMT	010000	/* Controller Format bit */
#define DTCPAR	020000	/* Drive to Controller Parity Error */
#define DINTR	040000	/* Drive Interrupt bit */
#define CCLR	0100000 /* Controller Clear bit */
#define CERR	0100000 /* Controller Error bit */

/*
*	Control and Status bit definitions for  hkcs2
*/
#define DLT	0100000 /* Data Late error */
#define WCE	040000	/* Write Check Error */
#define UPE	020000	/* Unibus Parity Error */
#define NED	010000	/* Nonexistent Drive error */
#define NEM	04000	/* Nonexistent Memory error */
#define PGE	02000	/* Programming error */
#define MDS	01000	/* Multiple Drive Select error */
#define UFE	0400	/* Unit Field Error */
#define SCLR	040	/* Subsystem Clear bit */
#define BAI	020	/* Bus Address Increment Inhibit bit */
#define RLS	010	/* Release bit	*/

/*
*	Control and Status bit definitions for  hkds
*/
#define SVAL	0100000 /* Status Valid bit */
#define CDA	040000	/* Current Drive Attention bit */
#define PIP	020000	/* Position In Progress bit */
#define WRL	04000	/* Write Lock bit */
#define DDT	0400	/* Disk Drive Type bit */
#define DRDY	0200	/* Drive Ready bit */
#define VV	0100	/* Volume Valid bit */
#define DROT	040	/* Drive Off Track Error */
#define SPLS	020	/* Speed Loss Error	*/
#define ACLO	010	/* Drive AC Low */
#define DOFST	04	/* Drive Offset bit */
#define DRA	01	/* Drive Available bit */

/*
*	Control and Status bit definitions for  hkerr
*/
#define DCK	0100000 /* Data Check error */
#define DUNS	040000	/* Drive Unsafe error */
#define OPI	020000	/* Operation Incomplete error */
#define DTE	010000	/* Drive Timing error */
#define DWLE	04000	/* Drive Write Lock error */
#define IDAE	02000	/* Invalid Disk Address Error */
#define COE	01000	/* Cylinder Overflow Error	*/
#define HRVC	0400	/* Header Vertical Redundance Check Error */
#define BSE	0200	/* Bad Sector Error */
#define ECH	0100	/* Error Correction Hard error */
#define DTYE	040	/* Drive Type error */
#define FMTE	020	/* Format Error */
#define CDPAR	010	/* Controller To Driver Parity Error */
#define NXF	04	/* Nonexecutable Function Error */
#define SKI	02	/* Seek Incomplete error */
#define ILF	01	/* Illegal Function Error */

struct
{
	int  drvtyp;	/* This contains the drive type */
	char mntflg;	/* This contains a mounted flag it is set to indicate*/
			/* that a drive type is known to the system */
	char recal;	/* recalibrate flag */
	int  ccyl;	/* stores drives current cylinder  */
	int  cs1;	/* Error reporting save locations */
	int  wc;
	int  ba;
	int  da;
	int  cs2;
	int  ds;
	int  err;
	int  as;
	int  dc;
	int  mr1;
	int  mr2;
	int  mr3;
}hk_r[NHK];

struct buf	hktab;	/* This is the buffer header used exclusivly by the RK06/07 for block I/O */
			/* the structure is the same as buf.h but useage is slightly different */

struct buf	rhkbuf; /* This buffer header is used by the RK06/07 for raw I/O. struct. same as hktab */

struct buf	hkutab[NHK]; /* Unit headers for overlapped seek driver */

#define trksec	av_back
#define cylin	b_resid
/*
 * Instrumentation (iostat) structures
 */

struct	ios	hk_ios[NHK];
#define DK_N	2
#define	DK_T	3	/* RK06/7 transfer rate indicator */

#define b_cylin b_resid

daddr_t dkblock();

/********   flag field defines for hk tables   **********/
#define HKBUSY	01	/* drive or controller is busy */
#define HKSEEK	02	/* drive is seeking */
#define HKOFFS	04	/* drive in offset mode */




/*
*
*	The following routine determines the drive type of the selected unit,
*	saves drive type in hk_r[].drvtyp,
*	and sets flag in hk_r[].mntflg
*
*/
hkmnt(dn)
{
	register int pri;

		hk_r[dn].drvtyp = 0;	/* set default drive type to RK06 */
		hk_ios[dn].dk_tr = DK_T;   /* (iostat) xfer rate indicator */
/*
 * Tell the instrumentation code in
 * the clock routine, where the iostat
 * structure for this controller is and
 * how many drives are on this controller.
 * Clock interrupts are locked out while
 * iostat pointers and counts are being set up !
 */
		pri = spl6();
		dk_iop[DK_N] = &hk_ios[0];
		dk_nd[DK_N] = NHK;
		splx(pri);
		hkcmd(dn, GO);	   /* select drive */
		if(HKADDR->hkcs1 & CERR && HKADDR->hkerr & DTYE)
		{
			hk_r[dn].drvtyp = SEL7; /* Drive type is RK07 */
			hkcmd(dn, DCLR|GO);	   /* clear error bits */
			hkcmd(dn, GO);	   /* get status */
		}
		if((HKADDR->hkcs1 & CERR) == 0)
			hk_r[dn].mntflg = 1;	/* set mnt flag and return */
}

/*
*
*	hkstrategy checks for overflow errors
*
*/
hkstrategy(bp)
register struct buf *bp;
{
	register struct buf *dp;
	register int unit;
	long sz, bn;
	int dn, dt;

	if(bp->b_flags & B_PHYS)
		mapalloc(bp);	/* phys I/O. see about allocating ubmap */

	unit = minor(bp->b_dev) & 077;	/* unit number from buffer header */
	dn = (unit >> 3) & 07;		/* get drive number	*/
	sz = bp->b_bcount;		/* size from buffer header */
	sz = (sz+511)>>9;		/* convert to number of blocks */

	/* if unit requested is greater than number available 
	* or
	* block number is < 0
	* or
	* block number +size is >= number of blocks in file system
	* or
	* mount flag is less than 0. Drive not available or no drive
	*
	* ERROR
	*
	* dkblock is an interleaved file system routine that returns
	* the actual block in the filesystem
	*/
	if(unit >= (NHK<<3)
		|| bp->b_blkno < 0
		|| (bn = dkblock(bp))+sz >= hk_sizes[unit&007].nblocks
		|| hk_r[dn].mntflg < 0)
	{
		u.u_error |= ENXIO;		/* zap the user process */
		bp->b_flags |= B_ERROR;		/* set ERROR flag */
		iodone(bp);	/* call iodone to return buffer to pool */
		return; 	/* bye bye */
	}

/*
*	no error so continue 
*/
	bp->b_cylin = bn/(NSECT*NTRAC) + hk_sizes[unit&07].cyloff;
	unit = dkunit(bp);	/* get the actual drive number
				   could be other than specified
				   with an interleaved file system */
	dp = &hkutab[unit];	/* get a pointer to the drives table */
	spl5();
	disksort(dp, bp);	/* sort the drives queue in ascending
				   positional order and load this
				   buffer on the drives queue	*/

/*	If drive is not active and no I/O pending
*	then start it up.
*/
	if(dp->b_active == 0 && hktab.b_active == 0)
	{
		hkustart(unit); 	/* start a drive seeking if needed */
		hkstart();		/* start I/O	*/
	}
	spl0();
}

hkustart(unit)
register unit;
{
	register struct buf *bp, *dp;
	daddr_t bn;
	int sn, tn, cn, dn;
	long	nblk;

	dn = unit;			/* get drive number	*/
	hk_ios[dn].dk_busy = 0;
	dp = &hkutab[dn];		/* grab pointer to unit table	*/
	if(( bp = dp->b_actf) == NULL)	/* nothing pending here so return */
		return;
	if(hk_r[dn].mntflg < 0)
	{	/* drive unloaded or not mountable */
		bp->b_flags |= B_ERROR; /* set error */
		dp->b_actf = bp->av_forw; /* get next buffer */
		iodone(bp);		/* return buffer */
		return; 		/* bye bye */
	}
	if(hk_r[dn].mntflg == 0)
	{
		hkmnt(dn);		/* make sure we know drive type */
		if(HKADDR->hkcs1 & CERR || (HKADDR->hkds & DRDY)== 0)
		{	/* error from mount. drive not available ? */
			hkerror(bp, dn, 0);	  /* count = 1 to save error */
			bp->b_flags |= B_ERROR; /* set error */
			hkerror(bp, dn, 1);	  /* fatal, log it */
			hkcmd(dn, IEI);	   /* clear controller error */
			hk_r[dn].mntflg = -1;	/* say bad news */
			hk_r[dn].ccyl = 0;	/* reset these */
			dp->b_actf = bp->av_forw; /* get next buffer */
			iodone(bp);		/* return buffer */
			return; 		/* bye bye */
		}
		hkcmd(dn, (PAKACK|GO));	       /* set volume valid */
		hk_r[dn].ccyl = 0;		/* set current cyl = 0 */
	}
	if(dp->b_active & HKSEEK || hktab.b_actf == NULL)
		goto done;
	bn = dkblock(bp);		/*get block number     */
	cn = bp->b_cylin;		/* get cylinder number	*/
	sn = bn%(NSECT*NTRAC);		/* calculate the */
	tn = sn/NSECT;			/* track number */
	sn = sn%NSECT;			/* get the sector number */

	if(hk_r[dn].ccyl != cn && hktab.b_actf != NULL)
	{		/* not on cylinder so seek */

		dp->b_active = HKSEEK|HKBUSY;	/* say drive is seeking */
		HKADDR->hkdc = cn;		/* desired cylinder */
		HKADDR->hkda = (tn <<8)| sn;	/* so no illegal addr */
		HKADDR->hkcs2 = dn;		/* select drive */
		hk_ios[dn].dk_busy++;		/* drive is active (seeking) */
		HKADDR->hkcs1 = (hk_r[dn].drvtyp|IEI|SEEK|GO);	/* do it */
		return;
	}
done:
	dp->b_active = HKBUSY;		/* say drive is active	*/
	dp->b_forw = NULL;		/* clear this forward link */
	if(hktab.b_actf == NULL)	/* if I/O table is 0 then */
		hktab.b_actf = dp;    /* load to I/O table (first in queue)*/
	else
		hktab.b_actl->b_forw = dp;	/* link to end of queue */
	hktab.b_actl = dp;			/* new end of queue */
}

hkstart()
{
	register struct buf *bp, *dp;
	register int unit;
	int com,cn,tn,sn,dn;
	daddr_t bn;

loop:
	if ((dp = hktab.b_actf) == NULL)	/* get a Unit table ptr */
	{
		HKADDR->hkcs1 = IEI;		/* nothing queued so make */
		return; 			/* sure IEI is set and bye */
	}
	if(dp->b_active & HKSEEK)
	{
		HKADDR->hkcs1 = IEI;
		return; 			/* drive is seeking. later */
	}
	if ((bp = dp->b_actf) == NULL)
	{					/* no buffer for this drive */
		hktab.b_actf = dp->b_forw;	/* get next drive from queue*/
		goto loop;			/* look some more */
	}
	hktab.b_active |= HKBUSY;	/* say that we're active */
	unit = minor(bp->b_dev)&077;	/* get the unit and file sys number */
	dn = dkunit(bp);		/* strip file sys #, get drive # */
	bn = dkblock(bp);		/* get the block number */
	cn =(bn/(NSECT*NTRAC))+hk_sizes[unit&07].cyloff;/* calc cylinder # */
	sn = bn%(NSECT*NTRAC);
	tn = sn/NSECT;			/* calculate track number */
	sn = sn%NSECT;			/* get the sector number */

#if HKEOFF
		/* try offset recovery. Offset only good on read */
	if((hktab.b_active & HKOFFS) == 0
		&& hktab.b_errcnt >= 16
		&& bp->b_flags & B_READ)
	{
		hktab.b_active = HKOFFS;
		hkcmd(dn, GO);
		HKADDR->hkas.w = hk_offset[hktab.b_errcnt &017];
		HKADDR->hkcs2 = dn;
		HKADDR->hkcs1 = (hk_r[dn].drvtyp|IEI|OFFSET|GO);
		return;
	}
	hktab.b_active &= ~HKOFFS;	/* clear offset flag */
#endif
	HKADDR->hkdc = cn;		/* load the desired cylinder */
	HKADDR->hkda = (tn<<8)|sn;	/* and the track , sector */
	HKADDR->hkba = bp->b_un.b_addr; /* get memory address */
	HKADDR->hkwc = -(bp->b_bcount>>1);/* comp and load the word count */
	com = ((bp->b_xmem&3)<<8)|IEI|GO; /* ext mem, IEI, and go bits */
	if(bp->b_flags & B_READ)
		com |= RCOM;		/* Read function */
	else
		com |= WCOM;		/* write function */
	com |= hk_r[dn].drvtyp; 	/* set drive type */
	HKADDR->hkcs2 = dn;		/* select drive number */
	HKADDR->hkcs1 = com;		/* and load it all into hkcs1 */
	hk_ios[dn].dk_busy++;		/* Instrumentation - disk active, */
	hk_ios[dn].dk_numb++;		/* count number of transfers, */
	unit = bp->b_bcount >> 6;	/* transfer size in 32 word chunks */
	hk_ios[dn].dk_wds += unit;	/* count total words transferred */
}

hkintr()
{
	register struct buf *bp, *dp;
	register int unit, ctr, dn;
	int seg, uba;
	int tpuis[2], tpbad[2], tppos, tpofst, bsp, pri;

	if((HKADDR->hkcs1 & CRDY) == 0) /* controller not ready. I/O active */
		return; 		/* wait for next interrupt */

	if(hktab.b_active)
	{
		dp = hktab.b_actf;	/* Doing I/O. get drive pointer */
		bp = dp->b_actf;	/* get buffer pointer */
		dn = dkunit(bp);	/* get drive number */
		if(hk_r[dn].recal || hktab.b_active & HKOFFS)
		{	/* recal in progress or offset active. see if done */
			hkcmd(dn, GO);   /* select the drive */
			if((HKADDR->hkds & DRDY)==0)
			{
				HKADDR->hkcs1 = IEI;
				return; 	/* recal still in prog */
			}
			hk_r[dn].recal = 0;	/* reset flag and */
			hktab.b_active &= ~HKBUSY;  /* retry I/O function */
			hkcmd(dn, GO);   /* select the drive */
		}
		if(HKADDR->hkcs1 & CERR)
		{
			if(hktab.b_errcnt == 0)
				hkerror(bp, dn, hktab.b_errcnt);

			/* if exceed retry count or Non-existent mem or */
			/* drive write lock error or cylinder overflow	*/
			/* or format error or bad sector error */
			/* or error during offset */
			/* then FATAL error */

			if(hktab.b_errcnt++ > NUMTRY
			   || HKADDR->hkcs2 & NEM
			   || HKADDR->hkerr & (DWLE|COE|FMTE|BSE)
			   || hktab.b_active & HKOFFS)
			{
				hktab.b_active |= HKBUSY; /* make sure set */
				bp->b_flags |= B_ERROR; /* Fatal error */
			}
			else if(HKADDR->hkds & DROT
				|| HKADDR->hkerr & (OPI|SKI))
			{				/* needs a recal   */
				hk_r[dn].recal++;	/* set recal flag */
				hkcmd(dn, (DCLR|GO));   /* clr drive */
				hkcmd(dn, (IEI|RECAL|GO));  /* issue recal */
				return; 		/* later */
			}
	

			else if((HKADDR->hkerr & (DCK|ECH))== DCK)
			{
				/* ECC error . recoverable */
			    pri = spl7();	  /* need lots of priority */
			    hkerror(bp, dn, 0);  /* make sure right registers */
			    tpuis[0] = UISA->r[0];  /* save User I PAR */
			    tpuis[1] = UISD->r[0];  /* save User I PDR */
			    uba = (HKADDR->hkcs1&01400)<<2; /* exmem */
			    uba += (((HKADDR->hkba>>1)&~0100000)>>5);
			    uba -= 8;
			    if(bp->b_flags&B_MAP)
			    {
				ctr = (uba >> 6) & 076;
				seg = UBMAP->r[ctr+1] << 10;
				seg |= (UBMAP->r[ctr] >> 6) & 01777;
				UISA->r[0] = seg + (uba & 0177);
			    } else
				UISA->r[0] = uba;
			    UISD->r[0] = 077406;	  /* Set User I PDR */
			    tppos = (HKADDR->hkec1-1)/16;   /* word addr */
			    tpofst = (HKADDR->hkec1-1)%16;  /* bit in word */
			    bsp = HKADDR->hkba & 077;	    /* 64 byte range*/
			    bsp += (tppos*2);
			    tpbad[0] = fuiword(bsp);	/* first bad word */
			    tpbad[1] = fuiword(bsp+2);	/* second bad word */
			    tpbad[0] ^= (HKADDR->hkec2<<tpofst); /* xor first word */

			    if(tpofst > 5)
			    {			/* must fix second word */
				    ctr = (HKADDR->hkec2 >> 1) & ~0100000;
				    if(tpofst < 15)
					    ctr = ctr >> (15 - tpofst);
				    tpbad[1] =^ ctr;	/* xor second word */
			    }
			    suiword(bsp,tpbad[0]);	/* set first word */
			    suiword((bsp+2),tpbad[1]);	/* set second word */
			    UISD->r[0] = tpuis[1];  /* restore User PDR */
			    UISA->r[0] = tpuis[0];  /* restore User PAR */
			    splx(pri);			/* back to old pri */
			    printf("%D ",bp->b_blkno+(HKADDR->hkwc -(-(bp->b_bcount>>1)))/256);
    			    prdev("ECC", bp->b_dev);
			    hktab.b_errcnt = 0;

			    /* now see if transfer finished. */
			    /* If not then reload xfer making sure */
			    /* the CCLR bit does not get set too */

			    if(HKADDR->hkwc != 0)
			    {
					HKADDR->hkcs1 = CCLR;
					while((HKADDR->hkcs1 & CRDY) == 0);
					HKADDR->hkwc = hk_r[dn].wc;
					HKADDR->hkba = hk_r[dn].ba;
					HKADDR->hkda = hk_r[dn].da;
					HKADDR->hkdc = hk_r[dn].dc;
					HKADDR->hkcs2 = dn;
				    HKADDR->hkcs1=(hk_r[dn].cs1&03576)|IEI|GO;
				    return;
			    }
			}
	
			else if(HKADDR->hkerr & ECH)
			{				/* soft error. retry */
				hktab.b_active &= ~HKBUSY;  /* retry I/O function */
				hkcmd(dn , (DCLR, GO)); /* clear drive's attn */
			}

			else if(HKADDR->hkcs2 & (UPE|DLT)
				|| HKADDR->hkerr & (HRVC|DTE)
				|| hkcomer(dn, bp))
			{				/* soft error. retry */
				hkcmd(dn , (DCLR, GO)); /* clear drive's attn */
				hktab.b_active &= ~HKBUSY;  /* retry I/O function */
			}

			else
			{
				/* must be fatal error */
				hktab.b_active |= HKBUSY;
				bp->b_flags |= B_ERROR;
			}
		}
		if(hktab.b_active & HKBUSY) /* need to return a buffer */
		{
			if(hktab.b_errcnt || bp->b_flags & B_ERROR)
				hkerror(bp, dn, hktab.b_errcnt);
			hktab.b_active = 0;		/* no I/O active */
			dp->b_active = 0;		/* no drive active */
			hktab.b_errcnt = 0;		/* clr I/O error count */
			dp->b_errcnt = 0;		/* clr drive error count */
			hktab.b_actf = dp->b_forw;	/* get next drive link */
			dp->b_actf = bp->av_forw;	/* get next I/O link */
			if(bp->b_flags & B_ERROR)
			    bp->b_resid = bp->b_bcount; /* nothing xfered */
			else
				bp->b_resid = 0;	/* xfer complete */
			iodone(bp);			/* return buffer */
#if HKEOFF
			if(hktab.b_errcnt >16 && bp->b_flags & B_READ)
			{
				hkcmd(dn, (DCLR|GO));	/* make sure attn is off */
				hkcmd(dn, OFFSET|GO);	/* return to zero */
			}
#endif
			hk_ios[dn].dk_busy = 0;	/* drive no longer active */
		}
		hkcmd(dn, (DCLR|GO));	/* make sure attn is off */
#if HKEOFF
		if(hktab.b_active & HKOFFS)
		{
			hkstart();
			return;
		}
#endif
	}
	hkattn();	/* go check attention lines */
}			/* and start any pending I/O */
	
hkattn()
{
	register struct buf *bp, *dp;
	register int dn, as;

	as = HKADDR->hkas.c[1]; 	/* get attn summary reg */
	for( dn = 0; dn < NHK; dn++)
	{
		if((as & (1 << dn)) == 0)
			continue;	/* find attn bit(s) */
		if(hk_r[dn].mntflg <= 0)
		{
			hkmnt(dn);
			if(hk_r[dn].mntflg > 0)
				hk_r[dn].mntflg = 0;
			hkcmd(dn, DCLR|GO);
			continue;
		}
		dp = &hkutab[dn];	/* get utable pointer */
		bp = dp->b_actf;	/* get active buffer pointer */
		hkcmd(dn , GO);	   /* select drive */
		while(HKADDR->hkds & PIP)
			hkcmd(dn, GO);
		if(HKADDR->hkcs1 & CERR)
		{
			if(dp->b_errcnt == 0)
				hkerror(bp, dn, dp->b_errcnt);

			if(dp->b_errcnt++ > NUMTRY
			     || (hkcomer(dn, bp)) == 0)
			{
				hkerror(bp, dn, dp->b_errcnt);
				if(dp->b_active){
					dp->b_actf = bp->av_forw;/* next link*/
					bp->b_flags |= B_ERROR; /* say bad */
					dp->b_errcnt = 0;/* not recoverable */
					iodone(bp);	      /* return buf */
				}
			}
			dp->b_active = 0;	/* drive not active */
			hk_r[dn].ccyl = 0;
		}

		if((HKADDR->hkds & DRDY) == 0)
		{	/* drive dropped off line */
			hk_r[dn].mntflg = -1;	/* not mounted */
			if(dp->b_active)
			{	/* something active */
				dp->b_active = 0;	/* say no more */
				bp->b_flags |= B_ERROR; /* error */
				dp->b_actf = bp->av_forw; /* get next buffer */
				iodone(bp);		/* return error */
			}
		}
		dp->b_active &= ~HKBUSY;	   /* make sure clear */
		hkcmd(dn, (DCLR|GO));	   /* clear drive attn */
	}
	for(dn = 0; dn < NHK; dn++)
	{
		dp = &hkutab[dn];	/* get utable pointer */
		if((dp->b_active & HKBUSY) == 0 && dp->b_actf)
			hkustart(dn);	/* if drive not busy and request
					   pending then start the drive */
	}
	if((hktab.b_active & HKBUSY) == 0)
		hkstart();		/* If no I/O active then start it */
}

hkcmd(dn, comm)
{
	int pri;


	while((HKADDR->hkcs1 & CRDY) == 0);	/* wait for ready */
	pri = spl5();				/* no interrupts */
	HKADDR->hkcs1 |= CCLR;			/* clear controller */
	while((HKADDR->hkcs1 & CRDY) == 0);	/* wait for contr ready */
	HKADDR->hkcs2 = dn;			/* load drive # */
	HKADDR->hkcs1 = (hk_r[dn].drvtyp|comm); /* load command */
	if((comm & IEI) == 0)
		while((HKADDR->hkcs1 & CRDY) == 0); /* need to wait here */
	splx(pri);
}

hkcomer(dn, bp)
struct buf *bp;
int dn;
{
	/** some errors are common to seek as well as i/o **/
	/** this routine checks for them.		  **/

	register struct buf *dp;
	register int soft, cnt;

	/* Check for Controller Type Errors first */

	if(HKADDR->hkcs1 & (CTO|DTCPAR) 	/* Controller timeout */
		|| HKADDR->hkcs2 & (UFE|PGE)	/* Drive to Cont. Parity */
		|| HKADDR->hkerr & CDPAR)	/* Cont. to Drive Parity */
						/* Unit Field Error */
						/* Programming Error */
		return(1);			/* are all soft errors */

	else if(HKADDR->hkcs2 & NED)		/* Non-Exist Drive */
		return(0);			/* return non-recoverable */

	else if(HKADDR->hkcs2 & MDS)		/* Multiple Drive Select */
	{					/* Have to clear the entire */
		HKADDR->hkcs2 = SCLR;		/* disk system to recover */
		while((HKADDR->hkcs1 & CRDY) == 0)
			;			/* wait for controller rdy */
		hktab.b_active = 0;		/* no I/O active */
		for(cnt = 0; cnt < NHK; cnt++)
		{
			dp = &hkutab[cnt];	/* no drive active */
			dp->b_active = 0;
		}
		return(0);			/* return Hard Error */
	}

	/* Now we can check for drive errors */

	else
		hkcmd(dn, GO);		   /* select drive */

	if(HKADDR->hkds & (ACLO|SPLS)		/* ACLO or Speed Loss */
	  || HKADDR->hkerr & (ILF|NXF|IDAE|DUNS)) /* Illegal Function */
						/* Nonexecutable Function */
						/* Invalid Disk Address */
		return(0);			/* Drive Unsafe are fatal */
	else
		return(1);		/* anything else is soft error?? */
}

hkerror(bp, dn, count)
struct buf *bp;
int dn, count;
{

	if(count == 0)
	{	/* first time through */
		/* save some registers */
		hk_r[dn].cs1 = HKADDR->hkcs1;
		hk_r[dn].wc = HKADDR->hkwc;
		hk_r[dn].ba = HKADDR->hkba;
		hk_r[dn].da = HKADDR->hkda;
		hk_r[dn].cs2 = HKADDR->hkcs2;
		hk_r[dn].ds = HKADDR->hkds;
		hk_r[dn].err = HKADDR->hkerr;
		hk_r[dn].as = HKADDR->hkas.w;
		hk_r[dn].dc = HKADDR->hkdc;
		hk_r[dn].mr1 = HKADDR->hkmr1;
		hk_r[dn].mr2 = HKADDR->hkmr2;
		hk_r[dn].mr3 = HKADDR->hkmr3;
	}
	else
#ifndef REGDUMP
		deverror(bp, hk_r[dn].cs2, hk_r[dn].err);
#else
	{
		if(bp->b_flags & B_ERROR || count >= NUMTRY)
			printf("\nfatal ");
		else
			printf("\nrecoverable ");
		if(bp)
			prdev("error", bp->b_dev);
		printf("Error Count = %d\n", count);
		printf("cs1 = %o\t   wc = %o\t	 ba = %o\n",hk_r[dn].cs1,hk_r[dn].wc,hk_r[dn].ba);
		printf(" da = %o\t  cs2 = %o\t	 ds = %o\n",hk_r[dn].da,hk_r[dn].cs2,hk_r[dn].ds);
		printf("err = %o\t  off = %o\t	 dc = %o\n",hk_r[dn].err,hk_r[dn].as,hk_r[dn].dc);
		printf("mr1 = %o\t  mr2 = %o\t	mr3 = %o\n",hk_r[dn].mr1,hk_r[dn].mr2,hk_r[dn].mr3);
	}
#endif
}

hkread(dev)
dev_t dev;
{
	physio(hkstrategy, &rhkbuf, dev, B_READ);
}

hkwrite(dev)
dev_t dev;
{
	physio(hkstrategy, &rhkbuf, dev, B_WRITE);
}