386BSD 0.1 development
authorWilliam F. Jolitz <wjolitz@soda.berkeley.edu>
Wed, 15 Jul 1992 00:55:21 +0000 (16:55 -0800)
committerWilliam F. Jolitz <wjolitz@soda.berkeley.edu>
Wed, 15 Jul 1992 00:55:21 +0000 (16:55 -0800)
Work on file usr/src/sys.386bsd/i386/isa/wd.c

Co-Authored-By: Lynne Greer Jolitz <ljolitz@cardio.ucsf.edu>
Synthesized-from: 386BSD-0.1

usr/src/sys.386bsd/i386/isa/wd.c [new file with mode: 0644]

diff --git a/usr/src/sys.386bsd/i386/isa/wd.c b/usr/src/sys.386bsd/i386/isa/wd.c
new file mode 100644 (file)
index 0000000..e9b1a10
--- /dev/null
@@ -0,0 +1,1261 @@
+/*-
+ * 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
+ */
+
+/* TODO:peel out buffer at low ipl, speed improvement */
+
+
+#include "wd.h"
+#if    NWD > 0
+
+#include "param.h"
+#include "dkbad.h"
+#include "systm.h"
+#include "conf.h"
+#include "file.h"
+#include "stat.h"
+#include "ioctl.h"
+#include "disklabel.h"
+#include "buf.h"
+#include "uio.h"
+#include "malloc.h"
+#include "machine/cpu.h"
+#include "i386/isa/isa_device.h"
+#include "i386/isa/icu.h"
+#include "i386/isa/wdreg.h"
+#include "syslog.h"
+#include "vm/vm.h"
+
+#define        RETRIES         5       /* number of retries before giving up */
+#define        MAXTRANSFER     32      /* max size of transfer in page clusters */
+
+#define wdnoreloc(dev) (minor(dev) & 0x80)     /* ignore partition table */
+#define wddospart(dev) (minor(dev) & 0x40)     /* use dos partitions */
+#define wdunit(dev)    ((minor(dev) & 0x38) >> 3)
+#define wdpart(dev)    (minor(dev) & 0x7)
+#define makewddev(maj, unit, part)     (makedev(maj,((unit<<3)+part)))
+#define WDRAW  3               /* 'd' partition isn't a partition! */
+
+#define b_cylin        b_resid         /* cylinder number for doing IO to */
+                               /* shares an entry in the buf struct */
+
+/*
+ * Drive states.  Used to initialize drive.
+ */
+
+#define        CLOSED          0               /* disk is closed. */
+#define        WANTOPEN        1               /* open requested, not started */
+#define        RECAL           2               /* doing restore */
+#define        OPEN            3               /* done with open */
+
+/*
+ * The structure of a disk drive.
+ */
+struct disk {
+       long    dk_bc;          /* byte count left */
+       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_port;        /* i/o port base */
+       
+        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? */
+       short   dk_flags;       /* drive characteistics found */
+#define        DKFL_DOSPART    0x00001  /* has DOS partition table */
+#define        DKFL_QUIET      0x00002  /* report errors back, but don't complain */
+#define        DKFL_SINGLE     0x00004  /* sector at a time mode */
+#define        DKFL_ERROR      0x00008  /* processing a disk error */
+#define        DKFL_BSDLABEL   0x00010  /* has a BSD disk label */
+#define        DKFL_BADSECT    0x00020  /* has a bad144 badsector table */
+#define        DKFL_WRITEPROT  0x00040  /* manual unit write protect */
+       struct wdparams dk_params; /* ESDI/IDE drive/controller parameters */
+       struct disklabel dk_dd; /* device configuration data */
+       struct  dos_partition
+               dk_dospartitions[NDOSPART];     /* DOS view of disk */
+       struct  dkbad   dk_bad; /* bad sector table */
+};
+
+struct disk    *wddrives[NWD];         /* table of units */
+struct buf     wdtab;
+struct buf     wdutab[NWD];            /* head of queue per drive */
+struct buf     rwdbuf[NWD];            /* buffers for raw IO */
+long   wdxfer[NWD];                    /* count of transfers */
+#ifdef WDDEBUG
+int    wddebug;
+#endif
+
+struct isa_driver wddriver = {
+       wdprobe, wdattach, "wd",
+};
+
+void wdustart(struct disk *);
+void wdstart();
+int wdcommand(struct disk *, int);
+int wdcontrol(struct buf *);
+int wdsetctlr(dev_t, struct disk *);
+int wdgetctlr(int, struct disk *);
+
+/*
+ * Probe for controller.
+ */
+int
+wdprobe(struct isa_device *dvp)
+{
+       int unit = dvp->id_unit;
+       struct disk *du;
+       int wdc;
+
+       if (unit > NWD)
+               return(0);
+
+       if ((du = wddrives[unit]) == 0) {
+               du = wddrives[unit] = (struct disk *)
+                       malloc (sizeof(struct disk), M_TEMP, M_NOWAIT);
+               du->dk_unit = unit;
+       }
+
+       wdc = du->dk_port = dvp->id_iobase;
+       
+       /* check if we have registers that work */
+       outb(wdc+wd_error, 0x5a) ;      /* error register not writable */
+       outb(wdc+wd_cyl_lo, 0xa5) ;     /* but all of cyllo are implemented */
+       if(inb(wdc+wd_error) == 0x5a || inb(wdc+wd_cyl_lo) != 0xa5)
+               goto nodevice;
+
+       /* reset the device */
+       outb(wdc+wd_ctlr, (WDCTL_RST|WDCTL_IDS));
+       DELAY(1000);
+       outb(wdc+wd_ctlr, WDCTL_IDS);
+       DELAY(1000);
+
+       /* execute a controller only command */
+       if (wdcommand(du, WDCC_DIAGNOSE) < 0)
+               goto nodevice;
+
+       (void) inb(wdc+wd_error);       /* XXX! */
+       outb(wdc+wd_ctlr, WDCTL_4BIT);
+       return (1);
+
+nodevice:
+       free(du, M_TEMP);
+       wddrives[unit] = 0;
+       return (0);
+}
+
+/*
+ * Attach each drive if possible.
+ */
+int
+wdattach(struct isa_device *dvp)
+{
+       int unit = dvp->id_unit;
+       struct disk *du = wddrives[unit];
+
+       if(wdgetctlr(unit, du) == 0)  {
+               int i, blank;
+               char c;
+
+               printf(" <");
+               for (i = blank = 0 ; i < sizeof(du->dk_params.wdp_model); i++) {
+                       char c = du->dk_params.wdp_model[i];
+
+                       if (blank && c == ' ') continue;
+                       if (blank && c != ' ') {
+                               printf(" %c", c);
+                               blank = 0;
+                               continue;
+                       } 
+                       if (c == ' ')
+                               blank = 1;
+                       else
+                               printf("%c", c);
+               }
+               printf(">");
+       }
+/* check for index pulses from each drive. if present, report and
+   allocate a bios drive position to it, which will be used by read disklabel */
+       du->dk_unit = unit;
+       return(1);
+}
+
+/* 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.
+ */
+int
+wdstrategy(register struct buf *bp)
+{
+       register struct buf *dp;
+       struct disklabel *lp;
+       register struct partition *p;
+       struct disk *du;        /* Disk unit to do the IO.      */
+       long maxsz, sz;
+       int     unit = wdunit(bp->b_dev);
+       int     s;
+
+       /* valid unit, controller, and request?  */
+       if (unit >= NWD || bp->b_blkno < 0 || (du = wddrives[unit]) == 0) {
+               bp->b_error = EINVAL;
+               bp->b_flags |= B_ERROR;
+               goto done;
+       }
+
+       /* "soft" write protect check */
+       if ((du->dk_flags & DKFL_WRITEPROT) && (bp->b_flags & B_READ) == 0) {
+               bp->b_error = EROFS;
+               bp->b_flags |= B_ERROR;
+               goto done;
+       }
+
+       /* have partitions and want to use them? */
+       if ((du->dk_flags & DKFL_BSDLABEL) != 0 && wdpart(bp->b_dev) != WDRAW) {
+
+               /*
+                * do bounds checking, adjust transfer. if error, process.
+                * if end of partition, just return
+                */
+               if (bounds_check_with_label(bp, &du->dk_dd, du->dk_wlabel) <= 0)
+                       goto done;
+               /* otherwise, process transfer request */
+       }
+
+q:
+       /* queue transfer on drive, activate drive and controller if idle */
+       dp = &wdutab[unit];
+       s = splbio();
+       disksort(dp, bp);
+       if (dp->b_active == 0)
+               wdustart(du);           /* start drive */
+       if (wdtab.b_active == 0)
+               wdstart(s);             /* start controller */
+       splx(s);
+       return;
+
+done:
+       /* toss transfer, we're done early */
+       biodone(bp);
+}
+
+/*
+ * Routine to queue a command to the controller.  The unit's
+ * request is linked into the active list for the controller.
+ * If the controller is idle, the transfer is started.
+ */
+static void
+wdustart(register struct disk *du)
+{
+       register struct buf *bp, *dp = &wdutab[du->dk_unit];
+
+       /* unit already active? */
+       if (dp->b_active)
+               return;
+
+       /* anything to start? */
+       bp = dp->b_actf;
+       if (bp == NULL)
+               return; 
+
+       /* link onto controller queue */
+       dp->b_forw = NULL;
+       if (wdtab.b_actf  == NULL)
+               wdtab.b_actf = dp;
+       else
+               wdtab.b_actl->b_forw = dp;
+       wdtab.b_actl = dp;
+
+       /* mark the drive unit as busy */
+       dp->b_active = 1;
+}
+
+/*
+ * 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.
+ */
+
+static void
+wdstart()
+{
+       register struct disk *du;       /* disk unit for IO */
+       register struct buf *bp;
+       struct disklabel *lp;
+       struct buf *dp;
+       register struct bt_bad *bt_ptr;
+       long    blknum, pagcnt, cylin, head, sector;
+       long    secpertrk, secpercyl, addr, i;
+       int     unit, s, wdc;
+
+loop:
+       /* is there a drive for the controller to do a transfer with? */
+       dp = wdtab.b_actf;
+       if (dp == NULL)
+               return;
+
+       /* is there a transfer to this drive ? if so, link it on
+          the controller's queue */
+       bp = dp->b_actf;
+       if (bp == NULL) {
+               wdtab.b_actf = dp->b_forw;
+               goto loop;
+       }
+
+       /* obtain controller and drive information */
+       unit = wdunit(bp->b_dev);
+       du = wddrives[unit];
+
+       /* if not really a transfer, do control operations specially */
+       if (du->dk_state < OPEN) {
+               (void) wdcontrol(bp);
+               return;
+       }
+
+       /* calculate transfer details */
+       blknum = bp->b_blkno + du->dk_skip;
+/*if(wddebug)printf("bn%d ", blknum);*/
+#ifdef WDDEBUG
+       if (du->dk_skip == 0)
+               printf("\nwdstart %d: %s %d@%d; map ", unit,
+                       (bp->b_flags & B_READ) ? "read" : "write",
+                       bp->b_bcount, blknum);
+       else
+               printf(" %d)%x", du->dk_skip, inb(wdc+wd_altsts));
+#endif
+       addr = (int) bp->b_un.b_addr;
+       if (du->dk_skip == 0)
+               du->dk_bc = bp->b_bcount;
+
+       lp = &du->dk_dd;
+       secpertrk = lp->d_nsectors;
+       secpercyl = lp->d_secpercyl;
+       cylin = blknum / secpercyl;
+       head = (blknum % secpercyl) / secpertrk;
+       sector = blknum % secpertrk;
+       if ((du->dk_flags & DKFL_BSDLABEL) != 0 && wdpart(bp->b_dev) != WDRAW)
+               cylin += lp->d_partitions[wdpart(bp->b_dev)].p_offset
+                               / secpercyl;
+
+       /* 
+        * See if the current block is in the bad block list.
+        * (If we have one, and not formatting.)
+        */
+       if ((du->dk_flags & (/*DKFL_SINGLE|*/DKFL_BADSECT))
+               == (/*DKFL_SINGLE|*/DKFL_BADSECT))
+           for (bt_ptr = du->dk_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.
+                        */
+#ifdef WDDEBUG
+                           printf("--- badblock code -> Old = %d; ",
+                               blknum);
+#endif
+                       blknum = lp->d_secperunit - lp->d_nsectors
+                               - (bt_ptr - du->dk_bad.bt_bad) - 1;
+                       cylin = blknum / secpercyl;
+                       head = (blknum % secpercyl) / secpertrk;
+                       sector = blknum % secpertrk;
+#ifdef WDDEBUG
+                           printf("new = %d\n", blknum);
+#endif
+                       break;
+               }
+       }
+/*if(wddebug)pg("c%d h%d s%d ", cylin, head, sector);*/
+       sector += 1;    /* sectors begin with 1, not 0 */
+
+       wdtab.b_active = 1;             /* mark controller active */
+       wdc = du->dk_port;
+
+       /* if starting a multisector transfer, or doing single transfers */
+       if (du->dk_skip == 0 || (du->dk_flags & DKFL_SINGLE)) {
+               if (wdtab.b_errcnt && (bp->b_flags & B_READ) == 0)
+                       du->dk_bc += DEV_BSIZE;
+
+               /* controller idle? */
+               while (inb(wdc+wd_status) & WDCS_BUSY)
+                       ;
+
+               /* stuff the task file */
+               outb(wdc+wd_precomp, lp->d_precompcyl / 4);
+#ifdef B_FORMAT
+               if (bp->b_flags & B_FORMAT) {
+                       outb(wdc+wd_sector, lp->d_gap3);
+                       outb(wdc+wd_seccnt, lp->d_nsectors);
+               } else {
+#endif
+               if (du->dk_flags & DKFL_SINGLE)
+                       outb(wdc+wd_seccnt, 1);
+               else
+                       outb(wdc+wd_seccnt, howmany(du->dk_bc, DEV_BSIZE));
+               outb(wdc+wd_sector, sector);
+
+#ifdef B_FORMAT
+               }
+#endif
+
+               outb(wdc+wd_cyl_lo, cylin);
+               outb(wdc+wd_cyl_hi, cylin >> 8);
+
+               /* set up the SDH register (select drive) */
+               outb(wdc+wd_sdh, WDSD_IBM | (unit<<4) | (head & 0xf));
+
+               /* wait for drive to become ready */
+               while ((inb(wdc+wd_status) & WDCS_READY) == 0)
+                       ;
+
+               /* initiate command! */
+#ifdef B_FORMAT
+               if (bp->b_flags & B_FORMAT)
+                       outb(wdc+wd_command, WDCC_FORMAT);
+               else
+#endif
+               outb(wdc+wd_command,
+                       (bp->b_flags & B_READ)? WDCC_READ : WDCC_WRITE);
+#ifdef WDDEBUG
+               printf("sector %d cylin %d head %d addr %x sts %x\n",
+                       sector, cylin, head, addr, inb(wdc+wd_altsts));
+#endif
+       }
+
+       /* if this is a read operation, just go away until it's done.   */
+       if (bp->b_flags & B_READ) return;
+
+       /* ready to send data?  */
+       while ((inb(wdc+wd_status) & WDCS_DRQ) == 0)
+               ;
+
+       /* then send it! */
+       outsw (wdc+wd_data, addr+du->dk_skip * DEV_BSIZE,
+               DEV_BSIZE/sizeof(short));
+       du->dk_bc -= DEV_BSIZE;
+}
+
+/* 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.
+ */
+void
+wdintr(struct intrframe wdif)
+{
+       register struct disk *du;
+       register struct buf *bp, *dp;
+       int status, wdc;
+       char partch ;
+
+       if (!wdtab.b_active) {
+#ifdef nyet
+               printf("wd: extra interrupt\n");
+#endif
+               return;
+       }
+
+       dp = wdtab.b_actf;
+       bp = dp->b_actf;
+       du = wddrives[wdunit(bp->b_dev)];
+       wdc = du->dk_port;
+
+#ifdef WDDEBUG
+       printf("I ");
+#endif
+
+       while ((status = inb(wdc+wd_status)) & WDCS_BUSY) ;
+
+       /* is it not a transfer, but a control operation? */
+       if (du->dk_state < OPEN) {
+               if (wdcontrol(bp))
+                       wdstart();
+               return;
+       }
+
+       /* have we an error? */
+       if (status & (WDCS_ERR | WDCS_ECCCOR)) {
+
+               du->dk_status = status;
+               du->dk_error = inb(wdc + wd_error);
+#ifdef WDDEBUG
+               printf("status %x error %x\n", status, du->dk_error);
+#endif
+               if((du->dk_flags & DKFL_SINGLE) == 0) {
+                       du->dk_flags |=  DKFL_ERROR;
+                       goto outt;
+               }
+#ifdef B_FORMAT
+               if (bp->b_flags & B_FORMAT) {
+                       bp->b_flags |= B_ERROR;
+                       goto done;
+               }
+#endif
+               
+               /* error or error correction? */
+               if (status & WDCS_ERR) {
+                       if (++wdtab.b_errcnt < RETRIES) {
+                               wdtab.b_active = 0;
+                       } else {
+                               if((du->dk_flags&DKFL_QUIET) == 0) {
+                                       diskerr(bp, "wd", "hard error",
+                                               LOG_PRINTF, du->dk_skip,
+                                               &du->dk_dd);
+#ifdef WDDEBUG
+                                       printf( "status %b error %b\n",
+                                               status, WDCS_BITS,
+                                               inb(wdc+wd_error), WDERR_BITS);
+#endif
+                               }
+                               bp->b_flags |= B_ERROR; /* flag the error */
+                       }
+               } else if((du->dk_flags&DKFL_QUIET) == 0) {
+                               diskerr(bp, "wd", "soft ecc", 0,
+                                       du->dk_skip, &du->dk_dd);
+               }
+       }
+outt:
+
+       /*
+        * If this was a successful read operation, fetch the data.
+        */
+       if (((bp->b_flags & (B_READ | B_ERROR)) == B_READ) && wdtab.b_active) {
+               int chk, dummy;
+
+               chk = min(DEV_BSIZE / sizeof(short), du->dk_bc / sizeof(short));
+
+               /* ready to receive data? */
+               while ((inb(wdc+wd_status) & WDCS_DRQ) == 0)
+                       ;
+
+               /* suck in data */
+               insw (wdc+wd_data,
+                       (int)bp->b_un.b_addr + du->dk_skip * DEV_BSIZE, chk);
+               du->dk_bc -= chk * sizeof(short);
+
+               /* for obselete fractional sector reads */
+               while (chk++ < 256) insw (wdc+wd_data, &dummy, 1);
+       }
+
+       wdxfer[du->dk_unit]++;
+       if (wdtab.b_active) {
+               if ((bp->b_flags & B_ERROR) == 0) {
+                       du->dk_skip++;          /* Add to successful sectors. */
+                       if (wdtab.b_errcnt && (du->dk_flags & DKFL_QUIET) == 0)
+                               diskerr(bp, "wd", "soft error", 0,
+                                       du->dk_skip, &du->dk_dd);
+                       wdtab.b_errcnt = 0;
+
+                       /* see if more to transfer */
+                       if (du->dk_bc > 0 && (du->dk_flags & DKFL_ERROR) == 0) {
+                               wdstart();
+                               return;         /* next chunk is started */
+                       } else if ((du->dk_flags & (DKFL_SINGLE|DKFL_ERROR))
+                                       == DKFL_ERROR) {
+                               du->dk_skip = 0;
+                               du->dk_flags &= ~DKFL_ERROR;
+                               du->dk_flags |=  DKFL_SINGLE;
+                               wdstart();
+                               return;         /* redo xfer sector by sector */
+                       }
+               }
+
+done:
+               /* done with this transfer, with or without error */
+               du->dk_flags &= ~DKFL_SINGLE;
+               wdtab.b_actf = dp->b_forw;
+               wdtab.b_errcnt = 0;
+               du->dk_skip = 0;
+               dp->b_active = 0;
+               dp->b_actf = bp->av_forw;
+               dp->b_errcnt = 0;
+               bp->b_resid = 0;
+               biodone(bp);
+       }
+
+       /* controller idle */
+       wdtab.b_active = 0;
+
+       /* anything more on drive queue? */
+       if (dp->b_actf)
+               wdustart(du);
+       /* anything more for controller to do? */
+       if (wdtab.b_actf)
+               wdstart();
+}
+
+/*
+ * Initialize a drive.
+ */
+int
+wdopen(dev_t dev, int flags, int fmt, struct proc *p)
+{
+       register unsigned int unit;
+       register struct disk *du;
+        int part = wdpart(dev), mask = 1 << part;
+        struct partition *pp;
+       struct dkbad *db;
+       int i, error = 0;
+       char *msg;
+
+       unit = wdunit(dev);
+       if (unit >= NWD) return (ENXIO) ;
+
+       du = wddrives[unit];
+       if (du == 0 && (unit&1) && wddrives[unit&~1]) {                 /*XXX*/
+               du = wddrives[unit] = (struct disk *)                   /*XXX*/
+                       malloc (sizeof(struct disk), M_TEMP, M_NOWAIT); /*XXX*/
+               du->dk_port = wddrives[unit&~1]->dk_port;               /*XXX*/
+       }                                                               /*XXX*/
+       if (du == 0) return (ENXIO) ;
+
+       if ((du->dk_flags & DKFL_BSDLABEL) == 0) {
+               du->dk_flags |= DKFL_WRITEPROT;
+               wdutab[unit].b_actf = NULL;
+
+               /*
+                * Use the default sizes until we've read the label,
+                * or longer if there isn't one there.
+                */
+               bzero(&du->dk_dd, sizeof(du->dk_dd));
+#undef d_type /* fix goddamn segments.h! XXX */
+               du->dk_dd.d_type = DTYPE_ST506;
+               du->dk_dd.d_ncylinders = 1024;
+               du->dk_dd.d_secsize = DEV_BSIZE;
+               du->dk_dd.d_ntracks = 8;
+               du->dk_dd.d_nsectors = 17;
+               du->dk_dd.d_secpercyl = 17*8;
+               du->dk_state = WANTOPEN;
+               du->dk_unit = unit;
+               if (part == WDRAW)
+                       du->dk_flags |= DKFL_QUIET;
+               else
+                       du->dk_flags &= ~DKFL_QUIET;
+
+               /* read label using "c" partition */
+               if (msg = readdisklabel(makewddev(major(dev), wdunit(dev), WDRAW),
+                               wdstrategy, &du->dk_dd, du->dk_dospartitions,
+                               &du->dk_bad, 0)) {
+                       if((du->dk_flags&DKFL_QUIET) == 0) {
+                               log(LOG_WARNING, "wd%d: cannot find label (%s)\n",
+                                       unit, msg);
+                               error = EINVAL;         /* XXX needs translation */
+                       }
+                       goto done;
+               } else {
+
+                       wdsetctlr(dev, du);
+                       du->dk_flags |= DKFL_BSDLABEL;
+                       du->dk_flags &= ~DKFL_WRITEPROT;
+                       if (du->dk_dd.d_flags & D_BADSECT)
+                               du->dk_flags |= DKFL_BADSECT;
+               }
+
+done:
+               if (error)
+                       return(error);
+
+       }
+        /*
+         * Warn if a partion is opened
+         * that overlaps another partition which is open
+         * unless one is the "raw" partition (whole disk).
+         */
+        if ((du->dk_openpart & mask) == 0 /*&& part != RAWPART*/ && part != WDRAW) {
+               int     start, end;
+
+                pp = &du->dk_dd.d_partitions[part];
+                start = pp->p_offset;
+                end = pp->p_offset + pp->p_size;
+                for (pp = du->dk_dd.d_partitions;
+                     pp < &du->dk_dd.d_partitions[du->dk_dd.d_npartitions];
+                       pp++) {
+                        if (pp->p_offset + pp->p_size <= start ||
+                            pp->p_offset >= end)
+                                continue;
+                        /*if (pp - du->dk_dd.d_partitions == RAWPART)
+                                continue; */
+                        if (pp - du->dk_dd.d_partitions == WDRAW)
+                                continue;
+                        if (du->dk_openpart & (1 << (pp -
+                                       du->dk_dd.d_partitions)))
+                                log(LOG_WARNING,
+                                    "wd%d%c: overlaps open partition (%c)\n",
+                                    unit, part + 'a',
+                                    pp - du->dk_dd.d_partitions + 'a');
+                }
+        }
+        if (part >= du->dk_dd.d_npartitions && part != WDRAW)
+                return (ENXIO);
+
+       /* insure only one open at a time */
+        du->dk_openpart |= mask;
+        switch (fmt) {
+        case S_IFCHR:
+                du->dk_copenpart |= mask;
+                break;
+        case S_IFBLK:
+                du->dk_bopenpart |= mask;
+                break;
+        }
+       return (0);
+}
+
+/*
+ * 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.
+ */
+static int
+wdcontrol(register struct buf *bp)
+{
+       register struct disk *du;
+       register unit;
+       unsigned char  stat;
+       int s, cnt;
+       extern int bootdev;
+       int cyl, trk, sec, i, wdc;
+       struct wdparams foo;
+
+       du = wddrives[wdunit(bp->b_dev)];
+       unit = du->dk_unit;
+       wdc = du->dk_port;
+       
+       switch (du->dk_state) {
+
+       tryagainrecal:
+       case WANTOPEN:                  /* set SDH, step rate, do restore */
+#ifdef WDDEBUG
+               printf("wd%d: recal ", unit);
+#endif
+               s = splbio();           /* not called from intr level ... */
+               wdgetctlr(unit, du);
+
+               outb(wdc+wd_sdh, WDSD_IBM | (unit << 4));
+               wdtab.b_active = 1;
+               outb(wdc+wd_command, WDCC_RESTORE | WD_STEP);
+               du->dk_state++;
+               splx(s);
+               return(0);
+
+       case RECAL:
+               if ((stat = inb(wdc+wd_status)) & WDCS_ERR) {
+                       if ((du->dk_flags & DKFL_QUIET) == 0) {
+                               printf("wd%d: recal", du->dk_unit);
+                               printf(": status %b error %b\n",
+                                       stat, WDCS_BITS, inb(wdc+wd_error),
+                                       WDERR_BITS);
+                       }
+                       if (++wdtab.b_errcnt < RETRIES)
+                               goto tryagainrecal;
+                       bp->b_error = ENXIO;    /* XXX needs translation */
+                       goto badopen;
+               }
+
+               /* some controllers require this ... */
+               wdsetctlr(bp->b_dev, du);
+
+               wdtab.b_errcnt = 0;
+               du->dk_state = OPEN;
+               /*
+                * The rest of the initialization can be done
+                * by normal means.
+                */
+               return(1);
+
+       default:
+               panic("wdcontrol");
+       }
+       /* NOTREACHED */
+
+badopen:
+       if ((du->dk_flags & DKFL_QUIET) == 0) 
+               printf(": status %b error %b\n",
+                       stat, WDCS_BITS, inb(wdc + wd_error), WDERR_BITS);
+       bp->b_flags |= B_ERROR;
+       return(1);
+}
+
+/*
+ * send a command and wait uninterruptibly until controller is finished.
+ * return -1 if controller busy for too long, otherwise
+ * return status. intended for brief controller commands at critical points.
+ * assumes interrupts are blocked.
+ */
+static int
+wdcommand(struct disk *du, int cmd) {
+       int timeout = 1000000, stat, wdc;
+
+       /* controller ready for command? */
+       wdc = du->dk_port;
+       while (((stat = inb(wdc + wd_status)) & WDCS_BUSY) && timeout > 0)
+               timeout--;
+       if (timeout <= 0)
+               return(-1);
+
+       /* send command, await results */
+       outb(wdc+wd_command, cmd);
+       while (((stat = inb(wdc+wd_status)) & WDCS_BUSY) && timeout > 0)
+               timeout--;
+       if (timeout <= 0)
+               return(-1);
+       if (cmd != WDCC_READP)
+               return (stat);
+
+       /* is controller ready to return data? */
+       while (((stat = inb(wdc+wd_status)) & (WDCS_ERR|WDCS_DRQ)) == 0 &&
+               timeout > 0)
+               timeout--;
+       if (timeout <= 0)
+               return(-1);
+
+       return (stat);
+}
+
+/*
+ * issue IDC to drive to tell it just what geometry it is to be.
+ */
+static int
+wdsetctlr(dev_t dev, struct disk *du) {
+       int stat, x, wdc;
+
+/*printf("C%dH%dS%d ", du->dk_dd.d_ncylinders, du->dk_dd.d_ntracks,
+       du->dk_dd.d_nsectors);*/
+
+       x = splbio();
+       wdc = du->dk_port;
+       outb(wdc+wd_cyl_lo, du->dk_dd.d_ncylinders+1);
+       outb(wdc+wd_cyl_hi, (du->dk_dd.d_ncylinders+1)>>8);
+       outb(wdc+wd_sdh, WDSD_IBM | (wdunit(dev) << 4) + du->dk_dd.d_ntracks-1);
+       outb(wdc+wd_seccnt, du->dk_dd.d_nsectors);
+       stat = wdcommand(du, WDCC_IDC);
+
+       if (stat < 0)
+               return(stat);
+       if (stat & WDCS_ERR)
+               printf("wdsetctlr: status %b error %b\n",
+                       stat, WDCS_BITS, inb(wdc+wd_error), WDERR_BITS);
+       splx(x);
+       return(stat);
+}
+
+/*
+ * issue READP to drive to ask it what it is.
+ */
+static int
+wdgetctlr(int u, struct disk *du) {
+       int stat, x, i, wdc;
+       char tb[DEV_BSIZE];
+       struct wdparams *wp;
+
+       x = splbio();           /* not called from intr level ... */
+       wdc = du->dk_port;
+       outb(wdc+wd_sdh, WDSD_IBM | (u << 4));
+       stat = wdcommand(du, WDCC_READP);
+
+       if (stat < 0)
+               return(stat);
+       if (stat & WDCS_ERR) {
+               splx(x);
+               return(inb(wdc+wd_error));
+       }
+
+       /* obtain parameters */
+       wp = &du->dk_params;
+       insw(wdc+wd_data, tb, sizeof(tb)/sizeof(short));
+       bcopy(tb, wp, sizeof(struct wdparams));
+
+       /* shuffle string byte order */
+       for (i=0; i < sizeof(wp->wdp_model) ;i+=2) {
+               u_short *p;
+               p = (u_short *) (wp->wdp_model + i);
+               *p = ntohs(*p);
+       }
+/*printf("gc %x cyl %d trk %d sec %d type %d sz %d model %s\n", wp->wdp_config,
+wp->wdp_fixedcyl+wp->wdp_removcyl, wp->wdp_heads, wp->wdp_sectors,
+wp->wdp_cntype, wp->wdp_cnsbsz, wp->wdp_model);*/
+
+       /* update disklabel given drive information */
+       du->dk_dd.d_ncylinders = wp->wdp_fixedcyl + wp->wdp_removcyl /*+- 1*/;
+       du->dk_dd.d_ntracks = wp->wdp_heads;
+       du->dk_dd.d_nsectors = wp->wdp_sectors;
+       du->dk_dd.d_secpercyl = du->dk_dd.d_ntracks * du->dk_dd.d_nsectors;
+       du->dk_dd.d_partitions[1].p_size = du->dk_dd.d_secpercyl *
+                       wp->wdp_sectors;
+       du->dk_dd.d_partitions[1].p_offset = 0;
+       /* dubious ... */
+       bcopy("ESDI/IDE", du->dk_dd.d_typename, 9);
+       bcopy(wp->wdp_model+20, du->dk_dd.d_packname, 14-1);
+       /* better ... */
+       du->dk_dd.d_type = DTYPE_ESDI;
+       du->dk_dd.d_subtype |= DSTYPE_GEOMETRY;
+
+       /* XXX sometimes possibly needed */
+       (void) inb(wdc+wd_status);
+       return (0);
+}
+
+
+/* ARGSUSED */
+int
+wdclose(dev_t dev, int flags, int fmt)
+{
+       register struct disk *du;
+        int part = wdpart(dev), mask = 1 << part;
+
+       du = wddrives[wdunit(dev)];
+
+       /* insure only one open at a time */
+        du->dk_openpart &= ~mask;
+        switch (fmt) {
+        case S_IFCHR:
+                du->dk_copenpart &= ~mask;
+                break;
+        case S_IFBLK:
+                du->dk_bopenpart &= ~mask;
+                break;
+        }
+       return(0);
+}
+
+int
+wdioctl(dev_t dev, int cmd, caddr_t addr, int flag)
+{
+       int unit = wdunit(dev);
+       register struct disk *du;
+       int error = 0;
+       struct uio auio;
+       struct iovec aiov;
+
+       du = wddrives[unit];
+
+       switch (cmd) {
+
+       case DIOCSBAD:
+                if ((flag & FWRITE) == 0)
+                        error = EBADF;
+               else
+                       du->dk_bad = *(struct dkbad *)addr;
+               break;
+
+       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;
+                else
+                        error = setdisklabel(&du->dk_dd,
+                                       (struct disklabel *)addr,
+                         /*(du->dk_flags & DKFL_BSDLABEL) ? du->dk_openpart : */0,
+                               du->dk_dospartitions);
+                if (error == 0) {
+                       du->dk_flags |= DKFL_BSDLABEL;
+                       wdsetctlr(dev, du);
+               }
+                break;
+
+        case DIOCWLABEL:
+               du->dk_flags &= ~DKFL_WRITEPROT;
+                if ((flag & FWRITE) == 0)
+                        error = EBADF;
+                else
+                        du->dk_wlabel = *(int *)addr;
+                break;
+
+        case DIOCWDINFO:
+               du->dk_flags &= ~DKFL_WRITEPROT;
+                if ((flag & FWRITE) == 0)
+                        error = EBADF;
+                else if ((error = setdisklabel(&du->dk_dd, (struct disklabel *)addr,
+                         /*(du->dk_flags & DKFL_BSDLABEL) ? du->dk_openpart :*/ 0,
+                               du->dk_dospartitions)) == 0) {
+                        int wlab;
+
+                       du->dk_flags |= DKFL_BSDLABEL;
+                       wdsetctlr(dev, du);
+
+                        /* simulate opening partition 0 so write succeeds */
+                        du->dk_openpart |= (1 << 0);            /* XXX */
+                        wlab = du->dk_wlabel;
+                        du->dk_wlabel = 1;
+                        error = writedisklabel(dev, wdstrategy,
+                               &du->dk_dd, du->dk_dospartitions);
+                        du->dk_openpart = du->dk_copenpart | du->dk_bopenpart;
+                        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;
+
+                       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);
+}
+
+#ifdef B_FORMAT
+int
+wdformat(struct buf *bp)
+{
+
+       bp->b_flags |= B_FORMAT;
+       return (wdstrategy(bp));
+}
+#endif
+
+int
+wdsize(dev_t dev)
+{
+       int unit = wdunit(dev), part = wdpart(dev), val;
+       struct disk *du;
+
+       if (unit >= NWD)
+               return(-1);
+
+       du = wddrives[unit];
+       if (du == 0 || du->dk_state == 0)
+               val = wdopen (makewddev(major(dev), unit, WDRAW), FREAD, S_IFBLK, 0);
+       if (du == 0 || val != 0 || du->dk_flags & DKFL_WRITEPROT)
+               return (-1);
+
+       return((int)du->dk_dd.d_partitions[part].p_size);
+}
+
+extern        char *vmmap;            /* poor name! */
+
+int
+wddump(dev_t dev)                      /* dump core after a system crash */
+{
+       register struct disk *du;       /* disk unit to do the IO */
+       register struct bt_bad *bt_ptr;
+       long    num;                    /* number of sectors to write */
+       int     unit, part, wdc;
+       long    blkoff, blknum, blkcnt;
+       long    cylin, head, sector, stat;
+       long    secpertrk, secpercyl, nblocks, i;
+       char *addr;
+       extern  int Maxmem;
+       static  wddoingadump = 0 ;
+       extern caddr_t CADDR1;
+
+       addr = (char *) 0;              /* starting address */
+
+       /* toss any characters present prior to dump */
+       while (sgetc(1))
+               ;
+
+       /* 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];
+       if (du == 0) return(ENXIO);
+       /* was it ever initialized ? */
+       if (du->dk_state < OPEN) return (ENXIO) ;
+       if (du->dk_flags & DKFL_WRITEPROT) return(ENXIO);
+       wdc = du->dk_port;
+
+       /* Convert to disk sectors */
+       num = (u_long) num * NBPG / du->dk_dd.d_secsize;
+
+       /* check if controller active */
+       /*if (wdtab.b_active) return(EFAULT); */
+       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;
+       blkoff = du->dk_dd.d_partitions[part].p_offset;
+
+/*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_status) & WDCS_BUSY) && (i-- > 0)) ;
+       outb(wdc+wd_sdh, WDSD_IBM | (unit << 4));
+       outb(wdc+wd_command, WDCC_RESTORE | WD_STEP);
+       while (inb(wdc+wd_status) & WDCS_BUSY) ;
+
+       /* some compaq controllers require this ... */
+       wdsetctlr(dev, du);
+       
+       blknum = dumplo + blkoff;
+       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, CADDR1, trunc_page(addr), VM_PROT_READ, TRUE);
+
+               /* compute disk address */
+               cylin = blknum / secpercyl;
+               head = (blknum % secpercyl) / secpertrk;
+               sector = blknum % secpertrk;
+
+#ifdef notyet
+               /* 
+                * See if the current block is in the bad block list.
+                * (If we have one.)
+                */
+                       for (bt_ptr = du->dk_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 - du->dk_bad.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));
+               while ((inb(wdc+wd_status) & WDCS_READY) == 0) ;
+
+               /* 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
+               outb(wdc+wd_command, WDCC_WRITE);
+               
+               /* Ready to send data?  */
+               while ((inb(wdc+wd_status) & WDCS_DRQ) == 0) ;
+               if (inb(wdc+wd_status) & WDCS_ERR) return(EIO) ;
+
+               outsw (wdc+wd_data, CADDR1+((int)addr&(NBPG-1)), 256);
+
+               if (inb(wdc+wd_status) & WDCS_ERR) return(EIO) ;
+               /* Check data request (should be done).         */
+               if (inb(wdc+wd_status) & WDCS_DRQ) return(EIO) ;
+
+               /* wait for completion */
+               for ( i = 1000000 ; inb(wdc+wd_status) & WDCS_BUSY ; i--) {
+                               if (i < 0) return (EIO) ;
+               }
+               /* error check the xfer */
+               if (inb(wdc+wd_status) & WDCS_ERR) return(EIO) ;
+
+               if ((unsigned)addr % (1024*1024) == 0) printf("%d ", num/2048) ;
+               /* update block count */
+               num--;
+               blknum++ ;
+               (int) addr += 512;
+
+               /* operator aborting dump? */
+               if (sgetc(1))
+                       return(EINTR);
+       }
+       return(0);
+}
+#endif