static char rcsid[] = "$Header: id.c,v 820.1 86/12/04 19:55:33 root Exp $";
static char sccsid[]="%W% %Y% %Q% %G%";

/************************************************************************
*									*
*			Copyright 1984 - 1986				*
*			VALID LOGIC SYSTEMS INCORPORATED		*
*									*
*	This listing contains confidential proprietary information	*
*	which is not to be disclosed to unauthorized persons without	*
*	written consent of an officer of Valid Logic Systems 		*
*	Incorporated.							*
*									*
*	The copyright notice appearing above is included to provide	*
*	statutory protection in the event of unauthorized or 		*
*	unintentional public disclosure.				*
*									*
************************************************************************/

/*
 * Interphase SMD 2190/Storager disk controller.
 */

#include "id.h"
#if NID > 0
#include "../h/param.h"
#include "../h/buf.h"
#include "../h/systm.h"
#include "../h/dir.h"
#include "../h/file.h"
#include "../h/inode.h"
#include "../h/ioctl.h"
#include "../h/user.h"
#include "../h/dk.h"
#include "../h/uio.h"
#include "../s32/dkio.h"
#include "../s32/dklabel.h"
#include "../s32/cpu.h"
#include "../s32dev/mbvar.h"
#include "../s32dev/isreg.h"
#include "../s32dev/isvar.h"

#include "../s32dev/is_ioctl.h"

extern u_long chipType;		/* For the right size delay loops */

struct is_disk is_disk[NID];		/* per disk structures */
struct mb_device *idinfo[NID];
struct buf idcbuf;			/* command buffer header */

/*
 * Possible drive descriptor bytes for Storager.
 * Due to a Fuji/Storager bug, the proper ddesc byte for the Fuji ESDI
 * drive must come first - the Fuji drive does not like to be mis-configured.
 */

#define NDDESC	4
u_char id_ddesc[] = { 
		IUDDESC_ESDI_SECTPULSE,
		IUDDESC_ST506, 
		IUDDESC_ESDI_ADDRMK, 
		IUDDESC_ESDI_BYTECLK
};


/*
 * Check to see if a slave is there 
 * (if a device is connected to controller).
 */

short st506delay = 1;

idslave(md, mc)
	struct mb_device *md;
	struct mb_ctlr *mc;
{
	register slave = md->md_slave;
	register struct is_ctlr *ic = is_ctlr + md->md_ctlr;
	register struct is_disk *id = is_disk + md->md_unit;
	register int i;
	int x;
	u_char ready = 0;			/* drive shows ready flag */

#define IDSLAVE_RDY(csr, slave) (csr & (ISCSR_RDYBIT<<slave))

	if (isaltdebug) 
		cdebugger("idslave\n");

	/*
	 * Check that the slave number is appropriate:
	 *
	 * If the disk number is less than zero, say-no-MORE!
	 *
	 * If the controller is a Storager and we are looking for 
	 * drives 2 or 3, then immediately return a failure -- 
	 * Storager only has two drives, numbered 0 and 1. (IS_STRGR_NSLAVE)
	 *
	 * If the controller is a 2190, check that the disk number is 
	 * no greater than 4 (IS_2190_NSLAVE).
	 */
	if ((slave < 0) ||
	    (slave >= (ic->ic_storager ? IS_STRGR_NSLAVE : IS_2190_NSLAVE)) )
		return (0);

	/*
	 * Determining if a device is there is a bit more complicated
	 * with the advent of ESDI.  ESDI drives do not show ready until
	 * they have been initialized properly.
	 *
	 * If we immediately see a ready bit, we can simply return.
	 * Otherwise, we need to try to initialize for all possibilities.
	 */

	/*
	 * The 2190 can support 4 SMD drives on a board.  "Slave" may then
	 * reasonably be 0-3.  The Storager can support 2 Winchesters and
	 * 2 floppies (yicch) on a board.  The Winchesters are slaves 0 and 1;
	 * the floppies are 2-3.  Slaves 0-3 are valid for Storager, but
	 * 2 and 3 will never be ready (so long as we don't support floppy 
	 * drives).
	 *
	 * Note that is the controller is a Storager and we're checking slaves
	 * 2 or 3, this code is never executed -- we return upon seeing a
	 * slave number that is too high.
	 */

	if (IDSLAVE_RDY(ic->ic_addr->iscsr, slave))
		return(1);

	if (!ic->ic_storager)			
		return(0); 	/* 2190 SMD controller and no ready bit */
		
	/*
	 * It's a Storager which is not showing ready for the drive:
	 * try initializing it with the various drive descriptor bytes.
	 * We call idfake_param() to get most of the right parameters,
	 * then patch the drive descriptor byte appropriately.
	 * Note that we have to wait a bit for the ready bit to come on.
	 * Bletch.
	 */

	/*
	 * Ahem.  We need to do this to run idcmd().
	 * Can you say "poorly structured code"?  
	 * Sure, I knew that you could...
	 */

	id->id_md = md;
	id->id_ic = ic;
	id->id_unit = slave;

	idfake_param(id, ic->ic_storager);

	for (i = 0; i < NDDESC ; i++ ) {

		id->id_drivedesc = id_ddesc[i]; 


		/* if it's a Siemens drive, spin it up */
		if (id->id_drivedesc == IUDDESC_ESDI_BYTECLK) {
			if (!(IDSLAVE_RDY(ic->ic_addr->iscsr, slave))) {
				if (idcmd(id, IPCMD_MOTORCTL, 0, 1, 0, 0, 0, 0))
					continue;
				printf ("Spinning up drive ... ");
				while (!(IDSLAVE_RDY(ic->ic_addr->iscsr, slave)));
				if (!(IDSLAVE_RDY(ic->ic_addr->iscsr, slave)))
					printf ("not ");
				printf ("ready\n");
			}
		}
		if (idinit(id) < 0)	/* should never happen */
			continue;

		/*
		 * There is a problem with the probe/slave/readlabel/init
		 * cycle followed by this driver.  Given two ST506 disks,
		 * units 0 and 1 (and perhaps other configurations -- we
		 * were not exhaustive in our testing of exactly WHAT breaks)
		 * -- the first will be seen by this code, and the second 
		 * will not. The problem obviously lies in some code executed
		 * in the time after we sense the first disk and before we
		 * try and sense the second -- "obviously" because we use the 
		 * same code for sensing both.  The other possibility is that
		 * some general board initialization is giving preferential
		 * treatment to disk 0 (all board commands that aren't supposed
		 * to do anything to the attached disks are run with the
		 * drive number set to 0, so this is a possibility).  Rather
		 * than trace down the problem now, we provide this fix:
		 * The delay loop, which was 1000, we make 4000.  Then the 
		 * second ST506 disk comes ready in time to be seen.
		 *
		 * Question: what about using a 68020, 
		 * which will run the loop faster?
		 *
		 * - bog 041686
		 */
		for (x = ((chipType == CHIPTYPE_68020) ? 16000 : 4000); 
			x-- ; )	/* a decent interval... */
			;

		if (IDSLAVE_RDY(ic->ic_addr->iscsr, slave)) {
			ready = 1;
			break;
		}

		/*
		 * If both disks are ST506 then by this time the second
		 * ST506 has come ready. However, if the first disk is
		 * ESDI, the second disk is ST506, and we're running
		 * on a 68020, the second disk usually has NOT come
		 * ready during this delay ( Don't ask me why, maybe it's
		 * some quirk in the Storager ). So, let's wait some more
		 * and try again.
		 *
		 * BW 012986
		 */

		if( !st506delay )
			continue;

		for (x = ((chipType == CHIPTYPE_68020) ? 16000 : 4000); 
			x-- ; )	/* a decent interval... */
			;

		if (IDSLAVE_RDY(ic->ic_addr->iscsr, slave)) {
			ready = 1;
			break;
		}
	}

	return(ready);
}


/*
 * Attach device to controller.
 */

idattach(md)
	register struct mb_device *md;	/* a5 */
{
	struct dk_label l;
	register struct is_ctlr *ic = is_ctlr + md->md_ctlr; /* a4 */
	register struct is_disk *id = is_disk + md->md_unit; /* a3 */
	register u_short *sp;	/* a2 */
	register u_short sum;	/* d7 */

	if (isaltdebug) cdebugger("idattach\n");

	/* set up disk struct and link into list of drives on controller */

	id->id_md = md;			/* id_md == a3@(0x152) */
	id->id_ic = ic;			/* id_ic == a3@(0x156) */
	id->id_unit = md->md_slave;	/* unit == a3@(14c); slave == a5@(8) */
	id->id_link = ic->ic_disk;	/* link == a3@(15a); disk == a4@(4a) */
	ic->ic_disk = id;
	ic->ic_ndisk++;

	/* try to init with crocked-up params so we can read block 0 */

	idfake_param(id, ic->ic_storager);

	if (!ic->ic_storager) {
		if (idinit(id) < 0) {
			printf("Can't initialize id%d\n", id->id_unit);
			if (isaltdebug) cdebugger(0);
			goto out;
		}

		if (!idreadlabel(id, &l) || !idchecklabel(id, &l))
			goto out;

	} else {
		/*
		 * There are 4 possiblities for a 
		 * drive descriptor byte: 
		 * ST506, ESDI step mode, ESDI serial mode, and
		 * ESDI special Hitachi mode.
		 * Need to try all possibilities.
		 */
		
		int i;
		int no_can_read = 1;

		for (i = 0; i < NDDESC ; i++ ) {

			id->id_drivedesc = id_ddesc[i]; 
			if (idinit(id) < 0)	/* should never happen */
				continue;

			if (idreadlabel(id, &l)) {	/* read worked */
				no_can_read = 0;
				break;
			}
		}
		if ((no_can_read) || !idchecklabel(id, &l))
			goto out;
	}
		
	/* label was good, fill in disk structure with real params */

	id->id_ncyl = l.dkl_ncyl;
	id->id_acyl = l.dkl_acyl;
	id->id_a_alloc = l.dkl_a_alloc;
	id->id_nhead = l.dkl_nhead;
	id->id_nsec = l.dkl_nsec;
	id->id_gap1 = l.dkl_gap1;
	id->id_gap2 = l.dkl_gap2;
	id->id_intrlv = l.dkl_intrlv;
	id->id_skew = l.dkl_skew;
	id->id_group = l.dkl_group;
	id->id_cache = l.dkl_cache;
	id->id_nsecpercyl = id->id_nsec * id->id_nhead;

	if (ic->ic_storager) {
		id->id_zerolatency = l.dkl_zerolatency;
		id->id_drivedesc = l.dkl_drivedesc;
		id->id_gap3 = l.dkl_gap3;
		id->id_bufstep = l.dkl_bufstep;
		id->id_stepwidth = l.dkl_stepwidth;
		id->id_stepinter = l.dkl_stepinter;
		id->id_headloadtime = l.dkl_headloadtime;
		id->id_seektime = l.dkl_seektime;
		id->id_precomp = l.dkl_precomp;
		id->id_redwrite = l.dkl_redwrite;
	}

	/* copy the partition table from the label */
	bcopy(l.dkl_sizes, id->id_sizes, sizeof id->id_sizes);
	l.dkl_asciilabel[sizeof l.dkl_asciilabel - 1] = 0;

#ifdef ISERRORLOGGING
	/*
	 * Initialize number of errors.
	 */
	id->isSoftErrors = 0;
	for (sum=0; sum<IPERR_NCLASSES; sum++)
		id->isFirmErrors[sum] = id->isHardErrors[sum] = 0;
#endif ISERRORLOGGING

#ifdef notdef
	printf(
	  "id%d: \"%s\", ncyl %d, acyl %d, a_alloc %d, nhead %d, nsec %d.\n",
		id->id_unit, l.dkl_asciilabel, id->id_ncyl,
		id->id_acyl, id->id_a_alloc, id->id_nhead, id->id_nsec);
#else  notdef
	printf(
	  "id%d0: \"%s\", ncyl %d, acyl %d, nhead %d, nsec %d.\n",
		(id->id_ic->ic_ctlr)*IS_MAX_NSLAVE + id->id_unit, 
		l.dkl_asciilabel, id->id_ncyl, id->id_acyl, 
		id->id_nhead, id->id_nsec);
#endif notdef

	if (isdebug) {
		register struct dk_sizes *p;

		printf("     partitions");
		for (p = id->id_sizes; p < id->id_sizes + DK_NSIZES; p++)
			if (p->sz_nblk != 0)
				printf(" [%x,%d,%d]", p - id->id_sizes,
					p->sz_cyloff, p->sz_nblk);
		printf(".\n");
	}

	/*
	 * Reintialize the controller for this disk with the 
	 * groovy parameters from the label.
	 */
	if (idinit(id) < 0) {
		printf("id%d: failed initializing after reading label.\n",
			(id->id_ic->ic_ctlr)*IS_MAX_NSLAVE + id->id_unit);
		goto out;
	}
out:
	if (md->md_dk >= 0)
		/*
		dk_mspw[md->md_dk] = (float)id->id_intrlv /
			(RPS * (id->id_nsec ? id->id_nsec : 1) *
			(DEV_BSIZE/2));
		*/
		dk_mspw[md->md_dk] = 0.000000001;
}


/*
 * Set up fake parameters that will allow us to issue
 * controller-only commands or read the first block.
 *
 * Actually, we set them up to be something rather reasonable.
 */

idfake_param(id, storager)
	register struct is_disk *id;
{
	if (storager) {
		id->id_ncyl = 1024;
		id->id_nhead = 5;
		id->id_nsec = 17;
		id->id_gap1 = 13;
		id->id_gap2 = 17;
		id->id_gap3 = 20;
		id->id_intrlv = 1;
		id->id_zerolatency = 1;
		id->id_drivedesc = 6;
		id->id_bufstep = 1;
		id->id_stepwidth = 1;
		id->id_stepinter = 1;
		id->id_seektime = 6;
		id->id_precomp = 0xffff;
		id->id_redwrite = 0xffff;
	} else {
		id->id_ncyl = 804;
		id->id_nhead = 20;
		id->id_nsec = 44;
		id->id_gap1 = 20;
		id->id_gap2 = 30;
		id->id_intrlv = 3;
	}

	/* common to both controller flavors */

	id->id_nsecpercyl = id->id_nhead * id->id_nsec;
	id->id_acyl = 38;
	id->id_a_alloc = 0;
	id->id_sizes->sz_cyloff = 0;
	id->id_sizes->sz_nblk = 2000000;	/* 1G bytes */
}



/*
 * Tries to read the disk label, which resides in block 0.
 * Returns true if successful.
 * Tries to verify block 0 first, in order to detect an unformatted disk.
 */

idreadlabel(id, label)
	struct is_disk *id;
	struct dk_label *label;
{
	if (idcmd(id, IPCMD_VERIFY, 0, 0, 0, 1, 0, 0) < 0)
		return(0);

	if (idcmd(id, IPCMD_READ, 0, 0, 0, 1, 
			(caddr_t)vtop((caddr_t)label), 0) < 0)
		return(0);
	
	return(1);
}

/*
 * Checks a label for correctness by looking at the checksum
 * and magic number.
 * Returns true if label was good, false otherwise.
 */

idchecklabel(id, label)
	struct is_disk *id;
	struct dk_label *label;
{
	register u_short *sp;		/* pointer for checksum calculation */
	register u_short sum = 0;	/* label checksum */

	for (sp = (u_short *)label ; sp < (u_short *)&label->dkl_cksum ; 
		sum ^= *sp++)
		{}

	if (label->dkl_magic != DKL_MAGIC || sum != label->dkl_cksum) {
		printf("id%d: bad label",
			(id->id_ic->ic_ctlr)*IS_MAX_NSLAVE + id->id_unit);
		if (label->dkl_magic != DKL_MAGIC)
			printf(", magic number 0x%x (should be 0x%x)",
				label->dkl_magic, DKL_MAGIC);
		if (sum != label->dkl_cksum)
			printf(", checksum 0x%x (should be 0x%x)",
				label->dkl_cksum, sum);
		printf(".\n");
		return(0);
	}

	return(1);
}


/*
 *  Open the disk.  All the work has already been done.
 */
idopen(dev)
	dev_t dev;
{
	int unit = idunit(dev);
	struct mb_device *md;

	if (unit >= NID || (md = idinfo[unit]) == 0 || !md->md_alive)
		return ENXIO;
	return 0;
}


idstrategy(bp)
	register struct buf *bp;
{
	register struct is_disk *id = is_disk + idunit(bp->b_dev);
	register struct dk_sizes *sz = id->id_sizes + idpart(bp->b_dev);
	int nblk;
	int s;

	nblk = bp->b_bcount / DEV_BSIZE;
	if (bp->b_blkno < 0 || bp->b_blkno + nblk > sz->sz_nblk) {
		if (isdebug)
			printf("id%d%x: bad blkno, blkno %d, nblk %d.\n",
				(id->id_ic->ic_ctlr)*IS_MAX_NSLAVE+id->id_unit,
				idpart(bp->b_dev), bp->b_blkno, nblk);
		goto err;
	}
	if (nblk == 0) {
		/* can't transfer less than one sector */
		if (isdebug)
			printf("id%d%x: bad bcount, blkno %d, bcount %d.\n",
				(id->id_ic->ic_ctlr)*IS_MAX_NSLAVE+id->id_unit,
				idpart(bp->b_dev), bp->b_blkno, bp->b_bcount);
		bp->b_resid = bp->b_bcount;
		goto out;
	}

	/* disksort() wants to see the cylinder number in b_resid */
	bp->b_resid = bp->b_blkno / id->id_nsecpercyl + sz->sz_cyloff;
	s = splIS();
	disksort(&id->id_tab, bp);
	if (!id->id_active)
		idstart(id);
	splx(s);
	return;
err:
	bp->b_flags |= B_ERROR;
out:
	iodone(bp);
}


idread(dev, uio)
	dev_t dev;
	struct uio *uio;
{
	return physio(idstrategy, &is_disk[idunit(dev)].id_rbuf,
		dev, B_READ, minphys, uio);
}

idwrite(dev, uio)
	dev_t dev;
	struct uio *uio;
{
	return physio(idstrategy, &is_disk[idunit(dev)].id_rbuf,
		dev, B_WRITE, minphys, uio);
}


/*ARGSUSED*/
idioctl(dev, cmd, addr, flag)
	dev_t dev;
	caddr_t addr;
{
	register struct is_disk *id = is_disk + idunit(dev);
	int s;

	switch (cmd) {
#define P ((struct dk_param *)addr)
	case DKIOCSETP:
		s = splIS();
		id->id_ncyl = P->dk_ncyl;
		id->id_acyl = P->dk_acyl;
		id->id_a_alloc = P->dk_a_alloc;
		id->id_nhead = P->dk_nhead;
		id->id_nsec = P->dk_nsec;
		id->id_intrlv = P->dk_intrlv;
		id->id_gap1 = P->dk_gap1;
		id->id_gap2 = P->dk_gap2;
		id->id_group = P->dk_group;
		id->id_cache = P->dk_cache;
		id->id_skew = P->dk_skew;
		id->id_nsecpercyl = id->id_nsec * id->id_nhead;

		if (id->id_ic->ic_storager) {
			/* Storager only fields */
			id->id_zerolatency = P->dk_zerolatency;
			id->id_drivedesc = P->dk_drivedesc;
			id->id_gap3 = P->dk_gap3;
			id->id_bufstep = P->dk_bufstep;
			id->id_stepwidth = P->dk_stepwidth;
			id->id_stepinter = P->dk_stepinter;
			id->id_headloadtime = P->dk_headloadtime;
			id->id_seektime = P->dk_seektime;
			id->id_precomp = P->dk_precomp;
			id->id_redwrite = P->dk_redwrite;
		}
		/*
		if (id->id_md->md_dk >= 0)
			dk_mspw[id->id_md->md_dk] = (float)id->id_intrlv /
				(RPS * (id->id_nsec ? id->id_nsec : 1) *
				(DEV_BSIZE/2));
		*/
		(void) idinit(id);
		splx(s);
		break;
	case DKIOCGETP:
		P->dk_ncyl = id->id_ncyl;
		P->dk_acyl = id->id_acyl;
		P->dk_a_alloc = id->id_a_alloc;
		P->dk_nhead = id->id_nhead;
		P->dk_nsec = id->id_nsec;
		P->dk_intrlv = id->id_intrlv;
		P->dk_gap1 = id->id_gap1;
		P->dk_gap2 = id->id_gap2;
		P->dk_group = id->id_group;
		P->dk_cache = id->id_cache;
		P->dk_skew = id->id_skew;
		if (id->id_ic->ic_storager) {
			/* Storager only */
			P->dk_zerolatency = id->id_zerolatency;
			P->dk_drivedesc = id->id_drivedesc;
			P->dk_gap3 = id->id_gap3;
			P->dk_bufstep = id->id_bufstep;
			P->dk_stepwidth = id->id_stepwidth;
			P->dk_stepinter = id->id_stepinter;
			P->dk_headloadtime = id->id_headloadtime;
			P->dk_seektime = id->id_seektime;
			P->dk_precomp = id->id_precomp;
			P->dk_redwrite = id->id_redwrite;
		}
#ifdef ISERRORLOGGING
		P->dkSoftErrors = id->isSoftErrors;
		for (s=0; s<IPERR_NCLASSES; s++)
		{
			P->dkFirmErrors[s] = id->isFirmErrors[s];
			P->dkHardErrors[s] = id->isHardErrors[s];
		}
#endif ISERRORLOGGING
		break;
#undef P

#ifdef KernelLabelWriting
	case DKIOCWLABEL:
		/* should put this all in a seperate routine */
		id->id_ncyl = l.dkl_ncyl;
		id->id_acyl = l.dkl_acyl;
		id->id_a_alloc = l.dkl_a_alloc;
		id->id_nhead = l.dkl_nhead;
		id->id_nsec = l.dkl_nsec;
		id->id_gap1 = l.dkl_gap1;
		id->id_gap2 = l.dkl_gap2;
		id->id_intrlv = l.dkl_intrlv;
		id->id_skew = l.dkl_skew;
		id->id_group = l.dkl_group;
		id->id_cache = l.dkl_cache;
		id->id_nsecpercyl = id->id_nsec * id->id_nhead;

		if (id->id_ic->ic_storager) {
			id->id_zerolatency = l.dkl_zerolatency;
			id->id_drivedesc = l.dkl_drivedesc;
			id->id_gap3 = l.dkl_gap3;
			id->id_bufstep = l.dkl_bufstep;
			id->id_stepwidth = l.dkl_stepwidth;
			id->id_stepinter = l.dkl_stepinter;
			id->id_headloadtime = l.dkl_headloadtime;
			id->id_seektime = l.dkl_seektime;
			id->id_precomp = l.dkl_precomp;
			id->id_redwrite = l.dkl_redwrite;
		}

		bcopy(l.dkl_sizes, id->id_sizes, sizeof id->id_sizes);
		l.dkl_asciilabel[sizeof l.dkl_asciilabel - 1] = 0;
		/* write label here */
		break;
#endif KernelLabelWriting

	case DKIOCSETS:
		bcopy(addr, (caddr_t)id->id_sizes, sizeof id->id_sizes);
		break;
	case DKIOCGETS:
		bcopy((caddr_t)id->id_sizes, addr, sizeof id->id_sizes);
		break;
	case DKIOCFORMAT:
		s = splIS();
		if (idformat(id, idpart(dev), *(int *)addr) < 0)
			u.u_error = EIO;
		splx(s);
		break;
	case DKIOCMAP:
		s = splIS();
		idmap(id, idpart(dev), *(int *)addr);
		splx(s);
		break;
	case DKIOCRESET:
		s = splIS();
		if (isreset(id->id_ic) < 0)
			u.u_error = EIO;
		splx(s);
		break;
	case DKIOCSETA:
		isautomap = *(int *)addr ? 1 : 0;
		break;
	case DKIOCGETA:
		*(int *)addr = isautomap;
		break;
	default:
		u.u_error = ENXIO;
		break;
	}
	return u.u_error;
}


idsize(dev)
	dev_t dev;
{
	int unit = idunit(dev);
	struct mb_device *md;

	if (unit >= NID || (md = idinfo[unit]) == 0 || !md->md_alive)
		return -1;
	return is_disk[unit].id_sizes[idpart(dev)].sz_nblk;
}


/*
 * Start up an operation on the drive.
 */

idstart(id)
	register struct is_disk *id;
{
	struct is_ctlr *ic;
	register struct buf *bp;
	u_char csr;
	int s;

again:
	if ((bp = id->id_actf) == 0)		/* null buffer - bogus */
		return;

	/* get the controller for this drive */

	ic = id->id_ic;
	if (ic->ic_busy)		/* controller busy, can't do it */
		return;
	if ((csr = ic->ic_addr->iscsr) & ISCSR_BUSY) {
		/*
		 * This should never happen.
		 * So don't try to recover nicely, just drop it on the floor.
		 */
		printf("id%d: idstart, controller busy, csr 0x%b.\n",
			ic->ic_ctlr, csr, ISCSR_BITS);
		s = splIS();
		id->id_active = 0;
		id->id_actf = bp->av_forw;
		splx(s);
		bp->b_flags |= B_ERROR;
		iodone(bp);
		ISLOG('b', id->id_unit, csr);
		goto again;
	}

	/* check to see if we are responding to an I/O from iscmd */

	if (bp == &idcbuf) {
		/* non-data xfer command from iscmd */

		IP = *(struct is_param *)bp->b_un.b_addr;	/* copy iopb */
		if (issdebug)
			printf("id%d: isustart, command.\n",
				id->id_unit);
		id->id_active = ACTIVE_CMD;
		ISLOG('c', id->id_unit, csr);
	} else {
		register int nblk;
		register daddr_t blkno;
		register int i;			/* used for speed */

		/* data xfer */
		if (issdebug)
			printf("id%d: isustart, io.\n", id->id_unit);

		/* fill in the iopb */

		IP.ip_cmd = bp->b_flags & B_READ ? IPCMD_READ : IPCMD_WRITE;
		blkno = bp->b_blkno;
		nblk = bp->b_bcount / DEV_BSIZE;

		IP.ip_nsec0 = nblk;
		IP.ip_nsec1 = nblk >> NBBY;
		id->id_cyl = (blkno + nblk - 1) / id->id_nsecpercyl
			+ id->id_sizes[idpart(bp->b_dev)].sz_cyloff;
		IP.ip_sec0 = i = (blkno % id->id_nsec);
		IP.ip_sec1 = i >> NBBY;
		IP.ip_head = (blkno / id->id_nsec) % id->id_nhead;
		IP.ip_cyl0 = i = bp->b_resid;	/* set to cylnum by idstart */
		IP.ip_cyl1 = i >> NBBY;
		IP.ip_buf0 = i = bp->b_flags & B_PHYS
			? (int) bp->b_un.b_addr : (int) vtop(bp->b_un.b_addr);
		IP.ip_buf1 = i >> NBBY;
		IP.ip_buf2 = i >> NBBY * 2;
		id->id_active = ACTIVE_IO;
		if ((i = id->id_md->md_dk) >= 0) {
			dk_xfer[i]++;
			dk_wds[i] += bp->b_bcount >> 6;
		}
		ISLOG('x', id->id_unit, csr);

		IP.ip_opt = IPOPT_NORMAL;
		IP.ip_stat = 0;
		IP.ip_err = 0;
		IP.ip_drive = id->id_md->md_slave;
		IP.ip_dma = IS_DMA;
		IP.ip_reladdr0 = 0;
		IP.ip_reladdr1 = 0;
		IP.ip_ioaddr0 = i = (int)ic->ic_addr - MBIO_VA;
		IP.ip_ioaddr1 = i >> NBBY;
	}

	ic->ic_busy = 1;
	ic->ic_unit = id->id_unit;

	/* start up command */

#ifdef M68020
	flushWriteQueue();
#endif M68020
	ic->ic_addr->iscsr = ISCSR_GO | ISCSR_BUS;
}


/*
 * Disk interrupt routine, called from isintr().
 * At entry we know we have had an interrupt.
 */
idintr(ctlr, csr)
	u_char csr;
{
	register struct is_ctlr *ic = is_ctlr + ctlr;
	register struct is_disk *id;
	register struct buf *bp;
	int i = 0;
	/* interrupting unit, unit from ic */
	register u_char int_unit, ic_unit = ic->ic_unit; 

	ISLOG('i', ic->ic_ctlr, csr);
	if (isidebug)
		printf("ic%d: isintr CI int, csr 0x%b.\n",
			ic->ic_ctlr, csr, ISCSR_BITS);


	if (!ic->ic_busy && csr & ISCSR_CI) {
		/*
		 * Bug: not using the controller, but a command interrupted.
		 */
		printf("id%d: spurious interrupt, csr 0x%b.\n",
				/* ic-> */ ic_unit, csr, ISCSR_BITS);
		goto start_new_op;
		
	}

	int_unit = ic->ic_addr->isintunit;	/* before it goes away... */

	/*
	 * Clear the interrupt.
	 */

	ic->ic_addr->iscsr = ISCSR_CLRI;
	i = ISTIMEOUTDELAY;
	while (ic->ic_addr->iscsr & ISCSR_CI && --i)	 /* wait for done */
		;

	if (csr & ISCSR_SI) {
		/* 
		 * Disk has gone off line.
		 */
		printf("id%d: disk offline.\n", /* ic-> */ ic_unit);
		goto start_new_op;
	}

	/*
	 * If we get here, we were using the controller and we recieved a
	 * command completion interrupt.
	 */

	ic->ic_busy = 0;

	/*
	 * TODO: Check software unit number against int_unit....
	 */

#ifdef notdef
	/*
	 * BROKEN:  ic->ic_unit is only valid relative to the controller
	 * (i.e. it is 0-3 for all controllers) Unit 0 on controller 0 will
	 * have the same is_disk as unit 0 on controller 3 if we do this.
	 * So don't.
	 */
	id = is_disk + /* ic-> */ ic_unit;
#else  notdef
	/*
	 *
	 * Instead, look down the list of is_disks hanging from the controller.
	 * What is it there for, anyways?
	 */
	for (id=ic->ic_disk; 
	     (id) && (id->id_unit != /* ic-> */ ic_unit); 
		id=id->id_link)
		;
#endif notdef

	if (!id)
		panic("id: disk not on controller's list");

	bp = id->id_actf;

	if (bp == &idcbuf) {
		/* copy back updated iopb */
		*(struct is_param *)bp->b_un.b_addr = IP;
	}

	/*
	 * How did we do?
	 */

	switch (IP.ip_stat) {

	 case IPSTAT_DONE:
		/*
		 * command was successful
		 */
		bp->b_resid = bp->b_bcount & DEV_BMASK;
#ifdef ISERRORLOGGING
		/*
		 * Remember firm errors as well as soft errors.
		 * hitting idclassifyerr() without an error will panic()
		 * (because I said so) so don't go in here via debugging
		 * code.
		 */
		if ((IP.ip_err != 0) || (id->id_errcnt != 0))
#else  ISERRORLOGGING
		/*
		 * Remember soft errors.
		 */
		if (IP.ip_err != 0 || isdebug)
#endif ISERRORLOGGING
		{
			/*
			 * Controller or software recovered after retry...
			 */
#ifdef ISERRORLOGGING
			idclassifyerr(&IP, id);
#endif ISERRORLOGGING
#ifdef ISNOISY
			idprint(bp->b_dev, bp, &IP,
				bp->b_flags & B_READ ?
				"read" : "write");
#endif ISNOISY
#ifdef ISERRORLOGGING
			/* 
			 * Clear error code and count from 
			 * firm or soft error (paranoia) 
			 */
			id->id_err = 0;
			id->id_errcnt = 0;
#endif ISERRORLOGGING
		}

		id->id_actf = bp->av_forw;
		iodone(bp);
		break;

	  case IPSTAT_ERR:
	  default:
		/*
		 * Oops.
		 */

#ifdef ISERRORLOGGING
		/*
		 * We have different criteria for 
		 * what should be retried and what shouldn't.
		 */
		switch (IP.ip_err)
		{
			/*
		 	 * Class I:
		 	 * MECHANICAL FAULT ERRORS -- do not retry.
		 	 */
			case IPERR_SEEK:	/* 0x12: Seek Error */
			case IPERR_FAULT:	/* 0x1e: Drive Faulted */
			case IPERR_IDSECTOR:	/* 0x21: ID Field Wrong Sect */
			case IPERR_CRC:		/* 0x22: CRC Error - ID Field */
			case IPERR_FMTTIME:	/* 0x27: Format Timeout */
			case IPERR_IDHEAD:	/* 0x2a: ID Field Wrong Head */
			case IPERR_SEEKTIM:	/* 0x2d: Seek Timeout */
			case IPERR_RESTORTIM:	/* 0x30: Restore Timeout */
				idhard_error(id, &IP, bp);
				break;
			/*
		 	 * Class II:
		 	 * Defective Media Errors -- retry up to 6 times.
		 	 */
			case IPERR_ECCCRC:	/* 0x13: ECC/CRC Data Error */
			case IPERR_HARD:	/* 0x23: Uncorr. Data Error */
			case IPERR_NOSEC:	/* 0x29: Sector Not Found   */
			case IPERR_UNRCVDATA:	/* 0x87: Unrec. Data Error   */
				idsoft_error(id, &IP, bp);
				break;
			/*
		 	 * Class III:
		 	 * Everything Else -- treat as a soft error.
		 	 */
			default:
				idsoft_error(id, &IP, bp);
				break;
		}
#else  ISERRORLOGGING
		switch (IP.ip_err) {

		 case IPERR_FAULT:		/* 0x1E: drive faulted. */
		 case IPERR_SEEK:		/* 0x12: seek error.    */
		 case IPERR_SEEKTIM:		/* 0x2D: seek timeout.  */
#ifdef notdef
		 /*
		  * Ancient error codes from the mists of time.
		  */
		 case IPERR_NOTCYL:
		 case IPERR_DSEEK:
#endif notdef
			/* issue a restore and try again */
			idprint(bp->b_dev, bp, &IP,
				"faulted or drive failure, trying to restore");
			if (idrestore(id) < 0) {
				/* hard error */
				idprint(bp->b_dev, bp, &IP, "restore failed");
				idhard_error(id, &IP, bp);
			} else {
				idsoft_error(id, &IP, bp);
			}
			break;
		
		 case IPERR_NOTRDY:
		 case IPERR_NOTSELECT:
			/* hard error */
			idprint(bp->b_dev, bp, &IP,
				"drive not ready or selected");
			idhard_error(id, &IP, bp);
			break;
		
		 default:
			/* check if soft error */
			idsoft_error(id, &IP, bp);
			break;
		}
#endif ISERRORLOGGING

		if (bp->b_flags & B_ERROR) {
			id->id_actf = bp->av_forw;
			iodone(bp);
		}
		break;
	}

	id->id_active = 0;

start_new_op:
	/*
	 * If controller not busy, start next operation.
	 */

	for (id = ic->ic_disk; !ic->ic_busy && id; id = id->id_link)
	     if (id->id_active == 0 && id->id_actf != 0)
			idstart(id);

	return 1;
}


/*
 * We have a hard error.
 * Complain and mark the buf flag with B_ERROR.
 */
idhard_error(id, ip, bp)
	struct is_disk *id;
	struct is_param *ip;
	struct buf *bp;
{
#ifdef ISERRORLOGGING
	/*
	 * Classify the error (Should always be a HARD ERROR).
	 */
	idclassifyerr(ip, id);
#endif ISERRORLOGGING
	id->id_errcnt = 0;
	idprint(bp->b_dev, bp, ip,
		bp->b_flags & B_READ ?
		"read (hard)" : "write (hard)");
	bp->b_flags |= B_ERROR;
	idrecover(bp, ip);
}

/*
 * We have a soft error.
 * See if we can retry and correct, if not we have a hard error.
 */
idsoft_error(id, ip, bp)
	struct is_disk *id;
	struct is_param *ip;
	struct buf *bp;
{
	if (id->id_errcnt < isretrycnt) {
		/* retry the command */
		id->id_errcnt++;
#ifdef ISERRORLOGGING
		/*
		 * Remember which sort of error we had.
		 * (Software retries will wipe out the old IOPB 
		 * and its record of the screwup)
		 */
		id->id_err = ip->ip_err;
#endif ISERRORLOGGING

#ifdef ISNOISY
		/* 
		 * This sort of stuff makes customers nervous.
		 * "Ignorance is bliss..."
		 */
		idprint(bp->b_dev, bp, ip,
			bp->b_flags & B_READ ?
			"read (retrying...)" : "write(retrying...)");
#endif ISNOISY

		idrestore(id);
	} else {
		idhard_error(id, ip, bp);
	}
}


idreset(ic)
	register struct is_ctlr *ic;
{
	register struct is_disk *id;
	register int i;
	
	for (id = ic->ic_disk; id; id = id->id_link) {
		if (!id->id_md->md_alive)
			continue;
		id->id_active = 0;
		(void) idinit(id);
		id->id_cyl = 0;
	}
}


idprint(dev, bp, ip, mesg)
	dev_t dev;
	register struct buf *bp;
	register struct is_param *ip;
	char *mesg;
{
#ifdef ISNOISY
	register u_char *i;
#endif ISNOISY
#ifdef ISERRORLOGGING
	register struct is_disk *id = is_disk + idunit(dev);
#endif ISERRORLOGGING

	if (bp == 0 || bp == &iscbuf)
		printf("id%d: ", idunit(dev));
	else
		printf("id%d%x: ", idunit(dev), idpart(dev));
	if (mesg)
		printf("%s, ", mesg);
	if (bp == 0 || bp == &iscbuf)
		printf("cmd 0x%x, ", ip->ip_cmd);
	else
		printf("bn %d, ", bp->b_blkno);
	switch (ip->ip_stat) {
	case IPSTAT_DONE:
		if (ip->ip_err != 0) {
			printf("soft error 0x%b, %d retries.\n",
				ip->ip_err, IPERR_BITS,
				ip->ip_err & IPERR_RETRY);
		} else
#ifdef ISERRORLOGGING
		/*
		 * Mention firm errors, too.
		 */
		if (id->id_errcnt != 0)
			printf("firm error 0x%x, %d retries.\n",
				id->id_err, id->id_errcnt);
		else
#endif ISERRORLOGGING

			printf("no error.\n");
		break;
	case IPSTAT_ERR:
		printf("error 0x%x \"%s.\" cmd 0x%x, from id%d\n",
			ip->ip_err, iserror(ip->ip_err), 
			ip->ip_cmd, idunit(dev));
		break;
	case IPSTAT_BUSY:
		printf("controller busy.\n");
		break;
	default:
		printf("unknown ip_stat 0x%x.\n", ip->ip_stat);
	}
#ifdef ISNOISY
	for (i=(u_char *)ip; i < ((u_char *)(ip))+(sizeof *ip); i++)
		printf("%2X ",*i);
	printf("\n");
#endif ISNOISY
}


/*
 * Run a controller command in asynchronous mode.
 */
idicmd(id, cmd, head, cyl, sec, nsec, addr, mesg)
	caddr_t addr;
	register struct is_disk *id;
	char *mesg;
{
	register struct is_ctlr *ic = id->id_ic;	/* our controller */
	register struct buf *bp = &idcbuf;	/* buf header for command */
	static struct is_param ip;		/* iopb for commands */
	u_int ioaddr;				/* addr of csr in I/O space */
	int s;					/* saved spl level */
	u_char stat;
	int b_flags;

	/*
	 * Wait for our turn.
	 */
	s = splIS();
	while (bp->b_flags & B_BUSY) {
		bp->b_flags |= B_WANTED;
		sleep((caddr_t)bp, PRIBIO+1);	/* let iowait() go first */
	}
	bp->b_flags = B_BUSY;
	splx(s);

	ip.ip_cmd = cmd;
	ip.ip_head = head;
	ip.ip_cyl0 = cyl;
	ip.ip_cyl1 = cyl >> NBBY;
	ip.ip_sec0 = sec;
	ip.ip_sec1 = sec >> NBBY;
	ip.ip_nsec0 = nsec;
	ip.ip_nsec1 = nsec >> NBBY;
	ip.ip_buf0 = (cmd == IPCMD_FORMAT) ? 'a' : (u_int)addr; /* fmt value */
	ip.ip_buf1 = (u_int)addr >> NBBY;
	ip.ip_buf2 = (u_int)addr >> (NBBY * 2);
	if (isdebug)
		printf("addr=%X, b0=%x, b1=%x, b2=%x\n",
			addr, ip.ip_buf0, ip.ip_buf1, ip.ip_buf2);
	ip.ip_opt = IPOPT_NORMAL;
	ip.ip_stat = 0;
	ip.ip_err = 0;
	ip.ip_drive = id->id_unit;
	ip.ip_dma = 128;
	ip.ip_reladdr0 = 0;
	ip.ip_reladdr1 = 0;

	ioaddr = (u_int) ic->ic_addr - MBIO_VA;
	ip.ip_ioaddr0 = ioaddr;
	ip.ip_ioaddr1 = ioaddr >> NBBY;
	bp->b_un.b_addr = (caddr_t)&ip;
	idstrategy(bp);
	iowait(bp);

	b_flags = bp->b_flags;		/* capture command status */

	/* FIX THIS
	 * so that the start routine loads up the address of the
	 * real iobp so that it gets updated properly.
	 */

#ifdef DumbCode
	/* the following is stupid because ip reflects the initial state
	   of the iopb, not the final state (it's copied to the controller's
	   own iopb, and then executed there, so the original does not
	   get the completion code, etc.
	   we could copy the completed iopb back.
	   truthfully, the whole business with copying the iopb in seems
	   bogus. oh well.
	 */

	if (ip.ip_stat != IPSTAT_DONE) {
		if (mesg != 0)
			itprint(itmkdev(it->it_unit), 0,
				&ip, mesg);
		if (ip.ip_stat == IPSTAT_ERR)
			it->it_err = ip.ip_err;
	}
#endif DumbCode

	s = splIS();
	if (bp->b_flags & B_WANTED)
		wakeup((caddr_t)bp);
	bp->b_flags = 0;
	splx(s);

	if (stat != IPSTAT_DONE) {
		if (mesg != 0)
			idprint(idmkdev(id->id_unit, 0), 0,
				ip, mesg);
		return -1;
	}
	return(b_flags & B_ERROR ? -1 : 0);
}


/*
 * Run a controller command in polled (synchronous) mode.
 * We poke the controller directly, poll for completion
 * and clear interrupts before returning.
 */
idcmd(id, cmd, head, cyl, sec, nsec, addr, mesg)
	caddr_t addr;
	register struct is_disk *id;
	char *mesg;
{
	register struct is_ctlr *ic = id->id_ic;
	register struct is_param *ip;
	register i;
	int s, stat;
	char csr = 0;

	if (ic->ic_busy) {
		printf("id%d: idcmd: controller busy\n",
			(id->id_ic->ic_ctlr)*IS_MAX_NSLAVE+id->id_unit);
		if (isaltdebug) cdebugger(0);
		return -1;
	}
	ic->ic_busy = 1;
	ip = &ic->ic_param;
	if (isdebug)
		printf("Iscmd:ip is at %x\n",ip);

	/*
	 * Gain access to the controller if its csr shows busy.
	 *
	 * Actually, the controller can only be busy if a command
	 * has gone berserk or we have some kind of bug.
	 * Thus, we should do a controller reset if the command
	 * doesn't time out here.  There may be a recursion problem
	 * here - if we call isreset() we may step on the parameter block
	 * that we are trying to execute here.
	 *
	 * Think about this.
	 */

	for (i = ISTIMEOUTDELAY; --i && (ic->ic_addr->iscsr & ISCSR_BUSY) ; );

	if (i == 0) {
		/* do this for the time being */
		printf("iscmd: controller is stuck - consider rebooting\n");
		ic->ic_busy = 0;
		return -1;
	}

	ip->ip_cmd = (u_char)cmd;
	ip->ip_head = (u_char)head;
	ip->ip_cyl0 = (u_char)cyl;
	ip->ip_cyl1 = (u_char)(cyl >> NBBY);
	ip->ip_sec0 = (u_char)sec;
	ip->ip_sec1 = (u_char)(sec >> NBBY);
	ip->ip_nsec0 = (u_char)nsec;
	ip->ip_nsec1 = (u_char)(nsec >> NBBY);
	ip->ip_buf0 = (cmd == IPCMD_FORMAT) ? 
				'a' : (u_char)addr; /* format fill value */
	ip->ip_buf1 = (u_char)((int)addr >> NBBY);
	ip->ip_buf2 = (u_char)((int)addr >> (NBBY * 2));
	if (isdebug)
		printf("addr=%X, b0=%x, b1=%x, b2=%x\n",
			addr, ip->ip_buf0, ip->ip_buf1, ip->ip_buf2);
	ip->ip_opt = IPOPT_NORMAL;
	ip->ip_stat = 0;
	ip->ip_err = 0;
	ip->ip_drive = id->id_unit;
	ip->ip_dma = 128;
	ip->ip_reladdr0 = 0;
	ip->ip_reladdr1 = 0;
	s = splIS(); 		/* wait until the last moment to spl */

#ifdef M68020
	flushWriteQueue();
#endif M68020
	ic->ic_addr->iscsr = ISCSR_GO | ISCSR_BUS;

	iswaituntildone(ip, &ic->ic_addr->iscsr);

	csr = ic->ic_addr->iscsr;		/* stash away for debug */
	ic->ic_addr->iscsr = ISCSR_CLRI;	/* clear interrupt condition */

	i = ISTIMEOUTDELAY;
	while (ic->ic_addr->iscsr & ISCSR_CI && i-- )	 /* wait for done */
		;
	
	if (ic->ic_storager && isaltdebug) {
		printf("alternate csr 0x%x\n", (ic->ic_addr + 1)->iscsr);
	}

	stat = ip->ip_stat;		/* Capture status from iopb */
	id->id_err = ip->ip_err;
	ic->ic_busy = 0;
	splx(s);

	if (stat != IPSTAT_DONE) {
		if (mesg != 0)
			idprint(idmkdev(id->id_unit, 0), 0,
				ip, mesg);
		return -1;
	}

	return 0;
failure:
	return -1;
}




/*
 * Initialize the controller for a specific disk.
 */
idinit(id)
	register struct is_disk *id;
{
static	struct is_uib uib;
	caddr_t addr;
	int ret_val;

	if (isudebug)
		printf("Isinit: uib is at %x\n",&uib);
	uib.iu_nhead = id->id_nhead;
	uib.iu_nsec = id->id_nsec;
	uib.iu_secsiz0 = DEV_BSIZE;
	uib.iu_secsiz1 = DEV_BSIZE >> NBBY;
	uib.iu_gap1 = id->id_gap1;
	uib.iu_gap2 = id->id_gap2;
	uib.iu_il = id->id_intrlv & IUIL_IL;
	uib.iu_retry = 10;
	uib.iu_ec = 1;
	uib.iu_rs = 1;
	uib.iu_bd = 0;
	uib.iu_ih = 1;
	uib.iu_2port = 0;
	uib.iu_si = /* 1 */ 0;		/* status change interrupts, please */
	uib.iu_skew = id->id_skew;
	uib.iu_grpsiz = id->id_group;

	if (id->id_ic->ic_storager) {
		int ncyl = id->id_ncyl + id->id_acyl;

		uib.iu_opoption = IUOPT_NORMAL;
		if (id->id_zerolatency)
			uib.iu_opoption |= IUOPT_ZEROLATENCY;
		uib.iu_drivedesc = id->id_drivedesc;
		uib.iu_gap3 = id->id_gap3;
		if (id->id_bufstep)
			uib.iu_stepcontrol = IUSTEPCNTL_BUFSTEP;
		uib.iu_stepwidth = id->id_stepwidth;
		uib.iu_stepinter0 = id->id_stepinter;
		uib.iu_stepinter1 = id->id_stepinter >> NBBY;
		uib.iu_headloadtime = id->id_headloadtime;
		uib.iu_seektime = id->id_seektime;
		uib.iu_ncyl0 = ncyl;
		uib.iu_ncyl1 = ncyl >> NBBY;
		uib.iu_precomp0 = id->id_precomp;
		uib.iu_precomp1 = id->id_precomp >> NBBY;
		uib.iu_redwrite0 = id->id_redwrite;
		uib.iu_redwrite1 = id->id_redwrite >> NBBY;
	} else {
		if (id->id_group != 0)
			uib.iu_il |= IUIL_GRP;
		if (id->id_cache)
			uib.iu_il |= IUIL_CE;
	}

	if (isudebug) {
		register char *u = (char *) &uib;
		register char *e;

		if (id->id_ic->ic_storager)
			e = (char *) &uib.iu_redwrite1;
		else
			e = (char *) &uib.iu_grpsiz;
		printf("Uib:\n");
		while (u <= e)
			printf("%x ",*u++);
		printf("\n");
	}
	addr = (caddr_t)vtop((caddr_t)&uib);

	ret_val =  idcmd(id, IPCMD_INIT, 0, 0, 0, 0, addr,
		"can't initialize drive");

	if (isudebug && id->id_ic->ic_storager) {
		register char *u = (char *) &uib;
		register char *e = (char *) &uib.iu_redwrite1;

		/* print out it's notion of the uib */
		idcmd(id, IPCMD_READUIB, 0, 0, 0, 0, addr,"can't read uib");

		printf("the Storager reports the uib:\n");
		while (u <= e)
			printf("%x ",*u++);
		printf("\n");
	}

	if (isudebug) 
		idstub();
	return(ret_val);
}

/* 
 * for debug 
 */
idstub(){}


u_char idfastformat = 1;

/*
 * Format a track and verify it.
 */

idformat(id, part, trk)
	register struct is_disk *id;
{
	int status, n;
	int head = trk % id->id_nhead;
	int cyl = trk / id->id_nhead + id->id_sizes[part].sz_cyloff;
	static char scratch[512];

	if (/*idicmd*/idcmd(id, IPCMD_FORMAT, head, cyl, 0, 0, 0,
			"idformat: can't format track") < 0)
		return(-1);

	/* verify the track */

	if (idfastformat)				/* cheat */
		return(0);

	for (n = 0 ; n < id->id_nsec ; n++) {
		if (/*idicmd*/idcmd(id, (isrdebug ? IPCMD_READ : IPCMD_VERIFY), 
		       	   head, cyl, n, 1, scratch, 0) < 0) {
			printf("idformat: verify failed, hd %d cyl %d sec %d\n",
				head, cyl, n);
			return(-1);
		}
	}

	return(0);
}

/* 
 * Map a bad track to an alternate.
 */

idmap(id, part, trk)
	register struct is_disk *id;
{
static	char b[DEV_BSIZE];
	int head = trk % id->id_nhead;
	int cyl = trk / id->id_nhead + id->id_sizes[part].sz_cyloff;
	if (idmap1(id, cyl, head, b) < 0)
		u.u_error = EIO;
}

/*
 * Restore the drive head before we retry an operation.
 * Called at splIS().
 */
idrestore(id)
	register struct is_disk *id;
{
	return(id->id_cyl =
		idcmd(id, IPCMD_RESTORE, 0, 0, 0, 0, 0, "can't reset drive"));
}


/*
 * Try to recover from a hard IO error
 * This is all run at splIS().
 *
 * If the operation is a read, then we first try to write into the
 * block to clear it up.
 * Otherwise, the track is mapped to an alternate track, and the data
 * copied to the new track as much as possible.
 * When this is done, a write operation is restarted, but a read
 * fails (as it should).
 *
 * Some data is inevitably lost in the transaction, but we don't
 * necessarily report an error.  The best thing is probably to panic
 * so fsck will be run after reboot.  But on the other hand,
 * the user might want to do his own cleaning up.
 *
 * We do the best we can.
 */
idrecover(bp, ip)
	register struct buf *bp;
	register struct is_param *ip;
{
static	char b[DEV_BSIZE];
	register struct is_disk *id = is_disk + idunit(bp->b_dev);
	struct is_ctlr *ic = id->id_ic;
	register i;
	int cyl, head, sec;

	/*
	 * Decide whether we should try to recover at all
	 */
	if (bp == &iscbuf || !isautomap || ip->ip_stat != IPSTAT_ERR)
		return;

	switch (ip->ip_err) {
	 case IPERR_HARD:
	 case IPERR_NOSEC:
		break;		/* correctable */
	 default:
		return; 	/* "No can do, boss" */
	}

	printf("id%d: trying to spare bad block.\n", idunit(bp->b_dev));

	/*
	 * Find out which sector is at fault.
	 * Note, ip is wiped out by the first iscmd().
	 */
	cyl = ip->ip_cyl0 | ip->ip_cyl1 << NBBY;
	head = ip->ip_head;
	sec = ip->ip_sec0 | ip->ip_sec1 << NBBY;
	for (i = ip->ip_nsec0 | ip->ip_nsec1 << NBBY; --i >= 0;) {
		if (idcmd(id, IPCMD_READ, head, cyl, sec, 1,
				(caddr_t)vtop((caddr_t)b), 0) < 0)
			break;
		if (++sec >= id->id_nsec) {
			sec = 0;
			if (++head >= id->id_nhead) {
				head = 0;
				cyl++;
			}
		}
	}
	if (i < 0) {
		printf("id%d: can't find bad block.\n", idunit(bp->b_dev));
		goto out;
	}
	printf("id%d: bad block found on %s, cyl %d, head %d, sec %d.\n",
		idunit(bp->b_dev), bp->b_flags & B_READ ? "read" : "write",
		cyl, head, sec);
	/*
	 * If a read, then first try writing into that block.
	 * The data is lost already.
	 */
	if ((bp->b_flags & B_READ) == 0)
		goto map;
	printf("id%d: attempting to overwrite block.\n", idunit(bp->b_dev));
	for (i = 0; i < sizeof b; i++)
		b[i] = i;
	if (idcmd(id, IPCMD_WRITE, head, cyl, sec, 1, (caddr_t)vtop((caddr_t)b),
	    "write failed") < 0)
		goto map;
	for (i = 0; i < sizeof b; i++)
		b[i] = 0;
	printf("id%d: reading back.\n", idunit(bp->b_dev));
	if (idcmd(id, IPCMD_READ, head, cyl, sec, 1, (caddr_t)vtop((caddr_t)b),
	    "read failed") < 0)
		goto map;
	for (i = 0; i < sizeof b && (u_char)b[i] != (u_char)i; i++)
		;
	if (i >= sizeof b) {
		printf("id%d: bad block salvaged by overwriting.\n",
			idunit(bp->b_dev));
		goto out;
	}
	printf("id%d: verify failed.\n", idunit(bp->b_dev));
map:
	/*
	 * Now, try to map the track to an alternate.
	 */
	if (idmap1(id, cyl, head, b) < 0)
		goto out;
	/*
	 * We can now try to write again.
	 * Some data was probably lost in the track copy, but that can't
	 * be helped, and it shouldn't reflect on this operation.
	 */
	if ((bp->b_flags & B_READ) == 0)
		bp->b_flags &= ~B_ERROR;
out:
	/* isreset(ic, 1); */		/* restart io */
	/* XXX call reboot directly? */
	printf("id%d: REBOOT AS SOON AS POSSIBLE.\n", idunit(bp->b_dev));
}



u_char idadebug;

/*
 * The guts of bad track mapping, called from both isrecover()
 * and ismap().
 */

idmap1(id, cyl, head, b)
	register struct is_disk *id;
	char *b;
{
	int acyl, ahead;
	int acylplus, aheadplus;
	char erred = 0;
	char unit = id->id_ic->ic_unit;
	int skipcount = 20;		/* max # of bad alternates we'll skip */

static	struct dk_label l;
	register u_short *sp;	/* for checksum calculation */
	register u_short sum;	/* ditto */

	/*
	 * First find out whether we have any alternates left.
	 * We must have at least two, one into which to map the bad track,
	 * and one as a scratch for copying the track.
	 * We need a scratch track because we try to save data from bad
	 * track, but simply mapping as bad destroys data on track.  
	 * Thus we copy to scratch first, then map.
	 *
	 * We always use the last of the alternates as scratch.
	 * We allocate alternate tracks in increasing head and cylinder order.
	 */

	printf("id%d: attempting to map bad track, cyl %d, head %d.\n",
		unit, cyl, head);

	/*
	 * Look for two formatted unallocated adjacent tracks.
	 * Yes, the requirement that they be adjacent is wrong.
	 * However, it makes the code much simpler.
	 *
	 * We try to verify the first sector in each track.  
	 * If we can't verify, it means that
	 * the track was "marked" as bad at disk formatting time by not
	 * being formatted (this is the convention we require of the 
	 * disk formatter).
	 */

	while (id->id_nhead * id->id_acyl > id->id_a_alloc + 1) {
		acyl = id->id_ncyl + (id->id_a_alloc / id->id_nhead);
		ahead = id->id_a_alloc % id->id_nhead;
		acylplus = id->id_ncyl + ((id->id_a_alloc + 1) / id->id_nhead);
		aheadplus = (id->id_a_alloc + 1) % id->id_nhead;
		id->id_a_alloc++;
		if (idadebug)
			printf("Looking at alternates (c,h) %d,%d  %d,%d\n",
				acyl, ahead, acylplus, aheadplus);
		if (idcmd(id, IPCMD_VERIFY, ahead, acyl, 0, 1, 0, 0) < 0) {
			switch (id->id_err) {
			 case IPERR_NOTRDY:
			 case IPERR_NOTSELECT:
			 case IPERR_FAULT:
			 case IPERR_SEEK:
			 case IPERR_SEEKTIM:
			 case IPERR_NOTCYL:
			 case IPERR_DSEEK:
				goto bad;
				break;
			 default:
				if (skipcount--) {
					printf("id%d: skipping bad alternate\n",
						unit);
					continue;
				} else {
					printf("id%d: too many bad alts\n",
						unit);
					goto bad;
				}
				break;
			}
		}
		if (idcmd(id, IPCMD_VERIFY, aheadplus, acylplus, 0,1,0,0)< 0) {
			switch (id->id_err) {
			 case IPERR_NOTRDY:
			 case IPERR_NOTSELECT:
			 case IPERR_FAULT:
			 case IPERR_SEEK:
			 case IPERR_SEEKTIM:
			 case IPERR_NOTCYL:
			 case IPERR_DSEEK:
				goto bad;
				break;
			 default:
				if (skipcount--) {
					printf("id%d: skipping bad alternate\n",
						unit);
					continue;
				} else {
					printf("id%d: too many bad alts\n",
						unit);
					goto bad;
				}
				break;
			}
		}
		/* if we got here, we found two good tracks - yummo! */
		break;
	}
	
	if (id->id_nhead * id->id_acyl <= id->id_a_alloc + 1) {
		/* one or fewer track left */
		printf("id%d: no more alternate tracks.\n", unit);
		goto bad;
	}

	/*
	 * Copy as much of the bad track as possible to the scratch
	 * track, map it, and copy the data back.
	 */

	if (idcptrack(id, cyl, head, acylplus, aheadplus, b) < 0)
		erred = 1;
	printf("id%d: mapping track to cyl %d, head %d.\n",
		unit, acyl, ahead);
	if (idcmd(id, IPCMD_MAP, head, cyl, ahead, acyl, 0, "map failed") < 0)
		goto bad;
	if (idcptrack(id, acylplus, aheadplus, cyl, head, b) < 0)
		erred = 1;	/* shouldn't get an error here */
	if (erred)
		printf("id%d: some data is lost.\n", unit);

	/*
	 * Update the a_alloc field in the disk label on the disk
	 * so that it reflects our allocation of another alternate track.
	 */

	if (idcmd(id, IPCMD_READ, 0, 0, 0, 1, (caddr_t)vtop((caddr_t)&l),
	    "can't read label for updating, serious disk error") < 0) {
		goto bad;
	}

	/*
	 * Assume label is good (don't check checksum, magic number).
	 * Update a_alloc field, recalculate checksum, and write back out.
	 */
	
	l.dkl_a_alloc = id->id_a_alloc; 

	for (sp = (u_short *)&l, sum = 0; sp < &l.dkl_cksum; sum ^= *sp++)
		;
	l.dkl_cksum = sum;

	if (idcmd(id, IPCMD_WRITE, 0, 0, 0, 1, (caddr_t)vtop((caddr_t)&l),
	    "can't update label, serious disk error") < 0) {
		goto bad;
	}

	printf("id%d: track cyl %d, head %d mapped to cyl %d, head %d.\n",
		unit, cyl, head, acyl, ahead);
	return 0;
bad:
	printf("id%d: can't map bad track.\n", unit);
	return -1;
}



/*
 * Try very hard to copy a track.
 */
idcptrack(id, fromcyl, fromhead, tocyl, tohead, b)
	register struct is_disk *id;
	char *b;
{
	register sec;
	int i;
	char erred = 0;

	printf("id%d: copying cyl %d, head %d to cyl %d, head %d.\n",
		id->id_unit, fromcyl, fromhead, tocyl, tohead);
	for (sec = 0; sec < id->id_nsec; sec++) {
		if (isdebug)
			printf("id%d: copying sector %d.\n",
				id->id_unit, sec);
		for (i = 20; --i >= 0 &&
		     idcmd(id, IPCMD_READ, fromhead, fromcyl, sec, 1,
		     vtop(b), isdebug ? "write failed" : 0) < 0;)
			break;
		if (i < 0) {
			printf("id%d: can't read sector %d.\n",
				id->id_unit, sec);
			erred = 1;
			continue;
		}
		for (i = 20; --i >= 0 &&
		     idcmd(id, IPCMD_WRITE, tohead, tocyl, sec, 1,
		     vtop(b), isdebug ? "write failed" : 0) < 0;)
			;
		if (i < 0) {
			printf("id%d: can't write sector %d.\n",
				id->id_unit, sec);
			erred = 1;
		}
	}
	return erred ? -1 : 0;
}

#ifdef ISERRORLOGGING
/*
 * Classifies errors into HARD, FIRM, or SOFT error classifications as
 * defined in Ed / Ross' memo of 4/21/1986:
 *
 *	1. SOFT ERROR
 *		Controller successfully completes an error but had to 
 *		execute one or more retries.
 *	2. FIRM ERROR
 *		Controller did not successfully complete an operation,
 *		but a subsequent retry by software succeeded.
 *	3. HARD ERROR
 *		Controller did not successfully complete an operation,
 *		and subsequent retries by software all failed as well.
 *
 * Further classifies Hard and Firm errors into Classes I, II, and III 
 * according to the Disk Evaluators' Bible.
 */
#ifndef notdef
idclassifyerr(iopbptr,id)
register struct is_param *iopbptr;
register struct is_disk *id;
{
	register int *errvec, error;

	/*
	 * And The Lord Saith:
	 *
	 * "Errors shalt thou classify into the following three categories:
	 *
	 * 1. Class I: Gross Mechanical Fault Errors
	 *	1. Seek Errors
	 *	2. I.D. Field Errors
	 *	3. Drive Faults
	 *	4. Drive Timeout Errors
	 * 2. Class II: Defective Media Errors - Should be Mapped
	 *	1. Data Errors
	 *	2. ECC/CRC Errors
	 *	3. Sector Not Found Errors
	 * 3. Class III: Miscellaneous Errors - Consult Engineering
	 *    (quoth the Lord, with Holy Ambiguity:)
	 *	1. All Other Errors.
	 *
	 * ...and it was the best they got."
	 */
	switch (iopbptr->ip_stat)
	{

	 case IPSTAT_DONE: /* command completed. */
		/*
		 * Firm Error implies errcnt nonzero: software had to retry.
		 */
		if (id->id_errcnt != 0)
		{
#ifdef ISNOISY
			printf("FIRM ERROR ");
#endif ISNOISY
			errvec = id->isFirmErrors;
			/* 
			 * iopbptr->ip_err (iopb's conception of the 
			 * error that occurred) will be zero because this
			 * operation succeeded. Retrieve error code
			 * which idsoft_error() stored earlier.
			 */
			error = id->id_err;
		}
		else if (iopbptr->ip_err != 0)
		{
#ifdef ISNOISY
			printf("SOFT ERROR ");
#endif ISNOISY
			/*
			 * must be a soft error.
			 */
			id->isSoftErrors++;
			return;	/* No classifications for soft errors */
		}
		else
			/*
			 * Note: don't turn on isdebug!
			 */
			panic("is: impossible error");
		break;

	  case IPSTAT_ERR:
	  default:
		/*
		 * Must be a hard error because 
		 * we didn't complete successfully.
		 */
#ifdef ISNOISY
		printf("HARD ERROR ");
#endif ISNOISY
		errvec = id->isHardErrors;
		error = iopbptr->ip_err;
		break;
	}

	/*
	 * For HARD or FIRM errors, 
	 * we further classify according to error type.
	 */
	switch (error)
	{
		/*
	 	 * Class I:
	 	 * "Dirty, appalling, socially unacceptable, gross,
	 	 * MECHANICAL FAULT ERRORS (come on down)"
	 	 */
		case IPERR_SEEK:	/* 0x12: Seek Error */
		case IPERR_FAULT:	/* 0x1e: Drive Faulted */
		case IPERR_IDSECTOR:	/* 0x21: ID Field Wrong Sect */
		case IPERR_CRC:		/* 0x22: CRC Error - ID Field */
		case IPERR_FMTTIME:	/* 0x27: Format Timeout */
		case IPERR_IDHEAD:	/* 0x2a: ID Field Wrong Head */
		case IPERR_SEEKTIM:	/* 0x2d: Seek Timeout */
		case IPERR_RESTORTIM:	/* 0x30: Restore Timeout */
#ifdef ISNOISY
			printf("(class 1) ");
#endif ISNOISY
			errvec[IPERR_CLASS1]++;
			break;
		/*
	 	 * Class II:
	 	 * "Bad Disk! Bad Disk! Use The Newspaper!"
	 	 * Defective Media Errors
	 	 */
		case IPERR_ECCCRC:	/* 0x13: ECC/CRC Data Error */
		case IPERR_HARD:	/* 0x23: Uncorr. Data Error */
		case IPERR_NOSEC:	/* 0x29: Sector Not Found   */
		case IPERR_UNRCVDATA:	/* 0x87: Unrec. Data Error   */
#ifdef ISNOISY
			printf("(class 2) ");
#endif ISNOISY
			errvec[IPERR_CLASS2]++;
			break;
		/*
	 	 * Class III:
	 	 * Everything Else.
	 	 */
		default:
#ifdef ISNOISY
			printf("(class 3) ");
#endif ISNOISY
			errvec[IPERR_CLASS3]++;
			break;
	}
}
#else  notdef
idclassifyerr(iopbptr,id)
register struct is_param *iopbptr;
register struct is_disk *id;
{
	register int *errvec, error;

	if (id->id_errcnt == 0)
	{
		/*
		 * SOFT ERROR. Squish.
		 */
		printf("SOFT ERROR: ");
		id->isSoftErrors++;
	}
	else
	{
		if (id->id_errcnt >= isretrycnt)
		{
			/*
		 	 * HARD ERROR. Oof.
		 	 */
			printf("HARD ERROR ");
			errvec = id->isHardErrors;
			error = iopbptr->ip_err;
		}
		else
		{
			/*
		 	 * FIRM ERROR. Jusssst Riiiiight.
		 	 */
			printf("FIRM ERROR ");
			errvec = id->isFirmErrors;
			error = id->id_err;
		}

		switch (error)
		{
			/*
		 	 * Class I:
		 	 * "Dirty, appalling, socially unacceptable, gross,
		 	 * MECHANICAL FAULT ERRORS (come on down)"
		 	 */
			case IPERR_SEEK:	/* 0x12: Seek Error */
			case IPERR_FAULT:	/* 0x1e: Drive Faulted */
			case IPERR_IDSECTOR:	/* 0x21: ID Field Wrong Sect */
			case IPERR_CRC:		/* 0x22: CRC Error - ID Field */
			case IPERR_FMTTIME:	/* 0x27: Format Timeout */
			case IPERR_IDHEAD:	/* 0x2a: ID Field Wrong Head */
			case IPERR_SEEKTIM:	/* 0x2d: Seek Timeout */
			case IPERR_RESTORTIM:	/* 0x30: Restore Timeout */
				printf("CLASS 1:\t");
				errvec[IPERR_CLASS1]++;
				break;
			/*
		 	 * Class II:
		 	 * "Bad Disk! Bad Disk! Use The Newspaper!"
		 	 * Defective Media Errors
		 	 */
			case IPERR_ECCCRC:	/* 0x13: ECC/CRC Data Error */
			case IPERR_HARD:	/* 0x23: Uncorr. Data Error */
			case IPERR_NOSEC:	/* 0x29: Sector Not Found   */
			case IPERR_UNRCVDATA:	/* 0x87: Unrec. Data Error   */
				printf("CLASS 2:\t");
				errvec[IPERR_CLASS2]++;
				break;
			/*
		 	 * Class III:
		 	 * Everything Else.
		 	 */
			default:
				printf("CLASS 3:\t");
				errvec[IPERR_CLASS3]++;
				break;
		}
	}
	return (0);
}
#endif notdef
#endif ISERRORLOGGING
#endif NID > 0
