/*
 * Charles H. Dickman 2001, 2002
 *
 * All rights reserved.
 *
 *  This code is available for all to use, so long as the
 *  rights as enunciated by the University of California, Berkeley
 *  below are also attributed to me.
 *  In short, do with this as you will, but give credit where
 *  credit is due.
 */
 

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

/* TODO:peel out buffer at low ipl,
   speed improvement, rewrite to clean code from garbage artifacts */


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

#include "param.h"
#include "../machine/seg.h"
#include "errno.h"
#include "systm.h"
#include "conf.h"
#include "file.h"
#include "stat.h"
#include "ioctl.h"
#include "disk.h"
#include "disklabel.h"
#include "buf.h"
#include "wdreg.h"
#include "syslog.h"


#ifdef DEV_BSIZE
#undef DEV_BSIZE
#undef DEV_BSHIFT
#endif
#define DEV_BSIZE 512
#define DEV_BSHIFT 9
#define MAX_XFR	8192	/* maximum bytes to transfer */

#define wdctlr(dev)     ((minor(dev) & 0300) >> 6)
#define wdunit(dev)     ((minor(dev) & 070) >> 3)
#define wdpart(dev)     ((minor(dev) & 007))

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

/*
 * The structure of a disk drive.
 */
struct  wd_disk {
	daddr_t wd_nblks;
        short   wd_bc;          /* byte count left */
        short   wd_skip;        /* blocks already transferred */
        char    wd_unit;        /* physical unit number */
        u_char  wd_stat;        /* copy of status reg. */
        u_char  wd_err;		/* copy of error reg. */
	u_char  wd_reg[8];	/* copy of disk registers */
	long	wd_intfail;	/* count of the number of interrupt enables */
        short   wd_open;        /* open/closed refcnt */
	struct  dkdevice wd_dk;	/* kernel resident part of label */
};

/*
 * Some shorthand for accessing the in-kernel label structure.
 */
#define wd_bopen        wd_dk.dk_bopenmask
#define wd_copen        wd_dk.dk_copenmask
#define wd_open         wd_dk.dk_openmask
#define wd_flags        wd_dk.dk_flags
#define wd_label        wd_dk.dk_label
#define wd_parts        wd_dk.dk_parts

struct  wd_disk	wddrives[NWD] = {0};    /* table of units */
struct  buf     wdtab = {0};
struct  buf     wdutab[NWD] = {0};	/* head of queue per drive */
long    	wdxfer[NWD] = {0};	/* count of transfers */
int     wdprobe(), wdattach(), wdintr();

static int wdc;		/* base IOpage address of adapter */
static int wdv;		/* interrupt vector */
int cyloffset;

#define WDASM
#define NO_WDMACRO
#define NO_WDINLINE
#define WDIDDUMP

#ifdef WDMACRO

#define inb(p) 		(*(char *)p)
#define inw(p) 		(*(short *)p)

#else

char
inb(p)
        char *p;
{
        return *p;
}

short
inw(p)
	int *p;
{
	return *p;
}

#endif

outb(p,val)
        char *p;
        char val;
{
/* printf("wd: outb: %o -> %o\n", val, (int)p); */
        *p = val;
}

outw(p,val)
        short *p;
        short val;
{
/* printf("wd: outw: %o -> %o\n", val, (int)p); */
        *p = val;
}

insw(src, dst, cnt)
        register short *src;
        register short *dst;
        register int cnt;
{
#ifndef WDASM
	extern int wdc;
        while (cnt--) {
		if ((inb(wdc+wd_altsts) & WDCS_DRQ) == 0)
			printf("wd: insw: no DRQ count = %d\n", 255 - cnt);
                *dst++ = *src;
	}
#else
	if (cnt == 0)
		return;		/* nothing to move */
	asm("mov	4(r5),r1");
	asm("mov	6(r5),r2");
	asm("mov	10(r5),r3");
	asm("1:");
	asm("mov	(r1),(r2)+");
	asm("sob	r3,1b");
#endif
}

outsw(dst, src, cnt)
        register short *src;
        register short *dst;
        register int cnt;
{
#ifndef WDASM
        while (cnt--)
                *dst = *src++;
#else
	if (cnt == 0)
		return;		/* nothing to move */
	asm("mov	6(r5),r1");
	asm("mov	4(r5),r2");
	asm("mov	10(r5),r3");
	asm("1:");
	asm("mov	(r1)+,(r2)");
	asm("sob	r3,1b");
#endif
}

#ifdef WDINLINE
#define wdwbusy()	while ((inb(wdc+wd_altsts) & WDCS_BUSY) != 0) 
#define wdwready()	while ((inb(wdc+wd_altsts) & WDCS_READY) == 0)
#define wdwdrq()	while ((inb(wdc+wd_altsts) & WDCS_DRQ) == 0)
#else

int
wdwbusy()
{
	extern int wdc;
	u_int d = 30000;

	while ((inb(wdc+wd_altsts) & WDCS_BUSY) != 0)
		if (d-- == 0)
			return 0;
	return 1;
}

int
wdwready()
{
	extern int wdc;
	u_int d = 30000;

	while ((inb(wdc+wd_altsts) & (WDCS_READY | WDCS_BUSY)) != WDCS_READY)
		if (d-- == 0)
			return 0;
	return 1;
}

int
wdwdrq()
{
	extern int wdc;
	char st;
	u_int d = 30000;

	while (1) {
		st = inb(wdc+wd_altsts);
		if ((st & (WDCS_BUSY | WDCS_READY | WDCS_DRQ)) 
			  == (WDCS_READY | WDCS_DRQ))
			break;
		if ((st & (WDCS_BUSY | WDCS_ERR)) == WDCS_ERR)
			break;
		if (d-- == 0)
			return 0;
	}
	return (st);
}


#endif
#define min(v1,v2)      ((v1)<(v2) ? (v1) : (v2))

wdrdump()
{
	int i;

	printf("wd dev register dump\n");
	printf("wd_csr = %b\n", fioword(IOBASE + wd_csr), WDA_BITS);
	printf("wd_altsts = %b\n", fioword(IOBASE + wd_altsts), WDCS_BITS);
	printf("wd_error = %b\n", fioword(IOBASE + wd_error), WDERR_BITS);

	for (i = IOBASE; i < IOBASE + 040; i += 2) 
		printf("%o = %o\n", i, fioword(i));
}

wdbufdump(bp)
	struct buf *bp;
{
	printf("wdbufdump: buf structure contents dump\n");
	printf("bp = %o\n", bp);
	printf("b_flags = %b\n", bp->b_flags, "\010\020RAMREMAP\017UBAREMAP\
		\016LOCKED\015BAD\014INVAL\013TAPE\012DELWRI\011ASYNC\010AGE\
		\007WANTED\006MAP\005PHYS\004BUSY\003ERROR\002DONE\001READ");
	printf("b_forw = %o, b_back = %o, av_forw = %o, av_back = %o\n",
		bp->b_forw, bp->b_back, bp->av_forw, bp->av_back);
	printf("b_bcount = %u, b_error = %o\n", bp->b_bcount, bp->b_error); 
	printf("b_dev = %o, wdctlr = %d, wdunit = %d, wdpart = %d\n",
		bp->b_dev, wdctlr(bp->b_dev),
		wdunit(bp->b_dev), wdpart(bp->b_dev));
	printf("b_xmem = %o, b_addr = %o, physaddr = %o\n",
		bp->b_xmem, bp->b_un.b_addr,
		(long)bp->b_xmem<<16 | (long)bp->b_un.b_addr);
	printf("bftopaddr(bp) = %o\n", bftopaddr(bp));
	printf("b_blkno = %ld\n", (long)bp->b_blkno);
}


/*
 * Called from autoconfig and raroot() to set the vector for a controller.
 * It is an error to attempt to set the vector more than once except for
 * the first controller which may have had the vector set from raroot().
 * In this case the error is ignored and the vector left unchanged.
 */

wdVec(ctlr, vector)
	int	ctlr;
	int	vector;
{
	extern int wdc;

	if (ctlr != 0)
		return(-1);
	if (wdv != 0) {
		return(0);
	}
	wdv = vector;
	outw(wdc+wd_vector, vector);
	outw(wdc+wd_csr, WDA_IE);
	return(0);
}

wdroot(csr)
        int csr;
{
	if (!csr)		/* XXX */
		csr = 0171000;	/* XXX */
	wdattach(csr, 0);
	wdVec(0, 0510);
}

/*
 * attach each drive if possible.
 */
wdattach(addr, unit)
        char *addr;
        int unit;
{
	wdc = (int)addr;
        outb(wdc+wd_ctlr,12); /* reset */
        DELAY(1000);
        outb(wdc+wd_ctlr,8);
	return(1);
}

#if 0
wdprobe(addr,vector)
	u_int *addr;
	int vector;
{

	return (1); /* XXX assume all is well */
}
#endif

/*
 * Strings in the sector returned by the identify command are
 * in words stored in bigendian order. Returns a string with
 * the order corrected and trailing spaces removed.
 */
char *
wdidstr(str, idsec, n)
	char *str;
	int *idsec;
	int n;
{
	int i;

	for (i = 0; i < n; i += 2) {
		str[i] = idsec[i >> 1] >> 8;
		str[i + 1] = idsec[i >> 1];
	}
	str[i] = '\0';
	for (i -= 1; i >= 0; i--)
		if (str[i] == ' ')
			str[i] = '\0';
	return (str);
}

#ifdef WDIDDUMP

wdiddump(du, idsec)
	register struct wd_disk *du;
	int *idsec;
{
	char str[64];

	log(LOG_NOTICE, "wd%d: ", du->wd_unit);
	wdidstr(str, &idsec[27], 40);
	log(LOG_NOTICE, "%s ", (str[0]) ? str : "undefined");
	if (idsec[49] & 0400)
		log(LOG_NOTICE, "size=%ld\n",
			((long)idsec[61] << 16) + idsec[60]); 
	else
		log(LOG_NOTICE, "LBA mode NOT supported!\n");
}

#else

wdiddump(du, idsec)
	register struct wd_disk *du;
	int *idsec;
{
	char str[64];

	printf("wd%d id results:\n", du->wd_unit);

	wdidstr(str, &idsec[27], 40);
	printf("Drive Model: %s\n", (str[0]) ? str : "undefined");

	wdidstr(str, &idsec[23], 8);
	printf("Firmware Rev: %s\n", (str[0]) ? str : "undefined");

	wdidstr(str, &idsec[10], 20);
	printf("Serial Number: %s\n", (str[0]) ? str : "undefined");

	if (idsec[49] & 0400) {		
		printf("total LBA addressable sectors: %ld\n",
			((long)idsec[61] << 16) + idsec[60]);
	}
	else
		printf("ACHTUNG! LBA mode NOT supported.\n");

	printf("CHS configuration: %d cyl, %d heads, %d sectors\n",
		idsec[1], idsec[3], idsec[6]);
	printf("total CHS addressable sectors: %ld\n", 
		(long)idsec[1]*idsec[3]*idsec[6]);
}

#endif

wdidentify(du, idsec)
	register struct wd_disk *du;
	int *idsec;
{
	int st, ie;

	/* have drive execute identity command */
	wdwbusy();
	outb(wdc+wd_sdh, du->wd_unit << 4);
	wdwready();

	/* disable interrupts from drive */
	ie = inw(wdc+wd_csr);
	outw(wdc+wd_csr, ie & ~WDA_IE);

	outb(wdc+wd_command, WDCC_IDENTIFY);
	wdwbusy();
	if ((st = inb(wdc+wd_altsts)) & WDCS_ERR) {
		log(LOG_NOTICE, 
			"wd%d err: identify command: st = %o, er = %o\n",
			du->wd_unit, st, inb(wdc+wd_error));
	} else {
		wdwdrq();
		insw(wdc+wd_data, idsec, 256);
	}
	
	/* eat interrupt and restore interrupt enable state */
	st = inb(wdc+wd_status);
	outw(wdc+wd_csr, ie);	

	return (0);	
}

void
wddfltlbl(du, lp)
	register struct	wd_disk *du;
	register struct	disklabel *lp;
{
	int idsec[256];
	struct partition *pi = &lp->d_partitions[0];
	bzero(lp, sizeof (*lp));
/*
 * NOTE: partition 0 ('a') is used to read the label.  Therefore 'a' must
 * start at the beginning of the disk!  If there is no label or the label
 * is corrupted then a label containing a geometry sufficient to  read/write
 * sector 1 (LABELSECTOR) is created.  
 */
	lp->d_type = DTYPE_ST506;
	lp->d_secsize = 512;			/* XXX */
	lp->d_nsectors = LABELSECTOR + 1;	/* # sectors/track */
	lp->d_ntracks = 1;			/* # tracks/cylinder */
	lp->d_secpercyl = LABELSECTOR + 1;	/* # sectors/cylinder */
	lp->d_ncylinders = 1;			/* # cylinders */
	lp->d_npartitions = 1;			/* 1 partition  = 'a' */
	lp->d_secperunit = lp->d_secpercyl*lp->d_ncylinders;

	if (wdidentify(du, idsec) == 0) {
		wdiddump(du, idsec);
		lp->d_nsectors = idsec[6];
		lp->d_ntracks = idsec[3];
		lp->d_secpercyl = idsec[6]*idsec[3];
		lp->d_ncylinders = idsec[1];
		lp->d_secperunit = ((long)idsec[61] << 16) + idsec[60];
	} 

/*
 * Need to put the information where the driver expects it.  This is normally
 * done after reading the label.  Since we're creating a fake label we have to
 * copy the invented geometry information to the right place.
 */
	du->wd_nblks = pi->p_size = lp->d_secperunit;
	pi->p_fstype = FS_V71K;
	pi->p_frag = 1;
	pi->p_fsize = 1024;
	bcopy(pi, du->wd_parts, sizeof (lp->d_partitions));
}

void
wdgetinfo(du, dev)
	register struct wd_disk *du;
	dev_t	dev;
{
	struct	disklabel locallabel;
	char	*msg;
	register struct disklabel *lp = &locallabel;
	extern wdstrategy();

	wddfltlbl(du, lp);
	msg = readdisklabel((dev & ~7) | 0, wdstrategy, lp);	/* 'a' */
	if (msg != 0) {
		log(LOG_NOTICE, "wd%da using labelonly geometry: %s\n",
			wdunit(dev), msg);
		wddfltlbl(du, lp);
	}
	mapseg5(du->wd_label, LABELDESC);
	bcopy(lp, (struct disklabel *)SEG5, sizeof (struct disklabel));
	normalseg5();
	bcopy(lp->d_partitions, du->wd_parts, sizeof (lp->d_partitions));
	return;
}

/* 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.  Multi-page transfers are supported.  All I/O requests must
 * be a multiple of a sector in length.
 */
wdstrategy(bp)
        register struct buf *bp;        /* IO operation to perform */
{
        register struct buf *dp;
        register struct wd_disk *du;       /* Disk unit to do the IO.      */
        register struct partition *p;
        int unit;
        int s;

        unit = wdunit(bp->b_dev);
        du = &wddrives[unit];
        if (unit >= NWD || !wdc) {
		bp->b_error = ENXIO;
                goto bad;
        }
        /*
         * Determine the size of the transfer, and make sure it is
         * within the boundaries of the partition.
         */
	s = partition_check(bp, &du->wd_dk);
	if	(s < 0)
		goto bad;
	if	(s == 0)
		goto done;

        p = &du->wd_parts[wdpart(bp->b_dev)];
        bp->b_cylin = ((bp->b_blkno + p->p_offset) >> 12);
        dp = &wdutab[unit];
        s = splbio();
        disksort(dp, bp);
        if (dp->b_active == 0)
                wdustart(du);           /* start drive if idle */
        if (wdtab.b_active == 0)
                wdstart();             /* start IO if controller idle */
        splx(s);
        return;

bad:
	bp->b_flags |= B_ERROR;
done:
        iodone(bp);
	return;
}

/* 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(du)
        register struct wd_disk *du;
{
        register struct buf *bp, *dp;

        dp = &wdutab[du->wd_unit];
        if (dp->b_active)
                return;
        bp = dp->b_actf;
        if (bp == NULL)
                return; 
        dp->b_forw = NULL;
        if (wdtab.b_actf  == NULL)              /* link unit into active list */
                wdtab.b_actf = dp;
        else
                wdtab.b_actl->b_forw = dp;
        wdtab.b_actl = dp;
        dp->b_active++;               /* 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.
 *
 * RESTRICTIONS:
 * 1.   The transfer length must be an exact multiple of the sector size.
 */

wdstart()
{
        register struct wd_disk *du;       /* disk unit for IO */
        register struct buf *bp;
        struct buf *dp;
	daddr_t blknum;
        int     addr, i;
        int     unit, s;
	segm	saveregs;
	int 	paddr;
	int	stat;

loop:
        dp = wdtab.b_actf;
        if (dp == NULL)
                return;
        bp = dp->b_actf;
        if (bp == NULL) {
                wdtab.b_actf = dp->b_forw;
                goto loop;
        }
        unit = wdunit(bp->b_dev);
        du = &wddrives[unit];

	if (wdtab.b_active == 0) {
        	wdtab.b_active++;             /* mark controller active */

       		blknum = (unsigned long) bp->b_blkno + du->wd_skip
		 	+ du->wd_parts[wdpart(bp->b_dev)].p_offset;

		du->wd_bc = min(bp->b_bcount, MAX_XFR);
		bp->b_resid = bp->b_bcount - du->wd_bc;
	
		if(!(stat = wdwbusy())) {
			log(LOG_NOTICE, "wd: timeout waiting for BSY\n");
error:
			bp->b_resid = bp->b_bcount;
			bp->b_flags |= B_ERROR;
			biodone(bp);
			return;
		}

		outb(wdc+wd_sdh, du->wd_reg[4] = WD_LBA | (unit << 4) 
			| (int)(blknum >> 24) & 017);

		if(!(stat = wdwready())) {
			log(LOG_NOTICE, "wd: timeout waiting for DRDY\n");
			goto error;
		};

		outb(wdc+wd_seccnt, du->wd_reg[0] = 
			(du->wd_bc + DEV_BSIZE - 1) >> DEV_BSHIFT);
		outb(wdc+wd_sector, du->wd_reg[1] = blknum);
		outb(wdc+wd_cyl_lo, du->wd_reg[2] = (blknum >> 8));
		outb(wdc+wd_cyl_hi, du->wd_reg[3] = (blknum >> 16));
		du->wd_reg[5] = 0;
		outb(wdc+wd_command, du->wd_reg[5] 
			= (bp->b_flags & B_READ) ? WDCC_READ : WDCC_WRITE);
	}
	if ((inw(wdc+wd_csr) & 0100) == 0) {
		du->wd_intfail++;
		outw(wdc+wd_csr, 0100);
	}

       	/* If this is a read operation, just go away until it's done. */
        if (bp->b_flags & B_READ)
		return;

        /* Ready to send data?  */
	if(!(stat = wdwdrq())) {
		log(LOG_NOTICE, "wd: timeout waiting for DRQ\n");
		goto error;
	}

	if (stat & WDCS_ERR) {
		log(LOG_NOTICE, "wd: write error st = %o er = %o\n",
			inb(wdc+wd_altsts), inb(wdc+wd_error));
		wdbufdump(bp);
		goto error;
	}

        /* ASSUMES CONTIGUOUS MEMORY */
	saveseg5(saveregs);
	addr = (int)mapin(bp);
	*KDSD5 = 077406; /* XXXXX buffer is set to 8k */
        outsw (wdc+wd_data, addr+du->wd_skip*DEV_BSIZE, DEV_BSIZE/2);
	mapout(bp);
	restorseg5(saveregs);
        du->wd_bc -= DEV_BSIZE;
}

/*
 * these are globally defined so they can be found
 * by the debugger easily in the case of a system crash
 */
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.
 */
wdintr(unit)
{
        register struct wd_disk *du;
        register struct buf *bp, *dp;
        int status;
        char partch;
	segm saveregs;
	int paddr;
	int addr;

        /* Shouldn't need this, but it may be a slow controller. */
	if(!wdwbusy()) {
		log(LOG_NOTICE, "wd: timeout waiting for BSY\n");
		bp->b_flags |= B_ERROR;
		goto done;
	}
	status = inb(wdc+wd_status); /* clear drive interrupt */
        if (!wdtab.b_active) {
                log(LOG_NOTICE, "wd: extra interrupt\n");
                return;
        }
        dp = wdtab.b_actf;
        bp = dp->b_actf;
        du = &wddrives[wdunit(bp->b_dev)];
        partch = wdpart(bp->b_dev) + 'a';

        if (status & WDCS_ERR) {
        	wd_errbn = bp->b_blkno + du->wd_skip
        		+ du->wd_parts[wdpart(bp->b_dev)].p_offset;
		printf("wd%d%c: ", du->wd_unit, partch);
		printf("hard %s error ",
			(bp->b_flags & B_READ)? "read":"write");
		printf("bn %ld\n", wd_errbn);
		wdrdump();
		bp->b_flags |= B_ERROR; /* flag the error */
        }

        /*
         * If this was a successful read operation, fetch the data.
         */
        if ((bp->b_flags & (B_READ | B_ERROR)) == B_READ) {
                int chk, dummy;

                chk = min(DEV_BSIZE/2, du->wd_bc/2);

                /* Ready to send data? */
		if(!wdwdrq()) {
			log(LOG_NOTICE, "wd: timeout waiting for DRQ\n");
			bp->b_flags |= B_ERROR;
			goto done;
		}

		saveseg5(saveregs);
		addr = (int)mapin(bp);
		*KDSD5 = 077406; /* XXXXXXX setting buffer size to 8k */
                insw(wdc+wd_data, addr + du->wd_skip*DEV_BSIZE, chk);
		restorseg5(saveregs);
                du->wd_bc -= 2*chk;
                while (chk++ < DEV_BSIZE/2) 
			dummy = inw(wdc+wd_data);
        }

      	wdxfer[du->wd_unit]++;

        if ((bp->b_flags & B_ERROR) == 0) {
                du->wd_skip++;          /* Add to successful sectors. */

                /* see if more to transfer */
                if (du->wd_bc > 0) {
                        wdstart(); 
                        return;         /* next chunk is started */
                }
        }

        /* done with this transfer, with or without error */
done:
     	wdtab.b_actf = dp->b_forw;
        wdtab.b_errcnt = 0;
        du->wd_skip = 0;
        dp->b_active = 0;
        dp->b_actf = bp->av_forw;
        dp->b_errcnt = 0;
        bp->b_resid += du->wd_bc;

        biodone(bp);

        wdtab.b_active = 0;
        if (dp->b_actf)
                wdustart(du);           /* requeue disk if more io to do */
        if (wdtab.b_actf)
                wdstart();              /* start IO on next drive */
}

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

        unit = wdunit(dev);
        if (unit >= NWD) 
		return (ENXIO);
        du = &wddrives[unit];
        du->wd_unit = unit;

	/* Wait for pending opens/closes */
	while (du->wd_flags & (DKF_OPENING | DKF_CLOSING))
		sleep(du, PRIBIO);

	if (du->wd_label == 0)
		du->wd_label = disklabelalloc();

	/*
 	 * On first open get label and partition info.  
	 * We may block reading the label so be careful to stop 
         * any other opens.
	 */
	if (du->wd_open == 0) {
		du->wd_flags |= DKF_OPENING;
		wdgetinfo(du, dev);
		du->wd_flags &= ~DKF_OPENING;
		wakeup(du);
	}

	/*
	 * Need to make sure the partition is not out of bounds. This requires
	 * mapping in the external label. This only happens when a partition
	 * is opened (at mount time) and isn't an efficiency problem.
	 */
	mapseg5(du->wd_label, LABELDESC);
	i = ((struct disklabel *)SEG5)->d_npartitions;
	normalseg5();

        if (part >= i)
                return (ENXIO);
	dkoverlapchk(du->wd_open, dev, du->wd_label, "wd");
        du->wd_open |= mask;
        switch (fmt) {
        case S_IFCHR:
                du->wd_copen |= mask;
                break;
        case S_IFBLK:
                du->wd_bopen |= mask;
                break;
        }
	return (0);
}

/* ARGSUSED */
wdclose(dev, flags, mode)
        dev_t dev;
        int flags, mode;
{
	register struct wd_disk *du;
	int mask, s;
	int unit = wdunit(dev);

	du = &wddrives[unit];
	if (du->wd_intfail)
		log(LOG_NOTICE, "wd%d: intfail = %ld\n", unit, du->wd_intfail);
	mask = 1 << wdpart(dev);
	if (mode == S_IFCHR)
		du->wd_copen &= ~mask;
	else if (mode == S_IFBLK)
		du->wd_bopen &= ~mask;
	else
		return(EINVAL);
	du->wd_open = du->wd_copen | du->wd_bopen;
	if (du->wd_open == 0) {
		du->wd_flags |= DKF_CLOSING;
		s = splbio();
		while (wdutab[unit].b_actf) {
			du->wd_flags |= DKF_WANTED;
			sleep(&wdutab[unit], PRIBIO);
		}
		splx(s);
		du->wd_flags &= ~(DKF_CLOSING | DKF_WANTED);
		wakeup(du);
	}
	return(0);
}

wdioctl(dev,cmd,addr,flag)
	dev_t dev;
	int cmd;
	caddr_t addr;
	int flag;
{
	int error = 0;
	struct dkdevice *disk;

	disk = &wddrives[wdunit(dev)].wd_dk;
	error = ioctldisklabel(dev, cmd, addr, flag, disk, wdstrategy);
	return (error);
}

wdsize(dev)
	dev_t dev;
{
	int unit = wdunit(dev);
	int part = wdpart(dev);
	struct wd_disk *du = &wddrives[unit];
	int val;

	if (unit >= NWD) 
		return(-1);
	if (du->wd_open == 0) {
		val = wdopen (dev, 0, 0);
		if (val < 0) {
printf("wdsize: wd%d%c: error opening device\n", unit, 'a'+part);
			return (-1);
		}
	}
	val = (u_long)du->wd_parts[part].p_size;
	wdclose(dev, 0, 0);
printf("wdsize: wd%d%c: size = %d\n", unit, 'a'+part, val);
	return (val);
}

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

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

	
#ifdef ARGO
outb(0x461,0);	/* disable failsafe timer */
#endif
	addr = (char *) 0;		/* starting address */
	/* size of memory to dump */
	num = Maxmem;
	unit = wdunit(dev);		/* eventually support floppies? */
	part = wdpart(dev);		/* file system */
	/* check for acceptable drive number */
	if (unit >= NWD) return(ENXIO);

	du = &wddrives[unit];
	/* was it ever initialized ? */
	if (du->wd_state < OPEN) return (ENXIO) ;

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

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

	secpertrk = du->wd_dd.d_nsectors;
	secpercyl = du->wd_dd.d_secpercyl;
	nblocks = du->wd_dd.d_partitions[part].p_size;
	cyloff = du->wd_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);

	/*wdtab.b_active = 1;		/* mark controller active for if we
					   panic during the dump */
	wddoingadump = 1  ;  i = 100000 ;
	while ((inb(wdc+wd_altsts) & WDCS_BUSY) && (i-- > 0)) ;
	outb(wdc+wd_sdh, WDSD_IBM | (unit << 4));
	outb(wdc+wd_command, WDCC_RESTORE | WD_STEP);
	while (inb(wdc+wd_altsts) & WDCS_BUSY) ;

	/* some compaq controllers require this ... */
	wdsetctlr(dev, du);
	
	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(kernel_pmap, vmmap, addr, VM_PROT_READ, TRUE);

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

#ifdef notyet
		/* 
		 * See if the current block is in the bad block list.
		 * (If we have one.)
		 */
	    		for (bt_ptr = dkbad[unit].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->wd_dd.d_secperunit)
					- du->wd_dd.d_nsectors
					- (bt_ptr - dkbad[unit].bt_bad) - 1;
				cylin = blknum / secpercyl;
				head = (blknum % secpercyl) / secpertrk;
				sector = blknum % secpertrk;
				break;
			}

#endif
		sector++;		/* origin 1 */

		/* select drive.     */
		outb(wdc+wd_sdh, WDSD_IBM | (unit<<4) | (head & 0xf));
		wdwready();

		/* transfer some blocks */
		outb(wdc+wd_sector, sector);
		outb(wdc+wd_seccnt,1);
		outb(wdc+wd_cyl_lo, cylin);
		outb(wdc+wd_cyl_hi, cylin >> 8);
#ifdef notdef
		/* lets just talk about this first...*/
		pg ("sdh 0%o sector %d cyl %d addr 0x%x",
			inb(wdc+wd_sdh), inb(wdc+wd_sector),
			inb(wdc+wd_cyl_hi)*256+inb(wdc+wd_cyl_lo), addr) ;
#endif
#ifdef ODYSSEUS
if(cylin < 46 || cylin > 91)pg("oops");
#endif
#ifdef PRIAM
if(cylin < 40 || cylin > 79)pg("oops");
#endif
		outb(wdc+wd_command, WDCC_WRITE);
		
		/* Ready to send data?	*/
		wdwdrq();
		if (inb(wdc+wd_altsts) & WDCS_ERR) return(EIO) ;

		outsw (wdc+wd_data, CADDR1+((int)addr&(NBPG-1)), 256);
		(int) addr += 512;

		if (inb(wdc+wd_altsts) & WDCS_ERR) return(EIO) ;
		/* Check data request (should be done).         */
		if (inb(wdc+wd_altsts) & WDCS_DRQ) return(EIO) ;

		/* wait for completion */
		for ( i = 1000000 ; inb(wdc+wd_altsts) & WDCS_BUSY ; i--) {
				if (i < 0) return (EIO) ;
		}
		/* error check the xfer */
		if (inb(wdc+wd_altsts) & WDCS_ERR) return(EIO) ;
		/* update block count */
		num--;
		blknum++ ;
if (num % 100 == 0) printf(".") ;
	}
	return(0);
}
#else
wddump(dev)
	dev_t dev;
{
printf("wddump: nothing here \n");
}
#endif
#endif
