summaryrefslogtreecommitdiff
path: root/sys/dev/isa/wdc.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/isa/wdc.c')
-rw-r--r--sys/dev/isa/wdc.c1686
1 files changed, 1686 insertions, 0 deletions
diff --git a/sys/dev/isa/wdc.c b/sys/dev/isa/wdc.c
new file mode 100644
index 00000000000..c0e79d93e16
--- /dev/null
+++ b/sys/dev/isa/wdc.c
@@ -0,0 +1,1686 @@
+/* $NetBSD: wd.c,v 1.150 1996/05/12 23:54:03 mycroft Exp $ */
+
+/*
+ * Copyright (c) 1994, 1995 Charles M. Hannum. All rights reserved.
+ *
+ * DMA and multi-sector PIO handling are derived from code contributed by
+ * Onno van der Linden.
+ *
+ * Atapi support added by Manuel Bouyer.
+ *
+ * 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 Charles M. Hannum.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
+ */
+
+#undef ATAPI_DEBUG_WDC
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/conf.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/buf.h>
+#include <sys/uio.h>
+#include <sys/malloc.h>
+#include <sys/device.h>
+#include <sys/disklabel.h>
+#include <sys/disk.h>
+#include <sys/syslog.h>
+#include <sys/proc.h>
+
+#include <vm/vm.h>
+
+#include <machine/cpu.h>
+#include <machine/intr.h>
+#include <machine/pio.h>
+
+#include <dev/isa/isavar.h>
+#include <dev/isa/isadmavar.h>
+#include <dev/isa/wdreg.h>
+#include <dev/isa/wdlink.h>
+
+#include <dev/atapi/atapilink.h>
+
+#define WAITTIME (10 * hz) /* time to wait for a completion */
+ /* this is a lot for hard drives, but not for cdroms */
+#define RECOVERYTIME hz/2
+#define WDCDELAY 100
+#define WDCNDELAY 100000 /* delay = 100us; so 10s for a controller state change */
+#if 0
+/* If you enable this, it will report any delays more than 100us * N long. */
+#define WDCNDELAY_DEBUG 50
+#endif
+
+#define WDIORETRIES 3 /* number of retries before giving up */
+
+#define WDPART(dev) DISKPART(dev)
+
+LIST_HEAD(xfer_free_list, wdc_xfer) xfer_free_list;
+
+int wdcprobe __P((struct device *, void *, void *));
+void wdcattach __P((struct device *, struct device *, void *));
+int wdcintr __P((void *));
+
+struct cfattach wdc_ca = {
+ sizeof(struct wdc_softc), wdcprobe, wdcattach
+};
+
+struct cfdriver wdc_cd = {
+ NULL, "wdc", DV_DULL
+};
+
+int wdc_ata_intr __P((struct wdc_softc *,struct wdc_xfer *));
+void wdcstart __P((struct wdc_softc *));
+void wdc_ata_start __P((struct wdc_softc *,struct wdc_xfer *));
+void wdc_atapi_start __P((struct wdc_softc *,struct wdc_xfer *));
+int wdcreset __P((struct wdc_softc *));
+void wdcrestart __P((void *arg));
+void wdcunwedge __P((struct wdc_softc *));
+void wdctimeout __P((void *arg));
+int wdccontrol __P((struct wd_link *));
+void wdc_ata_done __P((struct wdc_softc *, struct wdc_xfer *));
+void wdc_free_xfer __P((struct wdc_xfer *));
+void wdcerror __P((struct wdc_softc*, char *));
+void wdcbit_bucket __P(( struct wdc_softc *, int));
+int wdc_atapi_intr __P((struct wdc_softc *, struct wdc_xfer *));
+void wdc_atapi_done __P((struct wdc_softc *, struct wdc_xfer *));
+
+#ifdef ATAPI_DEBUG
+static int wdc_nxfer;
+#endif
+
+#ifdef WDDEBUG
+#define WDDEBUG_PRINT(args) printf args
+#else
+#define WDDEBUG_PRINT(args)
+#endif
+
+int
+wdcprobe(parent, match, aux)
+ struct device *parent;
+ void *match, *aux;
+{
+ struct wdc_softc *wdc = match;
+ struct isa_attach_args *ia = aux;
+ int iobase;
+
+ wdc->sc_iobase = iobase = ia->ia_iobase;
+
+ /* Check if we have registers that work. */
+ outb(iobase+wd_error, 0x5a); /* Error register not writable, */
+ outb(iobase+wd_cyl_lo, 0xa5); /* but all of cyllo are. */
+ if (inb(iobase+wd_error) == 0x5a || inb(iobase+wd_cyl_lo) != 0xa5) {
+ /*
+ * Test for a controller with no IDE master, just one
+ * ATAPI device. Select drive 1, and try again.
+ */
+ outb(iobase+wd_sdh, WDSD_IBM | 0x10);
+ outb(iobase+wd_error, 0x5a);
+ outb(iobase+wd_cyl_lo, 0xa5);
+ if (inb(iobase+wd_error) == 0x5a || inb(iobase+wd_cyl_lo)
+ != 0xa5)
+ return 0;
+ wdc->sc_flags |= WDCF_ONESLAVE;
+ }
+
+ if (wdcreset(wdc) != 0) {
+ delay(500000);
+ if (wdcreset(wdc) != 0)
+ return 0;
+ }
+
+ /* Select drive 0 or ATAPI slave device */
+ if (wdc->sc_flags & WDCF_ONESLAVE)
+ outb(iobase+wd_sdh, WDSD_IBM | 0x10);
+ else
+ outb(iobase+wd_sdh, WDSD_IBM);
+
+ /* Wait for controller to become ready. */
+ if (wait_for_unbusy(wdc) < 0)
+ return 0;
+
+ /* Start drive diagnostics. */
+ outb(iobase+wd_command, WDCC_DIAGNOSE);
+
+ /* Wait for command to complete. */
+ if (wait_for_unbusy(wdc) < 0)
+ return 0;
+
+ ia->ia_iosize = 8;
+ ia->ia_msize = 0;
+ return 1;
+}
+
+int
+wdprint(aux, wdc)
+ void *aux;
+ char *wdc;
+{
+ struct wd_link *d_link = aux;
+
+ if (!wdc)
+ printf(" drive %d", d_link->sc_drive);
+ return QUIET;
+}
+
+void
+wdcattach(parent, self, aux)
+ struct device *parent, *self;
+ void *aux;
+{
+ struct wdc_softc *wdc = (void *)self;
+ struct isa_attach_args *ia = aux;
+ int drive;
+
+ TAILQ_INIT(&wdc->sc_xfer);
+ wdc->sc_drq = ia->ia_drq;
+
+ printf("\n");
+
+ wdc->sc_ih = isa_intr_establish(ia->ia_ic, ia->ia_irq, IST_EDGE,
+ IPL_BIO, wdcintr, wdc, wdc->sc_dev.dv_xname);
+
+ wdc->ctlr_link.flags = 0;
+#ifdef ATAPI_DEBUG
+ wdc_nxfer = 0;
+#endif
+
+ /*
+ * Attach standard IDE/ESDI/etc. disks to the controller.
+ */
+ for (drive = 0; drive < 2; drive++) {
+ /* controller active while autoconf */
+ wdc->sc_flags |= WDCF_ACTIVE;
+
+ if (wdccommandshort(wdc, drive, WDCC_RECAL) != 0 ||
+ wait_for_ready(wdc) != 0) {
+ wdc->d_link[drive] = NULL;
+ wdc->sc_flags &= ~WDCF_ACTIVE;
+ } else {
+ wdc->sc_flags &= ~WDCF_ACTIVE;
+ wdc->d_link[drive] = malloc(sizeof(struct wd_link),
+ M_DEVBUF, M_NOWAIT);
+ if (wdc->d_link[drive] == NULL) {
+ printf("%s: can't allocate link for drive %d\n",
+ self->dv_xname, drive);
+ continue;
+ }
+ bzero(wdc->d_link[drive],sizeof(struct wd_link));
+ wdc->d_link[drive]->type = DRIVE;
+ wdc->d_link[drive]->wdc_softc =(caddr_t) wdc;
+ wdc->d_link[drive]->ctlr_link = &(wdc->ctlr_link);
+ wdc->d_link[drive]->sc_drive = drive;
+ if (wdc->sc_drq != DRQUNK)
+ wdc->d_link[drive]->sc_mode = WDM_DMA;
+ else
+ wdc->d_link[drive]->sc_mode = 0;
+
+ (void)config_found(self, (void *)wdc->d_link[drive],
+ wdprint);
+ }
+ }
+
+ /*
+ * Attach an ATAPI bus, if configured.
+ */
+ wdc->ab_link = malloc(sizeof(struct bus_link), M_DEVBUF, M_NOWAIT);
+ if (wdc->ab_link == NULL) {
+ printf("%s: can't allocate ATAPI link\n", self->dv_xname);
+ return;
+ }
+ bzero(wdc->ab_link,sizeof(struct bus_link));
+ wdc->ab_link->type = BUS;
+ wdc->ab_link->wdc_softc = (caddr_t)wdc;
+ wdc->ab_link->ctlr_link = &(wdc->ctlr_link);
+ wdc->ab_link->ctrl = self->dv_unit;
+ (void)config_found(self, (void *)wdc->ab_link, NULL);
+}
+
+/*
+ * Start I/O on a controller. This does the calculation, and starts a read or
+ * write operation. Called to from wdstart() to start a transfer, from
+ * wdcintr() to continue a multi-sector transfer or start the next transfer, or
+ * wdcrestart() after recovering from an error.
+ */
+void
+wdcstart(wdc)
+ struct wdc_softc *wdc;
+{
+ struct wdc_xfer *xfer;
+
+ if ((wdc->sc_flags & WDCF_ACTIVE) != 0 ) {
+ WDDEBUG_PRINT(("wdcstart: already active\n"));
+ return; /* controller aleady active */
+ }
+#ifdef DIAGNOSTIC
+ if ((wdc->sc_flags & WDCF_IRQ_WAIT) != 0)
+ panic("wdcstart: controller waiting for irq\n");
+#endif
+ /*
+ * XXX
+ * This is a kluge. See comments in wd_get_parms().
+ */
+ if ((wdc->sc_flags & WDCF_WANTED) != 0) {
+#ifdef ATAPI_DEBUG_WDC
+ printf("WDCF_WANTED\n");
+#endif
+ wdc->sc_flags &= ~WDCF_WANTED;
+ wakeup(wdc);
+ return;
+ }
+ /* is there a xfer ? */
+ xfer = wdc->sc_xfer.tqh_first;
+ if (xfer == NULL) {
+#ifdef ATAPI_DEBUG2
+ printf("wdcstart: null xfer\n");
+#endif
+ return;
+ }
+ wdc->sc_flags |= WDCF_ACTIVE;
+ if (xfer->c_flags & C_ATAPI) {
+#ifdef ATAPI_DEBUG_WDC
+ printf("wdcstart: atapi\n");
+#endif
+ wdc_atapi_start(wdc,xfer);
+ } else {
+ wdc_ata_start(wdc,xfer);
+ }
+}
+
+
+void
+wdc_ata_start(wdc, xfer)
+ struct wdc_softc *wdc;
+ struct wdc_xfer *xfer;
+{
+ struct wd_link *d_link;
+ struct buf *bp = xfer->c_bp;
+ int nblks;
+
+ d_link=xfer->d_link;
+
+loop:
+
+ if (wdc->sc_errors >= WDIORETRIES) {
+ wderror(d_link, bp, "hard error");
+ xfer->c_flags |= C_ERROR;
+ wdc_ata_done(wdc, xfer);
+ return;
+ }
+
+ /* Do control operations specially. */
+ if (d_link->sc_state < OPEN) {
+ /*
+ * Actually, we want to be careful not to mess with the control
+ * state if the device is currently busy, but we can assume
+ * that we never get to this point if that's the case.
+ */
+ if (wdccontrol(d_link) == 0) {
+ /* The drive is busy. Wait. */
+ return;
+ }
+ }
+
+ /*
+ * WDCF_ERROR is set by wdcunwedge() and wdcintr() when an error is
+ * encountered. If we are in multi-sector mode, then we switch to
+ * single-sector mode and retry the operation from the start.
+ */
+ if (wdc->sc_flags & WDCF_ERROR) {
+ wdc->sc_flags &= ~WDCF_ERROR;
+ if ((wdc->sc_flags & WDCF_SINGLE) == 0) {
+ wdc->sc_flags |= WDCF_SINGLE;
+ xfer->c_skip = 0;
+ }
+ }
+
+
+ /* When starting a transfer... */
+ if (xfer->c_skip == 0) {
+ daddr_t blkno;
+
+ WDDEBUG_PRINT(("\n%s: wdc_ata_start %s %d@%d; map ",
+ wdc->sc_dev.dv_xname,
+ (xfer->c_flags & B_READ) ? "read" : "write",
+ xfer->c_bcount, xfer->c_blkno));
+
+ blkno = xfer->c_blkno+xfer->c_p_offset;
+ xfer->c_blkno = blkno / (d_link->sc_lp->d_secsize / DEV_BSIZE);
+ } else {
+ WDDEBUG_PRINT((" %d)%x", xfer->c_skip,
+ inb(wdc->sc_iobase + wd_altsts)));
+ }
+
+ /*
+ * When starting a multi-sector transfer, or doing single-sector
+ * transfers...
+ */
+ if (xfer->c_skip == 0 || (wdc->sc_flags & WDCF_SINGLE) != 0 ||
+ d_link->sc_mode == WDM_DMA) {
+ daddr_t blkno = xfer->c_blkno;
+ long cylin, head, sector;
+ int command;
+
+ if ((wdc->sc_flags & WDCF_SINGLE) != 0)
+ nblks = 1;
+ else if (d_link->sc_mode != WDM_DMA)
+ nblks = xfer->c_bcount / d_link->sc_lp->d_secsize;
+ else
+ nblks =
+ min(xfer->c_bcount / d_link->sc_lp->d_secsize, 8);
+
+ /* Check for bad sectors and adjust transfer, if necessary. */
+ if ((d_link->sc_lp->d_flags & D_BADSECT) != 0
+#ifdef B_FORMAT
+ && (bp->b_flags & B_FORMAT) == 0
+#endif
+ ) {
+ long blkdiff;
+ int i;
+
+ for (i = 0;
+ (blkdiff = d_link->sc_badsect[i]) != -1; i++) {
+ blkdiff -= blkno;
+ if (blkdiff < 0)
+ continue;
+ if (blkdiff == 0) {
+ /* Replace current block of transfer. */
+ blkno =
+ d_link->sc_lp->d_secperunit -
+ d_link->sc_lp->d_nsectors - i - 1;
+ }
+ if (blkdiff < nblks) {
+ /* Bad block inside transfer. */
+ wdc->sc_flags |= WDCF_SINGLE;
+ nblks = 1;
+ }
+ break;
+ }
+ /* Tranfer is okay now. */
+ }
+
+ if ((d_link->sc_params.wdp_capabilities & WD_CAP_LBA) != 0) {
+ sector = (blkno >> 0) & 0xff;
+ cylin = (blkno >> 8) & 0xffff;
+ head = (blkno >> 24) & 0xf;
+ head |= WDSD_LBA;
+ } else {
+ sector = blkno % d_link->sc_lp->d_nsectors;
+ sector++; /* Sectors begin with 1, not 0. */
+ blkno /= d_link->sc_lp->d_nsectors;
+ head = blkno % d_link->sc_lp->d_ntracks;
+ blkno /= d_link->sc_lp->d_ntracks;
+ cylin = blkno;
+ head |= WDSD_CHS;
+ }
+
+ if (d_link->sc_mode == WDM_PIOSINGLE ||
+ (wdc->sc_flags & WDCF_SINGLE) != 0)
+ xfer->c_nblks = 1;
+ else if (d_link->sc_mode == WDM_PIOMULTI)
+ xfer->c_nblks = min(nblks, d_link->sc_multiple);
+ else
+ xfer->c_nblks = nblks;
+ xfer->c_nbytes = xfer->c_nblks * d_link->sc_lp->d_secsize;
+
+#ifdef B_FORMAT
+ if (bp->b_flags & B_FORMAT) {
+ sector = d_link->sc_lp->d_gap3;
+ nblks = d_link->sc_lp->d_nsectors;
+ command = WDCC_FORMAT;
+ } else
+#endif
+ switch (d_link->sc_mode) {
+ case WDM_DMA:
+ command = (xfer->c_flags & B_READ) ?
+ WDCC_READDMA : WDCC_WRITEDMA;
+ /*
+ * Start the DMA channel and bounce the buffer if
+ * necessary.
+ */
+ isa_dmastart(xfer->c_flags & B_READ ?
+ DMAMODE_READ : DMAMODE_WRITE,
+ xfer->databuf + xfer->c_skip,
+ xfer->c_nbytes, wdc->sc_drq);
+ break;
+
+ case WDM_PIOMULTI:
+ command = (xfer->c_flags & B_READ) ?
+ WDCC_READMULTI : WDCC_WRITEMULTI;
+ break;
+
+ case WDM_PIOSINGLE:
+ command = (xfer->c_flags & B_READ) ?
+ WDCC_READ : WDCC_WRITE;
+ break;
+
+ default:
+#ifdef DIAGNOSTIC
+ panic("bad wd mode");
+#endif
+ return;
+ }
+
+ /* Initiate command! */
+ if (wdccommand(d_link, command, d_link->sc_drive,
+ cylin, head, sector, nblks) != 0) {
+ wderror(d_link, NULL,
+ "wdc_ata_start: timeout waiting for unbusy");
+ wdcunwedge(wdc);
+ return;
+ }
+
+ WDDEBUG_PRINT(("sector %d cylin %d head %d addr %x sts %x\n",
+ sector, cylin, head, xfer->databuf,
+ inb(wdc->sc_iobase + wd_altsts)));
+
+ } else if (xfer->c_nblks > 1) {
+ /* The number of blocks in the last stretch may be smaller. */
+ nblks = xfer->c_bcount / d_link->sc_lp->d_secsize;
+ if (xfer->c_nblks > nblks) {
+ xfer->c_nblks = nblks;
+ xfer->c_nbytes = xfer->c_bcount;
+ }
+ }
+
+ /* If this was a write and not using DMA, push the data. */
+ if (d_link->sc_mode != WDM_DMA &&
+ (xfer->c_flags & (B_READ|B_WRITE)) == B_WRITE) {
+ if (wait_for_drq(wdc) < 0) {
+ wderror(d_link, NULL,
+ "wdc_ata_start: timeout waiting for drq");
+ wdcunwedge(wdc);
+ return;
+ }
+
+ /* Push out data. */
+ if ((d_link->sc_flags & WDF_32BIT) == 0)
+ outsw(wdc->sc_iobase + wd_data,
+ xfer->databuf + xfer->c_skip,
+ xfer->c_nbytes >> 1);
+ else
+ outsl(wdc->sc_iobase + wd_data,
+ xfer->databuf + xfer->c_skip,
+ xfer->c_nbytes >> 2);
+ }
+
+ wdc->sc_flags |= WDCF_IRQ_WAIT;
+ WDDEBUG_PRINT(("wdc_ata_start: timeout "));
+ timeout(wdctimeout, wdc, WAITTIME);
+ WDDEBUG_PRINT(("done\n"));
+}
+
+void
+wdc_atapi_start(wdc, xfer)
+ struct wdc_softc *wdc;
+ struct wdc_xfer *xfer;
+{
+ struct atapi_command_packet *acp = xfer->atapi_cmd;
+
+#ifdef ATAPI_DEBUG_WDC
+ printf("wdc_atapi_start, acp flags %lx\n",acp->flags);
+#endif
+ if (wdc->sc_errors >= WDIORETRIES) {
+ acp->status |= ERROR;
+ acp->error = inb (wdc->sc_iobase + wd_error);
+ wdc_atapi_done(wdc, xfer);
+ return;
+ }
+ if (wait_for_unbusy(wdc) != 0) {
+ if ((wdc->sc_status & WDCS_ERR) == 0) {
+ printf("wdc_atapi_start: not ready, st = %02x\n",
+ wdc->sc_status);
+ acp->status = ERROR;
+ return;
+ }
+ }
+
+ if (wdccommand((struct wd_link*)xfer->d_link, ATAPI_PACKET_COMMAND,
+ acp->drive, acp->data_size, 0, 0, 0) != 0) {
+ printf("wdc_atapi_start: can't send atapi paket command\n");
+ acp->status = ERROR;
+ wdc->sc_flags |= WDCF_IRQ_WAIT;
+ return;
+ }
+ if ((acp->flags & 0x0300) != ACAP_DRQ_INTR) {
+ int i, phase;
+ for (i=20000; i>0; --i) {
+ phase = (inb(wdc->sc_iobase + wd_ireason) &
+ (WDCI_CMD | WDCI_IN)) |
+ (inb(wdc->sc_iobase + wd_status) & WDCS_DRQ);
+ if (phase == PHASE_CMDOUT)
+ break;
+ delay(10);
+ }
+ if (phase != PHASE_CMDOUT ) {
+ printf("wdc_atapi_start: timout waiting PHASE_CMDOUT");
+ acp->status = ERROR;
+ wdc_atapi_done(wdc, xfer);
+ return;
+ }
+ outsw(wdc->sc_iobase + wd_data, acp->command,
+ acp->command_size / sizeof(short));
+ }
+ wdc->sc_flags |= WDCF_IRQ_WAIT;
+
+#ifdef ATAPI_DEBUG2
+ printf("wdc_atapi_start: timeout\n");
+#endif
+ timeout(wdctimeout, wdc, WAITTIME);
+ return;
+}
+
+
+/*
+ * Interrupt routine for the controller. Acknowledge the interrupt, check for
+ * errors on the current operation, mark it done if necessary, and start the
+ * next request. Also check for a partially done transfer, and continue with
+ * the next chunk if so.
+ */
+int
+wdcintr(arg)
+ void *arg;
+{
+ struct wdc_softc *wdc = arg;
+ struct wdc_xfer *xfer;
+
+ if ((wdc->sc_flags & WDCF_IRQ_WAIT) == 0) {
+ /* Clear the pending interrupt and abort. */
+ u_char s = inb(wdc->sc_iobase+wd_status);
+ u_char e = inb(wdc->sc_iobase+wd_error);
+ u_char i = inb(wdc->sc_iobase+wd_seccnt);
+
+#ifdef ATAPI_DEBUG_WDC
+ printf("wdcintr: inactive controller, "
+ "punting st=%02x er=%02x irr=%02x\n", s, e, i);
+#endif
+
+ if (s & WDCS_DRQ) {
+ int len = inb (wdc->sc_iobase + wd_cyl_lo) +
+ 256 * inb (wdc->sc_iobase + wd_cyl_hi);
+#ifdef ATAPI_DEBUG_WDC
+ printf ("wdcintr: clearing up %d bytes\n", len);
+#endif
+ wdcbit_bucket (wdc, len);
+ }
+ return 0;
+ }
+
+ WDDEBUG_PRINT(("wdcintr\n"));
+
+ wdc->sc_flags &= ~WDCF_IRQ_WAIT;
+ xfer = wdc->sc_xfer.tqh_first;
+ if (xfer->c_flags & C_ATAPI) {
+ (void) wdc_atapi_intr(wdc,xfer);
+ return 0;
+ } else {
+ return wdc_ata_intr(wdc,xfer);
+ }
+}
+
+
+int
+wdc_ata_intr(wdc,xfer)
+ struct wdc_softc *wdc;
+ struct wdc_xfer *xfer;
+{
+ struct wd_link *d_link;
+
+ d_link = xfer->d_link;
+
+ if (wait_for_unbusy(wdc) < 0) {
+ wdcerror(wdc, "wdcintr: timeout waiting for unbusy");
+ wdc->sc_status |= WDCS_ERR; /* XXX */
+ }
+
+ untimeout(wdctimeout, wdc);
+
+ /* Is it not a transfer, but a control operation? */
+ if (d_link->sc_state < OPEN) {
+ if (wdccontrol(d_link) == 0) {
+ /* The drive is busy. Wait. */
+ return 1;
+ }
+ WDDEBUG_PRINT(("wdc_ata_start from wdc_ata_intr(open) flags %d\n",
+ dc->sc_flags));
+ wdc_ata_start(wdc,xfer);
+ return 1;
+ }
+
+ /* Turn off the DMA channel and unbounce the buffer. */
+ if (d_link->sc_mode == WDM_DMA)
+ isa_dmadone(xfer->c_flags & B_READ ?
+ DMAMODE_READ : DMAMODE_WRITE,
+ xfer->databuf + xfer->c_skip, xfer->c_nbytes,
+ wdc->sc_drq);
+
+ /* Have we an error? */
+ if (wdc->sc_status & WDCS_ERR) {
+#ifdef WDDEBUG
+ wderror(d_link, NULL, "wdc_ata_start");
+#endif
+ if ((wdc->sc_flags & WDCF_SINGLE) == 0) {
+ wdc->sc_flags |= WDCF_ERROR;
+ goto restart;
+ }
+
+#ifdef B_FORMAT
+ if (bp->b_flags & B_FORMAT)
+ goto bad;
+#endif
+
+ wdcunwedge(wdc);
+ if (++wdc->sc_errors < WDIORETRIES)
+ return 1;
+
+ wderror(d_link, xfer->c_bp, "hard error");
+
+#ifdef B_FORMAT
+ bad:
+#endif
+ xfer->c_flags |= C_ERROR;
+ goto done;
+ }
+
+ /* If this was a read and not using DMA, fetch the data. */
+ if (d_link->sc_mode != WDM_DMA &&
+ (xfer->c_flags & (B_READ|B_WRITE)) == B_READ) {
+ if ((wdc->sc_status & (WDCS_DRDY | WDCS_DSC | WDCS_DRQ))
+ != (WDCS_DRDY | WDCS_DSC | WDCS_DRQ)) {
+ wderror(d_link, NULL, "wdcintr: read intr before drq");
+ wdcunwedge(wdc);
+ return 1;
+ }
+
+ /* Pull in data. */
+ if ((d_link->sc_flags & WDF_32BIT) == 0)
+ insw(wdc->sc_iobase+wd_data,
+ xfer->databuf + xfer->c_skip, xfer->c_nbytes >> 1);
+ else
+ insl(wdc->sc_iobase+wd_data,
+ xfer->databuf + xfer->c_skip, xfer->c_nbytes >> 2);
+ }
+
+ /* If we encountered any abnormalities, flag it as a soft error. */
+ if (wdc->sc_errors > 0 ||
+ (wdc->sc_status & WDCS_CORR) != 0) {
+ wderror(d_link, xfer->c_bp, "soft error (corrected)");
+ wdc->sc_errors = 0;
+ }
+
+ /* Adjust pointers for the next block, if any. */
+ xfer->c_blkno += xfer->c_nblks;
+ xfer->c_skip += xfer->c_nbytes;
+ xfer->c_bcount -= xfer->c_nbytes;
+
+ /* See if this transfer is complete. */
+ if (xfer->c_bcount > 0)
+ goto restart;
+
+done:
+ /* Done with this transfer, with or without error. */
+ wdc_ata_done(wdc, xfer);
+ return 0;
+
+restart:
+ /* Start the next operation */
+ WDDEBUG_PRINT(("wdc_ata_start from wdcintr flags %d\n",
+ wdc->sc_flags));
+ wdc_ata_start(wdc, xfer);
+
+ return 1;
+}
+
+int
+wdcreset(wdc)
+ struct wdc_softc *wdc;
+{
+ int iobase = wdc->sc_iobase;
+
+ /* Reset the device. */
+ outb(iobase+wd_ctlr, WDCTL_RST | WDCTL_IDS);
+ delay(1000);
+ outb(iobase+wd_ctlr, WDCTL_IDS);
+ delay(1000);
+ (void) inb(iobase+wd_error);
+ outb(iobase+wd_ctlr, WDCTL_4BIT);
+
+ if (wait_for_unbusy(wdc) < 0) {
+ printf("%s: reset failed\n", wdc->sc_dev.dv_xname);
+ return 1;
+ }
+
+ return 0;
+}
+
+void
+wdcrestart(arg)
+ void *arg;
+{
+ struct wdc_softc *wdc = arg;
+ int s;
+
+ s = splbio();
+ wdcstart(wdc);
+ splx(s);
+}
+
+/*
+ * Unwedge the controller after an unexpected error. We do this by resetting
+ * it, marking all drives for recalibration, and stalling the queue for a short
+ * period to give the reset time to finish.
+ * NOTE: We use a timeout here, so this routine must not be called during
+ * autoconfig or dump.
+ */
+void
+wdcunwedge(wdc)
+ struct wdc_softc *wdc;
+{
+ int unit;
+
+#ifdef ATAPI_DEBUG
+ printf("wdcunwedge\n");
+#endif
+
+ untimeout(wdctimeout, wdc);
+ wdc->sc_flags &= ~WDCF_IRQ_WAIT;
+ (void) wdcreset(wdc);
+
+ /* Schedule recalibrate for all drives on this controller. */
+ for (unit = 0; unit < 2; unit++) {
+ if (!wdc->d_link[unit]) continue;
+ if (wdc->d_link[unit]->sc_state > RECAL)
+ wdc->d_link[unit]->sc_state = RECAL;
+ }
+
+ wdc->sc_flags |= WDCF_ERROR;
+ ++wdc->sc_errors;
+
+ /* Wake up in a little bit and restart the operation. */
+ WDDEBUG_PRINT(("wdcrestart from wdcunwedge\n"));
+ wdc->sc_flags &= ~WDCF_ACTIVE;
+ timeout(wdcrestart, wdc, RECOVERYTIME);
+}
+
+int
+wdcwait(wdc, mask)
+ struct wdc_softc *wdc;
+ int mask;
+{
+ int iobase = wdc->sc_iobase;
+ int timeout = 0;
+ u_char status;
+#ifdef WDCNDELAY_DEBUG
+ extern int cold;
+#endif
+
+ WDDEBUG_PRINT(("wdcwait\n"));
+
+ for (;;) {
+ wdc->sc_status = status = inb(iobase+wd_status);
+ /*
+ * XXX
+ * If a single slave ATAPI device is attached, it may
+ * have released the bus. Select it and try again.
+ */
+ if (status == 0xff && wdc->sc_flags & WDCF_ONESLAVE) {
+ outb(iobase+wd_sdh, WDSD_IBM | 0x10);
+ wdc->sc_status = status = inb(iobase+wd_status);
+ }
+ if ((status & WDCS_BSY) == 0 && (status & mask) == mask)
+ break;
+ if (++timeout > WDCNDELAY) {
+#ifdef ATAPI_DEBUG2
+ printf("wdcwait: timeout, status %x\n", status);
+#endif
+ return -1;
+ }
+ delay(WDCDELAY);
+ }
+ if (status & WDCS_ERR) {
+ wdc->sc_error = inb(iobase+wd_error);
+ return WDCS_ERR;
+ }
+#ifdef WDCNDELAY_DEBUG
+ /* After autoconfig, there should be no long delays. */
+ if (!cold && timeout > WDCNDELAY_DEBUG) {
+ struct wdc_xfer *xfer = wdc->sc_xfer.tqh_first;
+ if (xfer == NULL)
+ printf("%s: warning: busy-wait took %dus\n",
+ wdc->sc_dev.dv_xname, WDCDELAY * timeout);
+ else
+ printf("%s(%s): warning: busy-wait took %dus\n",
+ wdc->sc_dev.dv_xname,
+ ((struct device*)xfer->d_link->wd_softc)->dv_xname,
+ WDCDELAY * timeout);
+ }
+#endif
+ return 0;
+}
+
+void
+wdctimeout(arg)
+ void *arg;
+{
+ struct wdc_softc *wdc = (struct wdc_softc *)arg;
+ int s;
+
+ WDDEBUG_PRINT(("wdctimeout\n"));
+
+ s = splbio();
+ if ((wdc->sc_flags & WDCF_IRQ_WAIT) != 0) {
+ wdc->sc_flags &= ~WDCF_IRQ_WAIT;
+ wdcerror(wdc, "lost interrupt");
+ wdcunwedge(wdc);
+ } else
+ wdcerror(wdc, "missing untimeout");
+ splx(s);
+}
+
+/*
+ * Wait for the drive to become ready and send a command.
+ * Return -1 if busy for too long or 0 otherwise.
+ * Assumes interrupts are blocked.
+ */
+int
+wdccommand(d_link, command, drive, cylin, head, sector, count)
+ struct wd_link *d_link;
+ int command;
+ int drive, cylin, head, sector, count;
+{
+ struct wdc_softc *wdc = (void*)d_link->wdc_softc;
+ int iobase = wdc->sc_iobase;
+ int stat;
+
+ WDDEBUG_PRINT(("wdccommand drive %d\n", drive));
+
+#if defined(DIAGNOSTIC) && defined(WDCDEBUG)
+ if ((wdc->sc_flags & WDCF_ACTIVE) == 0)
+ printf("wdccommand: controler not active (drive %d)\n", drive);
+#endif
+
+ /* Select drive, head, and addressing mode. */
+ outb(iobase+wd_sdh, WDSD_IBM | (drive << 4) | head);
+
+ /* Wait for it to become ready to accept a command. */
+ if (command == WDCC_IDP || d_link->type == BUS)
+ stat = wait_for_unbusy(wdc);
+ else
+ stat = wdcwait(wdc, WDCS_DRDY);
+
+ if (stat < 0) {
+#ifdef ATAPI_DEBUG
+ printf("wdcommand: xfer failed (wait_for_unbusy) status %d\n",
+ stat);
+#endif
+ return -1;
+ }
+
+ /* Load parameters. */
+ if (d_link->type == DRIVE && d_link->sc_lp->d_type == DTYPE_ST506)
+ outb(iobase + wd_precomp, d_link->sc_lp->d_precompcyl / 4);
+ else
+ outb(iobase + wd_features, 0);
+ outb(iobase + wd_cyl_lo, cylin);
+ outb(iobase + wd_cyl_hi, cylin >> 8);
+ outb(iobase + wd_sector, sector);
+ outb(iobase + wd_seccnt, count);
+
+ /* Send command. */
+ outb(iobase + wd_command, command);
+
+ return 0;
+}
+
+/*
+ * Simplified version of wdccommand().
+ */
+int
+wdccommandshort(wdc, drive, command)
+ struct wdc_softc *wdc;
+ int drive;
+ int command;
+{
+ int iobase = wdc->sc_iobase;
+
+ WDDEBUG_PRINT(("wdccommandshort\n"));
+
+#if defined(DIAGNOSTIC) && defined(WDCDEBUG)
+ if ((wdc->sc_flags & WDCF_ACTIVE) == 0)
+ printf("wdccommandshort: controler not active (drive %d)\n",
+ drive);
+#endif
+
+ /* Select drive. */
+ outb(iobase + wd_sdh, WDSD_IBM | (drive << 4));
+
+ if (wdcwait(wdc, WDCS_DRDY) < 0)
+ return -1;
+
+ outb(iobase + wd_command, command);
+
+ return 0;
+}
+
+void
+wdc_ata_done(wdc, xfer)
+ struct wdc_softc *wdc;
+ struct wdc_xfer *xfer;
+{
+ struct buf *bp = xfer->c_bp;
+ struct wd_link *d_link = xfer->d_link;
+ int s;
+
+ WDDEBUG_PRINT(("wdc_ata_done\n"));
+
+ /* remove this command from xfer queue */
+ s = splbio();
+ TAILQ_REMOVE(&wdc->sc_xfer, xfer, c_xferchain);
+ wdc->sc_flags &= ~(WDCF_SINGLE | WDCF_ERROR | WDCF_ACTIVE);
+ wdc->sc_errors = 0;
+ if (bp) {
+ if (xfer->c_flags & C_ERROR) {
+ bp->b_flags |= B_ERROR;
+ bp->b_error = EIO;
+ }
+ bp->b_resid = xfer->c_bcount;
+ wddone(d_link, bp);
+ biodone(bp);
+ } else {
+ wakeup(xfer->databuf);
+ }
+ xfer->c_skip = 0;
+ wdc_free_xfer(xfer);
+ d_link->openings++;
+ wdstart((void*)d_link->wd_softc);
+ WDDEBUG_PRINT(("wdcstart from wdc_ata_done, flags %d\n",
+ wdc->sc_flags));
+ wdcstart(wdc);
+ splx(s);
+}
+
+void
+wdc_exec_xfer(d_link, xfer)
+ struct wd_link *d_link;
+ struct wdc_xfer *xfer;
+{
+ struct wdc_softc *wdc=(struct wdc_softc *)d_link->wdc_softc;
+ int s;
+
+ WDDEBUG_PRINT(("wdc_exec_xfer\n"));
+
+ s = splbio();
+
+ /* insert at the end of command list */
+ TAILQ_INSERT_TAIL(&wdc->sc_xfer,xfer , c_xferchain)
+ WDDEBUG_PRINT(("wdcstart from wdc_exec_xfer, flags %d\n",
+ wdc->sc_flags));
+ wdcstart(wdc);
+ splx(s);
+}
+
+struct wdc_xfer *
+wdc_get_xfer(c_link,flags)
+ struct wdc_link *c_link;
+ int flags;
+{
+ struct wdc_xfer *xfer;
+ int s;
+
+ s = splbio();
+ if (xfer = xfer_free_list.lh_first) {
+ LIST_REMOVE(xfer, free_list);
+ splx(s);
+#ifdef DIAGNOSTIC
+ if ((xfer->c_flags & C_INUSE) != 0)
+ panic("wdc_get_xfer: xfer already in use\n");
+#endif
+ } else {
+ splx(s);
+#ifdef ATAPI_DEBUG
+ printf("wdc:making xfer %d\n",wdc_nxfer);
+#endif
+ xfer = malloc(sizeof(*xfer), M_DEVBUF,
+ ((flags & IDE_NOSLEEP) != 0 ? M_NOWAIT : M_WAITOK));
+ if (xfer == NULL)
+ return 0;
+
+#ifdef DIAGNOSTIC
+ xfer->c_flags &= ~C_INUSE;
+#endif
+#ifdef ATAPI_DEBUG
+ wdc_nxfer++;
+#endif
+ }
+#ifdef DIAGNOSTIC
+ if ((xfer->c_flags & C_INUSE) != 0)
+ panic("wdc_get_xfer: xfer already in use\n");
+#endif
+ bzero(xfer,sizeof(struct wdc_xfer));
+ xfer->c_flags = C_INUSE;
+ xfer->c_link = c_link;
+ return xfer;
+}
+
+void
+wdc_free_xfer(xfer)
+ struct wdc_xfer *xfer;
+{
+ int s;
+
+ s = splbio();
+ xfer->c_flags &= ~C_INUSE;
+ LIST_INSERT_HEAD(&xfer_free_list, xfer, free_list);
+ splx(s);
+}
+
+/*
+ * Implement operations needed before read/write.
+ * Returns 0 if operation still in progress, 1 if completed.
+ */
+int
+wdccontrol(d_link)
+ struct wd_link *d_link;
+{
+ struct wdc_softc *wdc = (void *)d_link->wdc_softc;
+
+ WDDEBUG_PRINT(("wdccontrol\n"));
+
+ switch (d_link->sc_state) {
+ case RECAL: /* Set SDH, step rate, do recal. */
+ if (wdccommandshort(wdc, d_link->sc_drive, WDCC_RECAL) != 0) {
+ wderror(d_link, NULL, "wdccontrol: recal failed (1)");
+ goto bad;
+ }
+ d_link->sc_state = RECAL_WAIT;
+ break;
+
+ case RECAL_WAIT:
+ if (wdc->sc_status & WDCS_ERR) {
+ wderror(d_link, NULL, "wdccontrol: recal failed (2)");
+ goto bad;
+ }
+ /* fall through */
+
+ case GEOMETRY:
+ if ((d_link->sc_params.wdp_capabilities & WD_CAP_LBA) != 0)
+ goto multimode;
+ if (wdsetctlr(d_link) != 0) {
+ /* Already printed a message. */
+ goto bad;
+ }
+ d_link->sc_state = GEOMETRY_WAIT;
+ break;
+
+ case GEOMETRY_WAIT:
+ if (wdc->sc_status & WDCS_ERR) {
+ wderror(d_link, NULL, "wdccontrol: geometry failed");
+ goto bad;
+ }
+ /* fall through */
+
+ case MULTIMODE:
+ multimode:
+ if (d_link->sc_mode != WDM_PIOMULTI)
+ goto open;
+ outb(wdc->sc_iobase + wd_seccnt, d_link->sc_multiple);
+ if (wdccommandshort(wdc, d_link->sc_drive,
+ WDCC_SETMULTI) != 0) {
+ wderror(d_link, NULL,
+ "wdccontrol: setmulti failed (1)");
+ goto bad;
+ }
+ d_link->sc_state = MULTIMODE_WAIT;
+ break;
+
+ case MULTIMODE_WAIT:
+ if (wdc->sc_status & WDCS_ERR) {
+ wderror(d_link, NULL,
+ "wdccontrol: setmulti failed (2)");
+ goto bad;
+ }
+ /* fall through */
+
+ case OPEN:
+ open:
+ wdc->sc_errors = 0;
+ d_link->sc_state = OPEN;
+ /*
+ * The rest of the initialization can be done by normal means.
+ */
+ return 1;
+
+ bad:
+ wdcunwedge(wdc);
+ return 0;
+ }
+
+ wdc->sc_flags |= WDCF_IRQ_WAIT;
+ timeout(wdctimeout, wdc, WAITTIME);
+ return 0;
+}
+
+/*
+ * Get the drive parameters, if ESDI or ATA, or create fake ones for ST506.
+ */
+int
+wdc_get_parms(d_link)
+ struct wd_link *d_link;
+{
+ struct wdc_softc *wdc = (struct wdc_softc *)d_link->wdc_softc;
+ int i;
+ char tb[DEV_BSIZE];
+ int s, error;
+
+ /*
+ * XXX
+ * The locking done here, and the length of time this may keep the rest
+ * of the system suspended, is a kluge. This should be rewritten to
+ * set up a transfer and queue it through wdstart(), but it's called
+ * infrequently enough that this isn't a pressing matter.
+ */
+
+ s = splbio();
+
+ while ((wdc->sc_flags & WDCF_ACTIVE) != 0) {
+ wdc->sc_flags |= WDCF_WANTED;
+ if ((error = tsleep(wdc, PRIBIO | PCATCH, "wdprm", 0)) != 0) {
+ splx(s);
+ return error;
+ }
+ }
+
+ wdc->sc_flags |= WDCF_ACTIVE;
+
+ if (wdccommandshort(wdc, d_link->sc_drive, WDCC_IDENTIFY) != 0 ||
+ wait_for_drq(wdc) != 0) {
+ /*
+ * We `know' there's a drive here; just assume it's old.
+ * This geometry is only used to read the MBR and print a
+ * (false) attach message.
+ */
+ strncpy(d_link->sc_lp->d_typename, "ST506",
+ sizeof d_link->sc_lp->d_typename);
+ d_link->sc_lp->d_type = DTYPE_ST506;
+
+ strncpy(d_link->sc_params.wdp_model, "unknown",
+ sizeof d_link->sc_params.wdp_model);
+ d_link->sc_params.wdp_config = WD_CFG_FIXED;
+ d_link->sc_params.wdp_cylinders = 1024;
+ d_link->sc_params.wdp_heads = 8;
+ d_link->sc_params.wdp_sectors = 17;
+ d_link->sc_params.wdp_maxmulti = 0;
+ d_link->sc_params.wdp_usedmovsd = 0;
+ d_link->sc_params.wdp_capabilities = 0;
+ } else {
+ strncpy(d_link->sc_lp->d_typename, "ESDI/IDE",
+ sizeof d_link->sc_lp->d_typename);
+ d_link->sc_lp->d_type = DTYPE_ESDI;
+
+ /* Read in parameter block. */
+ insw(wdc->sc_iobase + wd_data, tb, sizeof(tb) / sizeof(short));
+ bcopy(tb, &d_link->sc_params, sizeof(struct wdparams));
+
+ /* Shuffle string byte order. */
+ for (i = 0; i < sizeof(d_link->sc_params.wdp_model); i += 2) {
+ u_short *p;
+ p = (u_short *)(d_link->sc_params.wdp_model + i);
+ *p = ntohs(*p);
+ }
+ }
+
+ /* Clear any leftover interrupt. */
+ (void) inb(wdc->sc_iobase + wd_status);
+
+ /* Restart the queue. */
+ WDDEBUG_PRINT(("wdcstart from wdc_get_parms flags %d\n",
+ wdc->sc_flags));
+ wdc->sc_flags &= ~WDCF_ACTIVE;
+ wdcstart(wdc);
+
+ splx(s);
+ return 0;
+}
+
+void
+wdcerror(wdc, msg)
+ struct wdc_softc *wdc;
+ char *msg;
+{
+ struct wdc_xfer *xfer = wdc->sc_xfer.tqh_first;
+ if (xfer == NULL)
+ printf("%s: %s\n", wdc->sc_dev.dv_xname, msg);
+ else
+ printf("%s(%s): %s\n", wdc->sc_dev.dv_xname,
+ ((struct device*)xfer->d_link->wd_softc)->dv_xname, msg);
+}
+
+int
+wdc_atapi_get_params(ab_link, drive, id)
+ struct bus_link *ab_link;
+ u_int8_t drive;
+ struct atapi_identify *id;
+{
+ struct wdc_softc *wdc = (void*)ab_link->wdc_softc;
+ int status, len, excess = 0;
+ int s, error;
+
+ if (wdc->d_link[drive] != 0) {
+#ifdef ATAPI_DEBUG_PROBE
+ printf("wdc_atapi_get_params: WD drive %d\n", drive);
+#endif
+ return 0;
+ }
+
+ /*
+ * If there is only one ATAPI slave ion the bus,don't probe
+ * drive 0 (master)
+ */
+
+ if (wdc->sc_flags & WDCF_ONESLAVE && drive != 1)
+ return 0;
+
+#ifdef ATAPI_DEBUG_PROBE
+ printf("wdc_atapi_get_params: probing drive %d\n", drive);
+#endif
+
+ /*
+ * XXX
+ * The locking done here, and the length of time this may keep the rest
+ * of the system suspended, is a kluge. This should be rewritten to
+ * set up a transfer and queue it through wdstart(), but it's called
+ * infrequently enough that this isn't a pressing matter.
+ */
+
+ s = splbio();
+
+ while ((wdc->sc_flags & WDCF_ACTIVE) != 0) {
+ wdc->sc_flags |= WDCF_WANTED;
+ if ((error = tsleep(wdc, PRIBIO | PCATCH, "atprm", 0)) != 0) {
+ splx(s);
+ return error;
+ }
+ }
+
+ wdc->sc_flags |= WDCF_ACTIVE;
+ error = 1;
+ (void)wdcreset(wdc);
+ if ((status = wdccommand((struct wd_link*)ab_link,
+ ATAPI_SOFT_RESET, drive, 0, 0, 0, 0)) != 0) {
+#ifdef ATAPI_DEBUG
+ printf("wdc_atapi_get_params: ATAPI_SOFT_RESET"
+ "failed for drive %d: status %d error %d\n",
+ drive, status, wdc->sc_error);
+#endif
+ error = 0;
+ goto end;
+ }
+ if ((status = wait_for_unbusy(wdc)) != 0) {
+#ifdef ATAPI_DEBUG
+ printf("wdc_atapi_get_params: wait_for_unbusy failed "
+ "for drive %d: status %d error %d\n",
+ drive, status, wdc->sc_error);
+#endif
+ error = 0;
+ goto end;
+ }
+
+ if (wdccommand((struct wd_link*)ab_link, ATAPI_IDENTIFY_DEVICE,
+ drive, sizeof(struct atapi_identify), 0, 0, 0) != 0 ||
+ atapi_ready(wdc) != 0) {
+#ifdef ATAPI_DEBUG_PROBE
+ printf("ATAPI_IDENTIFY_DEVICE failed for drive %d\n", drive);
+#endif
+ error = 0;
+ goto end;
+ }
+ len = inb(wdc->sc_iobase + wd_cyl_lo) + 256 *
+ inb(wdc->sc_iobase + wd_cyl_hi);
+ if (len != sizeof(struct atapi_identify)) {
+ printf("Warning drive %d returned %d/%d of "
+ "indentify device data\n", drive, len,
+ sizeof(struct atapi_identify));
+ excess = len - sizeof(struct atapi_identify);
+ if (excess < 0)
+ excess = 0;
+ }
+ insw(wdc->sc_iobase + wd_data, id,
+ sizeof(struct atapi_identify)/sizeof(short));
+ wdcbit_bucket(wdc, excess);
+
+ end: /* Restart the queue. */
+ WDDEBUG_PRINT(("wdcstart from wdc_atapi_get_parms flags %d\n",
+ wdc->sc_flags));
+ wdc->sc_flags &= ~WDCF_ACTIVE;
+ wdcstart(wdc);
+ splx(s);
+ return error;
+}
+
+void
+wdc_atapi_send_command_packet(ab_link, acp)
+ struct bus_link *ab_link;
+ struct atapi_command_packet *acp;
+{
+ struct wdc_softc *wdc = (void*)ab_link->wdc_softc;
+ struct wdc_xfer *xfer;
+ u_int8_t flags = acp->flags & 0xff;
+ int s;
+
+ if (flags & A_POLLED) { /* Must use the queue and wdc_atapi_start */
+ struct wdc_xfer xfer_s;
+ int i;
+#ifdef ATAPI_DEBUG_WDC
+ printf("wdc_atapi_send_cmd: "
+ "flags %ld drive %d cmdlen %d datalen %d",
+ acp->flags, acp->drive, acp->command_size, acp->data_size);
+#endif
+ xfer = &xfer_s;
+ bzero(xfer, sizeof(xfer_s));
+ xfer->c_flags = C_INUSE|C_ATAPI|acp->flags;
+ xfer->d_link = (struct wd_link *)ab_link;
+ xfer->c_link = ab_link->ctlr_link;
+ xfer->c_bp = acp->bp;
+ xfer->atapi_cmd = acp;
+ xfer->c_blkno = 0;
+ xfer->databuf = acp->databuf;
+ xfer->c_bcount = acp->data_size;
+ if (wait_for_unbusy (wdc) != 0) {
+ if ((wdc->sc_status & WDCS_ERR) == 0) {
+ printf("wdc_atapi_send_command: not ready, "
+ "st = %02x\n", wdc->sc_status);
+ acp->status = ERROR;
+ return;
+ }
+ }
+
+ if (wdccommand((struct wd_link*)ab_link,
+ ATAPI_PACKET_COMMAND, acp->drive, acp->data_size,
+ 0, 0, 0) != 0) {
+ printf("can't send atapi paket command\n");
+ acp->status = ERROR;
+ return;
+ }
+
+ /* Wait for cmd i/o phase. */
+ for (i = 20000; i > 0; --i) {
+ int phase;
+ phase = (inb(wdc->sc_iobase + wd_ireason) &
+ (WDCI_CMD | WDCI_IN)) |
+ (inb(wdc->sc_iobase + wd_status) & WDCS_DRQ);
+ if (phase == PHASE_CMDOUT)
+ break;
+ delay(10);
+ }
+#ifdef ATAPI_DEBUG_WDC
+ printf("Wait for cmd i/o phase: i = %d\n", i);
+#endif
+
+ outsw(wdc->sc_iobase + wd_data, acp->command,
+ acp->command_size/ sizeof (short));
+
+ /* Wait for data i/o phase. */
+ for ( i= 20000; i > 0; --i) {
+ int phase;
+ phase = (inb(wdc->sc_iobase + wd_ireason) &
+ (WDCI_CMD | WDCI_IN)) |
+ (inb(wdc->sc_iobase + wd_status) & WDCS_DRQ);
+ if (phase != PHASE_CMDOUT)
+ break;
+ delay(10);
+ }
+
+#ifdef ATAPI_DEBUG_WDC
+ printf("Wait for data i/o phase: i = %d\n", i);
+#endif
+ while (wdc_atapi_intr(wdc, xfer)) {
+ for (i = 2000; i > 0; --i)
+ if ((inb(wdc->sc_iobase + wd_status)
+ & WDCS_DRQ) == 0)
+ break;
+#ifdef ATAPI_DEBUG_WDC
+ printf("wdc_atapi_intr: i = %d\n", i);
+#endif
+ }
+ wdc->sc_flags &= ~(WDCF_IRQ_WAIT | WDCF_SINGLE | WDCF_ERROR);
+ wdc->sc_errors = 0;
+ xfer->c_skip = 0;
+ return;
+ } else { /* POLLED */
+ xfer = wdc_get_xfer(ab_link->ctlr_link,
+ flags & A_NOSLEEP ? IDE_NOSLEEP : 0);
+ if (xfer == NULL) {
+ acp->status = ERROR;
+ return;
+ }
+ xfer->c_flags |= C_ATAPI|acp->flags;
+ xfer->d_link = (struct wd_link*) ab_link;
+ xfer->c_link = ab_link->ctlr_link;
+ xfer->c_bp = acp->bp;
+ xfer->atapi_cmd = acp;
+ xfer->c_blkno = 0;
+ xfer->databuf = acp->databuf;
+ xfer->c_bcount = acp->data_size;
+ wdc_exec_xfer((struct wd_link*)ab_link,xfer);
+ return;
+ }
+}
+
+
+/*
+ * the bit bucket
+ */
+void
+wdcbit_bucket(wdc, size)
+ struct wdc_softc *wdc;
+ int size;
+{
+ int iobase = wdc->sc_iobase;
+ int i;
+
+ for (i = 0 ; i < size / 2 ; i++) {
+ short null;
+ (void)insw(iobase + wd_data, &null, 1);
+ }
+
+ if (size % 2)
+ (void)inb(iobase + wd_data);
+}
+
+int
+wdc_atapi_intr(wdc, xfer)
+ struct wdc_softc *wdc;
+ struct wdc_xfer *xfer;
+{
+ struct atapi_command_packet *acp = xfer->atapi_cmd;
+ int len, phase, i;
+ int err, st, ire;
+
+ if (wait_for_unbusy(wdc) < 0) {
+ printf("wdc_atapi_intr: controller busy\n");
+ acp->status = ERROR;
+ acp->error = inb (wdc->sc_iobase + wd_error);
+ return 0;
+ }
+
+#ifdef ATAPI_DEBUG2
+ printf("wdc_atapi_intr: %s\n", wdc->sc_dev.dv_xname);
+#endif
+
+ len = inb(wdc->sc_iobase + wd_cyl_lo) +
+ 256 * inb(wdc->sc_iobase + wd_cyl_hi);
+
+ st = inb(wdc->sc_iobase + wd_status);
+ err = inb(wdc->sc_iobase + wd_error);
+ ire = inb(wdc->sc_iobase + wd_ireason);
+
+ phase = (ire & (WDCI_CMD | WDCI_IN)) | (st & WDCS_DRQ);
+#ifdef ATAPI_DEBUG_WDC
+ printf("wdc_atapi_intr: len %d st %d err %d ire %d :",
+ len, st, err, ire);
+#endif
+ switch (phase) {
+ case PHASE_CMDOUT:
+ /* send packet command */
+#ifdef ATAPI_DEBUG_WDC
+ printf("PHASE_CMDOUT\n");
+#endif
+
+#ifdef ATAPI_DEBUG_WDC
+ {
+ int i;
+ char *c = (char *)acp->command;
+ printf("wdc_atapi_intr: cmd ");
+ for (i = 0; i < acp->command_size; i++)
+ printf("%x ", c[i]);
+ printf("\n");
+ }
+#endif
+
+ wdc->sc_flags |= WDCF_IRQ_WAIT;
+ outsw(wdc->sc_iobase + wd_data, acp->command,
+ acp->command_size/ sizeof (short));
+ return 1;
+
+ case PHASE_DATAOUT:
+ /* write data */
+#ifdef ATAPI_DEBUG_WDC
+ printf("PHASE_DATAOUT\n");
+#endif
+ if ((acp->flags & (B_READ|B_WRITE)) != B_WRITE) {
+ printf("wdc_atapi_intr: bad data phase\n");
+ acp->status = ERROR;
+ return 1;
+ }
+ wdc->sc_flags |= WDCF_IRQ_WAIT;
+ if (xfer->c_bcount < len) {
+ printf("wdc_atapi_intr: warning: write only "
+ "%d of %d requested bytes\n", xfer->c_bcount, len);
+ outsw(wdc->sc_iobase + wd_data,
+ xfer->databuf + xfer->c_skip,
+ xfer->c_bcount / sizeof(short));
+ for (i = xfer->c_bcount; i < len; i += sizeof(short))
+ outw(wdc->sc_iobase + wd_data, 0);
+ xfer->c_bcount = 0;
+ return 1;
+ } else {
+ outsw(wdc->sc_iobase + wd_data,
+ xfer->databuf + xfer->c_skip, len / sizeof(short));
+ xfer->c_skip += len;
+ xfer->c_bcount -= len;
+ return 1;
+ }
+
+ case PHASE_DATAIN:
+ /* Read data */
+#ifdef ATAPI_DEBUG_WDC
+ printf("PHASE_DATAIN\n");
+#endif
+ if ((acp->flags & (B_READ|B_WRITE)) != B_READ) {
+ printf("wdc_atapi_intr: bad data phase\n");
+ acp->status = ERROR;
+ return 1;
+ }
+ wdc->sc_flags |= WDCF_IRQ_WAIT;
+ if (xfer->c_bcount < len) {
+ printf("wdc_atapi_intr: warning: reading only "
+ "%d of %d bytes\n", xfer->c_bcount, len);
+ insw(wdc->sc_iobase + wd_data,
+ xfer->databuf + xfer->c_skip,
+ xfer->c_bcount / sizeof(short));
+ wdcbit_bucket(wdc, len - xfer->c_bcount);
+ xfer->c_bcount = 0;
+ return 1;
+ } else {
+ insw(wdc->sc_iobase + wd_data,
+ xfer->databuf + xfer->c_skip, len / sizeof(short));
+ xfer->c_skip += len;
+ xfer->c_bcount -=len;
+ return 1;
+ }
+
+ case PHASE_ABORTED:
+ case PHASE_COMPLETED:
+#ifdef ATAPI_DEBUG_WDC
+ printf("PHASE_COMPLETED\n");
+#endif
+ if (st & WDCS_ERR) {
+ acp->error = inb(wdc->sc_iobase + wd_error);
+ acp->status = ERROR;
+ }
+#ifdef ATAPI_DEBUG_WDC
+ if (xfer->c_bcount != 0) {
+ printf("wdc_atapi_intr warning: bcount value "
+ "is %d after io\n", xfer->c_bcount);
+ }
+#endif
+ break;
+
+ default:
+ printf("wdc_atapi_intr: unknown phase %d\n", phase);
+ acp->status = ERROR;
+ }
+
+ wdc_atapi_done(wdc, xfer);
+ return (0);
+}
+
+
+void
+wdc_atapi_done(wdc, xfer)
+ struct wdc_softc *wdc;
+ struct wdc_xfer *xfer;
+{
+ struct atapi_command_packet *acp = xfer->atapi_cmd;
+ int s;
+
+ acp->data_size = xfer->c_bcount;
+
+ s = splbio();
+
+ /* remove this command from xfer queue */
+ wdc->sc_errors = 0;
+ xfer->c_skip = 0;
+ if ((xfer->c_flags & A_POLLED) == 0) {
+ untimeout(wdctimeout, wdc);
+ TAILQ_REMOVE(&wdc->sc_xfer, xfer, c_xferchain);
+ wdc->sc_flags &= ~(WDCF_SINGLE | WDCF_ERROR | WDCF_ACTIVE);
+ wdc_free_xfer(xfer);
+#ifdef ATAPI_DEBUG
+ printf("wdc_atapi_done: atapi_done\n");
+#endif
+ atapi_done(acp);
+#ifdef WDDEBUG
+ printf("wdcstart from wdc_atapi_intr, flags %d\n",
+ wdc->sc_flags);
+#endif
+ wdcstart(wdc);
+ } else
+ wdc->sc_flags &= ~(WDCF_SINGLE | WDCF_ERROR | WDCF_ACTIVE);
+
+ splx(s);
+}