/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION  1986,1987,1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
/* $Header:hd.c 12.0$ */
/* $ACIS:hd.c 12.0$ */
/* $Source: /ibm/acis/usr/sys/caio/RCS/hd.c,v $ */

#if !defined(lint) && !defined(NO_RCS_HDRS)
static char *rcsid = "$Header:hd.c 12.0$";
#endif

/* $Header:hd.c 12.0$ */
/* $ACIS:hd.c 12.0$ */
/*
 ** Hard Disk device driver for RT/PC.
 ** Supports ST506, ESDI, & EESDI adapters.
 **
 ** TODOs(?)
 ** 	- Use EESDI adapter sector buffers as a cache.  This can't be
 ** 	worth the effort for the adapters with 16k buffers, but could
 **	be justified by the 64k cards(?)
 **	- Comment code!  (More!)
 **
 ** A comment of all asterisks, like the following, precedes all entry points.
 */
/**********************************************************************/

#include	"hd.h"
#if NHD > 0
#include	"param.h"
#include	"systm.h"
#include	"buf.h"
#include	"vm.h"
#include	"time.h"
#include	"types.h"
#include	"proc.h"
#include	"errno.h"
#include	"kernel.h"
#include	"trace.h"
#include	"ioctl.h"
#include	"uio.h"
#include	"dk.h"
#include	"../machine/pte.h"
#include	"../machine/io.h"
#include 	"../machineio/hdreg.h"
#include	"../machineio/ioccvar.h"
#include	"../machineio/hdconfig.h"
#include	"../machine/dkio.h"
#include	"../machine/post.h"
#include	"../machineio/dmavar.h"
#include	"../machine/mmu.h"
#include	"../machine/debug.h"

#define	HDTIMEOUT	10		/* seconds til interrupt known lost */
#define	HDHANGMAX	1		/* #times to retry xfer that seems to
					 * make us lose an interrupt */
#define	MAX_WAIT_COUNT	1500000	  	/* about 20 seconds worth */

/* Disk block definitions */

#define	BSIZE	DEV_BSIZE
#define	BSHIFT	DEV_BSHIFT

/* minor device uses following bits
 * bits	0-2	partition number
 * bit	3-5	physical unit
 * bit	6	suppress retries 
 */
#define	unit(d)		((minor(d) >> 3) & 0x07)
#define	part(p)		(minor(p) & 0x07)
#define	NORETRY(d)	((minor(d) >> 6) & 0x01)

#define	NBPS(st)	512	/* or st->nbps	*/
#define	MAXNBPS		512	/* largest st->nbps supported */

#define	b_cylin	b_resid		/* also called b_errcnt (barf) */

/* Rename device registers (see struct hddevice in hdreg.h) */
#define	DATA		data
#define ERR		err
#define PCMP		err		/* (W) precompensation */
#define DIAGSTAT	err		/* (R) diagnose status */
#define SCT		sct
#define	SNO		sno
#define	CONFIGBITS	sno		/* (R) configuration info */
#define	VID		sno		/* (R) vendor id */
#define	CYLL		cyll
#define	ECLEVEL		cyll		/* (R) engineering change level */
#define CYLH		cylh
#define	UCODELEVEL	cylh		/* (R) adapter microcode level */
#define	SDH		sdh
#define	STAT		stat
#define	CMD		stat		/* (W) command */
#define INTR		intr
#define HFR		intr		/* (W) hard file register */
#define DMAERRA		dma_err_a
#define	DMACMDS		dma_err_a	/* (W) dma command argument stack */
#define	MODE		mode
#define	DMAERRB		dma_err_b
#define	DMASTAT		dma_stat
#define	DMACMD		dma_stat	/* (W) dma command */


caddr_t hdstd[] = {	/* standard adapter addresses (memory mapped IO) */
	(caddr_t)0xf00001f0,	/* primary adapter */
	(caddr_t)0xf0000170,	/* secondary adapter */
	0			/* 0 = end-of-list */
};

int hdprobe(), hdslave(), hdattach(), hdint();
int hdwstart = 0, hdwatch();		  /* watch routine */
int hddgo(), hdchanrelse(), hdminphys(), hdstrategy();

/* hddinfo contains pointers to the slaves (units) */
struct iocc_device *hddinfo[NHD];

struct iocc_ctlr *hdminfo[NHDC];


struct iocc_driver hdcdriver = {
	hdprobe, hdslave, hdattach, hddgo,
/*	addr	dname	dinfo		mname	minfo		intr	csr */
	hdstd,	"hd",	hddinfo,	"hdc",	hdminfo,	hdint,	2,
	hdchanrelse,	EARLY_INT
};

#define spl_disk()	_spl4()
#define spl_high()	_spl1()

/* Partition table */

#define	NHDST	10		/* support 9 (1 dummy) drives (for now) */
#define	NPART	8		/* number of partitions */
#define	MAXNSPT	36		/* largest # sectors/track - on any disk */
struct partab hdinit_sizes[NPART] = { /* dummy disk for non-recognized types */
	0, 0,			/* spare */
	0, 0,			/* spare */
	MAXNSPT*2, 0,		/* c - at least first two tracks */
	0, 0,			/* spare */
	0, 0,			/* spare */
	0, 0,			/* spare */
	0, 0,			/* spare */
	0, 0,			/* spare */
}, hd40m_sizes[NPART] = {	/* leaving room for reserved areas (AUSTIN) */
	15884,	1,		/* A=cyl 1 thru 187 */
	10032,	188,		/* B=cyl 188 thru 306 */
	87040,	0,		/* C=cyl 0 thru 1023 */
	15884,	307,		/* D=cyl 307 thru 493 */
	0,	0,
	43945,	494,		/* F=cyl 494 thru 1010 */
	59840,	307,		/* G=cyl 307 thru 1010 */
	0,	0,
}, hd70m_sizes[NPART] = {	/* leaving room for reserved areas (AUSTIN) */
	15884, 1,		/* A=cyl 1 thru 117 */
	33440, 118,		/* B=cyl 118 thru 363 */
	139264, 0,		/* C=cyl 0 thru 1023 */
	15884, 364,		/* D=cyl 364 thru 480 */
	55936, 481,		/* E=cyl 481 thru 892 */
	16592, 893,		/* F=cyl 893 thru 1014 */
	88536, 364,		/* G=cyl 364 thru 1014 */
	0, 0,
}, hd70c_sizes[NPART] = {
	15884,	1,		/* A=cyl 1 thru 104 */
	33440,	105,		/* B=cyl 105 thru 323 */
	141525,	0,		/* C=cyl 0 thru 924 */
	15884,	324,		/* D=cyl 324 thru 427 */
	55936,	428,		/* E=cyl 428 thru 793 */
	18819,	794,		/* F=cyl 794 thru 916 */
	90729,	324,		/* G=cyl 324 thru 916 */
	0,	0,
}, hd40r_sizes[NPART] = {
	15884,	1,		/* A=cyl 1 thru 134 */
	10032,	135,		/* B=cyl 135 thru 219 */
	87227,	0,		/* C=cyl 0 thru 732 */
	15884,	220,		/* D=cyl 220 thru 353 */
	0,	0,
	43911,	354,		/* F=cyl 354 thru 722 */
	59857,	220,		/* G=cyl 220 thru 722 */
	0,	0,
}, hd70r_sizes[NPART] = {
	15884,	1,		/* A=cyl 1 thru 64 */
	33440,	65,		/* B=cyl 65 thru 197 */
	142632,	0,		/* C=cyl 0 thru 565 */
	15884,	198,		/* D=cyl 198 thru 261 */
	55936,	262,		/* E=cyl 262 thru 483 */
	19404,	484,		/* F=cyl 484 thru 560 */
	91476,	198,		/* G=cyl 198 thru 560 */
	0,	0,
}, hd70e_sizes[NPART] = {
	15884,	1,		/* A=cyl 1 thru 65 */
	33440,	66,		/* B=cyl 66 thru 202 */
	142835,	0,		/* C=cyl 0 thru 582 */
	15884,	203,		/* D=cyl 203 thru 267 */
	55936,	268,		/* E=cyl 268 thru 496 */
	19600,	497,		/* F=cyl 497 thru 576 */
	91630,	203,		/* G=cyl 203 thru 576 */
	0,	0,
}, hd114e_sizes[NPART] = {
	15884,	1,		/* A=cyl 1 thru 65 */
	33440,	66,		/* B=cyl 66 thru 202 */
	224175,	0,		/* C=cyl 0 thru 914 */
	15884,	203,		/* D=cyl 203 thru 267 */
	55936,	268,		/* E=cyl 268 thru 496 */
	100940,	497,		/* F=cyl 497 thru 908 */
	172970,	203,		/* G=cyl 203 thru 908 */
	0,	0,
}, hd310e_sizes[NPART] = {
	15884,	1,		/* A=cyl 1 thru 33 */
	33440,	34,		/* B=cyl 34 thru 101 */
	606375,	0,		/* C=cyl 0 thru 1224 */
	15884,	691,		/* D=cyl 691 thru 723 */
	55936,	724,		/* E=cyl 724 thru 837 */
	189585,	838,		/* F=cyl 838 thru 1220 */
	262350,	691,		/* G=cyl 691 thru 1220 */
	291346,	102,		/* H=cyl 102 thru 690 */
}, hd310h_sizes[NPART] = {
	15884,	1,		/* A=cyl 1 thru 32 */
	33440,	33,		/* B=cyl 33 thru 98 */
	606390,	0,		/* C=cyl 0 thru 1188 */
	15884,	671,		/* D=cyl 671 thru 702 */
	55936,	703,		/* E=cyl 703 thru 812 */
	190230,	813,		/* F=cyl 813 thru 1185 */
	262650,	671,		/* G=cyl 671 thru 1185 */
	291346,	99,		/* H=cyl 99 thru 670 */
};

struct partab hdoff[NHD][NPART];	/* actual partition table */

struct hdst {

	struct partab *off;		/* 0: partition table */
	u_short ncpd;			/* 4: number of cylinders / drive */
	u_short pcmp;			/* 6: write precompensation cylinder */
	u_short nbps;			/* 8: number of bytes / sector */
	u_char nspt;			/* 10: number of sectors / track */
	u_char ntpc;			/* 11: number of tracks / cylinder */
	u_short nspc;			/* 12: number of sectors / cylinder */
	u_short step;			/* 14: drive stepping rate */
	/* 16 total */

} hdst[NHDST+NHD] = {
/*
 * hdinit_sizes is a dummy record that will work with all disks enough
 * to read the configuration record. It must be first in the following table.
 *
 *	 off		ncpd	pcmp		nbps	nspt	ntpc	nspc
 */

	{hdinit_sizes,	1,	NOPCMP<<2,	512,	17,	2,	17*2},
	{hd70m_sizes,	1024,	NOPCMP<<2,	512,	17,	8,	17*8},
	{hd40m_sizes,	1024,	NOPCMP<<2,	512,	17,	5,	17*5},
	{hd70c_sizes,	925,	NOPCMP<<2,	512,	17,	9,	17*9},
	{hd40r_sizes,	733,	300,		512,	17,	7,	17*7},
	{hd70r_sizes,	566,	300,		512,	36,	7,	36*7},
	{hd70e_sizes,	583,	NOPCMP<<2,	512,	35,	7,	35*7},
	{hd114e_sizes,	915,	NOPCMP<<2,	512,	35,	7,	35*7},
	{hd310e_sizes,	1225,	NOPCMP<<2,	512,	33,	15,	33*15},
	{hd310h_sizes,	1189,	NOPCMP<<2,	512,	34,	15,	34*15},

};

char hdtypes[NHDST+NHD][8] = {	/* names of the various disk types */
	"hdinit",				/* dummy */
	"hd70m", "hd40m", "hd70c",		/* (obsolete) vendor disks */
	"hd40r", "hd70r", "hd70e", "hd114e", "hd310e",	/* IBM disks */
	"hd310h"				/* vendor disk */
};



struct buf hdutab[NHD];			  /* start of request queue */
struct buf rhdbuf[NHD];			  /* header for raw i/o */

int disable;				/* 1 iff we have 2 old adapters */
/* these two pointers are used only when disable == 1 */
struct iocc_ctlr *hdactive = 0;		/* the currently active controller */
struct iocc_ctlr *hdwaiting;		/* the currently waiting controller */

/*
 * Software structure per (started & incomplete) IO operation.
 * Reached via bp->b_actl only.  This structure may be thought
 * of as an extension to the bp (passed into hdstrategy).
 * Note that hdi_softc[0] is used to head the free list.
 * Also note that field names are (intentionally) historical.
 */
#ifndef NHDXBUF
#define	NHDXBUF	48	/* max concurrent IOs - VERY arbitrary */
#endif	/* NHDXBUF */
struct hdi_softc {
	struct hdi_softc	*hd_forw;	/* free list forward pointer */
	struct hdi_softc	*hd_back;	/* free list backward pointer */
	int			hd_flag;	/* flag bits */
#define HD_FORWARD	0x01			/* (non-DMA) bad blk forward */
#define HD_ACTIVE	0x02			/* adapter transferring data */
#define	HD_PHYS		0x04			/* (DMA partial) saved B_PHYS */
	long			hd_bcount;	/* bp->b_bcount */
	caddr_t			hd_addr;	/* bp->b_addr */
	daddr_t			hd_blkno;	/* bp->b_blkno */
	long			hd_cylin;	/* bp->b_cylin */
	short			hd_errcnt;	/* for error retries */
	short			hd_hangcnt;	/* for watch retries */
	caddr_t			hd_physaddr;	/* (non-DMA) real address */
#define NO_ADDR		0			/* means no real address yet */
	int			hd_adapbuf;	/* (DMA) hdc_bufmap index */
	u_int			hd_ioaddr;	/* (DMA) TCW#/page_offset */
} hdi_softc[1+NHDXBUF];	/* array of free and inuse buffer extensions */
struct hdi_softc	*hdxbget();
#define	ACTIVE(bp)	((bp)->b_actl && \
			((struct hdi_softc *)(bp)->b_actl)->hd_flag & HD_ACTIVE)

#define HESDI_CHANNELS	2		/* only 2 possible dma channels */
#define	MAX_CHANNELS	HESDI_CHANNELS
#define HESDI_QDEPTH	2		/* adapter command queue depth */
#define	MAX_QDEPTH	HESDI_QDEPTH
#define	QSHIFT		1		/* log2(QDEPTH) */
#define STD_HESDI_BUF	32			/* 16k adapter buffer */
#define MAX_HESDI_BUF	128			/* 64k adapter buffer */
#define	MAX_HD_DRIVES		2		/* drives (slaves) per card */
#define	MAX_HESDI_DRIVES	3
#define	MAX_DRIVES	MAX_HESDI_DRIVES
#define	PACK_DR_Q(DRIVE, QUEUE)	((DRIVE)<<(QSHIFT) | (QUEUE))
/* software structure per adapter */
struct hdc_softc {
	int	hdc_flag;			/* 0: flag bits */
#define	CAN_DMA		0x01			/* will use DMA, not PIO */
#define DMA_MODE	0x02			/* card now in dma mode */
#define	GOT_CHANNEL	0x04			/* we own dma channel */
#define	WAIT_CHANNEL	0x08			/* we crave dma channel */
	int	hdc_slaves;			/* 4: max possible # slaves */
	int	hdc_slavebits;			/* 8: slaves found so far */
	/* shows drive & command queue using each adapter buffer. */
	u_char	hdc_bufmap[MAX_HESDI_BUF];	/* c: */
#define	FREEBUF	((u_char)0xff)
	/* points to I/O request for each drive's command queue */
	struct buf	*hdc_cmdq[MAX_DRIVES*MAX_QDEPTH]; /* 8c: */
	caddr_t	hdc_pbufp[MAX_DRIVES][MAX_QDEPTH]; /* a4: partial blk buf @s */
	u_int	hdc_timer;			/* bc: for hdwatch() */
	/* the following is assumed to be a power of two! */
	u_int	hdc_nbuf;			/* c0: # sector buf on card */
	short	hdc_channels[MAX_CHANNELS+1];	/* c4: usable dma channels */
	u_char	hdc_cfg;			/* ca: adapter configuration */
	u_char	hdc_ulevel;			/* cb: card microcode level */
} hdc_softc[NHDC];

/*
 * EESDI DMA transfers must be entire sectors.  We use hdd_pbuf as
 * intermediate buffers to transfer less than 512 bytes.
 */
u_char	hdd_pbuf[(NHDC*MAX_DRIVES*MAX_QDEPTH + 1)*MAXNBPS - 1];
#define	PARTIAL(BP)    ((BP)->b_un.b_addr >= (caddr_t)hdd_pbuf &&	\
			(BP)->b_un.b_addr < (caddr_t)hdd_pbuf + sizeof hdd_pbuf)


#ifdef DEBUG

#ifndef HDNODMA
#define HDNODMA	0
#endif	/* HDNODMA */

#ifndef	NOHDASSERTIONS
#define	HDASSERTIONS
#endif	/* !NOHDASSERTIONS */

int hd_nodma = HDNODMA;	/* patch *before* 1st hdprobe */

int hd_old_ucode = 0;	/* For test purposes only!  Data integrity dubious! */

#include "../machineio/dmareg.h"

#define HDDEBUG(how,stmt) if (hddebug & (how)) stmt	/* print it etc. */

#else	/* !DEBUG */

#define HDDEBUG(how,stmt) 	/* do nothing */

#endif	/* DEBUG */


/* enable hardware bug circumventions */
int hesdi_w_bug = 1;	/* probably never to be fixed */
int hesdi_d_bug = 1;	/* unfixable card design flaw */
int hesdi_r_bug = 1;	/* broke up through level 0x46 */
int hesdi_s_bug = 1;	/* should be fixed in ucode 0x48 */
int hesdi_q_bug = 1;	/* broke after 0x46, fixed in 0x55 */
int esdi_i_bug = 1;	/* ok on HESDI & ST506??? */
#define	MINUCODELEVEL	0x31	/* can set hd_old_ucode to use older levels */
#define SETPUCODELEVEL	0x48	/* new set parms function supported */

struct hdbad *hdbad[NHD];	/* the drives bad sector maps */

#define HDRETRIES	16	/* should be power of 2 */

caddr_t real_buf_addr();
#define REAL_BUF_ADDR(bp, bufaddr) (((bp->b_flags & B_PHYS) == 0) ? \
	((caddr_t)vtop(bufaddr)) : real_buf_addr(bp, bufaddr))

#define	NOISY	1	/* enable error messages */

struct iocc_ctlr	*icprobed = NULL;

/**********************************************************************/

/*
 * Autoconfiguration probe routine.  Main function is to make the adapter
 * interrupt and verify that adapter is working ok.
 */
hdprobe(reg, ic)
	register caddr_t		reg;
	register struct iocc_ctlr	*ic;
{
	register struct hddevice volatile	*hdaddr = (struct hddevice volatile *)reg;
	static int			first = 0;
	register int			i,
					result = PROBE_OK;
	struct post			*p1p,
					*p2p;
	extern struct post		*get_post();

#if	NHDC > 2
	/* We're dependant on there being no more than 2 adapters. */
	panic("hdprobe NHDC");	/* config problem! */
#endif	/* NHDC > 2 */
	/* The two adapters must have controller numbers 0 and 1. */
	if (ic->ic_ctlr > 1)
		panic("hdprobe int_ctlr");	/* config problem(?) */

	icprobed = ic;

	HDDEBUG(SHOW_INIT, printf("hdprobe(%x) called\n", reg));

	if (first == 0) {
		register char	t1 = ADAP_NONE, t2 = ADAP_NONE;
		u_long		p;

		first = 1;

		/* create buffer-extensions freelist */
		hdi_softc[0].hd_forw = hdi_softc[0].hd_back = &hdi_softc[0];
		for (i = 1; i <= NHDXBUF; i++)
			hdxbrel(&hdi_softc[i]);

		p = (u_long)hdd_pbuf+NBPS(st)-1 & ~(NBPS(st)-1);

		if ((p1p = get_post(reg)) != NULL) {
			/* sanity check this adapter's type */
			if ((t1 = p1p->adap_type) != ADAP_HD &&
			    t1 != ADAP_HD_HESDI)
				printf("hdc%d: unknown adapter type 0x%x\n", ic->ic_ctlr, t1);
			hdcinit(ic->ic_ctlr, t1, &p);
		}
		HDDEBUG(SHOW_INIT, printf("hdprobe: post 0x%x type 0x%x\n", p1p, t1));
		/* now find the OTHER adapter's address */
		for (i = 0; reg == hdstd[i]; i++) 
			;
		if ((p2p = get_post(hdstd[i])) != NULL) {
			/* sanity check OTHER adapter's type */
			if ((t2 = p2p->adap_type) != ADAP_HD &&
			    t2 != ADAP_HD_HESDI && t2 != ADAP_NONE)
				printf("hdc%d: unknown adapter type 0x%x\n", 1-ic->ic_ctlr, t1);
			hdcinit(1-ic->ic_ctlr, t2, &p);
		}
		HDDEBUG(SHOW_INIT, printf("hdprobe: post 0x%x type 0x%x\n", p2p, t2));
		/*
		 * disable interrupts on all HESDI adapters
		 */
		if (t1 == ADAP_HD_HESDI) {
			hdaddr->HFR = DISKDISI|DISKIRQ12;
			DELAY(100);
		}
		if (t2 == ADAP_HD_HESDI) {
			((struct hddevice volatile *)hdstd[i])->HFR = DISKDISI|DISKIRQ12;
			DELAY(100);
		}

		disable = t1==ADAP_HD && t2==ADAP_HD;	/* 2 old adapters? */
		/* also disable ints on 2nd old adapter */
		if (disable) {
			((struct hddevice volatile *)hdstd[i])->HFR = DISKDISI|DISKIRQ12;
			DELAY(100);
		}
		/* HESDIs use shared interrupts - reset IRQ12 if any HESDIs */
		if (t1 == ADAP_HD_HESDI || t2 == ADAP_HD_HESDI) {
			*shr_int_reset[12] = 0xff;
			DELAY(100);
		}
	}
	hdaddr->HFR = DISKIRQ12;	/* turn interrupts on */
	DELAY(100);

	hdaddr->CMD = DISKDIAG;
	PROBE_DELAY(2000000);		/* wait a bit for interrupt */
	if (hddiagstat(ic, "")) {
		result = PROBE_BAD;
	} else if (hdc_softc[ic->ic_ctlr].hdc_flag & CAN_DMA) {
		i = hdc_softc[ic->ic_ctlr].hdc_ulevel = hdaddr->UCODELEVEL;
		if (i < MINUCODELEVEL) {
			printf("hdc%d: ADAPTER MICROCODE IS OBSOLETE! (UPGRADE REQUIRED)\n", ic->ic_ctlr);
#ifdef DEBUG
			if (!hd_old_ucode)
#endif	/* DEBUG */
				result = PROBE_BAD;
		}
		printf("hdc%d: card level 0x%x microcode level 0x%x ",
			ic->ic_ctlr, hdaddr->ECLEVEL, i);
		i = hdc_softc[ic->ic_ctlr].hdc_cfg = hdaddr->CONFIGBITS;
		printf("configuration bits 0x%x\n", i);
		hdc_softc[ic->ic_ctlr].hdc_nbuf = i&DISKCFG64K ? MAX_HESDI_BUF
							    : STD_HESDI_BUF;
		hddreladapbuf(&hdc_softc[ic->ic_ctlr], (u_int)0); /* free all! */
	}
	if (result == PROBE_OK) {
		DELAY(10);			  /* allow to settle */
		if (check_error(DISKDIAG, hdaddr, NOISY, !DMA_MODE))
			result = PROBE_BAD;
	}

	DELAY(10);
	if (disable) {
		hdaddr->HFR = DISKDISI|DISKIRQ12;
		DELAY(500000);	/* required or ID-NOT-FOUND results (!) */
	}
	return(result);
}


/*
 * Release the buffer header extension to free list.
 */
hdxbrel(xbp)
	register struct hdi_softc	*xbp;
{
	register int			s;

#ifdef	HDASSERTIONS
	if (xbp < &hdi_softc[1] || xbp > &hdi_softc[NHDXBUF]) {
		printf("HD: HDXBREL: HELP! xbp=0x%x NOT AN XBUF!\n", xbp);
		return;
	}
#endif	/* HDASSERTIONS */
	s = spl_disk();

	hdi_softc[0].hd_forw->hd_back = xbp;
	xbp->hd_forw = hdi_softc[0].hd_forw;
	hdi_softc[0].hd_forw = xbp;
	xbp->hd_back = &hdi_softc[0];

	splx(s);
}


/*
 * Initialize the softc structure for controller "ctrl".
 * We're told to assume that controller is of type "type".
 */
hdcinit(ctlr, type, pspace)
	u_long	*pspace;
{
	register struct hdc_softc	*sp = &hdc_softc[ctlr];
	register u_int			dr,
					q;

	switch (type) {
	case ADAP_HD_HESDI:
		sp->hdc_slaves = MAX_HESDI_DRIVES;
		sp->hdc_flag =
#ifdef DEBUG
			(hd_nodma & 1<<ctlr) ? 0 :
#endif	/* DEBUG */
			CAN_DMA;
		sp->hdc_channels[0] = ctlr==0 ? DMA_CHAN0 : DMA_CHAN3;
		sp->hdc_channels[1] = DMA_CHAN1;	/* alternate channel */
		sp->hdc_channels[2] = DMA_END_CHAN;	/* end-of-list */
		for (dr = 0; dr < MAX_HESDI_DRIVES; dr++) {
			for (q = 0; q < HESDI_QDEPTH; q++) {
				sp->hdc_pbufp[dr][q] = (caddr_t)*pspace;
				*pspace += NBPS(st);
			}
		}
		break;
	case ADAP_NONE:
	default:
		/* fall thru to pretend it is ADAP_HD
		 * (for lack of any better ideas of what to do)
		 */
	case ADAP_HD:
		sp->hdc_slaves = MAX_HD_DRIVES;
		sp->hdc_flag = 0;
		break;
	}
}


/*
 * (Dma only) Releases adapter buffers for the "sp" controller.
 * The interface to this routine might seem odd. Our arguments don't say
 * which buffer to start releasing at and how many to release.  Instead,
 * the argument specifies 1 buffer which should be released and we release
 * it, AND all other buffers allocated to the same transfer as it.
 */
hddreladapbuf(sp, index)
	register struct hdc_softc	*sp;
	register u_int			index;
{
	register u_char			*map = sp->hdc_bufmap,
					cbufs = sp->hdc_nbuf;
	register u_char			cmdq = map[index];

	HDDEBUG(SHOW_DABUF, printf("HDDRELADAPBUF: index=%d cmdq=0x%x released:", index, cmdq));
	for (index = 0; index < cbufs; index++) {
		if (map[index] == cmdq) {
			map[index] = FREEBUF;
			HDDEBUG(SHOW_DABUF, printf(" %d", index));
		}
	}
	HDDEBUG(SHOW_DABUF, printf("\n"));
}


/*
 * Disk error checking routine.  For use when interrupts are off.
 */
check_error(cmd, hdaddr, noisy, dma)
	u_char				cmd;
	register struct hddevice volatile	*hdaddr;
	register int			noisy;
	register int			dma;
{
	int				status,
					error;

	status = dma==DMA_MODE ? hdaddr->DMASTAT : hdaddr->STAT;
	DELAY(10);
	if (dma==DMA_MODE
		? ((DDISKERRCODE|DDISKBUSY) & status) == 0
		: (DISKERR & status) == 0) {
		HDDEBUG(SHOW_INIT, {
			printf("HDINIT: Disk INIT ");
			printf("CMD=0x%x ", cmd);
			hdstatus((struct buf *)0, 0, hdaddr, status, 0, dma);
		});
		return(0);		  /* normal return */
	}
	if (noisy) {
		error = dma==DMA_MODE ? 0 : hdaddr->ERR;
		printf("HDINIT: Disk I/O Error CMD=0x%x ", cmd);
		DELAY(10);
		hdstatus((struct buf *)0, error, hdaddr, status, 0, dma);
	}
	return(1);			  /* error return */
}


/*
 * Generic hard disk routine to print a status message.
 */
hdstatus(bp, error, hdaddr, status, use_softc, dma)
	register struct buf		*bp;
	register int			error;
	register struct hddevice volatile	*hdaddr;
	register int			status;
	register int			use_softc; /* to override bp stuff */
	register int			dma;
{
	if (status == -1)
		status = dma==DMA_MODE ? hdaddr->DMASTAT : hdaddr->STAT;

	printf("hd: ");
	if (bp)
		printf("hd%d%c bn=%d ", unit(bp->b_dev), part(bp->b_dev) + 'a',
			use_softc ? ((struct hdi_softc *)bp->b_actl)->hd_blkno : bp->b_blkno);
	if (dma == DMA_MODE) {
		printf("Status=0x%x<", status>>8);
		switch(status>>8) {
			case DISKBBLKA:	
				printf("BAD-BLOCK-DETECTED-ALTERNATE");
				break;
			case DISKBBLK:	
				printf("BAD-BLOCK-DETECTED");
				break;
			case DISKCRC:	
				printf("DATA-CRC-ERROR");
				break;
			case DISKINVC:	
				printf("INVALID-COMMAND");
				break;
			case DISKWRTP:	
				printf("WRITE-PARITY");
				break;
			case DISKWRTF:	
				printf("WRITE-FAULT");
				break;
			case DISKIDNF:	
				printf("ID-NOT-FOUND");
				break;
			case DISKADAP:	
				printf("ADAPTER-ERROR");
				break;
			case DISKABRT:	
				printf("COMMAND-ABORTED");
				break;
			case DISKTR00:	
				printf("TR000-ERROR");
				break;
			case DISKMAM:	
				printf("MISSING-DATA/ADDRESS-MARK");
				break;
			case DISKNOERR:	
				printf("NO-ERROR");
				break;
			default:
				printf("UNKNOWN!?");
				break;
		}
		printf(">,0x%b ", status&0xff, HDD_STAT_FMT);
		printf("ErrA=0x%x ", error ? error>>16 : hdaddr->DMAERRA);
		printf("ErrB=0x%x ", error ? error&0xffff : hdaddr->DMAERRB);
	} else {
		if (error)
			printf("Err=0x%b ", error, HD_ERR_FMT);
		printf("Status=0x%b ", status, HD_STAT_FMT);
		printf("Cyl=%d ", ((hdaddr->CYLH << 8) | hdaddr->CYLL));
		printf("Hd=%d ", hdaddr->SDH & 0xF);
		printf("Sect=%d ", hdaddr->SNO);
		printf("SCT=%d ", hdaddr->SCT);
	}
	printf("\n");
}

/**********************************************************************/

#define QUES (-1)	/* question mark on config file drive def'n line */
/*
 * Autoconfiguration slave routine.  Sees if drive (slave) is there.
 */
hdslave(iod, hdaddr)
	register struct iocc_device	*iod;
	register struct hddevice volatile	*hdaddr;
{
	register struct hdc_softc	*sp;
	register int	result = 1,	/* default = slave exists */
			wildcarded,	/* "unit ?" in config file*/
			s = spl_disk();	/* inhibit interrupts */
	register int	slave = 0,
			bits;

	HDDEBUG(SHOW_INIT, printf("hdslave called iod=0x%x hdaddr=0x%x",
		iod, hdaddr));
	/* FYI: iod_ctlr == -1 if controller was wildcarded ("hdc ?") */
	sp = &hdc_softc[icprobed->ic_ctlr];
	bits = sp->hdc_slavebits;
	HDDEBUG(SHOW_INIT, printf(" softc=0x%x slaves=0x%x slavebits=0x%x",
		sp, sp->hdc_slaves, bits));

	if (wildcarded = iod->iod_slave == QUES) {	/* drive wildcarded? */
		/* find lowest slave # not taken */
		while (slave < sp->hdc_slaves && (bits & 1)) {
retry:
			slave++;
			bits >>= 1;
		}
		if (result = slave < sp->hdc_slaves) {
			iod->iod_slave = slave;	/* for hdreset */
		}
	}
	HDDEBUG(SHOW_INIT, printf(" iod_slave=0x%x\n", iod->iod_slave));
	if (result) {
		if ((result = hdreset(iod, hdaddr, !NOISY, !DMA_MODE)) != 0) {
			sp->hdc_slavebits |= (1 << iod->iod_slave);
		} else {
			register int un;

			if (wildcarded)
				iod->iod_slave = QUES;	/* restore wildcard */

			/*
			 * Circumvent hardware (microcode) problem.  Symptom
			 * was that miniroot panics on 1st swap on a HESDI 
			 * which has only one drive.  Cause is that the
			 * card won't do a dma write if the preceding operation
			 * was an attempt to RESTORE a non-existant drive.
			 */
			if (sp->hdc_flag & CAN_DMA  &&  hesdi_r_bug) {
				register struct iocc_device	*iod;
				/*
				 * Hide failed restore's (done in hdreset()) by
				 * resetting an already attached drive.  If
				 * there aren't any drives attached yet then
				 * there is no problem because either a) there
				 * will be a drive attached before we do a write
				 * or b) no drives will be attached, opened,
				 * written, etc.
				 */
				for (un = 0; un < NHD; un++)
					if ((iod = hddinfo[un]) != 0 && iod->iod_mi == icprobed) {
#ifdef	HDASSERTIONS
						if (!(sp->hdc_slavebits & (1 << iod->iod_slave)))
							printf("HDSLAVE: IOD ATTACHED BUT NOT IN SLAVEBITS!!!\n"); 
#endif	/* HDASSERTIONS */
						(void)hdreset(iod, hdaddr, NOISY, !DMA_MODE);
						break;
					}
			}
			if (wildcarded)
				goto retry;
		}
	}
	/*
	 * The adapter interrupt is disabled on entry, so we must disable
	 * it again on exit.
	 */
	hdaddr->HFR = DISKDISI|DISKIRQ12;
	DELAY(1000);
	splx(s);			  /* restore original priority */
	HDDEBUG(SHOW_INIT, printf("hdslave result=0x%x slavebits=0x%x\n",
		result, sp->hdc_slavebits));
	return(result);
}


/*
 * Resets a disk.  (Recovers from error states.)
 * Dma mode under construction - not yet used.
 */
hdreset(iod, hdaddr, noisy, dma)
	register struct iocc_device	*iod;
	register struct hddevice volatile	*hdaddr;
	register int			noisy;	/* complain on errors? */
	register int			dma;	/* dma or pio mode? */
{
	register struct hdst		*st = &hdst[iod->iod_type]; 
	register u_char			hd_sdh;
	register int			i;
	register struct hdc_softc	*sp;
	register int			cmd = DISKRSTR;

	sp = &hdc_softc[(iod->iod_mi ? iod->iod_mi : icprobed)->ic_ctlr];

	if (dma == DMA_MODE) {
		if (sp->hdc_ulevel >= SETPUCODELEVEL)
			cmd = DISKRSTP;
	} else
		hd_sdh = 0xA0 | ((iod->iod_slave&0x2)<<1 | iod->iod_slave&0x1)<<4 | st->ntpc-1;

#if DISKBUSY != DDISKBUSY
	panic("hdreset");
#endif

	for (i = 2; i > 0; --i) {
		if (!hdwait(DISKBUSY, 0, hdaddr, dma))
			continue;
		if (dma == DMA_MODE) {
			hdaddr->DMACMDS = st->ntpc-1 << 8 | st->nspt;
			/*
			 * This command will (try to) cause an interrupt:
			 */
			hdaddr->DMACMD = iod->iod_slave << QSHIFT+8 | cmd;
		} else {
			hdaddr->HFR = DISKHD3|DISKIRQ12;
			hdaddr->SDH = hd_sdh;
			hdaddr->PCMP = (st->pcmp >> 2);
			hdaddr->CYLL = 0;
			hdaddr->CYLH = 0;
			hdaddr->SCT = 1;
			hdaddr->SNO = 0;
			hdaddr->CMD = cmd | st->step;
		}
		if (!hdwait(DISKBUSY, 0, hdaddr, dma))
			continue;
		DELAY(1000);
		if (!check_error(cmd, hdaddr, noisy, dma))
			break;			/* did it! */
	}
	if (i == 0)
		return(0);			/* oops, could not init it */

	if (dma == DMA_MODE)
		return(1);

	DELAY(100);				/* insurance */
	hdaddr->SCT = st->nspt;
	hdaddr->HFR = DISKHD3|DISKIRQ12;	/* enables heads 8-15 */
	DELAY(10);				/* insurance */
	hdaddr->SDH = hd_sdh;
	cmd = sp->hdc_cfg & DISKCFGVEN && sp->hdc_ulevel >= SETPUCODELEVEL
	      ? DISKSETL
	      : DISKSETP;
	hdaddr->CMD = cmd;

	if (!hdwait(DISKBUSY|DISKRDY, DISKRDY, hdaddr, !DMA_MODE))
		return(0);		/* drive not ready - assume not there */

	if (cmd == DISKSETL) {
		u_int	cyl, hd, sec;	/* actual disk geometry */
		u_int	v;		/* vendor id */

		cyl = 1 + (hdaddr->CYLH << 8 | hdaddr->CYLL);
		hd = 1 + (hdaddr->SDH & 0xf0);
		sec = hdaddr->SCT;
		v = hdaddr->VID;
		HDDEBUG(SHOW_INIT,
			printf("hdsetp: cyl=0x%x hd=0x%x sec=0x%x v=0x%x\n",
			       cyl, hd, sec, v));
		/*
		 * TODO: Sanity check file info vs our idea of cyl, head, sect.
		 * Complain when? Do anything else with file info?
		 */
	}

	return(!check_error(cmd, hdaddr, NOISY, !DMA_MODE));
}


/*
 * Wait for a change in adapter status.  Returns zero if we timeout.
 */
hdwait(mask, compare, hdaddr, dma)
	u_char				mask,
					compare;
	register struct hddevice volatile	*hdaddr;
	register int			dma;
{
	register			status;
	register u_int			count = MAX_WAIT_COUNT;

	HDDEBUG(SHOW_WAIT, printf("hdwait(0x%x, 0x%x, 0x%x, 0x%x) ", mask, compare, hdaddr, dma));
	while (--count) {
		status = dma==DMA_MODE ? hdaddr->DMASTAT : hdaddr->STAT;
		if ((status & mask) == compare)
			break;
		DELAY(2);
	}
	if (count == 0) {
		HDDEBUG(SHOW_LOST, hddbreak(status, (int)mask, (int)compare, (int)hdaddr));
		printf("HD: adapter @ 0x%x TIMED OUT (0x%x & 0x%x != 0x%x)\n",
			hdaddr, status, mask, compare);
	}
	HDDEBUG(SHOW_WAIT, printf(" complete (0x%x)\n", status));
	return(count);
}

/**********************************************************************/

/*
 * Autoconfiguration attach routine.  Determines what type of drive it is.
 */
hdattach(iod)
	register struct iocc_device	*iod;
{
	register struct hddevice volatile *hdaddr = (struct hddevice volatile *)iod->iod_addr;
	register int			un = iod->iod_unit;
	register struct hdst		*st = &hdst[iod->iod_type];
	register struct buf		*dp;
	register int			s = spl_disk();	/* inhibit interrupts */
	register int			i;
	int				ncyl,
					ntrack,
					nsector;
	struct hdconfig			hdconfig;

	HDDEBUG(SHOW_INIT, printf("hdattach called iod=0x%x hdaddr=0x%x unit=0x%x\n", iod, hdaddr, un));

	/* initialize device header */
	dp = &hdutab[un];
	dp->b_actf = NULL;
	dp->b_actl = NULL;
	dp->b_active = 0;

	bzero((caddr_t) &hdconfig, sizeof hdconfig);
	hdrd(hdaddr, un, CONFIG_BLOCK, sizeof(struct hdconfig),
	     (caddr_t) & hdconfig, &hdst[0]);
	if (hdconfig.conf_magic != CONFIG_MAGIC || !hdconfig.conf_lastsect) {
		printf("hd%d: bad/missing configuration record\n",un);
	} else {
		ncyl = hdconfig.conf_maxcyl + 1;
		ntrack = hdconfig.conf_lasttrack + 1;
		nsector = hdconfig.conf_lastsect;
		HDDEBUG(SHOW_CONFIG, printf("hd%d: %d cyl, %d tracks, %d sectors\n",
		    un, ncyl, ntrack, nsector));
		for (i = 0, st = &hdst[0]; i < NHDST; ++i, ++st) {
			if (ncyl == st->ncpd && ntrack == st->ntpc && nsector == st->nspt)
				break;
		}
		if (i == NHDST) {
			if (!(hdconfig.conf_name[0]))
				bcopy("unknown", hdconfig.conf_name, 8);
			printf("hd%d: %s type (%d cyl, %d tracks, %d sectors)",
			       un, hdconfig.conf_name, ncyl, ntrack, nsector);
			i += un;	  /* allocate slot for unit */
			st = &hdst[i];	  /* point to proper place */
			st->ncpd = ncyl;
			st->ntpc = ntrack;
			st->nspt = nsector;
			st->nspc = nsector * ntrack;
			st->pcmp = hdconfig.conf_precomp << 2;
			st->nbps = 128 << hdconfig.conf_sectsize;
			st->off = hdinit_sizes;	 /* needs partition table */
			if (hdconfig.conf_name[0] == 0 && hdconfig.conf_name[2] == 'h')
				strncpy(hdtypes[i], hdconfig.conf_name+2,
					sizeof hdtypes[i]);
			else
				strncpy(hdtypes[i], hdconfig.conf_name,
					sizeof hdtypes[i]);
		} else
			printf("hd%d: %s", un, hdtypes[i]);
		printf("; interleave factor is %d to 1\n",
		       hdconfig.conf_interleave);
		if (iod->iod_type != i) { /* changed types - load adapter */
			iod->iod_type = i;
			hdreset(iod, hdaddr, NOISY, !DMA_MODE);
		}
	}
/*
 * read in the bad block table. If it is valid then allocate space for
 * it and read in the whole thing.
 */
	{
		struct hdbadtmp	 hdbadtemp;	/* temp place to read it in */
		struct hdbad		*hdb = (struct hdbad *) &hdbadtemp;

		hdrd(hdaddr, un, BAD_BLOCK_START, sizeof(struct hdbadtmp),
		     (caddr_t)hdb, st);
		if (!BAD_BLOCK_TABLE_OK(hdb)) {
			printf("hd%d: no bad block table\n", un);
		} else {
			register int len = (hdbadtemp.hdcount + 1) *
					   sizeof(struct hdmap);

			hdb = (struct hdbad *)calloc(len);
			HDDEBUG(SHOW_BAD, printf("hd%d: %d bytes of bad block table at %x\n", un, len, hdb));
			hdrd(hdaddr, un, BAD_BLOCK_START, len, (caddr_t)hdb, st);
			hdbad[un] = hdb; /* remember it */
			/*
			 * As shipped, disks usually have their bad blocks in
			 * the table, but without the replacement blocks
			 * identified yet.  Format(8) does that.  If there are
			 * any 0 replacement blocks we assign them in hdmakebad.
			 */
			for (i = hdb->hdcount - 1; i >= 0; i--) {
				if (hdb->hdmap[i].hdgood <= 0 &&
				    hdb->hdmap[i].hdbad != 0) {
					hdmakebad(hdb,un,st);
					break;
				}
			}
		}
	}
	/*
	 * Create a partition table.
	 */
	bzero(hdoff[un], NPART * sizeof (struct partab)); /* zero paritions */

	hdoff[un][2].start = 0;	/* force c partition to be the entire disk */
	hdoff[un][2].len = st->ncpd * st->ntpc * st->nspt;

	if (!hdgetpart(hdaddr, un, st, hdoff[un]))	/* get minidisk table */
		bcopy(st->off, hdoff[un], NPART * sizeof (struct partab));

	/*
	 * The adapter interrupt is disabled on entry, so we must disable
	 * it again on exit.
	 */
	hdaddr->HFR = DISKDISI|DISKIRQ12;
	DELAY(1000);
	splx(s);			  /* restore original priority */

	if (hdwstart == 0) {
		timeout(hdwatch, (caddr_t)0, hz);
		++hdwstart;
	}
	if (iod->iod_dk >= 0) {
		static float mspw = .0000016097;

		dk_mspw[iod->iod_dk] = mspw;
	}
	return(1);
}


/*
 * Interrupts off read routine.  Used to read in the configuration record
 * and the bad block records.  Note that it is ok to pass 0 for 'xbp' to hdgo
 * as it only uses it for the B_WRITE case.
 */
hdrd(hdaddr, un, block, count, addr, st)
	register struct hddevice volatile	*hdaddr;
	register int			block,
					count;
	register caddr_t		addr;
	register struct hdst		*st;	/* get type (may be unreal) */
{
	struct buf		buf;	/* only used internally in hd.c */
	register struct buf	*bp = &buf;

	HDDEBUG(SHOW_CONFIG, printf("hd%d: hdrd %d (%d bytes)\n", un, block, count));
	bp->b_dev = makedev(0, un << 3); /* make a dev */
	bp->b_flags = B_READ;
	bp->b_un.b_addr = addr;
	bp->b_bcount = count;
	bp->b_blkno = block;
	if (hdgo(hdaddr, st, (long)(block/st->nspc), block, bp, count,
		 (struct hdi_softc *)0))
		return(-1);
	do {
		if (!hdwait(DISKBUSY|DISKDRQ, DISKDRQ, hdaddr, !DMA_MODE) ||
		    check_error(DISKREAD, hdaddr, NOISY, !DMA_MODE))
			return(-1);	  /* oops, we got an error */
		adunload(addr, hdaddr, MIN(count, NBPS(st)), B_READ);
		count -= NBPS(st);
		addr += NBPS(st);
	}
	while (count > 0);
/*
 * if not a complete block transfer unload the rest of the adapter
 * buffer
 */
	if (count < 0)
		adunload((caddr_t)0, hdaddr, -count, B_READ); /* unload the rest */
	return(0);		/* normal return */
}


/*
 * Routine to actually start a disk operation.
 */
hdgo(hdaddr, st, cylin, blkno, bp, bcount, xbp)
	register struct hddevice volatile	*hdaddr;
	register struct hdst		*st;
	long				cylin;
	int				blkno;
	register struct buf		*bp;
	int				bcount;
	register struct hdi_softc	*xbp;
{
	register unsigned sec;
	register int un = unit(bp->b_dev);
	register int dr = hddinfo[un]->iod_slave;

	hdaddr->PCMP = st->pcmp >> 2;
	hdaddr->CYLL = cylin;
	hdaddr->CYLH = (cylin >> 8);

	sec = (unsigned)blkno * (unsigned)(BSIZE / NBPS(st));
	sec %= (st->nspc);

	hdaddr->HFR = DISKHD3|DISKIRQ12;	/* enables heads 8-15 */
	hdaddr->SDH = 0xA0 | ((dr&0x2)<<1 | dr&0x1)<<4 | sec/st->nspt;
	hdaddr->SNO = ((sec % st->nspt) + 1);
	hdaddr->SCT = howmany(bcount, NBPS(st));

	HDDEBUG(SHOW_GO, printf("HD%d (GO): cyl=%d track=%d sec=%d sct=%d\n",
	    un, cylin, sec / st->nspt, (sec % st->nspt) + 1, hdaddr->SCT));

	if (!hdwait(DISKBUSY | DISKRDY, DISKRDY, hdaddr, !DMA_MODE))
		return(-1);

	if (bp->b_flags & B_READ) {
		HDDEBUG(SHOW_ORDWR | SHOW_REGS,
		    printf("HDSTART: STARTING READ CMD...\n"));
		HDDEBUG(SHOW_REGS, hdstatus(bp, 0, hdaddr, -1, 0, !DMA_MODE));
		hdaddr->CMD = DISKREAD | NORETRY(bp->b_dev);
	} else {
		HDDEBUG(SHOW_ORDWR | SHOW_REGS,
		    printf("HDSTART: STARTING WRITE CMD...\n"));
		HDDEBUG(SHOW_REGS, hdstatus(bp, 0, hdaddr, -1, 0, !DMA_MODE));
		/*
		 * We always use no-retry on (PIO) EESDI writes due to
		 * an adapter problem.  Without no-retry performance is
		 * one rev per sector!
		 */
		if (hdc_softc[hddinfo[un]->iod_mi->ic_ctlr].hdc_flag & CAN_DMA
		    || NORETRY(bp->b_dev))
			hdaddr->CMD = DISKWRIT | DISKNORE;
		else
			hdaddr->CMD = DISKWRIT;
		if (!hdwait(DISKBUSY | DISKDRQ, DISKDRQ, hdaddr, !DMA_MODE))
			return(-1);
/*
 * following is for special case of hdwr which does it's
 * own adunload
 */
		if (xbp)
			hdunload(bp, xbp, hdaddr);
	}
	hdutab[un].b_cylin = cylin;	/* update current cylinder */

	return(0);
}


#ifdef DEBUG
int hdmicro = 0;		/* for testing available intersector time */
#endif	/* DEBUG */
/*
 * Load or unload the adapter buffer.
 * Called for write/read when a data transfer is required.
 * Adunload is called to transfer as much data as exists in the current page,
 * and then the next page address is obtained and the process continues.
 */
hdunload(bp, xbp, hdaddr)
	register struct buf		*bp;
	register struct hdi_softc	*xbp;
	struct hddevice volatile			*hdaddr;
{
/* count remaining = end-address - current address */
	register int	hdcnt = (bp->b_un.b_addr + bp->b_bcount) - xbp->hd_addr;
					  /* note bp != xbp */
	register caddr_t		addr;
	register int			nleft;
	register int			flag = bp->b_flags & B_READ;

#ifdef DEBUG
	DELAY(hdmicro);	
#endif	/* DEBUG */
	HDDEBUG(SHOW_COUNT, printf("hdcnt=%d ", hdcnt));
	if (hdcnt > NBPS(st))
		hdcnt = NBPS(st);	  /* actual amount to transfer */
/*
* following calculation yields the number of bytes left on this page
* it will give zero when at the start of a page which skips the first
* adunload.
*/
	nleft = PGOFSET - (((int)xbp->hd_addr - 1) & PGOFSET);
	addr = xbp->hd_physaddr; /* the real memory address */
	if (nleft > hdcnt)
		nleft = hdcnt;
	HDDEBUG(SHOW_COUNT, printf("nleft=%d ", nleft));
	if (nleft) {
		if (addr == NO_ADDR)
			addr = REAL_BUF_ADDR(bp, xbp->hd_addr); /* first time */
		xadunload(addr, hdaddr, nleft, flag);
		addr += nleft;
		xbp->hd_addr += nleft;
	}
	HDDEBUG(SHOW_COUNT, printf("nleft=%d ", hdcnt - nleft));
	if ((nleft = hdcnt - nleft) != 0) {
		/* more to xfer - we are at a page boundary */
		addr = REAL_BUF_ADDR(bp, xbp->hd_addr);
		xadunload(addr, hdaddr, nleft, flag);
		xbp->hd_addr += nleft;
		addr += nleft;
	}
	HDDEBUG(SHOW_COUNT, printf("nleft=%d ", NBPS(st) - hdcnt));
	if ((nleft = NBPS(st) - hdcnt) != 0)
		xadunload((caddr_t)0, hdaddr, nleft, flag); /* partial read */
	xbp->hd_physaddr = addr; /* updated real address */
	HDDEBUG(SHOW_COUNT, printf("addr=0x%x\n", addr));
	HDDEBUG(SHOW_PAGET, {
		if (bp->b_flags & B_PAGET) {
			prhex(xbp->hd_addr - hdcnt, 8);
			printf("%s hd_addr=0x%x\n", bp->b_flags & B_READ ? "read to" : "written from", xbp->hd_addr - hdcnt);
		}
	});
}


/*
 * Make a bad block table in simulation of what the format utility does.
 * This is only a stopgap measure - the resulting table is not written
 * back to the disk.
 */
hdmakebad(hdb, un, st)
	register struct hdbad	*hdb;
	register int		un;
	register struct hdst	*st;
{
	register int		replace_block = (st->ncpd - 1) * st->ntpc *
						st->nspt - MAXBADBLKS;
	register int		i,
				cnt = 0;

	for (i = 0; i < hdb->hdcount; ++i)
		if (hdb->hdmap[i].hdbad != 0 && hdb->hdmap[i].hdgood <= 0) {
			hdb->hdmap[i].hdgood = replace_block + i;
			++cnt;
		}
	printf("hd%d: %d blocks forwarded to 0 - fix by running format(8R)\n",
	       un, cnt);
}


/*
 * Read in VRM minidisk directory and look for minidisks of the appropriate
 * name (hd?[a-h]) and build hdoff[unit][0-7] entries for the given partitions.
 */
hdgetpart(hdaddr, un, st, off)
	struct hddevice volatile			*hdaddr;
	register int			un;
	struct hdst			*st;
	register struct partab		*off;
{
	struct minidirectory		minidirectory;
	int				cylsize = st->nspc;

	if (hdrd(hdaddr, un, MINIDISK_BLOCK, sizeof (struct minidirectory), (caddr_t) &minidirectory, st) < 0)
		return(0);		/* couldn't read it */
	/* the 0 means we start our partitions at cylinder 0 */
	return(dkfindpart(&minidirectory, off, cylsize, 0, un, "hd"));
}

/**********************************************************************/

/*
 * Open a hard disk partition (for reads and writes).
 */
hdopen(dev)
	dev_t				dev;
{
	register int			un = unit(dev);
	register int			pa = part(dev);
	register struct iocc_device	*iod;

	HDDEBUG(SHOW_ORDWR, printf("HD: JUST ENTERED HDOPEN(%x)!!\n", dev));

	if (un >= NHD || pa < 0 || pa >= NPART || (iod = hddinfo[un]) == 0 ||
	    iod->iod_alive == 0 || hdoff[un][pa].len == 0)
		return(ENXIO);

	return(0);
}

/**********************************************************************/

/*
 *	Raw read routine:
 *	  This routine calls physio which computes and validates
 *	  a physical address from the current logical address.
 *
 *		Arguments:
 *		  Full device number
 *		  An I/O request vector ("uio")
 *		Functions:
 *		  Verifies the drive number.
 *		  Call physio which does the actual raw (physical) I/O
 *		  The arguments to physio are:
 *			pointer to the strategy routine
 *			buffer for raw I/O
 *			device
 *			read/write flag
 *			pointer to the minphys routine
 */
hdread(dev, uio)
	register dev_t		dev;
	register struct uio	*uio;
{
	register int		un = unit(dev);

	HDDEBUG(SHOW_ORDWR, printf("HD: JUST ENTERED HDREAD()!!\n"));
	if (un >= NHD)
		return(ENXIO);
	return(physio(hdstrategy, &rhdbuf[un], dev, B_READ, hdminphys, uio));
}

/**********************************************************************/

/*
 *	Raw write routine:
 *	  This routine calls physio which computes and validates
 *	  a physical address from the current logical address.
 *
 *		Arguments:
 *		  Full device number
 *		  An I/O request vector ("uio")
 *		Functions:
 *		  Verifies the drive number.
 *		  Call physio which does the actual raw (physical) I/O
 *		  The arguments to physio are:
 *			pointer to the strategy routine
 *			buffer for raw I/O
 *			device
 *			read/write flag
 *			pointer to the minphys routine
 */
hdwrite(dev, uio)
	dev_t			dev;
	register struct uio	*uio;
{
	register int		un = unit(dev);

	HDDEBUG(SHOW_ORDWR, printf("HD: JUST ENTERED HDWRITE()!!\n"));
	if (un >= NHD)
		return(ENXIO);
	return(physio(hdstrategy, &rhdbuf[un], dev, B_WRITE, hdminphys, uio));
}

/**********************************************************************/

/*
 * Device minphys routine.  Its job is to reduce bp->b_bcount to the
 * maximum we can do in a single transfer.
 */
hdminphys(bp)
	register struct buf		*bp;
{
	register struct iocc_device	*iod;
	register struct iocc_ctlr	*ic;
	extern unsigned 		(*dma_get_minphys())();
	register int			un;

	un = unit(bp->b_dev);
	if (un >= NHD || (iod = hddinfo[un]) == 0 || iod->iod_alive == 0)
		return;	/* hdstrategy will report the error */

	minphys(bp);	/* enforces kernel-wide b_bcount restriction */

	ic = iod->iod_mi;
	if (hdc_softc[ic->ic_ctlr].hdc_flag & CAN_DMA) {
		(*dma_get_minphys(ic))(bp);	/* dma's bcount restrictions */
		/*
		 * Dma minphys routines can leave us w/ a partial
		 * xfer *and* leftovers.  We can't handle that so
		 * we truncate any partial (pushing it into leftovers).
		 */
		if (bp->b_bcount >= NBPS(st))
			bp->b_bcount &= ~(NBPS(st) - 1);
		/*
		 * Writing to a HESDI we must break transfers up into adapter
		 * buffer sized chunks.  This is a software circumvention
		 * of an adapter microcode bug.
		 */
		if (hesdi_w_bug && !(bp->b_flags & B_READ))
			if (bp->b_bcount > hdc_softc[ic->ic_ctlr].hdc_nbuf * NBPS(st))
				bp->b_bcount = hdc_softc[ic->ic_ctlr].hdc_nbuf * NBPS(st);
	}
}

/**********************************************************************/

/*
 * Process ioctl's
 * The only one supported at the moment returns the partition size and
 * geometry information for newfs.
 * One grotty feature is that the size returned is adjusted for the C 
 * partition so that it doesn't clobber the bad-block table at the
 * end of the disk. It does this for partitions that start at block 0
 * and have a non-zero length (this is slightly preferable to checking
 * for part(dev) == 2). It is questionable whether one should 
 * make a filesystem on a C partition instead of making an 'a'
 * partition for the entire usable disk via the partition table.
 */
/*ARGSUSED*/
hdioctl(dev, cmd, data, flag)
	caddr_t				data;
	dev_t				dev;
{
	register int			un = unit(dev);
	register struct iocc_device	*iod;
	register struct hdst		*st;
	register struct partab		*off;

	if (un >= NHD || (iod = hddinfo[un]) == 0 || iod->iod_alive == 0)
		return(ENODEV);
	st = &hdst[iod->iod_type];

	off = &hdoff[un][part(dev)];		/* partition table */

	if (cmd == DKIOCGPART) {
		register struct dkpart *dk = (struct dkpart *) data;

		dk->dk_size = off->len;			/* size */
		if (off->start == 0 && off->len != 0)
			dk->dk_size -= st->nspc + MAXBADBLKS; /* grotty - make the
							/* proper available size for newfs */
		dk->dk_start = off->start * st->nspc;	/* start */
		dk->dk_blocksize = NBPS(st);		/* device blocksize */
		dk->dk_ntrack = st->ntpc;		/* tracks */
		dk->dk_nsector = st->nspt;		/* sectors */
		dk->dk_ncyl = off->len / st->nspc;	/* number of cylinders */
		strcpy(dk->dk_name, hdtypes[iod->iod_type]); /* copy name */
		return(0);
	} else
		return(ENOTTY);		/* sigh */
}

/**********************************************************************/

/*
 * Partition size routine.  Used by swap system, for one.
 */
hdsize(dev)
	dev_t				dev;
{
	register int			un = unit(dev);
	register int			pa = part(dev);
	register struct iocc_device	*iod;

	if (un >= NHD || pa < 0 || pa >= NPART ||
	    (iod = hddinfo[un]) == 0 || iod->iod_alive == 0 ||
             hdoff[un][pa].len<=0)
		return(-1);
	return(hdoff[un][pa].len);
}

/**********************************************************************/

/*
 *	Strategy Routine:
 *	Arguments:
 *	  Pointer to buffer structure
 *	Function:
 *	  Check validity of request
 *	  Queue the request
 *	  Start up the device if idle
 *
 *	Note:
 *	  Block numbers (in b_blkno) are RELATIVE to the start of
 *	  the partition.
 *	  Cylinder numbers (in b_cylin) are ABSOLUTE.
 */
hdstrategy(bp)
	register struct buf		*bp;
{
	register int			un,
					pa;
	register struct hdst		*st;
	register struct buf		*dp,
					*cp;
	register struct iocc_device	*iod;
	register struct partab		*off;
	long				sz,
					bn;
	int				s;
	register struct iocc_ctlr	*ic;
	register struct hdc_softc	*sp;

	un = unit(bp->b_dev);
	pa = part(bp->b_dev);
	if (un >= NHD || pa >= NPART || pa < 0 || (iod = hddinfo[un]) == 0 || iod->iod_alive == 0) {
		bp->b_error = ENXIO;
		goto bad;
	}
	off = hdoff[un];	/* proper partition table */
	bn = bp->b_blkno;
	ic = iod->iod_mi;
	st = &hdst[iod->iod_type];

	sz = bp->b_bcount;
#ifdef	HDASSERTIONS
	hdminphys(bp);
	if (sz != bp->b_bcount)	/* nobody called our minphys 1st?!!! */
		panic("hdstrategy b_bcount");
#endif	/* HDASSERTIONS */
	sz = (sz + BSIZE - 1) >> BSHIFT;

	if (bn < 0 || bn >= off[pa].len ||
	    bn+sz > off[pa].len && !(bp->b_flags & B_READ)) {
		bp->b_error = EINVAL;
bad:

		HDDEBUG(SHOW_ENQ, {
			printf("HD: HDSTRATEGY -- BAD %s REQUEST",
			    (bp->b_flags & B_READ) ? "READ" : "WRITE");
			printf("\nblkno=%d bcount=%d baddr=0x%x",
			    bp->b_blkno, bp->b_bcount, bp->b_un.b_addr);
			printf(" bdev=0x%x\n", bp->b_dev);
			printf("unit=%d part'n=%d ", un, pa);
			printf(" psize (blocks)=%d", off[pa].len);
			printf(" pcyl=%d\n", off[pa].start);
		});

		bp->b_flags |= B_ERROR;
		iodone(bp);
		return;
	}
	if (bn + sz > off[pa].len)
		bp->b_bcount = (off[pa].len - bn) * BSIZE;
	bp->b_cylin = bn/st->nspc + off[pa].start;

	s = spl_disk();			  /* CPU level 4 interrupt */
	dp = &hdutab[un];

	disksort(dp, bp);	/* insert bp into device's queue */

	bp->b_actl = NULL;	/* no buffer extension yet */

	HDDEBUG(SHOW_ENQ, {
		printf("HD: HDSTRATEGY: flags=%b",
			bp->b_flags,B_FLAGS_FMT);
		printf(" blkno=%d bcount=%d baddr= 0x%x\n",
		    bp->b_blkno, bp->b_bcount, bp->b_un.b_addr);
		printf(" b_cylin=%d", bp->b_cylin);
		printf(" bdev=0x%x", bp->b_dev);
		printf(" unit=%d part=%d", un, pa);
		printf(" psize=%d",
		    off[pa].len);
		printf(" pcyl=%d\n", off[pa].start);
	});

	(void) hdustart(iod);

	sp = &hdc_softc[ic->ic_ctlr];
	if (sp->hdc_flag & CAN_DMA) {
#ifdef TRACE
		u_int	sec = (u_int)bp->b_blkno * (u_int)(BSIZE / NBPS(st));

		sec %= st->nspc;
#endif	/* TRACE */
		trace(TR_D_ENQ,
		      (bp->b_cylin & 0xfff)<<20 | (sec/st->nspt & 0xf)<<16 |
			(sec%st->nspt +1 & 0xff)<<8 |
			howmany(bp->b_bcount, NBPS(st)),
		      ((u_int)bp->b_un.b_addr & 0xffffff)<<8 |
			((caddr_t)ic->ic_addr == hdstd[1])<<7 |
			!(bp->b_flags & B_READ)<<6 | NORETRY(bp->b_dev)<<4 |
			(hddinfo[unit(bp->b_dev)]->iod_slave & 0x3)<<1);

		HDDEBUG(SHOW_WRITECHECK, {
			if (!(bp->b_flags & B_READ)) hddwcheck(bp, 0, 0, 0);
		});
		if (hddgetchan(ic, sp)) {
			/*
			 * not only is there no channel available, there
			 * probably never will be one (or else we'd be
			 * still in hddgetchan waiting for it)
			 */
			printf("HD: DMA CHANNEL ABUSE!!\n");
			bp->b_flags |= B_ERROR;
			iodone(bp);
			splx(s);
			return;
		}
		if (!(sp->hdc_flag & DMA_MODE)) {    /* 1st dma? */
			sp->hdc_flag |= DMA_MODE;
			hddmode((struct hddevice volatile *)ic->ic_addr);
		}
		hddstart(ic);
	} else if (!disable || hdactive == 0 || hdactive == iod->iod_mi) {
		/*
		 * either multiple controllers may be active or
		 * no controller is active or our controller is active
		 */
		if (!ic->ic_tab.b_active)	/* one at a time! */
			hdstart(ic);
	} else {	/* other controller apparently is active */
		hdwaiting = iod->iod_mi;  /* remember we're waiting */
		cp = &hdactive->ic_tab;
		/* test if other actually doing anything if not, then switch */
		if (cp->b_active == 0)
			(void) hdswitch(hdwaiting);
		else {
			HDDEBUG(SHOW_ENQ, printf("hdstrategy: other controller (%x) active\n", hdactive));
		}
	}
	splx(s);
}


/*
 *	Device Startup Routine:
 *	Arguments:
 *	  Pointer to device structure
 *	Function:
 *	  Queue the device to the controller
 */
hdustart(iod)
	register struct iocc_device	*iod;
{
	register struct buf		*dp;
	register struct iocc_ctlr	*ic;

	HDDEBUG(SHOW_DEQ, printf("hdustart(%x)\n", iod));
	if (iod == 0)
		return(0);		  /* Why this check? */
	ic = iod->iod_mi;
	dp = &hdutab[iod->iod_unit];
	if (dp->b_actf == NULL) {
		HDDEBUG(SHOW_DEQ, printf("hdustart: actf == NULL\n"));
	} else {
		/*
		 * Device is ready to go.
		 * Put it on the ready queue for the controller
		 * (unless it is already there)
		 */
		if (dp->b_active) {
			HDDEBUG(SHOW_DEQ, printf("hdustart: active=%d\n", dp->b_active));
		} else {
			dp->b_forw = NULL;
			if (ic->ic_tab.b_actf == NULL)
				ic->ic_tab.b_actf = dp;
			else
				ic->ic_tab.b_actl->b_forw = dp;
			ic->ic_tab.b_actl = dp;
			dp->b_active = 1;
		}
	}
	return(0);
}


/*
 * Dma channel acquisition function.
 */
hddgetchan(ic, sp)
	register struct iocc_ctlr	*ic;
	register struct hdc_softc	*sp;
{
	if (!(sp->hdc_flag & GOT_CHANNEL)) {
		if (sp->hdc_flag & WAIT_CHANNEL) {
			while (!(sp->hdc_flag & GOT_CHANNEL)) {
				sleep((caddr_t)sp, PRIBIO);
			}
		} else {
			ic->ic_dmaflags = DMA_EXCLUSIVE | DMA_PHYSICAL |
					  DMA_PAGE | DMA_CASCADE;
			ic->ic_dmabuf = NULL;	/* just get channel */
			ic->ic_dmachannel = dma_select_chan(sp->hdc_channels);
			if (dma_setup(ic) != DMA_OK_RET) {
				/* all our potential channels must be being used
				 * EXCLUSIVE ---
				 * And they may well stay that way forever so it
				 * is time to give up:
				 */
				return(1);
			}
			if (!(sp->hdc_flag & GOT_CHANNEL)) {
				/* 
				 * our request is queued in dma driver
				 * hddgo will get called eventually...
				 */
				sp->hdc_flag |= WAIT_CHANNEL;
				while (!(sp->hdc_flag & GOT_CHANNEL)) {
					sleep((caddr_t)sp, PRIBIO);
				}
			}
		}
	}
	return(0);
}


/*
 * HDDMA_BURST == log2(burst)-2
 * burst == 1 << (HDDMA_BURST + 2)
 * minimum burst is 4 bytes; maximum is 512
 */
#ifndef HDDMA_BURST
#define	HDDMA_BURST	0x01	/* burst length == 8 bytes */
#endif	/* HDDMA_BURST */
u_int	 hdd_burst = HDDMA_BURST;

/*
 * This function gets the adapter into dma mode.
 */
hddmode(hdaddr)
	register struct hddevice volatile	*hdaddr;
{
	hdaddr->MODE = hdd_burst<<1 | 1;
	delay(100);	/* 100 (!) ms until card is in dma mode */
}


/*
 *	DMA Capable Controller Startup Routine:
 *	Arguments:
 *	  Pointer to controller structure
 *	Function:
 *	  Compute device-dependent parameters
 *	  Kick off dma
 */
hddstart(ic)
    register struct iocc_ctlr   *ic;
{
    register struct hdc_softc   *sp = &hdc_softc[ic->ic_ctlr];
    register struct buf         *bp,
   				*dp,
				*dp0;
    register struct hdi_softc   *xbp;
    register int		qlen,
    				q;
    register u_int		ioaddr,
    				dr,
				dq;	/* packed drive and queue */
    register struct iocc_device *iod;
    register struct hdst	*st;

    HDDEBUG(SHOW_DEQ, printf("hddstart(0x%x, 0x%x)\n", ic, sp));

    for (;;) {    /* start as many xfers as possible */
	/*
         * Find a startable xfer to do & allocate resources for it.
         * The qlen loop is so we start idle devices before we pile
         * more work onto busy devices.
         */
        for (qlen = 0, dp = NULL;
             qlen < MIN(HESDI_QDEPTH, 2) && dp == NULL; qlen++)
            for (dp0 = ic->ic_tab.b_actf; dp0 != NULL; dp0 = dp0->b_forw) {
                iod = hddinfo[dp0 - hdutab];
                dr = iod->iod_slave;
		if (qlen != hddcmdqlen(sp, dr))
		    continue;
                /* find first inactive transfer */
                bp = dp0->b_actf;
                for (bp = dp0->b_actf; bp != NULL; bp = bp->b_actf)
                    if (!ACTIVE(bp))
                        break;
                if (bp == NULL)
                    continue; /* drive has no work to do */
		/*
		 * bp now is the xfer we'll consider starting
		 */
                if (qlen != 0) {
		    register struct buf	**bpp;

                    st = &hdst[iod->iod_type];
                    /* find the xfer in progress */
	            for (bpp = &sp->hdc_cmdq[PACK_DR_Q(dr, 0)];
		         *bpp == NULL; bpp++)
                        ;
                    /*
                     * We don't want to start a transfer now that would
                     * involve a seek.  This is because requests falling
                     * between our current cylinder and the one we'd seek
                     * to may get queued before the current xfer completes.
                     */
                    if (bp->b_cylin < (*bpp)->b_cylin ||
                        bp->b_cylin > (*bpp)->b_cylin +
                                (BSIZE/NBPS(st) * (*bpp)->b_blkno % st->nspc +
                                 howmany((*bpp)->b_bcount, NBPS(st)))
                            / st->nspc)
                        continue;
		    /*
		     * Microcode bug introduced after R46 results in frequent
		     * lost interrupts if we queue both a read and a write.
		     * Ucode is fixed in R55.
		     */
		    if (hesdi_q_bug &&
		        sp->hdc_ulevel > 0x46 && sp->hdc_ulevel < 0x55 &&
			(bp->b_flags & B_READ) != ((*bpp)->b_flags & B_READ))
			    continue;
                }
                /* allocate adapter command queue slot */
                q = hddgetcmdq(sp, dr, bp);
                if (q == -1)
                    continue;    /* try next drive */
		dq = PACK_DR_Q(dr, q);
                HDDEBUG(SHOW_DCMD,
                        printf("HDDSTART: got cmdq[%d][%d]\n", dr, q));

                xbp = (struct hdi_softc *)bp->b_actl;
                if (xbp == NULL) {
                    xbp = hdxbget();
                    if (xbp == NULL) {
                        sp->hdc_cmdq[dq] = NULL;	/* release cmd q slot */
                        HDDEBUG(SHOW_DCMD,
                                printf("HDDSTART: release cmdq[%d][%d]\n",
                                       dr, q));
                        continue;
                    }
                    /* save original parms */
                    xbp->hd_addr = bp->b_un.b_addr;
                    xbp->hd_cylin = bp->b_cylin;
                    xbp->hd_blkno = bp->b_blkno;
                    xbp->hd_bcount = bp->b_bcount;
                }

                /* handle partial sector xfers */
                if (bp->b_bcount < NBPS(st) || PARTIAL(bp)) {
                    bp->b_bcount = NBPS(st);
                    bp->b_un.b_addr = sp->hdc_pbufp[dr][q];
                    if (bp->b_flags & B_PHYS) {
                        bp->b_flags &= ~B_PHYS;
                        xbp->hd_flag |= HD_PHYS;
                    }
                } else	/* postpone handling partial sector (if any) */
                    bp->b_bcount &= ~(NBPS(st) - 1);
     
                /* allocate adapter sector buffers */
                xbp->hd_adapbuf = hddgetadapbuf((u_int)(bp->b_bcount/NBPS(st)),
                                                sp, dq);
                if (xbp->hd_adapbuf < 0) {
                    if (bp->b_actl == NULL)
                         hddunforward(bp, xbp);
                    sp->hdc_cmdq[dq] = NULL;
                    HDDEBUG(SHOW_DCMD,
                            printf("HDDSTART: release cmdq[%d][%d]\n", dr, q));
                    continue;    /* try next drive */
                }
                HDDEBUG(SHOW_DABUF,
                        printf("HDDSTART: got %d adapter bufs starting at %d\n",
                               bp->b_bcount/NBPS(st), xbp->hd_adapbuf));
     
                /*
                 * Allocate TCWs.  Xfer is delayed if there are too few.
                 */
                ioaddr = dma_map(ic->ic_dmachannel, bp);
                if (ioaddr == (unsigned)DMA_INV_IOADDR) {
		    /*
		     * Panic if we cannot get any TCWs but we have no xfers
		     * active.  "Can't happen" because nobody else could have
		     * the TCWs.  Might happen if we don't always free all the
		     * TCWs we've allocated - if we change b_bcount or b_addr
		     * before calling dma_free_map for instance.
		     */
                    if (ic->ic_tab.b_active == 0)
                        panic("hddstart tcws");
                    hddreladapbuf(sp, (u_int)xbp->hd_adapbuf);
                    if (bp->b_actl == NULL)
                        hddunforward(bp, xbp);
                    sp->hdc_cmdq[dq] = NULL;
                    HDDEBUG(SHOW_DCMD,
                            printf("HDDSTART: release cmdq[%d][%d]\n", dr, q));
                    continue;    /* try next drive */
                }

                /*
                 * At this point we are committed to asking
                 * the adapter to do the transfer.  Errors
                 * "can't happen" from here until the IO
                 * interrupt for this transfer.
                 *
                 * Exit loops now only for indentation's sake.
                 */
                dp = dp0;
                break;
            }
        if (dp == NULL)
            break;        /* nothing startable */
        if (bp->b_actl == NULL)
            bp->b_actl = (struct buf *)xbp;    /* init buf extension */
        xbp->hd_flag |= HD_ACTIVE;
        /*
         * If writing a partial sector we must fill output buffer first.
         */
        if (!(bp->b_flags & B_READ) && PARTIAL(bp))
	    hddwrpartial(bp, xbp);

        ic->ic_tab.b_active++;

        HDDEBUG(SHOW_WRITECHECK, {
            if (!(bp->b_flags & B_READ))
                hddwcheck(bp, 0, 0, 0);
        });
        HDDEBUG(SHOW_DEQ,
            printf("HD: HDDSTART -- DEQ %s\nblkno=%d bcount=%d baddr=0x%x bdev=0x%x\n",
                   bp->b_flags&B_READ ? "READ" : "WRITE", bp->b_blkno,
                   bp->b_bcount, bp->b_un.b_addr, bp->b_dev));
        HDDEBUG(SHOW_XFER, printf("HDDSTART: starting %s\n",
                                  bp->b_flags&B_READ ? "read" : "write"));
     
        hddgo(ic, bp->b_bcount, ioaddr, bp);
    }
    if (ic->ic_tab.b_active == 0) {
        if (!hesdi_d_bug) {
            sp->hdc_flag &= ~GOT_CHANNEL;
	    dma_done(ic->ic_dmachannel);
        }
    } else
	sp->hdc_timer = 0;
}


/*
 * Returns the length of a drives command queue.
 */
int
hddcmdqlen(sp, dr)
	register struct hdc_softc	*sp;
	register u_int			dr;	/* drive/slave # */
{
	register u_int			q,
					rv;

	rv = 0;
	for (q = 0; q < HESDI_QDEPTH; q++)
		if (sp->hdc_cmdq[PACK_DR_Q(dr, q)] != NULL)
			rv++;
	return(rv);
}


/*
 * Used to allocate a command queue slot for a given drive.
 */
hddgetcmdq(sp, dr, bp)
	register struct hdc_softc	*sp;
	register u_int			dr;	/* drive/slave # */
	register struct buf		*bp;
{
	register u_int			q,
					dq;

	for (q = 0; q < HESDI_QDEPTH; q++) {
		dq = PACK_DR_Q(dr, q);
		if (sp->hdc_cmdq[dq] == NULL) {
			sp->hdc_cmdq[dq] = bp;
			return(q);
		}
	}
	return(-1);
}


/*
 * Get a new buffer header extension from free list
 */
struct hdi_softc *
hdxbget()
{
	register int			s;
	register struct hdi_softc	*xbp;

	s = spl_disk();

	xbp = hdi_softc[0].hd_forw;
	if (xbp == &hdi_softc[0]) {
		xbp = (struct hdi_softc *)0;
		printf("HD: OUT OF XBUFS! NHDXBUF=%d\n", NHDXBUF);
	} else {
		xbp->hd_back->hd_forw = xbp->hd_forw;
		xbp->hd_forw->hd_back = xbp->hd_back;
		/*
		 * Initialize a few key fields.
		 */
		xbp->hd_flag = 0;
		xbp->hd_errcnt = 0;
		xbp->hd_hangcnt = 0;
	}

	splx(s);

	return(xbp);
}


/*
 * Allocate sufficient adapter buffers for an n sector transfer.
 * Note that adapter supports wrapping around from high buffers to
 * low so we do too.  Returns starting buffer number.
 */
hddgetadapbuf(n, sp, dq)
	register u_int			n;	/* # bufs needed */
	struct hdc_softc		*sp;
	u_int				dq;	/* drive# & queue# */
{
	register u_char			*map = sp->hdc_bufmap;
	register u_int			cbufs = sp->hdc_nbuf;
	register int			i,
					rv = 0;

	if (n < 1)
		return(-1);	/* Bad request */
	if (n > cbufs)
		n = cbufs;	/* Aok! Card will wrap xfer thru all bufs */
	/*
	 * Find n free buffers.  Tricky, non-intuitive loop.  But quick.
	 */
	n -= 1;
	cbufs -= 1;		/* cbufs must be a power of 2! */
loop:
	for (i = rv + n; i >= rv; i -= 1) {
		if (map[i & cbufs] != FREEBUF) {
			if (i >= cbufs)
				return(-1);	/* no n consecutive free bufs */
			rv = i + 1;
			goto loop;
		}
	}
	/*
	 * Found them.  Now mark them in use.
	 * They are marked with the command's drive & command queue position to
	 * facilitate freeing them later.
	 */
	for (i = rv + n; i >= rv; i -= 1)
		map[i & cbufs] = dq;
	return(rv);
}


/*
 * Copy saved information back into bp from xbp and release xbp.  Used at end
 * of transfer (from hddint) and when a transfer has to be deferred due to
 * lack of resources (from hddstart).
 */
hddunforward(bp, xbp)
	register struct buf		*bp;
	register struct hdi_softc	*xbp;
{

	bp->b_un.b_addr = xbp->hd_addr;
	bp->b_cylin = xbp->hd_cylin;
	bp->b_blkno = xbp->hd_blkno;
	bp->b_bcount = xbp->hd_bcount;
	/*
	 * Restore raw IO flag if we cleared it.  Redundant (but harmless) if
	 * we already restored it in hddunstart.
	 */
	if (xbp->hd_flag & HD_PHYS) {
		xbp->hd_flag &= ~HD_PHYS;
		bp->b_flags |= B_PHYS;
	}

	bp->b_actl = NULL;
	hdxbrel(xbp);
}


/*
 * This function copies partial sectors (< 512 bytes) being written into our
 * local buffers (hdd_pbuf array).  The we fill out the sector with zeroes
 * before writing it.
 */
hddwrpartial(bp, xbp)
	register struct buf		*bp;
	register struct hdi_softc	*xbp;
{
	register int		i = xbp->hd_bcount % NBPS(st);
	register caddr_t	vfrom = xbp->hd_addr + xbp->hd_bcount-i;

	if (!(xbp->hd_flag & HD_PHYS)) {
		bcopy(vfrom, bp->b_un.b_addr, i);
	} else {
		/*
		 * Copy in partial sector we will be writing
		 */
		register caddr_t	from, from2;
		register int		s, v;
		register u_int		n;	/* bytes left on page */

		bp->b_flags |= B_PHYS;
		from = real_buf_addr(bp, vfrom);
		n = PGOFSET + 1 - ((u_int)from & PGOFSET);
		if (n >= i) {
			s = spl_high();
			GET_VR0(v); /* go Virtual=Real mode for bcopy */
			bcopy(from, bp->b_un.b_addr, i);
		} else {
			from2 = real_buf_addr(bp, vfrom + n);
			s = spl_high();
			GET_VR0(v);
			bcopy(from, bp->b_un.b_addr, n);
			bcopy(from2, bp->b_un.b_addr + n, i - n);
		}
		SET_VR(v);
		splx(s);
		bp->b_flags &= ~B_PHYS;
	}
	bzero(bp->b_un.b_addr + i, NBPS(st) - i);
	HDDEBUG(SHOW_PAGET, {
		if (bp->b_flags & B_PAGET) {
			prhex(bp->b_un.b_addr, 8);
			printf("write vfrom=0x%x b_addr=0x%x\n", vfrom,
			       bp->b_un.b_addr);
		}
	});
}


/*
 * Dma go routine.  Kicks adapter to actually start the transfer.
 */
hddgo(ic, len, ioaddr, bp)
	register struct iocc_ctlr	*ic;
	register long			len;
	register u_int			ioaddr;
	register struct buf		*bp;
{
	register struct hdc_softc	*sp = &hdc_softc[ic->ic_ctlr];
	register struct hddevice volatile	*hdaddr =(struct hddevice volatile *)ic->ic_addr;
	register struct iocc_device	*iod;
	register struct hdi_softc	*xbp;
	register struct hdst		*st;
	register struct buf		*dp,
					*bp0;
	register u_int			sec;

	if (len == 0) {		/* called from dma_setup ? */
		if (ioaddr == 0 && bp == NULL) { /* asked for & got channel */
			sp->hdc_flag |= GOT_CHANNEL;
			if (sp->hdc_flag & WAIT_CHANNEL) {
				sp->hdc_flag &= ~WAIT_CHANNEL;
				wakeup((caddr_t)sp);
			}
			return;
		} else {	/* dma code must be in error */
			/*
			 * Don't know enough about what resources we have to
			 * back out safely.
			 */
			panic("hddgo len0");
		}
	}
	xbp = (struct hdi_softc *)bp->b_actl;
	dp = &hdutab[unit(bp->b_dev)];
	iod = hddinfo[unit(bp->b_dev)];
	st = &hdst[iod->iod_type];

#ifdef	HDASSERTIONS
	if (len % NBPS(st) != 0)
		panic("hddgo len");
	if ((ioaddr & PGOFSET) != ((u_int)bp->b_un.b_addr & PGOFSET) ||
	    (ioaddr & 0xfe0000) != 0xfe0000)
		panic("hddgo ioaddr");
	if (!(sp->hdc_flag & GOT_CHANNEL))
		panic("hddgo channel");	/* are we ever confused! */
	if (sp->hdc_cmdq[sp->hdc_bufmap[xbp->hd_adapbuf]] != bp)
		panic("hddgo pointers");
#endif	/* HDASSERTIONS */

	len /= NBPS(st);

	dma_go(ic->ic_dmachannel);

	if (iod->iod_dk >= 0) {
		dk_busy |= 1<<iod->iod_dk;
		dk_seek[iod->iod_dk] += (bp->b_cylin != dp->b_cylin);
		dk_xfer[iod->iod_dk]++;
		dk_wds[iod->iod_dk] += len*NBPS(st) >> 6; /* 64 = pdp11 click */
	}

	bp0 = dp->b_actf;
	if (bp0 != NULL && bp0 != bp) {
#ifdef	HDASSERTIONS
		if (!ACTIVE(bp0))
			panic("hddgo dp->b_actf");
		if (bp != bp0->b_actf)
			panic("hddgo bp");
#endif	/* HDASSERTIONS */
		if (bp->b_cylin > bp0->b_cylin) {
			/* bring bp to front of q */
			hddbufdeq(dp, bp0, NOISY);
			disksort(dp, bp0);
		}
	}

	HDDEBUG(SHOW_WRITECHECK, {
		if (!(bp->b_flags & B_READ))
			hddwcheck(bp, ioaddr, 0, 0);
	});

	(void)hdwait(DDISKBUSY, 0, hdaddr, DMA_MODE);
	if (ic->ic_dmachannel == sp->hdc_channels[0])	/* primary channel? */
		hdaddr->HFR = DISKIRQ12;
	else	/* use alternate channel */
		hdaddr->HFR = DISKADMA | DISKIRQ12;

	sec = (u_int)bp->b_blkno * (u_int)(BSIZE / NBPS(st));
	sec %= st->nspc;

	HDDEBUG(SHOW_DGO, printf("HDDGO: cyl=0x%x trk=0x%x sec=0x%x sct=0x%x buf=0x%x io@=0x%x dr|q=0x%x chan=%d\n", bp->b_cylin, sec/st->nspt, sec%st->nspt + 1, len, xbp->hd_adapbuf, ioaddr, sp->hdc_bufmap[xbp->hd_adapbuf], ic->ic_dmachannel));

	xbp->hd_ioaddr = ioaddr;	/* for dma_free_map later */
	ioaddr >>= 1;	/* adapter will shift left 1 */

	trace(TR_D_GO,
	      (bp->b_cylin & 0xfff)<<20 |
			(sec/st->nspt & 0xf)<<16 |
			(sec%st->nspt +1 & 0xff)<<8 |
			len & 0xff,
	      (ioaddr & 0xffff)<<16 |
			(xbp->hd_adapbuf & 0xff)<<8 |
			((caddr_t)hdaddr == hdstd[1])<<7 |
			!(bp->b_flags & B_READ)<<6 |
			NORETRY(bp->b_dev)<<4 |
			sp->hdc_bufmap[xbp->hd_adapbuf] & 0x7);

	(void)hdwait(DDISKBUSY, 0, hdaddr, DMA_MODE);

	/* stack 0 = CYLINDER */
	hdaddr->DMACMDS = bp->b_cylin;
	/* stack 1 = HEAD_ADDRESS | SECTOR_ADDRESS */
	hdaddr->DMACMDS = sec/st->nspt << 8 | sec%st->nspt + 1;
	/* stack 2 = SECTOR_COUNT | BUFFER_ADDRESS  */
	hdaddr->DMACMDS = len << 8 | xbp->hd_adapbuf;
	/* stack 3 = (ignored) | DMA_ADDRESS_HIGH */
	hdaddr->DMACMDS = ioaddr >> 16;
	/* stack 4 = DMA_ADDRESS_MIDDLE | DMA_ADDRESS_LOW */
	hdaddr->DMACMDS = ioaddr&0xffff;

	(void)hdwait(DDISKBUSY, 0, hdaddr, DMA_MODE);
	if (bp->b_flags & B_READ) {
		HDDEBUG(SHOW_ORDWR | SHOW_REGS,
			printf("HDDGO: STARTING READ CMD...\n"));
		HDDEBUG(SHOW_REGS, hdstatus(bp, 0, hdaddr, -1, 0, DMA_MODE));
		hdaddr->DMACMD = sp->hdc_bufmap[xbp->hd_adapbuf] << 8 |
				 DISKREAD | NORETRY(bp->b_dev);
	} else {
		HDDEBUG(SHOW_ORDWR | SHOW_REGS,
			printf("HDDGO: STARTING WRITE CMD...\n"));
		HDDEBUG(SHOW_REGS, hdstatus(bp, 0, hdaddr, -1, 0, DMA_MODE));
		hdaddr->DMACMD = sp->hdc_bufmap[xbp->hd_adapbuf] << 8 |
				 DISKWRIT | NORETRY(bp->b_dev);
	}

	dp->b_cylin = bp->b_cylin;	/* update current cylinder */
}


/*
 * Routine to remove a buffer structure from a device's queue
 */
hddbufdeq(dp, bp, err)
	register struct buf	*dp,
				*bp;
	register int		err;	/* complain if not found */
{
	register struct buf	*bp0;

	/*
	 * yank bp from device's queue
	 */
	if ((bp0 = dp->b_actf) == bp) {
		dp->b_actf = bp->b_actf;
		bp0 = NULL;
	} else {
		for (; bp0; bp0 = bp0->b_actf)
			if (bp0->b_actf == bp) {
				bp0->b_actf = bp->b_actf;
				break;
			}
		if (err && bp0 == NULL)	/* bp not found?! */
			printf("HD: HDDBUFDEQ: bp 0x%x not on dp 0x%x queue!\n",
			       bp, dp);
	}
	if (dp->b_actl == bp)
		dp->b_actl = bp0;
}


/*
 *	Controller Startup Routine:
 *	Arguments:
 *	  Pointer to controller structure
 *	Function:
 *	  Compute device-dependent parameters
 *	  Start up controller
 *	  Indicate request to i/o monitor routines
 */
hdstart(ic)
	register struct iocc_ctlr	*ic;
{
	register struct buf		*bp,
					*dp;
	register struct hdst		*st;
	register struct hddevice volatile	*hdaddr;
	register int			un;
	register struct iocc_device	*iod;
	register struct hdi_softc	*xbp;

	HDDEBUG(SHOW_DEQ, printf("hdstart(%x)\n", ic));
loop:
	/*
	 * Pull a device off the controller queue.
	 * If that device has work to do, do it;
	 * otherwise check the next device in the queue.
	 * Note that hdint takes care of rotating the controller between
	 * devices that need servicing.
	 */
	if ((dp = ic->ic_tab.b_actf) == NULL)
		return;
	if ((bp = dp->b_actf) == NULL) {
		ic->ic_tab.b_actf = dp->b_forw;
		goto loop;
	}

	xbp = (struct hdi_softc *)bp->b_actl;
	if (xbp == NULL) {
		xbp = hdxbget();
		if (xbp == NULL)
			return;
		bp->b_actl = (struct buf *)xbp;
	}

	xbp->hd_flag |= HD_ACTIVE;

	xbp->hd_addr = bp->b_un.b_addr;
	xbp->hd_cylin = bp->b_cylin;
	xbp->hd_blkno = bp->b_blkno;
	xbp->hd_bcount = bp->b_bcount;

	un = unit(bp->b_dev);
	HDDEBUG(SHOW_LED, DISPLAY(minor(bp->b_dev))); /* put into LEDs */
	st = &hdst[(iod=hddinfo[un])->iod_type]; /* get type */
	HDDEBUG(SHOW_DEQ, {
		printf("HD: HDSTART -- DEQ %s REQUEST",
		    (bp->b_flags & B_READ) ? "READ" : "WRITE");
		printf("\nblkno=%d bcount=%d baddr=0x%x",
		    bp->b_blkno, bp->b_bcount, bp->b_un.b_addr);
		printf(" bdev=0x%x", bp->b_dev);
		printf(" b_cylin=%d\n", bp->b_cylin);
	}
	);
	ic->ic_tab.b_active = 1;	  /* Mark controller active */
	hdaddr = (struct hddevice volatile *)ic->ic_addr;
	if (disable && hdactive == 0) {
		hdactive = ic;		  /* remember which one is active */
		hdaddr->HFR = DISKHD3|DISKIRQ12;	/* allow interrupts */
		DELAY(10);		  /* needed?? */
	}
	xbp->hd_physaddr = NO_ADDR; /* mark as not translated */
	HDDEBUG(SHOW_XFER, printf("HDSTART: starting %s\n",
	    (bp->b_flags & B_READ) ? "read" : "write"));

	if (iod->iod_dk >= 0) {
		dk_busy |= 1<<iod->iod_dk;
		dk_seek[iod->iod_dk] += (bp->b_cylin != dp->b_cylin);
		dk_xfer[iod->iod_dk]++;
		dk_wds[iod->iod_dk] += bp->b_bcount >> 6; /* 64 = pdp11 click */
	}
	(void)hdgo(hdaddr, st, bp->b_cylin, (int)bp->b_blkno, bp, (int)bp->b_bcount, xbp);
	hdc_softc[ic->ic_ctlr].hdc_timer = 0;
	return;
}


/*
 * Turn off the current controller (since there are no requests for it)
 * and turn on one with requests pending (ic1).
 */
hdswitch(ic1)
	register struct iocc_ctlr	*ic1;
{
#if NHDC > 1
	register struct iocc_ctlr	*ic2 = hdactive; /* active device */
	register struct hddevice volatile *hdaddr2 = (struct hddevice volatile *)ic2->ic_addr;
	register struct hddevice volatile *hdaddr1 = (struct hddevice volatile *)ic1->ic_addr;
	register struct buf		*cp;
	register int			i;

	HDDEBUG(SHOW_SWITCH, printf("hdswitch: off=0x%x on=0x%x\n",
				    hdaddr2, hdaddr1));
	hdaddr2->HFR = DISKDISI;	/* turn off old controller */
	DELAY(10);			/* needed? */
	i = hdaddr2->STAT;		/* pick up status (just in case) */
	DELAY(10);			/* needed? */
	i = hdaddr1->STAT;		/* pick up status (just in case) */
	i=i;				/* for lint & picky compilers */
	hdactive = 0;			/* nothing is active yet */
	if (ic2->ic_tab.b_actf == 0)
		hdwaiting = 0;		/* nobody can be waiting */
	else
		hdwaiting = ic2;	/* remember that it is waiting */
	cp = &ic1->ic_tab;
	if (cp->b_actf && cp->b_active == 0)
		hdstart(ic1);
#ifdef	HDASSERTIONS
	else
		printf("hdswitch: nothing to start??\n");
#endif	/* HDASSERTIONS */
#else	/* NHDC > 1 */
	panic("hdswitch");
#endif	/* NHDC > 1 */
}

/**********************************************************************/

/*
 * DMA channel release routine.
 * Called from dma.c.  Returns nonzero to tell dma.c that we won't (can't)
 * release the channel.
 */
/*ARGSUSED*/
int
hdchanrelse(channel)
	register short	channel;
{
	return(1);
}

/**********************************************************************/

/*
 *	Interrupt Routine:
 *	  Check completion status
 *	  Indicate completion to i/o monitor routines
 *	  log errors
 *	  start next request
 */
hdint(ctlr, irq)
{
	register struct buf		*bp,
					*dp;
	register struct iocc_ctlr	*ic;
	register struct hddevice volatile	*hdaddr;
	register int			un,
					status,
					error;
	register struct hdi_softc	*xbp;
	register struct hdc_softc	*sp;
	extern int			cold;

	if (cold) {
		if (irq == 12 || irq == 14) {	/* for us? */
			if (icprobed->ic_addr == NULL)
				panic("hdint addr");
			status = ((struct hddevice volatile *)icprobed->ic_addr)->STAT;	/* clear interrupt */
		}
		HDDEBUG(SHOW_INIT|SHOW_INTR,
			printf("HDINT: COLD!=0 - INT. IGNORED\n"));
		return(1);
	}
	/* get proper controller */
	ic = hdminfo[ctlr];
#ifdef	HDASSERTIONS
	if (!ic)
		panic("hdint ic=0");
#endif	/* HDASSERTIONS */
	hdaddr = (struct hddevice volatile *)ic->ic_addr;

	sp = &hdc_softc[ctlr];
	if (sp->hdc_flag & DMA_MODE)
		return(hddint(sp, ic, hdaddr));
 
	if (irq == 12 && !(hdaddr->INTR & 0x80)) {
		/* if new adapter (shared interrupts on 12) */
		/* note int status reg NOT trustworthy if cold */
		HDDEBUG(SHOW_INTR, printf("HDINT: NOT OURS - INT. PASSED\n"));
		return(1);
	}

	/* get status & clear interrupt */
	status = hdaddr->STAT & DISKIMASK;

	if (ic->ic_tab.b_active == 0) {
#if defined(HDASSERTIONS) || defined(DEBUG)
#ifdef	DEBUG
		if (hddebug & SHOW_INTR ||
#else
		if (
#endif	/* DEBUG */
#ifdef	HDASSERTIONS
			!disable) printf("HDINT: NO I/O IN PROGRESS!!\n");
#else
			0) printf("HDINT: NO I/O IN PROGRESS!!\n");
#endif	/* HDASSERTIONS */
#endif	/* HDASSERTIONS || DEBUG */
		return(1);
	}

	dp = ic->ic_tab.b_actf;
	bp = dp->b_actf;
	un = unit(bp->b_dev);
	xbp = (struct hdi_softc *)bp->b_actl;

	if (status & DISKERR) {

		error = hdaddr->ERR;
		if ((error & (DISKBBLK|DISKIDNF)) && (xbp->hd_flag & HD_FORWARD) == 0 && hdbadblk(bp, xbp, hdaddr))
			goto done;

		if (++xbp->hd_errcnt <= HDRETRIES) {
			HDDEBUG(SHOW_RETRY,{
				printf("HD: DISK I/O ERROR ... RETRYING\n");
				hdstatus(bp, error, hdaddr, status, 1, !DMA_MODE); 
			});
			if (xbp->hd_errcnt % (HDRETRIES/2)  == 0) {
				HDDEBUG(SHOW_RETRY, printf("RESETTING DRIVE\n"));
				hdreset(hddinfo[un], hdaddr, NOISY, !DMA_MODE);
			}
			hdstart(ic);	/* retry it */
			goto done;
		}
		printf("HD: Hard Disk I/O Error\n");
		hdstatus(bp, error, hdaddr, status, 1, !DMA_MODE);

		bp->b_flags |= B_ERROR;
	} else if (status & DISKBUSY) {
#ifdef	HDASSERTIONS
		printf("HDINT: DISK BUSY!? - INT. SWALLOWED(choke)\n");
#else
		HDDEBUG(SHOW_INTR,
			printf("HDINT: DISK BUSY!? - INT. SWALLOWED(choke)\n"));
#endif	/* HDASSERTIONS */
		goto done;
	} else if (status & DISKWFLT &&
		   hdst[hddinfo[un]->iod_type].off != hd40r_sizes) {
		/* hd40r treats recoverable errors as write-faults */
		printf("HD: Write Fault\n");
		hdstatus(bp, (int) hdaddr->ERR, hdaddr, status, 1, !DMA_MODE);
		bp->b_flags |= B_ERROR;
	} else if (status & DISKDRQ) {
		if (bp->b_flags & B_READ) {
			HDDEBUG(SHOW_INTR, {
				printf("HDINT: DRQ INTERRUPT --> (READ)");
				printf(" b_addr=0x%x\n", xbp->hd_addr);
				hdstatus(bp, 0, hdaddr, status, 1, !DMA_MODE);
			});
			hdunload(bp, xbp, hdaddr);
			xbp->hd_blkno++;	/* current block number */
			xbp->hd_bcount -= NBPS(st);	/* amount left to xfer */
			if (!(xbp->hd_flag & HD_FORWARD) &&
			    xbp->hd_addr < (bp->b_un.b_addr + bp->b_bcount))
				goto done;
			HDDEBUG(SHOW_ORDWR, printf("HDINT: READ REQUEST COMPLETE !!\n"));
		} else {
			HDDEBUG(SHOW_INTR, {
				printf("HDINT: DRQ INTERRUPT --> (WRITE)");
				printf(" b_addr=0x%x\n", xbp->hd_addr);
				hdstatus(bp, 0, hdaddr, status, 1, !DMA_MODE);
			});
			hdunload(bp, xbp, hdaddr);
			xbp->hd_blkno++;	/* current block number */
			xbp->hd_bcount -= NBPS(st);	/* amount left to xfer */
			goto done;
		}
	} else if (status & DISKRDY) {
		if (bp->b_flags & B_READ) {
#ifdef	HDASSERTIONS
			printf("HDINT: NO WRITE IN PROGRESS - INT. SWALLOWED(choke)\n");
#else
			HDDEBUG(SHOW_INTR,
				printf("HDINT: NO WRITE IN PROGRESS - INT. SWALLOWED(choke)\n"));
#endif	/* HDASSERTIONS */
			goto done;
		} else {
			xbp->hd_blkno++;	/* for err msgs & write forward case */
			xbp->hd_bcount -= NBPS(st);
			if (xbp->hd_bcount > 0 && !(xbp->hd_flag & HD_FORWARD)) {
#ifdef	HDASSERTIONS
				printf("HDINT: NO DRQ (0x%x, 0x%x) - INT. TREATED AS WRITE REQUEST COMPLETE!!\n", xbp->hd_bcount, xbp->hd_flag);
#else
				HDDEBUG(SHOW_INTR|SHOW_ORDWR,
					printf("HDINT: NO DRQ (0x%x, 0x%x) - INT. TREATED AS WRITE REQUEST COMPLETE!!\n", xbp->hd_bcount, xbp->hd_flag));
#endif	/* HDASSERTIONS */
			} else
				HDDEBUG((SHOW_ORDWR|SHOW_INTR), printf("HDINT: WRITE REQUEST COMPLETE !!\n"));
		}
	} else {
#ifdef	HDASSERTIONS
		printf("HDINT: NO STATUS - INT. IGNORED\n");
#else
		HDDEBUG(SHOW_INTR, printf("HDINT: NO STATUS - INT. IGNORED\n"));
#endif	/* HDASSERTIONS */
		return(1);
	}
	/*
	 * operation appears complete - check to see if we are in
	 * the middle of a bad block forward operation
	 * if so then restart the rest of the original operation.
	 */
	if (xbp->hd_flag & HD_FORWARD) {
		register struct hdst *st = &hdst[hddinfo[un]->iod_type];

		xbp->hd_flag &= ~HD_FORWARD; /* no longer fowarding */
		HDDEBUG(SHOW_BAD, printf("hdgo(%d,%d,%d)\n", xbp->hd_cylin,
					 xbp->hd_blkno, xbp->hd_bcount));
		(void)hdgo(hdaddr, st, xbp->hd_cylin, (int)xbp->hd_blkno, bp,
			   (int)xbp->hd_bcount, xbp);
	} else 
		hddone(ic, dp, bp, xbp, un);
done:
	sp->hdc_timer = 0;
	return(0);
}


/*
 * Flag current non-dma request as complete
 */
hddone(ic, dp, bp, xbp, un)
	register struct iocc_ctlr	*ic;
	register struct buf		*dp,
					*bp;
	register struct hdi_softc	*xbp;
	register int			un;
{
	register struct iocc_device	*iod;

	ic->ic_tab.b_active = 0;
	ic->ic_tab.b_actf = dp->b_forw;	/* rotate to next drive */
	dp->b_active = 0;
	dp->b_actf = bp->av_forw;
	bp->b_resid = 0;	  /* XXX *//* Error recovery! */
	bp->b_actl = NULL;
	hdxbrel(xbp);
	iodone(bp);

	iod = hddinfo[un];
	if (iod->iod_dk >= 0)
		dk_busy &= ~(1<<iod->iod_dk);
	/*
	 * If more work to do on this drive, restart it.
	 */
	(void) hdustart(iod);
	/*
	 * If there are devices ready to transfer, start the controller.
	 * Otherwise, if the other (old) controller is waiting for a chance
	 * then start it. (The other controller can suffer from starvation
	 * if this controller's queue takes a long time to empty.)
	 */
	if (ic->ic_tab.b_actf)
		hdstart(ic);
	else if (disable && hdwaiting != 0)
		(void) hdswitch(hdwaiting);
}


/*
 *	DMA Interrupt Routine:
 *	  Check completion status
 *	  Indicate completion to i/o monitor routines
 *	  log errors
 *	  start next request(s)
 */
hddint(sp, ic, hdaddr)
	register struct hdc_softc	*sp;
	register struct iocc_ctlr	*ic;
	register struct hddevice volatile	*hdaddr;
{
	register u_int			un,
					dq;	/* packed drive# & queue# */
	register struct buf		*bp, *dp;
	register struct hdi_softc	*xbp;
	register struct iocc_device	*iod;
	register int			status,
					error,
					len;
	u_char				ec;
	register struct hdst 		*st; 

	if (!(hdaddr->INTR & 0x80)) {
		HDDEBUG(SHOW_INTR, printf("HD: Int. Not Ours - Ignored\n"));
		return(1);
	}

	/* hardware status */
	status = hdaddr->DMASTAT;	/* we leave this byte-swapped */
	/* use un as scratch to get error un-byte-swapped */
	un = hdaddr->DMAERRA;
	error = (un&0xff)<<8 | un>>8;
	error <<= 16;
	un = hdaddr->DMAERRB;
	error |= (un&0xff)<<8 | un>>8;
	ec = *(u_char *)&hdaddr->DMASTAT;	/* clear interrupt */

	trace(TR_D_INT, error, ((caddr_t)hdaddr == hdstd[1])<<31 | status);

	if (ic->ic_tab.b_active == 0) {
		printf("HD: BAD INTERRUPT! ADAPTER NOT ACTIVE!\n");
		hdstatus((struct buf *)0, error, hdaddr, status, 0, DMA_MODE);
		return(0);	/* must be our int - but why for? */
	}

	dq = (status&(DDISKDRIVE|DDISKQPOS));

	if ((dq&DDISKDRIVE)>>QSHIFT >= MAX_HESDI_DRIVES) { /* verify drive # */
		printf("HD: BAD INTERRUPT! NO SUCH DRIVE!\n");
		hdstatus((struct buf *)0, error, hdaddr, status, 0, DMA_MODE);
		return(0);	/* must be our int - but why for? */
	}

	bp = sp->hdc_cmdq[dq];

	if (bp == NULL) {
		printf("HD: BAD INTERRUPT! DRIVE/QUEUE NOT ACTIVE!\n");
		hdstatus((struct buf *)0, error, hdaddr, status, 0, DMA_MODE);
		return(0);	/* must be our int - but why for? */
	}

	un = unit(bp->b_dev);
	dp = &hdutab[un];
	xbp = (struct hdi_softc *)bp->b_actl;

	if (!(status & DDISKVALID)) {
		printf("HD: BAD INTERRUPT! INVALID STATUS!\n");
		hdstatus(bp, error, hdaddr, status, 0, DMA_MODE);
		return(0);
	}
	if (status & DDISKCORR) {
		HDDEBUG(SHOW_INTR, {
			printf("HD: Corrected Data (Informational)\n");
			hdstatus(bp, error, hdaddr, status, 0, DMA_MODE);
		});
		sp->hdc_timer = 0;
		return(0);
	}

	iod = hddinfo[un];
	st = &hdst[iod->iod_type]; 

	if ((ec = (status & DDISKERRCODE) >> 8) == DISKNOERR) {
		hddunstart(ic, bp);		/* hddstart clean up */
		if (xbp->hd_errcnt < 0) {
			xbp->hd_errcnt = 1 - xbp->hd_errcnt;
			HDDEBUG(SHOW_RETRY, {
				printf("DRIVE RESET OK... RETRYING TRANSFER\n");
				hdstatus(bp, error, hdaddr,
					 status, 0, DMA_MODE);
			});
			hddstart(ic);
			return(0);
		} else if (PARTIAL(bp)) {
			if (bp->b_flags & B_READ)
				hddrdpartial(bp, xbp);
			/* all done! (partial always last sector transfered) */
		} else {
			bp->b_un.b_addr += bp->b_bcount;
			len = bp->b_un.b_addr - xbp->hd_addr;	/* bytes done */
			if ((bp->b_bcount = xbp->hd_bcount - len) > 0) {
				register u_int	spc = st->nspc;
				register u_int	blks = len/NBPS(st);
				struct buf	b;	/* fake buffer */
	
				/*
				 * Xfer is incomplete - requeue to complete it.
				 */
				hddbufrep(dp, bp, &b);	/* fool disksort */
				bp->b_blkno = xbp->hd_blkno + blks;
				bp->b_cylin = xbp->hd_cylin +
					      (xbp->hd_blkno%spc + blks)/spc;
				disksort(dp, bp);	/* requeue */
				hddbufdeq(dp, &b, NOISY); /* dequeue fake buf */

				goto rotate;
			}
			HDDEBUG(SHOW_TEXT, {
				if ((bp->b_flags & (B_PGIN|B_PHYS|B_READ)) == (B_PGIN|B_PHYS|B_READ) && len >= NBPG)
					if (ck_text(bp->b_proc, (char *)(bp->b_un.b_addr) - NBPG, "hddint")) {
						u_int pg;

						for (pg = 0; pg < 8; pg++)
							printf("tcw(%d,%d)<<11 = 0x%x\n", ic->ic_dmachannel, pg, TCW(ic->ic_dmachannel, pg)<<11);
					}
			});
		}
	} else {
		if (xbp->hd_errcnt < 0) {
			printf("HD: restore during error retry failed!\n");
		} else switch (ec) {

		case DISKIDNF:	/* ID NOT FOUND error */
		case DISKMAM:	/* data ADDRESS MARK NOT FOUND error */
		case DISKTR00:	/* TRACK 0 error */
		case DISKCRC:	/* UNCORRECTABLE DATA ECC ERROR */
		case DISKWRTF:	/* WRITE FAULT error */
		case DISKBBLK:	/* BAD BLOCK detected */
			if (hddbadblk(ic, bp, error, dp, un)) {
				hddstart(ic);
				return(0);
			}
			if (ec == DISKBBLK)
				break;
			/* fall thru to retry... */
		case DISKABRT:	/* ABORTED COMMAND error */
			if (xbp->hd_errcnt < HDRETRIES) {
				if (xbp->hd_errcnt % (HDRETRIES/2) ==
				    HDRETRIES/2 - 1) {
					/*
					 * We use a negative error count as a
					 * flag indicating that we just reset.
					 */
					xbp->hd_errcnt = -xbp->hd_errcnt;
					HDDEBUG(SHOW_RETRY, {
						printf("HD: DISK I/O ERROR ... RESETTING DRIVE\n");
						hdstatus(bp, error, hdaddr, status, 0, DMA_MODE);
					});
					if (sp->hdc_ulevel >= SETPUCODELEVEL) {
						hdaddr->DMACMDS = st->ntpc-1 << 8 | st->nspt;
						hdaddr->DMACMD = dq << 8 |
								 DISKRSTP;
					} else
						hdaddr->DMACMD = dq << 8 |
								 DISKRSTR;
					sp->hdc_timer = 0;
				} else {
					xbp->hd_errcnt++;
					hddunstart(ic, bp);
					HDDEBUG(SHOW_RETRY,{
						printf("HD: DISK I/O ERROR ... RETRYING\n");
						hdstatus(bp, error, hdaddr, status, 0, DMA_MODE);
					});
					hddstart(ic);	/* retry it */
				}
				return(0);
			}
			break;
		default:
		case DISKADAP:	/* ADAPTER error (means what?) */
		case DISKWRTP:	/* WRITE PARITY */
		case DISKBBLKA:	/* BAD BLOCK detected "ALTERNATE" */
		case DISKINVC:	/* INVALID COMMAND */
			printf("HD: BAD INTERRUPT? UNEXPECTED STATUS!\n");
			break;	/* possibly we can recover... */
		}
		hddunstart(ic, bp);		/* hddstart clean up */
		printf("HD: Hard Disk I/O Error\n");
		hdstatus(bp, error, hdaddr, status, 0, DMA_MODE);
		bp->b_flags |= B_ERROR;
	}
	HDDEBUG(SHOW_WRITECHECK, {
		if (!(bp->b_flags & B_READ))
			hddwcheck(bp, xbp->hd_ioaddr, 0, 0);
	});
	hdddone(dp, bp, xbp);
rotate: /* rotate to next drive */
	hdddevdeq(ic, dp);	/* dequeue drive */
	(void) hdustart(iod);	/* requeue drive */

	hddstart(ic);
	return(0);
}


/*
 * Flag current dma request as complete
 */
hdddone(dp, bp, xbp)
	register struct buf		*dp,
					*bp;
	register struct hdi_softc	*xbp;
{
	hddbufdeq(dp, bp, NOISY);	/* dequeue bp */
	hddunforward(bp, xbp);		/* restore saved bp fields */
	bp->b_resid = 0;		/* XXX *//* Error recovery! */
	iodone(bp);			/* wakeup hdstrategy caller */
}


/*
 * Undo hddstart() stuff.
 */
hddunstart(ic, bp)
	register struct iocc_ctlr	*ic;
	register struct buf		*bp;
{
	register struct hdi_softc	*xbp = (struct hdi_softc *)bp->b_actl;
	register struct iocc_device	*iod = hddinfo[unit(bp->b_dev)];
	register struct hdc_softc	*sp = &hdc_softc[ic->ic_ctlr];
	register u_char			dq;

	if (iod->iod_dk >= 0)
		dk_busy &= ~(1<<iod->iod_dk);
	if (dma_free_map(ic->ic_dmachannel, xbp->hd_ioaddr, bp->b_bcount) !=
	    DMA_OK_RET)
		printf("HD: HDDUNSTART: dma_free_map FAILED!!\n");
	ic->ic_tab.b_active--;
	xbp->hd_flag &= ~HD_ACTIVE;
	dq = sp->hdc_bufmap[xbp->hd_adapbuf];
	hddreladapbuf(sp, (u_int)xbp->hd_adapbuf);
	if (xbp->hd_flag & HD_PHYS) {
		xbp->hd_flag &= ~HD_PHYS;
		bp->b_flags |= B_PHYS;
	}
	sp->hdc_cmdq[dq] = NULL;	/* release command q slot */
	HDDEBUG(SHOW_DCMD, printf("HDDUNSTART: release cmdq[%d]\n", dq));
}


/*
 * This function copies partial sectors (< 512 bytes) just read into our
 * local buffers (hdd_pbuf array) out to their final destination.  The
 * point is that we copy out only that part of the sector which was
 * requested, not the entire sector which hardware forced us to read.
 */
hddrdpartial(bp, xbp)
	register struct buf		*bp;
	register struct hdi_softc	*xbp;
{
	register int		i = xbp->hd_bcount % NBPS(st);
	register caddr_t	vto = xbp->hd_addr + xbp->hd_bcount - i;

	HDDEBUG(SHOW_PAGET, {
		if (bp->b_flags & B_PAGET) {
			prhex(bp->b_un.b_addr, 8);
			printf("read vto=0x%x b_addr=0x%x\n", vto, bp->b_un.b_addr);
		}
	});
	if (!(bp->b_flags & B_PHYS)) {
		bcopy(bp->b_un.b_addr, vto, i);
	} else {
		/*
		 * Copy out partial sector just read in
		 */
		register caddr_t	to, to2;
		register int		s, v;
		register u_int		n;

		to = real_buf_addr(bp, vto);
		n = PGOFSET + 1 - ((u_int)to & PGOFSET);
		if (n >= i) {
			s = spl_high();
			GET_VR0(v);
			bcopy(bp->b_un.b_addr, to, i);
		} else {
			to2 = real_buf_addr(bp, vto + n);
			s = spl_high();
			GET_VR0(v);
			bcopy(bp->b_un.b_addr, to, n);
			bcopy(bp->b_un.b_addr + n, to2, i - n);
		}
		SET_VR(v);
		splx(s);
	}
}


/*
 * Replace a buffer on a device queue with a placeholder buffer.
 * This is done so that we can change our buffer b_cylin and
 * reinsert it into the queue without screwing anything up.
 * (The less palatable alternatives:
 *	- Delete the buffer and reinsert it.  This would make us
 *	take an extra scan across the disk before the xfer is
 *	completed (if our buffer is the last of a scan and we are
 *	increasing its b_cylin OR if we are leaving b_cylin
 *	unchanged and it is not the last of a scan).
 *	- Just change the buffer's b_cylin in place.  This is bad
 *	because raising the b_cylin will make it look like the last
 *	of a scan, screwing up subsequent buffer queue-insertions.
 * If this doesnt make sense, grok disksort and then reread this.)
 */
hddbufrep(dp, bp, fakebp)
	register struct buf	*dp,
				*bp,
				*fakebp;
{
	register struct buf	*bp0;

	if ((bp0 = dp->b_actf) == bp)
		dp->b_actf = fakebp;
	else {
		for (; bp0; bp0 = bp0->b_actf)
			if (bp0->b_actf == bp) {
				bp0->b_actf = fakebp;
				break;
			}
		if (bp0 == NULL)
			return;
	}
	if (dp->b_actl == bp)
		dp->b_actl = fakebp;
	fakebp->b_actf = bp->b_actf;
	/*
	 * now copy fields that disksort uses to order buffers (just cylin)
	 */
	fakebp->b_cylin = bp->b_cylin;
}


/*
 * Dma bad block forwarding routine.
 */
/*ARGSUSED*/
hddbadblk(ic, bp, hsc, dp, un)
	register struct iocc_ctlr	*ic;
	register struct buf		*bp;
	int				hsc; /* packed Head/Sector/Cylinder */
	struct buf			*dp;
	u_int				un;
{
	register int			blknew, blklo, blkhi, nb;
	register struct hdi_softc	*xbp = (struct hdi_softc *)bp->b_actl;
	struct iocc_device		*iod = hddinfo[un];
	register struct hdst 		*st = &hdst[iod->iod_type]; 
	struct hdbad			*hdb = hdbad[un];
	int				i;
	struct buf			b;	/* fake buffer */

	/*
	 * On multisector xfers adapter often reports error on wrong sector.  It
	 * usually will report error on some sector preceeding the actual bad
	 * one, but sometimes will report error on a sector following those we
	 * tried to transfer!  So we ignore our "hsc" parameter.  Fortunately
	 * bad blocks are very rare due to the hidden defect scheme...
	 */

	/*
	 * Initialize:
	 *	nb = number of blocks being transferred
	 *	blklo = absolute block # of 1st block in current transfer
	 *	blkhi = absolute block # of last block being transferred + 1
	 *	blknew = 0 (means no bad block identified yet)
	 */
	nb = bp->b_bcount / NBPS(st);
	if (nb < 1)
		nb = 1;
	blklo = bp->b_blkno%st->nspc + bp->b_cylin*st->nspc;
	blkhi = blklo + nb;
	blknew = 0;

	/* do we have a valid bad block table? */
	if (hdb != 0 && BAD_BLOCK_TABLE_OK(hdb)) {
		register u_int		blkbad;

		/*
		 * So bad block table is valid.  Now scan it to
		 * make blknew the replacement block for the first
		 * (known) bad block in range blklo...blkhi-1.
		 */
		for (i = 0; i < hdb->hdcount; ++i) {
			blkbad = hdb->hdmap[i].hdbad;
			if (blkbad >= blklo && blkbad < blkhi) {
				blkhi = blkbad;
				blknew = hdb->hdmap[i].hdgood;
			}
		}
	}
	if (blknew == 0) {	/* very rare case - need not optimize */
		/*
		 * No (known) bad block in transfer.  This means we have
		 * some other (ie serious) sort of error.  If we are only
		 * transferring one block then we can trust the hardware
		 * and assume that that one has the error.
		 */
		if (nb == 1)
			return(0);	/* error - did not remap a bad block */
		/*
		 * Got N (>1) blocks in transfer.  We'll start a one block
		 * transfer of the first block.  If that fails then we've 
		 * identified the erroneous block - we're done.  If it succeeds
		 * then code in hddint() will try to complete the transfer by
		 * doing the last N-1 blocks.  But that transfer will fail,
		 * bringing us back here to do a 1 block transfer of the 2nd
		 * block...  Note: frequency of non-bad-block errors does *not*
		 * justify binary search.
		 */
		blkhi = blklo + 1;
	}
	/*
	 * Must clean up *before* changing fields in bp.
	 * (And *after* we know we'll return a 1.)
	 */
	hddunstart(ic, bp);	/* hddstart clean up */

	if (blkhi == blklo) { /* 1st block bad - xfer its replacement */
		/* follow chains of bad blocks */
		while ((i = hdmapbad(hdb, blknew)) != 0)
			blknew = i;
		bp->b_bcount = NBPS(st);
	} else {	/* 1st block ok - xfer up to bad block */
		blknew = blklo;
		bp->b_bcount = (blkhi - blklo) * NBPS(st);
	}

	hddbufrep(dp, bp, &b);		/* fool disksort */

	/* blknew is now absolute bn we want to start an xfer at */
	bp->b_cylin = blknew / st->nspc;
	bp->b_blkno += blknew - blklo;	/* keep it relative to partition */
	if (!PARTIAL(bp)) {	/* (PARTIAL b_addr to be figured in hddstart) */
		bp->b_un.b_addr = xbp->hd_addr + (blklo - (xbp->hd_cylin*st->nspc + xbp->hd_blkno%st->nspc)) * NBPS(st);	/* (trust me) */
#ifdef	HDASSERTIONS
		if (bp->b_un.b_addr < xbp->hd_addr ||
		    bp->b_un.b_addr >= xbp->hd_addr + xbp->hd_bcount)
			panic("hddbadblk");
#endif	/* HDASSERTIONS */
	}

	disksort(dp, bp);		/* requeue bp */
	hddbufdeq(dp, &b, NOISY);	/* dequeue buf used to fool disksort */

	return(1);	/* success (or block in error not yet identified) */
}


/*
 * Look up "blkno" in the bad block table and return the block that it is
 * mapped to, or 0 if it cannot be found or the table is not valid.
 */
hdmapbad(hdb, blkno)
	register struct hdbad	*hdb;
	register int		blkno;
{
	register u_int		i,
				newblkno = 0;

	if (hdb == 0 || !BAD_BLOCK_TABLE_OK(hdb)) {
		return(0);		  /* bad defect table - don't use it */
	}
	for (i = 0; i < hdb->hdcount; ++i)
		if (hdb->hdmap[i].hdbad == blkno) {
			newblkno = hdb->hdmap[i].hdgood;
			break;
		}
	return(newblkno);
}


/*
 * Dequeue a device from its controller's device chain.
 */
hdddevdeq(ic, dp)
	struct iocc_ctlr	*ic;
	register struct buf	*dp;
{
	register struct buf	*dp0;
	/*
	 * yank dp from controller's queue
	 */
	dp->b_active = 0;
	if ((dp0 = ic->ic_tab.b_actf) == dp) {
		ic->ic_tab.b_actf = dp->b_forw;
		dp0 = NULL;
	} else {
		for (; dp0; dp0 = dp0->b_forw)
			if (dp0->b_forw == dp) {
				dp0->b_forw = dp->b_forw;
				break;
			}
		if (dp0 == NULL)
			printf("HD: HDDDEVDEQ: dp 0x%x not on ic 0x%x queue!\n",
				dp, ic);
	}
	if (ic->ic_tab.b_actl == dp)
		ic->ic_tab.b_actl = dp0;
}


/*
 * bad block forwarding routine
 * 1. calculate actual block number from cylin and blkno
 * 2. look this up in the bad block forwarding table
 * 3. start a 1 block transfer on the replacement block
 * 4. calculate the cyl and block info for the next block after
 *	this one
 * result values:
 *	0	bad block is not in bad block table
 *	!= 0	worked (operation started on forwarded block)
 */

hdbadblk(bp, xbp, hdaddr)
	register struct buf		*bp;
	register struct hdi_softc	*xbp;
	register struct hddevice volatile	*hdaddr;
{
	register int			un = unit(bp->b_dev);
	register struct hdbad		*hdb = hdbad[un];
	register struct hdst		*st = &hdst[hddinfo[un]->iod_type];
/*
 * bp->b_cylin contains the start cylinder (absolute)
 * bp->b_blkno contains the start block (relative)
 * 	we will calculate the partition offset by undoing the original
 *	b_cylin calculation.
 */
	register int blkno = (bp->b_cylin - bp->b_blkno / st->nspc) * st->nspc + xbp->hd_blkno;
	register int i;
	register int newblkno;		  /* replacement block number */

	HDDEBUG(SHOW_BAD, printf("hd%d: bad block %d\n", un, blkno));
	newblkno = hdmapbad(hdb, blkno);
	if (newblkno == 0)
		return(0);		  /* did not find it */
	while ((i = hdmapbad(hdb, newblkno)) != 0)
		newblkno = i;		/* handle double mapped case */
	HDDEBUG(SHOW_BAD, printf("hd%d: bad block %d forwarded to %d\n",
	    un, blkno, newblkno));

/*
 * If there is more data to transfer after this sector,
 * set a flag to remember that we are doing a bad-block-forward
 * and adjust the starting cylinder that hdint will use.
 * The starting sector (in xbp->hd_blkno) still points to the bad sector
 * and will be bumped on entry to hdint, so we don't touch it here.
 */
	if (xbp->hd_bcount > NBPS(st)) {
		xbp->hd_flag |= HD_FORWARD; /* mark as doing forward */
		xbp->hd_cylin = (blkno + 1) / st->nspc;
	}
/*
 * in case of write we have to back off the transfer that loaded the
 * adapter ram so that we can load it again from the proper place.
 * we will mark the real address as invalid (since that is easier
 * than calculating if we really need to or not).
 * the performance penalty of this is small compared to the time taken
 * for the two seeks to pick up the replacment bad block.
 */
	if ((bp->b_flags & B_READ) == 0) {
		xbp->hd_addr -= (xbp->hd_addr - bp->b_un.b_addr - 1)%NBPS(st)
				+ 1;	/* wasn't actually xfered */
		/* kill the real memory address (just in case) */
		xbp->hd_physaddr = NO_ADDR;
	}
	HDDEBUG(SHOW_BAD, {
		printf("addr=0x%x next xfr: absolute cyl %d relative sec %d \n",
		       xbp->hd_addr, xbp->hd_cylin, xbp->hd_blkno);
		hdstatus(bp, 0, hdaddr, -1, 1, !DMA_MODE);
	}
	);

/*
 * break new block number into cyl and block number and start the
 * transfer
 */
	(void)hdgo(hdaddr, st, (long)(newblkno/st->nspc),
		   (int)(newblkno % st->nspc), bp, NBPS(st), xbp);
	return(1);
}

/**********************************************************************/

/*
 * Watchdog timer - checks to see if we've lost an interrupt by having timer
 * click HDTIMEOUT times without being cleared (by int or start routine)
 */
/*ARGSUSED*/
hdwatch(reg)
	caddr_t				reg;
{
	register struct hdc_softc	*sp;
	register struct iocc_ctlr	*ic;
	register int			i,
					s = spl_disk();

	for (i = 0; i < NHDC; ++i) {
		sp = &hdc_softc[i];
		if ((ic = hdminfo[i]) == 0 || ic->ic_alive == 0 ||
		    ic->ic_tab.b_active == 0) {
			sp->hdc_timer = 0;	/* not doing anything */
		} else if (++sp->hdc_timer > HDTIMEOUT) {
			register struct buf	*dp = ic->ic_tab.b_actf;
			register int		dma = sp->hdc_flag & DMA_MODE;

			sp->hdc_timer = 0;
			if (dp != NULL  && (dma || dp->b_actf != NULL))
				hdhung(i, ic, sp, dp, dma);
		}
	}
	timeout(hdwatch, (caddr_t)0, hz);	/* check again in 1 second */
	splx(s);
}


/*
 * This half of the watch routine is called only after we're "sure"
 * we have a hung controller.
 */
hdhung(ctlr, ic, sp, dp, dma)
	int				ctlr;
	register struct iocc_ctlr	*ic;
	register struct hdc_softc	*sp;
	register struct buf		*dp;
	int				dma;
{
	register struct hddevice volatile *hdaddr = (struct hddevice volatile *)ic->ic_addr;
	register struct buf		*bp;
	register struct hdi_softc	*xbp;
	register int			status,
					error;

	HDDEBUG(SHOW_LOST, hddbreak((int)ctlr, (int)ic, (int)sp, (int)dp));
	printf("hdc%d: lost interrupt(s)\n", ctlr);
	(void)hdwait(DISKBUSY, 0, hdaddr, dma);
	if (dma) {
		register u_int			dr,
						q;

		status = hdaddr->DMASTAT;
		/* use dr as scratch to get error un-byte-swapped */
		dr = hdaddr->DMAERRA;
		error = ((dr&0xff)<<8 | dr>>8) << 16;
		dr = hdaddr->DMAERRB;
		error |= (dr&0xff)<<8 | dr>>8;
		/*
		 * For all active transfers:
		 *	0) Print status info so we know what was active
		 *	1) Clean up (call hddunstart)
		 * 	1) Ensure that it is on its device's queue
		 *	2) Bump its hang count
		 *	3) Kill it if its hang count exceeds HDHANGMAX
		 */
		for (dr = 0; dr < sp->hdc_slaves; dr++)
			for (q = 0; q < MAX_QDEPTH; q++) {
				bp = sp->hdc_cmdq[PACK_DR_Q(dr, q)];
				if (bp != NULL) {
					hdstatus(bp, error, hdaddr, status,
						 0, DMA_MODE);
					hddunstart(ic, bp);
					hddbufdeq(dp, bp, !NOISY);
					disksort(dp, bp);
                			xbp = (struct hdi_softc *)bp->b_actl;
					if (++xbp->hd_hangcnt > HDHANGMAX) {
						bp->b_flags |= B_ERROR;
						printf("HD: WARNING: above transfer not completed!\n");
						hdddone(dp, bp, xbp);
					}
				}
			}
		(void)hddshutdown(ic, "hdwatch");	/* go to PIO mode */
		sp->hdc_flag &= ~DMA_MODE;
		hdresetall(hdaddr);			/* RESTORE each drive */
		hddmode(hdaddr);			/* back to DMA mode */
		sp->hdc_flag |= DMA_MODE;
		hddstart(ic);
	} else {
		register int	un;

		bp = dp->b_actf;
		error = hdaddr->ERR;
		status = hdaddr->STAT;
		hdstatus(bp, error, hdaddr, status, 1, !DMA_MODE);
		un = unit(bp->b_dev);
		if (esdi_i_bug &&
		    bp->b_flags & B_READ &&
		    strcmp("hd70r", hdtypes[hddinfo[un]->iod_type]) == 0 &&
		    error == DISKIDNF &&
		    (status&DISKIMASK) == (DISKRDY|DISKDRQ|DISKERR)) {
			/*
			 * Bad spot in id on an hd70r can make ESDI card hang.
			 * This has only been encountered on reads of hd70r and
			 * always it has looked like the card meant to give us
			 * an id-not-found but just forgot to interrupt.  So we
			 * make believe...
			 */
			HDDEBUG(SHOW_LOST,
				printf("hdhung: faking the interrupt\n"));
			if (hdint(ctlr, 14))
				panic("hdhung fakeint");
			return;
		}
		hdaddr->CMD = DISKDIAG;
		delay(5000);			/* XXX */
		(void)hddiagstat(ic, "hdwatch");
		hdresetall(hdaddr);		/* RESTORE each drive */
               	xbp = (struct hdi_softc *)bp->b_actl;
		if (++xbp->hd_hangcnt > HDHANGMAX) {
			bp->b_flags |= B_ERROR;
			printf("HD: WARNING: above transfer not completed!\n");
			hddone(ic, dp, bp, xbp, un);
		} else {
			/* 
			 * Restart transfer from the top.  Xfer is
			 * reinitialized except for hd_hangcnt.
			 */
			xbp->hd_flag = 0;
			xbp->hd_errcnt = 0;
			xbp->hd_addr = bp->b_un.b_addr;
			xbp->hd_cylin = bp->b_cylin;
			xbp->hd_blkno = bp->b_blkno;
			xbp->hd_bcount = bp->b_bcount;
			hdstart(ic);
		}
	}
}


/*
 * Reset all of an adapter's drives.
 */
hdresetall(hdaddr)
	register struct hddevice volatile	*hdaddr;
{
	register int	un;

	for (un = 0; un < NHD; un++)
		if (hddinfo[un] &&
		    hddinfo[un]->iod_addr == (caddr_t)hdaddr)
			hdreset(hddinfo[un], hdaddr, NOISY, !DMA_MODE);
}


/*
 * Shut down adapter from DMA mode to PIO mode.
 */
int
hddshutdown(ic, caller)
	register struct iocc_ctlr	*ic;	/* controller */
	register char			*caller;
{
	register struct hddevice volatile *hdaddr = (struct hddevice volatile *)ic->ic_addr;

	if (hesdi_s_bug) {
		/*
		 * Microcode bug makes adapter hang on a shutdown
		 * if there are any outstanding transfers.
		 */
		while (hdaddr->DMASTAT & DDISKBUSY) {
			if (hdwait(DDISKBUSY, 0, hdaddr, DMA_MODE) == 0)
				return(1);
			delay(20);
			if (!hddintrwait(hdaddr)) {
				printf("%s (before shutdown) ADAPTER WONT INTERRUPT!\n", caller);
				return(1);
			}
			delay(20);
		}
	}
	hdaddr->DMACMD = DISKSHUT;
	delay(100);
	if (!hddintrwait(hdaddr)) {
		printf("%s (after shutdown) ADAPTER WONT INTERRUPT!\n", caller);
		return(1);
	}
	delay(100);
	return(hddiagstat(ic, caller));
}


hddiagstat(ic, caller)
	register struct iocc_ctlr	*ic;	/* controller */
	register char			*caller;
{
	register int			err;

	err = ((struct hddevice volatile *)ic->ic_addr)->DIAGSTAT; /* diagnose status */
	if ((err & DISKDIAGMASK) != DISKDIAGOK) {
		printf("hdc%d: %s diagnose=0x%x ERROR\n",
		       ic->ic_ctlr, caller, err);
		return(1);
	}
	return(0);
}


/*
 * Waits for a completion "interrupt" when interrupts are off.
 */
hddintrwait(hdaddr)
	register struct hddevice volatile	*hdaddr;
{
	register int	dummy,
			timo = MAX_WAIT_COUNT/10;

	while (timo--) {
		if (hdaddr->INTR & 0x80) {
			dummy = *(u_char *)&hdaddr->DMASTAT; /* clear intr */
			dummy = dummy;	/* for lint and perverse compilers */
			*shr_int_reset[12] = 0xff;	/* clear shared int */
			return(timo);
		} else
			DELAY(2 * 10);
	}
	return(0);
}


/**********************************************************************/

/*
 * Do a dump.
 * We are called from dumpsys() in machdep.c.
 *
 * 1. go to high priority and V=R mode (just in case we aren't there
 *    already)
 * 2. reset the disk and adapter (just in case in middle of a transfer)
 * 3. write out the dump in one track chunks.
 *
 * One complication is that we can't write location 0, so we write the
 * virtual address (0xe0000000) instead. The rest of the time we write
 * the physical address.
 * Another complication is the case where there is a hole in the address
 * space. We write zeros (thru adunload) in this case. 'savecore' will
 * put a hole into the file so it doesn't really hurt to do this.
 */
hddump(dev)
	dev_t		dev;
{
	extern int			dumpsize;	/* the size to dump */
	struct hddevice volatile			*hdaddr;
	register struct iocc_ctlr	*ic;	/* controller */
	char				*start;	/* current core addr to write */
	int				num,	/* number of blocks to write */
					blk,	/* number to write this chunk */
					un;	/* the physical unit number */
	struct partab			*sizes;	/* partition table pointer */
	register struct iocc_device	*iod;
	register struct hdst		*st;
	int				base;	/* base address of partition */
	int				error = 0;
	int				v, s;

	s = _spl0();	/* make sure interrupts inhibited */
	GET_VR0(v);	/* go into Virtual=Real mode */

	un = unit(dev);
	if (un >= NHD || (iod = hddinfo[un]) == 0 || iod->iod_alive == 0) {
		error = ENXIO;
		goto exit;
	}
	ic = iod->iod_mi;
	hdaddr = (struct hddevice volatile *)ic->ic_addr;
	if (hdc_softc[ic->ic_ctlr].hdc_flag & DMA_MODE) {
		if (hddshutdown(ic, "hddump")) {
			error = EFAULT;
			goto exit;
		}
		hdc_softc[ic->ic_ctlr].hdc_flag &= ~DMA_MODE;
	}
	if (!hdreset(iod,hdaddr, NOISY, !DMA_MODE)) {	/* reset it */
		error = EFAULT;
		goto exit;
	}
	num = dumpsize * NBPG / NBPS(st);	/* number of disk blocks */
	start = 0;
	st = &hdst[iod->iod_type];
	sizes = hdoff[un];
	if (dumplo < 0 || dumplo >= sizes[part(dev)].len) {
		error = EINVAL;
		goto exit;
	}
	if (dumplo + num > sizes[part(dev)].len)
		num = sizes[part(dev)].len - dumplo;
	base = sizes[part(dev)].start * st->nspc;
	HDDEBUG(SHOW_DUMP, printf("dumping from %x to %x at offset %x of dev %x\n", start, num * NBPS(st), dumplo, dev));
	while (num > 0) {
		int bn;	/* absolute block number to write at */

		blk = MIN(num, st->nspt);
		bn = dumplo + base + ((int)start >> BSHIFT);
		HDDEBUG(SHOW_DUMP, printf("%x        \r",(int) start >> BSHIFT));
		if (hdwr(hdaddr, dev, bn, blk * NBPS(st), start, st) < 0) {
			register int i;

			/*
			 * write error:
			 * try writing one block at a time (doing bad-block
			 * forwarding as we go)
			 */
			for (i=0; i<blk; ++i) {
				int newblk = hdmapbad(hdbad[unit(dev)], bn);
				if (newblk == 0)
					newblk = bn;	/* not mapped */
				if (hdwr(hdaddr, dev, newblk,  NBPS(st), start, st) < 0) {
					error = EIO;	/* couldn't fix it */
					goto exit;
				}
				bn++;
				start += NBPS(st);
				num--;
			}
		} else {
			start += blk*NBPS(st);
			num -= blk;
		}
	}
exit:
	SET_VR(v);
	splx(s);
	return(error);
}

/*
 * Write routine called to write the core dump.
 * Note that it is ok to pass 0 for 'xbp' to hdgo as it has a special
 * code for this case (and does not do the hdunload).
 * The addr == 0 is to get around the fact that adunload treats 0
 * as a special case
 */
hdwr(hdaddr, dev, block, count, addr, st)
	register struct hddevice volatile	*hdaddr;
	register int			block,
					count;
	register caddr_t		addr;
	register struct hdst		*st;	/* get type (may be unreal) */
	dev_t				dev;
{
	struct buf			buf; /* only used internally in hd.c */
	register struct buf		*bp = &buf;
	register int			v;	/* page number */

	HDDEBUG(SHOW_DUMPIO, printf("hd%d: hdwr %d 0x%x (%d bytes)\n", unit(dev), block, addr, count));
	if (addr == 0)
		addr = (char *) SYSBASE;
	bp->b_dev = dev;
	bp->b_flags = B_WRITE;
	bp->b_un.b_addr = addr;
	bp->b_bcount = count;
	bp->b_blkno = block;
	if (hdgo(hdaddr, st, (long)(block/st->nspc), block, bp, count,
		 (struct hdi_softc *)0))
		return(-1);
	do {
		if (!hdwait(DISKDRQ|DISKBUSY, DISKDRQ, hdaddr, !DMA_MODE))
			return(-1);
		v = btop(addr);	/* page number */
		adunload(ishole(v) ? (caddr_t) 0 : addr,
			hdaddr, MIN(count, NBPS(st)), B_WRITE);
		if (!hdwait(DISKBUSY, 0, hdaddr, !DMA_MODE) ||
		    check_error(DISKWRIT, hdaddr, NOISY, !DMA_MODE))
			return(-1);	  /* oops, we got an error */
		count -= NBPS(st);
		addr += NBPS(st);
	}
	while (count > 0);
/*
 * if not a complete block transfer unload the rest of the adapter
 * buffer
 */
	if (count < 0)
		adunload((caddr_t)0, hdaddr, -count, B_WRITE); /* unload the rest */
	return(0);		/* normal completion */
}

/**********************************************************************/
/*
 * miscellaneous debug routines
 */

#ifdef DEBUG

/*
 * nice place to put a breakpoint
 */
hddbreak(a, b, c, d)
{
}

#define	M(N)	((1<<(N))-1)
/*ARGSUSED*/
hddwcheck(bp, ioaddr, x, y)
	register struct buf	*bp;
	register u_int	 	ioaddr;
{
#ifdef notdef	/* works only if single user! */
	register caddr_t	p = bp->b_un.b_addr;
	register long		len = bp->b_bcount;
	register daddr_t	blk = bp->b_blkno;

	/* this if ensures only that the *first* TCW is correct */
	if (ioaddr && p != (caddr_t)(SYSBASE |
	       (MMU_HATIPT[TCW(hddinfo[unit(bp->b_dev)]->iod_mi->ic_dmachannel,
			       ioaddr>>11 & M(6)) &
			   M(13)].key_addrtag & MMU_VPAGE_MASK)<<11 |
	       ioaddr & M(11)))
		panic("hddwcheck ioaddr");

	while (len >= sizeof(daddr_t)) { /* special data check! */
		if (*(daddr_t *)p != blk)
			hddbreak(bp, ioaddr, x, y);
		len -= NBPS(st);
		p += NBPS(st);
		blk++;
	}
#endif	/* notdef */
}

#endif	/* DEBUG */

/**********************************************************************/

#endif	/* NHD */
