diff options
author | Thomas Bushnell <thomas@gnu.org> | 1997-02-25 21:28:37 +0000 |
---|---|---|
committer | Thomas Bushnell <thomas@gnu.org> | 1997-02-25 21:28:37 +0000 |
commit | f07a4c844da9f0ecae5bbee1ab94be56505f26f7 (patch) | |
tree | 12b07c7e578fc1a5f53dbfde2632408491ff2a70 /i386/i386at/nhd.c |
Initial source
Diffstat (limited to 'i386/i386at/nhd.c')
-rw-r--r-- | i386/i386at/nhd.c | 1430 |
1 files changed, 1430 insertions, 0 deletions
diff --git a/i386/i386at/nhd.c b/i386/i386at/nhd.c new file mode 100644 index 0000000..72b4cfc --- /dev/null +++ b/i386/i386at/nhd.c @@ -0,0 +1,1430 @@ +/* + * 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 <hd.h> +#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 <sys/types.h> +#include <sys/ioctl.h> +#include "vm_param.h" +#include <kern/time_out.h> +#include <vm/vm_kern.h> +#include <vm/pmap.h> +#include <device/param.h> +#include <device/buf.h> +#include <device/errno.h> +#include <device/device_types.h> +#include <device/disk_status.h> +#include <chips/busses.h> +#include <i386/machspl.h> +#include <i386/pio.h> +#include <i386at/cram.h> +#include <i386at/disk.h> +#include <i386at/nhdreg.h> + +#include <scsi/rz_labels.h> + + +/* 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 */ |