/* $NetBSD: wdc.c,v 1.8 1997/11/05 22:19:07 bouyer 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "atapibus.h" #include "wd.h" #if NATAPIBUS > 0 #include #include #endif #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 5 /* 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 }; void wdcstart __P((struct wdc_softc *)); int wdcreset __P((struct wdc_softc *, int)); #define VERBOSE 1 #define SILENT 0 void wdcrestart __P((void *arg)); void wdcunwedge __P((struct wdc_softc *)); void wdctimeout __P((void *arg)); int wdccontrol __P((struct wdc_softc*, struct wd_link *)); void wdc_free_xfer __P((struct wdc_xfer *)); void wdcerror __P((struct wdc_softc*, char *)); void wdcbit_bucket __P(( struct wdc_softc *, int)); #if NWD > 0 int wdprint __P((void *, const char *)); int wdsetctlr __P((struct wd_link *)); int wdc_ata_intr __P((struct wdc_softc *,struct wdc_xfer *)); void wdc_ata_start __P((struct wdc_softc *,struct wdc_xfer *)); void wdc_ata_done __P((struct wdc_softc *, struct wdc_xfer *)); #endif /* NWD > 0 */ #if NATAPIBUS > 0 void wdc_atapi_minphys __P((struct buf *bp)); void wdc_atapi_start __P((struct wdc_softc *,struct wdc_xfer *)); int wdc_atapi_intr __P((struct wdc_softc *, struct wdc_xfer *)); void wdc_atapi_done __P((struct wdc_softc *, struct wdc_xfer *)); int wdc_atapi_send_command_packet __P((struct scsipi_xfer *sc_xfer)); #define MAX_SIZE MAXPHYS /* XXX */ #endif #ifdef ATAPI_DEBUG2 static int wdc_nxfer; #endif #ifdef WDDEBUG #define WDDEBUG_PRINT(args) printf args #else #define WDDEBUG_PRINT(args) #endif #if NATAPIBUS > 0 static struct scsipi_adapter wdc_switch = { wdc_atapi_send_command_packet, wdc_atapi_minphys, 0, 0 }; #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; if (wdcreset(wdc, SILENT) != 0) { /* * if the reset failed, there is no master. test for ATAPI signature * on the slave device. If no ATAPI slave, wait 5s and retry a reset. */ outb(iobase+wd_sdh, WDSD_IBM | 0x10); /* slave */ if (inb(iobase + wd_cyl_lo) == 0x14 && inb(iobase + wd_cyl_hi) == 0xeb) { wdc->sc_flags |= WDCF_ONESLAVE; goto drivefound; } else { delay(500000); if (wdcreset(wdc, SILENT) != 0) return 0; } } /* reset succeeded. Test registers */ if (inb(iobase + wd_cyl_lo) == 0x14 && inb(iobase + wd_cyl_hi) == 0xeb) goto drivefound; /* not ATAPI. Test registers */ outb(iobase+wd_error, 0x58); /* Error register not writable, */ outb(iobase+wd_cyl_lo, 0xa5); /* but all of cyllo are. */ if (inb(iobase+wd_error) != 0x58 && inb(iobase+wd_cyl_lo) == 0xa5) goto drivefound; /* No master. Test atapi signature on slave */ outb(iobase+wd_sdh, WDSD_IBM | 0x10); if (inb(iobase + wd_cyl_lo) == 0x14 && inb(iobase + wd_cyl_hi) == 0xeb) { wdc->sc_flags |= WDCF_ONESLAVE; goto drivefound; } return 0; drivefound: /* 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; } void wdcattach(parent, self, aux) struct device *parent, *self; void *aux; { struct wdc_softc *wdc = (void *)self; struct isa_attach_args *ia = aux; #if NWD > 0 int drive; #endif TAILQ_INIT(&wdc->sc_xfer); wdc->sc_drq = ia->ia_drq; printf("\n"); if (wdc->sc_drq != -1) { if (isa_dmamap_create(parent, wdc->sc_drq, MAXPHYS, BUS_DMA_NOWAIT|BUS_DMA_ALLOCNOW)) { printf("%s: can't create map for drq %d\n", wdc->sc_dev.dv_xname, wdc->sc_drq); wdc->sc_drq = -1; } } wdc->sc_ih = isa_intr_establish(ia->ia_ic, ia->ia_irq, IST_EDGE, IPL_BIO, wdcintr, wdc); #ifdef ATAPI_DEBUG2 wdc_nxfer = 0; #endif #if NATAPIBUS > 0 /* * Attach an ATAPI bus, if configured. */ wdc->ab_link = malloc(sizeof(struct scsipi_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 scsipi_link)); wdc->ab_link->type = BUS_ATAPI; wdc->ab_link->openings = 1; wdc->ab_link->scsipi_atapi.type = ATAPI; wdc->ab_link->scsipi_atapi.channel = 0; wdc->ab_link->adapter_softc = (caddr_t)wdc; wdc->ab_link->adapter = &wdc_switch; (void)config_found(self, (void *)wdc->ab_link, NULL); #endif /* NATAPIBUS > 0 */ #if NWD > 0 /* * Attach standard IDE/ESDI/etc. disks to the controller. */ for (drive = 0; drive < 2; drive++) { /* if a disk is already present, skip */ if ((wdc->sc_drives_mask & (1 << drive)) != 0) { continue; } /* 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 = ATA; wdc->d_link[drive]->wdc_softc =(caddr_t) wdc; wdc->d_link[drive]->drive = drive; if (wdc->sc_drq != DRQUNK) wdc->d_link[drive]->sc_mode = WDM_DMA; else wdc->d_link[drive]->sc_mode = 0; wdc->sc_drives_mask |= (1 << drive); (void)config_found(self, (void *)wdc->d_link[drive], wdprint); } } #endif /* NWD > 0 */ /* explicitely select an existing drive, to avoid spurious interrupts */ if (wdc->sc_flags & WDCF_ONESLAVE) outb(wdc->sc_iobase+wd_sdh, WDSD_IBM | 0x10); /* slave */ else outb(wdc->sc_iobase+wd_sdh, WDSD_IBM); /* master */ /* * Reset controller. The probe, with some combinations of ATA/ATAPI * device keep it in a mostly working, but strange state (with busy * led on) */ wdcreset(wdc, VERBOSE); } /* * 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 /* is there a xfer ? */ xfer = wdc->sc_xfer.tqh_first; if (xfer == NULL) { #ifdef ATAPI_DEBUG2 printf("wdcstart: null xfer\n"); #endif /* * XXX * This is a kluge. See comments in wd_get_parms(). */ if ((wdc->sc_flags & WDCF_WANTED) != 0) { #ifdef ATAPI_DEBUG2 printf("WDCF_WANTED\n"); #endif wdc->sc_flags &= ~WDCF_WANTED; wakeup(wdc); } return; } wdc->sc_flags |= WDCF_ACTIVE; #ifdef ATAPI_DEBUG2 printf("wdcstart: drive %d\n", (int)xfer->d_link->drive); #endif outb(wdc->sc_iobase+wd_sdh, WDSD_IBM | xfer->d_link->drive << 4); #if NATAPIBUS > 0 && NWD > 0 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); #else /* NATAPIBUS > 0 && NWD > 0 */ #if NATAPIBUS > 0 #ifdef ATAPI_DEBUG_WDC printf("wdcstart: atapi\n"); #endif wdc_atapi_start(wdc,xfer); #endif /* NATAPIBUS > */ #if NWD > 0 wdc_ata_start(wdc,xfer); #endif /* NWD > 0 */ #endif /* NATAPIBUS > 0 && NWD > 0 */ } #if NWD > 0 int wdprint(aux, wdc) void *aux; const char *wdc; { struct wd_link *d_link = aux; if (!wdc) printf(" drive %d", d_link->drive); return QUIET; } 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; if (wdc->sc_errors >= WDIORETRIES) { wderror(d_link, bp, "wdc_ata_start hard error"); xfer->c_flags |= C_ERROR; wdc_ata_done(wdc, xfer); return; } /* Do control operations specially. */ if (d_link->sc_state < READY) { /* * 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(wdc, 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. */ isa_dmastart(wdc->sc_dev.dv_parent, wdc->sc_drq, xfer->databuf + xfer->c_skip, xfer->c_nbytes, NULL, xfer->c_flags & B_READ ? DMAMODE_READ : DMAMODE_WRITE, BUS_DMA_NOWAIT); 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(wdc, d_link, command, d_link->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")); } 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"); return 0; } wdc->sc_flags &= ~WDCF_IRQ_WAIT; untimeout(wdctimeout, wdc); /* Is it not a transfer, but a control operation? */ if (d_link->sc_state < READY) { if (wdccontrol(wdc, d_link) == 0) { /* The drive is busy. Wait. */ return 1; } WDDEBUG_PRINT(("wdc_ata_start from wdc_ata_intr(open) flags 0x%x\n", dc->sc_flags)); wdc_ata_start(wdc,xfer); return 1; } /* Turn off the DMA channel. */ if (d_link->sc_mode == WDM_DMA) isa_dmadone(wdc->sc_dev.dv_parent, 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 if (++wdc->sc_errors < WDIORETRIES) { if (wdc->sc_errors == (WDIORETRIES + 1) / 2) { #if 0 wderror(wd, NULL, "wedgie"); #endif wdcunwedge(wdc); return 1; } goto restart; } wderror(d_link, xfer->c_bp, "wdc_ata_intr 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 1; restart: /* Start the next operation */ WDDEBUG_PRINT(("wdc_ata_start from wdcintr flags 0x%x\n", wdc->sc_flags)); wdc_ata_start(wdc, xfer); return 1; } 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 0x%x\n", wdc->sc_flags)); wdcstart(wdc); splx(s); } /* * Get the drive parameters, if ESDI or ATA, or create fake ones for ST506. */ int wdc_get_parms(wdc, d_link) struct wdc_softc * wdc; struct wd_link *d_link; { 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->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 0x%x\n", wdc->sc_flags)); wdc->sc_flags &= ~WDCF_ACTIVE; wdcstart(wdc); splx(s); return 0; } /* * Implement operations needed before read/write. * Returns 0 if operation still in progress, 1 if completed. */ int wdccontrol(wdc, d_link) struct wdc_softc *wdc; struct wd_link *d_link; { WDDEBUG_PRINT(("wdccontrol\n")); switch (d_link->sc_state) { case RECAL: /* Set SDH, step rate, do recal. */ if (wdccommandshort(wdc, d_link->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 ready; outb(wdc->sc_iobase + wd_seccnt, d_link->sc_multiple); if (wdccommandshort(wdc, d_link->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 READY: ready: wdc->sc_errors = 0; d_link->sc_state = READY; /* * 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; } #endif /* NWD > 0 */ /* * 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); #ifdef ATAPI_DEBUG_WDC u_char e = inb(wdc->sc_iobase+wd_error); u_char i = inb(wdc->sc_iobase+wd_seccnt); printf("wdcintr: inactive controller, " "punting st=%02x er=%02x irr=%02x\n", s, e, i); #else inb(wdc->sc_iobase+wd_error); inb(wdc->sc_iobase+wd_seccnt); #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")); xfer = wdc->sc_xfer.tqh_first; #if NATAPIBUS > 0 && NWD > 0 if (xfer->c_flags & C_ATAPI) { return wdc_atapi_intr(wdc,xfer); } else return wdc_ata_intr(wdc,xfer); #else /* NATAPIBUS > 0 && NWD > 0 */ #if NATAPIBUS > 0 return wdc_atapi_intr(wdc,xfer); #endif /* NATAPIBUS > 0 */ #if NWD > 0 return wdc_ata_intr(wdc,xfer); #endif /* NWD > 0 */ #endif /* NATAPIBUS > 0 && NWD > 0 */ } int wdcreset(wdc, verb) struct wdc_softc *wdc; int verb; { 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) { if (verb) 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, VERBOSE); /* Schedule recalibrate for all drives on this controller. */ for (unit = 0; unit < 2; unit++) { if (!wdc->d_link[unit]) wdccommandshort(wdc, unit, ATAPI_SOFT_RESET); else 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 iobase %x\n", iobase)); 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_DEBUG printf("wdcwait: timeout, status %x error %x\n", status, inb(iobase+wd_error)); #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; struct wdc_xfer *xfer = wdc->sc_xfer.tqh_first; int s; WDDEBUG_PRINT(("wdctimeout\n")); s = splbio(); if ((wdc->sc_flags & WDCF_IRQ_WAIT) != 0) { wdcerror(wdc, "lost interrupt"); printf("\ttype: %s\n", (xfer->c_flags & C_ATAPI) ? "atapi":"ata"); printf("\tc_bcount: %d\n", xfer->c_bcount); printf("\tc_skip: %d\n", xfer->c_skip); wdcintr(wdc); wdc->sc_flags &= ~WDCF_IRQ_WAIT; 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(wdc, d_link, command, drive, cylin, head, sector, count) struct wdc_softc *wdc; struct wd_link *d_link; int command; int drive, cylin, head, sector, count; { 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 == ATAPI) 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 == ATA && 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_exec_xfer(wdc, d_link, xfer) struct wdc_softc *wdc; struct wd_link *d_link; struct wdc_xfer *xfer; { 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 0x%x\n", wdc->sc_flags)); wdcstart(wdc); xfer->c_flags |= C_NEEDDONE; /* we can now call upper level done() */ splx(s); } struct wdc_xfer * wdc_get_xfer(flags) int flags; { struct wdc_xfer *xfer; int s; s = splbio(); if ((xfer = xfer_free_list.lh_first) != NULL) { 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_DEBUG2 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_DEBUG2 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; 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); } 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(%d): %s\n", wdc->sc_dev.dv_xname, xfer->d_link->drive, msg); } /* * 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); } #if NATAPIBUS > 0 void wdc_atapi_minphys (struct buf *bp) { if(bp->b_bcount > MAX_SIZE) bp->b_bcount = MAX_SIZE; minphys(bp); } void wdc_atapi_start(wdc, xfer) struct wdc_softc *wdc; struct wdc_xfer *xfer; { struct scsipi_xfer *sc_xfer = xfer->atapi_cmd; #ifdef ATAPI_DEBUG_WDC printf("wdc_atapi_start, acp flags %x \n",sc_xfer->flags); #endif if (wdc->sc_errors >= WDIORETRIES) { if ((wdc->sc_status & WDCS_ERR) == 0) { sc_xfer->error = XS_DRIVER_STUFFUP; /* XXX do we know more ? */ } else { sc_xfer->error = XS_SENSE; sc_xfer->sense.atapi_sense = 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); sc_xfer->error = XS_SELTIMEOUT; } #if 0 /* don't get the sense yet, as this may be just UNIT ATTENTION */ else { #ifdef ATAPI_DEBUG_WDC printf("wdc_atapi_start: sense %02x\n", wdc->sc_error); #endif sc_xfer->error = XS_SENSE; sc_xfer->sense.atapi_sense = wdc->sc_error; } wdc_atapi_done(wdc, xfer); return; #endif } if (wdccommand(wdc, (struct wd_link*)xfer->d_link, ATAPI_PACKET_COMMAND, sc_xfer->sc_link->scsipi_atapi.drive, sc_xfer->datalen, 0, 0, 0) != 0) { printf("wdc_atapi_start: can't send atapi paket command\n"); sc_xfer->error = XS_DRIVER_STUFFUP; wdc_atapi_done(wdc, xfer); return; } if ((sc_xfer->sc_link->scsipi_atapi.cap & 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"); sc_xfer->error = XS_SELTIMEOUT; wdc_atapi_done(wdc, xfer); return; } outsw(wdc->sc_iobase + wd_data, sc_xfer->cmd, sc_xfer->cmdlen / sizeof(short)); } wdc->sc_flags |= WDCF_IRQ_WAIT; #ifdef ATAPI_DEBUG2 printf("wdc_atapi_start: timeout\n"); #endif timeout(wdctimeout, wdc, WAITTIME); return; } int wdc_atapi_get_params(ab_link, drive, id) struct scsipi_link *ab_link; u_int8_t drive; struct atapi_identify *id; { struct wdc_softc *wdc = (void*)ab_link->adapter_softc; int status, len, excess = 0; int s, error; /* if a disk is already present, skip */ if ((wdc->sc_drives_mask & (1 << drive)) != 0) { #ifdef ATAPI_DEBUG_PROBE printf("wdc_atapi_get_params: drive %d present\n", drive); #endif return 0; } /* * If there is only one ATAPI slave on 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, VERBOSE); if ((status = wdccommand(wdc, (struct wd_link*)(&(ab_link->scsipi_atapi)), 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(wdc, (struct wd_link*)(&(ab_link->scsipi_atapi)), 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); wdc->sc_drives_mask |= (1 << drive); end: /* Restart the queue. */ WDDEBUG_PRINT(("wdcstart from wdc_atapi_get_parms flags 0x%x\n", wdc->sc_flags)); wdc->sc_flags &= ~WDCF_ACTIVE; wdcstart(wdc); splx(s); return error; } int wdc_atapi_send_command_packet(sc_xfer) struct scsipi_xfer *sc_xfer; { struct scsipi_link *sc_link = sc_xfer->sc_link; struct wdc_softc *wdc = (void*)sc_link->adapter_softc; struct wdc_xfer *xfer; int flags = sc_xfer->flags; if (flags & SCSI_POLL) { /* should use the queue and wdc_atapi_start */ struct wdc_xfer xfer_s; int i, s; s = splbio(); #ifdef ATAPI_DEBUG_WDC printf("wdc_atapi_send_cmd: " "flags 0x%x drive %d cmdlen %d datalen %d", sc_xfer->flags, sc_link->scsipi_atapi.drive, sc_xfer->cmdlen, sc_xfer->datalen); #endif xfer = &xfer_s; bzero(xfer, sizeof(xfer_s)); xfer->c_flags = C_INUSE|C_ATAPI|flags; xfer->d_link = (struct wd_link *)(&sc_link->scsipi_atapi); xfer->c_bp = sc_xfer->bp; xfer->atapi_cmd = sc_xfer; xfer->c_blkno = 0; xfer->databuf = sc_xfer->data; xfer->c_bcount = sc_xfer->datalen; 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); sc_xfer->error = XS_SELTIMEOUT; } else { sc_xfer->error = XS_SENSE; sc_xfer->sense.atapi_sense = wdc->sc_error; } splx(s); return COMPLETE; } if (wdccommand(wdc, (struct wd_link*)(&sc_link->scsipi_atapi), ATAPI_PACKET_COMMAND, sc_link->scsipi_atapi.drive, sc_xfer->datalen, 0, 0, 0) != 0) { printf("can't send atapi paket command\n"); sc_xfer->error = XS_DRIVER_STUFFUP; splx(s); return COMPLETE; } /* 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, sc_xfer->cmd, sc_xfer->cmdlen/ 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 wdc->sc_flags |= WDCF_IRQ_WAIT; while ((sc_xfer->flags & ITSDONE) == 0) { 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; splx(s); return COMPLETE; } else { /* POLLED */ xfer = wdc_get_xfer(flags & SCSI_NOSLEEP ? IDE_NOSLEEP : 0); if (xfer == NULL) { return TRY_AGAIN_LATER; } xfer->c_flags |= C_ATAPI|sc_xfer->flags; xfer->d_link = (struct wd_link*)(&sc_link->scsipi_atapi); xfer->c_bp = sc_xfer->bp; xfer->atapi_cmd = sc_xfer; xfer->c_blkno = 0; xfer->databuf = sc_xfer->data; xfer->c_bcount = sc_xfer->datalen; wdc_exec_xfer(wdc, xfer->d_link, xfer); #ifdef ATAPI_DEBUG_WDC printf("wdc_atapi_send_command_packet: wdc_exec_xfer, flags 0x%x\n", sc_xfer->flags); #endif return (sc_xfer->flags & ITSDONE) ? COMPLETE : SUCCESSFULLY_QUEUED; } } int wdc_atapi_intr(wdc, xfer) struct wdc_softc *wdc; struct wdc_xfer *xfer; { struct scsipi_xfer *sc_xfer = xfer->atapi_cmd; int len, phase, i, retries=0; int err, st, ire; #ifdef ATAPI_DEBUG2 printf("wdc_atapi_intr: %s\n", wdc->sc_dev.dv_xname); #endif if (wait_for_unbusy(wdc) < 0) { if ((wdc->sc_status & WDCS_ERR) == 0) { printf("wdc_atapi_intr: controller busy\n"); return 0; } else { sc_xfer->error = XS_SENSE; sc_xfer->sense.atapi_sense = wdc->sc_error; } #ifdef ATAPI_DEBUG_WDC printf("wdc_atapi_intr: wdc_atapi_done(), error %d\n", sc_xfer->error); #endif wdc_atapi_done(wdc, xfer); return 0; } again: 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 *)sc_xfer->cmd; printf("wdc_atapi_intr: cmd "); for (i = 0; i < sc_xfer->cmdlen; i++) printf("%x ", c[i]); printf("\n"); } #endif outsw(wdc->sc_iobase + wd_data, sc_xfer->cmd, sc_xfer->cmdlen/ sizeof (short)); return 1; case PHASE_DATAOUT: /* write data */ #ifdef ATAPI_DEBUG_WDC printf("PHASE_DATAOUT\n"); #endif if ((sc_xfer->flags & SCSI_DATA_OUT) == 0) { printf("wdc_atapi_intr: bad data phase\n"); sc_xfer->error = XS_DRIVER_STUFFUP; return 0; } 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_skip += xfer->c_bcount; xfer->c_bcount = 0; } 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 ((sc_xfer->flags & SCSI_DATA_IN) == 0) { printf("wdc_atapi_intr: bad data phase\n"); sc_xfer->error = XS_DRIVER_STUFFUP; return 0; } 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_skip += xfer->c_bcount; xfer->c_bcount = 0; } 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) { sc_xfer->error = XS_SENSE; sc_xfer->sense.atapi_sense = inb (wdc->sc_iobase + wd_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: if (++retries<500) { DELAY(100); goto again; } printf("wdc_atapi_intr: unknown phase %d\n", phase); if (st & WDCS_ERR) { sc_xfer->error = XS_SENSE; sc_xfer->sense.atapi_sense = inb (wdc->sc_iobase + wd_error); } else { sc_xfer->error = XS_DRIVER_STUFFUP; } } #ifdef ATAPI_DEBUG_WDC printf("wdc_atapi_intr: wdc_atapi_done() (end), error %d\n", sc_xfer->error); #endif wdc_atapi_done(wdc, xfer); return (1); } void wdc_atapi_done(wdc, xfer) struct wdc_softc *wdc; struct wdc_xfer *xfer; { struct scsipi_xfer *sc_xfer = xfer->atapi_cmd; int s; int need_done = xfer->c_flags & C_NEEDDONE; #ifdef ATAPI_DEBUG printf("wdc_atapi_done: flags 0x%x\n", (u_int)xfer->c_flags); #endif sc_xfer->resid = xfer->c_bcount; wdc->sc_flags &= ~WDCF_IRQ_WAIT; /* remove this command from xfer queue */ wdc->sc_errors = 0; xfer->c_skip = 0; if ((xfer->c_flags & SCSI_POLL) == 0) { s = splbio(); untimeout(wdctimeout, wdc); TAILQ_REMOVE(&wdc->sc_xfer, xfer, c_xferchain); wdc->sc_flags &= ~(WDCF_SINGLE | WDCF_ERROR | WDCF_ACTIVE); wdc_free_xfer(xfer); sc_xfer->flags |= ITSDONE; if (need_done) { #ifdef ATAPI_DEBUG printf("wdc_atapi_done: scsipi_done\n"); #endif scsipi_done(sc_xfer); } #ifdef WDDEBUG printf("wdcstart from wdc_atapi_intr, flags 0x%x\n", wdc->sc_flags); #endif wdcstart(wdc); splx(s); } else { wdc->sc_flags &= ~(WDCF_SINGLE | WDCF_ERROR | WDCF_ACTIVE); sc_xfer->flags |= ITSDONE; } } #endif /* NATAPIBUS > 0 */