/*-
 * Copyright (c) 1990 The Regents of the University of California.
 * All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * William Jolitz.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	from: @(#)wd.c	7.2 (Berkeley) 5/9/91
 *	$Id: wd.c,v 1.3 1994/07/08 12:02:35 paulus Exp $
 */

#include "wd.h"
#if	NWD + NFD > 0

#include <sys/param.h>
#include <sys/dkbad.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/disklabel.h>
#include <sys/buf.h>
#include <sys/uio.h>
#include <sys/syslog.h>
#include <sys/device.h>
#include <vm/vm.h>
#include <machine/cpu.h>
#include <da30/da30/iio.h>
#include <da30/da30/isr.h>

#define	RETRIES		5	/* number of retries before giving up */

#define wdctlr(dev)	((minor(dev) & 0xC0) >> 5)
#define wdisfd(dev)	((minor(dev) & 0x20))
#define wdunit(dev)	((minor(dev) & 0x18) >> 3)
#define wdpart(dev)	((minor(dev) & 0x7))

#define wd_cfd(dev)	(wdisfd(dev)? &fdcd: &wdcd)

#define b_cylin	b_resid		/* cylinder number for doing IO to */
				/* shares an entry in the buf struct */

/*
 * Drive states.  Used for open and format operations.
 * States < OPEN (> 0) are transient, during an open operation.
 * OPENRAW is used for unlabeled disks, and for floppies, to inhibit
 * bad-sector forwarding.
 */
#define RAWDISK		8		/* raw disk operation, no translation*/
#define ISRAWSTATE(s)	(RAWDISK&(s))	/* are we in a raw state? */
#define DISKSTATE(s)	(~RAWDISK&(s))	/* are we in a given state regardless
					   of raw or cooked mode? */

#define	CLOSED		0		/* disk is closed. */
					/* "cooked" disk states */
#define	WANTOPEN	1		/* open requested, not started */
#define	RECAL		2		/* doing restore */
#define	RDLABEL		3		/* reading pack label */
#define	RDBADTBL	4		/* reading bad-sector table */
#define	OPEN		5		/* done with open */

#define	WANTOPENRAW	(WANTOPEN|RAWDISK)	/* raw WANTOPEN */
#define	RECALRAW	(RECAL|RAWDISK)	/* raw open, doing restore */
#define	OPENRAW		(OPEN|RAWDISK)	/* open, but unlabeled disk or floppy */


/*
 * The structure of a disk drive.
 */
struct	disk {
	struct disklabel dk_dd;	/* device configuration data */
	int	dk_nsect;	/* # sectors left to do */
	caddr_t	dk_addr;	/* current memory address */
	daddr_t	dk_secnum;	/* current sector number */
	int	dk_fsec;	/* number of first sector on track */
	short	dk_sdh;		/* size/drive/head register value */
	char	dk_stepr;	/* stepping rate # */
	char	dk_precomp;	/* precomp cyl / 4 */
	short	dk_skip;	/* blocks already transferred */
	char	dk_unit;	/* physical unit number */
	char	dk_state;	/* control state */
	u_char	dk_status;	/* copy of status reg. */
	u_char	dk_error;	/* copy of error reg. */
	short	dk_open;	/* open/closed refcnt */
	u_long  dk_copenpart;   /* character units open on this drive */
	u_long  dk_bopenpart;   /* block units open on this drive */
	u_long  dk_openpart;    /* all units open on this drive */
	short	dk_wlabel;	/* label writable? */
	int	dk_flags;
	struct	disklabel *dk_dfltlabel;
};

/* Values for flags */
#define NO_LABEL	1	/* don't look for label on disk */
#define NO_BADSECT	2	/* don't look for bad sector table */
#define IS_FLOPPY	4	/* this is a floppy disk drive */

/*
 * This label is used as a default when initializing a new or raw disk.
 * It really only lets us access the first track until we know more.
 */
struct disklabel dflt_sizes = {
	DISKMAGIC, DTYPE_ST506, 0, "default", "",
		1024,		/* sector size */
		9,		/* # of sectors per track */
		5,		/* # of tracks per cylinder */
		1024,		/* # of cylinders per unit */
		9*5,		/* # of sectors per cylinder */
		1024*5*9,	/* # of sectors per unit */
		0, 0, 0,	/* spares/track, spares/cyl, alt cyl */
		3600,		/* rotational speed */
		2,		/* hardware sector interleave */
		0, 0, 0,	/* skew/track, skew/cyl, head switch time */
		0,		/* track-to-track seek, usec */
		D_ECC,		/* generic flags */
		0,0,0,0,0,
		0,0,0,0,0,
		DISKMAGIC,
		0,
		8,		/* # partitions */
		8192,		/* boot area size */
		MAXBSIZE,	/* max superblock size */
		{{11520, 0},	/* A=root filesystem */
		 {11495, 34560},/* B=swap */
		 {46080, 0},	/* C=whole disk */
		 {11520, 11520},
		 {11520, 23040},
		 {0,	0},
		 {0,	0},
		 {0,	0}},
};

/* default disk label for 5 1/4" floppy disks */
struct disklabel dflt_sizes_floppy_5 = {
	DISKMAGIC, DTYPE_FLOPPY, 0, "5.25\" floppy", "",
		512,		/* sector size */
		15,		/* # of sectors per track */
		2,		/* # of tracks per cylinder */
		80,		/* # of cylinders per unit */
		30,		/* # of sectors per cylinder */
		2400,		/* # of sectors per unit */
		0, 0, 0,	/* spares/track, spares/cyl, alt cyl */
		360,		/* rotational speed */
		2,		/* hardware sector interleave */
		0, 0, 0,	/* skew/track, skew/cyl, head switch time */
		3000,		/* track-to-track seek, usec */
		D_REMOVABLE,	/* generic flags */
		0,0,0,0,0,
		0,0,0,0,0,
		DISKMAGIC,
		0,
		8,		/* # partitions */
		8192,		/* boot area size */
		MAXBSIZE,	/* max superblock size */
		{{2400, 0},	/* A=whole disk */
		 {0,    0},
		 {2400, 0},	/* C=whole disk */
		 {0,    0},
		 {0,	0},
		 {0,	0},
		 {0,	0},
		 {0,	0}},
};

/* default disk label for 3.5" floppy disks */
struct disklabel dflt_sizes_floppy_3 = {
	DISKMAGIC, DTYPE_FLOPPY, 0, "3.5\" floppy", "",
		512,		/* sector size */
		18,		/* # of sectors per track */
		2,		/* # of tracks per cylinder */
		80,		/* # of cylinders per unit */
		36,		/* # of sectors per cylinder */
		2880,		/* # of sectors per unit */
		0, 0, 0,	/* spares/track, spares/cyl, alt cyl */
		300,		/* rotational speed */
		2,		/* hardware sector interleave */
		0, 0, 0,	/* skew/track, skew/cyl, head switch time */
		3000,		/* track-to-track seek, usec */
		D_REMOVABLE,	/* generic flags */
		0,0,0,0,0,
		0,0,0,0,0,
		DISKMAGIC,
		0,
		8,		/* # partitions */
		8192,		/* boot area size */
		MAXBSIZE,	/* max superblock size */
		{{2880, 0},	/* A=whole disk */
		 {0,    0},
		 {2880, 0},	/* C=whole disk */
		 {0,    0},
		 {0,	0},
		 {0,	0},
		 {0,	0},
		 {0,	0}},
};

/*
 * Layout of I/O registers
 */
typedef unsigned char	uchar;

struct wdc {
	uchar	data;
	uchar	error;
	uchar	scount;
	uchar	sector;
	uchar	cyl_lo;
	uchar	cyl_hi;
	uchar	sdh;
	uchar	csr;
	uchar	pad1[7];
	uchar	ie_csr;			/* write here => enable interrupts */
};

#define DATA_BUFFER	0x8000		/* offset from wdc adrs */

/* Status register bits */
#define	STAT_BUSY	0x80
#define STAT_READY	0x40
#define STAT_WFLT	0x20
#define STAT_DRQ	8
#define STAT_ECCCOR	4
#define STAT_CHANGED	2
#define STAT_ERROR	1

/* Commands */
#define RESTORE		0x10
#define READ_SECTOR	0x20
#define WRITE_SECTOR	0x30
#define FORMAT		0x50
#define SEEK		0x70

/* Error register bits */
#define ERR_ACMD	4
#define ERR_TR0		2

/* printf %b formats */
#define STAT_BITS	"\020\010busy\006ready\006wrflt\005skdone\004drq\003ecc\002dchg\001err"
#define ERR_BITS	"\020\010badblk\007crc\006id_crc\005idnf\003acmd\002tr000\001dam"

/*
 * State
 */
static	struct	dkbad	dkbad[NWD+NFD];
struct	disk	wddrives[NWD+NFD] = {0};	/* table of units */
struct	buf	wdutab[NWD+NFD] = {0};	/* head of queue per drive */
struct	buf	rwdbuf[NWD+NFD] = {0};	/* buffers for raw IO */
long	wdxfer[NWD+NFD] = {0};		/* count of transfers */
int	writeprotected[NWD+NFD] = { 0 };
volatile struct wdc *wdc_adr;

int wdintr();

/*
 * Autoconfiguration stuff
 */

void wfcattach __P((struct device *, struct device *, void *));
int  wfcmatch __P((struct device *, struct cfdata *, void *));

struct wfcsoftc {
    struct device	wfc_dev;
    struct isr		wfc_isr;
    volatile struct wdc *wfc_adr;
    int			wfc_active;
    int			wfc_errcnt;
    struct wfdsoftc	*wfc_actf;
    struct wfdsoftc	*wfc_actl;
};

struct cfdriver wfccd = {
    NULL, "wfc", wfcmatch, wfcattach, DV_DULL, sizeof(struct wfcsoftc), 0
};

int
wfcmatch(parent, cf, args)
    struct device *parent;
    struct cfdata *cf;
    void *args;
{
    volatile struct wdc *wdc;

    wdc = (volatile struct wdc *) IIO_CFLOC_ADDR(cf);
    if (badbaddr((caddr_t)wdc))
	return 0;

    wdc->cyl_lo = 0xa5;
    wdc->error = 0x5a;
    return wdc->cyl_lo == 0xa5 && wdc->error != 0x5a;
}

void
wfcattach(parent, self, args)
    struct device *parent, *self;
    void *args;
{
    struct wfcsoftc *p;

    iio_print(self->dv_cfdata);

    /* save the address */
    p = (struct wfcsoftc *) self;
    p->wfc_adr = (volatile struct wdc *) IIO_CFLOC_ADDR(self->dv_cfdata);

    /* connect the interrupt */
    p->wfc_isr.isr_intr = wdintr;
    p->wfc_isr.isr_arg = self->dv_unit;
    p->wfc_isr.isr_ipl = IIO_CFLOC_LEVEL(self->dv_cfdata);
    isrlink(&p->wfc_isr);

    /* configure the slaves */
    while (config_found(self, NULL, NULL))
	;
}

int  wfdmatch __P((struct device *, struct cfdata *, void *));
void wdattach __P((struct device *, struct device *, void *));
void fdattach __P((struct device *, struct device *, void *));

struct wfdsoftc {
    struct device	wfd_dev;
    struct disk		wfd_drive;
    struct wfdsoftc	*wfd_actf;
    struct buf		wfd_utab;
    struct dkbad	wfd_bad;
    struct evcnt	wfd_xfer;
};

struct cfdriver wdcd = {
    NULL, "wd", wfdmatch, wdattach, DV_DISK, sizeof(struct wfdsoftc), 0
};

struct cfdriver fdcd = {
    NULL, "fd", wfdmatch, fdattach, DV_DISK, sizeof(struct wfdsoftc), 0
};

int
wfdmatch(parent, cf, args)
    struct device *parent;
    struct cfdata *cf;
    void *args;
{
    return 1;
}

void
wdattach(parent, self, args)
    struct device *parent, *self;
    void *args;
{
    struct wfdsoftc *dv;

    printf("\n");

    dv = (struct wfdsoftc *) self;
    dv->wfd_drive.dk_fsec = 0;
    dv->wfd_drive.dk_sdh = self->dv_unit << 3;
    dv->wfd_drive.dk_dfltlabel = &dflt_sizes;

    evcnt_attach(self, "xfer", &dv->wfd_xfer);
}

void
fdattach(parent, self, args)
    struct device *parent, *self;
    void *args;
{
    struct wfdsoftc *dv;

    printf(" unit %d\n", self->dv_cfdata->cf_loc[0]);

    /* XXX Set the floppies to require no label or badsect tbl */
    dv = (struct wfdsoftc *) self;
    dv->wfd_drive.dk_fsec = 1;
    dv->wfd_drive.dk_sdh = 0x18 + (self->dv_unit << 1);
    dv->wfd_drive.dk_flags = NO_LABEL | NO_BADSECT | IS_FLOPPY;

    /* XXX fd1 is 3.5", fd0 is 5.25" */
    dv->wfd_drive.dk_dfltlabel = self->dv_unit? &dflt_sizes_floppy_3:
					&dflt_sizes_floppy_5;

    evcnt_attach(self, "xfer", &dv->wfd_xfer);
}


/*
 * Read/write routine for a buffer.  Finds the proper unit, range checks
 * arguments, and schedules the transfer.  Does not wait for the transfer
 * to complete.  All I/O requests must be a multiple of a sector in length.
 */
void
wdstrategy(bp)
	register struct buf *bp;	/* IO operation to perform */
{
	register struct wfdsoftc *dv;
	struct wfcsoftc *wfp;
	register struct buf *dp;
	register struct disk *du;	/* Disk unit to do the IO.	*/
	register struct partition *p;
	long	sz, ssize;
	struct cfdriver *cfd = wd_cfd(bp->b_dev);
	int	unit = wdunit(bp->b_dev);
	int	s;

	if (unit >= cfd->cd_ndevs
	    || (dv = (struct wfdsoftc *) cfd->cd_devs[unit]) == NULL
	    || (bp->b_blkno < 0)) {
		printf("wdstrat: dev = 0x%x, blkno = %d, bcount = %d\n",
			bp->b_dev, bp->b_blkno, bp->b_bcount);
		printf("wd:error in wdstrategy\n");
		bp->b_error = EINVAL;
		goto bad;
	}
	wfp = (struct wfcsoftc *) dv->wfd_dev.dv_parent;
	du = &dv->wfd_drive;
	if (DISKSTATE(du->dk_state) != OPEN)
		goto q;

	/*
	 * Determine the size of the transfer, and make sure
	 * we don't inadvertently overwrite the disk label.
	 */
	ssize = du->dk_dd.d_secsize;
	p = &du->dk_dd.d_partitions[wdpart(bp->b_dev)];
	sz = bp->b_bcount / ssize;
	if (bp->b_blkno + p->p_offset <= LABELSECTOR &&
#if LABELSECTOR != 0
	    bp->b_blkno + p->p_offset + sz > LABELSECTOR &&
#endif
	    (bp->b_flags & B_READ) == 0 && du->dk_wlabel == 0) {
		bp->b_error = EROFS;
		goto bad;
	}

	/* reject if block # outside partition
	   or count not a multiple of the sector size */
	if( bp->b_blkno < 0 || bp->b_blkno > p->p_size
	   || (bp->b_bcount & (ssize - 1)) != 0 ){
		bp->b_error = EINVAL;
		goto bad;
	}

	/* if exactly at end of disk, return an EOF */
	if( bp->b_blkno == p->p_size ){
		bp->b_resid = bp->b_bcount;
		biodone(bp);
		return;
	}

	/* if it would run past end of partition, truncate */
	if( bp->b_blkno + sz > p->p_size )
		bp->b_bcount = (p->p_size - bp->b_blkno) * ssize;

	bp->b_cylin = (bp->b_blkno + p->p_offset) / du->dk_dd.d_secpercyl;

q:
	dp = &dv->wfd_utab;
	s = splbio();
	disksort(dp, bp);
	if (dp->b_active == 0)
		wdustart(dv);		/* start drive if idle */
	if (wfp->wfc_active == 0)
		wdstart(wfp);		/* start IO if controller idle */
	splx(s);
	return;

bad:
	bp->b_flags |= B_ERROR;
	biodone(bp);
}

/*
 * Routine to queue a read or write command to the controller.  The request is
 * linked into the active list for the controller.  If the controller is idle,
 * the transfer is started.
 */
wdustart(dv)
register struct wfdsoftc *dv;
{
	register struct buf *bp, *dp;
	struct wfcsoftc *wfp;

	dp = &dv->wfd_utab;
	wfp = (struct wfcsoftc *) dv->wfd_dev.dv_parent;
	if (dp->b_active)
		return;
	bp = dp->b_actf;
	if (bp == NULL)
		return;	
	dv->wfd_actf = NULL;
	if (wfp->wfc_actf == NULL)  /* link unit into active list */
		wfp->wfc_actf = dv;
	else
		wfp->wfc_actl->wfd_actf = dv;
	wfp->wfc_actl = dv;
	dp->b_active = 1;		/* mark the drive as busy */
}

/*
 * Controller startup routine.  This does the calculation, and starts
 * a single-sector read or write operation.  Called to start a transfer,
 * or from the interrupt routine to continue a multi-sector transfer.
 */

wdstart(wfp)
struct wfcsoftc *wfp;
{
	register struct wfdsoftc *dv;
	register struct disk *du;	/* disk unit for IO */
	register volatile struct wdc *wdc = wfp->wfc_adr;
	register struct buf *bp;
	struct buf *dp;
	register struct bt_bad *bt_ptr;
	struct partition *p;
	long	blknum, pagcnt, cylin, head, sector;
	long	secsize, secpertrk, secpercyl, i;
	int	unit, s;

loop:
	dv = wfp->wfc_actf;
	if (dp == NULL)
		return;
	dp = &dv->wfd_utab;
	bp = dp->b_actf;
	if (bp == NULL) {
		wfp->wfc_actf = dv->wfd_actf;
		goto loop;
	}
	unit = wdunit(bp->b_dev);
	dv = (struct wfdsoftc *) wd_cfd(bp->b_dev)->cd_devs[unit];
	du = &dv->wfd_drive;
	if (DISKSTATE(du->dk_state) <= RDLABEL) {
		if (wdcontrol(bp)) {
			dp->b_actf = bp->b_actf;
			goto loop;	/* done */
		}
		return;
	}

	secsize = du->dk_dd.d_secsize;
	p = &du->dk_dd.d_partitions[wdpart(bp->b_dev)];
	if( du->dk_skip == 0 ){
		du->dk_nsect = bp->b_bcount / secsize;
		du->dk_addr = bp->b_un.b_addr;
		du->dk_secnum = bp->b_blkno;
		if( DISKSTATE(du->dk_state) == OPEN )
			du->dk_secnum += p->p_offset;
	}

	secpertrk = du->dk_dd.d_nsectors;
	secpercyl = du->dk_dd.d_secpercyl;
	blknum = du->dk_secnum;
	cylin = blknum / secpercyl;
	head = (blknum % secpercyl) / secpertrk;
	sector = blknum % secpertrk;

#ifdef	WDDEBUG
	if (du->dk_skip == 0) {
		printf("\nwdstart %s: %s %d@%d; map ", dv->wfd_dev.dv_xname,
			(bp->b_flags & B_READ) ? "read" : "write",
			bp->b_bcount, blknum);
	} else {
		printf(" %d)", du->dk_skip);
	}
#endif

	/* 
	 * See if the current block is in the bad block list.
	 * (If we have one, and not formatting.)
	 */
	if( DISKSTATE(du->dk_state) == OPEN )
	    for( bt_ptr = dv->wfd_bad.bt_bad; bt_ptr->bt_cyl != (u_short) -1;
		bt_ptr++ ){
		if (bt_ptr->bt_cyl > cylin)
			/* Sorted list, and we passed our cylinder. quit. */
			break;
		if( bt_ptr->bt_cyl != cylin
		   || bt_ptr->bt_trksec != (head << 8) + sector )
			continue;	/* not our block */

		/*
		 * Found bad block.  Calculate new block addr.
		 * This starts at the end of the disk (skip the
		 * last track which is used for the bad block list),
		 * and works backwards to the front of the disk.
		 */
#ifdef	WDDEBUG
		printf("--- badblock code -> Old = %d; ", blknum);
#endif
		blknum = du->dk_dd.d_secperunit - du->dk_dd.d_nsectors
				- (bt_ptr - dv->wfd_bad.bt_bad) - 1;
		cylin = blknum / secpercyl;
		head = (blknum % secpercyl) / secpertrk;
		sector = blknum % secpertrk;
#ifdef	WDDEBUG
		printf( "new = %d\n", blknum);
#endif
		break;
	}

	while( (wdc->csr & STAT_BUSY) != 0 )
		;

#if 0
	if (bp->b_flags & B_FORMAT) {
		wr(wdc+wd_sector, du->dk_dd.dk_gap3);
		wr(wdc+wd_seccnt, du->dk_dd.dk_nsectors);
	} /* else { */
#endif
	wdc->error = du->dk_precomp;	/* precomp cylinder/4 */
	wdc->scount = 1;
	wdc->sector = sector + du->dk_fsec;	/* sectors may begin with 1 */
	wdc->cyl_lo = cylin;
	wdc->cyl_hi = cylin >> 8;

	/* Set up the SDH register (select drive).     */
	wdc->sdh = du->dk_sdh | head;
#if 0
	if (bp->b_flags & B_FORMAT)
		wr(wdc+wd_command, WDCC_FORMAT);
	else
#endif

	if( (wdc->csr & STAT_READY) == 0 ){
		printf("%s: not ready\n", dv->wfd_dev.dv_xname);
		bp->b_flags |= B_ERROR;
		bp->b_error = EIO;
		wddone();
		goto loop;
	}

	wdc->ie_csr = (bp->b_flags & B_READ)? READ_SECTOR : WRITE_SECTOR;
	/*printf("sector %d cylin %d head %d addr %x\n",
	    sector, cylin, head, du->dk_addr);*/
	wfp->wfc_active = 1;		/* mark controller active */
		
	/* If this is a write operation, send the data */
	if( (bp->b_flags & B_READ) == 0 ){
		register volatile long *wp;
		register long *addr;
		register short nlong;

		/* Ready to send data?	*/
		while( (wdc->csr & STAT_DRQ) == 0 )
			;

		addr = (long *) du->dk_addr;
		wp = (volatile long *) ((char *) wdc + DATA_BUFFER);
		nlong = secsize / sizeof(long) - 1;
		do {
			*wp = *addr++;
		} while( --nlong != -1 );
	}
}

/*
 * these are globally defined so they can be found
 * by the debugger easily in the case of a system crash
 */
daddr_t wd_errsector;
daddr_t wd_errbn;
unsigned char wd_errstat;

/* Interrupt routine for the controller.  Acknowledge the interrupt, check for
 * errors on the current operation, mark it done if necessary, and start
 * the next request.  Also check for a partially done transfer, and
 * continue with the next chunk if so.
 */
int
wdintr(unit)
int unit;
{
	register struct wfcsoftc *wfp =
	    (struct wfcsoftc *) wfccd.cd_devs[unit];
	register volatile struct wdc *wdc = wfp->wfc_adr;
	register struct wfdsoftc *dv;
	register struct	disk *du;
	register struct buf *bp, *dp;
	int status, secsize, t;
	char partch;

	status = wdc->csr;

	if (!wfp->wfc_active) {
		return 0;
	}

	/* Shouldn't need to poll, but it may be a slow controller. */
	t = 3;
	while( (status & STAT_BUSY) != 0 ){
	    if (--t <= 0)
		return 0;	/* someone else's interrupt */
	    status = wdc->csr;
	}

#ifdef	WDDEBUG
	printf("I ");
#endif
	dv = wfp->wfc_actf;
	dp = &dv->wfd_utab;
	bp = dp->b_actf;
	du = &dv->wfd_drive;
	partch = wdpart(bp->b_dev) + 'a';
	secsize = du->dk_dd.d_secsize;
	if (DISKSTATE(du->dk_state) <= RDLABEL) {
		if (wdcontrol(bp))
			goto done;
		return 1;
	}

	if( (status & (STAT_ERROR | STAT_ECCCOR)) != 0 ){
		wd_errstat = wdc->error;	/* save error status */
#ifdef	WDDEBUG
		printf("status %x error %x\n", status, wd_errstat);
		printf("scount %x sec %x cyl %x %x sdh %x\n",
			wdc->scount, wdc->sector, wdc->cyl_hi,
			wdc->cyl_lo, wdc->sdh);
#endif
#if 0
		if (bp->b_flags & B_FORMAT) {
			du->dk_status = status;
			du->dk_error = wdp->wd_error;
			bp->b_flags |= B_ERROR;
			bp->b_error = EIO;
			goto done;
		}
#endif
		
		wd_errsector = du->dk_secnum;
		wd_errbn = bp->b_blkno + du->dk_skip;
		if( (status & STAT_ERROR) != 0 ){
			if (++wfp->wfc_errcnt < RETRIES) {
				wfp->wfc_active = 0;
			} else {
				printf("%s%c: ", dv->wfd_dev.dv_xname, partch);
				printf(
				    "hard %s error, sn %d bn %d status %b error %b\n",
				    (bp->b_flags & B_READ)? "read":"write",
				    wd_errsector, wd_errbn, status, STAT_BITS,
				    wd_errstat, ERR_BITS);
				bp->b_flags |= B_ERROR;	/* flag the error */
				bp->b_error = EIO;
			}
		} else
			log(LOG_WARNING,"%s%c: data corrected sn %d bn %d\n",
				dv->wfd_dev.dv_xname, partch, wd_errsector,
				wd_errbn);
	}

	/*
	 * If this was a read operation, fetch the data.
	 * (Fetch it even if an error occurred so we clear DRQ.)
	 */
	if( (bp->b_flags & B_READ) != 0 ){
		register volatile long *wp;
		register long *addr;
		register short nlong;

		/* Ready to receive data? */
		while( (wdc->csr & STAT_DRQ) == 0 )
			;

		addr = (long *) du->dk_addr;
		wp = (volatile long *) ((char *) wdc + DATA_BUFFER);
		nlong = secsize / sizeof(long) - 1;
		do {
			*addr++ = *wp;
		} while( --nlong != -1 );
	}

	dv->wfd_xfer.ev_count++;
	if ((bp->b_flags & B_ERROR) == 0) {
	    if( (status & STAT_ERROR) == 0 ){
		du->dk_skip++;		/* Add to successful sectors. */
		if (wfp->wfc_errcnt) {
		    log(LOG_WARNING, "%s%c: ", dv->wfd_dev.dv_xname, partch);
		    log(LOG_WARNING,
			"soft %s error, sn %d bn %d error %b retries %d\n",
			(bp->b_flags & B_READ) ? "read" : "write",
			wd_errsector, wd_errbn, wd_errstat,
			ERR_BITS, wfp->wfc_errcnt);
		    wfp->wfc_errcnt = 0;
		}
		du->dk_addr += secsize;
		++du->dk_secnum;
		if( --du->dk_nsect > 0 ){
		    /* inline optimized wdstart() so we don't miss the sector */
		    register int i;

		    i = wdc->sector + 1;
		    if( i < du->dk_dd.d_nsectors + du->dk_fsec )
			wdc->sector = i;
		    else {
			/* sector overflowed; increment track */
			wdc->sector = du->dk_fsec;
			i = (du->dk_secnum / du->dk_dd.d_nsectors)
				% du->dk_dd.d_ntracks;
			wdc->sdh = du->dk_sdh | i;
			if( i == 0 ){
			    /* track overflowed; increment cylinder */
			    if( ++wdc->cyl_lo == 0 )
				++wdc->cyl_hi;
			}
		    }
		}
	    }

	    /* see if more to transfer */
	    if( du->dk_nsect > 0 ){
		/* inline wdstart() */
		wdc->scount = 1;
		wdc->ie_csr = (bp->b_flags & B_READ)? READ_SECTOR : WRITE_SECTOR;
		wfp->wfc_active = 1;

		/* If this is a write operation, send the data */
		if( (bp->b_flags & B_READ) == 0 ){
		    register volatile long *wp;
		    register long *addr;
		    register short nlong;

		    /* Ready to send data?	*/
		    while( (wdc->csr & STAT_DRQ) == 0 )
			;

		    addr = (long *) du->dk_addr;
		    wp = (volatile long *) ((char *) wdc + DATA_BUFFER);
		    nlong = secsize / sizeof(long) - 1;
		    do {
			*wp = *addr++;
		    } while( --nlong != -1 );
		}

		return 1;		/* next chunk is started */
	    }
	}

done:
	/* done with this transfer, with or without error */
	wddone(wfp);

	if (wfp->wfc_actf)
		wdstart(wfp);		/* start IO on next drive */

	return 1;
}

/*
 * Done with the current transfer
 */
wddone(wfp)
struct wfcsoftc *wfp;
{
	register struct wfdsoftc *dv;
	register struct	disk *du;
	register struct buf *bp, *dp;

	dv = wfp->wfc_actf;
	dp = &dv->wfd_utab;
	bp = dp->b_actf;
	du = &dv->wfd_drive;

	du->dk_skip = 0;
	wfp->wfc_actf = dv->wfd_actf;
	wfp->wfc_errcnt = 0;
	wfp->wfc_active = 0;
	dp->b_actf = bp->b_actf;
	dp->b_errcnt = 0;
	dp->b_active = 0;
	bp->b_resid = 0;
	biodone(bp);
	if (dp->b_actf)
		wdustart(dv);		/* requeue disk if more io to do */
}

/*
 * Initialize a drive.
 */
wdopen(dev, flags, fmt)
	dev_t dev;
	int flags, fmt;
{
	register unsigned int unit;
	register struct buf *bp;
	register struct disk *du;
	struct wfdsoftc *dv;
	struct wfcsoftc *wfp;
	int part = wdpart(dev), mask = 1 << part;
	struct partition *pp;
	struct dkbad *db;
	int i, error = 0;

	unit = wdunit(dev);
	if (unit >= wd_cfd(dev)->cd_ndevs
	    || (dv = (struct wfdsoftc *) wd_cfd(dev)->cd_devs[unit]) == NULL)
		return (ENXIO) ;

	wfp = (struct wfcsoftc *) dv->wfd_dev.dv_parent;
	du = &dv->wfd_drive;

#ifdef	WDDEBUG
	printf("wdopen %s%c, dk_open=%d\n", dv->wfd_dev.dv_xname,
	       part+'a', du->dk_open);
#endif

	if (du->dk_open > 0){
		du->dk_open++ ;
		goto partcheck;	/* already is open, don't mess with it */
	}

	du->dk_unit = unit;
	dv->wfd_utab.b_actf = NULL;
#if 0
	if (flags & O_NDELAY)
		du->dk_state = WANTOPENRAW;
	/* else */
#endif
	du->dk_state = WANTOPEN;

	/*
	 * Use the default sizes until we've read the label,
	 * or longer if there isn't one there.
	 */
	du->dk_dd = *du->dk_dfltlabel;
	wdsetsdh(dv);

	/*
	 * Recal, read of disk label will be done in wdcontrol
	 * during first read operation.
	 */
	bp = geteblk(du->dk_dd.d_secsize);
	bp->b_dev = dev - part;		/* was dev & 0xff00 */
	bp->b_bcount = 0;
	bp->b_blkno = LABELSECTOR;
	bp->b_flags = B_READ;
	wdstrategy(bp);
	biowait(bp);
	if (bp->b_flags & B_ERROR) {
		error = ENXIO;
		du->dk_state = CLOSED;
		goto done;
	}
	if (du->dk_state == OPENRAW) {
		du->dk_state = OPENRAW;
		goto done;
	}

	/*
	 * Read bad sector table into memory.
	 */
	if( (du->dk_flags & NO_BADSECT) == 0 ){
	    i = 0;
	    do {
		bp->b_flags = B_BUSY | B_READ;
		bp->b_blkno = du->dk_dd.d_secperunit - du->dk_dd.d_nsectors + i;
		bp->b_bcount = du->dk_dd.d_secsize;
		wdstrategy(bp);
		biowait(bp);
	    } while ((bp->b_flags & B_ERROR) && (i += 2) < 10 &&
		     i < du->dk_dd.d_nsectors);
	    db = (struct dkbad *)(bp->b_un.b_addr);
#define DKBAD_MAGIC 0x4321
	    if ((bp->b_flags & B_ERROR) == 0 && db->bt_mbz == 0 &&
		db->bt_flag == DKBAD_MAGIC) {
		dv->wfd_bad = *db;
	    } else {
		printf("%s: %s bad-sector file\n", dv->wfd_dev.dv_xname,
		       (bp->b_flags & B_ERROR) ? "can't read" : "no");
		dv->wfd_bad.bt_bad[0].bt_cyl = -1;	/* empty list */
	    }
	} else
	    dv->wfd_bad.bt_bad[0].bt_cyl = -1;

	du->dk_state = OPEN;

done:
	bp->b_flags = B_INVAL | B_AGE;
	brelse(bp);
	if (error == 0)
		++du->dk_open;
	if (part >= du->dk_dd.d_npartitions)
		return (ENXIO);

	/*
	 * Warn if a partion is opened
	 * that overlaps another partition which is open
	 * unless one is the "raw" partition (whole disk).
	 */
    partcheck:

#define RAWPART	 2	       /* 'c' partition */     /* XXX */
	if ((du->dk_openpart & mask) == 0 && part != RAWPART) {
		int	start, end, pt;

		pp = &du->dk_dd.d_partitions[part];
		start = pp->p_offset;
		end = pp->p_offset + pp->p_size;
		pp = du->dk_dd.d_partitions;
		for( pt = 0; pt < du->dk_dd.d_npartitions; ++pt, ++pp ){
			if (pp->p_offset + pp->p_size <= start ||
			    pp->p_offset >= end)
				continue;
			if (pt == RAWPART)
				continue;
			if (du->dk_openpart & (1 << pt))
				log(LOG_WARNING,
				    "%s%c: overlaps open partition (%c)\n",
				    dv->wfd_dev.dv_xname, part + 'a', pt + 'a');
		}
	}

	du->dk_openpart |= mask;
	switch (fmt) {
	case S_IFCHR:
		du->dk_copenpart |= mask;
		break;
	case S_IFBLK:
		du->dk_bopenpart |= mask;
		break;
	}
	return (error);
}

/*
 * Implement operations other than read/write.
 * Called from wdstart or wdintr during opens and formats.
 * Uses finite-state-machine to track progress of operation in progress.
 * Returns 0 if operation still in progress, 1 if completed.
 */
wdcontrol(bp)
	register struct buf *bp;
{
	struct wfdsoftc *dv;
	struct wfcsoftc *wfp;
	register volatile struct wdc *wdc;
	register struct disk *du;
	register volatile long *wp;
	register long *addr;
	register short nlong;
	unsigned char  stat;
	int s, cnt;
	extern int bootdev, cyloffset;
	struct disklabel *lp;

	dv = (struct wfdsoftc *) wd_cfd(bp->b_dev)->cd_devs[wdunit(bp->b_dev)];
	wfp = (struct wfcsoftc *) dv->wfd_dev.dv_parent;
	wdc = wfp->wfc_adr;
	du = &dv->wfd_drive;

	switch (DISKSTATE(du->dk_state)) {

	tryagainrecal:
	case WANTOPEN:			/* set SDH, step rate, do restore */
#ifdef	WDDEBUG
		printf("%s: recal ", dv->wfd_dev.dv_xname);
#endif
		s = splbio();		/* not called from intr level ... */
		wdc->sdh = du->dk_sdh;
		wfp->wfc_active = 1;
		wdc->ie_csr = RESTORE + du->dk_stepr;
		du->dk_state = (du->dk_state & RAWDISK) | RECAL;
		splx(s);
		return(0);

	case RECAL:
		if( ((stat = wdc->csr) & STAT_ERROR) != 0 ){
			printf("%s: recal: status %b error %b\n", dv->wfd_dev.dv_xname,
				stat, STAT_BITS, wdc->error, ERR_BITS);
			if (++wfp->wfc_errcnt < RETRIES)
				goto tryagainrecal;
			goto badopen;
		}

		wfp->wfc_errcnt = 0;
		if (ISRAWSTATE(du->dk_state)) {
			du->dk_state = OPENRAW;
			return(1);
		}
		if( (du->dk_flags & NO_LABEL) != 0 ){
		    du->dk_state = OPEN;
		    return 1;
		}

retry:
#ifdef	WDDEBUG
		printf("rdlabel ");
#endif
		/*
		 * Read in sector LABELSECTOR to get the pack label
		 * and geometry.
		 */
		wdc->scount = 1;
		wdc->sector = LABELSECTOR + du->dk_fsec;
		wdc->cyl_lo = 0;
		wdc->cyl_hi = 0;
		wdc->sdh = du->dk_sdh;
		wdc->ie_csr = READ_SECTOR;
		du->dk_state = RDLABEL;
		return(0);

	case RDLABEL:
		if ((stat = wdc->csr) & STAT_ERROR) {
			printf("%s: read label: status %b error %b\n",
			       dv->wfd_dev.dv_xname, stat, STAT_BITS,
			       wdc->error, ERR_BITS);
			if (++wfp->wfc_errcnt < RETRIES)
				goto retry;
			goto badopen;
		}

		/* Ready to receive data? */
		while( (wdc->csr & STAT_DRQ) == 0 )
			;

		addr = (long *) bp->b_un.b_addr;
		wp = (volatile long *) ((char *) wdc + DATA_BUFFER);
		nlong = du->dk_dd.d_secsize / sizeof(long) - 1;
		do {
			*addr++ = *wp;
		} while( --nlong != -1 );

		lp = (struct disklabel *) (bp->b_un.b_addr + LABELOFFSET);
		if (lp->d_magic == DISKMAGIC) {
			du->dk_dd = *lp;
			wdsetsdh(dv);
			/* XXX should reset ctrler's idea of stepping rate */
		} else {
			printf("%s: bad disk label (%x)\n", dv->wfd_dev.dv_xname,
				lp->d_magic);
			du->dk_state = OPENRAW;
		}

		if (du->dk_state == RDLABEL)
			du->dk_state = RDBADTBL;
		/*
		 * The rest of the initialization can be done
		 * by normal means.
		 */
		return(1);

	default:
		panic("wdcontrol");
	}
	/* NOTREACHED */

badopen:
	du->dk_state = OPENRAW;
	return(1);
}

/* Work out the appropriate value for the SDH register */
wdsetsdh(dv)
struct wfdsoftc *dv;
{
	register struct disk *du;
	int sdh, stepr;

	du = &dv->wfd_drive;
	du->dk_precomp = 0xFF;		/* default = no precomp */
	sdh = du->dk_sdh & 0x1E;
	if( (du->dk_flags & IS_FLOPPY) == 0 ){
		/* hard disk */
		if( (du->dk_dd.d_flags & D_ECC) != 0 )
			sdh |= 0x80;
		stepr = du->dk_dd.d_trkseek / 500;
		if( du->dk_dd.d_precompcyl != 0 )
			du->dk_precomp = du->dk_dd.d_precompcyl >> 2;
	} else {
		/* floppy disk */
		stepr = du->dk_dd.d_trkseek / 1000;
		if( stepr > 7 )
			stepr -= (stepr - 6) >> 1;
	}
	if( stepr > 15 )
		stepr = 15;

	switch( du->dk_dd.d_secsize ){
	case 128:	sdh |= 0x60;	break;
	case 256:			break;
	case 512:	sdh |= 0x20;	break;
	case 1024:	sdh |= 0x40;	break;
	default:
		printf("%s: bad sector size (%d) in label\n",
			dv->wfd_dev.dv_xname, du->dk_dd.d_secsize);
		sdh |= 0x20;		/* default to 512 bytes/sector */
	}

	du->dk_sdh = sdh;
	du->dk_stepr = stepr;
}


/* ARGSUSED */
wdclose(dev, flags, fmt)
	dev_t dev;
	int flags, fmt;
{
	register struct disk *du;
	struct wfdsoftc *dv;

	dv = (struct wfdsoftc *) wd_cfd(dev)->cd_devs[wdunit(dev)];
	du = &dv->wfd_drive;
	du->dk_open--;
#ifdef	WDDEBUG
	printf("wdclose %s%c, dk_open=%d\n", dv->wfd_dev.dv_xname,
	       wdpart(dev)+'a', du->dk_open);
#endif
	/*if (du->dk_open == 0) du->dk_state = CLOSED ; does not work */
	return(0);
}

int
wdread(dev, uio, flags)
	dev_t dev;
	struct uio *uio;
	int flags;
{
	struct wfdsoftc *dv;

	dv = (struct wfdsoftc *) wd_cfd(dev)->cd_devs[wdunit(dev)];
	return raw_disk_io(dev, uio, dv->wfd_drive.dk_dd.d_secsize);
}

int
wdwrite(dev, uio, flags)
	dev_t dev;
	struct uio *uio;
	int flags;
{
	struct wfdsoftc *dv;

	dv = (struct wfdsoftc *) wd_cfd(dev)->cd_devs[wdunit(dev)];
	return raw_disk_io(dev, uio, dv->wfd_drive.dk_dd.d_secsize);
}

wdioctl(dev, cmd, addr, flag, p)
	dev_t dev;
	caddr_t addr;
	int cmd, flag;
	struct proc *p;
{
	int unit = wdunit(dev);
	register struct disk *du;
	struct wfdsoftc *dv;
	int error = 0, wlab;

	dv = (struct wfdsoftc *) wd_cfd(dev)->cd_devs[wdunit(dev)];
	du = &dv->wfd_drive;

	switch (cmd) {

	case DIOCGDINFO:
		*(struct disklabel *)addr = du->dk_dd;
		break;

	case DIOCGPART:
		((struct partinfo *)addr)->disklab = &du->dk_dd;
		((struct partinfo *)addr)->part =
				&du->dk_dd.d_partitions[wdpart(dev)];
		break;

	case DIOCSDINFO:
		if ((flag & FWRITE) == 0) {
			error = EBADF;
			break;
		}
		error = setdisklabel(&du->dk_dd, (struct disklabel *)addr,
				     0, 0);
		break;

	case DIOCWLABEL:
		if ((flag & FWRITE) == 0)
			error = EBADF;
		else
			du->dk_wlabel = *(int *)addr;
		break;

	case DIOCWDINFO:
		if ((flag & FWRITE) == 0) {
			error = EBADF;
			break;
		}
		error = setdisklabel(&du->dk_dd, (struct disklabel *)addr,
				     0, 0);
		if( error != 0 )
			break;

		/* simulate opening partition 0 so write succeeds */
		/* dk->dk_openpart |= (1 << 0);	    /* XXX */
		wlab = du->dk_wlabel;
		du->dk_wlabel = 1;
		error = writedisklabel(dev, wdstrategy, &du->dk_dd, NULL);
		du->dk_wlabel = wlab;
		break;

#ifdef notyet
	case DIOCGDINFOP:
		*(struct disklabel **)addr = &(du->dk_dd);
		break;

	case DIOCWFORMAT:
		if ((flag & FWRITE) == 0)
			error = EBADF;
		else {
			register struct format_op *fop;
			struct uio auio;
			struct iovec aiov;
			extern int wdformat();

			fop = (struct format_op *)addr;
			aiov.iov_base = fop->df_buf;
			aiov.iov_len = fop->df_count;
			auio.uio_iov = &aiov;
			auio.uio_iovcnt = 1;
			auio.uio_resid = fop->df_count;
			auio.uio_segflg = 0;
			auio.uio_offset =
				fop->df_startblk * du->dk_dd.d_secsize;
			error = physio(wdformat, &rwdbuf[unit], dev, B_WRITE,
				minphys, &auio);
			fop->df_count -= auio.uio_resid;
			fop->df_reg[0] = du->dk_status;
			fop->df_reg[1] = du->dk_error;
		}
		break;
#endif

	default:
		error = ENOTTY;
		break;
	}
	return (error);
}

#if 0
wdformat(bp)
	struct buf *bp;
{

	bp->b_flags |= B_FORMAT;
	return (wdstrategy(bp));
}
#endif

wdsize(dev)
	dev_t dev;
{
	register unit = wdunit(dev);
	register part = wdpart(dev);
	struct wfdsoftc *dv;
	register struct disk *du;
	register int val;

	if (unit >= wd_cfd(dev)->cd_ndevs
	    || (dv = (struct wfdsoftc *) wd_cfd(dev)->cd_devs[unit]) == NULL)
		return(-1);
	du = &dv->wfd_drive;
	if (du->dk_state == 0) {
		val = wdopen (dev, 0, 0);
		if (val < 0)
			return (-1);
	}
	return du->dk_dd.d_partitions[part].p_size;
}

extern	char *vmmap;	    /* poor name! */

wddump(dev)				/* dump core after a system crash */
	dev_t dev;
{
	register volatile struct wdc *wdc;
	register struct disk *du;	/* disk unit to do the IO */
	register struct bt_bad *bt_ptr;
	register volatile long *wp;
	register long *addr;
	register short nlong;
	long	num;			/* number of sectors to write */
	int	unit, part;
	long	cyloff, blknum, blkcnt;
	long	cylin, head, sector, stat;
	long	secpertrk, secpercyl, nblocks, i;
	static  wddoingadump = 0 ;
	extern CMAP1;
	extern char CADDR1[];

	return ENXIO;		/* don't want dumps for now */
#if 0
	addr = (long *) 0;		/* starting address */
	num = Maxmem;			/* size of memory to dump */

	unit = wdunit(dev);		/* eventually support floppies? */
	part = wdpart(dev);		/* file system */
	/* check for acceptable drive number */
	if (BAD_UNIT(unit))
		return(ENXIO);
	unit = INDEX(unit);

	du = &dv->wfd_drive;
	/* was it ever initialized ? */
	if (du->dk_state < OPEN)
		return (ENXIO) ;

	/* Convert to disk sectors */
	num = (u_long) num * NBPG / du->dk_dd.d_secsize;

	/* check if controller active */
	if (wddoingadump)
		return(EFAULT);

	secpertrk = du->dk_dd.d_nsectors;
	secpercyl = du->dk_dd.d_secpercyl;
	nblocks = du->dk_dd.d_partitions[part].p_size;
	cyloff = du->dk_dd.d_partitions[part].p_offset / secpercyl;

/*pg("xunit %x, nblocks %d, dumplo %d num %d\n", part,nblocks,dumplo,num);*/
	/* check transfer bounds against partition size */
	if ((dumplo < 0) || ((dumplo + num) > nblocks))
		return(EINVAL);

	wddoingadump = 1;		/* mark controller active for if we
					   panic during the dump */

	i = 100000;
	while ((wdc->csr & STAT_BUSY) && (i-- > 0))
		;
	if( i == 0 )
		return EIO;
	wdc->sdh = du->dk_sdh;
	wdc->csr = RESTORE + du->dk_stepr;
	while( (wdc->csr & STAT_BUSY) != 0 )
		;

	blknum = dumplo;
	while (num > 0) {
#ifdef notdef
		if (blkcnt > MAXTRANSFER) blkcnt = MAXTRANSFER;
		if ((blknum + blkcnt - 1) / secpercyl != blknum / secpercyl)
			blkcnt = secpercyl - (blknum % secpercyl);
			    /* keep transfer within current cylinder */
#endif
		pmap_enter(pmap_kernel(), vmmap, (char *) addr,
			   VM_PROT_READ, TRUE);

		/* compute disk address */
		cylin = blknum / secpercyl;
		head = (blknum % secpercyl) / secpertrk;
		sector = blknum % secpertrk + du->dk_fsec;
		cylin += cyloff;

#ifdef notyet
		/* 
		 * See if the current block is in the bad block list.
		 * (If we have one.)
		 */
    		for (bt_ptr = dv->wfd_bad.bt_bad;
				bt_ptr->bt_cyl != -1; bt_ptr++) {
			if (bt_ptr->bt_cyl > cylin)
				/* Sorted list, and we passed our cylinder.
					quit. */
				break;
			if (bt_ptr->bt_cyl == cylin &&
				bt_ptr->bt_trksec == (head << 8) + sector) {
			/*
			 * Found bad block.  Calculate new block addr.
			 * This starts at the end of the disk (skip the
			 * last track which is used for the bad block list),
			 * and works backwards to the front of the disk.
			 */
				blknum = (du->dk_dd.d_secperunit)
					- du->dk_dd.d_nsectors
					- (bt_ptr - dv->wfd_bad.bt_bad) - 1;
				cylin = blknum / secpercyl;
				head = (blknum % secpercyl) / secpertrk;
				sector = blknum % secpertrk;
				break;
			}

#endif

		/* select drive.     */
		wdc->sdh = du->dk_sdh + (head & 7);

		/* spin until ready.  XXX should we abort instead? */
		while( (wdc->csr & STAT_READY) == 0 )
			;

		/* transfer some blocks */
		wdc->scount = 1;
		wdc->sector = sector;
		wdc->cyl_lo = cylin;
		wdc->cyl_hi = cylin >> 8;
/*#ifdef notdef*/
		/* lets just talk about this first...*/
		pg ("sdh 0%o sector %d cyl %d addr 0x%x",
			sector, cylin, addr);
/*#endif*/
		wdc->csr = WRITE_SECTOR;
		
		/* Ready to send data?	*/
		while( (wdc->csr & STAT_DRQ) == 0 )
			;
		if( (wdc->csr & STAT_ERROR) != 0 )
			return EIO;


		/* Ready to send data?	*/
		while( (wdc->csr & STAT_DRQ) == 0 )
			;

		/* ASSUMES CONTIGUOUS MEMORY */
		wp = (volatile long *) ((char *) wdc + DATA_BUFFER);
		nlong = du->dk_dd.d_secsize / sizeof(long) - 1;
		do {
			*wp = *addr++;
		} while( --nlong != -1 );

		/* wait for completion */
		for ( i = 1000000 ; wdc->csr & STAT_BUSY ; i--) {
			if (i < 0) return (EIO) ;
		}

		/* error check the xfer */
		if( (wdc->csr & STAT_ERROR) != 0 )
			return EIO;

		/* update block count */
		num--;
		blknum++ ;
		if (num % 100 == 0) printf (".") ;
	}
	return(0);
#endif /*0*/
}
#endif	/* NWD+NFD > 0 */
