/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION  1986,1987,1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
/* $Header: /usr/src/sys/rt/dev/RCS/fd.c,v 1.12 1994/06/04 15:46:28 md Exp $ */
/* $ACIS:fd.c 12.0$ */
/* $Source: /usr/src/sys/rt/dev/RCS/fd.c,v $ */

#if !defined(lint) && !defined(NO_RCS_HDRS)
static char *rcsid = "$Header: /usr/src/sys/rt/dev/RCS/fd.c,v 1.12 1994/06/04 15:46:28 md Exp $";
#endif
#include "fd.h"
#if NFD > 0

#include <sys/param.h>
#include <sys/vm.h>
#include <sys/buf.h>
#include <sys/time.h>
#include <sys/proc.h>
#include <sys/errno.h>
#include <sys/disklabel.h>
#include <ufs/fs.h>
#include <machine/pte.h>
#include <machine/io.h>
#include "rt/include/ioccvar.h"
#include "rt/rt/debug.h"
#include <sys/trace.h>

#include <machine/mmu.h>

#define spl_high()	_spl1()
caddr_t real_buf_addr();
#define REAL_BUF_ADDR(bp,bufaddr) (((bp->b_flags & B_PHYS) == 0) ? \
	((caddr_t)vtop(bufaddr)) : real_buf_addr(bp,bufaddr))


#include <sys/kernel.h>
#include <sys/uio.h>
#include <sys/file.h>
#include "rt/dev/dmavar.h"
#include "rt/dev/fdreg.h"
#include "rt/dev/fdvar.h"
#include "rt/include/fdio.h"
#include "rt/include/dkio.h"

/* per-controller data */
struct fd_ctlr fd_ctlr[NFDC];

/* per-drive buffers */
struct buf rfdbuf[NFD];			  /* buffers for raw I/O */
struct buf fdutab[NFD];			  /* per drive buffers */

/* per-drive data */
struct fd_softc fd_softc[NFD];
/* End of per-drive data */

struct iocc_device *fddinfo[NFD];
struct iocc_ctlr *fdminfo[NFDC];

int fdprobe(), fdslave(), fdattach(), fddgo(), fdint(), fdwatch(), 
			fdcallint(), fdverify(), fdstrategy();

/* dma_minphys */
unsigned (*dma_get_minphys())();

caddr_t fdstd[] = {
	(caddr_t)0xf00003f2, (caddr_t)0xf0000372, 0
};

/* because the floppy disk first register is write-only it appears to
 * not exist to autoconf.
 * we set idr_csr to 2 to find a read/write register
 */
struct iocc_driver fdcdriver = {
	fdprobe, fdslave, fdattach, fddgo, fdstd, "fd", fddinfo, "fdc", fdminfo,
	fdint, 2
};

int fdwstart;

char fdresult();

/*
 * Drive description table.
 */
struct fdst fdst[] = {
      /*sec/track heads sec/cyl #cyl byte/disk   gpl  fgpl  step  xfer drive delay_factor */
	/* 360K diskette in 1.2M drive */
	{    9,     2,   2 * 9,  40,2*9*40*FDBPS,0x23,0x50, 0xdf,0x01,
	FD1200K,"fd360k", 2},
	/* 1.2M diskette */
	{    15,    2,   2 * 15, 80,2*15*80*FDBPS,0x1b,0x54,0xdf,0x00,
	FD1200K,"fd1200k", 2},
	/* 360K diskette in 360k drive */
	{    9,     2,   2 * 9,  40,2*9*40*FDBPS,0x2a,0x50, 0xdf,0x02,
	FD360K ,"fd360k", 1},
	/* 720K in a 1.4M diskette (3.5 in.) UNTESTED */
	{   9,    2,     2*9,  80,  2*9*80*FDBPS,0x1b,0x54,0xdf,0x00,
	FD1440K, "fd720k", 2},
	/* 1.4M in a 1.4M diskette (3.5 in.) */
	{   18,    2,     2*18,  80,  2*18*80*FDBPS,0x1b,0x54,0xdf,0x00,
	FD1440K, "fd1.4m", 2},
	/* 720K in a 720K diskette (3.5 in.) UNTESTED */
	{   9,     2,     2*9,   80,  2*9*80*FDBPS,0x2a,0x50,0xdf,0x02,
	FD720K, "fd720k", 1},
};

/*
 * This flag disables verify on writes. In the future it may be possible to
 * turn this on or off depending on the hardware installed.
 */
int	nofdverify = 1;

struct iocc_ctlr	*fdicprobed;

fdprobe(reg, ic)
	register struct fddevice *reg;
	register struct iocc_ctlr	*ic;
{
	DEBUGF(fddebug > 8,printf("fdprobe entered\n"););
	reg->fd_digout = FDMOTORON(0) | FDINTENABLE;
	delay(FDMOTORSTART/1000);
	reg->fd_digout = 0;
	PROBE_DELAY(FDMOTORSTART);
	fdicprobed = ic;
	return (PROBE_OK);
}

int fdfrc144 = 0;

fdslave(iod, reg)
	register struct iocc_device *iod;
	register struct fddevice *reg;
{ 
	register int unit = iod->iod_unit;
	struct fd_ctlr *fdc = &fd_ctlr[fdicprobed->ic_ctlr];
	struct fd_softc *sc = &fd_softc[unit];
	int cyl,count,status0,status3,i;

	DEBUGF(fddebug > 8,printf("fdslave entered; ctlr=%d, unit=%d\n",
				  fdicprobed->ic_ctlr, iod->iod_unit););
	/* motor on */
	reg->fd_digout = FDMOTORON(unit) | FDINTENABLE | iod->iod_slave;
	delay(FDMOTORSTART/1000);

	fddinfo[unit] = iod;
	iod->iod_addr = (caddr_t) reg;
	
	/* find out if drive exists */
	fdrecal(unit);
	delay(FDRECAL/1000);
	DEBUGF(fddebug,printf("status3=%b\n",sc->sc_sr3,FDSR3););

	/* get result */
	fdread_status(unit);

	/*
	 * if the trak0 line is not on, then try once more for a 
	 * quick retry (it appears that on the 1.2 MB drive if 
	 * the head is in the last cylinder that a fdrecal doesn't
	 * quite work the first time).
	 */
	if (((status3=sc->sc_sr3) & FDTRAK0) == 0) {
		fdrecal(unit);
		delay(FDRECAL/10000);
		/* get result */
		fdread_status(unit);
	}

	/*
	 * if the trak0 line is not on, the floppy drive is not there.
	 */
	if (((status3=sc->sc_sr3) & FDTRAK0) == 0) {
		/* ignore the interrupt */
		fdc->fdc_state = FDS_SLAVE;
		/* turn off motor */
		reg->fd_digout = 0;
		return(0);
	}

	/*
	 * All the rest of this code is to determine which
	 * type of drive is attached. First we do a seek to
	 * 0,0 to clear the hardware. Then we seek to sector
	 * 50. This will "peg" 360K drives to cylinder 40, but 
	 * leave 1.2Meg at cylinder 50). Then we start seeking 
	 * back toward 0. The 360K will get back to 0 first.
	 */
	fdc->fdc_state = FDS_SLAVE;
	sc->sc_dens = 1;		/* don't double step seeks */
	fdseek(unit,0,0);
	count = fdwaitint(fdc);

	/* get result */
	fdcmd(unit,FDSENI);
	status0 = fdresult(unit,status0);
	cyl = fdresult(unit,cyl);
	DEBUGF(fddebug,printf("track = %d status=0x%b status3=%b count=%d\n",
		cyl,status0,FDSR0,status3,FDSR3,count););

	/* seek to test sector */
	fdc->fdc_state = FDS_SLAVE;
	fdseek(unit,50,0);
	count = fdwaitint(fdc);
	delay(FDRECAL/1000);

	/* get result */
	fdcmd(unit,FDSENI);
	status0 = fdresult(unit,status0);
	cyl = fdresult(unit,cyl);
	DEBUGF(fddebug,printf("track = %d status=0x%b status3=%b count=%d\n",
		cyl,status0,FDSR0,status3,FDSR3,count););

	for (i=10; i >=0; i--) {

		/* sense drive status */
		fdread_status(unit);
		if ((status3=sc->sc_sr3) & FDTRAK0) {
			break;
		}

		/* seek to next track */
		fdc->fdc_state = FDS_SLAVE;
		fdseek(unit,i,0);
		count = fdwaitint(fdc);

		fdcmd(unit,FDSENI);
		status0 = fdresult(unit,status0);
		cyl = fdresult(unit,cyl);
		DEBUGF(fddebug,
			printf("track = %d status=0x%b status3=%b count=%d\n",
				       cyl,status0,FDSR0,status3,FDSR3,count););
	}
	DEBUGF(fddebug,printf("count=%d\n",i););
	if ( i > 0) {
		printf("fd%d: 360K drive\n",unit);
		sc->sc_drive = FD360K;
/* 	} else if (fdfrc144) { */
/* 	} else if ((1<<unit) & iod->iod_flags) { */
	} else if (iod->iod_flags) {
		printf("fd%d: 1.44M drive\n",unit);
		sc->sc_drive = FD1440K;
	} else {
		printf("fd%d: 1.2M drive\n",unit);
		sc->sc_drive = FD1200K;
	}
	/* turn off motor */
	reg->fd_digout = 0;

	return(1);
}

fdwaitint(fdc)
	struct fd_ctlr *fdc;
{
	int	timeout=10000000;
	int	count = 0;

	while ((fdc->fdc_state == FDS_SLAVE) && (timeout--)) {
		DELAY(1);
		count++;
	}
	DELAY(5);
	if (timeout == -1)
		printf("seek error on init\n");
	return(count);
}

fdattach(iod)
	register struct iocc_device *iod;
{
	int unit = iod->iod_unit;
	register struct buf *dp = &fdutab[unit];
	register struct fd_softc *sc = &fd_softc[unit];
	register struct fd_ctlr *fdc = &fd_ctlr[iod->iod_ctlr];
	register struct iocc_ctlr *ic = fdminfo[iod->iod_ctlr];

	/* initialize variables */
	dp->b_actf = NULL;
	dp->b_actl = NULL;
	dp->b_active = 0;
	ic->ic_dmachannel = DMA_CHAN2;
	ic->ic_dmaflags = (DMA_SINGLE|DMA_INCREMENT|DMA_PAGE|DMA_PHYSICAL);
	sc->sc_dens=0;
	sc->sc_motor=0;
	sc->sc_open=0;
	sc->sc_bopen = 0;
	sc->sc_copen = 0;
	sc->sc_curcyl=0;
	sc->sc_flags=0;
	sc->sc_suppress=0;
	sc->sc_verbuf = NULL;
	fdc->fdc_lstdens=0;
	fdc->fdc_drives=0;
	fdc->fdc_state=FDS_IDLE;
	return (1);
}

/*
 * handle the openning and closing of the block/character device separately
 * to keep track whether or not the floppy is really open.
 */
fdbopen(dev,flags)	/* block open */
	dev_t	dev;
	int	flags;
{
	fdkopen(dev,flags,1);
}

fdcopen(dev,flags)     /* character open */
	dev_t	dev;
	int	flags;
{
	fdkopen(dev,flags,0);
}

fdkopen(dev, flags,block)
	dev_t dev;
	int   flags;
	int   block;
{
	register int unit = FDUNIT(dev);
	register struct fd_softc *sc;
	register struct iocc_device *iod;
	register int error,s;
	struct fd_ctlr *fdc;

	DEBUGF(fddebug > 8,printf("fdopen entered\n"););
	trace(TR_F_OPE, pack(dev, block), flags);

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

	sc = &fd_softc[unit];
	fdc = &fd_ctlr[iod->iod_ctlr];

loop:
	s=FD_SPL();
	if (sc->sc_open == 0) {

		/* don't try to autodensity while someone else is */
		if (sc->sc_flags & FDF_LOCK) {
			splx(s);
			sleep((caddr_t) sc,FDPRI);
			goto loop;
		} else {
			sc->sc_flags |= FDF_LOCK;
		}
			
		/*
		 * read device status to determine if
		 * a floppy is present in the drive and
		 * what density it is
		 */
		if (sc->sc_motor == 0) {
			error=fdautodensity(dev,flags);
			if (error) {
				sc->sc_flags &= ~FDF_LOCK;
				splx(s);
				wakeup((caddr_t) sc);
				return(error);
			}
		}
		if (fdwstart++ == 0) {
			fdc->fdc_tocnt = 0;
			timeout(fdwatch, (caddr_t)0, hz); /* start watchdog */
		}
		DEBUGF(fddebug > 8,printf("fdopen: \n"););
		sc->sc_flags &= ~FDF_LOCK;
		fdc->fdc_drives++;		/* number of open drives on ctlr*/
	} else {
		if (sc->sc_flags & FDF_LOCK) {
			DEBUGF(fddebug > 8, printf("fdopen: EBUSY  sc->sc_open=%d  \n", sc->sc_open););
			splx(s);
			return (EBUSY);
		}
		if ((sc->sc_sr3 & FDNOWRITE) && (flags & FWRITE)) {
			uprintf("fd%d: write protected.\n",unit);
			splx(s);
			return (EIO);
		}
	}
	if ((nofdverify == 0) && (flags & FWRITE) && (sc->sc_verbuf == NULL)) {
		sc->sc_verbuf = geteblk(fdst[sc->sc_dens].nsect*FDBPS);
	}
	sc->sc_open++;
	if (block) {
		sc->sc_bopen++;
	} else {
		sc->sc_copen++;
	}
	splx(s);
	wakeup((caddr_t) sc);

	DEBUGF(fddebug > 8,printf("fdopen exit\n"););
	return (0);
}


/* do the hardware initialization */
fdautodensity(dev,flags)
	dev_t dev;
	int   flags;
{
	register int unit = FDUNIT(dev);
	register struct fd_softc *sc = &fd_softc[unit];
	register struct fd_ctlr *fdc = &fd_ctlr[fddinfo[unit]->iod_ctlr];
	int fdretries = ((flags == FDFORMREQ) ? FDFORMRETRIES : FDRETRIES);

	DEBUGF(fddebug > 8,printf("fdautodensity entered\n"););

	sc->sc_errcnt=0;
	if ((FDTYPE(dev)) && (flags != FDINFOREQ)) {
	    struct buf *bp = geteblk(FDBPS);

	    DEBUGF(fddebug,printf("fixed density type device  "););

	    switch(sc->sc_dens=(FDTYPE(dev)>>4)) {
		case FD1200K1200K:	/* all these types are valid */
		case FD360K360K:
		case FD720K1440K:
		case FD1440K1440K:
		case FD720K720K:
		    break;
		default:		/* invalid drive type, assume 1.2M */
		    sc->sc_dens = FD1200K1200K;
	    }

	    DEBUGF(fddebug,printf("density = %d, drive =%d\n",sc->sc_dens,sc->sc_drive););
	    sc->sc_flags = (unit & FDF_DEVTYPE) | FDF_LOCK;
	    bp->b_dev = dev;
	    bp->b_flags = B_SETUP|B_BUSY;
	    bp->b_blkno = 0;
	    bp->b_error = 0;
	    bp->av_forw = 0;
	    trace(TR_F_AUT, pack(dev, bp->b_bcount), bp->b_blkno);
	    (void) fdstrategy(bp);
	    trace(TR_F_WAI, pack(bp->b_dev, bp->b_bcount), bp->b_blkno);
	    iowait(bp);
	    trace(TR_F_REL, pack(bp->b_dev, bp->b_bcount), bp->b_blkno);
	    brelse(bp);
	    sc->sc_retries = -1;
	    sc->sc_drive = fdst[sc->sc_dens].drive;
	} else {
	    for (sc->sc_retries=0;(sc->sc_retries < fdretries) && (flags != FDINFOREQ);sc->sc_retries++) {
		struct buf *bp = geteblk(FDBPS);

		sc->sc_flags = (unit & FDF_DEVTYPE) | FDF_LOCK;
		bp->b_dev = dev;
		bp->b_flags = B_SETUP|B_BUSY;
		bp->b_blkno = 0;
		bp->b_error = 0;
		bp->av_forw = 0;
		trace(TR_F_AUT, pack(dev, bp->b_bcount), bp->b_blkno);
		(void) fdstrategy(bp);
		trace(TR_F_WAI, pack(bp->b_dev, bp->b_bcount), bp->b_blkno);
		iowait(bp);


		/* this last piece of code tries to read sector 1 
		 * track 0 * if it can then we are at the correct 
		 * density, otherwise * try different density
		 */
		DEBUGF(fddebug,printf("density = %d, retries= %d",sc->sc_dens,sc->sc_retries););
		DEBUGF(fddebug,printf(" drive =%d\n",sc->sc_drive););

		sc->sc_flags = (unit & FDF_DEVTYPE) | FDF_LOCK;
		bp->b_dev = dev;
		bp->b_flags = B_READ|B_BUSY;
		bp->b_error = 0;
		bp->b_blkno = fdst[sc->sc_dens].nspc;	/* seek to cylinder 1 */
		bp->av_forw = 0;
		bp->b_bcount = FDBPS;
		sc->sc_curcyl = 0;		/* force a seek each round */
		sc->sc_resid = 0;
		/*
		 * read device status to determine if
		 * a floppy is present in the drive and
		 * what density it is
		 */
		(void) fdstrategy(bp);
		DEBUGF(fddebug,printf("strategy complete\n"););
		if (fdwstart++ == 0) {
			fdc = &fd_ctlr[fddinfo[unit]->iod_mi->ic_ctlr];
			fdc->fdc_tocnt = 0;
			timeout(fdwatch, (caddr_t)0, hz); /* start watchdog */
		}
		trace(TR_F_WAI, pack(bp->b_dev, bp->b_bcount), bp->b_blkno);
		iowait(bp);
		DEBUGF(fddebug,printf("Floppy in drive\n"););

		/* check command status register after operation */
		if (((sc->sc_sr0 & FDSR0MASK) != 0) || (sc->sc_curcyl != 1) || (sc->sc_hrdhd != 0) || (bp->b_flags & B_ERROR)) {
			if (++sc->sc_errcnt > 1) { /* force two errors before changing den */
				sc->sc_errcnt = 0;
				switch (sc->sc_drive) {

				default:
				case FDUNKNOWN:
					if (++sc->sc_dens >FDMAXTYPE)
						sc->sc_dens=FD360K1200K;
					break;

				case FD360K:
					sc->sc_dens=FD360K360K;
					break;

				case FD1200K:
					sc->sc_dens = (sc->sc_dens == FD1200K1200K) ?
										FD360K1200K : FD1200K1200K;
					break;

				case FD1440K:
					sc->sc_dens = (sc->sc_dens == FD1440K1440K) ?
										FD720K1440K : FD1440K1440K;
					break;

				case FD720K:
					sc->sc_dens = FD720K720K;
				}
			}
		} else {
			trace(TR_F_REL, pack(bp->b_dev, bp->b_bcount), bp->b_blkno);
			brelse(bp);
			sc->sc_retries = -1;
			sc->sc_drive = fdst[sc->sc_dens].drive;
			break;
		}
		trace(TR_F_REL, pack(bp->b_dev, bp->b_bcount), bp->b_blkno);
		brelse(bp);

		if (sc->sc_flags & FDF_TIMEOUT) {
			fdmotoroff(unit);
			uprintf("fd%d: door open or hardware fault.\n",unit);
			return(ENXIO);	/* return error if no disk */
        }

		if ((sc->sc_sr3 & FDNOWRITE) && (flags & FWRITE)) {
			fdmotoroff(unit);
			uprintf("fd%d: write protected.\n",unit);
			return (EIO);
		}

	
	    } /* end for */
	} /* end if */

	if (flags == FDINFOREQ) {
		sc->sc_flags |= FDF_INFO;
	}
	else {
		if ((sc->sc_sr3 & FDNOWRITE) && (flags & FWRITE)) {
			fdmotoroff(unit);
			uprintf("fd%d: write protected.\n",unit);
			return (EIO);
		}


		DEBUGF(fddebug,printf("Flags = %x ",flags););
	
		if (sc->sc_retries > 0) {
	 		if (flags == FDFORMREQ) 
				sc->sc_flags |= FDF_FORMAT;
			else {
				fdmotoroff(unit);
				uprintf("fd%d: bad or unformatted floppy.\n",unit);
				return (ENXIO);
			}
		}

		DEBUGF(fddebug > 0, {
			switch (sc->sc_dens) {
			case FD360K1200K:
				printf("fd: opening floppy drive for 360k in 1.2M\n");
				break;
			case FD1200K1200K:
				printf("fd: opening floppy drive for 1.2M in 1.2M\n");
				break;
			case FD360K360K:
				printf("fd: opening floppy drive for 360K in 360K\n");
				break;
			case FD720K1440K:
				printf("fd: opening floppy drive for 720K in 1440K\n");
				break;
			case FD1440K1440K:
				printf("fd: opening floppy drive for 1440K in 1440K\n");
				break;
			case FD720K720K:
				printf("fd: opening floppy drive for 720K in 720K\n");
			}
		});
	}

	return (0);
}

fdread(dev, uio)
	dev_t dev;
	struct uio *uio;
{
	int unit = FDUNIT(dev);
	struct fd_softc *sc = &fd_softc[unit];
	DEBUGF(fddebug > 0x70,printf("fdread entered\n"););

	if (uio->uio_offset < 0)
		return (ENXIO);
	if (sc->sc_flags & (FDF_FORMAT | FDF_INFO))
		return (ENXIO);
	return (physio(fdstrategy, &rfdbuf[unit], dev, B_READ, 
	  	dma_get_minphys(&fd_ctlr[fddinfo[unit]->iod_ctlr]), uio));
}


fdwrite(dev, uio)
	dev_t dev;
	struct uio *uio;
{
	int unit = FDUNIT(dev);
	struct fd_softc *sc = &fd_softc[unit];
	DEBUGF(fddebug > 0x70,printf("fdwrite entered\n"););

	if (uio->uio_offset < 0)
		return (ENXIO);
	if (sc->sc_flags & (FDF_FORMAT | FDF_INFO))
		return (ENXIO);
	return (physio(fdstrategy, &rfdbuf[unit], dev, B_WRITE,
	  	dma_get_minphys(&fd_ctlr[fddinfo[unit]->iod_ctlr]), uio));
}


/*
 * Control routine:
 * processes four kinds of requests:
 *
 *	(1) Set density (i.e., format the diskette) according to
 *		  that specified data parameter
 *	(2) Arrange for the next sector to be written with a deleted-
 *		  data mark.
 *	(3) Report whether the last sector read had a deleted-data mark
 *	(4) Report the density of the diskette in the indicated drive
 *	    (since the density it automatically determined by the driver,
 *	     this is the only way to let an application program know the
 *	     density)
 *
 * Requests relating to deleted-data marks can be handled right here.
 * A "set density" (format) request, however, must additionally be
 * processed through "fdstart", just like a read or write request.
 */

fdioctl(dev, cmd, data, flag)
	dev_t dev;
	caddr_t data;
{
	int unit = FDUNIT(dev);
	struct fd_softc *sc;
	int s;
	DEBUGF(fddebug > 8,printf("fdioctl entered\n"););

	sc = &fd_softc[unit];

	switch (cmd) {

	case FDIOC_FORMAT:
		if ((flag & FWRITE) == 0)
			return(EBADF);
		if (sc->sc_open > 1)
			return (EBUSY);

		switch (*(int *)data) {

			case 0:
				switch (sc->sc_drive) {
					case FDUNKNOWN:
						return(EIO);
					case FD360K:
						sc->sc_dens=FD360K360K;
						break;
					case FD720K:
						sc->sc_dens=FD360K360K;
						break;
					case FD1200K:
						sc->sc_dens=FD360K1200K;
						break;
					case FD1440K:
						sc->sc_dens=FD720K1440K;
						break;
				}
				break;

			case 1:
				switch (sc->sc_drive) {
					case FDUNKNOWN:
					case FD360K:
					case FD720K:
						return(EIO);
					case FD1200K:
						sc->sc_dens=FD1200K1200K;
						break;
					case FD1440K:
						sc->sc_dens=FD1440K1440K;
						break;
				}
				break;

			default:
				return(EIO);
		}
		return (fdformat(dev));

		/* get density */
	case FDIOC_GDENS:
		*(int *)data = sc->sc_dens;
		return (0);

		/* Get the type of drive */
	case FDIOC_GTYPE:
		*(int *)data = sc->sc_drive;
		return(0);

		/* Set the type of drive */
	case FDIOC_STYPE:
		switch (*(int *)data) {
			case FD1200K:
				sc->sc_dens=FD1200K1200K;
				break;
			case FD360K:
				sc->sc_dens=FD360K360K;
				break;
			case FD1440K:
				sc->sc_dens=FD1440K1440K;
				break;
			case FD720K:
				sc->sc_dens=FD720K720K;
				break;
			case FDUNKNOWN:
				break;
			default:
				return(EINVAL);
		}
		sc->sc_drive = *(int *)data;
		return(0);

		/* reset adapter */
	case FDIOC_RESET:
		s=FD_SPL();
		fdrecal(unit);
		delay(FDRECAL/1000);
		fdreset(fddinfo[unit]->iod_ctlr);
		splx(s);
		return (0);

	case DKIOCGPART: {
		register struct dkpart *dk = (struct dkpart *) data;
		register struct fdst *st = &fdst[sc->sc_dens];

		dk->dk_size = st->nspc * st->ncyl;	/* size */
		dk->dk_start = 0;			/* start */
		dk->dk_blocksize = FDBPS;		/* device blocksize */
		dk->dk_ntrack = st->ntrak;		/* tracks/cylinder */
		dk->dk_nsector = st->nsect;		/* sectors/track */
		dk->dk_ncyl = st->ncyl;			/* cylinders */
		strcpy(dk->dk_name,st->name); /* copy name */
		return(0);
		}
	case DIOCGDINFO: {
		register struct disklabel *dl = (struct disklabel *) data;
		register struct fdst *st = &fdst[sc->sc_dens];

		dl->d_magic = DISKMAGIC;
		dl->d_type = DTYPE_FLOPPY;
		dl->d_subtype = 0;
		switch (sc->sc_drive) {
		    case FD360K:
			strcpy(dl->d_typename, /*st->name*/"fd360k");
			break;
		    case FD720K:
			strcpy(dl->d_typename, /*st->name*/"fd720k");
			break;
		    case FD1200K:
			strcpy(dl->d_typename, /*st->name*/"fd1200k");
			break;
		    case FD1440K:
			strcpy(dl->d_typename, /*st->name*/"fd1440k");
			break;
		}
			/* disk geometry: */
		dl->d_secsize = 512;
		dl->d_nsectors = st->nsect;
		dl->d_ntracks = st->ntrak;
		dl->d_ncylinders = st->ncyl;
		dl->d_secpercyl = st->nspc;
		dl->d_secperunit = st->ncyl * st->ntrak * st->nsect;
		dl->d_sparespertrack = 0;
		dl->d_sparespercyl = 0;
		dl->d_acylinders = 0;
		dl->d_magic2 = DISKMAGIC;
		dl->d_rpm = 6;
		dl->d_interleave = 1;
/*	u_short	d_checksum;		/* xor of data incl. partitions */

			/* filesystem and partition information: */
		dl->d_npartitions = 1;
		dl->d_partitions[0].p_size = dl->d_secperunit;
		dl->d_partitions[0].p_offset = 0;
		dl->d_partitions[0].p_fsize = 512;
		dl->d_partitions[0].p_fstype = FS_BSDFFS;
		dl->d_partitions[0].p_frag = 8;
		dl->d_partitions[0].p_cpg = 16;
		dl->d_bbsize = BBSIZE;
		dl->d_sbsize = SBSIZE;
		return(0);
		}
	}
	return(ENOTTY);		/* sigh */
}

/*
 * use the two different close routines for block and char.
 */
fdbclose(dev, flag)	/* block */
	dev_t	dev;
	int	flag;
{
	fdclose(dev,flag,1);
}
fdcclose(dev, flag)	/* character */
	dev_t	dev;
	int	flag;
{
	fdclose(dev,flag,0);
}
/*ARGSUSED*/
fdclose(dev, flag,block)
	dev_t dev;
	int	flag,block;
{
	register struct fd_softc *sc = &fd_softc[FDUNIT(dev)];
	register struct fd_ctlr *fdc = &fd_ctlr[fddinfo[FDUNIT(dev)]->iod_ctlr];

	trace(TR_F_CLO, pack(dev, block), flag);
	if (block) {
		sc->sc_bopen = 0;
	} else {
		sc->sc_copen = 0;
	}
	if ((sc->sc_bopen == 0) && (sc->sc_copen == 0)) {
		sc->sc_open = 0;
		fdc->fdc_drives--;
	}
	DEBUGF(fddebug > 8,printf("fdclose: dev=0x%x, sc_open=%d\n", dev, sc->sc_open););
}


/*
*	Strategy Routine:
*	Arguments:
*	  Pointer to  I/O buffer structure
*	  R/W function flag
*	Function:
*	  Start up the device
*/


fdstrategy(bp)
	register struct buf *bp;
{
	struct iocc_device *iod;
	register struct buf *dp;
	int s, unit = FDUNIT(bp->b_dev);
	struct fd_softc *sc = &fd_softc[unit];
	register struct fdst *st = &fdst[sc->sc_dens];
	DEBUGF(fddebug > 8,printf("fdstrategy entered\n"););

	if (sc->sc_open == 0 && !(sc->sc_flags&FDF_LOCK)) {
		printf("fd not open! bp=%x b_flags=%x b_blkno=%x b_bcount=%x\n", bp, bp->b_flags, bp->b_blkno, bp->b_bcount);
	}
	trace(TR_F_STR, pack(bp->b_dev, bp->b_bcount), bp->b_blkno);
	if (unit >= NFD)
		goto bad;
	iod = fddinfo[unit];
	if (iod == 0 || iod->iod_alive == 0)
		goto bad;

	if (bp->b_blkno < 0)  {
		DEBUGF(fddebug > 0,printf("fdstrategy: requested a sector out of range\n"););
		goto bad;
	}
	/* if total transfer execeeds disk capacity, truncate b_bcount */
	sc->sc_trunc=0;
	if (((bp->b_blkno * BSIZE) + bp->b_bcount) > st->nbpd) {

		if (bp->b_blkno*BSIZE >= st->nbpd) {	/* over end of disk? */
			bp->b_resid = bp->b_bcount;
			iodone(bp);
			return;
		}

		sc->sc_trunc=bp->b_bcount;
		bp->b_bcount = st->nbpd - (bp->b_blkno * BSIZE); 
		if (bp->b_bcount < 0)
			bp->b_bcount = 0;
		sc->sc_trunc-=bp->b_bcount;
		DEBUGF(fddebug > 0,printf("fdstrategy: transfer count too large, reduced to = %d (dec) \n", bp->b_bcount););
	}
	if (bp->b_bcount <= 0) {
		bp->b_resid = sc->sc_trunc;
		goto done;
	}

	s = FD_SPL();
	DEBUGF(fddebug > 1,printf("fdstrat: bp=0x%x, fl=0x%x, un=%d, bl=%d, cnt=%d\n", bp, bp->b_flags, unit, bp->b_blkno, bp->b_bcount););
	bp->b_cylin = bp->b_blkno;	  /* don't care to calculate trackno */
	dp = &fdutab[unit];
	disksort(dp, bp);		  /* */
	if (dp->b_active == 0) {
		fdustart(iod);
		bp = &iod->iod_mi->ic_tab;
		DEBUGF(fddebug > 8,printf("fdstrategy after fdustart  bp=0x%x\n", bp););
		if (bp->b_actf && bp->b_active == 0) {
			fdstart(iod->iod_mi);
		} else {
			trace(TR_F_STR, pack(bp->b_dev, 1), -1);
		}
	} else {
		trace(TR_F_STR, pack(bp->b_dev, 2), -1);
	}
	splx(s);

	DEBUGF(fddebug > 8,printf("fdstrategy exit\n"););
	return;

bad:
	trace(TR_F_STR, pack(bp->b_dev, 3), -1);
	bp->b_error = ENXIO;
	bp->b_flags |= B_ERROR;
done:
	if (bp->b_resid != 0) {
		trace(TR_F_STR, pack(bp->b_dev, 0), bp->b_resid);
	} else {
		trace(TR_F_STR, pack(bp->b_dev, 4), -1);
	}
	iodone(bp);
	return;
}


/*
 * Unit start routine.
 * Put this unit on the ready queue for the controller
 */
fdustart(iod)
	register struct iocc_device *iod;
{
	struct buf *dp = &fdutab[iod->iod_unit];
	struct iocc_ctlr *ic = iod->iod_mi;

	DEBUGF(fddebug > 8,printf("fdustart entered ic=0x%x\n", ic););

	dp->b_forw = NULL;
	if (ic->ic_tab.b_actf == NULL)
		ic->ic_tab.b_actf = dp;
	else
		ic->ic_tab.b_actl->b_forw = dp;
	ic->ic_tab.b_actl = dp;
	dp->b_active++;
}


/*
 * Controller start routine.
 * Start a new transfer or continue a multisector
 * transfer. If this is a new transfer (dp->b_active == 1)
 * save the start address of the data buffer and the total
 * byte count in the soft control structure. These are
 * restored into the buffer structure when the transfer has
 * been completed, before calling 'iodone'.
 */
fdstart(ic)
	register struct iocc_ctlr *ic;
{
	register struct fdst *st;
	register struct fd_ctlr *fdc;
	register struct fd_softc *sc;
	struct buf *dp, *bp;
	int unit;
	register int sector;
	register char cylinder, head;

	DEBUGF(fddebug > 8,printf("fdstart entered\n"););

	if (ic->ic_tab.b_active) {
		trace(TR_F_STA, -1, ic->ic_tab.b_active);
		return;
	}
loop:
	if ((dp = ic->ic_tab.b_actf) == NULL) {
		trace(TR_F_STA, -1, -1);
		return;
	}
	if ((bp = dp->b_actf) == NULL) {
		ic->ic_tab.b_actf = dp->b_forw;
		goto loop;
	}
	trace(TR_F_STA, pack(bp->b_dev, bp->b_bcount), bp->b_blkno);
	ic->ic_tab.b_active++;
	unit = FDUNIT(bp->b_dev);
	sc = &fd_softc[unit];
	fdc = &fd_ctlr[ic->ic_ctlr];
	if (fdc->fdc_lstdens == sc->sc_dens)
		fdctrl(unit,fdc->fdc_ctrl);	/* select drive */
	else
		fdmotoron(unit);		/* set proper xfer rate */
	st = &fdst[sc->sc_dens];

	/* save multi-sector info */
	if (dp->b_active == 1) {
		sc->sc_resid = bp->b_bcount; /* */
		sc->sc_bcnt = bp->b_bcount;
		sc->sc_uaddr = bp->b_un.b_addr;
		sc->sc_reduce = 0;
		dp->b_active++;
	}
	bp->b_bcount = sc->sc_resid;

	if (bp->b_flags & B_SETUP) {
		fdc->fdc_state= FDS_SETUP;
		(void) fdint(ic->ic_ctlr);
		return;
	}
	DEBUGF(fddebug > 8,printf("fdstart: "););
	/* for either read or write, we must SEEK */
	fdc->fdc_tocnt = 0;
	/* enable interupts */
	fdctrl(unit,fdc->fdc_ctrl | FDINTENABLE);

	sector = ((bp->b_blkno * BSIZE) + (sc->sc_bcnt - sc->sc_resid)) /FDBPS;
	cylinder = sector / (unsigned)(st->nspc);
	sector %= st->nspc;
	head = (sector / st->nsect) & 1;
	sector %= (st->nsect);

	sc->sc_cyl = cylinder;
	sc->sc_sec = (char) sector;
	sc->sc_head = head;

	/* if we're having problems try transfering only one sector */
	if (((++ic->ic_tab.b_errcnt >= FD_REDUCE_XFER) && 
		((sc->sc_flags & FDF_FORMAT) == 0)) || (sc->sc_reduce)) {
		sc->sc_maxbyt = FDBPS;
		sc->sc_reduce = 1;
	} else {
		sc->sc_maxbyt = (st->nsect - sector) * FDBPS;
	}

	if (bp->b_bcount > sc->sc_maxbyt)
		bp->b_bcount = sc->sc_maxbyt;

	if ((sc->sc_curcyl != cylinder) || (sc->sc_hrdhd != head)) {
		fdc->fdc_state = FDS_SEEK;
		/* send seek commands to adapter */
		fdseek(unit,cylinder, head);
	} else {
		DEBUGF(fddebug > 8,printf("fdstart: no seek needed...sc->sc_curcyl=0x%x  req. cyl=0x%x\n", sc->sc_curcyl, cylinder););
		ic->ic_dmabuf = bp;
		if (dma_setup(ic)) {
			ic->ic_tab.b_active = 0;
			bp->b_error = EIO;
			bp->b_flags |= B_ERROR;
			/* disable interupts */
			fdctrl(unit,fdc->fdc_ctrl & ~FDINTENABLE);
			trace(TR_F_STA, bp->b_dev<<16 | bp->b_flags, -1);
			iodone(bp);
		} 
		DEBUGF(fddebug > 8,printf("fdstart: after dma_setup\n"););
	}
	DEBUGF(fddebug > 8,printf("fdstart: exiting"););
}

/* ARGSUSED */
fddgo(ic,length,addr,bp)
	struct iocc_ctlr *ic;
	int length,addr;	/* note: addr is a dma address, NOT a pointer */
	struct buf *bp;
{
	register int unit = FDUNIT(bp->b_dev);
	struct fd_softc *sc = &fd_softc[unit];
	struct fd_ctlr *fdc = &fd_ctlr[ic->ic_ctlr];

	register struct fdst *st = &fdst[sc->sc_dens];
	register char sector = sc->sc_sec;
	register char cylinder = sc->sc_cyl;
	register char head = sc->sc_head;

	DEBUGF(fddebug > 8,printf("fddgo: entered\n"););


	sc->sc_error = 0;
	DEBUGF(fddebug > 8,printf("\rfd: cyl %x  sec %x  count %x  ", cylinder, sector, bp->b_bcount););
					  /* */

	/* enable the channel */
	dma_go(ic->ic_dmachannel);

	trace(TR_F_DGO, pack(bp->b_dev, bp->b_bcount), bp->b_blkno);
	/* determine if READ or WRITE or FORMAT */
	if (bp->b_flags & B_CTRL) {
		/* FORMAT */
		fdc->fdc_state = FDS_FORMAT;
		fdcmd(unit,FDFORMAT); 		/* start format (1 track) */
		fdcmd(unit,(head << FDBIT2)+(char) fddinfo[unit]->iod_slave);
		fdcmd(unit,FDSECSIZE);		/* byte per sector (2= 512)*/
		fdcmd(unit,st->nsect);		/* sectors per cylinder */
		fdcmd(unit,st->fgpl);		/* Gap length */
		fdcmd(unit,FDPATTERN);		/* pattern */
		DEBUGF(fddebug > 8,printf("fddgo: exiting\n"););
		return;
	}
	if (bp->b_flags & B_READ) {
		/* READ */
		if (bp->b_flags & B_VERIFY) {
			fdc->fdc_state = FDS_VERIFY;
		} else {
			fdc->fdc_state = FDS_READ;
		}
		fdcmd(unit,FDREAD);
	} else {
		/* WRITE */
		fdc->fdc_state = FDS_WRITE; 
		fdcmd(unit,FDWRITE);
	}
	/* end read/write if */


	/* common disk setup code for read and write */
	fdcmd(unit,(head << FDBIT2) + (char) fddinfo[unit]->iod_slave);
	fdcmd(unit,cylinder);			/* cylinder */
	fdcmd(unit,head);	  		/* put head number at bit 1 */
	fdcmd(unit,(sector + 1));		/* sector number (1-n) */
	fdcmd(unit,FDSECSIZE);			/* sector size */
	fdcmd(unit,st->nsect);			/* last sector on track */
	fdcmd(unit,st->gpl);			/* gap length */
	fdcmd(unit,FDSECBASE);			/* sector size */

	DEBUGF(fddebug > 8,printf("fddgo: exiting\n"););

}


fdint(ctlr)
	int ctlr;
{
	int unit;
	struct iocc_ctlr *ic = fdminfo[ctlr];
	register struct buf *bp, *dp;
	register struct fd_softc *sc;
	register struct fdst *st; 
	struct iocc_device *iod;
	struct fd_ctlr *fdc = &fd_ctlr[ctlr];

	DEBUGF(fddebug,printf("fdint: entered; ctlr=%d  ",ctlr););

	/* special state used by fdslave only */
	if (fdc->fdc_state == FDS_SLAVE) {
		fdc->fdc_state = FDS_IDLE;
		DELAY(5);
		trace(TR_F_INT, -1, 1);
		return;
	}
	if (!ic->ic_tab.b_active) {
		DEBUGF(fddebug > 1,printf("fdint: ic not active\n"););
		trace(TR_F_INT, -1, 2);
		return (1);
	}
	dp = ic->ic_tab.b_actf;
	if (!dp->b_active) {
		DEBUGF(fddebug > 1,printf("fdint: dp->b_active not active\n"););
		trace(TR_F_INT, -1, 3);
		return(1);
	}
	bp = dp->b_actf;
	trace(TR_F_INT, pack(bp->b_dev, bp->b_bcount), bp->b_blkno);
	unit = FDUNIT(bp->b_dev);
	sc = &fd_softc[unit];
	st = &fdst[sc->sc_dens];
	iod = fddinfo[unit];
	fdc->fdc_tocnt = 0;
	DEBUGF(fddebug>0,printf(" fdintstate= %s ",statestring[fdc->fdc_state].value););
	fdctrl(unit,fdc->fdc_ctrl & ~FDINTENABLE);
	if (sc->sc_flags & (FDF_TIMEOUT)) {
		if ((fdc->fdc_state == FDS_READ) || (fdc->fdc_state == FDS_FORMAT) || (fdc->fdc_state == FDS_WRITE) || (fdc->fdc_state == FDS_VERIFY)) {
			dma_done(ic->ic_dmachannel);
			fdunload_results(unit);
		}
		goto giveup;
	}

	switch (fdc->fdc_state) {

		/*
		 * Incomplete commands.  Perform next step
		 * and return.  Note that b_active is set on
		 * entrance and, therefore, also on exit.
		 */
	case FDS_SEEK:
		fdc->fdc_state = FDS_SKDON;
		timeout(fdcallint, ctlr, st->delay*FDHDSETTLE);
		DEBUGF(fddebug,printf("\n"););
		return(0);

	case FDS_SKDON:				/* done with seek */
		fdcmd(unit,FDSENI);

		/* get result */
		sc->sc_sr0 = fdresult(unit,sc->sc_sr0);
		sc->sc_curcyl = fdresult(unit,sc->sc_curcyl);
		sc->sc_hrdcyl=sc->sc_curcyl;
		sc->sc_curcyl = (sc->sc_dens == 0) ? (sc->sc_curcyl / 2) : sc->sc_curcyl;
		(void) fdwait(unit,FDSTATDAB | FDSTATDBB, 0); /* wait for READY */
		DEBUGF(fddebug,printf("Seek ret = 0x%b", (int) sc->sc_sr0,FDSR0););

		/* if error */
		if ((sc->sc_sr0 & FDSR0MASK) != 0)
			goto error;
		fdctrl(unit,fdc->fdc_ctrl | FDINTENABLE);
		ic->ic_dmabuf = bp;
		if (dma_setup(ic)) {
			ic->ic_tab.b_errcnt = 0;
			ic->ic_tab.b_active = 0;
			bp->b_error = EIO;
			bp->b_flags |= B_ERROR;
			goto cleanup;
		}

		DEBUGF(fddebug,printf("\n"););
		return (0);

	case FDS_READ:
	case FDS_FORMAT:
		dma_done(ic->ic_dmachannel);

		DEBUGF(fddebug > 8, {
			if (bp->b_flags & B_READ) 
				printf("fdint: B_READ; ");
			else
				printf("fdint: B_CTRL: ");
		});
		fdunload_results(unit);

		/* if error */
		if ((sc->sc_sr0 & FDSR0MASK) != 0)
			goto error;

		fdc->fdc_state = FDS_IDLE;	  /* end read/write/format */
		goto done;

	case FDS_WRITE:
		dma_done(ic->ic_dmachannel);

		DEBUGF(fddebug > 8, {
			printf("fdint: B_WRITE; ");
		});

		fdunload_results(unit);

		/* if error */
		if ((sc->sc_sr0 & FDSR0MASK) != 0)
			goto error;

		fdc->fdc_state = FDS_IDLE;	  /* end read/write/format */

		/*
		 * if no verify is set then just continue
		 */	
		if (nofdverify)
			goto done;

		/*
		 * verify what we just wrote
	 	 */
		ic->ic_tab.b_active = 0;
		sc->sc_write_retry = ic->ic_tab.b_errcnt;
		ic->ic_tab.b_errcnt = 0;

		sc->sc_verify = bp;
		dp->b_actf = sc->sc_verbuf;
		dp->b_actf->b_flags = B_VERIFY | B_READ | B_BUSY;
		dp->b_actf->b_blkno = bp->b_blkno;
		dp->b_actf->b_dev = bp->b_dev;
		dp->b_actf->b_error = bp->b_error;
		dp->b_actf->av_forw = bp->av_forw;
		dp->b_actf->b_bcount = bp->b_bcount;
		
		fdstart(ic);
		return(0);

	case FDS_VERIFY:
		dma_done(ic->ic_dmachannel);

		DEBUGF(fddebug > 8, {
			printf("fdint: B_VERIFY; ");
		});

		fdunload_results(unit);

		/* if error */
		if ((sc->sc_sr0 & FDSR0MASK) != 0) 
			/* retry read */
			goto error;

		/* complete the task off-level */
		timeout(fdverify,ctlr,0);
		return(0);


	/*
	 * verify complete
	 */
	case FDS_VER_DONE:
		/* continue with the write */
		sc->sc_verify->b_error = bp->b_error;
		bp = sc->sc_verify;
		dp->b_actf = bp;
		fdc->fdc_state = FDS_IDLE;	  /* end read/write/format */
		goto done;

	/*
	 * error detected, retry the write.
	 */
	case FDS_VER_ERROR:
		sc->sc_verify->b_error = bp->b_error;
		ic->ic_tab.b_errcnt = sc->sc_write_retry;
		bp = sc->sc_verify;
		dp->b_actf = bp;
		fdc->fdc_state = FDS_WRITE;
		goto error;

	case FDS_SETUP:
		/* don't reset the controller if the other unit is open */
		if (fdc->fdc_drives == 0)
			fdctrl(unit,fdc->fdc_ctrl & FDRST);
		sc->sc_error = 0;
		fdmotoron(unit);
		fdcmd(unit,FDSPEC);
		fdcmd(unit,st->step);
		fdcmd(unit,FDLOAD);
		fdrecal(unit);
		DEBUGF(fddebug,printf ("Fdrecall completed\n"););
		if (sc->sc_error) {
			bp->b_error=EIO;
			bp->b_flags=B_ERROR;
		} 
		fdc->fdc_state = FDS_DONE;
		timeout(fdcallint,ctlr,st->delay*FDRECALDELAY); 
		DEBUGF(fddebug,printf("\n"););
		return(0);

	case FDS_RECAL:
		fdc->fdc_state = FDS_IDLE;
		ic->ic_tab.b_active = 0;
		fdstart(ic);
		DEBUGF(fddebug,printf("\n"););
		return(0);

	case FDS_DONE: 
		fdc->fdc_state = FDS_IDLE;
		ic->ic_tab.b_errcnt = 0;
		ic->ic_tab.b_active = 0;
		goto cleanup;

	default:
		printf("fd%d: state %s (reset)\n", unit, statestring[fdc->fdc_state].value);
		fdreset(ctlr);
		return (0);		  /* */
	}
error:
	/*
	 * In case of an error:
	 *  (a) In case of TIMEOUT giveup.
	 *  (b) Otherwise try operation FD_ERROR_RETRY more times
	 *  (c) Make auto density do it's own retrying
	 */


	if (++ic->ic_tab.b_errcnt >= (sc->sc_open ? FD_ERROR_RETRY : 3)) {
		goto giveup;
	}
	fdrecal(unit);
	fdc->fdc_state=FDS_RECAL;
	timeout(fdcallint, ctlr, st->delay*FDRECALDELAY);
	DEBUGF(fddebug,printf("\n"););
	return (0);

giveup:
	if (sc->sc_open == 0)
		goto done;
	/*
	 * Hard I/O error --
	 *	Current request is aborted, next request is started.
	 */

	sc->sc_trunc += sc->sc_resid;
	sc->sc_resid = 0;		  /* make sure the transfer is terminated */

	ic->ic_tab.b_errcnt = 0;
	ic->ic_tab.b_active = 0;
	fdread_status(unit);
	if (!(sc->sc_suppress)) {
		printf("fd%d: hard error trk = %d  sec =%d ",unit,
					(int) sc->sc_cyl, (int)sc->sc_sec);
		printf("Sr0 = 0x%b ",(int)sc->sc_sr0,FDSR0);
		printf("Sr1 = 0x%b ",(int)sc->sc_sr1,FDSR1);
		printf("Sr2 = 0x%b ",(int)sc->sc_sr2,FDSR2);
		printf("Sr3 = 0x%b ",(int)sc->sc_sr3,FDSR3);
		printf("Cyl= %d Sec=%d Head=%d St=%d ",sc->sc_hrdcyl,
					sc->sc_hrdsc,sc->sc_hrdhd,sc->sc_size);
		printf("state = %s.\n",statestring[fdc->fdc_state].value);
	}

	fdrecal(unit);
	bp->b_flags |= B_ERROR;
	fdc->fdc_state = FDS_IDLE;
	ic->ic_tab.b_active = 0;
	ic->ic_tab.b_errcnt = 0;
	goto cleanup;

done:
	DEBUGF(fddebug > 8,printf("fdint: at done; "););

	ic->ic_tab.b_active = 0;
	ic->ic_tab.b_errcnt = 0;

	/* multi-sector tricks */
	if ((sc->sc_resid -= sc->sc_maxbyt) > 0) {
		bp->b_un.b_addr += sc->sc_maxbyt;
		fdstart(ic);
		DEBUGF(fddebug,printf("\n"););
		return (0);
	}
cleanup:
	bp->b_resid = sc->sc_trunc;
	bp->b_un.b_addr = sc->sc_uaddr;
	bp->b_bcount = sc->sc_bcnt;

	dp->b_actf = bp->av_forw;
	if (bp->b_resid != 0) {
		trace(TR_F_INT, pack(bp->b_dev, -1), bp->b_resid);
	} else {
		trace(TR_F_INT, bp->b_dev<<16 | bp->b_flags, -1);
	}
	iodone(bp);
	fdc->fdc_state = FDS_IDLE;
	ic->ic_tab.b_actf = dp->b_forw;
	dp->b_active = 0;
	dp->b_errcnt = 0;
	DEBUGF(fddebug > 8,printf(".. bp=%x, new=%x\n", bp, dp->b_actf););

	/*
	 * If this unit has more work to do,
	 * start it up right away
	 */
	if (dp->b_actf)
		fdustart(iod);

	fdstart(ic);
	DEBUGF(fddebug > 8,printf("fdint: exit; "););
	DEBUGF(fddebug,printf("\n"););
	return (0);
}

fdcallint(ctlr)
{
	int	s = FD_SPL();

	DEBUGF(fddebug>0,printf("fdcallint: ctlr=%d\n",ctlr););
	(void) fdint(ctlr);
	splx(s);
	return;
}

#define MIN3(x,y,z) ( ((x)<(y)) ? (((z)<(x))?(z):(x)) : (((z)<(y))?(z):(y))) 
/* these shouln't be here XXX */
#define PAGE_SIZE	2048
#define PAGE_MASK	(PAGE_SIZE-1)
/* return bytes to the end of the page */
#define PAGE_END(x)	(PAGE_SIZE - ((unsigned)(x) & PAGE_MASK))


/*
 * Handle the verify function off level
 */
fdverify(ctlr)
	int ctlr;
{
	int unit,x;
	struct iocc_ctlr *ic = fdminfo[ctlr];
	register struct buf *bp, *dp;
	register struct fd_softc *sc;
	struct fd_ctlr *fdc = &fd_ctlr[ctlr];
	caddr_t	new_buf,old_buf,real_old_buf,real_new_buf;

	dp = ic->ic_tab.b_actf;
	bp = dp->b_actf;
	unit = FDUNIT(bp->b_dev);
	sc = &fd_softc[unit];
	fdc->fdc_tocnt = 0;
	old_buf = sc->sc_verify->b_un.b_addr;
	new_buf = bp->b_un.b_addr;
	real_new_buf = real_buf_addr(bp,new_buf);
	real_old_buf = REAL_BUF_ADDR(sc->sc_verify,old_buf);

	/* 
	 * verify that we read back the proper data
	 * NOTE it is possible that the buffers do not
	 * have the same page alignment.
	 */		
	while (bp->b_bcount) {
		/* 
		 * compare the minimum of:
		 * 1) the bytes left on the original buffer's page
		 * 2) the bytes left on the new buffer's page
		 * 3) the remaining bytes of the transfer that haven't
		 *    been compared
		 */
		int max_cmp = MIN3(PAGE_END(real_new_buf),bp->b_bcount,
						PAGE_END(real_old_buf));
#ifdef DEBUG
		/* make sure their are no infinite loops */
		if ((max_cmp == 0) || (bp->b_bcount < 0)) {
			panic("fdverify bad count");
		}
#endif DEBUG
		GET_VR0(x);
		if (bcmp(real_new_buf,real_old_buf,max_cmp)) {
			SET_VR(x);
			/*
			 * retry write
			 */
			fdc->fdc_state = FDS_VER_ERROR;

			/*
			 * Keep the post transfer cleanup/retry code together
			 */
			fdcallint(ctlr);
			return;
		}
		SET_VR(x);
		/*
		 * If bp->b_bcount is not 0, then we must have crossed a page
		 * boundary (e.i. bp->b_bcount was not the minimum value in
		 * the MIN3 case). Get the address of the new buffers for
		 * comparing.
		 */
		if (bp->b_bcount -= max_cmp) {
			new_buf += max_cmp;
			old_buf += max_cmp;
			real_new_buf=real_buf_addr(bp,new_buf);
			real_old_buf=REAL_BUF_ADDR(sc->sc_verify,old_buf);
		}
	}
	fdc->fdc_state = FDS_VER_DONE;

	/*
	 * Keep the post transfer cleanup/retry code together
	 */
	fdcallint(ctlr);
	return;
}

fdwatch()
{

	register struct iocc_device *iod;
	register struct iocc_ctlr *ic;
	register struct fd_softc *sc;
	struct fd_ctlr *fdc;
	int i, dopen = 0;
	int s=FD_SPL();

	for (i = 0; i < NFD; i++) {
		iod = fddinfo[i];
		if (iod == 0 || iod->iod_alive == 0)
			continue;
		sc = &fd_softc[i];
		if ((sc->sc_open == 0) && (fdutab[i].b_active == 0)) {
			/*
			 * It's now safe to release sc_verbuf
			 */
			if (!nofdverify && sc->sc_verbuf) {
				register struct buf *bp = sc->sc_verbuf;

				bp->b_bcount = 
				   fdst[sc->sc_dens].nsect*FDBPS;
				trace(TR_F_REL, pack(bp->b_dev, bp->b_bcount), bp->b_blkno);
 				brelse(bp);
				sc->sc_verbuf = NULL;
			}
			if (sc->sc_motor >= 1) {
				if (--sc->sc_motor < 1) {
					/* turn off motor */
					fdmotoroff(i);
				} else {
					dopen++;
				}
			}
			continue;
		}
		dopen++;
	}
	/* check the controller loop */
	for (i = 0; i < NFDC; i++) {
		ic = fdminfo[i];
		if (ic == 0 || ic->ic_alive == 0)
			continue;
		fdc = &fd_ctlr[ic->ic_ctlr];
		if (++fdc->fdc_tocnt >= FD_MAXTIMEOUT) {
			if (ic->ic_tab.b_active) {
				int 	unit;
				unit = FDUNIT(ic->ic_tab.b_actf->b_actf->b_dev);
				if (fdc->fdc_tocnt == FD_MAXTIMEOUT) {
				    sc->sc_flags|=FDF_TIMEOUT;
				    /* 
				     * If an operation was started this will 
				     * generate and interupt
				     */
				     fdctrl(0,fdc->fdc_ctrl & ~FDINTENABLE);
				} else {
				     /* Something is really wrong call fdint by hand */
				     fdc->fdc_tocnt = 0;
				     printf("fd%d: timeout\n", unit);
				     (void) fdint(ic->ic_ctlr);
				}
			} else {
				fdc->fdc_tocnt = 0;
			}
		}
	}
	if (dopen) {
		timeout(fdwatch, (caddr_t)0, hz);
	} else {
		fdwstart = 0;
	}
	splx(s);
}


fdreset(ctlr)
	int ctlr;
{
	register struct iocc_ctlr *dm;

	DEBUGF(fddebug > 8,printf("fdreset entered\n"););

	if ((dm = fdminfo[ctlr]) != 0 && dm->ic_alive != 0) {
		fd_ctlr[ctlr].fdc_state = FDS_IDLE;
		fdstart(dm);
	}
}



/*
 * Initiate a format command.
 */
fdformat(dev)
	dev_t dev;
{
	register int unit = FDUNIT(dev);
	register struct fd_softc *sc = &fd_softc[unit];
	struct buf *bp = geteblk(FDBPS);
	register struct fdst *st = &fdst[sc->sc_dens];
	register int error=0,cyl,head,sect;
	register struct fdformdata *fdformdata = (struct fdformdata *) bp->b_un.b_addr;

	/* Set up */
	bp->b_dev = dev;
	bp->b_error = 0;
	bp->b_flags |= B_SETUP;
	bp->b_blkno = 0;
	bp->av_forw = 0;
	bp->b_bcount = FDBPS;
	trace(TR_F_FOR, pack(bp->b_dev, bp->b_bcount), bp->b_blkno);
	(void) fdstrategy(bp);
	trace(TR_F_WAI, pack(bp->b_dev, bp->b_bcount), bp->b_blkno);
	iowait(bp);
	if (bp->b_flags & B_ERROR) {
		error = bp->b_error;
		goto out;
	}

	sc->sc_flags = FDF_FORMAT | FDF_LOCK;
	for (cyl = 0; cyl < st->ncyl ; cyl++) {
		for (head = 0; head < st->ntrak; head++) {
			int	error_cnt = 0;
format_retry:
			/* load the sector information */
			for (sect = 0; sect < st->nsect; sect++) {
				fdformdata[sect].fdtrack = (char) cyl;
				fdformdata[sect].fdhead = (char) head;
				fdformdata[sect].fdsect = (char) sect + 1;
				fdformdata[sect].fdsize = FDSECSIZE;
			}
			DEBUGF(fddebug,{
			register caddr_t tmpaddr = bp->b_un.b_addr;
			register int count;
			printf("\n");
			for (count=0; count < st->nsect; count++) {
				char xcyl = *tmpaddr++;
				char xhd = *tmpaddr++;
				char xsec = *tmpaddr++;
				char xsize = *tmpaddr++;
			 	printf("<<cyl=%d head=%d sect=%d size=%d\n",
					xcyl,xhd,xsec,xsize);
				xcyl = fdformdata[count].fdtrack;
				xhd = fdformdata[count].fdhead;
				xsec = fdformdata[count].fdsect;
				xsize = fdformdata[count].fdsize;
			 	printf(">>cyl=%d head=%d sect=%d size=%d\n",
					xcyl,xhd,xsec,xsize);
			}
			printf("head = %d\n",head);
			});

			/* Format */
			bp->b_dev = dev;
			bp->b_flags = B_CTRL|B_BUSY;
			bp->b_error = 0;
			bp->b_blkno = (cyl * st->nspc) + (head * st->nsect);
			bp->b_bcount = st->nsect * FDBPS;
			bp->av_forw = 0;
			(void) fdstrategy(bp);
			trace(TR_F_WAI, pack(bp->b_dev, bp->b_bcount), bp->b_blkno);
			iowait(bp);
			if (bp->b_flags & B_ERROR) {
				if (++error_cnt > FD_ERROR_RETRY) {
					error = EIO;
					break;
				}
				goto format_retry;
			}

			/*
			 * if the no verify is on don't
			 */
			if (nofdverify)
				continue;

			/* verify the format */
			for (sect = 0 ; sect < st->nsect ; sect++) {
				sc->sc_suppress = 1;
				bp->b_dev = dev;
				bp->b_flags = B_READ|B_BUSY;
				bp->b_error = 0;
				bp->b_blkno = (cyl * st->nspc) + 
						(head * st->nsect) + sect;
				bp->b_bcount = FDBPS;
				bp->av_forw = 0;
				DEBUGF(fddebug,printf("vread block = %d\n",
								bp->b_blkno););
				(void) fdstrategy(bp);
				trace(TR_F_WAI, pack(bp->b_dev, bp->b_bcount), bp->b_blkno);
				iowait(bp);
				sc->sc_suppress = 0;
				/* no read errors, sector must exist */
				if (bp->b_flags & B_ERROR) {
					if (++error_cnt > FD_ERROR_RETRY) {
						error = EIO;
						break;
					}
					goto format_retry;
				}
			}
			if (error)
				break;
		}
		if(error)
			break;
	}
out:
	sc->sc_flags &= ~FDF_LOCK & ~FDF_FORMAT;
	trace(TR_F_REL, pack(bp->b_dev, bp->b_bcount), bp->b_blkno);
	brelse(bp);	
	return (error);
}

/* read extended status */
fdread_status(unit)
	int unit;
{
	register struct fd_softc *sc = &fd_softc[unit];

	fdcmd(unit,FDREADSTAT);			  /* read status */
	fdcmd(unit,(char) fddinfo[unit]->iod_slave);	  /* drive head */
	sc->sc_sr3 = fdresult(unit,FDWAITREAD);
	DEBUGF(fddebug,printf("fdread_status: 0x%b\n", (int) sc->sc_sr3, FDSR3););
}


/* recalibrate floppy drive (reset head to track 0)*/
fdrecal(unit)
	int unit;
{
	register struct fd_softc *sc = &fd_softc[unit];

	fdcmd(unit,FDREST);
	fdcmd(unit, (char) fddinfo[unit]->iod_slave);
	fdread_status(unit);
/*	if ((sc->sc_sr3 & FDTRAK0))
		sc->sc_error++; 
	else */
		sc->sc_curcyl = 0;
}

/* start support routines for fd.c (these aren't "classic" driver routines)   */

fdmotoron(unit)
{
	register struct iocc_device *iod = fddinfo[unit];
	register struct fddevice *fdadapter = (struct fddevice *) iod->iod_addr;
	register struct fd_softc *sc = &fd_softc[unit];
	register struct fd_ctlr *fdc = &fd_ctlr[iod->iod_ctlr];

	fdctrl(unit,fdc->fdc_ctrl | FDMOTORON(iod->iod_slave));
	/* set drive transfer rate here too */
	fdadapter->fd_digin = fdst[sc->sc_dens].xfer;
	fdctrl(unit,fdc->fdc_ctrl | FDMOTORON(iod->iod_slave));
	sc->sc_motor = FDMOTORWAIT;
	fdc->fdc_lstdens = sc->sc_dens;
}


/* turn off motor if other unit is open turn it on */
fdmotoroff(unit)
{
	register struct iocc_device *iod = fddinfo[unit]; 
	register struct iocc_device *iod2;
	register struct fd_softc *sc = &fd_softc[unit];
	register struct fd_ctlr *fdc = &fd_ctlr[iod->iod_ctlr];
	register int unit2;

	if (((unit2 = FDOTHERUNIT(unit)) < NFD) && (iod2 = fddinfo[unit2]) && (iod2->iod_alive)) {
		fdctrl(unit2,fdc->fdc_ctrl & FDMOTOROFF(iod->iod_slave));
	} else {
		fdctrl(unit,fdc->fdc_ctrl & FDMOTOROFF(iod->iod_slave));
	}
	sc->sc_motor = 0;
}

fdctrl(unit,cmd)
	unsigned char cmd;
{
	register struct iocc_device *iod = fddinfo[unit];
	register struct fddevice *fdadapter = (struct fddevice *) iod->iod_addr;
	register struct fd_ctlr *fdc = &fd_ctlr[iod->iod_ctlr];

	fdc->fdc_ctrl = cmd & FDCTRLMASK;
	fdadapter->fd_digout = cmd | iod->iod_slave; 
}

/* send command to floppy disk controller */
fdcmd(unit,cmd)
	char cmd;
{
	register struct fddevice *fdadapter = (struct fddevice *) fddinfo[unit]->iod_addr;
	register struct fd_softc *sc = &fd_softc[unit];

	if (fdwait(unit,FDSTATRQM | FDSTATDIO, FDSTATRQM) == 0) /* wait for READY */
		sc->sc_error++;
	fdadapter->fd_data = cmd;	  /* */
}


/* Wait for disk I/O to complete */
fdwait(unit,mask, compare)
	unsigned char mask;
	unsigned char compare;
{
	register struct fddevice *fdadapter = (struct fddevice *) fddinfo[unit]->iod_addr;
	int timeout = FDWAITTIME;
	u_char status;

	while ((((status = fdadapter->fd_status) & mask) != compare) && (--timeout))
		DELAY( (int) 2);

	if (timeout)
		return(1);
	else {
		DEBUGF(fddebug,{
			printf("fdwait: mask 0x%b,",mask,FDSTAT);
			printf(" compare 0x%b, ",compare,FDSTAT);
			printf("status 0x%b,\n",status,FDSTAT);
		});
		return(0);
	}
}


/* read result byte from the NEC diskette controller */
char fdresult(unit,value)
	char value;
{
	register struct fddevice *fdadapter = (struct fddevice *) fddinfo[unit]->iod_addr;
	register struct fd_softc *sc = &fd_softc[unit];
	char result,temp;

	if (fdwait(unit,FDSTATRQM | FDSTATDIO, FDSTATRQM | FDSTATDIO))  /* wait for READY */
		result = fdadapter->fd_data;	  /* */
	else {
	 	temp = fdadapter->fd_data;
		if (value =  FDWAITREAD)
			result = temp;
		else
			result = value;
		sc->sc_error++;
	}

	return (result);
}


fdseek(unit,cylinder, head)
	unsigned char cylinder, head;
{
	register struct fd_softc *sc = &fd_softc[unit];

	DEBUGF(fddebug > 8,printf("fdseek: entered  cyl=%d  head=%d;  sc->sc_dens=%d", cylinder, head, sc->sc_dens););

	fdcmd(unit,FDSEEK);
	fdcmd(unit,(head << FDBIT2) + (char) fddinfo[unit]->iod_slave);
	/* double step on a 360k diskette in 1.2M drive */
	if (sc->sc_dens == 0)
		fdcmd(unit,cylinder * FDDBLSTEP); /* cylinder */
	else
		fdcmd(unit,cylinder);	  /* cylinder */

	DEBUGF(fddebug > 8,printf("fdseek: exiting; "););
}


/* unload command results */
fdunload_results(unit)
{
	register struct fd_softc *sc = &fd_softc[unit];

	sc->sc_sr0 = fdresult(unit,FDWAITREAD);
	sc->sc_sr1 = fdresult(unit,FDWAITREAD);
	sc->sc_sr2 = fdresult(unit,FDWAITREAD);
	sc->sc_hrdcyl = fdresult(unit,sc->sc_hrdcyl); /* next sector is at this cylinder */
	sc->sc_hrdhd = fdresult(unit,sc->sc_hrdhd);
	sc->sc_hrdsc = fdresult(unit,sc->sc_hrdsc);
	sc->sc_size = fdresult(unit,sc->sc_size);
	DEBUGF(fddebug > 0, {
		printf("FD: unload Sr0 = 0x%b ",(int) sc->sc_sr0,FDSR0); /* */
		printf(" Dsr1 = 0x%b ",(int) sc->sc_sr1,FDSR1); /* */ 
		printf(" DSr2 = 0x%b ",(int) sc->sc_sr2,FDSR2);	/* */
		printf(" cyl = %x ",sc->sc_hrdcyl);/* */
		printf(" hd = %x ", sc->sc_hrdhd); /* */
		printf(" sec = %x ", sc->sc_hrdsc); /* */
		printf(" n = %x\n ", sc->sc_size); /* */
	});
}

#endif					  /* end if for NFD */
