summaryrefslogtreecommitdiff
path: root/i386/i386at/nhd.c
diff options
context:
space:
mode:
authorThomas Bushnell <thomas@gnu.org>1997-02-25 21:28:37 +0000
committerThomas Bushnell <thomas@gnu.org>1997-02-25 21:28:37 +0000
commitf07a4c844da9f0ecae5bbee1ab94be56505f26f7 (patch)
tree12b07c7e578fc1a5f53dbfde2632408491ff2a70 /i386/i386at/nhd.c
Initial source
Diffstat (limited to 'i386/i386at/nhd.c')
-rw-r--r--i386/i386at/nhd.c1430
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 */