File:  [MW Coherent from dump] / coherent / b / kernel / io.386 / at.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs
Wed May 29 04:56:37 2019 UTC (7 years ago) by root
Branches: MarkWilliams, MAIN
CVS tags: relic, HEAD
coherent

/* (-lgl
 * 	COHERENT Driver Kit Version 1.1.0
 * 	Copyright (c) 1982, 1990 by Mark Williams Company.
 * 	All rights reserved. May not be copied without permission.
 *
 -lgl) */
/*
 * This is a driver for the
 * hard disk on the AT.
 *
 * Reads drive characteristics from ROM (thru interrupt vector 0x41 and 0x46).
 * Reads partition information from disk.
 */

/*
 * -----------------------------------------------------------------
 * Includes.
 */
#include	<sys/coherent.h>
#include 	<sys/fdisk.h>
#include	<sys/hdioctl.h>
#include	<sys/buf.h>
#include	<sys/con.h>
#include	<sys/devices.h>
#include	<sys/stat.h>
#include	<sys/typed.h>
#include	<errno.h>

/*
 * -----------------------------------------------------------------
 * Definitions.
 *	Constants.
 *	Macros with argument lists.
 *	Typedefs.
 *	Enums.
 */
/*
 * Configurable parameters
 */
#define	HDIRQ	14			/* Level 14 */
#define	HDBASE	0x01F0			/* Port base */
#define NDRIVE	2			/* only two drives supported */
#define	SOFTLIM	6			/*  (7) num of retries before diag */
#define	HARDLIM	8			/* number of retries before fail */
#define	BADLIM	100			/* num to stop recov if flagged bad */

#define	BIT(n)		(1 << (n))

#define	CMOSA	0x70			/* write cmos address to this port */
#define	CMOSD	0x71			/* read cmos data through this port */

#ifdef _I386
#  define	ATCACHE 0	/* no cache for us in this driver code */
#  define	VERBOSE	1
#else
#  ifndef	ATCACHE
#    if VERBOSE > 0
#      define	ATCACHE	2	/* local cache size in blocks */
#    else
#      define	ATCACHE 0	/* no cache for small code */
#    endif
#  endif
#endif

/*
 * I/O Port Addresses
 */
#define	DATA_REG	(HDBASE+0)	/* data (r/w) */
#define	AUX_REG		(HDBASE+1)	/* error(r), write precomp cyl/4 (w) */
#define	NSEC_REG	(HDBASE+2)	/* sector count (r/w) */
#define	SEC_REG		(HDBASE+3)	/* sector number (r/w) */
#define	LCYL_REG	(HDBASE+4)	/* low cylinder (r/w) */
#define	HCYL_REG	(HDBASE+5)	/* high cylinder (r/w) */
#define	HDRV_REG	(HDBASE+6)	/* drive/head (r/w) (D<<4)+(1<<H) */
#define	CSR_REG		(HDBASE+7)	/* status (r), command (w) */
#define	HF_REG		(HDBASE+0x206)	/* Usually 0x3F6 */

/*
 * Error from AUX_REG (r)
 */
#define	DAM_ERR		BIT(0)		/* data address mark not found */
#define	TR0_ERR		BIT(1)		/* track 000 not found */
#define	ABT_ERR		BIT(2)		/* aborted command */
#define	ID_ERR		BIT(4)		/* id not found */
#define	ECC_ERR		BIT(6)		/* data ecc error */
#define	BAD_ERR		BIT(7)		/* bad block detect */

/*
 * Status from CSR_REG (r)
 */
#define	ERR_ST		BIT(0)		/* error occurred */
#define	INDEX_ST	BIT(1)		/* index pulse */
#define	SOFT_ST		BIT(2)		/* soft (corrected) ECC error */
#define	DRQ_ST		BIT(3)		/* data request */
#define	SKC_ST		BIT(4)		/* seek complete */
#define	WFLT_ST		BIT(5)		/* improper drive operation */
#define	RDY_ST		BIT(6)		/* drive is ready */
#define	BSY_ST		BIT(7)		/* controller is busy */

/*
 * Commands to CSR_REG (w)
 */
#define	RESTORE(rate)	(0x10+(rate))	/* X */
#define	SEEK(rate)	(0x70+(rate))	/* X */
#define	READ_CMD	(0x20)		/* X */
#define	WRITE_CMD	(0x30)		/* X */
#define	FORMAT_CMD	(0x50)		/* X */
#define	VERIFY_CMD	(0x40)		/* X */
#define	DIAGNOSE_CMD	(0x90)		/* X */
#define	SETPARM_CMD	(0x91)		/* X */

/*
 * Device States.
 */
#define	SIDLE	0			/* controller idle */
#define	SRETRY	1			/* seeking */
#define	SREAD	2			/* reading */
#define	SWRITE	3			/* writing */

/*
 * -----------------------------------------------------------------
 * Functions.
 *	Import Functions.
 *	Export Functions.
 *	Local Functions.
 */
extern int	nulldev();
extern int	nonedev();

/*
 * Driver configuration.
 */
static void	atload();
static void	atunload();
static void	atopen();
static void	atread();
static void	atwrite();
static int	atioctl();
static void	atwatch();
static void	atblock();

/*
 * Forward Referenced Functions.
 */
static void	atreset();
static int	atdequeue();
static void	atstart();
static void	atintr();
static void	atdefer();
static int	aterror();
static void	atrecov();
static void	atdone();

/*
 * -----------------------------------------------------------------
 * Global Data.
 *	Import Variables.
 *	Export Variables.
 *	Local Variables.
 */
extern typed_space	boot_gift;
extern short		n_atdr;

#ifndef _I386
extern saddr_t		sds;
#endif

CON	atcon	= {
	DFBLK|DFCHR,			/* Flags */
	AT_MAJOR,			/* Major index */
	atopen,				/* Open */
	nulldev,			/* Close */
	atblock,			/* Block */
	atread,				/* Read */
	atwrite,			/* Write */
	atioctl,			/* Ioctl */
	nulldev,			/* Powerfail */
	atwatch,			/* Timeout */
	atload,				/* Load */
	atunload			/* Unload */
};

/*
 * Patchable variables.
 *	ATSECS is number of seconds to wait for an expected interrupt.
 *	ATSREG needs to be 3F6 for most new IDE drives;  needs to be
 *		1F7 for Perstor controllers and some old IDE drives.
 *		Either value works with most drives.
 */
int	ATSECS = 6;
int	ATSREG = 0x3F6;

/*
 * Drive Parameters - copied from ROM.
 * If patched, use the given values instead of reading from the ROM.
 * NOTE: Exactly duplicates hdparm_s struct.
 */
struct dparm_s {
	unsigned short	d_ncyl;		/* number of cylinders */
	unsigned char	d_nhead;	/* number of heads */
#pragma align 1
	unsigned short	d_rwcc;		/* reduced write current cyl */
	unsigned short	d_wpcc;		/* write pre-compensation cyl */
#pragma align
	unsigned char	d_eccl;		/* max ecc data length */
	unsigned char	d_ctrl;		/* control byte */
	unsigned char	d_fill2[3];
	unsigned short	d_landc;	/* landing zone cylinder */
	unsigned char	d_nspt;		/* number of sectors per track */
	unsigned char	d_fill3;

}	atparm[ NDRIVE ] = {
	0				/* Initialized to allow patching */
};

/*
 * Partition Parameters - copied from disk.
 *
 *	There are NDRIVE * NPARTN positions for the user partitions,
 *	plus NDRIVE additional partitions to span each drive.
 *
 *	Aligning partitions on cylinder boundaries:
 *	Optimal partition size: 2 * 3 * 4 * 5 * 7 * 17 = 14280 blocks
 *	Acceptable partition size:  3 * 4 * 5 * 7 * 17 =  7140 blocks
 */
static struct fdisk_s pparm[NDRIVE*NPARTN + NDRIVE];

/*
 * Per disk controller data.
 * Only one controller; no more, no less.
 */
static struct	at	{
	BUF		*at_actf;	/* Link to first */
	BUF		*at_actl;	/* Link to last */
#ifdef _I386
	paddr_t		at_addr;	/* Source/Dest virtual address */
#else
	faddr_t		at_addr;
#endif
	daddr_t		at_bno;		/* Block # on disk */
	unsigned	at_nsec;	/* # of sectors on current transfer */
	unsigned	at_drv;
	unsigned	at_head;
	unsigned	at_cyl;
	unsigned	at_sec;
	unsigned	at_partn;
	unsigned char	at_dtype[ NDRIVE ];	/* drive type, 0 if unused */
	unsigned char	at_tries;
	unsigned char	at_state;
	unsigned char	at_caching;		/* caching in progress */
#if	ATCACHE > 0
	unsigned char	at_cdrv[ ATCACHE ];	/* cached drive */
	daddr_t		at_cbno[ ATCACHE ];	/* cached block number */
	unsigned char *	at_cbuf[ ATCACHE ];	/* cached block */
#endif
	unsigned	at_bad_drv;
	unsigned	at_bad_head;
	unsigned	at_bad_cyl;
}	at;

static BUF	dbuf;			/* For raw I/O */

static char timeout_msg[] = "at%d: TO\n";

/**
 *
 * void
 * atload()	- load routine.
 *
 *	Action:	The controller is reset and the interrupt vector is grabbed.
 *		The drive characteristics are set up at this time.
 */
static void
atload()
{
	register unsigned int u;
	register struct dparm_s * dp;
	struct { unsigned short off, seg; } p;

	if (n_atdr <= 0)
		return;

	/* Flag drives 0, 1 as present or not. */
	at.at_dtype[0] = 1;
	at.at_dtype[1] = n_atdr > 1 ? 1 : 0;

#if 0
/* hex dump boot gift */
{
int bgi;
unsigned char * bgp = (char *)&boot_gift;
printf("&boot_gift = %lx", &boot_gift);
for (bgi = 0; bgi < 80; bgi++) {
	printf(" %x", (*bgp++));
}
}
#endif

	/*
	 * Obtain Drive Characteristics.
	 */
	for (u = 0, dp = &atparm[0]; u < n_atdr; ++dp, ++u) {
		struct dparm_s int_dp;

		if (dp->d_ncyl == 0) {
			/*
			 * Not patched.
			 *
			 * If tertiary boot sent us parameters,
			 *   Use "fifo" routines to fetch them.
			 *   This only gives us ncyl, nhead, and nspt.
			 *   Make educated guesses for other parameters:
			 *   Set landc to ncyl, wpcc to -1.
			 *   Set ctrl to 0 or 8 depending on head count.
			 *
			 * Follow INT 0x41/46 to get drive static BIOS drive
			 * parameters, if any.
			 *
			 * If there were no parameters from tertiary boot,
			 * or if INT 0x4? nhead and nspt match tboot parms,
			 *   use "INT" parameters (will give better match on
			 *   wpcc, landc, and ctrl fields, which tboot can't
			 *   give us).
			 */

			FIFO *ffp;
			typed_space *tp;
			int found, parm_int;

if (F_NULL != (ffp = fifo_open(&boot_gift, 0))) {
			for (found = 0; !found && (tp = fifo_read(ffp)); ) {
				BIOS_DISK *bdp = (BIOS_DISK *)tp->ts_data;
				if ((T_BIOS_DISK == tp->ts_type) &&
				    (u == bdp->dp_drive) ) {
					found = 1;
					dp->d_ncyl = bdp->dp_cylinders;
					dp->d_nhead = bdp->dp_heads;
					dp->d_nspt = bdp->dp_sectors;
					dp->d_wpcc = 0xffff;
					dp->d_landc = dp->d_ncyl;
					if (dp->d_nhead > 8)
						dp->d_ctrl |= 8;
				}
			}
			fifo_close(ffp);
}

			if (u == 0)
				parm_int = 0x41;
			else /* (u == 1) */
				parm_int = 0x46;
#ifdef _I386
			pxcopy((paddr_t)(parm_int*4), &p, sizeof p, SEG_386_KD);
			pxcopy((paddr_t)(p.seg<<4L)+p.off,
				&int_dp, sizeof(int_dp), SEG_386_KD);
#else
			pkcopy((paddr_t)(parm_int*4), &p, sizeof p);
			pkcopy((paddr_t) (p.seg << 4L) + p.off,
				&int_dp, sizeof(int_dp));
#endif
			if (!found || 
			    (dp->d_nhead == int_dp.d_nhead
			     && dp->d_nspt == int_dp.d_nspt)) {
			     *dp = int_dp;
				printf("Using INT 0x%x",parm_int);
			} else
				printf("Using INT 0x13(08)");
		} else {
			printf("Using patched");
			/*
			 * Avoid incomplete patching.
			 */
			if (at.at_dtype[u] == 0)
				at.at_dtype[u] = 1;
			if (dp->d_nspt == 0)
				dp->d_nspt = 17;
#if FORCE_CTRL_8
			if (dp->d_nhead > 8)
				dp->d_ctrl |= 8;
#endif

		}
#if VERBOSE > 0
	printf(" drive %d parameters\n", u);

	/* intersegment printf only gets 6 words of arguments */	
	printf( "at%d: ncyl=%d nhead=%d wpcc=%d ",
	  u, dp->d_ncyl, dp->d_nhead, dp->d_wpcc);
	printf(" eccl=%d ctrl=%d landc=%d nspt=%d\n",
	  dp->d_eccl, dp->d_ctrl, dp->d_landc, dp->d_nspt);
#endif
	}

	/*
	 * Initialize Drive Size.
	 */
	for (u = 0, dp = &atparm[0]; u < n_atdr; ++dp, ++u) {

		if (at.at_dtype[u] == 0)
			continue;

		pparm[NDRIVE*NPARTN + u].p_size =
			(long) dp->d_ncyl * dp->d_nhead * dp->d_nspt;
	}

	/*
	 * Initialize Drive Controller.
	 */
	atreset();

	setivec(HDIRQ, atintr);

#if ATCACHE > 0
	at.at_cdrv[0] = -1;
	at.at_cbuf[0] = kalloc(BSIZE);
#endif

#if ATCACHE > 1
	at.at_cdrv[1] = -1;
	at.at_cbuf[1] = kalloc(BSIZE);
#endif

	at.at_bad_drv = -1;
}

/**
 *
 * void
 * atunload()	- unload routine.
 */
static void
atunload()
{
	clrivec(HDIRQ);
}

/**
 *
 * void
 * atreset()	-- reset hard disk controller, define drive characteristics.
 */
static void
atreset()
{
	register int u;
	register struct dparm_s * dp;

	/*
	 * Reset controller for a minimum of 4.8 microseconds.
	 */
	outb(HF_REG, 4);
	for (u = 100; --u != 0;)
		;
	outb(HF_REG, atparm[0].d_ctrl & 0x0F);
	myatbsyw(0);
	if (inb(AUX_REG) != 0x01) {
		/*
		 * Some IDE drives always timeout on initial reset.
		 * So don't report first timeout.
		 */
		static one_bad;

		if (one_bad) {
			printf("at: hd controller reset timeout\n");
		} else
			one_bad = 1;
	}

	/*
	 * Initialize drive parameters.
	 */
	for (u = 0, dp = &atparm[0]; u < n_atdr; ++dp, ++u) {

		if (at.at_dtype[u] == 0)
			continue;

		myatbsyw(u);

		/*
		 * Set drive characteristics.
		 * 0x1F1 - AUX_REG
		 * 0x1F2 - NSEC_REG
		 * 0x1F3 - SEC_REG
		 * 0x1F4 - LCYL_REG
		 * 0x1F5 - HCYL_REG
		 * 0x1F6 - HDRV_REG
		 * 0x1F7 - CSR_REG
		 */
		outb(HF_REG,	dp->d_ctrl);
		outb(AUX_REG,  dp->d_wpcc / 4);
		outb(NSEC_REG, dp->d_nspt);
		outb(SEC_REG, 0x01);
		outb(LCYL_REG, (char)(dp->d_ncyl));
		outb(HCYL_REG, (char)(dp->d_ncyl >> 8));
		outb(HDRV_REG, 0xA0 + (u<<4) + dp->d_nhead - 1);
		outb(CSR_REG,  SETPARM_CMD);
		myatbsyw(u);

		/*
		 * Restore heads.
		 */
		outb(CSR_REG, RESTORE(0));
		myatbsyw(u);
	}
}

/**
 *
 * void
 * atopen(dev, mode)
 * dev_t dev;
 * int mode;
 *
 *	Input:	dev = disk device to be opened.
 *		mode = access mode [IPR,IPW, IPR+IPW].
 *
 *	Action:	Validate the minor device.
 *		Update the paritition table if necessary.
 */
static void
atopen(dev, mode)
register dev_t	dev;
{
	register int d;		/* drive */
	register int p;		/* partition */

	p = minor(dev) % (NDRIVE*NPARTN);

	if (minor(dev) & SDEV) {
		d = minor(dev) % NDRIVE;
		p += NDRIVE * NPARTN;
	}
	else
		d = minor(dev) / NPARTN;

	if ((d >= NDRIVE) || (at.at_dtype[d] == 0)) {
printf("atopen: drive not present ");
		u.u_error = ENXIO;
		return;
	}

	if (minor(dev) & SDEV) {
		return;
	}

	/*
	 * If partition not defined read partition characteristics.
	 */
	if (pparm[p].p_size == 0)
		fdisk(makedev(major(dev), SDEV + d), &pparm[ d * NPARTN ]);

	/*
	 * Ensure partition lies within drive boundaries and is non-zero size.
	 */
	if ((pparm[p].p_base+pparm[p].p_size) > pparm[d+NDRIVE*NPARTN].p_size) {
#ifdef _I386
printf("atopen: p_size too big ");
		u.u_error = EINVAL;
#else
		u.u_error = EBADFMT;
#endif
	} else if (pparm[p].p_size == 0) {
printf("atopen: p_size zero ");
		u.u_error = ENODEV;
	}
}

/**
 *
 * void
 * atread(dev, iop)	- write a block to the raw disk
 * dev_t dev;
 * IO * iop;
 *
 *	Input:	dev = disk device to be written to.
 *		iop = pointer to source I/O structure.
 *
 *	Action:	Invoke the common raw I/O processing code.
 */
static void
atread(dev, iop)
dev_t	dev;
IO	*iop;
{
	ioreq(&dbuf, iop, dev, BREAD, BFRAW|BFBLK|BFIOC);
}

/**
 *
 * void
 * atwrite(dev, iop)	- write a block to the raw disk
 * dev_t dev;
 * IO * iop;
 *
 *	Input:	dev = disk device to be written to.
 *		iop = pointer to source I/O structure.
 *
 *	Action:	Invoke the common raw I/O processing code.
 */
static void
atwrite(dev, iop)
dev_t	dev;
IO	*iop;
{
	ioreq(&dbuf, iop, dev, BWRITE, BFRAW|BFBLK|BFIOC);
}

/**
 *
 * int
 * atioctl(dev, cmd, arg)
 * dev_t dev;
 * int cmd;
 * char * vec;
 *
 *	Input:	dev = disk device to be operated on.
 *		cmd = input/output request to be performed.
 *		vec = (pointer to) optional argument.
 *
 *	Action:	Validate the minor device.
 *		Update the paritition table if necessary.
 */
static int
atioctl(dev, cmd, vec)
register dev_t	dev;
int cmd;
char * vec;
{
	int d;

	/*
	 * Identify drive number.
	 */
	if (minor(dev) & SDEV)
		d = minor(dev) % NDRIVE;
	else
		d = minor(dev) / NPARTN;

	/*
	 * Identify input/output request.
	 */
	switch (cmd) {

	case HDGETA:
		/*
		 * Get hard disk attributes.
		 */
		kucopy(&atparm[d], vec, sizeof(atparm[0]));
		return(0);

	case HDSETA:
		/* Set hard disk attributes. */
		ukcopy(vec, &atparm[d], sizeof(atparm[0]));
		at.at_dtype[d] = 1;		/* set drive type nonzero */
		pparm[NDRIVE * NPARTN + d].p_size =
			(long) atparm[d].d_ncyl * atparm[d].d_nhead * atparm[d].d_nspt;
		atreset();
		return 0;

	default:
		u.u_error = EINVAL;
		return(-1);
	}
}

/**
 *
 * void
 * atwatch()		- guard against lost interrupt
 *
 *	Action:	If drvl[AT_MAJOR] is greater than zero, decrement it.
 *		If it decrements to zero, simulate a hardware interrupt.
 */
static void
atwatch()
{
	register BUF * bp = at.at_actf;
	register int s;

	s = sphi();
	if (--drvl[AT_MAJOR].d_time > 0) {
		spl(s);
		return;
	}
	printf("at%d%c: bno=%U head=%u cyl=%u <Watchdog Timeout>\n",
		at.at_drv,
		(bp->b_dev & SDEV) ? 'x' : at.at_partn % NPARTN + 'a',
		bp->b_bno, at.at_head, at.at_cyl);

	/*
	 * Reset hard disk controller.
	 *
	 * Mark current cylinder as bad so atstart() will fail.
	 * Otherwise would lock up if this track NEVER gives enough IRQ's.
	 */
	at.at_bad_drv	= at.at_drv;
	at.at_bad_head	= at.at_head;
	at.at_bad_cyl	= at.at_cyl;
	atreset();
	atstart();
	spl(s);
}

/**
 *
 * void
 * atblock(bp)	- queue a block to the disk
 *
 *	Input:	bp = pointer to block to be queued.
 *
 *	Action:	Queue a block to the disk.
 *		Make sure that the transfer is within the disk partition.
 */
static void
atblock(bp)
register BUF	*bp;
{
	register struct fdisk_s *pp;
	int partn = minor(bp->b_dev) % (NDRIVE*NPARTN);

	bp->b_resid = bp->b_count;

	if (minor(bp->b_dev) & SDEV)
		partn += NDRIVE * NPARTN;

	pp = &pparm[ partn ];

	/*
	 * Check for read at end of partition.
	 */
	if ((bp->b_req == BREAD) && (bp->b_bno == pp->p_size)) {
		bdone(bp);
		return;
	}

	/*
	 * Range check disk region.
	 */
	if (((bp->b_bno + (bp->b_count/BSIZE)) > pp->p_size)
	|| (bp->b_count % BSIZE) || bp->b_count == 0) {
		bp->b_flag |= BFERR;
		bdone(bp);
		return;
	}

	bp->b_actf = NULL;
	if (at.at_actf == NULL)
		at.at_actf = bp;
	else
		at.at_actl->b_actf = bp;
	at.at_actl = bp;

	if (at.at_state == SIDLE)
		if (atdequeue())
			atstart();
}

/**
 *
 * int
 * atdequeue()		- obtain next disk read/write operation
 *
 *	Action:	Pull some work from the disk queue.
 *
 *	Return:	0 = no work.
 *		* = work to do.
 */
static int
atdequeue()
{
	register BUF * bp;
	register struct fdisk_s * pp;
	unsigned int nspt;

	for (;;) {
		at.at_caching = 0;
		at.at_tries   = 0;

		if ((bp = at.at_actf) == NULL)
			return (0);

		at.at_partn = minor(bp->b_dev) % (NDRIVE*NPARTN);

		if (minor(bp->b_dev) & SDEV) {
			at.at_partn += (NDRIVE*NPARTN);
			at.at_drv  = minor(bp->b_dev) % NDRIVE;
		}
		else
			at.at_drv = minor(bp->b_dev) / NPARTN;
		nspt = atparm[at.at_drv].d_nspt;

		pp = &pparm[ at.at_partn ];
		at.at_bno   = pp->p_base + bp->b_bno;
		at.at_nsec  = bp->b_count / BSIZE;
#ifdef _I386
		at.at_addr = bp->b_paddr;
#else
		at.at_addr = bp->b_faddr;
#endif

#if ATCACHE > 0
		if (bp->b_req == BWRITE) {

			/*
			 * Invalidate cache if write might overlap.
			 */
			if (at.at_nsec > 1) {
				at.at_cdrv[0] = -1;
#if ATCACHE > 1
				at.at_cdrv[1] = -1;
#endif
			}
			else if (at.at_bno == at.at_cbno[0])
				at.at_cdrv[0] = -1;
#if ATCACHE > 1
			else if (at.at_bno == at.at_cbno[1])
				at.at_cdrv[1] = -1;
#endif
		}
		else if (at.at_nsec == 1) {

			/*
			 * Test for cache hit on block 0.
			 */
			if ((at.at_drv == at.at_cdrv[0])
			&&   (at.at_bno == at.at_cbno[0])) {
#ifdef _I386
				xpcopy(at.at_cbuf[0], bp->b_paddr,
					BSIZE, SEG_386_KD+SEG_VIRT);
#else
				kpcopy(at.at_cbuf[0], bp->b_paddr, BSIZE);
#endif
				at.at_actf  = bp->b_actf;
				bp->b_resid = 0;
				bdone(bp);
				continue;
			}

#if ATCACHE > 1
			/*
			 * Test for cache hit on block 1.
			 */
			if ((at.at_drv == at.at_cdrv[1])
			&&   (at.at_bno == at.at_cbno[1])) {
#ifdef _I386
				xpcopy(at.at_cbuf[1], bp->b_paddr,
					BSIZE, SEG_386_KD|SEG_VIRT);
#else
				kpcopy(at.at_cbuf[1], bp->b_paddr, BSIZE);
#endif
				at.at_actf  = bp->b_actf;
				bp->b_resid = 0;
				bdone(bp);
				continue;
			}
#endif

			/*
			 * Enable caching if no backlog for disk i/o.
			 */
			if (bp->b_actf == NULL) {
				/*
				 * Enable caching on single block reads
				 * when at least one block left on same track.
				 */
				at.at_caching = nspt - 1 - (at.at_bno % nspt);
#if ATCACHE > 1
				if (at.at_caching >= 2) {
					at.at_caching   = 2;
					at.at_cdrv[2-1] = -1;
				}
#endif

				if (at.at_caching) {
					at.at_nsec  += at.at_caching;
					at.at_cdrv[1-1] = -1;
				}
			}
		}
#endif

		return (1);
	}
}

/**
 *
 * void
 * atstart()	- start or restart next disk read/write operation.
 *
 *	Action:	Initiate disk read/write operation.
 */
static void
atstart()
{
	register struct dparm_s *dp;

	dp = &atparm[ at.at_drv ];

	at.at_cyl  = (at.at_bno / dp->d_nspt) / dp->d_nhead;
	at.at_head = (at.at_bno / dp->d_nspt) % dp->d_nhead;
	at.at_sec  = (at.at_bno % dp->d_nspt) + 1;

	/*
	 * Check for repeated access to most recently identified bad track.
	 */
	if ((at.at_drv  == at.at_bad_drv)
	  && (at.at_cyl  == at.at_bad_cyl)
	  && (at.at_head == at.at_bad_head)) {
	  	BUF * bp = at.at_actf;
		printf("at%d%c: bno=%U head=%u cyl=%u <Track Flagged Bad>\n",
			at.at_drv,
			(bp->b_dev & SDEV) ? 'x' : at.at_partn % NPARTN + 'a',
			bp->b_bno,
			at.at_head,
			at.at_cyl);
		bp->b_flag |= BFERR;
		atdone(bp);
		return;
	}

	myatbsyw(at.at_drv);

	outb(HF_REG,   dp->d_ctrl);
	outb(AUX_REG,  dp->d_wpcc / 4);
	outb(NSEC_REG, at.at_nsec);
	outb(SEC_REG,  at.at_sec);
	outb(LCYL_REG, at.at_cyl);
	outb(HCYL_REG, at.at_cyl >> 8);
	outb(HDRV_REG, (at.at_drv << 4) + at.at_head + 0xA0);

	if (at.at_actf->b_req == BWRITE) {

		outb(CSR_REG, WRITE_CMD);

		while (atdrq() == 0)
			printf(timeout_msg, at.at_drv);

		atsend(at.at_addr);
		at.at_state = SWRITE;
	}
	else {
		outb(CSR_REG, READ_CMD);
		at.at_state = SREAD;
	}
	drvl[AT_MAJOR].d_time = ATSECS;
}

/**
 *
 * void
 * atintr()	- Interrupt routine.
 *
 *	Clear interrupt then defer actual processing.
 */
static void
atintr()
{
	inb(CSR_REG);		/* clears controller interrupt */
	defer(atdefer, 0);
}

/**
 *
 * void
 * atdefer()	- Deferred service of hard disk interrupt.
 *
 *	Action:	Service disk interrupt.
 *		Transfer required data.
 *		Update state.
 */
static void
atdefer()
{
	register BUF * bp = at.at_actf;

	switch (at.at_state) {

	case SRETRY:
		atstart();
		break;

	case SREAD:
		/*
		 * Check for I/O error before waiting for data.
		 */
		if (aterror()) {
			atrecov();
			break;
		}

		/*
		 * Wait for data, or forever.
		 */
		if (atdrq() == 0)
			printf(timeout_msg, at.at_drv);

#if ATCACHE > 0
		/*
		 * Cache data block.
		 */
		if (at.at_caching == at.at_nsec) {
#ifdef _I386
			atrecv(at.at_cbuf[ at.at_nsec - 1 ]);
#else
			atrecv(at.at_cbuf[ at.at_nsec - 1 ], sds);
#endif
		} else
#endif
		/*
		 * Read data block.
		 */
			atrecv(at.at_addr);

		/*
		 * Check for I/O error after reading data.
		 */
		if (aterror()) {
			atrecov();
			break;
		}

#if ATCACHE > 0
		/*
		 * Validate cached blocks.
		 */
		if (at.at_caching == at.at_nsec) {
			at.at_cbno[ at.at_nsec - 1 ] = at.at_bno;
			at.at_cdrv[ at.at_nsec - 1 ] = at.at_drv;
			at.at_caching--;
		}
		else
#endif
		{
#ifdef _I386
			at.at_addr += BSIZE;
#else
			FP_OFF(at.at_addr) += BSIZE;
#endif
			bp->b_resid -= BSIZE;
		}

		at.at_tries = 0;
		at.at_bno++;

		/*
		 * Check for end of transfer.
		 */
		if (--at.at_nsec == 0)
			atdone(bp);
		break;

	case SWRITE:
		/*
		 * Check for I/O error.
		 */
		if (aterror()) {
			atrecov();
			break;
		}

#ifdef _I386
		at.at_addr += BSIZE;
#else
		FP_OFF(at.at_addr) += BSIZE;
#endif
		bp->b_resid -= BSIZE;
		at.at_tries  = 0;
		at.at_bno++;

		/*
		 * Check for end of transfer.
		 */
		if (--at.at_nsec == 0) {
			atdone(bp);
			break;
		}

		/*
		 * Wait for ability to send data, or forever.
		 */
		while (atdrq() == 0)
			printf(timeout_msg, at.at_drv);

		/*
		 * Send data block.
		 */
		atsend(at.at_addr);
	}
}

/**
 *
 * int
 * aterror()
 *
 *	Action:	Check for drive error.
 *		If found, increment error count and report it.
 *
 *	Return: 0 = No error found.
 *		1 = Error occurred.
 */
static int
aterror()
{
	register BUF * bp = at.at_actf;
	register int csr;
	register int aux;

	if ((csr = inb(ATSREG)) & (ERR_ST|WFLT_ST)) {

		aux = inb(AUX_REG);

		/*
		 * Don't retry or report failures on cache reads.
		 */
#if ATCACHE > 0
		if ((at.at_state == SREAD) && (at.at_caching == at.at_nsec)) {
			at.at_tries = BADLIM;
			return 1;
		}
#endif

		if (aux & BAD_ERR) {
			at.at_tries	= BADLIM;
			at.at_bad_drv	= at.at_drv;
			at.at_bad_head	= at.at_head;
			at.at_bad_cyl	= at.at_cyl;
		}
		else if (++at.at_tries < SOFTLIM)
			return 1;

		printf("at%d%c: bno=%U head=%u cyl=%u",
			at.at_drv,
			(bp->b_dev & SDEV) ? 'x' : at.at_partn % NPARTN + 'a',
			(bp->b_count/BSIZE) + bp->b_bno
				+ at.at_caching - at.at_nsec,
			at.at_head, at.at_cyl);

#if VERBOSE > 0
		if ((csr & RDY_ST) == 0)
			printf(" <Drive Not Ready>");
		if (csr & WFLT_ST)
			printf(" <Write Fault>");

		if (aux & DAM_ERR)
			printf(" <No Data Addr Mark>");
		if (aux & TR0_ERR)
			printf(" <Track 0 Not Found>");
		if (aux & ID_ERR)
			printf(" <ID Not Found>");
		if (aux & ECC_ERR)
			printf(" <Bad Data Checksum>");
		if (aux & ABT_ERR)
			printf(" <Command Aborted>");
#else
		if ((csr & (RDY_ST|WFLT_ST)) != RDY_ST)
			printf(" csr=%x", csr);
		if (aux & (DAM_ERR|TR0_ERR|ID_ERR|ECC_ERR|ABT_ERR))
			printf(" aux=%x", aux);
#endif
		if (aux & BAD_ERR)
			printf(" <Block Flagged Bad>");

		if (at.at_tries < HARDLIM)
			printf(" retrying...");
		printf("\n");
		return 1;
	}
	return 0;
}

/**
 *
 * void
 * atrecov()
 *
 *	Action:	Attempt recovery.
 */
static void
atrecov()
{
	register BUF *bp = at.at_actf;
	register int cmd = SEEK(0);
	register int cyl = at.at_cyl;

	switch (at.at_tries) {

	case 1:
	case 2:
		/*
		 * Move in 1 cylinder, then retry operation
		 */
		if (--cyl < 0)
			cyl += 2;
		break;

	case 3:
	case 4:
		/*
		 * Move out 1 cylinder, then retry operation
		 */
		if (++cyl >= atparm[ at.at_drv ].d_ncyl)
			cyl -= 2;
		break;

	case 5:
	case 6:
		/*
		 * Seek to cylinder 0, then retry operation
		 */
		cyl = 0;
		break;

	default:
		/*
		 * Restore drive, then retry operation
		 */
		cmd = RESTORE(0);
		cyl = 0;
		break;
	}

	/*
	 * Retry operation [after repositioning head]
	 */
	if (at.at_tries < HARDLIM) {
		drvl[AT_MAJOR].d_time = (cmd == RESTORE(0))
			? (ATSECS * 2) : ATSECS;
		outb(LCYL_REG, cyl);
		outb(HCYL_REG, cyl >> 8);
		outb(HDRV_REG, (at.at_drv << 4) + 0xA0);
		outb(CSR_REG, cmd);
		at.at_state = SRETRY;
	}

	/*
	 * Give up on block.
	 */
	else {
		/*
		 * Not a cache-read error.
		 */
#if ATCACHE > 0
		if ((at.at_state != SREAD) || (at.at_caching != at.at_nsec))
#endif
			bp->b_flag |= BFERR;

		atdone(bp);
	}
}

/**
 *
 * void
 * atdone(bp)
 * BUF * bp;
 *
 *	Action:	Release current i/o buffer to the O/S.
 */
static void
atdone(bp)
register BUF * bp;
{
	drvl[AT_MAJOR].d_time = 0;
	at.at_state = SIDLE;
	at.at_actf  = bp->b_actf;
	bdone(bp);

	if (atdequeue())
		atstart();
}

int
notBusy()
{
	return (inb(ATSREG) & BSY_ST) == 0;
}

int
dataRequested()
{
	return inb(ATSREG) & DRQ_ST;
}

/*
 * Wait while controller is busy.
 *
 * Return 0 if timeout, nonzero if not busy.
 */
int
myatbsyw(unit) int unit;
{
	if (busyWait(notBusy, ATSECS * HZ))
		return 1;
	printf(timeout_msg, unit);
	return 0;
}

/*
 * Wait for controller to initiate request.
 *
 * Return 0 if timeout, 1 if data requested.
 */
int
atdrq()
{
	return busyWait(dataRequested, ATSECS * HZ);
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.