/* * Copyright (c) 1994 Shantanu Goel * All Rights Reserved. * * Permission to use, copy, modify and distribute this software and its * documentation is hereby granted, provided that both the copyright * notice and this permission notice appear in all copies of the * software, derivative works or modified versions, and any portions * thereof, and that both notices appear in supporting documentation. * * THE AUTHOR ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" * CONDITION. THE AUTHOR DISCLAIMS ANY LIABILITY OF ANY KIND FOR * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. * * MODIFIED BY KEVIN T. VAN MAREN, University of Utah, CSL * Copyright (c) 1996, University of Utah, CSL * * Uses a 'unified' partition code with the SCSI driver. * Reading/Writing disklabels through the kernel is NOT recommended. * (The preferred method is through the raw device (wd0), with no * open partitions). setdisklabel() should work for the in-core * fudged disklabel, but will not change the partitioning. The driver * *never* sees the disklabel on the disk. * */ #include #if NHD > 0 && !defined(LINUX_DEV) /* * Hard disk driver. * * Supports: * 1 controller and 2 drives. * Arbitrarily sized read/write requests. * Misaligned requests. * Multiple sector transfer mode (not tested extensively). * * TODO: * 1) Real probe routines for controller and drives. * 2) Support for multiple controllers. The driver does * not assume a single controller since all functions * take the controller and/or device structure as an * argument, however the probe routines limit the * number of controllers and drives to 1 and 2 respectively. * * Shantanu Goel (goel@cs.columbia.edu) */ #include #include #include "vm_param.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* this is for the partition code */ typedef struct ide_driver_info { dev_t dev; /* struct buf *bp; */ int sectorsize; } ide_driver_info; #define MAX_IDE_PARTS 32 /* max partitions per drive */ static char *drive_name[4]={"wd0: ","wd1: ","xxx ","yyy "}; /* * XXX: This will have to be fixed for controllers that * can support upto 4 drives. */ #define NDRIVES_PER_HDC 2 #define NHDC ((NHD + NDRIVES_PER_HDC - 1) / NDRIVES_PER_HDC) #define b_cylin b_resid #define B_ABS B_MD1 #define B_IDENTIFY (B_MD1 << 1) /* shift right SLICE_BITS + PARTITION_BITS. Note: 2^10 = 1024 sub-parts */ #define hdunit(dev) (((dev) >> 10) & 3) #define hdpart(dev) ((dev) & 0x3ff) #define MAX_RETRIES 12 /* maximum number of retries */ #define OP_TIMEOUT 7 /* time to wait (secs) for an operation */ /* * Autoconfiguration stuff. */ struct bus_ctlr *hdminfo[NHDC]; struct bus_device *hddinfo[NHD]; int hdstd[] = { 0 }; int hdprobe(), hdslave(), hdstrategy(); void hdattach(); struct bus_driver hddriver = { hdprobe, hdslave, hdattach, 0, hdstd, "hd", hddinfo, "hdc", hdminfo, 0 }; /* * BIOS geometry. */ struct hdbios { int bg_ncyl; /* cylinders/unit */ int bg_ntrk; /* tracks/cylinder */ int bg_precomp; /* write precomp cylinder */ int bg_nsect; /* sectors/track */ } hdbios[NHD]; /* * Controller state. */ struct hdcsoftc { int sc_state; /* transfer fsm */ caddr_t sc_addr; /* buffer address */ int sc_resid; /* amount left to transfer */ int sc_amt; /* amount currently being transferred */ int sc_cnt; /* amount transferred per interrupt */ int sc_sn; /* sector number */ int sc_tn; /* track number */ int sc_cn; /* cylinder number */ int sc_recalerr; /* # recalibration errors */ int sc_ioerr; /* # i/o errors */ int sc_wticks; /* watchdog */ caddr_t sc_buf; /* buffer for unaligned requests */ } hdcsoftc[NHDC]; /* * Transfer states. */ #define IDLE 0 /* controller is idle */ #define SETPARAM 1 /* set disk parameters */ #define SETPARAMDONE 2 /* set parameters done */ #define RESTORE 3 /* recalibrate drive */ #define RESTOREDONE 4 /* recalibrate done */ #define TRANSFER 5 /* perform I/O transfer */ #define TRANSFERDONE 6 /* transfer done */ #define IDENTIFY 7 /* get drive info */ #define IDENTIFYDONE 8 /* get drive info done */ #define SETMULTI 9 /* set multiple mode count */ #define SETMULTIDONE 10 /* set multiple mode count done */ /* * Drive state. */ struct hdsoftc { int sc_flags; #define HDF_SETPARAM 0x001 /* set drive parameters before I/O operation */ #define HDF_RESTORE 0x002 /* drive needs recalibration */ #define HDF_WANT 0x004 /* some one is waiting for drive */ #define HDF_UNALIGNED 0x008 /* request is not a multiple of sector size */ #define HDF_SETMULTI 0x010 /* set multiple count before I/O operation */ #define HDF_MULTIDONE 0x020 /* multicnt field is valid */ #define HDF_IDENTDONE 0x040 /* identify command done */ #define HDF_LBA 0x080 /* use LBA mode */ int sc_multicnt; /* current multiple count */ int sc_abssn; /* absolute sector number (for {RD,WR}ABS) */ int sc_abscnt; /* absolute sector count */ int sc_openpart; /* bit mask of open partitions */ struct hdident sc_id; /* info returned by identify */ } hdsoftc[NHD]; struct buf hdtab[NHDC]; /* controller queues */ struct buf hdutab[NHD]; /* drive queues */ struct disklabel hdlabel[NHD]; /* disklabels -- incorrect info! */ struct diskpart array[NHD*MAX_IDE_PARTS]; /* partition info */ /* * To enable multiple mode, * set this, recompile, and reboot the machine. */ int hdenmulti = 0; char *hderrchk(); struct buf *geteblk(); int hdwstart = 0; void hdwatch(); /* * Probe for a controller. */ int hdprobe(xxx, um) int xxx; struct bus_ctlr *um; { struct hdcsoftc *hdc; if (um->unit >= NHDC) { printf("hdc%d: not configured\n", um->unit); return (0); } if (um->unit > 0) { /* XXX: only 1 controller */ printf("nhd:probe for 2+ controllers -- not implemented\n"); return (0); } /* * XXX: need real probe */ hdc = &hdcsoftc[um->unit]; if (!hdc->sc_buf) kmem_alloc(kernel_map, (vm_offset_t *)&hdc->sc_buf, I386_PGBYTES); take_ctlr_irq(um); return (1); } /* * Probe for a drive. */ int hdslave(ui) struct bus_device *ui; { int type; if (ui->unit >= NHD) { printf("hd%d: not configured\n", ui->unit); return (0); } if (ui->unit > 1) /* XXX: only 2 drives */ return (0); /* * Find out if drive exists by reading CMOS. */ outb(CMOS_ADDR, 0x12); type = inb(CMOS_DATA); if (ui->unit == 0) type >>= 4; type &= 0x0f; return (type); } /* * Attach a drive to the system. */ void hdattach(ui) struct bus_device *ui; { char *tbl; unsigned n; /* struct hdsoftc *sc = &hdsoftc[ui->unit]; */ struct disklabel *lp = &hdlabel[ui->unit]; struct hdbios *bg = &hdbios[ui->unit]; /* * Set up a temporary disklabel from BIOS parameters. * The actual partition table will be read during open. */ n = *(unsigned *)phystokv(ui->address); tbl = (unsigned char *)phystokv((n & 0xffff) + ((n >> 12) & 0xffff0)); bg->bg_ncyl = *(unsigned short *)tbl; bg->bg_ntrk = *(unsigned char *)(tbl + 2); bg->bg_precomp = *(unsigned short *)(tbl + 5); bg->bg_nsect = *(unsigned char *)(tbl + 14); fudge_bsd_label(lp, DTYPE_ESDI, bg->bg_ncyl*bg->bg_ntrk*bg->bg_nsect, bg->bg_ntrk, bg->bg_nsect, SECSIZE, 3); /* FORCE sector size to 512... */ printf(": ntrak(heads) %d, ncyl %d, nsec %d, size %u MB", lp->d_ntracks, lp->d_ncylinders, lp->d_nsectors, lp->d_secperunit * lp->d_secsize / (1024*1024)); } int hdopen(dev, mode) dev_t dev; int mode; { int unit = hdunit(dev), part = hdpart(dev) /*, error */; struct bus_device *ui; struct hdsoftc *sc; struct diskpart *label; if (unit >= NHD || (ui = hddinfo[unit]) == 0 || ui->alive == 0) return (ENXIO); if (!hdwstart) { hdwstart++; timeout(hdwatch, 0, hz); } sc = &hdsoftc[unit]; /* should this be changed so only gets called once, even if all partitions are closed and re-opened? */ if (sc->sc_openpart == 0) { hdinit(dev); if (sc->sc_flags & HDF_LBA) printf("hd%d: Using LBA mode\n", ui->unit); } /* Note: should set a bit in the label structure to ensure that aliasing prevents multiple instances to be opened. */ #if 0 if (part >= MAXPARTITIONS || lp->d_partitions[part].p_size == 0) return (ENXIO); #endif 0 label=lookup_part(&array[MAX_IDE_PARTS*unit], hdpart(dev)); if (!label) return (ENXIO); sc->sc_openpart |= 1 << part; return (0); } int hdclose(dev) dev_t dev; { int unit = hdunit(dev), s; struct hdsoftc *sc = &hdsoftc[unit]; sc->sc_openpart &= ~(1 << hdpart(dev)); if (sc->sc_openpart == 0) { s = splbio(); while (hdutab[unit].b_active) { sc->sc_flags |= HDF_WANT; assert_wait((event_t)sc, FALSE); thread_block((void (*)())0); } splx(s); } return (0); } int hdread(dev, ior) dev_t dev; io_req_t ior; { return (block_io(hdstrategy, minphys, ior)); } int hdwrite(dev, ior) dev_t dev; io_req_t ior; { return (block_io(hdstrategy, minphys, ior)); } int hdgetstat(dev, flavor, data, count) dev_t dev; dev_flavor_t flavor; dev_status_t data; mach_msg_type_number_t *count; { int unit = hdunit(dev), part = hdpart(dev); struct hdsoftc *sc = &hdsoftc[unit]; struct disklabel *lp = &hdlabel[unit]; struct buf *bp; struct diskpart *label; label=lookup_part(&array[MAX_IDE_PARTS*unit], hdpart(dev)); switch (flavor) { case DEV_GET_SIZE: if (label) { data[DEV_GET_SIZE_DEVICE_SIZE] = (label->size * lp->d_secsize); data[DEV_GET_SIZE_RECORD_SIZE] = lp->d_secsize; *count = DEV_GET_SIZE_COUNT; } else { /* Kevin: added checking here */ data[DEV_GET_SIZE_DEVICE_SIZE] = 0; data[DEV_GET_SIZE_RECORD_SIZE] = 0; *count = 0; } break; case DIOCGDINFO: case DIOCGDINFO - (0x10 << 16): dkgetlabel(lp, flavor, data, count); break; case V_GETPARMS: { struct disk_parms *dp; struct hdbios *bg = &hdbios[unit]; if (*count < (sizeof(struct disk_parms) / sizeof(int))) return (D_INVALID_OPERATION); dp = (struct disk_parms *)data; dp->dp_type = DPT_WINI; dp->dp_heads = lp->d_ntracks; dp->dp_cyls = lp->d_ncylinders; dp->dp_sectors = lp->d_nsectors; dp->dp_dosheads = bg->bg_ntrk; dp->dp_doscyls = bg->bg_ncyl; dp->dp_dossectors = bg->bg_nsect; dp->dp_secsiz = lp->d_secsize; dp->dp_ptag = 0; dp->dp_pflag = 0; if (label) { dp->dp_pstartsec = label->start; dp->dp_pnumsec = label->size; } else { /* added by Kevin */ dp->dp_pstartsec = -1; dp->dp_pnumsec = -1; } *count = sizeof(struct disk_parms) / sizeof(int); break; } case V_RDABS: if (*count < lp->d_secsize / sizeof(int)) { printf("hd%d: RDABS, bad size %d\n", unit, *count); return (EINVAL); } bp = geteblk(lp->d_secsize); bp->b_flags = B_READ | B_ABS; bp->b_blkno = sc->sc_abssn; bp->b_dev = dev; bp->b_bcount = lp->d_secsize; hdstrategy(bp); biowait(bp); if (bp->b_flags & B_ERROR) { printf("hd%d: RDABS failed\n", unit); brelse(bp); return (EIO); } bcopy(bp->b_un.b_addr, (caddr_t)data, lp->d_secsize); brelse(bp); *count = lp->d_secsize / sizeof(int); break; case V_VERIFY: { int i, amt, n, error = 0; bp = geteblk(I386_PGBYTES); bp->b_blkno = sc->sc_abssn; bp->b_dev = dev; amt = sc->sc_abscnt; n = I386_PGBYTES / lp->d_secsize; while (amt > 0) { i = (amt > n) ? n : amt; bp->b_bcount = i * lp->d_secsize; bp->b_flags = B_READ | B_ABS; hdstrategy(bp); biowait(bp); if (bp->b_flags & B_ERROR) { error = BAD_BLK; break; } amt -= bp->b_bcount; bp->b_blkno += i; } brelse(bp); data[0] = error; *count = 1; break; } default: return (D_INVALID_OPERATION); } return (0); } int hdsetstat(dev, flavor, data, count) dev_t dev; dev_flavor_t flavor; dev_status_t data; mach_msg_type_number_t count; { int unit = hdunit(dev); /* , part = hdpart(dev); */ int error = 0 /*, s */; struct hdsoftc *sc = &hdsoftc[unit]; struct disklabel *lp = &hdlabel[unit]; struct buf *bp; switch (flavor) { case DIOCWLABEL: case DIOCWLABEL - (0x10 << 16): break; case DIOCSDINFO: case DIOCSDINFO - (0x10 << 16): if (count != (sizeof(struct disklabel) / sizeof(int))) return (D_INVALID_SIZE); error = setdisklabel(lp, (struct disklabel *)data); if (error == 0 && (sc->sc_flags & HDF_LBA) == 0) sc->sc_flags |= HDF_SETPARAM; break; case DIOCWDINFO: case DIOCWDINFO - (0x10 << 16): if (count != (sizeof(struct disklabel) / sizeof(int))) return (D_INVALID_SIZE); error = setdisklabel(lp, (struct disklabel *)data); if (error == 0) { if ((sc->sc_flags & HDF_LBA) == 0) sc->sc_flags |= HDF_SETPARAM; error = hdwritelabel(dev); } break; case V_REMOUNT: hdinit(dev); break; case V_ABS: if (count != 1 && count != 2) return (D_INVALID_OPERATION); sc->sc_abssn = *(int *)data; if (sc->sc_abssn < 0 || sc->sc_abssn >= lp->d_secperunit) return (D_INVALID_OPERATION); if (count == 2) sc->sc_abscnt = *((int *)data + 1); else sc->sc_abscnt = 1; if (sc->sc_abscnt <= 0 || sc->sc_abssn + sc->sc_abscnt > lp->d_secperunit) return (D_INVALID_OPERATION); break; case V_WRABS: if (count < (lp->d_secsize / sizeof(int))) { printf("hd%d: WRABS, bad size %d\n", unit, count); return (D_INVALID_OPERATION); } bp = geteblk(lp->d_secsize); bcopy((caddr_t)data, bp->b_un.b_addr, lp->d_secsize); bp->b_flags = B_WRITE | B_ABS; bp->b_blkno = sc->sc_abssn; bp->b_bcount = lp->d_secsize; bp->b_dev = dev; hdstrategy(bp); biowait(bp); if (bp->b_flags & B_ERROR) { printf("hd%d: WRABS failed\n", unit); error = EIO; } brelse(bp); break; default: return (D_INVALID_OPERATION); } return (error); } int hddevinfo(dev, flavor, info) dev_t dev; int flavor; char *info; { switch (flavor) { case D_INFO_BLOCK_SIZE: *((int *)info) = SECSIZE; /* #defined to 512 */ break; default: return (D_INVALID_OPERATION); } return (0); } /* Kevin T. Van Maren: Added this low-level routine for the unified partition code. A pointer to this routine is passed, along with param* */ int ide_read_fun(struct ide_driver_info *param, int sectornum, char *buff) { struct buf *bp; bp = geteblk(param->sectorsize); bp->b_flags = B_READ | B_ABS; bp->b_bcount = param->sectorsize; bp->b_blkno = sectornum; /* WARNING: DEPENDS ON NUMBER OF BITS FOR PARTITIONS */ bp->b_dev = param->dev & ~0x3ff; hdstrategy(bp); biowait(bp); if ((bp->b_flags & B_ERROR) == 0) bcopy((char *)bp->b_un.b_addr, buff, param->sectorsize); else { printf("ERROR!\n"); return(B_ERROR); } brelse(bp); return(0); } /* * Initialize drive. */ int hdinit(dev) dev_t dev; { int unit = hdunit(dev); struct hdsoftc *sc = &hdsoftc[unit]; struct disklabel *lp = &hdlabel[unit], *dlp; struct buf *bp = 0; int numpart; struct ide_driver_info ide_param = { dev, /* bp, */ lp->d_secsize }; int ret; /* * Issue identify command. */ if ((sc->sc_flags & HDF_IDENTDONE) == 0) { sc->sc_flags |= HDF_IDENTDONE; bp = geteblk(lp->d_secsize); /* sector size #defined to 512 */ bp->b_flags = B_IDENTIFY; bp->b_dev = dev; hdstrategy(bp); biowait(bp); if ((bp->b_flags & B_ERROR) == 0) { bcopy((char *)bp->b_un.b_addr, (char *)&sc->sc_id, sizeof(struct hdident)); /* * Check if drive supports LBA mode. */ if (sc->sc_id.id_capability & 2) sc->sc_flags |= HDF_LBA; } } /* * Check if drive supports multiple read/write mode. */ hdmulti(dev); /* Note: label was fudged during attach! */ /* ensure the 'raw disk' can be accessed reliably */ array[MAX_IDE_PARTS*unit].start=0; array[MAX_IDE_PARTS*unit].size=lp->d_secperunit; /* fill in root for MY reads */ #if 0 array[MAX_IDE_PARTS*unit].subs=0; array[MAX_IDE_PARTS*unit].nsubs=0; array[MAX_IDE_PARTS*unit].type=0; array[MAX_IDE_PARTS*unit].fsys=0; #endif 0 numpart=get_only_partition(&ide_param, (*ide_read_fun), &array[MAX_IDE_PARTS*unit],MAX_IDE_PARTS,lp->d_secperunit, drive_name[unit]); printf("%s %d partitions found\n",drive_name[unit],numpart); if ((sc->sc_flags & HDF_LBA) == 0) sc->sc_flags |= HDF_SETPARAM; brelse(bp); return(ret); } /* * Check if drive supports multiple read/write mode. */ int hdmulti(dev) dev_t dev; { int unit = hdunit(dev); struct hdsoftc *sc = &hdsoftc[unit]; struct buf *bp; struct hdident *id; if (sc->sc_flags & HDF_MULTIDONE) return(0); sc->sc_flags |= HDF_MULTIDONE; if (hdenmulti == 0) return(0); /* * Get drive information by issuing IDENTIFY command. */ bp = geteblk(DEV_BSIZE); bp->b_flags = B_IDENTIFY; bp->b_dev = dev; hdstrategy(bp); biowait(bp); id = (struct hdident *)bp->b_un.b_addr; /* * If controller does not recognise IDENTIFY command, * or does not support multiple mode, clear count. */ if ((bp->b_flags & B_ERROR) || !id->id_multisize) sc->sc_multicnt = 0; else { sc->sc_multicnt = id->id_multisize; printf("hd%d: max multiple size %u", unit, sc->sc_multicnt); /* * Use 4096 since it is the minimum block size in FFS. */ if (sc->sc_multicnt > 4096 / 512) sc->sc_multicnt = 4096 / 512; printf(", using %u\n", sc->sc_multicnt); sc->sc_flags |= HDF_SETMULTI; } brelse(bp); } /* * Write label to disk. */ int hdwritelabel(dev) dev_t dev; { int unit = hdunit(dev), error = 0; long labelsect; struct buf *bp; struct disklabel *lp = &hdlabel[unit]; printf("hdwritelabel: no longer implemented\n"); #if 0 bp = geteblk(lp->d_secsize); bp->b_flags = B_READ | B_ABS; bp->b_blkno = LBLLOC + lp->d_partitions[PART_DISK].p_offset; bp->b_bcount = lp->d_secsize; bp->b_dev = dev; hdstrategy(bp); biowait(bp); if (bp->b_flags & B_ERROR) { printf("hd%d: hdwritelabel(), error reading disklabel\n",unit); error = EIO; goto out; } *(struct disklabel *)bp->b_un.b_addr = *lp; /* copy disk label */ bp->b_flags = B_WRITE | B_ABS; hdstrategy(bp); biowait(bp); if (bp->b_flags & B_ERROR) { printf("hd%d: hdwritelabel(), error writing disklabel\n",unit); error = EIO; } out: brelse(bp); #endif 0 return (error); } /* * Strategy routine. * Enqueue request on drive. */ int hdstrategy(bp) struct buf *bp; { int unit = hdunit(bp->b_dev), part = hdpart(bp->b_dev), s; long bn, sz, maxsz; struct buf *dp; struct hdsoftc *sc = &hdsoftc[unit]; struct bus_device *ui = hddinfo[unit]; struct disklabel *lp = &hdlabel[unit]; struct diskpart *label; if (bp->b_flags & B_IDENTIFY) { bp->b_cylin = 0; goto q; } bn = bp->b_blkno; if (bp->b_flags & B_ABS) goto q1; sz = (bp->b_bcount + lp->d_secsize - 1) / lp->d_secsize; label=lookup_part(&array[MAX_IDE_PARTS*unit], hdpart(bp->b_dev)); if (label) { maxsz = label->size; } else { bp->b_flags |= B_ERROR; bp->b_error = EINVAL; goto done; } if (bn < 0 || bn + sz > maxsz) { if (bn == maxsz) { bp->b_resid = bp->b_bcount; goto done; } sz = maxsz - bn; if (sz <= 0) { bp->b_flags |= B_ERROR; bp->b_error = EINVAL; goto done; } bp->b_bcount = sz * lp->d_secsize; } bn += lp->d_partitions[part].p_offset; bn += label->start; q1: bp->b_cylin = (sc->sc_flags & HDF_LBA) ? bn : bn / lp->d_secpercyl; q: dp = &hdutab[unit]; s = splbio(); disksort(dp, bp); if (!dp->b_active) { hdustart(ui); if (!hdtab[ui->mi->unit].b_active) hdstart(ui->mi); } splx(s); return(0); done: biodone(bp); return(0); } /* * Unit start routine. * Move request from drive to controller queue. */ int hdustart(ui) struct bus_device *ui; { struct buf *bp; struct buf *dp; bp = &hdutab[ui->unit]; if (bp->b_actf == 0) return(0); dp = &hdtab[ui->mi->unit]; if (dp->b_actf == 0) dp->b_actf = bp; else dp->b_actl->b_forw = bp; bp->b_forw = 0; dp->b_actl = bp; bp->b_active++; } /* * Start output on controller. */ int hdstart(um) struct bus_ctlr *um; { long bn; struct buf *bp; struct buf *dp; struct hdsoftc *sc; struct hdcsoftc *hdc; struct bus_device *ui; struct disklabel *lp; struct diskpart *label; /* * Pull a request from the controller queue. */ dp = &hdtab[um->unit]; if ((bp = dp->b_actf) == 0) return(0); bp = bp->b_actf; hdc = &hdcsoftc[um->unit]; ui = hddinfo[hdunit(bp->b_dev)]; sc = &hdsoftc[ui->unit]; lp = &hdlabel[ui->unit]; label = lookup_part(&array[MAX_IDE_PARTS*hdunit(bp->b_dev)], hdpart(bp->b_dev)); /* * Mark controller busy. */ dp->b_active++; if (bp->b_flags & B_IDENTIFY) { hdc->sc_state = IDENTIFY; goto doit; } /* * Figure out where this request is going. */ if (sc->sc_flags & HDF_LBA) hdc->sc_cn = bp->b_cylin; else { bn = bp->b_blkno; if ((bp->b_flags & B_ABS) == 0) { bn += label->start; /* partition must be valid */ } hdc->sc_cn = bp->b_cylin; hdc->sc_sn = bn % lp->d_secpercyl; hdc->sc_tn = hdc->sc_sn / lp->d_nsectors; hdc->sc_sn %= lp->d_nsectors; } /* * Set up for multi-sector transfer. */ hdc->sc_addr = bp->b_un.b_addr; hdc->sc_resid = bp->b_bcount; hdc->sc_wticks = 0; hdc->sc_recalerr = 0; hdc->sc_ioerr = 0; /* * Set initial transfer state. */ if (sc->sc_flags & HDF_SETPARAM) hdc->sc_state = SETPARAM; else if (sc->sc_flags & HDF_RESTORE) hdc->sc_state = RESTORE; else if (sc->sc_flags & HDF_SETMULTI) hdc->sc_state = SETMULTI; else hdc->sc_state = TRANSFER; doit: /* * Call transfer state routine to do the actual I/O. */ hdstate(um); } /* * Interrupt routine. */ int hdintr(ctlr) int ctlr; { int timedout; struct bus_ctlr *um = hdminfo[ctlr]; struct bus_device *ui; struct buf *bp; struct buf *dp = &hdtab[ctlr]; struct hdcsoftc *hdc = &hdcsoftc[ctlr]; if (!dp->b_active) { (void) inb(HD_STATUS(um->address)); printf("hdc%d: stray interrupt\n", ctlr); return(0); } timedout = hdc->sc_wticks >= OP_TIMEOUT; hdc->sc_wticks = 0; /* * Operation timed out, terminate request. */ if (timedout) { bp = dp->b_actf->b_actf; ui = hddinfo[hdunit(bp->b_dev)]; hderror("timed out", ui); hdsoftc[ui->unit].sc_flags |= HDF_RESTORE; bp->b_flags |= B_ERROR; bp->b_error = EIO; hddone(ui, bp); return(0); } /* * Let transfer state routine handle the rest. */ hdstate(um); } /* * Transfer finite state machine driver. */ int hdstate(um) struct bus_ctlr *um; { char *msg; int op; struct buf *bp; struct hdsoftc *sc; struct bus_device *ui; struct disklabel *lp; struct hdcsoftc *hdc = &hdcsoftc[um->unit]; struct hdbios *bg; bp = hdtab[um->unit].b_actf->b_actf; ui = hddinfo[hdunit(bp->b_dev)]; lp = &hdlabel[ui->unit]; sc = &hdsoftc[ui->unit]; bg = &hdbios[ui->unit]; /* * Ensure controller is not busy. */ if (!hdwait(um)) goto ctlr_err; while (1) switch (hdc->sc_state) { case SETPARAM: /* * Set drive parameters. */ outb(HD_DRVHD(um->address), 0xa0 | (ui->slave << 4) | (lp->d_ntracks - 1)); outb(HD_SECTCNT(um->address), lp->d_nsectors); outb(HD_CMD(um->address), CMD_SETPARAM); hdc->sc_state = SETPARAMDONE; return(0); case SETPARAMDONE: /* * Set parameters complete. */ if (msg = hderrchk(um)) goto bad; sc->sc_flags &= ~HDF_SETPARAM; hdc->sc_state = RESTORE; break; case RESTORE: /* * Recalibrate drive. */ outb(HD_DRVHD(um->address), 0xa0 | (ui->slave << 4)); outb(HD_CMD(um->address), CMD_RESTORE); hdc->sc_state = RESTOREDONE; return(0); case RESTOREDONE: /* * Recalibration complete. */ if (msg = hderrchk(um)) { if (++hdc->sc_recalerr == 2) goto bad; hdc->sc_state = RESTORE; break; } sc->sc_flags &= ~HDF_RESTORE; hdc->sc_recalerr = 0; if (sc->sc_flags & HDF_SETMULTI) hdc->sc_state = SETMULTI; else hdc->sc_state = TRANSFER; break; case TRANSFER: /* * Perform I/O transfer. */ sc->sc_flags &= ~HDF_UNALIGNED; hdc->sc_state = TRANSFERDONE; hdc->sc_amt = hdc->sc_resid / lp->d_secsize; if (hdc->sc_amt == 0) { sc->sc_flags |= HDF_UNALIGNED; hdc->sc_amt = 1; } else if (hdc->sc_amt > 256) hdc->sc_amt = 256; if (sc->sc_multicnt > 1 && hdc->sc_amt >= sc->sc_multicnt) { hdc->sc_cnt = sc->sc_multicnt; hdc->sc_amt -= hdc->sc_amt % hdc->sc_cnt; if (bp->b_flags & B_READ) op = CMD_READMULTI; else op = CMD_WRITEMULTI; } else { hdc->sc_cnt = 1; if (bp->b_flags & B_READ) op = CMD_READ; else op = CMD_WRITE; } if (sc->sc_flags & HDF_LBA) { outb(HD_DRVHD(um->address), (0xe0 | (ui->slave << 4) | ((hdc->sc_cn >> 24) & 0x0f))); outb(HD_SECT(um->address), hdc->sc_cn); outb(HD_CYLLO(um->address), hdc->sc_cn >> 8); outb(HD_CYLHI(um->address), hdc->sc_cn >> 16); } else { outb(HD_DRVHD(um->address), 0xa0 | (ui->slave << 4) | hdc->sc_tn); outb(HD_SECT(um->address), hdc->sc_sn + 1); outb(HD_CYLLO(um->address), hdc->sc_cn); outb(HD_CYLHI(um->address), hdc->sc_cn >> 8); } outb(HD_SECTCNT(um->address), hdc->sc_amt & 0xff); outb(HD_PRECOMP(um->address), bg->bg_precomp / 4); outb(HD_CMD(um->address), op); if ((bp->b_flags & B_READ) == 0) { int i; caddr_t buf; if (sc->sc_flags & HDF_UNALIGNED) { buf = hdc->sc_buf; bcopy(hdc->sc_addr, buf, hdc->sc_resid); bzero(buf + hdc->sc_resid, lp->d_secsize - hdc->sc_resid); } else buf = hdc->sc_addr; for (i = 0; i < 1000000; i++) if (inb(HD_STATUS(um->address)) & ST_DREQ) { loutw(HD_DATA(um->address), buf, hdc->sc_cnt * lp->d_secsize / 2); return(0); } goto ctlr_err; } return(0); case TRANSFERDONE: /* * Transfer complete. */ if (msg = hderrchk(um)) { if (++hdc->sc_ioerr == MAX_RETRIES) goto bad; /* * Every fourth attempt print a message * and recalibrate the drive. */ if (hdc->sc_ioerr & 3) hdc->sc_state = TRANSFER; else { hderror(msg, ui); hdc->sc_state = RESTORE; } break; } if (bp->b_flags & B_READ) { if (sc->sc_flags & HDF_UNALIGNED) { linw(HD_DATA(um->address), hdc->sc_buf, lp->d_secsize / 2); bcopy(hdc->sc_buf, hdc->sc_addr, hdc->sc_resid); } else linw(HD_DATA(um->address), hdc->sc_addr, hdc->sc_cnt * lp->d_secsize / 2); } hdc->sc_resid -= hdc->sc_cnt * lp->d_secsize; if (hdc->sc_resid <= 0) { bp->b_resid = 0; hddone(ui, bp); return(0); } if (sc->sc_flags & HDF_LBA) hdc->sc_cn += hdc->sc_cnt; else { hdc->sc_sn += hdc->sc_cnt; while (hdc->sc_sn >= lp->d_nsectors) { hdc->sc_sn -= lp->d_nsectors; if (++hdc->sc_tn == lp->d_ntracks) { hdc->sc_tn = 0; hdc->sc_cn++; } } } hdc->sc_ioerr = 0; hdc->sc_addr += hdc->sc_cnt * lp->d_secsize; hdc->sc_amt -= hdc->sc_cnt; if (hdc->sc_amt == 0) { hdc->sc_state = TRANSFER; break; } if ((bp->b_flags & B_READ) == 0) { int i; for (i = 0; i < 1000000; i++) if (inb(HD_STATUS(um->address)) & ST_DREQ) { loutw(HD_DATA(um->address), hdc->sc_addr, hdc->sc_cnt * lp->d_secsize / 2); return(0); } goto ctlr_err; } return(0); case IDENTIFY: /* * Get drive info. */ hdc->sc_state = IDENTIFYDONE; outb(HD_DRVHD(um->address), 0xa0 | (ui->slave << 4)); outb(HD_CMD(um->address), CMD_IDENTIFY); return(0); case IDENTIFYDONE: /* * Get drive info complete. */ if (msg = hderrchk(um)) goto bad; linw(HD_DATA(um->address), (u_short *)bp->b_un.b_addr, 256); hddone(ui, bp); return(0); case SETMULTI: /* * Set multiple mode count. */ hdc->sc_state = SETMULTIDONE; outb(HD_DRVHD(um->address), 0xa0 | (ui->slave << 4)); outb(HD_SECTCNT(um->address), sc->sc_multicnt); outb(HD_CMD(um->address), CMD_SETMULTI); return(0); case SETMULTIDONE: /* * Set multiple mode count complete. */ sc->sc_flags &= ~HDF_SETMULTI; if (msg = hderrchk(um)) { sc->sc_multicnt = 0; goto bad; } hdc->sc_state = TRANSFER; break; default: printf("hd%d: invalid state\n", ui->unit); panic("hdstate"); /*NOTREACHED*/ } ctlr_err: msg = "controller error"; bad: hderror(msg, ui); bp->b_flags |= B_ERROR; bp->b_error = EIO; sc->sc_flags |= HDF_RESTORE; hddone(ui, bp); } /* * Terminate current request and start * any others that are queued. */ int hddone(ui, bp) struct bus_device *ui; struct buf *bp; { struct bus_ctlr *um = ui->mi; struct hdsoftc *sc = &hdsoftc[ui->unit]; struct hdcsoftc *hdc = &hdcsoftc[um->unit]; struct buf *dp = &hdtab[um->unit]; sc->sc_flags &= ~HDF_UNALIGNED; /* * Remove this request from queue. */ hdutab[ui->unit].b_actf = bp->b_actf; biodone(bp); bp = &hdutab[ui->unit]; dp->b_actf = bp->b_forw; /* * Mark controller and drive idle. */ dp->b_active = 0; bp->b_active = 0; hdc->sc_state = IDLE; /* * Start up other requests. */ hdustart(ui); hdstart(um); /* * Wakeup anyone waiting for drive. */ if (sc->sc_flags & HDF_WANT) { sc->sc_flags &= ~HDF_WANT; wakeup((caddr_t)sc); } } /* * Wait for controller to be idle. */ int hdwait(um) struct bus_ctlr *um; { int i, status; for (i = 0; i < 1000000; i++) { status = inb(HD_STATUS(um->address)); if ((status & ST_BUSY) == 0 && (status & ST_READY)) return (status); } return (0); } /* * Check for errors on completion of an operation. */ char * hderrchk(um) struct bus_ctlr *um; { int status; status = inb(HD_STATUS(um->address)); if (status & ST_WRTFLT) return ("write fault"); if (status & ST_ERROR) { status = inb(HD_ERROR(um->address)); if (status & ERR_DAM) return ("data address mark not found"); if (status & ERR_TR0) return ("track 0 not found"); if (status & ERR_ID) return ("sector not found"); if (status & ERR_ECC) return ("uncorrectable ECC error"); if (status & ERR_BADBLK) return ("bad block detected"); if (status & ERR_ABORT) return ("command aborted"); return ("hard error"); } return (NULL); } /* * Print an error message. */ hderror(msg, ui) char *msg; struct bus_device *ui; { char *op; int prn_sn = 0; struct hdcsoftc *hdc = &hdcsoftc[ui->mi->unit]; switch (hdc->sc_state) { case SETPARAM: case SETPARAMDONE: op = "SETPARAM: "; break; case RESTORE: case RESTOREDONE: op = "RESTORE: "; break; case TRANSFER: case TRANSFERDONE: if (hdutab[ui->unit].b_actf->b_flags & B_READ) op = "READ: "; else op = "WRITE: "; prn_sn = 1; break; case IDENTIFY: case IDENTIFYDONE: op = "IDENTIFY: "; break; case SETMULTI: case SETMULTIDONE: op = "SETMULTI: "; break; default: op = ""; break; } printf("hd%d: %s%s", ui->unit, op, msg); if (prn_sn) { if (hdsoftc[ui->unit].sc_flags & HDF_LBA) printf(", bn %d", hdc->sc_cn); else printf(", cn %d tn %d sn %d", hdc->sc_cn, hdc->sc_tn, hdc->sc_sn + 1); } printf("\n"); } /* * Watchdog routine. * Check for any hung operations. */ void hdwatch() { int unit, s; timeout(hdwatch, 0, hz); s = splbio(); for (unit = 0; unit < NHDC; unit++) if (hdtab[unit].b_active && ++hdcsoftc[unit].sc_wticks >= OP_TIMEOUT) hdintr(unit); splx(s); } #endif /* NHD > 0 && !LINUX_DEV */