File:  [MW Coherent from dump] / coherent / b / kernel / io.386 / fl.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 Device Driver Kit version 2.0.1.b
 *	Copyright (c) 1982, 1991, 1992 by Mark Williams Company.
 *	All rights reserved. May not be copied without permission.
 -lgl) */
/*
 * This is a driver for the IBM AT and "above"
 * floppy drives, using interrupts and DMA on
 * the NEC 756 floppy chip. Ugh.
 * Handles single, double and quad
 * density drives, 8, 9, 15 or 18 sectors per track.
 * It wil NOT run on an XT;  it requires the CMOS setup memory.
 *
 * Minor device assignments: xxuuhkkk
 *	uu - unit = 0/1/2/3
 *	kkk - kind, struct fdata infra.
 *	h - alternating head rather than side by side
 *
 */

#include	<sys/coherent.h>
#ifdef	_I386
#include	<sys/reg.h>
#else
#include	<sys/i8086.h>
#endif
#include	<sys/buf.h>
#include	<sys/con.h>
#include	<sys/stat.h>
#include	<errno.h>
#include	<sys/uproc.h>
#include	<sys/proc.h>
#include	<sys/fdioctl.h>
	/*
	 * Additional internal commands not available
	 * to outside users:
	 */
#include	<sys/sched.h>
#include	<sys/dmac.h>
#include	<sys/devices.h>
#include	<sys/seg.h>
#include	<sys/inode.h>

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

#define MAXDRVS  2	/* Maximum number of drives that we will support */
#define MAXSCTRS 21	/* Maximum acceptable sectors per track.  It is  */
			/* possible to put as many as 10 sectors/track	 */
			/* on low density diskettes, 18 sectors/track on */
			/* 5 1/4" high-density diskettes, and 21 sectors */
			/* per track on 3 1/2" high density diskettes.   */
			/* the maximum on "2.88" meg diskettes is not    */
			/* known as of this writing, but may be as high  */
			/* as 42.					 */
#define MAXTYPE  4	/* Maximum "type" value as set in the CMOS RAM.  */
			/* The current value of this is 4, the type for  */
			/* 1.44 meg diskettes.				 */

#ifdef FL_XTRA
/* This is conditioned out for now due to end-of-volume problems. */
#define FL_CYL_2STEP	42
#define FL_CYL_HDLO	82
#define FL_CYL_HDHI	83
#else
#define FL_CYL_2STEP	40
#define FL_CYL_HDLO	80
#define FL_CYL_HDHI	80
#endif

/*
 * Patchable parameters.
 */

int	fl_srt = 0xD;	/* Floppy seek step rate, in unit -2 millisec */
			/* NOT DIRECTLY ENCODED */
			/* All drives HAVE to work at 5 msec/step, so */
			/* they HAVE to work at 6 msec/step!  (The use of */
			/* 8 msec/step in the old PCs was just IBM being */
			/* excessively conservative. MS/PC-DOS, starting with */
			/* version 2.0, changes it to 6 msec/step.  There */
			/* are, in fact, lots of drives that will step at */
			/* 4 msec/step, but there's no sense in pushing it.) */
int	fl_hlt = 1;	/* Floppy head load time, in unit 4 millisec */
int	fl_hut = 0xF;	/* Floppy head unload time, in unit 32 millisec */
int	fl_disp = 0;	/* If nonzero, print drive parameters on screen */

/*
 * Patchable variables for compatibility with IBM products:
 *
 * FL_DSK_CH_PROB - some machines always have the disk changed line turned on.
 * Currently some PS/1's (Consultant, Professional - possibly most of them)
 * have this problem, so the default value of zero assumes normal disk change
 * line operation.
 *
 * FL_AUTO_PARM - Only try to autosense floppy parameters if this variable
 * is nonzero.  The PS/2-L40 floppy controller apparently has trouble changing
 * from low density to high density.  Missing address marks when reading
 * a HD floppy are the symptom if FL_AUTO_PARM is set when it shouldn't be.
 */
int 	FL_DSK_CH_PROB = 0;
int	FL_AUTO_PARM = 0;

static int jopen;

/*
Here's the problem. We need to be able to tell if the disk has been changed.
There is an i/o port we can read, but on some systems, we can get constant
false positives. This causes massive floppy slowdown as the disk is constantly
recalibrating. The solution is not completely satisfactory, but it's the best
one I could come up with. Basically, what I said was since we can't tell when
the disk has changed, we will act as if it has changed every time we do an
open or a reset. The code

		if (FL_DSK_CH_PROB)
			jopen = 2;

indicates the need to pretend that the disk has changed. It is set to 2 since
there are two parts to the change procedure. Additional code dependent on
the value of FL_DSK_CH_PROB says that if we have not just down an open, then
we should skip the recal.  Otherwise, decrement the counter, and do the
recal. - mlk */

int	flload();
int	flunload();
void	flopen();
int	flclose();
int	flblock();
int	flread();
int	flwrite();
int	flioctl();
int	fldelay();
int	flintr();
int	fltimeout();
int	nulldev();

void	fldrvstatus();
CON	flcon	= {
	DFBLK | DFCHR,			/* Flags */
	FL_MAJOR,			/* Major index */
	flopen,				/* Open */
	flclose,			/* Close */
	flblock,			/* Block */
	flread,				/* Read */
	flwrite,			/* Write */
	flioctl,			/* Ioctl */
	nulldev,			/* Powerfail */
	fltimeout,			/* Timeout */
	flload,				/* Load */
	flunload			/* Unload */
};

#define	MTIMER	2			/* Motor timeout */
#define FDCDOR	0x3F2			/* Digital output */
#define	FDCDAT	0x3F5			/* Data register */
#define	FDCMSR	0x3F4			/* Main status register */
#define	FDCRATE	0x3F7			/* Transfer rate (500,300,250 Kbps) */
#define FDCCHGL 0x3F7			/* Port where we read the disk */
					/* changed line */
#define DSKCHGD 0x80			/* Diskette changed line bit. */

#define	DORDS	0x03			/* Drive select bits */
#define	DORNMR	0x04			/* Not master reset */
#define	DORIEN	0x08			/* Interrupt, DMA enable */
#define	DORMS	0xF0			/* Motor enables */

#define	MSRDB	0x0F			/* Drive busy */
#define	MSRCB	0x10			/* Control busy */
#define	MSRNDMA	0x20			/* Not DMA */
#define	MSRDIO	0x40			/* Data direction */
#define	MSRRQM	0x80			/* Request for master */

/*
 * Status Register 0 - Bit Definitions.
 */
#define	ST0_US0	0x01			/* Unit Select 0 */
#define	ST0_US1	0x02			/* Unit Select 1 */
#define	ST0_HD	0x04			/* Head Address */
#define	ST0_NR	0x08			/* Not Ready */
#define	ST0_EC	0x10			/* Equipment Check */
#define	ST0_SE	0x20			/* Seek End */
#define	ST0_IC	0xC0			/* Interrupt code */
#define	ST0_NT	0x00			/* Normal Termination */

/*
 * Status Register 1 - Bit Definitions.
 */
#define	ST1_MA	0x01			/* Missing Address Mark */
#define	ST1_NW	0x02			/* Not writeable */
#define	ST1_ND	0x04			/* No Data */
	/*	0x08 */			/* Not used - always 0 */
#define	ST1_OR	0x10			/* Overrun */
#define	ST1_DE	0x20			/* Data Error */
	/*	0x40 */			/* Not used - always 0 */
#define	ST1_EN	0x80			/* End of Cylinder */

/*
 * Status Register 2 - Bit Definitions.
 */
#define	ST2_MD	0x01			/* Missing Address Mark in Data Field */
#define	ST2_BC	0x02			/* Bad Cylinder */
#define	ST2_SN	0x04			/* Scan Not Satisfied */
#define	ST2_SH	0x08			/* Scan Equal Hit */
#define	ST2_WC	0x10			/* Wrong Cylinder */
#define	ST2_DD	0x20			/* Data Error in Data Field */
#define	ST2_CM	0x40			/* Control Mark */
	/*	0x80 */			/* Not used - always 0 */

/*
 * Status Register 3 - Bit Definitions.
 */
#define	ST3_US0	0x01			/* Unit Select 0 */
#define	ST3_US1	0x02			/* Unit Select 1 */
#define	ST3_HD	0x04			/* Head Address */
#define	ST3_TS	0x08			/* Two Sides */
#define	ST3_T0	0x10			/* Track 0 */
#define	ST3_RDY	0x20			/* Ready */
#define ST3_WP	0x40			/* Write Protected */
#define	ST3_FT	0x80			/* Fault */

/*
 * Controller Commands.
 */
#define	CMDSPEC	0x03			/* Specify */
#define	CMDRCAL	0x07			/* Recal */
#define	CMDSEEK	0x0F			/* Seek */
#define	CMDRDAT	0x66			/* Read data */
#define	CMDWDAT	0x45			/* Write data */
#define CMDSINT 0x08			/* Sense interrupt status */
#define CMDSDRV 0x04			/* Sense drive status */
#define CMDRDID 0x4A			/* Read ID */
#define	CMDFMT	0x4D			/* Format track */

/*
 * Driver States.
 */
#define	SIDLE	0			/* Idle */
#define	SSEEK	1			/* Need seek */
#define	SRDWR	2			/* Need read/write command */
#define	SENDIO	3			/* Need end I/O processing */
#define	SDELAY	4			/* Delay before next disk operation */
#define	SHDLY	5			/* Head settling delay before r/w */
#define SLOCK	6			/* Got DMA controller lock */
#define SRECAL1 7			/* First recalibrate attempt */
#define SRECAL2 8			/* Try seeking to track 2 */
#define SRECAL3 9			/* Second recalibrate, if necessary */
#define SGOTO2	10			/* After seek to cylinder 2 */
#define SRDID	11			/* Get sector ID from FDC */
#define SSIDTST 12			/* Testing # of sides */

#define funit(x)	(minor(x) >> 4)   /* Unit/drive number */
#define fkind(x)	(minor(x) & 0x7)  /* Kind of format */
#define fhbyh(x)	(minor(x) & 0x8)  /* 0=Side by side, 1=Head by head */

static			/* Parameters for each kind of format */
struct	FDATA	{
	int	fd_size;	/* Blocks per diskette */
	int	fd_nhds;	/* Heads per drive */
	int	fd_trks;	/* Tracks per side */
	int	fd_offs;	/* Sector base */
	int	fd_nspt;	/* Sectors per track */
	char	fd_GPL[4];	/* Controller gap param (indexed by rate) */
	char	fd_N;		/* Controller size param */
	char	fd_FGPL;	/* Format gap length */
} fdata[] = {
/* 8 sectors per track, surface by surface seek. */
	{  320,1,40,0, 8, { 0x00,0x20,0x20 }, 2,0x58 }, /* Single sided */
	{  640,2,40,0, 8, { 0x00,0x20,0x20 }, 2,0x58 }, /* Double sided */
	{ 1280,2,80,0, 8, { 0x00,0x20,0x20 }, 2,0x58 }, /* Quad density */
/* 9 sectors per track, surface by surface seek. */
	{  360,1,40,0, 9, { 0x00,0x20,0x20 }, 2,0x50 }, /* Single sided */
	{  720,2,40,0, 9, { 0x00,0x20,0x20 }, 2,0x50 }, /* Double sided */
	{ 1440,2,80,0, 9, { 0x00,0x20,0x20 }, 2,0x50 }, /* Quad density */
/* 15 sectors per track, surface by surface seek. */
	{ 2400,2,80,0,15, { 0x1B,0x00,0x00 }, 2,0x54 }, /* High capacity */
/* 18 sectors per track, surface by surface seek. */
	{ 2880,2,80,0,18, { 0x1B,0x00,0x00 }, 2,0x6C }	/* 1.44 3.5" */
};

static			/* Parameters for each device type */
struct	FRATES	{
	char	fl_hi_kind;		/* "fdata" initial try for hi dens. */
	char	fl_hi_rate;		/* -1 here for lo-dens. 	    */
	char	fl_lo_kind;		/* Lo-dens "fdata" entry to try 1st.*/
	char	fl_lo_rate;		/* Proper lo-density rate for type. */
	char	dflt_kind;		/* Default parameters.		    */
	char	dflt_rate;		/* Default data rate.		    */
} frates[] =	{
	{ 4,-1, 4,-1, 4, 2 },		/* Type 0 = no drive */
	{ 4,-1, 4, 2, 4, 2 },		/* Type 1 = 360K  */
	{ 6, 0, 4, 1, 6, 0 },		/* Type 2 = 1.2M  */
	{ 4,-1, 5, 2, 5, 2 },		/* Type 3 = 720K  */
	{ 7, 0, 5, 2, 7, 0 }		/* Type 4 = 1.44M */
};

static
struct	FL	{
	BUF	*fl_actf;		/* Queue, forward */
	BUF	*fl_actl;		/* Queue, backward */
	paddr_t	fl_addr;		/* Address */
	int	fl_nsec;		/* # of sectors */
	int	fl_secn;		/* Current sector */
	struct	FDATA fl_fd[MAXDRVS];	/* Disk kind data */
	int	fl_fcyl;		/* Floppy cylinder # */
	int	fl_2step[MAXDRVS];	/* =1 for double-stepping */
	char	fl_incal[MAXDRVS];	/* Disk in cal flags and current cyl */
	char	fl_dsk_chngd[MAXDRVS];	/* Diskette changed flags */
	char	fl_ndsk;		/* # of drives */
	char	fl_unit;		/* Current unit # */
	char	fl_selected_unit;	/* Last unit selected */
	char	fl_mask;		/* Handy unit mask */
	char	fl_hbyh;		/* 0/1 = Side by side/Head by head */
	char	fl_nerr;		/* Error count */
	int	fl_ncmdstat;		/* Number of cmd status bytes recvd */
	char	fl_cmdstat[8];		/* Command Status buffer */
	int	fl_nintstat;		/* Number of intr status bytes recvd */
	char	fl_intstat[4];		/* Interrupt Status buffer */
	int	fl_ndrvstat;		/* Number of drv status bytes read */
	char	fl_drvstat[2];		/* Drive Status buffer */
	int	fl_fsec;		/* Floppy sector # */
	int	fl_head;		/* Floppy head */
	char	fl_state;		/* Processing state */
	char	fl_mstatus;		/* Motor status */
	char	fl_time[MAXDRVS];	/* Motor timeout */
	char	fl_rate[MAXDRVS];	/* Data rate: 500,300,250,?? kbps */
	char	fl_type[MAXDRVS];	/* Type of drive: 2 = HiCap */
	char	fl_rate_set;		/* Currently set data rate */
	int	fl_wflag;		/* Write operation  */
	int	fl_recov;		/* Recovery initiated */
	int	fl_opct[MAXDRVS];	/* open count for each unit */
	int	fl_we[MAXDRVS];		/* write enable for each unit */
}	fl;

/*
 *	We need some areas in global RAM to use as BUF structures
 *	and as data areas for special functions such as formatting,
 *	reading the drive status and reading sector IDs.  There is
 *	one set for each drive.  When the blocks for a drive are in
 *	use, the "drv_locked" char is set to non-zero, and is
 *	cleared otherwise.  While this scheme doesn't provide
 *	complete reentrancy, it does allow both drives to be used
 *	"at once" by separate tasks.
 */
static	char	drv_locked[MAXDRVS];	/* One for each possible drive */
static	char	sw3[MAXDRVS];
static  BUF     flbuf[MAXDRVS];
/*
 *	These next items are related to the floppy disk system
 *	in general, so we only need one of each.
 */
static	TIM	fltim;
static	TIM	fldmalck;	/* DMA lock deferred function structure. */
static	char	scratch_buffer[BSIZE];
static	char	fl_clrng_cd;
static	char	fl_intlv_ct,	/* Counts sectors to find interleave.	 */
		fl_get_intlv,	/* =1 to start search for interleave.	 */
		fl_lk4_id,	/* Sector ID to look for for interleave. */
		fl_alt_kind,	/* Alternate disk parameter index and	 */
		fl_alt_rate,	/* data rate to use if not first value.  */
		fl_1st_ID,	/* Detects when all track sectors scanned*/
		fl_hi_ID;	/* Highest sector ID read so far.	 */

/*-------- DEBUG START ---------*/
#define FL_DEBUG 1
#if FL_DEBUG
#define PUSH3(a,b,c)	push3(a,b,c)
#define POP3()		pop3()

#define XX	75

static int A[XX], B[XX], C[XX];
static int xxp = 0;	/* next avail index */

static push3(a,b,c)
int a,b,c;
{
	if (xxp < XX) {
		A[xxp] = a;
		B[xxp] = b;
		C[xxp] = c;
		xxp++;
	}
}

static pop3()
{
	int i;

	printf("pop3: ");
	for (i = 0; i < xxp; i++) {
		printf("c5=%d 1st=%d hi=%d ", A[i], B[i], C[i]);
	}
	xxp = 0;
	printf(":done ");
}
#else
#define PUSH3(a,b,c)
#define POP3()
#endif
/*-------- DEBUG END ---------*/

/*
 * The load routine asks the
 * switches how many drives are present
 * in the machine, and sets up the field
 * in the floppy database. It also grabs
 * the level 6 interrupt vector.
 */
static
flload()
{
	register int	eflag;
	register int	s, t;

	fl_clrng_cd = 0;

	/*
	 * Ensure DMA channel 2 is turned off.
	 * The Computerland ROM does not disable DMA channel after autoboot
	 * from hard disk.  The Western Digital controller board appears to
	 * send a dma burst when the floppy controller chip is reset.
	 */
	dmaoff(2);

	/*
	 * Read floppy equipment byte from CMOS ram
	 *	drive 0 is in high nibble, drive 1 is in low nibble.
	 */
	outb(0x70, 0x10);
	/* delay */
	eflag = inb(0x71);

	/*
	 * Reinitialize patchable parameters for IBM AT.
	 */
	fl_srt = 0xD;	/* Floppy seek step rate, in unit -2 ms */
			/* NOT DIRECTLY ENCODED */
	fl_hlt = 25;	/* Floppy head load time, in unit 4 ms */

	/*
	 * Define AT drive information.
	 */
	fl.fl_type[0] = eflag >> 4;
	fl.fl_type[1] = eflag & 15;
	fl.fl_ndsk = 0;
	for (s = 0; s < MAXDRVS; s++) {
		drv_locked[s] = 0;
		fl.fl_dsk_chngd[s] = 1;
		fl.fl_incal[s] = -1;
		t = fl.fl_type[s];
		if (t > MAXTYPE)		  /* --in case we get, like, */
			fl.fl_type[s] = t = MAXTYPE;	/* a 2.88 meg drive. */
		fl.fl_rate[s] = frates[t].dflt_rate;
		fl.fl_fd[s] = fdata[frates[t].dflt_kind];
		if (t) fl.fl_ndsk = s + 1;	  /* Type 0 = no drive. */

		if (FL_DSK_CH_PROB)
			jopen = 2;
	}

	fl.fl_rate_set = 0;

	/*
	 * Initialize the floppy disk controller (if we
	 * have any floppy drives).
	 */
	if (fl.fl_ndsk) {

		s = sphi();

		outb(FDCDOR, 0);
		setivec(6, &flintr);
		/* "Not Reset FDC" must remain low for at least 14 clocks */
		outb(FDCDOR, 0);
		fl.fl_state = SIDLE;
		outb(FDCDOR, DORNMR | DORIEN);

		fl.fl_mstatus = 0;		/* No motors on */
		fl.fl_selected_unit = -1;	/* No unit selected */

		flspecify();
		outb(FDCRATE, fl.fl_rate_set);

		spl(s);
	}
}

/*
 * Release resources.
 */
flunload()
{
	/*
	 * Cancel timed function.
	 */
	timeout(&fltim, 0, NULL, NULL);

	/*
	 * Cancel periodic (1 second) invocation.
	 */
	drvl[FL_MAJOR].d_time = 0;

	/*
	 * Turn motors off.
	 */
	outb(FDCDOR, DORNMR);		/* Leave interrupts disabled. */

	/*
	 * Clear interrupt vector.
	 */
	if (fl.fl_ndsk)
		clrivec(6);
}

/*
 * The open routine screens out
 * opens of illegal minor devices and
 * performs the NEC specify command if
 * this is the very first floppy disk
 * open call.
 */
static void
flopen(dev, mode)
dev_t	dev;
int	mode;
{
	register int unit_number = funit(dev);
	register int s;

	/*
	 * Validate existence and data rate (Gap length != 0).
	 */
	if ((unit_number >= fl.fl_ndsk)
	  || (fl.fl_type[unit_number] == 0)
	  || (fdata[fkind(dev)].fd_GPL[flrate(dev)] == 0)) {
		u.u_error = ENXIO;
		goto badFlopen;		/* status. */
	}

	if (FL_DSK_CH_PROB)
		jopen = 2;

	/*
	 * May need to write - see if diskette is write proteced.
	 * We do this with a "Sense Drive Status" command.  Since
	 * this requires the use of the FDC, we have to schedule it
	 * like data transfer I/O or FORMAT even though it doesn't
	 * use the DMA.
	 */
	if (fl.fl_opct[unit_number] == 0) {	/* first open for this floppy */
		if (drv_locked[unit_number]) {	/* Work areas avail? */
			u.u_error = EBUSY;		/* No. */
			goto badFlopen;		/* status. */
		} else {
			drv_locked[unit_number] = 1;	/* Grab work areas. */
			flbuf[unit_number].b_dev = dev;
			flbuf[unit_number].b_req = BFLSTAT;
			sw3[unit_number] = 0;
							/* Get drive status. */
			s = flQhang(&flbuf[unit_number]);

			for (;;) {
				s = sphi();
				if (fl.fl_state == SIDLE)
					flfsm();
				spl(s);
				if (sw3[unit_number])
					break;
				if (fl.fl_state != SIDLE)
#ifdef _I386
					x_sleep(&fl.fl_state,
					  pridisk, slpriSigCatch, "flopen");
#else
					v_sleep(&fl.fl_state,
					  CVBLKIO, IVBLKIO, SVBLKIO,
					  "flopen");
#endif
				if (SELF->p_ssig && nondsig()) {  /* signal? */
					u.u_error = EINTR;
					goto badFlopen;
				}
			}

                        if (flbuf[unit_number].b_resid != 0) {
				u.u_error = EDATTN;	/* Couldn't get drive */
				goto badFlopen;		/* status. */
			}

			/* The payoff - set write enable status. */
			fl.fl_we[unit_number] =
			  ((sw3[unit_number] & ST3_WP)==0);
			drv_locked[unit_number] = 0;	/* Release work areas */
		}

		/*
		 * If the drive is low density (no change line) we should
		 * flag the need to verify the disk format and density.
		 * High density drives (which are also dual density) have
		 * change lines that we can check each time we want to read
		 * the drive.
		 */
		if (frates[fl.fl_type[unit_number]].fl_hi_rate == -1) {
			fl.fl_incal[unit_number] = -1;
			fl.fl_dsk_chngd[unit_number] = 1;

			if (FL_DSK_CH_PROB)
				jopen = 2;
		}
	}	/* end of first open stuff */

	/* If opening for write, volume must be write enabled. */
	if ((mode & IPW) && !fl.fl_we[unit_number]) {
		printf("fd%d: <Write Protected>\n", fl.fl_unit);
		u.u_error = EROFS;	/* Diskette write */
		goto badFlopen;		/* protected. */
	}

	fl.fl_opct[unit_number]++;
badFlopen:
	return;
}

/*
 * flclose()
 */
static
flclose(dev, mode)
dev_t	dev;
int	mode;
{
	register int unit_number = funit(dev);

	fl.fl_opct[unit_number]--;
}

/*
 * The read routine just calls
 * off to the common raw I/O processing
 * code, using a static buffer header in
 * the driver.
 */

static
flread(dev, iop)

dev_t	dev;
IO	*iop;

{
	dmareq(&flbuf[funit(dev)], iop, dev, BREAD);
}

/*
 * The write routine is just like the
 * read routine, except that the function code
 * is write instead of read.
 */

static
flwrite(dev, iop)

dev_t	dev;
IO	*iop;

{
	dmareq(&flbuf[funit(dev)], iop, dev, BWRITE);
}

/*
 * The ioctl routine simply queues a format request
 * using the flbuf for the specified drive.
 * The only valid command is to format a track.
 * The parameter block contains the header records supplied to the controller.
 */

static
flioctl(dev, com, par)
dev_t	dev;
int	com;
char	*par;
{
	register unsigned s;
	register struct fdata *fdp;
	unsigned hd, cyl;

	if (com != FDFORMAT) {
		u.u_error = EINVAL;
		return;
	}

	fdp = &fdata[fkind(dev)];		/* Locate formatting	*/
	cyl = getubd(par);			/* parameters.		*/
	hd  = getubd(par+1);

	if (hd > 1 || cyl >= fdp->fd_trks) {
		u.u_error = EINVAL;
		return;
	}

	/*
	 * The following may need some explanation.
	 * dmareq will:
	 *	claim the buffer,
	 *	bounds check the parameter buffer,
	 *	lock the parameter buffer in memory,
	 *	convert io_seek to b_bno,
	 *	dispatch the request,
	 *	wait for completion,
	 *	and unlock the parameter buffer.
	 * The b_bno is reconverted to hd, cyl in flfsm.
	 */

	s = fhbyh(dev) ? (cyl * fdp->fd_nhds + hd) : (hd * fdp->fd_trks + cyl);
	s *= fdp->fd_nspt;
	u.u_io.io_seek = ((long)s) * BSIZE;
#ifdef _I386
	u.u_io.io.vbase = par;
#else
	u.u_io.io_base = par;
#endif
	u.u_io.io_ioc = fdp->fd_nspt * 4;
	dmareq(&flbuf[funit(dev)], &u.u_io, dev, BFLFMT);
	return 0;
}

/*
 * Start up block I/O on a
 * buffer. Check that the block number
 * is not out of range, given the style of
 * the disk. Put the buffer header into the
 * device queue. Start up the disk if the
 * device is idle.
 */
static
flblock(bp)
register BUF	*bp;
{
	register int	s;
	register unsigned bno;

	/* Nasty implicit dependency on BSIZE == 2**9 */
	bno = bp->b_bno + (bp->b_count >> 9) - 1;

{ /*DEBUG*/
	int first = bp->b_bno, last = bno;
	int fdatasz = fdata[fkind(bp->b_dev)].fd_size;
	int fl_fdsz = fl.fl_fd[funit(bp->b_dev)].fd_size;
/*DEBUG*/

	if ((bp->b_req == BFLFMT)
	&&   ((unsigned)bp->b_bno >= fdata[fkind(bp->b_dev)].fd_size)) {
		bp->b_flag |= BFERR;
		bdone(bp);
		return;
	}

	if (bp->b_req != BFLFMT) {
		if ((unsigned)bp->b_bno >=
		  fl.fl_fd[funit(bp->b_dev)].fd_size)  {
			bp->b_flag |= BFERR;
			bdone(bp);
			return;
		}
		if (bno >= fl.fl_fd[funit(bp->b_dev)].fd_size) {
			if (bp->b_flag & BFRAW) {
				bp->b_flag |= BFERR;
			}
			bp->b_resid = bp->b_count;
			bdone(bp);		/* return w/ b_resid != 0 */
			return;
		}
		if ((bp->b_count & 0x1FF) != 0) {
			bp->b_flag |= BFERR;
			bdone(bp);
			return;
		}
	}
} /*DEBUG*/

	flQhang(bp);			/* Put the block in the queue. */

	s = sphi();
	if (fl.fl_state == SIDLE)	/* --if necessary, to */
		flfsm();		/* get things moving. */
	spl(s);
}

/*
 * This routine hangs a BUF in the processing queue
 */

static
flQhang(bp)
register BUF *bp;
{
	register int s = sphi();     /* No interrupts during chaining, please */

	bp->b_actf = NULL;

	if (fl.fl_actf == NULL)
		fl.fl_actf = bp;
	else
		fl.fl_actl->b_actf = bp;

	fl.fl_actl = bp;

	spl(s);
}

/*
 * This finite state machine is
 * responsible for all sequencing on the disk.
 * It builds the commands, does the seeks, spins up
 * the drive motor for 1 second on the first call,
 * and so on.
 * Note that the format command is rather obscurely shoehorned into this.
 */
static
flfsm()
{
	register BUF	*bp;
	register int	flcmd;
	register int	i;
	int		dods;	/* for PS/1, do disk swap */

again:
	bp = fl.fl_actf;

	switch (fl.fl_state) {

	case SIDLE:
T_HAL(0x40000, printf("SIDLE "));
		drvl[FL_MAJOR].d_time = 1;

		if (bp == NULL)
			break;

		fl.fl_unit = funit(bp->b_dev);
		fl.fl_mask = 0x10 << fl.fl_unit;

#if 0
printf("drv%d: cmd=%d (%s), position=%d, count=%d\n",
fl.fl_unit,
bp->b_req,
  (bp->b_req == BREAD)	     ? "BREAD"
: (bp->b_req == BWRITE)      ? "BWRITE"
: (bp->b_req == BFLSTAT)     ? "BFLSTAT"
: (bp->b_req == BFLFMT)      ? "BFLFMT"       : "?????",
bp->b_bno,
bp->b_count);
#endif
		/*
		 * We do an entire check for drive status here
		 */
		if (bp->b_req == BFLSTAT) {
			fl.fl_drvstat[0] = 0;
			fldrvstatus();
			sw3[fl.fl_unit] = fl.fl_drvstat[0] | 3;
			bp->b_resid = (fl.fl_ndrvstat == 1) ? 0 : 1;
			fl.fl_actf  = bp->b_actf;
			fl.fl_state = SIDLE;
			goto again;
		}

		fl.fl_hbyh = fhbyh(bp->b_dev);

		fl.fl_addr = bp->b_paddr;
		fl.fl_secn = bp->b_bno;
		fl.fl_time[fl.fl_unit] = 0;

		if ((fl.fl_nsec = bp->b_count>>9) == 0)
			fl.fl_nsec = 1;

		fl.fl_nerr = 0;

		/*
		 * Motor is turned off - turn it on, wait 1 second
		 * (for write operations only)
		 */
		if (((fl.fl_mstatus & fl.fl_mask) == 0)
		|| (fl.fl_unit != fl.fl_selected_unit)) {
			fldrvselect();
			if ((bp->b_req == BWRITE)
			|| (bp->b_req == BFLFMT)) {
				timeout(&fltim, HZ, fldelay, SSEEK);
				fl.fl_state = SDELAY;
				break;
			}
		}

		/* no break */

	case SSEEK:
T_HAL(0x40000, printf("SSEEK "));
		fldrvselect();			/* Keep drive turned on */

		/*
		 * Test dual-density drive's disk changed line.  We must
		 * test now before we (possibly) recalibrate the drive
		 * which would lose us the disk changed indication.
		 */

		if ((frates[fl.fl_type[fl.fl_unit]].fl_hi_rate != -1)
		&&   (inb(FDCCHGL) & DSKCHGD)
		&&   (fl_clrng_cd == 0)) {
			/* See note at def of FL_DSK_CH_PROB above */
			if (FL_DSK_CH_PROB) {
				if (jopen) {
					jopen--;
					fl.fl_dsk_chngd[fl.fl_unit] = 1;
					fl.fl_incal[fl.fl_unit] = -1;
				}
			} else {
				fl.fl_dsk_chngd[fl.fl_unit] = 1;
				fl.fl_incal[fl.fl_unit] = -1;
			}
		}

		fl_clrng_cd = 0;

		/*
		 * If we have a format command on cylinder zero, head
		 * zero, we must recalibrate the drive first, and set
		 * up the transfer speed and FDC stuff.  We ignore
		 * a disk changed condition since the current format
		 * (it may, remember, be unformatted!) is of no
		 * consequence.
		 */
		if (bp->b_req == BFLFMT) {
			fl.fl_dsk_chngd[fl.fl_unit] = 0;
			fl.fl_fd[fl.fl_unit] = fdata[fkind(bp->b_dev)];
			fl.fl_rate[fl.fl_unit] =
			fl.fl_rate_set		 = flrate(bp->b_dev);
			if ((fl.fl_fd[fl.fl_unit].fd_trks < 45)
			  && (fl.fl_type[fl.fl_unit] != 1))
				fl.fl_2step[fl.fl_unit] = 1;
			outb(FDCRATE, fl.fl_rate_set);
			if (fl.fl_secn == 0)
				fl.fl_incal[fl.fl_unit] = -1;
		}
		/*
		 * Drive is not calibrated - seek to track 0.
		 */
		if (fl.fl_incal[fl.fl_unit] == -1) {
			flput(CMDRCAL);
			flput(fl.fl_unit);
			fl.fl_state = SRECAL1;
			break;
		} else goto Recalibrated;

	case SRECAL1:
T_HAL(0x40000, printf("SRECAL1 "));
		/*
		 * If the recalibrate had to step more than 77 cylinders
		 * it will fail.  We must check for this condition and
		 * try once more.  With some controllers we will also get
		 * an error if the head STARTS over cylinder 0.  In either
		 * event we will force a seek to track 2, then recalibrate
		 * again.  If this fails, we can't recalibrate the drive.
		 */
		if ((fl.fl_nintstat != 2)
		  || ((fl.fl_intstat[0] & (ST0_IC | ST0_SE)) != ST0_SE)) {
			flput(CMDSEEK);
			flput(fl.fl_unit);
			flput(2);
			fl.fl_state = SRECAL2;
			break;
		} else goto RecalibrateOK;
	case SRECAL2:
T_HAL(0x40000, printf("SRECAL2 "));
		flput(CMDRCAL);
		flput(fl.fl_unit);
		fl.fl_state = SRECAL3;
		break;
	case SRECAL3:
T_HAL(0x40000, printf("SRECAL3 "));
		if ((fl.fl_nintstat != 2)
		  || ((fl.fl_intstat[0] & (ST0_IC | ST0_SE)) != ST0_SE)) {
RecalFailed:
			printf("fd%d: <Can't Recalibrate>\n", fl.fl_unit);
			clrQ(bp->b_dev);
			goto again;
		}
RecalibrateOK:
		flput(CMDSEEK); 		/* We now get off of cyl 0  */
		flput(fl.fl_unit);		/* to try to clear the disk */
		flput(2);			/* changed line, which acts */
		fl.fl_state = SGOTO2;		/* differently on different */
		break;				/* controllers.  <sigh>  We */
						/* use cyl 2 since all for- */
	case SGOTO2:				/* matted disks will have a */
T_HAL(0x40000, printf("SGOTO2 "));
		if ((fl.fl_nintstat != 2)	/* track here.		    */
		  || ((fl.fl_intstat[0] & (ST0_IC | ST0_SE)) != ST0_SE))
			goto RecalFailed;

		fl.fl_incal[fl.fl_unit] = 2;	/* Heads now on cylinder 2. */

Recalibrated:
		/*
		 * Now, if we don't have to check the interleave factor,
		 * we can continue with the seek!
		 */
		if (fl.fl_dsk_chngd[fl.fl_unit] == 0) goto RateKnown;

		/*
		 * <sigh>.  Okay, first we'll try the requested density.
		 */

						/* First we'll make sure   */
						/* we're sitting on cyl 2. */
		if (fl.fl_incal[fl.fl_unit] != 2) goto RecalibrateOK;

		/*
		 * We start by trying the requested density:
		 */

							/* Get requested rate.*/
		i = fl.fl_rate[fl.fl_unit] = flrate(bp->b_dev);
							/* This next mess gets*/
							/* the disk parameters*/
							/* and the alternate  */
							/* values.	      */
		if (i == frates[fl.fl_type[fl.fl_unit]].fl_hi_rate){

			fl.fl_fd[fl.fl_unit] =
			 fdata[frates[fl.fl_type[fl.fl_unit]].fl_hi_kind];
                        if (FL_AUTO_PARM) {
				fl_alt_kind =
				  frates[fl.fl_type[fl.fl_unit]].fl_lo_kind;
				fl_alt_rate =
				  frates[fl.fl_type[fl.fl_unit]].fl_lo_rate;
			}
			else {
				fl_alt_kind =
				  frates[fl.fl_type[fl.fl_unit]].fl_hi_kind;
				fl_alt_rate =
				    frates[fl.fl_type[fl.fl_unit]].fl_hi_rate;
			}
		} else {
			fl.fl_fd[fl.fl_unit] =
			 fdata[frates[fl.fl_type[fl.fl_unit]].fl_lo_kind];
                        if (FL_AUTO_PARM) {
				fl_alt_kind =
				  frates[fl.fl_type[fl.fl_unit]].fl_hi_kind;
				fl_alt_rate =
				    frates[fl.fl_type[fl.fl_unit]].fl_hi_rate;
			} else {
				fl_alt_kind =
				  frates[fl.fl_type[fl.fl_unit]].fl_lo_kind;
				fl_alt_rate =
				  frates[fl.fl_type[fl.fl_unit]].fl_lo_rate;
			}
		}

		fl.fl_state  = SRDID;		/* Set up to read sector IDs. */
		fl_get_intlv = 1;
		fl_intlv_ct  =
		fl_lk4_id    =
		fl_1st_ID    =
		fl_hi_ID     = 0;

		/*
		 * Now we try the rate to see if we can read sector IDs
		 */
TryRate:
		if (fl.fl_rate_set != i)
			outb(FDCRATE, fl.fl_rate_set = i);
GetNextID:
		flput(CMDRDID);
		flput(fl.fl_unit);		/* Always read side 0. */

		break;				/* Wait for ID to arrive. */

	case SRDID:
T_HAL(0x40000, printf("SRDID "));

		if ((fl.fl_ncmdstat < 7)	/* Did we get an ID? */
		  || ((fl.fl_cmdstat[0] & ST0_IC) != ST0_NT) ) {
			if (fl_alt_rate == -1) { /* No, is there an alternate?*/
				flstatus();	 /* No, we can't go on.       */
				clrQ(bp->b_dev);
PUSH3(-1,0,1);
				goto again;
			} else {
				fl.fl_fd[fl.fl_unit] = fdata[fl_alt_kind];
				i = fl.fl_rate[fl.fl_unit] = fl_alt_rate;
				fl_alt_rate = -1; /* Flag tried alternate.    */
PUSH3(-1,0,2);
				goto TryRate;	  /* Try alternate density.   */
			}
		}

		/*
		 * Test interleave
		 */

		if (fl_get_intlv)		/* Looking for interleave? */
			if (fl_lk4_id) {	/* Yes; started yet?	   */
				if (fl_lk4_id == fl.fl_cmdstat[5]) /* Yes. */
					fl_get_intlv = 0; /* We have a hit.*/
				else			  /* No hit yet,   */
					fl_intlv_ct++;	  /* count sector. */
			} else if (fl.fl_cmdstat[5] < 5) {/* Can we start yet?*/
				fl_intlv_ct = 1;	     /* Yes; count, */
				fl_lk4_id = fl.fl_cmdstat[5] + 1;/* set ID  */
			}					 /* to find.*/

		/*
		 * Look for highest ID on track
		 */

PUSH3(fl.fl_cmdstat[5], fl_1st_ID, fl_hi_ID);
		if (fl.fl_cmdstat[5] != fl_1st_ID) {
			if (fl_1st_ID == 0)
				fl_1st_ID = fl.fl_cmdstat[5];
			if (fl.fl_cmdstat[5] > fl_hi_ID)
				fl_hi_ID = fl.fl_cmdstat[5];
			goto GetNextID;
		}

		/*
		 * Be sure we have the interleave
		 */

		if (fl_get_intlv) goto GetNextID;

		/*
		 * So now we know the density and sectors/track
		 */

		fl.fl_dsk_chngd[fl.fl_unit] = 0;
		fl.fl_fd[fl.fl_unit].fd_nspt = fl_hi_ID;

		/*
		 * There is a problem with the approach used here -
		 * it assumes that once scan of a track starts, all
		 * sectors appear in physical order without any misses.
		 * Unfortunately, this is not always the case, especially
		 * with 1.44 M 3-1/2" drives.
		 *
		 * A workaround which fixes incorrect nspt reading appears
		 * below.
		 */
		if (fl.fl_fd[fl.fl_unit].fd_nspt > 15
		  && fl.fl_fd[fl.fl_unit].fd_nspt < 18)
			fl.fl_fd[fl.fl_unit].fd_nspt = 18;

		/*
		 * We're (supposedly) sitting on track 2.  We'll
		 * look at the last sector ID we've read.  If it's 1,
		 * we need to do double-stepping.
		 */

		if (fl.fl_2step[fl.fl_unit] = (fl.fl_cmdstat[3] == 1)) {
			fl.fl_fd[fl.fl_unit].fd_trks = FL_CYL_2STEP;
			fl.fl_incal[fl.fl_unit] = 1;
		} else					/* Most 1.2M drives */
			fl.fl_fd[fl.fl_unit].fd_trks = /* have 83 cyls!   */
			  (fl.fl_type[fl.fl_unit] == 2)
			  ? FL_CYL_HDHI : FL_CYL_HDLO;

		/*
		 * We next test for one or two sides:
		 */

		if (fl.fl_rate[fl.fl_unit] == 0) {	    /* If diskette is */
			fl.fl_fd[fl.fl_unit].fd_nhds = 2; /* high-density it*/
PUSH3(-1,0,4);
			goto DiskEstablished;		    /* will have two  */
		}					    /* sides.	      */

		/*
		 * If the diskette is requested by caller as low density
		 * we use the specified number of sides------
		 */

		if (fdata[fkind(bp->b_dev)].fd_nspt < 12) {
			fl.fl_fd[fl.fl_unit].fd_nhds =
			  fdata[fkind(bp->b_dev)].fd_nhds;
PUSH3(-1,0,5);
			goto DiskEstablished;
		}

		/*
		 * --otherwise we check to see:
		 */

		flput(CMDRDID); 		/* Just try to read ANY sector*/
		flput(fl.fl_unit | 0x04);	/* ID from side two.	      */
		fl.fl_state = SSIDTST;
PUSH3(-1,0,6);
		break;

	case SSIDTST:				/* If we succeeded, we have */
T_HAL(0x40000, printf("SSIDTST "));
						/* 2 sides, else we have 1. */
		fl.fl_fd[fl.fl_unit].fd_nhds = ((fl.fl_ncmdstat < 7)
			  || ((fl.fl_cmdstat[0] & ST0_IC) != ST0_NT) ) ? 1 : 2;

		/*
		 * So now we now know all about the diskette!
		 */
DiskEstablished:
		fl.fl_fd[fl.fl_unit].fd_size = fl.fl_fd[fl.fl_unit].fd_nhds
		  * fl.fl_fd[fl.fl_unit].fd_trks
		  * fl.fl_fd[fl.fl_unit].fd_nspt;

		if (fl_disp) {
			printf("fl%d: rate=%d, sctrs/trk=%d, hds=%d, cyls=%d,"
			  " size=%d, intlv=%d, stp=%d\n",
			  fl.fl_unit,
			  fl.fl_rate[fl.fl_unit],
			  fl.fl_fd[fl.fl_unit].fd_nspt,
			  fl.fl_fd[fl.fl_unit].fd_nhds,
			  fl.fl_fd[fl.fl_unit].fd_trks,
			  fl.fl_fd[fl.fl_unit].fd_size,
			  fl_intlv_ct,
			  fl.fl_2step[fl.fl_unit]+1);
			POP3();
		}

		/*
		 * Finally, if the diskette drive has a change line
		 * we'll force it off by reading a sector on cylinder 2.
		 * Usually the testing for rate, the seek to cylinder 2
		 * and the soon-to-follow read or write would clear this,
		 * but there are some controllers that don't always clear
		 * it.	The machine I've been testing on clears it sometimes
		 * and not other times.  I hope there aren't machines even
		 * flakier than this!
		 */

		if ((frates[fl.fl_type[fl.fl_unit]].fl_hi_rate != -1)
		  && (inb(FDCCHGL) & DSKCHGD)) {
			dods = 1;

			/* See note at def of FL_DSK_CH_PROB above */
			if (FL_DSK_CH_PROB) {
				if (jopen) {
					jopen--;
				} else
					dods = 0;
			}
			if (dods) {
				fl.fl_fcyl = fl.fl_incal[fl.fl_unit];
				fl.fl_head = 0;
				fl.fl_fsec = 1;
#ifdef _I386
				fl.fl_addr = MAPIO(allocp.sr_segp->s_vmem,
				((int)scratch_buffer - (int)allocp.sr_base));
#else
				fl.fl_addr = vtop(scratch_buffer, sds);
#endif
				fl_clrng_cd = 1;
				goto Sought;
			}	
		}

RateKnown:
		/*
		 * Set data rate if changed.
		 */
		if (fl.fl_rate_set != (i = fl.fl_rate[fl.fl_unit]))
			outb(FDCRATE, fl.fl_rate_set = i);

		/*
		 * Next we must convert the ordinal block number to
		 * cylinder/head/sector form.
		 */
		fl.fl_fsec = (fl.fl_secn % fl.fl_fd[fl.fl_unit].fd_nspt) + 1;

		/*
		 * Seek cylinder by cylinder (XENIX/DOS compatible).
		 */
		if (fl.fl_hbyh) {
			fl.fl_head = fl.fl_secn / fl.fl_fd[fl.fl_unit].fd_nspt;
			fl.fl_fcyl = fl.fl_head / fl.fl_fd[fl.fl_unit].fd_nhds;
			fl.fl_head = fl.fl_head % fl.fl_fd[fl.fl_unit].fd_nhds;
		}
		
		/*
		 * Seek surface by surface.
		 */
		else {
			fl.fl_fcyl = fl.fl_secn / fl.fl_fd[fl.fl_unit].fd_nspt;
			fl.fl_head = fl.fl_fcyl / fl.fl_fd[fl.fl_unit].fd_trks;
			fl.fl_fcyl = fl.fl_fcyl % fl.fl_fd[fl.fl_unit].fd_trks;
		}
					/* Don't seek unless we have to. */
		if (fl.fl_fcyl == fl.fl_incal[fl.fl_unit])
			goto Sought;		/* Past tense of seek. */

		fl.fl_incal[fl.fl_unit] = fl.fl_fcyl; /* Save new cylinder. */

		flput(CMDSEEK);
		flput((fl.fl_head<<2) | fl.fl_unit);
						/* If disk is around 40 tracks*/
						/* and drive is not 40 track- */
		if ((fl.fl_fd[fl.fl_unit].fd_trks < 45)
		  && (fl.fl_type[fl.fl_unit] != 1))
			flput(fl.fl_fcyl << 1); 	/* -use double step.  */
		else
			flput(fl.fl_fcyl);		/* Single step.       */

		fl.fl_state = SHDLY;
		break;

	case SHDLY:
T_HAL(0x40000, printf("SHDLY "));
		/*
		 * Delay for minimum 15 milliseconds after seek before w/fmt.
		 * 2 clock ticks would give 10-20 millisecond (100 Hz clock).
		 * 3 clock ticks gives	    20-30 millisecond (100 Hz clock).
		 */
		if (bp->b_req != BREAD) {
			timeout(&fltim, 3, fldelay, SRDWR);
			fl.fl_state = SDELAY;
			break;
		}
		/* no break */

	case SRDWR:
T_HAL(0x40000, printf("SRDWR "));
Sought:
		/*
		 * Disable watchdog timer while waiting to lock DMA controller.
		 */
		fl.fl_time[fl.fl_unit] = -1;

		/*
		 * Next state will be DMA locked state.
		 */
		fl.fl_state = SLOCK;

		/*
		 * If DMA controller locked by someone else, exit for now.
		 */
		if (dmalock(&fldmalck, flfsm, 0) != 0)
			return;

	case SLOCK:
T_HAL(0x40000, printf("SLOCK "));
		/*
		 * Reset watchdog timer to restart timeout sequence.
		 */

		fl.fl_time[fl.fl_unit] = 0;

		flcmd = CMDRDAT;
		fl.fl_wflag = 0;

		if (fl_clrng_cd == 0)
			if (bp->b_req == BWRITE) {
				fl.fl_wflag = 1;
				flcmd = CMDWDAT;
			}

			else if (bp->b_req == BFLFMT) {
				fl.fl_wflag = 1;
				flcmd = CMDFMT;

#ifdef _I386
				if(!dmaon(2, P2P(fl.fl_addr),bp->b_count,
				  fl.fl_wflag))
#else
				if(dmaon(2, fl.fl_addr, bp->b_count,
				  fl.fl_wflag) == 0)
#endif
					goto straddle;

				else
					goto command;
			}

#ifdef _I386
		if (dmaon(2, P2P(fl.fl_addr), 512, fl.fl_wflag) == 0)
#else
		if (dmaon(2, fl.fl_addr, 512, fl.fl_wflag) == 0)
#endif
		{
straddle:
			devmsg(bp->b_dev, "fd: DMA page straddle at %x:%x",
				fl.fl_addr);
			dmaunlock(&fldmalck);
			bp->b_flag |= BFERR;
			fldone(bp);
			goto again;
		}
command:
		dmago(2);
		flput(flcmd);
		flput((fl.fl_head<<2) | fl.fl_unit);

		if (bp->b_req == BFLFMT) {
			flput(fl.fl_fd[fl.fl_unit].fd_N);	/* N */
			flput(fl.fl_fd[fl.fl_unit].fd_nspt);	/* SC */
			flput(fl.fl_fd[fl.fl_unit].fd_FGPL);	/* GPL */
			flput(0xF6);				/* D */
		}
		
		else {
			flput(fl.fl_fcyl);
			flput(fl.fl_head);
			flput(fl.fl_fsec);
			flput(fl.fl_fd[fl.fl_unit].fd_N);	/* N */
			flput(fl.fl_fd[fl.fl_unit].fd_nspt);	/* EOT */
			flput(fl.fl_fd[fl.fl_unit].fd_GPL[fl.fl_rate_set]);
								/* GPL */
			flput(0xFF);				/* DTL */
		}

		fl.fl_state = SENDIO;
		break;

	case SENDIO:
T_HAL(0x40000, printf("SENDIO "));
		fl.fl_time[fl.fl_unit] = 0;
		dmaoff(2);
		dmaunlock(&fldmalck);

		if (fl_clrng_cd) {
			fl.fl_state = SIDLE;
			wakeup(&fl.fl_state);
			goto again;
		}

		/*
		 * We now check for errors.  If the error is a data
		 * CRC error, we KNOW we're on the correct track, and
		 * we just retry the read once before recalibrating.
		 * We recalibrate for all other errors.
		 */
		if ((fl.fl_cmdstat[0] & ST0_IC) != ST0_NT) {
			if (++fl.fl_nerr < 5) {
				if (fl.fl_cmdstat[2] & ST2_DD) {
					if (fl.fl_nerr & 1)
					  goto SetSEEKState;
					else
					  goto Ask4Recal;
				} else {
Ask4Recal:
					fl.fl_incal[fl.fl_unit] = -1;
SetSEEKState:
					fl.fl_state = SSEEK;
				}
			} else {
				flstatus();		/* Total failure; */
				bp->b_flag |= BFERR;	/* we give up.	  */
				fldone(bp);
			}
		}

		else if (--fl.fl_nsec == 0) {
			bp->b_resid = 0;
			fldone(bp);
		}
		
		else {
			++fl.fl_secn;
			fl.fl_addr += 512;	/* 512 == fl.fl_fd.fd_nbps */
			fl.fl_state = SSEEK;
		}

		/*
		 * Delay for minimum 1.5 msecs after writing before seek.
		 */
		if (fl.fl_wflag) {
			timeout(&fltim, 2, fldelay, fl.fl_state);
			fl.fl_state = SDELAY;
			break;
		}

		goto again;

	case SDELAY:
T_HAL(0x40000, printf("SDELAY "));
		/*
		 * Ignore interrupts until timeout occurs.
		 */
		break;

	default:
		panic("fds");
	}
}

/*
 * Delay before initiating next operation.
 * This allows the floppy motor to turn on,
 * the head to settle before writing,
 * the erase head to turn off after writing, etc.
 */
static
fldelay(state)
int state;
{
	int s;

	s = sphi();
	if (fl.fl_state == SDELAY) {
		fl.fl_state = state;
		flfsm();
	}
	spl(s);
}

/*
 * The flrate function returns the data rate for the flopen and flfsm routines.
 */
static int
flrate(dev)
register dev_t dev;
{
	register int unit = funit(dev);
	register int rate = frates[fl.fl_type[unit]].fl_hi_rate;

	if ((rate == -1) || (fdata[fkind(dev)].fd_nspt < 15))
		rate = frates[fl.fl_type[unit]].fl_lo_rate;

	return(rate);
}

/*
 * This routine is called by the
 * clock handler every second. If the drive
 * has been idle for a long time it turns off
 * the motor and shuts off the timeouts.
 */

static
fltimeout()
{
	register int	unit;
	register int	mask;
	register int	s;

	s = sphi();

	/*
	 * Scan all drives, looking for motor timeouts.
	 */
	for (unit=0, mask=0x10; unit < MAXDRVS; unit++, mask <<= 1) {

		/*
		 * Ignore drives which aren't spinning.
		 */
		if ((fl.fl_mstatus & mask) == 0)
			continue;

		/*
		 * If timer is disabled (i.e. we are waiting for the DMA
		 * controller), go on to the next drive.
		 */
		if (fl.fl_time[unit] < 0)
			continue;

		/*
		 * Leave recently accessed (in last 4 seconds) drives spinning.
		 */
		if (++fl.fl_time[unit] < MTIMER)
			continue;

		/*
		 * Timeout drives which have been inactive for 5 seconds.
		 */
		fl.fl_mstatus &= ~mask;
		if (unit == fl.fl_selected_unit)
			fl.fl_selected_unit = -1;

		/*
		 * Not selected drive, or selected drive is idle.
		 */
		if ((unit != fl.fl_unit) || (fl.fl_state == SIDLE))
			continue;

		/*
		 * Active drive did not complete operation within 5 seconds.
		 * Attempt recovery.
		 */
		flrecov();

		/*
		 * Initiate next block request.
		 */
		if (fl.fl_state == SIDLE)
			flfsm();
	}

	/*
	 * Physically turn off drives which timed out.
	 */
	outb(FDCDOR, DORNMR | DORIEN | fl.fl_mstatus | fl.fl_unit);

	/*
	 * Stop checking once all drives have been stopped.
	 */
	if (fl.fl_mstatus == 0)
		drvl[FL_MAJOR].d_time = 0;

	spl(s);
}

/*
 * Remove all pending requests for a device from the queue
 * (used after errors).
 */
static
clrQ(dev)
register int dev;
{
	register BUF *bp, *bp2;
	int s;

	s = sphi();

	while ((bp = fl.fl_actf) && (bp->b_dev == dev)) {
		bp->b_flag |= BFERR;		/* Strip BUFs from front */
		fl.fl_actf = bp->b_actf;	/* of queue.		 */
		bdone(bp);
	}
	while (bp) {
		fl.fl_actl = bp;
		if ((bp2 = bp->b_actf) && (bp2->b_dev == dev)) {
			bp2->b_flag |= BFERR;	  /* Strip BUFs from rest  */
			bp->b_actf = bp2->b_actf; /* rest of queue.	   */
			bdone(bp2);
		} else
			bp = bp2;
	}
	fl.fl_state = SIDLE;
	wakeup(&fl.fl_state);
	spl(s);
}

/*
 * The recovery routine resets and reprograms the floppy controller,
 * and discards any queued requests on the current drive.
 * This is required if the floppy door is open, or diskette is missing.
 */
static
flrecov()
{
	register int	x;

	if (FL_DSK_CH_PROB)
		jopen = 2;

	/*
	 * Disable DMA transfer.
	 * Reset floppy controller.
	 */
	dmaoff(2);

	/*
	 * Unlock the controller if locked by us.
	 */

	outb(FDCDOR, 0);
	fl.fl_state = SIDLE;
	wakeup(&fl.fl_state);
	dmaunlock(&fldmalck); 		/* Ensures 14 clock cycles */
	outb(FDCDOR, DORNMR | DORIEN);

	fl.fl_mstatus = 0;			/* No motors on */
	fl.fl_selected_unit = -1;		/* No unit selected */

	/*
	 * Program floppy controller.
	 */
	flspecify();				/* Forces wait */

	/*
	 * Program transfer bps.
	 */
	outb(FDCRATE, fl.fl_rate_set);

	/*
	 * Drives are no longer in calibration.
	 */
	for (x = 0; x < MAXDRVS; x++)
		fl.fl_incal[1] = -1;

	/*
	 * Abort all block requests on current drive after 1st recov attempt.
	 */
	if (fl.fl_actf) {
		printf("fd%d: <Door Open>\n", fl.fl_unit);     /* Message    */
		clrQ(fl.fl_actf->b_dev);		/* Dump pending reqs. */
		fl.fl_dsk_chngd[fl.fl_unit] = 1;	/* Make disk changed. */
	}

	/*
	 * Delay before setting controller state to idle.
	 * This gives time for spurious floppy interrupts to occur.
	 * NOTE: Can't call flfsm(), since it may call us (future revision).
	 */
	timeout(&fltim, HZ/4, fldelay, SIDLE);
	fl.fl_state = SDELAY;
}

/*
 * The interrupt routine gets all
 * the status bytes the controller chip
 * will give it, then issues a sense interrupt
 * status command (which is necessary for a seek
 * to complete!) and throws all of the status
 * bytes away.
 */

static
flintr()
{
	register int s;

	s = sphi();
	flsense();

	if (fl.fl_state != SIDLE)
		flfsm();

	spl(s);
}

/*
 * Fldone() returns current request to operating system.
 */
fldone(bp)
register BUF * bp;
{
	fl.fl_actf  = bp->b_actf;
	fl.fl_state = SIDLE;
	bdone(bp);
	wakeup(&fl.fl_state);
}

/*
 * Send Specify command and data bytes to FDC
 */

static
flspecify()
{
	flput(CMDSPEC);
	flput((fl_srt << 4) | fl_hut);
	flput(fl_hlt << 1);
}

/*
 * Flsense() issues a sense interrupt status command
 * to restore the controller to a quiescent state.
 */

static
flsense()
{
	flcmdstatus();			/* Get command status. */
	flintstatus();			/* Get int status, just in case. */
}

/*
 * Get status (if any) from last command
 */
static
flcmdstatus()
{
	register int	b;
	register int	n = 0;		/* # of status bytes read */
	register int	i = 0;		/* Timeout count */
	register int	s;

	s = sphi();

	/*
	 * Read all the status bytes the controller will give us.
	 */

	for (;;) {
		while (((b=inb(FDCMSR)) & MSRRQM) == 0) {
			if (--i == 0) {
				printf("flintr: timeout\n");
				break;
			}
		}

		if ((b & MSRDIO) == 0)
			break;

		b = inb(FDCDAT);
		if (n < sizeof(fl.fl_cmdstat))
			fl.fl_cmdstat[n++] = b;
	}

	fl.fl_ncmdstat = n;
	spl(s);
}

/*
 * Get Inteerupt status
 */
static
flintstatus()
{
	register int	b;
	register int	n = 0;		/* # of status bytes read */
	register int	i = 0;		/* Timeout count */
	register int	s;

	s = sphi();

	/*
	 * Issue a sense interrupt command and stash result.
	 */
	flput(CMDSINT);

	n = 0;
	for (;;) {
		while (((b=inb(FDCMSR)) & MSRRQM) == 0)
			if (--i == 0) {
				printf("flintsense: timeout\n");
				break;
			}

		if ((b & MSRDIO) == 0)
			break;

		b = inb(FDCDAT);
		if (n < sizeof(fl.fl_intstat))
			fl.fl_intstat[n++] = b;
	}
	fl.fl_nintstat = n;
	spl(s);
}

/*
 * Get the drive status
 */
static void
fldrvstatus()
{
	register int	b;
	register int	n = 0;		/* # of status bytes read */
	register int	i = 0;		/* Timeout count */
	register int	s;

	s = sphi();

	fldrvselect();			/* Be sure drive is selected */

	/*
	 * Issue a sense drive status command and stash result.
	 */
	flput(CMDSDRV);
	flput(fl.fl_unit);

	for (;;) {
		spl(s);
		if (SELF->p_ssig && nondsig()) {  /* signal? */
			u.u_error = EINTR;
			break;
		}
		s = sphi();
		while (((b=inb(FDCMSR)) & MSRRQM) == 0)
			if (--i == 0) {
				printf("fldrvsense: timeout\n");
				break;
			}

		if ((b & MSRDIO) == 0)
			break;

		b = inb(FDCDAT);
		if (n < sizeof(fl.fl_drvstat))
			fl.fl_drvstat[n++] = b;
	}
	fl.fl_ndrvstat = n;
	spl(s);
}

/*
 *	fldrvselect() selects the drive and turns its motor on
 */
static
fldrvselect()
{
	fl.fl_time[fl.fl_unit] = 0;		/* Start motor-on timeout. */
	fl.fl_mstatus |= fl.fl_mask;
	outb(FDCDOR, DORNMR | DORIEN | fl.fl_mstatus | fl.fl_unit);
	fl.fl_selected_unit = fl.fl_unit;	/* This unit is running. */
	flsense();				/* Just in case --- */
}

/*
 * Send a command byte to the
 * NEC chip, first waiting until the chip
 * says that it is ready. No timeout is
 * performed; if the chip dies, we do too!
 */

static
flput(b)

int	b;

{
	register int i = 0;

	while ((inb(FDCMSR) & (MSRRQM | MSRDIO)) != MSRRQM) {
		if (--i == 0) {
			printf("flput(%x): timeout\n",b);
			return;
		}
	}

	outb(FDCDAT, b);
}

/*
 * Dissassemble the floppy error status for user reference.
 */

static
flstatus()
{
	printf("fd%d: head=%u cyl=%u",
		fl.fl_cmdstat[0] & 3,
		fl.fl_head, fl.fl_fcyl);

	/*
	 * Report on ST0 bits.
	 */
	if (fl.fl_ncmdstat >= 1) {
		if (fl.fl_cmdstat[0] & ST0_NR)
			printf(" <Not Ready>");

		if (fl.fl_cmdstat[0] & ST0_EC)
			printf(" <Equipment Check>");
	}

	/*
	 * Report on ST1 bits.
	 */
	if (fl.fl_ncmdstat >= 2) {
		if (fl.fl_cmdstat[1] & ST1_MA)
			printf(" <Missing Address Mark>");

		if (fl.fl_cmdstat[1] & ST1_NW)
			printf(" <Write Protected>");

		if (fl.fl_cmdstat[1] & ST1_ND)
			printf(" <No Data>");

		if (fl.fl_cmdstat[1] & ST1_OR)
			printf(" <Overrun>");

		if (fl.fl_cmdstat[1] & ST1_DE)
			printf(" <Data Error>");

		if (fl.fl_cmdstat[1] & ST1_EN)
			printf(" <End of Cyl>");
	}

	/*
	 * Report on ST2 bits.
	 */
	if (fl.fl_ncmdstat >= 3) {
		if (fl.fl_cmdstat[2] & ST2_MD)
			printf(" <Missing Data Address Mark>");

		if (fl.fl_cmdstat[2] & ST2_BC)
			printf(" <Bad Cylinder>");

		if (fl.fl_cmdstat[2] & ST2_WC)
			printf(" <Wrong Cylinder>");

		if (fl.fl_cmdstat[2] & ST2_DD)
			printf(" <Bad Data CRC>");

		if (fl.fl_cmdstat[2] & ST2_CM)
			printf(" <Data Deleted>");
	}

	printf("\n");
}
/*			  * * * * End of FL.C * * * *			*/

unix.superglobalmegacorp.com

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