 /*
  * 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/ca_atr/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$ */

#include "hd.h"
#if NHD > 0
#include	"../h/param.h"
#include	"../h/vm.h"
#include	"../h/buf.h"
#include	"../h/time.h"
#include	"../h/proc.h"
#include	"../h/errno.h"
#include	"../machine/pte.h"
#include	"../machine/io.h"
#include	"../machineio/ioccvar.h"
#include	"../h/kernel.h"
#include	"../h/systm.h"
#include	"../h/cmap.h"
#include	"../h/dk.h"
#include	"../h/uio.h"
#include	"../h/ioctl.h"
#include	"../machineio/hdconfig.h"
#include	"../machine/dkio.h"
#include	"../ca_atr/part.h"
#include	"../ca_atr/pcif.h"
#include	"../pc_code/dio.h"
#include	"../machine/mmu.h"
#include	"../machine/debug.h"

#define B_REAL	B_XXX		/* flag to indicate using REAL addresses */

#define HDMAXPHYS	(1024 * 60)	/* 60K maximum transfer for hd & fd */

#define PAGESIZE 	NBPG
#define HDTIMEOUT	30
#define BSIZE		DEV_BSIZE
#define	BSHIFT		DEV_BSHIFT
#define HDUNIT(d) 	((minor(d) >> 3) & 0x07)	/* bit	3 unit */
#define HDPART(p)  	(minor(p) & 0x07)	/* bits 0-2 part */

#define b_cylin b_resid
#define HD_SPL()	_spl3()	/* at CPU level 3 */

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

int             hddebug;

#else !DEBUG
#define HDDEBUG(how,stmt)

#endif

extern struct hdinfo hdinfo[];

struct pc_dpl   hd_dpl[NHD];

caddr_t         hdstd[] = {
			   (caddr_t) 0x000001f0, 0
};

caddr_t         real_buf_addr();

int 
hdprobe(), hdslave(), hdattach(), hdint();
int             hdwstart, hdwatch();	/* watch routine */

int        hdminphys();
int	   hdsuspend();

struct iocc_device *hddinfo[NHD];
struct iocc_ctlr *hdminfo[NHDC];

struct iocc_driver hdcdriver = {
	hdprobe, hdslave, hdattach,
/*    dgo	addr	dname	dinfo	mname	minfo	intr	csr	 */
        0,	hdstd, "hd",  hddinfo, "hdc",  hdminfo, hdint,  0,
/* chanrelse		flags	suspend	*/
	0,	DRIVER_SUSPEND, hdsuspend
};

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

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

unsigned        hdbusy;
#define HDTYPELEN	8	/* length of disk type name */

struct hd_softc {
	int             timer;	/* timer for watchdog */
	u_short         nspc[NHD];
	char            hdtype[NHD][HDTYPELEN];
}               hds;

char           *bioserrmsg();

int		hd_pc_cb;		/* the PC control block addr */

hdprobe()
{

	hd_pc_cb = get_pc_cb(HDENT);

	set_pcvec_map(HDIRQ, 1);

	hd_dpl[0].op_code = DISKOP_STATUS;
	hd_dpl[0].drive = 0x80;

	if (pc_req(CB_HDREQ, &hd_dpl[0], HDENT) < 0)
		return (PROBE_BAD);

	PROBE_DELAY(1000000);	/* delay for enough time for interrupt */

	return (PROBE_OK);

}


/*
 * determine if a slave exists and if so what type of drive it is.
 * the adapter interrupt is disabled on entry, so we must disable
 * it again on exit.
 */
hdslave(iod)
	register struct iocc_device *iod;
{
	struct pc_dpl   dpl;
	struct minidirectory minidirectory;
	register struct minidirectory *md = &minidirectory;
	struct hdconfig hdconfig;
	register struct hdconfig *hdc = &hdconfig;
	register int    unit = iod->iod_unit;
	register struct buf *dp;
	register int    s = HD_SPL();	/* inhibit interrupts */
	register int    result = 0;	/* assume failure */

	set_pcvec_map(HDIRQ, 0);	/* turn off ints on the PC */

	dp = &hdutab[unit];
	dp->b_actf = NULL;
	dp->b_actl = NULL;
	dp->b_active = 0;

	hds.nspc[unit] = hdinfo[unit].ntpc * hdinfo[unit].nspt;

	if (hdinfo[unit].flag & HDINFO_PRESENT) {	/* if there's a drive */
		dpl.op_code = DISKOP_RESET;	/* reset the drive */
		dpl.drive = unit;

		if (hdop(&dpl) < 0)
			goto out;

		bzero(hdoff[unit], NPART * sizeof(struct partab));	/* zero the partab */

		hdoff[unit][7].start = 0;	/* h partition starts at 0 */
		hdoff[unit][7].len = hdinfo[unit].ncpd * hdinfo[unit].ntpc * hdinfo[unit].nspt;	/* the whole drive */

		if (hdinfo[unit].flag & HDINFO_42PART) {	/* if there's a 4.3 part */
			hdoff[unit][2].start = hdinfo[unit].pstart + BOOTCYLS;	/* c part */
			hdoff[unit][2].len = hdinfo[unit].plen * hds.nspc[unit];


			printf("hd%d: %d blocks allocated for 4.3\n", unit, hdoff[unit][2].len);
			printf("hd%d: %d cylinders %d heads %d sectors\n", unit, hdinfo[unit].plen, hdinfo[unit].ntpc, hdinfo[unit].nspt);

			/* read in the config record and get the disk type */

			dpl.op_code = DISKOP_READ;
			dpl.drive = unit;
			dpl.atr_cnt[0] = sizeof(struct hdconfig);
			dpl.atr_addr[0] = (int) hdc;
			dpl.cyl = hdoff[unit][2].start;
			dpl.sector = CONFIG_BLOCK;

			if (hdop(&dpl) < 0)
				goto out;

			if (hdconfig.conf_name[0] == 0 && hdconfig.conf_name[2] == 'h')
				strncpy(hds.hdtype[unit], hdconfig.conf_name+2,HDTYPELEN);
			else
				strncpy(hds.hdtype[unit], hdconfig.conf_name,HDTYPELEN);

			printf("hd%d: type %s\n", unit, hds.hdtype[unit]);

			/*
			 * read in the minidisk dir and setup the logical
			 * partitions 
			 */

			dpl.op_code = DISKOP_READ;
			dpl.drive = unit;
			dpl.atr_cnt[0] = sizeof(struct minidirectory);
			dpl.atr_addr[0] = (int) md;
			dpl.cyl = hdoff[unit][2].start;
			dpl.sector = MINIDISK_BLOCK;

			if (hdop(&dpl) < 0)
				goto out;

			if (!dkfindpart(md, hdoff[unit], hds.nspc[unit], hdinfo[unit].pstart + BOOTCYLS, unit, "hd")) {
				printf("hd%d: invalid/empty minidisk table\n", unit);
			}
		}		/* end of there's a 4.3 part */
	}
	 /* end if there is a drive */ 
	else
		goto out;

	result = 1;		/* good return */
out:	splx(s);
	set_pcvec_map(HDIRQ, 1);	/* turn on ints on the PC again */
	return (result);	/* and return */
}



hdopen(dev)
	dev_t           dev;
{
	register int    unit = HDUNIT(dev);
	register int    part = HDPART(dev);
	register struct iocc_device *iod;

	HDDEBUG(SHOW_ORDWR, printf("hd%d: Entering hdopen.\n", unit));

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

	HDDEBUG(SHOW_ORDWR, printf("hd%d: Leaving hdopen.\n", unit));
	return (0);
}


/*
 *	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    unit = HDUNIT(bp->b_dev);
	register int    part = HDPART(bp->b_dev);
	register struct iocc_device *iod;
	register struct partab *off;
	register struct buf *dp;
	register int    s;
	register int    sz;

	off = hdoff[unit];

	HDDEBUG(SHOW_ORDWR, printf("hd%d: hdstrategy(%x)\n", unit, bp));

	sz = (bp->b_bcount + BSIZE - 1) >> BSHIFT;

	if ((unit >= NHD) || (part >= NPART) || (iod = hddinfo[unit]) == 0 ||
		(iod->iod_alive == 0) || (iod == 0) ||
		(bp->b_blkno < 0) || (bp->b_blkno >= off[part].len) ||
		(bp->b_blkno + sz > off[part].len && (bp->b_flags & B_READ == 0))) {
		bp->b_flags |= B_ERROR;
		iodone(bp);
		return;
	}
	if (bp->b_blkno + sz > off[part].len)
		bp->b_bcount = (off[part].len - bp->b_blkno) * BSIZE;

	bp->b_cylin = (bp->b_blkno / hds.nspc[unit]) + off[part].start;


	HDDEBUG(SHOW_ORDWR, printf("hdstrat: blockno=%x cylin=%x\n", bp->b_blkno, bp->b_cylin));

	s = HD_SPL();		/* CPU level 4 interrupt */
	dp = &hdutab[unit];
	disksort(dp, bp);

	/*
	 * See if the drive is active. If it isn't, call ustart routine to
	 * queue request to controller (and maybe start a seek?) If (now) the
	 * controller has work to do, but is not active, call start routine
	 * to start the controller. 
	 */

	if (dp->b_active == 0) {
		(void) hdustart(iod);
		bp = &iod->iod_mi->ic_tab;
		if (bp->b_actf && bp->b_active == 0)
			(void) hdstart(iod->iod_mi);
	}
	splx(s);

}



/*
 *	Raw read routine:
 *	  This routine calls physio which computes and validates
 *	  a physical address from the current logical address.
 *
 *	    Arguments:
 *	      Full device number
 *	    Functions:
 *	      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
 */
hdread(dev, uio)
	register dev_t  dev;
	register struct uio *uio;
{
	register int    unit = HDUNIT(dev);

	HDDEBUG(SHOW_ORDWR, printf("hd%d: Entered hdread\n", unit));
	if (unit >= NHD)
		return (ENXIO);
	return (physio(hdstrategy, &rhdbuf[unit], dev, B_READ, hdminphys, uio));
}


/*
 *	Raw write routine:
 *	arguments (to hdwrite):
 *	  Full device number
 *	Functions:
 *	  Call physio which does the actual raw (physical) I/O
 */
hdwrite(dev, uio)
	dev_t           dev;
	register struct uio *uio;
{
	register int    unit = HDUNIT(dev);

	HDDEBUG(SHOW_ORDWR, printf("hd%d: Entered hdwrite\n", unit));
	if (unit >= NHD)
		return (ENXIO);
	return (physio(hdstrategy, &rhdbuf[unit], dev, B_WRITE, hdminphys, uio));
}



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

	HDDEBUG(SHOW_ORDWR, printf("hd: hdustart(%x)\n",iod));

	dp = &hdutab[iod->iod_unit];
	ic = iod->iod_mi;

	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++;


	return (0);
}


/*
 *	Controller Startup Routine:
 *
 *
 */
hdstart(ic)
	register struct iocc_ctlr *ic;
{
	register struct buf *bp, *dp;
	register int    unit;
	register struct iocc_device *iod;

	HDDEBUG(SHOW_ORDWR, printf("hd: Entering hdustart(%x)\n",ic));

loop:
	if ((dp = ic->ic_tab.b_actf) == NULL)
		return (0);
	if ((bp = dp->b_actf) == NULL) {
		ic->ic_tab.b_actf = dp->b_forw;
		goto loop;
	}
	ic->ic_tab.b_active++;	/* Mark controller active */

	unit = HDUNIT(bp->b_dev);
	iod = hddinfo[unit];

	if (iod->iod_dk >= 0) {
		dk_busy |= 1 << iod->iod_dk;
		dk_seek[iod->iod_dk]++;
		dk_xfer[iod->iod_dk]++;
		dk_wds[iod->iod_dk] += bp->b_bcount >> 6;
	}
	hddgo(bp);
	return (0);
}


/* 
 * This function sends the command out via a cbcb call
 */

hddgo(bp)
	register struct buf *bp;
{
	int             old_window, i, unit = HDUNIT(bp->b_dev);
	register struct pc_stat *hd_stat;
	int		head, sector, xfer_cnt, cnt;
	char           *vir_addr, *xfer_addr;

	if ((bp->b_bcount < 0) || (bp->b_bcount > 0x10000)) {
		printf("hd%d: bad bcount = %x\n", unit, bp->b_bcount);
		bp->b_error = EIO;
		return (-1);
	}
	old_window = get_512_window();	/* Save the current window */
	hd_stat = (struct pc_stat *) (set_512_window(hd_pc_cb) + pcif_512_fw);
	hd_stat += unit;

	while (hdbusy) {
		delay(10);
		if (i++ > 100)
			panic("hd: Non-sync request attempt.");
	}

	vir_addr = bp->b_un.b_addr;
	xfer_addr = (bp->b_flags & B_REAL) ? vir_addr : real_buf_addr(bp, vir_addr);
	xfer_cnt = (u_short) bp->b_bcount;

	HDDEBUG(SHOW_ORDWR,
		printf("hdddgo: flags=%x blkno=%x cyl=%x addr=%x count=%x\n",
		       bp->b_flags, bp->b_blkno, bp->b_cylin, bp->b_un.b_addr, bp->b_bcount));

	if (bp->b_flags & B_READ)
		hd_dpl[unit].op_code = DISKOP_READ;
	else
		hd_dpl[unit].op_code = DISKOP_WRITE;

	hd_dpl[unit].atr_addr[0] = (int) xfer_addr;	/* set up 1st tcw */

	if ((cnt = (PAGESIZE - ((int) xfer_addr & 0x000007ff))) > xfer_cnt)
		hd_dpl[unit].atr_cnt[0] = (u_short) xfer_cnt;
	else
		hd_dpl[unit].atr_cnt[0] = (u_short) cnt;

	xfer_cnt -= hd_dpl[unit].atr_cnt[0];	/* adjust the total count */
	vir_addr += hd_dpl[unit].atr_cnt[0];	/* adjust the virtual addr */
	/* we're on a boundry now */
	for (i = 1; xfer_cnt > PAGESIZE; i++) {	/* while there's a count */
		xfer_addr = (bp->b_flags & B_REAL) ? vir_addr : real_buf_addr(bp, vir_addr);	/* fill out tcw's */
		hd_dpl[unit].atr_addr[i] = (int) xfer_addr;
		hd_dpl[unit].atr_cnt[i] = PAGESIZE;
		vir_addr += PAGESIZE;
		xfer_cnt -= PAGESIZE;
	}

	if (xfer_cnt) {		/* if there is a partial page left, do it */
		xfer_addr = (bp->b_flags & B_REAL) ? vir_addr : real_buf_addr(bp, vir_addr);	/* fill out tcw's */
		hd_dpl[unit].atr_addr[i] = (int) xfer_addr;
		hd_dpl[unit].atr_cnt[i] = xfer_cnt;
		i++;
	}
	hd_dpl[unit].atr_addr[i] = 0;	/* mark end of request */
	hd_dpl[unit].atr_cnt[i] = 0;

	/* Then fill in the rest of the dpl and send it off */

	sector = bp->b_blkno % hds.nspc[unit];	/* get remainder after cylinder */
	head = (sector / hdinfo[unit].nspt);	/* get head */
	sector = (sector % hdinfo[unit].nspt) + 1;	/* get sector (1 base) */

#ifdef DEBUG
	if ((u_short) sector != sector ||
	    (u_short) bp->b_cylin != bp->b_cylin ||
	    (u_short) head != head ||
	    bp->b_bcount > HDMAXPHYS ||
	    i >= 32)
		panic("hd: bad hddgo");
#endif DEBUG
	hd_dpl[unit].drive = unit + 0x80;	/* load up the dpl */
	hd_dpl[unit].sector = (u_short) sector;
	hd_dpl[unit].cyl = (u_short) bp->b_cylin;
	hd_dpl[unit].head = (u_short) head;
	hd_dpl[unit].number = (u_short) ((bp->b_bcount + BSIZE - 1) / BSIZE);


	hdbusy++;


	HDDEBUG(SHOW_ORDWR,
		printf("hddgo: dpl---- blkno=%x drive=%x sector=%x cyl=%x head=%x number=%x\n",
		       bp->b_blkno, hd_dpl[unit].drive, hd_dpl[unit].sector,
		 hd_dpl[unit].cyl, hd_dpl[unit].head, hd_dpl[unit].number));


	hd_stat->return_code = 0;

	set_512_window(old_window);

	if (pc_req(CB_HDREQ, &hd_dpl[unit], HDENT) < 0)
		return (-1);

	return (0);
}

hdint(ctlr)
	int             ctlr;
{
	register struct buf *bp, *dp;
	register struct iocc_ctlr *ic;
	struct iocc_device *iod;
	struct pc_stat *hd_stat;
	register int    unit, old_window;

	ic = hdminfo[ctlr];

	if (ic == 0 || ic->ic_tab.b_active == 0) {
		HDDEBUG(SHOW_INTR, printf("HD: UNKNOWN INTERRUPT\nHD: NO I/O IN PROGRESS!!\n\n"));
		return (1);
	}
	dp = ic->ic_tab.b_actf;
	if (dp->b_active == 0) {
		HDDEBUG(SHOW_INTR, printf("HD: dp->b_active not active\n"));
		return (1);
	}
	bp = dp->b_actf;
	unit = HDUNIT(bp->b_dev);

	old_window = get_512_window();	/* Save the current window */
	hd_stat = (struct pc_stat *) (set_512_window(hd_pc_cb) + pcif_512_fw);
	hd_stat += unit;


	if (hd_stat->return_code & BIOS_ERROR) {
		printf("hd%d%c: BIOS ERROR CODE=%x<%s> bn=%d cyl=%d\n",
		       unit, HDPART(bp->b_dev) + 'a', (char) (hd_stat->return_code & 0x00ff),
		       bioserrmsg((char) (hd_stat->return_code & 0x00ff)), bp->b_blkno, bp->b_cylin);
		bp->b_flags |= B_ERROR;
	}
	set_512_window(old_window);

	/*
	 * Flag current request complete 
	 */
	if (ic->ic_tab.b_active) {	/* For when we do error recovery */
		ic->ic_tab.b_active = 0;
		ic->ic_tab.b_errcnt = 0;
		ic->ic_tab.b_actf = dp->b_forw;	/* rotate to next drive */
		dp->b_active = 0;
		dp->b_errcnt = 0;
		dp->b_actf = bp->av_forw;
		bp->b_resid = (bp->b_flags & B_ERROR) ? bp->b_bcount : 0;	/* XXX *//* Error
										 * recovery! */
		iodone(bp);

		/*
		 * If more work to do on this drive, restart it. 
		 */
		iod = hddinfo[unit];
		if (iod->iod_dk >= 0)
			dk_busy &= ~(1 << iod->iod_dk);
		if (dp->b_actf)
			hdustart(iod);
	}
	hdbusy = 0;		/* clear the busy flag */

	hds.timer = 0;		/* did not timeout */

	hdstart(ic);

	return (0);
}





hdattach(iod)
	register struct iocc_device *iod;
{
	static float    mspw =.0000016097;
	HDDEBUG(SHOW_INIT, printf("hdattach(%x)\n", iod));
	if (hdwstart == 0) {
		timeout(hdwatch, (caddr_t) 0, hz);
		++hdwstart;
	}
	if (iod->iod_dk >= 0)
		dk_mspw[iod->iod_dk] = mspw;
	return (1);
}




/*
 * watchdog timer - checks to see if we've lost an interrupt
 * by having the timer click 10 times without being cleared
 * (by interrupt routine)
 */

hdwatch(reg)
	caddr_t         reg;
{
	register struct hd_softc *hd = &hds;
	register struct iocc_ctlr *ic;
	register int    s = HD_SPL();
	register int    i;
	register struct buf *dp, *bp;
	struct pc_stat *hd_stat;
	register int    old_window;

	for (i = 0; i < NHDC; ++i) {
		if ((ic = hdminfo[i]) == 0 || ic->ic_alive == 0 || ic->ic_tab.b_active == 0) {
			hd->timer = 0;
			continue;	/* not doing anything */
		}
		if (++hd->timer > HDTIMEOUT) {
			if ((dp = ic->ic_tab.b_actf) == NULL || (bp = dp->b_actf) == NULL)
				continue;

			old_window = get_512_window();
			hd_stat = (struct pc_stat *) (set_512_window(hd_pc_cb) + pcif_512_fw);
			hd_stat += HDUNIT(bp->b_dev);

			if (hd_stat->return_code & (BIOS_ERROR | BIOS_RET_OK)) {
				set_512_window(old_window);
				hd->timer = 0;
				printf("hd%d: hdint called from hdwatch\n", HDUNIT(bp->b_dev));
				hdint(i);
			} else {
				hdbusy = 0;	/* clear the busy flag */
				hd->timer = 0;
				set_512_window(old_window);
				printf("hd%d: lost interrupt\n", HDUNIT(bp->b_dev));
				hdstart(ic);	/* retry it */
			}
		}
	}
	timeout(hdwatch, (caddr_t) 0, hz);
	splx(s);
}



/*
 * non-interrupt driven diskop routine that uses PC DISK OPS  
 * Reads and writes are packaged into a buf and sent to
 * hddgo, all others are sent directly from here.
 */
hdop(dpl)
	register struct pc_dpl *dpl;
{
	register int    old_window, i = 0;
	register struct pc_stat *hd_stat;
	register ushort unit = dpl->drive;
	struct buf      bp;
	int             timeout = (dpl->op_code == DISKOP_RESET) ? 2000 : 1000;
	int             result = -1;

	old_window = get_512_window();	/* Save the current window */
	hd_stat = (struct pc_stat *) (set_512_window(hd_pc_cb) + pcif_512_fw);
	hd_stat += unit;

	if ((dpl->op_code == DISKOP_READ) || (dpl->op_code == DISKOP_WRITE)) {
		if (dpl->op_code == DISKOP_READ)
			bp.b_flags = B_READ;
		else
			bp.b_flags = B_WRITE | B_REAL;	/* XXX only used for
							 * dumps */

		bp.b_un.b_addr = (caddr_t) dpl->atr_addr[0];
		bp.b_bcount = dpl->atr_cnt[0];
		bp.b_blkno = dpl->sector;
		bp.b_cylin = dpl->cyl;
		bp.b_dev = (dev_t) ((unit & 0x07) << 3);

		if (hddgo(&bp) < 0) {
			printf("hd%d: hddgo failed\n", unit);
			goto out;
		}
	} else {
		hd_dpl[unit].op_code = dpl->op_code;
		hd_dpl[unit].drive = unit + 0x80;

		hd_stat->return_code = 0;
		hdbusy++;
		if (pc_req(CB_HDREQ, (char *) &hd_dpl[unit], HDENT) < 0) {
			goto out;
		}
	}

	while ((hd_stat->return_code & (BIOS_ERROR | BIOS_RET_OK)) == 0) {
		delay(10);
		if (i++ > timeout) {
			printf("PC POLL TIMEOUT.\n");
			goto out;
		}
	}

	if (hd_stat->return_code & BIOS_ERROR) {
		printf("hd%d: BIOS ERROR CODE=%x<%s>\n", unit,
		       (char) (hd_stat->return_code & 0x00ff),
		       bioserrmsg((char) (hd_stat->return_code & 0x00ff)));
		goto out;
	}
/*	delay(100);				/* XXX */
	hdbusy = 0;
	result = 0;		/* everything is ok */
out:
	set_512_window(old_window);
	return (result);
}





/*
 * return partition size (for swap partitions)
 */

hdsize(dev)
	dev_t           dev;
{
	register int    unit = HDUNIT(dev);
	register struct iocc_device *iod;

	if (unit >= NHD || (iod = hddinfo[unit]) == 0 || iod->iod_alive == 0)
		return (-1);

	return (hdoff[unit][HDPART(dev)].len);
}



#define	DBSIZE	51

/*
 * do a dump.
 * 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 'DBSIZE' chunks.
 */

hddump(dev)
	dev_t           dev;
{
	extern int      dumpsize;	/* the size to dump */
	char           *start;	/* current core address to write */
	int             num,	/* number of blocks to write */
	                blk,	/* number to write this chunk */
	                unit;	/* the physical unit number */
	struct partab  *sizes;	/* partition table pointer */
	register struct iocc_device *iod;
	int             base;	/* base address of the partition */
	struct pc_dpl   dpl;

/*	(void) _spl0();		/* make sure interrupts inhibited */
/*	maskints(0xffff);	/* mask/hold all interrupts on PS2 side */
/*
 * we have to allow interrupts so that any incidental interrupts (say from
 * token ring) don't cause us to loop on the PS/2 side with lower level
 * interrupts (disk) masked. Sigh.
 */
	(void) _spl6();		/* allow interrupts ! */
	set_pcvec_map(HDIRQ, 0);	/* turn off disk ints on the PC */

	unit = HDUNIT(dev);
	if (unit >= NHD)
		return (ENXIO);

	iod = hddinfo[unit];
	if (iod == 0 || iod->iod_alive == 0)
		return (ENXIO);

	dpl.op_code = DISKOP_RESET;	/* reset the drive */
	dpl.drive = unit;

	if (hdop(&dpl) < 0)
		return (EFAULT);

	num = dumpsize * NBPG / BSIZE;	/* number of disk blocks */
	start = 0;
	sizes = hdoff[unit];
	if (dumplo < 0 || dumplo + num > sizes[HDPART(dev)].len)
		return (EINVAL);

	base = sizes[HDPART(dev)].start * hds.nspc[unit];


	HDDEBUG(SHOW_DUMP, printf("dumping from %x to %x at offset %x of dev %x\n", start, num * BSIZE, dumplo, dev));

	while (num > 0) {
		daddr_t         bn;	/* absolute block number to write at */

		blk = num > DBSIZE ? DBSIZE : num;
		bn = dumplo + base + ((int) start >> BSHIFT);
		HDDEBUG(SHOW_DUMP, printf("%x        \r", (int) start >> BSHIFT));

		dpl.op_code = DISKOP_WRITE;
		dpl.drive = unit;
		dpl.atr_cnt[0] = blk * BSIZE;
		dpl.atr_addr[0] = (u_long) start;
		dpl.cyl = bn / hds.nspc[unit];
		dpl.sector = bn % hds.nspc[unit];

		if (hdop(&dpl) < 0)
			return (EIO);

		start += blk * BSIZE;
		num -= blk;

	}
	return (0);
}


/*
 * 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.
 * 
 * DBB Isn't the size also adjusted for the CE cylinder?
 * 
 * The ATR version does check for part(dev)==2.
 * And the size returned remains grotty.
 */

hdioctl(dev, cmd, data, flag)
	caddr_t         data;
	dev_t           dev;
{
	register int    unit = HDUNIT(dev);
	register int    part = HDPART(dev);
	register struct iocc_device *iod;
	register struct partab *off;

	if (unit >= NHD || (iod = hddinfo[unit]) == 0 || iod->iod_alive == 0)
		return (ENODEV);

	off = &hdoff[unit][part];	/* partition table */

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

		bzero(data, sizeof(struct dkpart));	/* make sure its zero */
		dk->dk_size = off->len;	/* size */
		dk->dk_ntrack = (short) hdinfo[unit].ntpc;
		dk->dk_nsector = (short) hdinfo[unit].nspt;
		dk->dk_start = off->start * hdinfo[unit].ntpc * hdinfo[unit].nspt;
		dk->dk_blocksize = BSIZE;	/* device blocksize */
		dk->dk_ncyl = off->len / (hdinfo[unit].ntpc * hdinfo[unit].nspt);

		strncpy(dk->dk_name, hds.hdtype[unit], HDTYPELEN);	/* copy name */
		return (0);
	} else
		return (ENOTTY);
}

/*
 * note that the MAXHDPHYS must be less than 64k so that it will fit into
 * a short.
 */
hdminphys(bp)
	struct buf     *bp;
{

	if (bp->b_bcount > HDMAXPHYS)
		bp->b_bcount = HDMAXPHYS;
}


/*
 * routine called when we suspend and when we return
 * all we have to do is pick up a new hd_pc_cb when we
 * return. At some point we might want to stop the watch
 * routine but this isn't necessary now because timeouts
 * don't run while we are suspended.
 */
hdsuspend(iod, idr, how)
	struct iocc_device *iod;
	struct iocc_driver *idr;
{
	if (how == SUSPEND_DONE) {
		hd_pc_cb = get_pc_cb(HDENT);	/* get pc_cb addr */
	}
}

#endif	NHD

#include "fd.h"			/* for NFD */

#if NHD > 0 || NFD > 0
char           *
bioserrmsg(code)
	char            code;
{
	switch (code) {
	case BIOS_STAT_FAIL:
		return ("Sense-Failed");
	case BIOS_STAT_ERR:
		return ("Status-Error");
	case BIOS_WR_FAULT:
		return ("Write-Fault");
	case BIOS_UNDEF_ERR:
		return ("Undefined-Error");
	case BIOS_NOT_RDY:
		return ("Not-Ready");
	case BIOS_TO:
		return ("Timeout");
	case BIOS_SEEK_FAIL:
		return ("Seek-Failed");
	case BIOS_CTRL_FAIL:
		return ("Controller-Failure");
	case BIOS_ECC_OK:
		return ("ECC-Corrected");
	case BIOS_CRC_ERR:
		return ("CRC/ECC-Error");
	case BIOS_DMA_ARB:
		return ("DMA-ARB");
	case BIOS_CTRL_AM:
		return ("CTRL-ADDR-MARK");
	case BIOS_INVAL_SEC:
		return ("Invalid-Sector");
	case BIOS_INVAL_MED:
		return ("Invalid-Media");
	case BIOS_BAD_TRACK:
		return ("Bad-Cylinder");
	case BIOS_BAD_SECTOR:
		return ("Bad-Block");
	case BIOS_DMA_BNDRY:
		return ("DMA-boundary");
	case BIOS_DMA_OVRUN:
		return ("DMA-Overrun");
	case BIOS_PARAM_FAIL:
		return ("Param-Fail");
	case BIOS_DSKT_CHG:
		return ("Diskette-Changed");
	case BIOS_RESET_FAIL:
		return ("Reset-Failed");
	case BIOS_REC_NFND:
		return ("Sector-Not-Found");
	case BIOS_WRT_PROT:
		return ("Write-Protected");
	case BIOS_BAD_ADDR:
		return ("Addr-Not-Found");
	case BIOS_BAD_CMD:
		return ("Invalid-function");
	default:
		return ("Unknown-Error");
	}
}
#endif
