/*
 * 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/standca/RCS/hd.c,v $ */

#ifndef lint
static char *rcsid = "$Header:hd.c 12.0$";
#endif

#include        "param.h"
#include        "inode.h"
#include        "fs.h"
#include        "time.h"
#include        "proc.h"
#include 	"../machineio/hdreg.h"
#include 	"../machineio/hdconfig.h"


#define MAXHDBAD MAXBADBLKS	/* use config file information */

#include 	"saio.h"
#include	"sa.h"

#include	"../machine/post.h"

#ifndef NHD
#define NHD	6
#endif NHD


#define	CPARTOFF(block)  block 

#define HD_OK	0		       /* ok result from check_error */
#define HD_BAD	1		       /* not ok */

#ifndef SQUEEEEZE
#define PRINTF(format) printf format
#else
#define PRINTF(format) 
#endif

/* Disk block definitions */

#define	BSIZE	DEV_BSIZE
#define	BSHIFT	DEV_BSHIFT

#define RETRY_COUNT	2	       /* number of times to retry errors */
#define NO_ADDR	0x80000000	       /* a non-existant address of adunload */

#define	DATA	data
#define ERR	err
#define PCMP	err
#define SCT	sct
#define	SNO	sno
#define	CYLL	cyll
#define CYLH	cylh
#define	UCODELEVEL	cylh
#define	SDH	sdh
#define	STAT	stat
#define	CMD	stat
#define INTR	intr

struct hddevice *adapters[2] = {
	(struct hddevice *)  0x000001f0, /* adapter 1 */
#if NHD > 2
	(struct hddevice *)  0x00000170  /* adapter 2 */
#endif /* NHD > 2 */
};

/* Partition table */

#define NHDST 11		/* support 10 (1 dummy) drives (for now) */

struct partab hdinit_sizes[NHDST] = {/* dummy disk for non-recognized types */
	0, 0,				  /* spare */
	0, 0,				  /* spare */
	72, 0,				  /* c - at least first two tracks */
	0, 0,				  /* spare */
	0, 0,				  /* spare */
	0, 0,				  /* spare */
	0, 0,				  /* spare */
	0, 0,				  /* spare */
}, hd40m_sizes[8] = {
	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[8] = {
	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,
}, hd40r_sizes[8] = {
	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,
}, hd70c_sizes[8] = {
	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,
}, hd70r_sizes[8] = {
	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,
}, hd20i_sizes[8] = {		/* DBB This is AT BIOS drive type 2 */
	15844,	2,		/* A=cyl 2 thru 234 */
	8908, 	235,		/* B=cyl 235 thru 365 */
	41752,	0,		/* C=cyl 0 thru 613 */
	0,	0,
	0,	0,
	0,	0,
	15844,	366,		/* G=cyl 366 thru 598 */
	0,	0,
}, hd70e_sizes[8] = {
	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[8] = {
	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[8] = {
	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[8] = {
	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][8];		/* actual partition table indexed by drive number */

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	step 
 */

	{ hdinit_sizes,	1,	1024,	512,	17,	2,	17 * 2,	0 },	/* 70 MB */
	{ hd70m_sizes,	1024,	1024,	512,	17,	8,	17 * 8,	0 },	/* 70 MB */
	{ hd40m_sizes,	1024,	1024,	512,	17,	5,	17 * 5,	0 },	/* 40 MB */
	{ hd40r_sizes,	733,	300,	512,	17,	7,	17 * 7,	0 },	 /* 40MB IBM */
	{ hd70c_sizes,	925,	925,	512,	17,	9,	17 * 9,	0 },	/* 70 MB */
	{ hd70r_sizes,	566,	300,	512,	36,	7,	36 * 7,	0 },	 /* 70MB IBM */
	{ hd20i_sizes,	615,	300,	512,	17,	4, 	17 * 4, 0 },	/* 20 MB */
	{ hd70e_sizes,	583,	1020,	512,	35,	7,	35 * 7,	0 },	 /* 70MB IBM */
	{ hd114e_sizes,	915,	1020,	512,	35,	7,	35 * 7,	0 },	 /* 114MB IBM */
	{ hd310e_sizes,	1225,	1020,	512,	33,	15,	33 * 15, 0 },	 /* 310MB IBM */
	{ hd310h_sizes,	1189,	1020,	512,	34,	15,	34 * 15, 0 },	 /* 310MB */
};

char hdtype_names[NHDST+NHD][8] = {
	"hdinit", "hd70m", "hd40m", "hd40r", "hd70c", "hd70r", "hd20i",
	"hd70e", "hd114e", "hd310e", "hd310h"
};

int hd_type[NHD];		       /* type drive types (index into hdst) */
char hd_ecc_msgs;
int hderrblk;			       /* the current block number in error */

#ifdef DEBUG
#define HDDEBUG(how,stmt) if (hddebug & (how)) stmt	/* print it */
#else
#define HDDEBUG(how,stmt)	/* do nothing */
#endif

#define	SHOW_STRAT	0x01
#define SHOW_START	0x02
#define SHOW_REGS	0x04
#define SHOW_INIT	0x08
#define SHOW_WAIT	0x10
#define SHOW_COUNT	0x20
#define SHOW_XFER	0x40
#define SHOW_ORDWR	0x80
#define SHOW_BAD	0x800
#define SHOW_CONFIG	0x2000
#define SHOW_IOCTL	0x4000
#define SHOW_FORMAT	0x8000

int hddebug = 0;

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

int adap0ndisk = 0;	/* primary adapter can have this many disks (2 or 3) */

char *cmd_str();

int hdquiet = 0;
int hdquietinit = 0;	/* set by format.c (sigh) */

#ifndef SQUEEEEZE
int hd_any_ucode = 1;	/* XXX see format.c & hd_old_ucode in kernel hd.c */
#endif

/*
*	Strategy Routine:
*	Arguments:
*	  Pointer to  I/O buffer structure
*	  R/W function flag
*	Function:
*	  Start up the device
*/
hdstrategy(io, func)
	register struct iob *io;
{
	register struct hdst *st = &hdst[hd_type[io->i_unit]];
	register int rc;

	HDDEBUG(SHOW_STRAT, {
		printf("HD: HDSTRATEGY -- RECEIVED %s REQUEST",
		    cmd_str(io->i_flgs));
		printf("\n  bn= %d  cc= %d  ma= 0x%x",
		    io->i_bn, io->i_cc, io->i_ma);
		printf("\n  unit= 0x%x start block =%d\n",
		    io->i_unit, io->i_boff);
		printf("  ncpd=%d nspt=%d ntpc=%d\n",
		    st->ncpd, st->nspt, st->ntpc);
	}
	);

	DISPLAY(io->i_unit);		/* display the unit number */

	rc = hdio(io->i_bn, io->i_unit, io->i_ma, io->i_cc, st, io->i_flgs);
	if (rc < 0) {
		io->i_error = -rc;     /* return the error number */
		io->i_errblk = hderrblk; /* return block in error */
	}
	return (rc);
}


/*
 * low level I/O routine that actually does the operation and
 * returns the appropriate status information.
 * this is done this way so that the bad block forwarding can call hdio
 * to read the forwarded block without requiring clumsy flags to remember
 * what we are doing.
 */

hdio(bn, unit, addr, old_cc, st, flgs)
	register int bn;
	register caddr_t addr;
	register int unit;
	int old_cc;
	register struct hdst *st;
{
	register struct hddevice *adapter;
	register u_short cylinder;
	register u_long sector;
	register int len = 0;
	register int cc = old_cc;      /* working copy */
	register int status;
	register int error;
	register int drive;
	int errcnt = 0;			/* number of errors */
	register int chk_rdy;


	if (unit < adap0ndisk) {
		drive = unit;
		adapter = adapters[0];
	} else {
		drive = unit - adap0ndisk;
		adapter = adapters[1];
	}
/*
 * we come back to here when we are doing the rest of an interrupted
 * multi-block transfer after encountering a bad block
 */
restart:
	cylinder = (bn * (unsigned)(BSIZE / st->nbps)) /
	    (unsigned)(st->ntpc * st->nspt);
	sector = bn * (unsigned)(BSIZE / st->nbps);
	sector %= (st->nspt * st->ntpc);

	OUT(&adapter->PCMP, (st->pcmp >> 2));
	OUT(&adapter->INTR, DISKHD3);	/* Either enable pre-comp or head 8-15 */
	OUT(&adapter->CYLL, (cylinder & 0xff));
	OUT(&adapter->CYLH, ((cylinder >> 8) & 0xff));
	OUT(&adapter->SDH, 0xA0 | ((2&drive)<<1 | 1&drive)<<4 | sector/st->nspt);
	OUT(&adapter->SNO, ((sector % st->nspt) + 1));
	OUT(&adapter->SCT, (cc + (st->nbps - 1)) / st->nbps);

	hdwait(DISKBUSY | DISKRDY, DISKRDY, adapter, 1);

	HDDEBUG(SHOW_ORDWR | SHOW_REGS,
		printf("HDSTRATEGY: STARTING %s REQUEST...\n",
			cmd_str(flgs)));
	HDDEBUG(SHOW_REGS, {
		cylinder = ((IN(&adapter->CYLH) << 8) | IN(&adapter->CYLL));
		printf("blk=%d addr=%x count=%d", bn, addr, cc);
		printf(" Cyl=%d", cylinder);
		printf(" Hd=%d", IN(&adapter->SDH) & 0xF);
		printf(" Sect=%d", IN(&adapter->SNO));
		printf(" SCT=%d\n", IN(&adapter->SCT));
	}
	);

	switch (flgs & F_TYPEMASK) {

	case (F_RDDATA):

		if (flgs & F_SEVRE)
			OUT(&adapter->CMD, DISKREAD | DISKNORE);
		else
			OUT(&adapter->CMD, DISKREAD);
		chk_rdy = 0;
		break;

#ifndef SQUEEEEZE
	case (F_WRDATA):

		if (flgs & F_SEVRE)
			OUT(&adapter->CMD, DISKWRIT | DISKNORE);
		else
			OUT(&adapter->CMD, DISKWRIT);
		break;

	case (F_VERIFY | F_RDDATA):

		if (flgs & F_SEVRE)
			OUT(&adapter->CMD, DISKRDVR | DISKNORE);
		else
			OUT(&adapter->CMD, DISKRDVR);
		chk_rdy = 1;
		break;

	case (F_FORMAT | F_WRDATA):

		HDDEBUG(SHOW_FORMAT, {
			unsigned *ip = (unsigned *)addr;

			printf("Format data=");
			do {
				printf(" %x", *ip);
			} while (*ip++);
			printf("\n");
		});
		OUT(&adapter->SNO, 48);
		OUT(&adapter->SCT, st->nspt);
		OUT(&adapter->CMD, DISKFRMT);
		break;

#endif
	default:
/*		io->i_error = ECMD;	/* mark error type */
		flgs &= ~F_TYPEMASK;
		return (-ECMD);
	}
#ifndef SQUEEEEZE
/* on write command transfer the data now */
	if ((flgs & F_RDDATA) == 0) {
		hdwait(DISKDRQ|DISKBUSY, DISKDRQ, adapter, 1);
		len = hdwr(adapter, addr, cc);
		addr += len;
		cc -= len;
		chk_rdy = cc <= 0;
	}
#endif SQUEEEEZE
loop:
	hdwait(DISKBUSY, 0, adapter, 1);
	if (chk_rdy)
		hdwait(DISKRDY|DISKERR|DISKWFLT, 0, adapter, 0);
	else
		hdwait(DISKDRQ|DISKERR|DISKWFLT, 0, adapter, 0);
	switch ((status = IN(&adapter->STAT)) & DISKIMASK) {

	case DISKDRQ | DISKRDY:
		++bn;		       /* count the block */

		if (flgs & F_RDDATA)
			len = hdrd(adapter, addr, cc);
#ifndef SQUEEEEZE
		else
			len = hdwr(adapter, addr, cc);
#endif !SQUEEEEZE
		
		cc -= len;     /* adjust the count */
		addr += len;   /* adjust the address */
		chk_rdy = cc <= 0;
		goto loop;

	case DISKRDY:
		HDDEBUG(SHOW_ORDWR,
			printf("HD: %s REQUEST COMPLETE !!\n", cmd_str(flgs)));
		if (flgs & F_VERIFY)
			cc = 0;	       /* force proper return value */
		break;

#ifndef SQUEEEEZE
	case DISKWFLT:
		PRINTF(("HD: Write Fault --> Status = 0x%x\n", status));
		exit(4);
#endif !SQUEEEEZE

	default:

		if (status & DISKERR) {

/*
 * in the case of a bad block that is to be forwarded to another block
 * near the end of the disk we will first back off the changes to the
 * address and length (since the adapter buffer is loaded before we know
 * about he bad block error) 
 * we then call the bad block forwarding routine to calculate the 
 * block we are forwarding to. It will transfer that one block and 
 * then return.
 * if we have more blocks to transfer we will go to the start of this
 * routine to start the rest of the transfers from scratch where we
 * left off. 
 * we keep 'addr' 'cc' and 'bn' up to date for this purpose.
 */
			error = IN(&adapter->ERR);
			if (flgs & F_WRDATA) {
				addr -= len; /* length of last xfer */
				cc += len; /* back off last buffer load */
			}
			if ((error & DISKBBLK)) {
				if (((flgs & F_NBSF) == 0) && (len = hdbadblk(bn, unit, addr, cc, st, flgs)) > 0) {
					addr += len;
					cc -= len;
					++bn;
					if (cc > 0)
						goto restart;
					/* restart multiblock transfer */
					hderrblk = bn;
					return (old_cc - cc); /* completed */
				} else {
					check_error(cmd_str(flgs), adapter, unit);
					PRINTF(("HD: uncorrectable bad block\n"));
					hderrblk = bn;
					return (-EBSE);	/* flag error found */
				}
			} else if (error & DISKCRC) {
				if (hd_ecc_msgs)
					check_error(cmd_str(flgs), adapter, unit);
				hderrblk = bn;
				return (-EECC);
			} else {       /* other error status */
				check_error(cmd_str(flgs), adapter, unit);
				hderrblk = bn;
				if (++errcnt >= RETRY_COUNT)
					return (-EIO); /* other error */
				else {
					hdreset(adapter, unit, st); /* reset it */
					goto restart;
				}
			}
		}
		check_error(cmd_str(flgs), adapter, unit);
		PRINTF(("HD: UNKNOWN STATUS -- status = 0x%x\n", status));
		hderrblk = bn;
		return (-EIO);	       /* flag error found */
	}
	HDDEBUG(SHOW_ORDWR, printf("hdio: return(%d)\n", old_cc - cc));
	return (old_cc - cc);	       /* return amount transfered */
}


hdopen(io)
	register struct iob *io;
{
	register struct hdst *st = &hdst[hd_type[io->i_unit]];
	register struct hddevice *adapter;
	register int boff = io->i_boff;
	register int i;
	register struct partab *off;
	static int first = 0;

	HDDEBUG(SHOW_ORDWR,
	    printf("HD: IN HDOPEN()!!\n\n"));

	if ((io->i_unit < 0 || io->i_unit >= NHD) ||
	    boff < 0 || boff > 7) {
		PRINTF(("HD: BAD DRIVE NUMBER (%d) OR INVALID PARTITION (%d)\n", io->i_unit, boff));
		return(-ENXIO);
	}

	if (first++ == 0) {
		struct post *get_post(), *pp;

		if ((pp = get_post((u_short)(u_int)adapters[0])) == NULL)
			_stop("hd: No primary adapter?");
		adap0ndisk = pp->adap_type==ADAP_HD_HESDI ? 3 : 2;
	}
	adapter = adapters[io->i_unit >= adap0ndisk];

#ifndef SQUEEEEZE
	if (hd_any_ucode == 0) {	/* up-level ucode desired? */
		if (get_post((u_short)(u_int)adapter)->adap_type == ADAP_HD_HESDI) {
			OUT(&adapter->CMD, DISKDIAG);
			/* spin wait for "interrupt" (diagnose complete) */
			while (!(IN(&adapter->INTR) & 0x80))
				;
			/* verify ucode level */
			if (IN(&adapter->UCODELEVEL) < 0x31) {
				hd_any_ucode = 1;
				_stop("hd: Obsolete adapter microcode!\n");
			}
		}
		hd_any_ucode = 1;
	}
#endif

	for (i = RETRY_COUNT; i >= 0; --i)
		if (hdreset(adapter, io->i_unit, st) == HD_OK)
			break;

	if (i < 0)
		_stop("hd: Can't init drive");

	hdinit(adapter, io->i_unit);

	HDDEBUG(SHOW_INIT, printf("HD: HDOPEN -- INITIALIZATION COMPLETE\n\n"));

	st = &hdst[hd_type[io->i_unit]]; /* get proper table entry */

	off = hdoff[io->i_unit];	/* get partition table entry */

	if (off[boff].len == 0) {
		PRINTF(("HD%d: invalid partition %d\n", io->i_unit, boff));
		return(-ENXIO);
	}

	io->i_boff = off[boff].start * (st->nspt * st->ntpc);

	HDDEBUG(SHOW_ORDWR,
	    printf("HD: boff=%d ==>%d\n", boff, io->i_boff));
	if (hdreset(adapter, io->i_unit, st) != HD_OK)
		_stop("hd: can't re-init drive");
	return(0);
}


/*
 * reset the device by loading the parameters and then
 * doing a restore.
 * return 0	if all went well
 *       ~0	if it didn't
 * (return code is passed back from check_error)
 */
hdreset(adapter, unit, st)
	register struct hddevice *adapter;
	register int unit;
	register struct hdst *st;
{
	register int rc;
	u_char hd_sdh;
	register int drive;

	if (unit < adap0ndisk)
		drive = unit;
	else
		drive = unit - adap0ndisk;
	hd_sdh = 0xA0 | ((2&drive)<<1 | 1&drive)<<4 | st->ntpc-1;

	OUT(&adapter->INTR, DISKHD3);	/* Enable heads 8-15 */
	OUT(&adapter->SDH, hd_sdh);
	OUT(&adapter->PCMP, (st->pcmp >> 2));
	OUT(&adapter->CYLL, 0);
	OUT(&adapter->CYLH, 0);
	OUT(&adapter->SCT, 1);
	OUT(&adapter->SNO, 0);
	OUT(&adapter->CMD, DISKRSTR | st->step);
	DELAY(2);

	hdwait(DISKBUSY, 0, adapter, 1);
	if ((rc = check_error("RESTORE", adapter, unit)) == HD_BAD)
		return (rc);

	OUT(&adapter->SCT, st->nspt);
	OUT(&adapter->INTR, DISKHD3);	/* Enable heads 8-15 */
	OUT(&adapter->SDH, hd_sdh);
	OUT(&adapter->CMD, DISKSETP);
	DELAY(2);

	hdwait(DISKBUSY, 0, adapter, 1);
	return (check_error("SET PARAMETERS", adapter, unit));
}


/****************************************************************************
*                                                                           *
*       Disk error checking and  message routine			    *
*									    *
* results:								    *
*	== 0	all ok							    *
*	!= 0	error detected (and message printed)			    *
*                                                                           *
*****************************************************************************/

check_error(cmd, adapter, unit)
	register char *cmd;
	register struct hddevice *adapter;
	register int unit;
{
	register status;
	u_char error = IN(&adapter->ERR);

	status = IN(&adapter->STAT);
	if ((DISKERR & status) == 0)
		return (HD_OK);

	if (!(hdquiet && error == DISKIDNF)) {
		printf("HD%d: Disk I/O Error - CMD=%s\n", unit, cmd);
		PRINTF((" Err=0x%b ", error,
		    "\20\1MISSING-MARK\2TR000-ERROR\3CMD-ABORTED\5ID-NOT-FOUND\7DATA-CRC\10BAD-BLOCK"));
		PRINTF(("Status=0x%b ", status,
		    "\20\1ERROR\2INDEX-MARK\3ECC-CORRECTION\4DATA-REQ\5SEEK-DONE\6WRITE-FAULT\7DRIVE-READY\10BUSY"));
		printf("Cyl=%d ", ((IN(&adapter->CYLH) << 8) | IN(&adapter->CYLL)));
		printf("Hd=%d ", IN(&adapter->SDH) & 0xF);
		printf("Sect=%d ", IN(&adapter->SNO));
		printf("SCT=%d ", IN(&adapter->SCT));
		printf("\n");
	}

	return (HD_BAD);	       /* return error indication */
}


/*****************************************************************************
*                                                                            *
*       Wait for disk I/O to complete					     *
*									     *
******************************************************************************/
hdwait(mask, compare, adapter, equal)
	register unsigned char mask;
	register unsigned char compare;
	register struct hddevice *adapter;
	register int equal;
{
	register status;

	HDDEBUG(SHOW_WAIT, printf("hdwait(%x,%x,%x,%x) ...", mask, compare, adapter, equal));
	for (;;) {
		status = IN(&adapter->STAT);
		if (((status & mask) == compare) == equal)
			break;
		DELAY(2);
	}
	HDDEBUG(SHOW_WAIT, printf(" complete (%x)\n", status));
}



/*
 * read in the configuration record and look it up in known hdst
 * to determine the disk type and parameters
 */
hdinit(adapter, unit)
	register struct hddevice *adapter;
	register int unit;
{
	register struct hdst *st = &hdst[hd_type[unit]]; /* initial choice */
	register int i;
	struct hdconfig hdconfig;
	struct hdbadtmp hdbadtemp;	       /* temp place to read it in */
	register struct hdbad *hdb = (struct hdbad *) &hdbadtemp;
	register int ncyl, ntrack, nsector;

	hdquiet = hdquietinit;
	if ((i=hdio(CPARTOFF(CONFIG_BLOCK), unit, (caddr_t)&hdconfig, sizeof(struct hdconfig), st, F_RDDATA)) < 0 ||
	    hdconfig.conf_magic != CONFIG_MAGIC || !hdconfig.conf_lastsect) {
		if (!hdquietinit)
			PRINTF(("%s configuration record (block %d)\n", i<0 ? "could not read" : "bad", CPARTOFF(CONFIG_BLOCK)));
BadConfig:
		hdconfig.conf_maxcyl = 0;
		hdconfig.conf_lasttrack = st->ntpc-1;
		hdconfig.conf_lastsect = 17;

		hdconfig.conf_sectsize = 02;	       /* 15: sectors size 02 = 512 */
	}
	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",
	    unit, 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) {
		i += unit;	       /* allocate slot for it */
		st = &hdst[i];	       /* point to entry */
		PRINTF(("hd%d: %s type (%d cyl, %d tracks, %d sectors)\n",
		    unit, hdconfig.conf_name[0] ? hdconfig.conf_name : "unknown", ncyl, ntrack, nsector));
		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 */
		bcopy(hdconfig.conf_name,hdtype_names[i],sizeof hdconfig.conf_name);
	}
	HDDEBUG(SHOW_CONFIG, printf("hd%d: %s\n", unit, hdtype_names[i]));
	hd_type[unit] = i;
/*
 * read in the bad block table. If it is valid then allocate space for
 * it and read in the whole thing.
 */

	if (hdio(CPARTOFF(BAD_BLOCK_START), unit, (caddr_t)hdb, sizeof(struct hdbadtmp), st, F_RDDATA) < 0) {
		HDDEBUG(SHOW_CONFIG, printf("hd%d: could not read bad block table\n", unit));
	}
	if (hdb->hddefect[0] != 'D' || hdb->hddefect[1] != 'E' ||
	    hdb->hddefect[2] != 'F' || hdb->hddefect[3] != 'E' ||
	    hdb->hddefect[4] != 'C' || hdb->hddefect[5] != 'T' ||
	    hdb->hdcount < 0 || hdb->hdcount >= MAXHDBAD) {
		HDDEBUG(SHOW_CONFIG, printf("hd%d: no bad block table\n", unit));
	} else {
		register int len = (hdbadtemp.hdcount + 1) << 3;

		hdb = (struct hdbad *)calloc(len, 1);
		HDDEBUG(SHOW_BAD, printf("hd%d: %d bytes of bad block table at %x\n", unit, len, hdb));

		if (hdio(CPARTOFF(BAD_BLOCK_START), unit, (caddr_t)hdb, len, st, F_RDDATA) < 0) {
			HDDEBUG(SHOW_CONFIG, printf("hd%d: could not read bad block table\n", unit));
		}
		hdbad[unit] = hdb;     /* remember it */
	}
	bzero(hdoff[unit], 8 * sizeof (struct partab));
	bcopy(st->off+2, hdoff[unit]+2, sizeof (struct partab)); /* c part'n */
/* if no partition table on the disk then we'll use the default */
	if (!hdgetpart(adapter,unit,st,hdoff[unit]))
		bcopy(st->off, hdoff[unit], 8 * sizeof (struct partab));
	hdquiet = hdquietinit = 0;
}

typedef u_short hd_data_t;

#define HDXFERSIZE (sizeof (hd_data_t))

#define COUNT	16
 /*
  * this block read routine is used to copy data out of the adapter
  * for cases when 'buffaddr' is on a page boundary.
  * since it is on a page boundary it is also on a 32bit and a 16 bit
  * boundary.
  * flag == F_WRDATA	for write
  * flag == F_RDDATA 	for read
  */
adunload(buffaddr, adreg, hdcnt, flag)
	register hd_data_t * buffaddr;
	register int hdcnt;
	register hd_data_t * adreg;
	register int flag;
{
	HDDEBUG(SHOW_COUNT, printf("addr=%x count=%d ", buffaddr, hdcnt)); /* */
	if (flag & F_RDDATA) {
		if (buffaddr == (hd_data_t *)NO_ADDR) {
			register int i;
			while ((hdcnt -= HDXFERSIZE) >= 0)
				i = INW(adreg);
			return;
		}
		for (; (hdcnt -= HDXFERSIZE * COUNT) >= 0;) {
			buffaddr[0] = INW(adreg);
			buffaddr[1] = INW(adreg);
			buffaddr[2] = INW(adreg);
			buffaddr[3] = INW(adreg);
			buffaddr[4] = INW(adreg);
			buffaddr[5] = INW(adreg);
			buffaddr[6] = INW(adreg);
			buffaddr[7] = INW(adreg);
			buffaddr[8] = INW(adreg);
			buffaddr[9] = INW(adreg);
			buffaddr[10] = INW(adreg);
			buffaddr[11] = INW(adreg);
			buffaddr[12] = INW(adreg);
			buffaddr[13] = INW(adreg);
			buffaddr[14] = INW(adreg);
			buffaddr[15] = INW(adreg);
			buffaddr += COUNT;
		}
		hdcnt += HDXFERSIZE * COUNT; /* correct overshoot */
		for (; hdcnt > 0; hdcnt -= HDXFERSIZE)
			*buffaddr++ = INW(adreg);
	} else {
		if (buffaddr == (hd_data_t *)NO_ADDR) {
			while ((hdcnt -= HDXFERSIZE) >= 0)
				OUTW(adreg, 0);
			return;
		}
		for (; (hdcnt -= HDXFERSIZE * COUNT) >= 0;) {
			OUTW(adreg, buffaddr[0]);
			OUTW(adreg, buffaddr[1]);
			OUTW(adreg, buffaddr[2]);
			OUTW(adreg, buffaddr[3]);
			OUTW(adreg, buffaddr[4]);
			OUTW(adreg, buffaddr[5]);
			OUTW(adreg, buffaddr[6]);
			OUTW(adreg, buffaddr[7]);
			OUTW(adreg, buffaddr[8]);
			OUTW(adreg, buffaddr[9]);
			OUTW(adreg, buffaddr[10]);
			OUTW(adreg, buffaddr[11]);
			OUTW(adreg, buffaddr[12]);
			OUTW(adreg, buffaddr[13]);
			OUTW(adreg, buffaddr[14]);
			OUTW(adreg, buffaddr[15]);
			buffaddr += COUNT;
		}
		hdcnt += HDXFERSIZE * COUNT; /* correct overshoot */
		for (; hdcnt > 0; hdcnt -= HDXFERSIZE)
			OUTW(adreg, *buffaddr++);
	}
}


/*
 * read from the adapter
 * returns 'len' the number of bytes transfered
 */
hdrd(adapter, addr, cc)
	register struct hddevice *adapter;
	register caddr_t addr;
	register int cc;
{
	register struct hdst *st = &hdst[0];
	register int len = st->nbps;   /* all have same length */
	HDDEBUG(SHOW_XFER, printf("hdrd: adapter=%x addr=%x count=%d\n", adapter, addr, cc));
	if (len > cc)
		len = cc;
	adunload((hd_data_t *)addr, (hd_data_t *)adapter, len, F_RDDATA);
	if (len != st->nbps)
		adunload((hd_data_t *)NO_ADDR, (hd_data_t *)adapter, st->nbps - len, F_RDDATA);
	return (len);
}


#ifndef SQUEEEEZE
/*
 * write to the adapter
 * returns 'len' the number of bytes transfered
 */
hdwr(adapter, addr, cc)
	register struct hddevice *adapter;
	register caddr_t addr;
	register int cc;
{
	register struct hdst *st = &hdst[0];
	register int len = st->nbps;   /* all have same length */

	HDDEBUG(SHOW_XFER, printf("hdwr: adapter=%x addr=%x count=%d\n", adapter, addr, cc));
	if (len > cc)
		len = cc;
	adunload((hd_data_t *)addr, (hd_data_t *)adapter, len, F_WRDATA);
	if (len != st->nbps)
		adunload((hd_data_t *)NO_ADDR, (hd_data_t *)adapter, st->nbps - len, F_WRDATA);
	return (len);
}
#endif !SQUEEEEZE


/*
 * 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:
 *	-1	bad block is not in bad block table
 *	>= 0	worked (operation done on forwarded block)
 */

hdbadblk(bn, unit, addr, cc, st, flgs)
	register int bn;
	register int unit;
	register caddr_t addr;
	register int cc;
	register struct hdst *st;
{
	register struct hdbad *hdb = hdbad[unit];
/*
 * "bn" is the current block number (in error)
 */
	register int i;
	register int newbn = 0;	       /* replacement block number */

	bn -= CPARTOFF(0);		/* get relative to C partition */
	HDDEBUG(SHOW_BAD, printf("hd%d: bad block %d addr=%x count=%d\n", unit, bn, addr, cc));
	if (hdb == 0 || hdb->hddefect[0] != 'D' || hdb->hddefect[1] != 'E' ||
	    hdb->hddefect[2] != 'F' || hdb->hddefect[3] != 'E' ||
	    hdb->hddefect[4] != 'C' || hdb->hddefect[5] != 'T' ||
	    hdb->hdcount < 0 || hdb->hdcount >= MAXHDBAD)
		return (0);	       /* bad defect table - don't use it */
	for (i = 0; i < hdb->hdcount; ++i)
		if (hdb->hdmap[i].hdbad == bn) {
			newbn = hdb->hdmap[i].hdgood;
			break;
		}
	if (newbn == 0)
		return (-1);	       /* did not find it */
	HDDEBUG(SHOW_BAD, printf("hd%d: bad block %d forwarded to %d\n",
	    unit, bn, newbn));
/*
 * transfer only up to 1 block from the forwarded block 
 */
	if (cc > st->nbps)
		cc = st->nbps;
	i = hdio(CPARTOFF(newbn), unit, addr, cc, st, flgs);
	/* recursively call hdio for 1 block */
	return (i);		       /* return with sucess/fail indication */
}


/*
 *
 *       PERFORM SPECIAL DISK FUNCTIONS
 *
 */

hdioctl(io, cmd, arg)
	register struct iob *io;
	register int cmd;
	register char *arg;
{
#ifndef SQUEEEEZE
	register int unit = io->i_unit;
	register struct hdst *st = &hdst[hd_type[unit]];
	int flag;

	HDDEBUG(SHOW_IOCTL,
	    printf("unit=%x type=%x hdioctl(%x,%x,%x)\n", unit, hd_type[unit], io, cmd, arg));
	switch (cmd) {

	case (SAIODEBUG):

		flag = (int)arg;
		if (flag > 0)
			hddebug |= flag;
		else
			hddebug &= ~flag;
		return (0);

	case (SAIODEVDATA):

		{
			register struct st *tmp = (struct st *)arg;

			tmp->nsect = (short)st->nspt;
			tmp->ntrak = (short)st->ntpc;
			tmp->nspc = (short)st->nspc;
			tmp->nbps = (short)st->nbps;
			tmp->ncyl = (short)st->ncpd;
			tmp->steprate = (short)st->step;
			tmp->precompcyl = (short)st->pcmp;
			tmp->off = (struct partab *) hdoff[unit];
			{	/* return adapter type for format */
				register int adapter = unit>=adap0ndisk;
				struct post *get_post(), *pp;

				if ((pp = get_post((u_short)(u_int)adapters[adapter])) == NULL)
					_stop("hd: No adapter?");
				tmp->adap = pp->adap_type==ADAP_HD_HESDI ?
					    HD_ADAPTER_HESDI : HD_ADAPTER_ESDI;
			}
			return (0);
		}
	case (SAIOWDEVDATA):

		{
			register struct st *tmp = (struct st *)arg;

			st->nspt = tmp->nsect;
			st->ntpc = tmp->ntrak;
			st->nspc = tmp->nspc;
			st->nbps = tmp->nbps;
			st->ncpd = tmp->ncyl;
			st->step = tmp->steprate;
			st->pcmp = tmp->precompcyl;
			if (hdreset(adapters[unit >= adap0ndisk], unit, st) != HD_OK)
				_stop("hd: can't re-init drive");
			return (0);
		}
	case (SAIOECCMSGS):

		hd_ecc_msgs = (char)(u_int)arg;
		return (0);
	}
	io->i_error = ECMD;
#endif SQUEEEEZE
	return (-1);
}


hdgetpart(adapter,unit,st,off)
register struct hddevice *adapter;
register int unit;
struct hdst *st;
register struct partab *off;
{
	struct minidirectory minidirectory;
	int cylsize = st->nspc;

	if (hdio(CPARTOFF(MINIDISK_BLOCK), unit, (caddr_t) & minidirectory, sizeof(struct minidirectory), st, F_RDDATA) < 0) 
		return(0);		/* couldn't read it */
	return(dkfindpart(&minidirectory, off, cylsize, 0, unit, "hd"));
}

struct post *
get_post(io_addr)	/* shouldn't be part of hd.c? */
	register u_short io_addr;
{
	register struct post *pp;
	
	for (pp = &POST_CB.posts[0]; pp < &POST_CB.posts[NPOSTS]; pp++) {
		if (pp->primary == io_addr)
			return (pp);
	}
	return (NULL);
}

char *
cmd_str(flgs)
	register int	flgs;
{
	switch(flgs & F_TYPEMASK) {
		case F_RDDATA:
			return(flgs&F_SEVRE ? "READ (no retries)" : "READ");
		case F_WRDATA:
			return(flgs&F_SEVRE ? "WRITE (no retries)" : "WRITE");
		case F_VERIFY|F_RDDATA:
			return(flgs&F_SEVRE ? "READ-VERIFY (no retries)" : "READ-VERIFY");
		case F_FORMAT|F_WRDATA:
			return("FORMAT");
		default:
			return("UNKNOWN COMMAND");
	}
}
