summaryrefslogtreecommitdiffstats
path: root/sys/i386/isa/wd.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/i386/isa/wd.c')
-rw-r--r--sys/i386/isa/wd.c1827
1 files changed, 1827 insertions, 0 deletions
diff --git a/sys/i386/isa/wd.c b/sys/i386/isa/wd.c
new file mode 100644
index 0000000..2794ecd
--- /dev/null
+++ b/sys/i386/isa/wd.c
@@ -0,0 +1,1827 @@
+#define WD_COUNT_RETRIES
+static int wdtest = 0;
+
+/*-
+ * 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.37 1994/04/10 11:17:13 csgr Exp $
+ */
+
+/* TODO:
+ * o Bump error count after timeout.
+ * o Satisfy ATA timing in all cases.
+ * o Finish merging berry/sos timeout code (bump error count...).
+ * o Merge/fix TIH/NetBSD bad144 code.
+ * o Merge/fix Dyson/NetBSD clustering code.
+ * o Don't use polling except for initialization. Need to
+ * reorganize the state machine. Then "extra" interrupts
+ * shouldn't happen (except maybe one for initialization).
+ * o Fix disklabel, boot and driver inconsistencies with
+ * bad144 in standard versions.
+ * o Support extended DOS partitions.
+ * o Support swapping to DOS partitions.
+ * o Look at latest linux clustering methods. Our disksort()
+ * gets in the way of clustering.
+ * o Handle bad sectors, clustering, disklabelling, DOS
+ * partitions and swapping driver-independently. Use
+ * i386/dkbad.c for bad sectors. Swapping will need new
+ * driver entries for polled reinit and polled write).
+ */
+
+#include "wd.h"
+#if NWDC > 0
+
+#include "param.h"
+#include "dkbad.h"
+#include "systm.h"
+#include "kernel.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.h"
+#include "i386/isa/isa_device.h"
+#include "i386/isa/wdreg.h"
+#include "syslog.h"
+#include "vm/vm.h"
+
+#define TIMEOUT 10000
+#define RETRIES 5 /* number of retries before giving up */
+#define RECOVERYTIME 500000 /* usec for controller to recover after err */
+#define MAXTRANSFER 256 /* max size of transfer in sectors */
+#define BAD144_NO_CYL 0xffff /* XXX should be in dkbad.h; bad144.c uses -1 */
+
+#ifdef notyet
+#define wdnoreloc(dev) (minor(dev) & 0x80) /* ignore partition table */
+#endif
+#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! */
+
+/* Cylinder number for doing IO to. Shares an entry in the buf struct. */
+#define b_cylin b_resid
+
+/*
+ * This biotab field doubles as a field for the physical unit number on
+ * the controller.
+ */
+#define id_physid id_scsiid
+
+/*
+ * 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_ctrlr; /* physical controller number */
+ char dk_unit; /* physical unit number */
+ char dk_lunit; /* logical unit number */
+ char dk_state; /* control state */
+ u_char dk_status; /* copy of status reg. */
+ u_char dk_error; /* copy of error reg. */
+ u_char dk_timeout; /* countdown to next timeout */
+ 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_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 */
+#define DKFL_LABELLING 0x00080 /* readdisklabel() in progress */
+ struct wdparams dk_params; /* ESDI/IDE drive/controller parameters */
+ struct disklabel dk_dd; /* device configuration data */
+ struct disklabel dk_dd2; /* DOS view converted to label */
+ struct dos_partition
+ dk_dospartitions[NDOSPART]; /* DOS view of disk */
+ struct dkbad dk_bad; /* bad sector table */
+ long dk_badsect[127]; /* 126 plus trailing -1 marker */
+};
+
+static struct disk *wddrives[NWD]; /* table of units */
+static struct buf wdtab[NWDC];
+static struct buf wdutab[NWD]; /* head of queue per drive */
+#ifdef notyet
+static struct buf rwdbuf[NWD]; /* buffers for raw IO */
+#endif
+static long wdxfer[NWD]; /* count of transfers */
+
+
+static void bad144intern(struct disk *);
+static int wdprobe(struct isa_device *dvp);
+static int wdattach(struct isa_device *dvp);
+static void wdustart(struct disk *du);
+static void wdstart(int ctrlr);
+static int wdcontrol(struct buf *bp);
+static int wdcommand(struct disk *du, u_int cylinder, u_int head,
+ u_int sector, u_int count, u_int command);
+static int wdsetctlr(struct disk *du);
+static int wdwsetctlr(struct disk *du);
+static int wdgetctlr(struct disk *du);
+static void wderror(struct buf *bp, struct disk *du, char *mesg);
+static void wdflushirq(struct disk *du, int old_ipl);
+static int wdreset(struct disk *du);
+static void wdsleep(int ctrlr, char *wmesg);
+static void wdtimeout(caddr_t cdu);
+static int wdunwedge(struct disk *du);
+static int wdwait(struct disk *du, u_char bits_wanted, int timeout);
+
+struct isa_driver wdcdriver = {
+ wdprobe, wdattach, "wdc",
+};
+
+extern char *readdisklabel();
+
+/*
+ * Probe for controller.
+ */
+static int
+wdprobe(struct isa_device *dvp)
+{
+ int unit = dvp->id_unit;
+ struct disk *du;
+
+ if (unit >= NWDC)
+ return (0);
+ du = malloc(sizeof *du, M_TEMP, M_NOWAIT);
+ if (du == NULL)
+ return (0);
+ bzero(du, sizeof *du);
+ du->dk_ctrlr = dvp->id_unit;
+ du->dk_port = dvp->id_iobase;
+
+ /* check if we have registers that work */
+ outb(du->dk_port + wd_cyl_lo, 0xa5); /* wd_cyl_lo is read/write */
+ if (inb(du->dk_port + wd_cyl_lo) != 0xa5)
+ goto nodevice;
+
+ if (wdreset(du) != 0 && (DELAY(RECOVERYTIME), wdreset(du)) != 0)
+ goto nodevice;
+
+ /* execute a controller only command */
+ if (wdcommand(du, 0, 0, 0, 0, WDCC_DIAGNOSE) != 0
+ || wdwait(du, 0, TIMEOUT) < 0)
+ goto nodevice;
+
+ /*
+ * drive(s) did not time out during diagnostic :
+ * Get error status and check that both drives are OK.
+ * Table 9-2 of ATA specs suggests that we must check for
+ * a value of 0x01
+ *
+ * Strangely, some controllers will return a status of
+ * 0x81 (drive 0 OK, drive 1 failure), and then when
+ * the DRV bit is set, return status of 0x01 (OK) for
+ * drive 2. (This seems to contradict the ATA spec.)
+ */
+ du->dk_error = inb(du->dk_port + wd_error);
+ /* printf("Error : %x\n", du->dk_error); */
+ if(du->dk_error != 0x01) {
+ if(du->dk_error & 0x80) { /* drive 1 failure */
+
+ /* first set the DRV bit */
+ u_int sdh;
+ sdh = inb(du->dk_port+ wd_sdh);
+ sdh = sdh | 0x10;
+ outb(du->dk_port+ wd_sdh, sdh);
+
+ /* Wait, to make sure drv 1 has completed diags */
+ if ( wdwait(du, 0, TIMEOUT) < 0)
+ goto nodevice;
+
+ /* Get status for drive 1 */
+ du->dk_error = inb(du->dk_port + wd_error);
+ /* printf("Error (drv 1) : %x\n", du->dk_error); */
+
+ if(du->dk_error != 0x01)
+ goto nodevice;
+ } else /* drive 0 fail */
+ goto nodevice;
+ }
+
+
+ free(du, M_TEMP);
+ return (IO_WDCSIZE);
+
+nodevice:
+ free(du, M_TEMP);
+ return (0);
+}
+
+/*
+ * Attach each drive if possible.
+ */
+static int
+wdattach(struct isa_device *dvp)
+{
+ int unit, lunit;
+ struct isa_device *wdup;
+ struct disk *du;
+
+ if (dvp->id_unit >= NWDC)
+ return (0);
+
+ for (wdup = isa_biotab_wdc; wdup->id_driver != 0; wdup++) {
+ if (wdup->id_iobase != dvp->id_iobase)
+ continue;
+ lunit = wdup->id_unit;
+ if (lunit >= NWD)
+ continue;
+ unit = wdup->id_physid;
+
+ du = malloc(sizeof *du, M_TEMP, M_NOWAIT);
+ if (du == NULL)
+ continue;
+ if (wddrives[lunit] != NULL)
+ panic("drive attached twice");
+ wddrives[lunit] = du;
+ bzero(du, sizeof *du);
+ du->dk_ctrlr = dvp->id_unit;
+ du->dk_unit = unit;
+ du->dk_lunit = lunit;
+ du->dk_port = dvp->id_iobase;
+
+ /*
+ * Print out description of drive.
+ * wdp_model can be [0..40] bytes, thus \0 can be missing so
+ * so copy it and add a null before printing.
+ */
+ if (wdgetctlr(du) == 0) {
+ char buf[sizeof(du->dk_params.wdp_model) + 1];
+ bcopy(du->dk_params.wdp_model, buf, sizeof(buf)-1);
+ buf[sizeof(buf)-1] = '\0';
+ printf("wdc%d: unit %d (wd%d): <%s>\n",
+ dvp->id_unit, unit, lunit, buf);
+ if (du->dk_params.wdp_heads == 0)
+ printf("wd%d: size unknown\n", lunit);
+ else
+ printf("wd%d: %luMB (%lu total sec), ",
+ lunit,
+ du->dk_dd.d_secperunit
+ * du->dk_dd.d_secsize / (1024 * 1024),
+ du->dk_dd.d_secperunit);
+ printf("%lu cyl, %lu head, %lu sec, bytes/sec %lu\n",
+ du->dk_dd.d_ncylinders,
+ du->dk_dd.d_ntracks,
+ du->dk_dd.d_nsectors,
+ du->dk_dd.d_secsize);
+ /*
+ * Start timeout routine for this drive.
+ * XXX timeout should be per controller.
+ */
+ wdtimeout((caddr_t)du);
+ } else {
+ free(du, M_TEMP);
+ wddrives[lunit] = NULL;
+ }
+ }
+
+ /*
+ * Discard any interrupts generated by wdgetctlr(). wdflushirq()
+ * doesn't work now because the ambient ipl is too high.
+ */
+ wdtab[dvp->id_unit].b_active = 2;
+
+ 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.
+ */
+void
+wdstrategy(register struct buf *bp)
+{
+ register struct buf *dp;
+ struct disk *du;
+ int lunit = wdunit(bp->b_dev);
+ int s;
+
+ /* valid unit, controller, and request? */
+ if (lunit >= NWD || bp->b_blkno < 0 || (du = wddrives[lunit]) == NULL) {
+
+ 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;
+ }
+
+ /*
+ * Do bounds checking, adjust transfer, and set b_cylin.
+ */
+ if (bounds_check_with_label(bp, wddospart(bp->b_dev)
+ ? &du->dk_dd2 : &du->dk_dd,
+ du->dk_wlabel) <= 0)
+ goto done;
+
+ /*
+ * Check for *any* block on this transfer being on the bad block list
+ * if it is, then flag the block as a transfer that requires
+ * bad block handling. Also, used as a hint for low level disksort
+ * clustering code to keep from coalescing a bad transfer into
+ * a normal transfer. Single block transfers for a large number of
+ * blocks associated with a cluster I/O are undersirable.
+ */
+ if( du->dk_flags & DKFL_BADSECT) {
+ int i;
+ int nsecs = howmany(bp->b_bcount, DEV_BSIZE);
+ int blkend = bp->b_pblkno + nsecs;
+ for(i=0;du->dk_badsect[i] != -1 && du->dk_badsect[i] < blkend;i++) {
+ if( du->dk_badsect[i] >= bp->b_pblkno) {
+ bp->b_flags |= B_BAD;
+ break;
+ }
+ }
+ }
+
+ /* queue transfer on drive, activate drive and controller if idle */
+ dp = &wdutab[lunit];
+ s = splbio();
+
+ /* cldisksort(dp, bp, 254*DEV_BSIZE); */
+ disksort(dp, bp);
+
+ if (dp->b_active == 0)
+ wdustart(du); /* start drive */
+
+ /* Pick up changes made by readdisklabel(). */
+ if (du->dk_flags & DKFL_LABELLING && du->dk_state > RECAL) {
+ wdsleep(du->dk_ctrlr, "wdlab");
+ du->dk_state = WANTOPEN;
+ }
+
+ if (wdtab[du->dk_ctrlr].b_active == 0)
+ wdstart(du->dk_ctrlr); /* start controller */
+ splx(s);
+ return;
+
+done:
+ s = splbio();
+ /* toss transfer, we're done early */
+ biodone(bp);
+ splx(s);
+}
+
+/*
+ * 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_lunit];
+ int ctrlr = du->dk_ctrlr;
+
+ /* unit already active? */
+ if (dp->b_active)
+ return;
+
+ /* anything to start? */
+ bp = dp->b_actf;
+ if (bp == NULL)
+ return;
+
+ dp->b_actf = bp->b_actf;
+ bp->b_actf = NULL;
+ /* link onto controller queue */
+ if (wdtab[ctrlr].b_actf == NULL) {
+ wdtab[ctrlr].b_actf = bp;
+ } else {
+ *wdtab[ctrlr].b_actb = bp;
+ }
+ wdtab[ctrlr].b_actb = &bp->b_actf;
+
+ /* 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(int ctrlr)
+{
+ register struct disk *du;
+ register struct buf *bp;
+ struct disklabel *lp;
+ struct buf *dp;
+ register struct bt_bad *bt_ptr;
+ long blknum, cylin, head, sector;
+ long secpertrk, secpercyl;
+ int lunit;
+
+loop:
+ /* is there a drive for the controller to do a transfer with? */
+ bp = wdtab[ctrlr].b_actf;
+ if (bp == NULL)
+ return;
+
+ /* obtain controller and drive information */
+ lunit = wdunit(bp->b_dev);
+ du = wddrives[lunit];
+
+ /* if not really a transfer, do control operations specially */
+ if (du->dk_state < OPEN) {
+ if (du->dk_state != WANTOPEN)
+ printf("wd%d: wdstart: weird dk_state %d\n",
+ du->dk_lunit, du->dk_state);
+ if (wdcontrol(bp) != 0)
+ printf("wd%d: wdstart: wdcontrol returned nonzero, state = %d\n",
+ du->dk_lunit, du->dk_state);
+ return;
+ }
+
+ /* calculate transfer details */
+ blknum = bp->b_pblkno + du->dk_skip;
+#ifdef WDDEBUG
+ if (du->dk_skip == 0)
+ printf("wd%d: wdstart: %s %d@%d; map ", lunit,
+ (bp->b_flags & B_READ) ? "read" : "write",
+ bp->b_bcount, blknum);
+ else
+ printf(" %d)%x", du->dk_skip, inb(du->dk_port + wd_altsts));
+#endif
+
+ lp = &du->dk_dd;
+ secpertrk = lp->d_nsectors;
+ secpercyl = lp->d_secpercyl;
+
+ if (du->dk_skip == 0) {
+ du->dk_bc = bp->b_bcount;
+ if (bp->b_flags & B_BAD) {
+ du->dk_flags |= DKFL_SINGLE;
+ }
+ }
+
+ if ((du->dk_flags & (DKFL_SINGLE|DKFL_BADSECT)) /* 19 Aug 92*/
+ == (DKFL_SINGLE|DKFL_BADSECT)) {
+ int i;
+
+ for(i=0;
+ du->dk_badsect[i] != -1 && du->dk_badsect[i] <= blknum;
+ i++) {
+
+ if( du->dk_badsect[i] == blknum) {
+ /*
+ * XXX the offset of the bad sector table ought
+ * to be stored in the in-core copy of the table.
+ */
+#define BAD144_PART 2 /* XXX scattered magic numbers */
+#define BSD_PART 0 /* XXX should be 2 but bad144.c uses 0 */
+ if (lp->d_partitions[BSD_PART].p_offset != 0)
+ blknum = lp->d_partitions[BAD144_PART].p_offset
+ + lp->d_partitions[BAD144_PART].p_size;
+ else
+ blknum = lp->d_secperunit;
+ blknum -= lp->d_nsectors + i + 1;
+
+ break;
+ }
+ }
+ }
+
+
+ cylin = blknum / secpercyl;
+ head = (blknum % secpercyl) / secpertrk;
+ sector = blknum % secpertrk;
+
+ wdtab[ctrlr].b_active = 1; /* mark controller active */
+
+ /* if starting a multisector transfer, or doing single transfers */
+ if (du->dk_skip == 0 || (du->dk_flags & DKFL_SINGLE)) {
+ u_int command;
+ u_int count;
+
+ if (wdtab[ctrlr].b_errcnt && (bp->b_flags & B_READ) == 0)
+ du->dk_bc += DEV_BSIZE;
+
+#ifdef B_FORMAT
+ if (bp->b_flags & B_FORMAT) {
+ command = WDCC_FORMAT;
+ count = lp->d_nsectors;
+ sector = lp->d_gap3 - 1; /* + 1 later */
+ } else
+#endif
+ {
+ if (du->dk_flags & DKFL_SINGLE)
+ count = 1;
+ else
+ count = howmany(du->dk_bc, DEV_BSIZE);
+ command = (bp->b_flags & B_READ)
+ ? WDCC_READ : WDCC_WRITE;
+ }
+
+ /*
+ * XXX this loop may never terminate. The code to handle
+ * counting down of retries and eventually failing the i/o
+ * is in wdintr() and we can't get there from here.
+ */
+ if (wdtest != 0) {
+ if (--wdtest == 0) {
+ wdtest = 100;
+ printf("dummy wdunwedge\n");
+ wdunwedge(du);
+ }
+ }
+ while (wdcommand(du, cylin, head, sector, count, command)
+ != 0) {
+ wderror(bp, du,
+ "wdstart: timeout waiting to give command");
+ wdunwedge(du);
+ }
+#ifdef WDDEBUG
+ printf("cylin %ld head %ld sector %ld addr %x sts %x\n",
+ cylin, head, sector,
+ (int)bp->b_un.b_addr + du->dk_skip * DEV_BSIZE,
+ inb(du->dk_port + wd_altsts));
+#endif
+ }
+
+ /*
+ * Schedule wdtimeout() to wake up after a few seconds. Retrying
+ * unmarked bad blocks can take 3 seconds! Then it is not good that
+ * we retry 5 times.
+ *
+ * XXX wdtimeout() doesn't increment the error count so we may loop
+ * forever. More seriously, the loop isn't forever but causes a
+ * crash.
+ *
+ * TODO fix b_resid bug elsewhere (fd.c....). Fix short but positive
+ * counts being discarded after there is an error (in physio I
+ * think). Discarding them would be OK if the (special) file offset
+ * was not advanced.
+ */
+ du->dk_timeout = 1 + 3;
+
+ /* 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 (wdwait(du, WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ, TIMEOUT) < 0) {
+ wderror(bp, du, "wdstart: timeout waiting for DRQ");
+ /*
+ * XXX what do we do now? If we've just issued the command,
+ * then we can treat this failure the same as a command
+ * failure. But if we are continuing a multi-sector write,
+ * the command was issued ages ago, so we can't simply
+ * restart it.
+ *
+ * XXX we waste a lot of time unnecessarily translating block
+ * numbers to cylin/head/sector for continued i/o's.
+ */
+ }
+
+ /* then send it! */
+ outsw(du->dk_port + wd_data,
+ (void *)((int)bp->b_un.b_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(int unit)
+{
+ register struct disk *du;
+ register struct buf *bp, *dp;
+
+ if (wdtab[unit].b_active == 2)
+ return; /* intr in wdflushirq() */
+ if (!wdtab[unit].b_active) {
+#ifndef LAPTOP
+ printf("wdc%d: extra interrupt\n", unit);
+#endif
+ return;
+ }
+
+ bp = wdtab[unit].b_actf;
+ du = wddrives[wdunit(bp->b_dev)];
+ dp = &wdutab[du->dk_lunit];
+
+ du->dk_timeout = 0;
+
+ if (wdwait(du, 0, TIMEOUT) < 0) {
+ wderror(bp, du, "wdintr: timeout waiting for status");
+ du->dk_status |= WDCS_ERR; /* XXX */
+ }
+
+ /* is it not a transfer, but a control operation? */
+ if (du->dk_state < OPEN) {
+ wdtab[unit].b_active = 0;
+ switch (wdcontrol(bp)) {
+ case 0:
+ return;
+ case 1:
+ wdstart(unit);
+ return;
+ case 2:
+ goto done;
+ }
+ }
+
+ /* have we an error? */
+ if (du->dk_status & (WDCS_ERR | WDCS_ECCCOR)) {
+oops:
+#ifdef WDDEBUG
+ wderror(bp, du, "wdintr");
+#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_error = EIO;
+ bp->b_flags |= B_ERROR;
+ goto done;
+ }
+#endif
+
+ /* error or error correction? */
+ if (du->dk_status & WDCS_ERR) {
+ if (++wdtab[unit].b_errcnt < RETRIES) {
+ wdtab[unit].b_active = 0;
+ } else {
+ wderror(bp, du, "hard error");
+ bp->b_error = EIO;
+ bp->b_flags |= B_ERROR; /* flag the error */
+ }
+ } else
+ wderror(bp, du, "soft ecc");
+ }
+
+ /*
+ * If this was a successful read operation, fetch the data.
+ */
+ if (((bp->b_flags & (B_READ | B_ERROR)) == B_READ)
+ && wdtab[unit].b_active) {
+ int chk, dummy;
+
+ chk = min(DEV_BSIZE / sizeof(short), du->dk_bc / sizeof(short));
+
+ /* ready to receive data? */
+ if ((du->dk_status & (WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ))
+ != (WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ))
+ wderror(bp, du, "wdintr: read intr arrived early");
+ if (wdwait(du, WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ, TIMEOUT) != 0) {
+ wderror(bp, du, "wdintr: read error detected late");
+ goto oops;
+ }
+
+ /* suck in data */
+ insw(du->dk_port + wd_data,
+ (void *)((int)bp->b_un.b_addr + du->dk_skip * DEV_BSIZE),
+ chk);
+ du->dk_bc -= chk * sizeof(short);
+
+ /* XXX for obsolete fractional sector reads. */
+ while (chk++ < DEV_BSIZE / sizeof(short))
+ insw(du->dk_port + wd_data, &dummy, 1);
+ }
+
+ wdxfer[du->dk_lunit]++;
+outt:
+ if (wdtab[unit].b_active) {
+ if ((bp->b_flags & B_ERROR) == 0) {
+ du->dk_skip++; /* add to successful sectors */
+ if (wdtab[unit].b_errcnt)
+ wderror(bp, du, "soft error");
+ wdtab[unit].b_errcnt = 0;
+
+ /* see if more to transfer */
+ if (du->dk_bc > 0 && (du->dk_flags & DKFL_ERROR) == 0) {
+ wdtab[unit].b_active = 0;
+ wdstart(unit);
+ 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;
+ wdtab[unit].b_active = 0;
+ wdstart(unit);
+ return; /* redo xfer sector by sector */
+ }
+ }
+
+done: ;
+ /* done with this transfer, with or without error */
+ du->dk_flags &= ~DKFL_SINGLE;
+ wdtab[unit].b_actf = bp->b_actf;
+ wdtab[unit].b_errcnt = 0;
+ bp->b_resid = bp->b_bcount - du->dk_skip * DEV_BSIZE;
+ dp->b_active = 0;
+ dp->b_errcnt = 0;
+ du->dk_skip = 0;
+ biodone(bp);
+ }
+
+ /* controller idle */
+ wdtab[unit].b_active = 0;
+
+ /* anything more on drive queue? */
+ wdustart(du);
+ /* anything more for controller to do? */
+ if (wdtab[unit].b_actf)
+ wdstart(unit);
+}
+
+/*
+ * Initialize a drive.
+ */
+int
+wdopen(dev_t dev, int flags, int fmt, struct proc *p)
+{
+ register unsigned int lunit;
+ register struct disk *du;
+ int part = wdpart(dev), mask = 1 << part;
+ struct partition *pp;
+ char *msg;
+ struct disklabel save_label;
+
+ lunit = wdunit(dev);
+ if (lunit >= NWD)
+ return (ENXIO);
+ du = wddrives[lunit];
+ if (du == NULL)
+ return (ENXIO);
+
+ /* Finish flushing IRQs left over from wdattach(). */
+ if (wdtab[du->dk_ctrlr].b_active == 2)
+ wdtab[du->dk_ctrlr].b_active = 0;
+
+ /*
+ * That's all for valid DOS partitions. We don't need a BSD label.
+ * The openmask is only used for checking BSD partitions so we don't
+ * need to maintain it.
+ */
+ if (wddospart(dev)) {
+ /* XXX we do need a disklabel for now. */
+ if ((du->dk_flags & DKFL_BSDLABEL) == 0)
+ return (ENXIO);
+
+ return (part > NDOSPART ? ENXIO : 0);
+ }
+
+ while (du->dk_flags & DKFL_LABELLING)
+ tsleep((caddr_t)&du->dk_flags, PZERO - 1, "wdopen", 1);
+ if ((du->dk_flags & DKFL_BSDLABEL) == 0) {
+ /*
+ * wdtab[ctrlr].b_active != 0 implies
+ * wdutab[lunit].b_actf == NULL (?)
+ * so the following guards most things (until the next i/o).
+ * It doesn't guard against a new i/o starting and being
+ * affected by the label being changed. Sigh.
+ */
+ wdsleep(du->dk_ctrlr, "wdopn1");
+
+ du->dk_flags |= DKFL_LABELLING | DKFL_WRITEPROT;
+ du->dk_state = WANTOPEN;
+ wdutab[lunit].b_actf = NULL;
+
+ /*
+ * Read label using WDRAW partition.
+ *
+ * If the drive has an MBR, then the current geometry (from
+ * wdgetctlr()) is used to read it; then the BIOS/DOS
+ * geometry is inferred and used to read the label off the
+ * 'c' partition. Otherwise the label is read using the
+ * current geometry. The label gives the final geometry.
+ * If bad sector handling is enabled, then this geometry
+ * is used to read the bad sector table. The geometry
+ * changes occur inside readdisklabel() and are propagated
+ * to the driver by resetting the state machine.
+ */
+ save_label = du->dk_dd;
+ du->dk_dd.d_partitions[WDRAW].p_offset = 0;
+ du->dk_dd.d_partitions[WDRAW].p_size = 0x7fffffff;/* XXX */
+#define WDSTRATEGY ((int (*)(struct buf *)) wdstrategy) /* XXX */
+ msg = readdisklabel(makewddev(major(dev), lunit, WDRAW),
+ WDSTRATEGY, &du->dk_dd,
+ du->dk_dospartitions, &du->dk_bad);
+/*
+ msg = readdisklabel(makewddev(major(dev), lunit, WDRAW),
+ WDSTRATEGY, &du->dk_dd);
+*/
+ du->dk_flags &= ~DKFL_LABELLING;
+ if (msg != NULL) {
+ du->dk_dd = save_label;
+ log(LOG_WARNING, "wd%d: cannot find label (%s)\n",
+ lunit, msg);
+ if (part != WDRAW)
+ return (EINVAL); /* XXX needs translation */
+ } else {
+ int dospart;
+ unsigned long newsize, offset, size;
+
+ du->dk_flags |= DKFL_BSDLABEL;
+ du->dk_flags &= ~DKFL_WRITEPROT;
+ if (du->dk_dd.d_flags & D_BADSECT) {
+ du->dk_flags |= DKFL_BADSECT;
+ bad144intern(du);
+ }
+
+ /*
+ * Force WDRAW partition to be the whole disk.
+ */
+ offset = du->dk_dd.d_partitions[WDRAW].p_offset;
+ if (offset != 0) {
+ printf(
+ "wd%d: changing offset of 'd' partition from %lu to 0\n",
+ du->dk_lunit, offset);
+ du->dk_dd.d_partitions[WDRAW].p_offset = 0;
+ }
+ size = du->dk_dd.d_partitions[WDRAW].p_size;
+ newsize = du->dk_dd.d_secperunit; /* XXX */
+ if (size != newsize) {
+ printf(
+ "wd%d: changing size of 'd' partition from %lu to %lu\n",
+ du->dk_lunit, size, newsize);
+ du->dk_dd.d_partitions[WDRAW].p_size = newsize;
+ }
+
+ /*
+ * Convert DOS partition data to a label.
+ */
+ du->dk_dd2 = du->dk_dd;
+ bzero(du->dk_dd2.d_partitions,
+ sizeof du->dk_dd2.d_partitions);
+ du->dk_dd2.d_partitions[0].p_size
+ = du->dk_dd.d_secperunit; /* XXX */
+ for (dospart = 1; dospart <= NDOSPART; dospart++) {
+ du->dk_dd2.d_partitions[dospart].p_offset =
+ du->dk_dospartitions[dospart - 1].dp_start;
+ du->dk_dd2.d_partitions[dospart].p_size =
+ du->dk_dospartitions[dospart - 1].dp_size;
+ }
+ }
+
+ /* Pick up changes made by readdisklabel(). */
+ wdsleep(du->dk_ctrlr, "wdopn2");
+ du->dk_state = WANTOPEN;
+ }
+
+ /*
+ * 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 != 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 == WDRAW)
+ continue;
+ if (du->dk_openpart
+ & (1 << (pp - du->dk_dd.d_partitions)))
+ log(LOG_WARNING,
+ "wd%d%c: overlaps open partition (%c)\n",
+ lunit, part + 'a',
+ pp - du->dk_dd.d_partitions + 'a');
+ }
+ }
+ if (part >= du->dk_dd.d_npartitions && part != WDRAW)
+ return (ENXIO);
+
+ switch (fmt) {
+ case S_IFCHR:
+ du->dk_copenpart |= mask;
+ break;
+ case S_IFBLK:
+ du->dk_bopenpart |= mask;
+ break;
+ }
+ du->dk_openpart = du->dk_copenpart | du->dk_bopenpart;
+
+ 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, 2 if error.
+ */
+static int
+wdcontrol(register struct buf *bp)
+{
+ register struct disk *du;
+ int ctrlr;
+
+ du = wddrives[wdunit(bp->b_dev)];
+ ctrlr = du->dk_ctrlr;
+
+ switch (du->dk_state) {
+ case WANTOPEN:
+tryagainrecal:
+ wdtab[ctrlr].b_active = 1;
+ if (wdcommand(du, 0, 0, 0, 0, WDCC_RESTORE | WD_STEP) != 0) {
+ wderror(bp, du, "wdcontrol: wdcommand failed");
+ goto maybe_retry;
+ }
+ du->dk_state = RECAL;
+ return (0);
+ case RECAL:
+ if (du->dk_status & WDCS_ERR || wdsetctlr(du) != 0) {
+ wderror(bp, du, "wdcontrol: recal failed");
+maybe_retry:
+ if (du->dk_status & WDCS_ERR)
+ wdunwedge(du);
+ du->dk_state = WANTOPEN;
+ if (++wdtab[ctrlr].b_errcnt < RETRIES)
+ goto tryagainrecal;
+ bp->b_error = ENXIO; /* XXX needs translation */
+ bp->b_flags |= B_ERROR;
+ return (2);
+ }
+ wdtab[ctrlr].b_errcnt = 0;
+ du->dk_state = OPEN;
+ /*
+ * The rest of the initialization can be done by normal
+ * means.
+ */
+ return (1);
+ }
+ panic("wdcontrol");
+ return (2);
+}
+
+/*
+ * Wait uninterruptibly until controller is not busy, then send it a command.
+ * The wait usually terminates immediately because we waited for the previous
+ * command to terminate.
+ */
+static int
+wdcommand(struct disk *du, u_int cylinder, u_int head, u_int sector,
+ u_int count, u_int command)
+{
+ u_int wdc;
+
+ if (wdwait(du, 0, TIMEOUT) < 0)
+ return (1);
+ wdc = du->dk_port;
+ outb(wdc + wd_precomp, du->dk_dd.d_precompcyl / 4);
+ outb(wdc + wd_cyl_lo, cylinder);
+ outb(wdc + wd_cyl_hi, cylinder >> 8);
+ outb(wdc + wd_sdh, WDSD_IBM | (du->dk_unit << 4) | head);
+ outb(wdc + wd_sector, sector + 1);
+ outb(wdc + wd_seccnt, count);
+ if (wdwait(du, command == WDCC_DIAGNOSE || command == WDCC_IDC
+ ? 0 : WDCS_READY, TIMEOUT) < 0)
+ return (1);
+ outb(wdc + wd_command, command);
+ return (0);
+}
+
+/*
+ * issue IDC to drive to tell it just what geometry it is to be.
+ */
+static int
+wdsetctlr(struct disk *du)
+{
+ int error = 0;
+#ifdef WDDEBUG
+ printf("wd(%d,%d): wdsetctlr: C %lu H %lu S %lu\n",
+ du->dk_ctrlr, du->dk_unit,
+ du->dk_dd.d_ncylinders, du->dk_dd.d_ntracks,
+ du->dk_dd.d_nsectors);
+#endif
+ if (du->dk_dd.d_ntracks == 0 || du->dk_dd.d_ntracks > 16) {
+ struct wdparams *wp;
+
+ printf("wd%d: can't handle %lu heads from partition table ",
+ du->dk_lunit, du->dk_dd.d_ntracks);
+ /* obtain parameters */
+ wp = &du->dk_params;
+ if (wp->wdp_heads > 0 && wp->wdp_heads <= 16) {
+ printf("(controller value %lu restored)\n",
+ wp->wdp_heads);
+ du->dk_dd.d_ntracks = wp->wdp_heads;
+ }
+ else {
+ printf("(truncating to 16)\n");
+ du->dk_dd.d_ntracks = 16;
+ }
+ }
+
+ if (du->dk_dd.d_nsectors == 0 || du->dk_dd.d_nsectors > 255) {
+ printf("wd%d: cannot handle %lu sectors (max 255)\n",
+ du->dk_lunit, du->dk_dd.d_nsectors);
+ error = 1;
+ }
+ if (error) {
+ wdtab[du->dk_ctrlr].b_errcnt += RETRIES;
+ return (1);
+ }
+ if (wdcommand(du, du->dk_dd.d_ncylinders, du->dk_dd.d_ntracks - 1, 0,
+ du->dk_dd.d_nsectors, WDCC_IDC) != 0
+ || wdwait(du, WDCS_READY, TIMEOUT) < 0) {
+ wderror((struct buf *)NULL, du, "wdsetctlr failed");
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Wait until driver is inactive, then set up controller.
+ */
+static int
+wdwsetctlr(struct disk *du)
+{
+ int stat;
+ int x;
+
+ wdsleep(du->dk_ctrlr, "wdwset");
+ x = splbio();
+ stat = wdsetctlr(du);
+ wdflushirq(du, x);
+ splx(x);
+ return (stat);
+}
+
+/*
+ * issue READP to drive to ask it what it is.
+ */
+static int
+wdgetctlr(struct disk *du)
+{
+ int i;
+ char tb[DEV_BSIZE];
+ struct wdparams *wp;
+
+ if (wdcommand(du, 0, 0, 0, 0, WDCC_READP) != 0
+ || wdwait(du, WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ, TIMEOUT) != 0) {
+ /* XXX need to check error status after final transfer. */
+ /*
+ * Old drives don't support WDCC_READP. Try a seek to 0.
+ * Some IDE controllers return trash if there is no drive
+ * attached, so first test that the drive can be selected.
+ * This also avoids long waits for nonexistent drives.
+ */
+ if (wdwait(du, 0, TIMEOUT) < 0)
+ return (1);
+ outb(du->dk_port + wd_sdh, WDSD_IBM | (du->dk_unit << 4));
+ DELAY(5000); /* usually unnecessary; drive select is fast */
+ if ((inb(du->dk_port + wd_status) & (WDCS_BUSY | WDCS_READY))
+ != WDCS_READY
+ || wdcommand(du, 0, 0, 0, 0, WDCC_RESTORE | WD_STEP) != 0
+ || wdwait(du, WDCS_READY | WDCS_SEEKCMPLT, TIMEOUT) != 0)
+ return (1);
+
+ /*
+ * Fake minimal drive geometry for reading the MBR.
+ * readdisklabel() may enlarge it to read the label and the
+ * bad sector table.
+ */
+ du->dk_dd.d_secsize = DEV_BSIZE;
+ du->dk_dd.d_nsectors = 17;
+ du->dk_dd.d_ntracks = 1;
+ du->dk_dd.d_ncylinders = 1;
+ du->dk_dd.d_secpercyl = 17;
+ du->dk_dd.d_secperunit = 17;
+
+ /*
+ * Fake maximal drive size for writing the label.
+ */
+ du->dk_dd.d_partitions[WDRAW].p_size = 64 * 16 * 1024;
+
+ /*
+ * Fake some more of the label for printing by disklabel(1)
+ * in case there is no real label.
+ */
+ du->dk_dd.d_type = DTYPE_ST506;
+ du->dk_dd.d_subtype |= DSTYPE_GEOMETRY;
+ strncpy(du->dk_dd.d_typename, "Fake geometry",
+ sizeof du->dk_dd.d_typename);
+
+ /* Fake the model name for printing by wdattach(). */
+ strncpy(du->dk_params.wdp_model, "unknown",
+ sizeof du->dk_params.wdp_model);
+
+ return (0);
+ }
+
+ /* obtain parameters */
+ wp = &du->dk_params;
+ insw(du->dk_port + 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);
+ }
+ /*
+ * Clean up the wdp_model by converting nulls to spaces, and
+ * then removing the trailing spaces.
+ */
+ for (i=0; i < sizeof(wp->wdp_model); i++) {
+ if (wp->wdp_model[i] == '\0') {
+ wp->wdp_model[i] = ' ';
+ }
+ }
+ for (i=sizeof(wp->wdp_model)-1; i>=0 && wp->wdp_model[i]==' '; i--) {
+ wp->wdp_model[i] = '\0';
+ }
+
+#ifdef WDDEBUG
+ printf(
+"\nwd(%d,%d): wdgetctlr: gc %x cyl %d trk %d sec %d type %d sz %d model %s\n",
+ du->dk_ctrlr, du->dk_unit, wp->wdp_config,
+ wp->wdp_fixedcyl + wp->wdp_removcyl, wp->wdp_heads,
+ wp->wdp_sectors, wp->wdp_cntype, wp->wdp_cnsbsz,
+ wp->wdp_model);
+#endif
+
+ /* update disklabel given drive information */
+ du->dk_dd.d_secsize = DEV_BSIZE;
+ 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[WDRAW].p_size = du->dk_dd.d_secperunit
+ = du->dk_dd.d_secpercyl * du->dk_dd.d_ncylinders;
+ /* 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;
+
+ return (0);
+}
+
+
+/* ARGSUSED */
+int
+wdclose(dev_t dev, int flags, int fmt)
+{
+ register struct disk *du;
+ int part = wdpart(dev), mask = 1 << part;
+
+ if (wddospart(dev))
+ return (0);
+
+ du = wddrives[wdunit(dev)];
+
+ switch (fmt) {
+ case S_IFCHR:
+ du->dk_copenpart &= ~mask;
+ break;
+ case S_IFBLK:
+ du->dk_bopenpart &= ~mask;
+ break;
+ }
+ du->dk_openpart = du->dk_copenpart | du->dk_bopenpart;
+
+ return (0);
+}
+
+int
+wdioctl(dev_t dev, int cmd, caddr_t addr, int flag)
+{
+ int lunit = wdunit(dev);
+ register struct disk *du;
+ int error = 0;
+#ifdef notyet
+ struct uio auio;
+ struct iovec aiov;
+#endif
+
+ du = wddrives[lunit];
+
+ 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:
+ if (wddospart(dev))
+ return (EINVAL);
+ ((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,
+#if 0
+ /*
+ * XXX setdisklabel() uses the
+ * openmask to allow it to reject
+ * changing open partitions. Why
+ * are we pretending nothing is
+ * open?
+ */
+ du->dk_flags & DKFL_BSDLABEL
+ ? du->dk_openpart :
+#endif
+ 0,
+ du->dk_dospartitions);
+ if (error == 0) {
+ du->dk_flags |= DKFL_BSDLABEL;
+ wdwsetctlr(du); /* XXX - check */
+ }
+ 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,
+#if 0
+ du->dk_flags & DKFL_BSDLABEL
+ ? du->dk_openpart :
+#endif
+ 0,
+ du->dk_dospartitions)) == 0) {
+ int wlab;
+
+ du->dk_flags |= DKFL_BSDLABEL;
+ wdwsetctlr(du); /* XXX - check */
+
+ /* 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 /* XXX the 386BSD interface is different */
+ error = physio(wdformat, &rwdbuf[lunit], 0, 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 lunit = wdunit(dev), part = wdpart(dev), val;
+ struct disk *du;
+ int size;
+
+ if (lunit >= NWD || wddospart(dev) || (du = wddrives[lunit]) == NULL) {
+ return (-1);
+ }
+ val = 0;
+ if (du->dk_state == CLOSED) {
+ val = wdopen(makewddev(major(dev), lunit, WDRAW),
+ FREAD, S_IFBLK, 0);
+ }
+ if (val != 0 || du->dk_flags & DKFL_WRITEPROT) {
+ return (-1);
+ }
+ size = ((int)du->dk_dd.d_partitions[part].p_size);
+ return size;
+}
+
+extern char *ptvmmap; /* poor name! */
+
+/*
+ * Dump core after a system crash.
+ */
+int
+wddump(dev_t dev)
+{
+ register struct disk *du;
+ register struct bt_bad *bt_ptr;
+ struct disklabel *lp;
+ long num; /* number of sectors to write */
+ int lunit, part;
+ long blkoff, blknum;
+ long blkchk, blkcnt, blknext;
+ long cylin, head, sector;
+ long secpertrk, secpercyl, nblocks;
+ char *addr;
+ extern int Maxmem;
+ static int wddoingadump = 0;
+ extern caddr_t CADDR1;
+
+ /* Toss any characters present prior to dump. */
+ while (sgetc(1))
+ ;
+
+ /* Check for acceptable device. */
+ /* XXX should reset to maybe allow du->dk_state < OPEN. */
+ lunit = wdunit(dev); /* eventually support floppies? */
+ part = wdpart(dev);
+ if (lunit >= NWD || wddospart(dev) || (du = wddrives[lunit]) == NULL
+ || du->dk_state < OPEN || du->dk_flags & DKFL_WRITEPROT)
+ return (ENXIO);
+
+ /* Size of memory to dump, in disk sectors. */
+ num = (u_long)Maxmem * NBPG / du->dk_dd.d_secsize;
+
+ 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;
+
+#if 0
+ pg("part %x, nblocks %d, dumplo %d num %d\n",
+ part, nblocks, dumplo, num);
+#endif
+
+ /* Check transfer bounds against partition size. */
+ if (dumplo < 0 || dumplo + num > nblocks)
+ return (EINVAL);
+
+ /* Check if we are being called recursively. */
+ if (wddoingadump)
+ return (EFAULT);
+
+#if 0
+ /* Mark controller active for if we panic during the dump. */
+ wdtab[du->dk_ctrlr].b_active = 1;
+#endif
+ wddoingadump = 1;
+
+ /* Recalibrate the drive. */
+ DELAY(5); /* ATA spec XXX NOT */
+ if (wdcommand(du, 0, 0, 0, 0, WDCC_RESTORE | WD_STEP) != 0
+ || wdwait(du, WDCS_READY | WDCS_SEEKCMPLT, TIMEOUT) != 0
+ || wdsetctlr(du) != 0) {
+ wderror((struct buf *)NULL, du, "wddump: recalibrate failed");
+ return (EIO);
+ }
+
+ du->dk_flags |= DKFL_SINGLE;
+ addr = (char *) 0;
+ blknum = dumplo + blkoff;
+ while (num > 0) {
+ blkcnt = num;
+ if (blkcnt > MAXTRANSFER)
+ blkcnt = MAXTRANSFER;
+ /* Keep transfer within current cylinder. */
+ if ((blknum + blkcnt - 1) / secpercyl != blknum / secpercyl)
+ blkcnt = secpercyl - (blknum % secpercyl);
+ blknext = blknum + blkcnt;
+
+ /*
+ * See if one of the sectors is in the bad sector list
+ * (if we have one). If the first sector is bad, then
+ * reduce the transfer to this one bad sector; if another
+ * sector is bad, then reduce reduce the transfer to
+ * avoid any bad sectors.
+ */
+ if ((du->dk_flags & (DKFL_SINGLE | DKFL_BADSECT))
+ == (DKFL_SINGLE | DKFL_BADSECT))
+ for (blkchk = blknum; blkchk < blknum + blkcnt; blkchk++) {
+ cylin = blkchk / secpercyl;
+ head = (blkchk % secpercyl) / secpertrk;
+ sector = blkchk % secpertrk;
+ for (bt_ptr = du->dk_bad.bt_bad;
+ bt_ptr->bt_cyl != BAD144_NO_CYL; 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. */
+ blkcnt = blkchk - blknum;
+ if (blkcnt > 0) {
+ blknext = blknum + blkcnt;
+ goto out;
+ }
+ blkcnt = 1;
+ blknext = blknum + blkcnt;
+ /*
+ * Found bad block. Calculate new block number.
+ * 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.
+ */
+ /* XXX as usual. */
+#ifdef WDDEBUG
+ printf("--- badblock code -> Old = %ld; ",
+ blknum);
+#endif
+ lp = &du->dk_dd;
+ if (lp->d_partitions[BSD_PART].p_offset != 0)
+ blknum = lp->d_partitions[BAD144_PART]
+ .p_offset
+ + lp->d_partitions[BAD144_PART]
+ .p_size;
+ else
+ blknum = lp->d_secperunit;
+ blknum -= lp->d_nsectors
+ + (bt_ptr - du->dk_bad.bt_bad) + 1;
+#ifdef WDDEBUG
+ printf("new = %ld\n", blknum);
+#endif
+ break;
+ }
+ }
+ }
+out:
+
+ /* Compute disk address. */
+ cylin = blknum / secpercyl;
+ head = (blknum % secpercyl) / secpertrk;
+ sector = blknum % secpertrk;
+
+#if 0
+ /* Let's just talk about this first... */
+ pg("cylin l%d head %ld sector %ld addr 0x%x count %ld",
+ cylin, head, sector, addr, blkcnt);
+#endif
+
+ /* Do the write. */
+ if (wdcommand(du, cylin, head, sector, blkcnt, WDCC_WRITE)
+ != 0) {
+ wderror((struct buf *)NULL, du,
+ "wddump: timeout waiting to to give command");
+ return (EIO);
+ }
+ while (blkcnt != 0) {
+ pmap_enter(kernel_pmap, (vm_offset_t)CADDR1, trunc_page(addr),
+ VM_PROT_READ, TRUE);
+
+ /* Ready to send data? */
+ DELAY(5); /* ATA spec */
+ if (wdwait(du, WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ, TIMEOUT)
+ < 0) {
+ wderror((struct buf *)NULL, du,
+ "wddump: timeout waiting for DRQ");
+ return (EIO);
+ }
+ outsw(du->dk_port + wd_data,
+ CADDR1 + ((int)addr & (NBPG - 1)),
+ DEV_BSIZE / sizeof(short));
+ addr += DEV_BSIZE;
+ if ((unsigned)addr % (1024 * 1024) == 0)
+ printf("%ld ", num / (1024 * 1024 / DEV_BSIZE));
+ num--;
+ blkcnt--;
+ }
+
+ /* Wait for completion. */
+ DELAY(5); /* ATA spec XXX NOT */
+ if (wdwait(du, WDCS_READY | WDCS_SEEKCMPLT, TIMEOUT) < 0) {
+ wderror((struct buf *)NULL, du,
+ "wddump: timeout waiting for status");
+ return (EIO);
+ }
+
+ /* Check final status. */
+ if (du->dk_status
+ & (WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ | WDCS_ERR)
+ != (WDCS_READY | WDCS_SEEKCMPLT)) {
+ wderror((struct buf *)NULL, du,
+ "wddump: extra DRQ, or error");
+ return (EIO);
+ }
+
+ /* Update block count. */
+ blknum = blknext;
+
+ /* Operator aborting dump? */
+ if (sgetc(1) & 0xff) /* EWS: A hack to work with syscons */
+ return (EINTR);
+ }
+ return (0);
+}
+
+static void
+wderror(struct buf *bp, struct disk *du, char *mesg)
+{
+ if (bp == NULL)
+ printf("wd%d: %s:\n", du->dk_lunit, mesg);
+ else
+ diskerr(bp, "wd", mesg, LOG_PRINTF, du->dk_skip, &du->dk_dd);
+ printf("wd%d: status %b error %b\n", du->dk_lunit,
+ du->dk_status, WDCS_BITS, du->dk_error, WDERR_BITS);
+}
+
+/*
+ * Discard any interrupts that were latched by the interrupt system while
+ * we were doing polled i/o.
+ */
+static void
+wdflushirq(struct disk *du, int old_ipl)
+{
+ wdtab[du->dk_ctrlr].b_active = 2;
+ splx(old_ipl);
+ (void)splbio();
+ wdtab[du->dk_ctrlr].b_active = 0;
+}
+
+/*
+ * Reset the controller.
+ */
+static int
+wdreset(struct disk *du)
+{
+ int wdc;
+
+ wdc = du->dk_port;
+ (void)wdwait(du, 0, TIMEOUT);
+ outb(wdc + wd_ctlr, WDCTL_IDS | WDCTL_RST);
+ DELAY(10 * 1000);
+ outb(wdc + wd_ctlr, WDCTL_IDS);
+ if (wdwait(du, WDCS_READY | WDCS_SEEKCMPLT, TIMEOUT) != 0
+ || (du->dk_error = inb(wdc + wd_error)) != 0x01)
+ return (1);
+ outb(wdc + wd_ctlr, WDCTL_4BIT);
+ return (0);
+}
+
+/*
+ * Sleep until driver is inactive.
+ * This is used only for avoiding rare race conditions, so it is unimportant
+ * that the sleep may be far too short or too long.
+ */
+static void
+wdsleep(int ctrlr, char *wmesg)
+{
+ while (wdtab[ctrlr].b_active)
+ tsleep((caddr_t)&wdtab[ctrlr].b_active, PZERO - 1, wmesg, 1);
+}
+
+static void
+wdtimeout(caddr_t cdu)
+{
+ struct disk *du;
+ int x;
+
+ du = (struct disk *)cdu;
+ x = splbio();
+ if (du->dk_timeout != 0 && --du->dk_timeout == 0) {
+ wderror((struct buf *)NULL, du, "interrupt timeout");
+ wdunwedge(du);
+ wdflushirq(du, x);
+ du->dk_skip = 0;
+ du->dk_flags |= DKFL_SINGLE;
+ wdstart(du->dk_ctrlr);
+ }
+ timeout((timeout_func_t)wdtimeout, cdu, hz);
+ splx(x);
+}
+
+/*
+ * Reset the controller after it has become wedged. This is different from
+ * wdreset() so that wdreset() can be used in the probe and so that this
+ * can restore the geometry .
+ */
+static int
+wdunwedge(struct disk *du)
+{
+ struct disk *du1;
+ int lunit;
+
+ /* Schedule other drives for recalibration. */
+ for (lunit = 0; lunit < NWD; lunit++)
+ if ((du1 = wddrives[lunit]) != NULL && du1 != du
+ && du1->dk_ctrlr == du->dk_ctrlr
+ && du1->dk_state > WANTOPEN)
+ du1->dk_state = WANTOPEN;
+
+ DELAY(RECOVERYTIME);
+ if (wdreset(du) == 0) {
+ /*
+ * XXX - recalibrate current drive now because some callers
+ * aren't prepared to have its state change.
+ */
+ if (wdcommand(du, 0, 0, 0, 0, WDCC_RESTORE | WD_STEP) == 0
+ && wdwait(du, WDCS_READY | WDCS_SEEKCMPLT, TIMEOUT) == 0
+ && wdsetctlr(du) == 0)
+ return (0);
+ }
+ wderror((struct buf *)NULL, du, "wdunwedge failed");
+ return (1);
+}
+
+/*
+ * Wait uninterruptibly until controller is not busy and either certain
+ * status bits are set or an error has occurred.
+ * The wait is usually short unless it is for the controller to process
+ * an entire critical command.
+ * Return 1 for (possibly stale) controller errors, -1 for timeout errors,
+ * or 0 for no errors.
+ * Return controller status in du->dk_status and, if there was a controller
+ * error, return the error code in du->dk_error.
+ */
+#ifdef WD_COUNT_RETRIES
+static int min_retries[NWDC];
+#endif
+
+static int
+wdwait(struct disk *du, u_char bits_wanted, int timeout)
+{
+ int wdc;
+ u_char status;
+
+#define POLLING 1000
+
+ wdc = du->dk_port;
+ timeout += POLLING;
+ do {
+#ifdef WD_COUNT_RETRIES
+ if (min_retries[du->dk_ctrlr] > timeout
+ || min_retries[du->dk_ctrlr] == 0)
+ min_retries[du->dk_ctrlr] = timeout;
+#endif
+ DELAY(5); /* ATA spec XXX NOT */
+ du->dk_status = status = inb(wdc + wd_status);
+ if (!(status & WDCS_BUSY)) {
+ if (status & WDCS_ERR) {
+ du->dk_error = inb(wdc + wd_error);
+ /*
+ * We once returned here. This is wrong
+ * because the error bit is apparently only
+ * valid after the controller has interrupted
+ * (e.g., the error bit is stale when we wait
+ * for DRQ for writes). So we can't depend
+ * on the error bit at all when polling for
+ * command completion.
+ */
+ }
+ if ((status & bits_wanted) == bits_wanted)
+ return (status & WDCS_ERR);
+ }
+ if (timeout < TIMEOUT)
+ /*
+ * Switch to a polling rate of about 1 KHz so that
+ * the timeout is almost machine-independent. The
+ * controller is taking a long time to respond, so
+ * an extra msec won't matter.
+ */
+ DELAY(1000);
+ } while (--timeout != 0);
+ return (-1);
+}
+
+/*
+ * Internalize the bad sector table.
+ */
+void bad144intern(struct disk *du) {
+ int i;
+ if (du->dk_flags & DKFL_BADSECT) {
+ for (i = 0; i < 127; i++) {
+ du->dk_badsect[i] = -1;
+ }
+ for (i = 0; i < 126; i++) {
+ if (du->dk_bad.bt_bad[i].bt_cyl == 0xffff) {
+ break;
+ } else {
+ du->dk_badsect[i] =
+ du->dk_bad.bt_bad[i].bt_cyl * du->dk_dd.d_secpercyl +
+ (du->dk_bad.bt_bad[i].bt_trksec >> 8) * du->dk_dd.d_nsectors
++
+ (du->dk_bad.bt_bad[i].bt_trksec & 0x00ff);
+ }
+ }
+ }
+}
+
+#endif /* NWDC > 0 */
OpenPOWER on IntegriCloud