summaryrefslogtreecommitdiff
path: root/sys/dev/ata/ata-disk.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/ata/ata-disk.c')
-rw-r--r--sys/dev/ata/ata-disk.c585
1 files changed, 585 insertions, 0 deletions
diff --git a/sys/dev/ata/ata-disk.c b/sys/dev/ata/ata-disk.c
new file mode 100644
index 000000000000..bfbf877e791c
--- /dev/null
+++ b/sys/dev/ata/ata-disk.c
@@ -0,0 +1,585 @@
+/*-
+ * Copyright (c) 1998,1999 Søren Schmidt
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer,
+ * without modification, immediately at the beginning of the file.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $Id: ata-disk.c,v 1.14 1999/03/01 21:03:15 sos Exp sos $
+ */
+
+#include "ata.h"
+#include "atadisk.h"
+#include "opt_devfs.h"
+
+#if NATA > 0 && NATADISK > 0
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/proc.h>
+#include <sys/device.h>
+#include <sys/malloc.h>
+#include <sys/buf.h>
+#include <sys/disklabel.h>
+#include <sys/diskslice.h>
+#include <sys/devicestat.h>
+#include <sys/fcntl.h>
+#include <sys/conf.h>
+#include <sys/stat.h>
+#ifdef DEVFS
+#include <sys/devfsext.h>
+#endif
+#include <machine/clock.h>
+#include <i386/isa/isa.h>
+#include <i386/isa/isa_device.h>
+#include <dev/ata/ata-all.h>
+#include <dev/ata/ata-disk.h>
+
+static d_open_t adopen;
+static d_close_t adclose;
+static d_write_t adwrite;
+static d_read_t adread;
+static d_ioctl_t adioctl;
+static d_strategy_t adstrategy;
+static d_psize_t adpsize;
+
+#define BDEV_MAJOR 0
+#define CDEV_MAJOR 3
+static struct cdevsw ad_cdevsw = {
+ adopen, adclose, adread, adwrite,
+ adioctl, nostop, nullreset, nodevtotty,
+ seltrue, nommap, adstrategy, "ad",
+ NULL, -1, nodump, adpsize,
+ D_DISK, 0, -1
+};
+
+/* misc defines */
+#define UNIT(dev) (dev>>3 & 0x1f) /* assume 8 minor # per unit */
+#define NUNIT 16 /* max # of devices */
+
+/* prototypes */
+static void ad_attach(void *);
+static void ad_strategy(struct buf *);
+static void ad_start(struct ad_softc *);
+static void ad_sleep(struct ad_softc *, int8_t *);
+static int32_t ad_command(struct ad_softc *, u_int32_t, u_int32_t, u_int32_t, u_int32_t, u_int32_t);
+static int8_t ad_version(u_int16_t);
+static void ad_drvinit(void);
+
+static struct ad_softc *adtab[NUNIT];
+static int32_t adnlun = 0; /* number of config'd drives */
+static struct intr_config_hook *ad_attach_hook;
+
+static void
+ad_attach(void *notused)
+{
+ struct ad_softc *adp;
+ int32_t ctlr, dev;
+ int8_t model_buf[40+1];
+ int8_t revision_buf[8+1];
+
+ /* now, run through atadevices and look for ATA disks */
+ for (ctlr=0; ctlr<MAXATA && atadevices[ctlr]; ctlr++) {
+ for (dev=0; dev<2; dev++) {
+ if (atadevices[ctlr]->ata_parm[dev]) {
+ adp = adtab[adnlun];
+ if (adp)
+ printf("ad%d: unit already attached\n", adnlun);
+ adp = malloc(sizeof(struct ad_softc), M_DEVBUF, M_NOWAIT);
+ if (adp == NULL)
+ printf("ad%d: failed to allocate driver storage\n", adnlun);
+ bzero(adp, sizeof(struct ad_softc));
+ adp->controller = atadevices[ctlr];
+ adp->ata_parm = atadevices[ctlr]->ata_parm[dev];
+ adp->unit = (dev == 0) ? ATA_MASTER : ATA_SLAVE;
+ adp->cylinders = adp->ata_parm->cylinders;
+ adp->heads = adp->ata_parm->heads;
+ adp->sectors = adp->ata_parm->sectors;
+ adp->total_secs = adp->ata_parm->lbasize;
+
+ /* support multiple sectors / interrupt ? */
+ if (ad_command(adp, ATA_C_SET_MULTI, 0, 0, 0, 16))
+ adp->transfersize = DEV_BSIZE;
+ else {
+ if (ata_wait(adp->controller, ATA_S_DRDY) < 0)
+ adp->transfersize = DEV_BSIZE;
+ else
+ adp->transfersize = 16*DEV_BSIZE;
+ }
+ bpack(adp->ata_parm->model, model_buf, sizeof(model_buf));
+ bpack(adp->ata_parm->revision, revision_buf,
+ sizeof(revision_buf));
+ printf("ad%d: <%s/%s> ATA-%c disk at ata%d as %s\n",
+ adnlun,
+ model_buf, revision_buf,
+ ad_version(adp->ata_parm->versmajor),
+ ctlr,
+ (adp->unit == ATA_MASTER) ? "master" : "slave ");
+ printf("ad%d: %luMB (%u sectors), "
+ "%u cyls, %u heads, %u S/T, %u B/S\n",
+ adnlun,
+ adp->total_secs / ((1024L * 1024L) / DEV_BSIZE),
+ adp->total_secs,
+ adp->cylinders,
+ adp->heads,
+ adp->sectors,
+ DEV_BSIZE);
+ printf("ad%d: %d secs/int, %d depth queue \n",
+ adnlun, adp->transfersize / DEV_BSIZE,
+ adp->ata_parm->queuelen & 0x1f);
+ devstat_add_entry(&adp->stats, "ad", adnlun, DEV_BSIZE,
+ DEVSTAT_NO_ORDERED_TAGS,
+ DEVSTAT_TYPE_DIRECT | DEVSTAT_TYPE_IF_IDE,
+ 0x180);
+ bufq_init(&adp->queue);
+ adtab[adnlun++] = adp;
+ }
+ }
+ }
+ config_intrhook_disestablish(ad_attach_hook);
+}
+
+static int32_t
+adopen(dev_t dev, int32_t flags, int32_t fmt, struct proc *p)
+{
+ int32_t lun = UNIT(dev);
+ struct ad_softc *adp;
+ struct disklabel label;
+ int32_t error;
+
+#ifdef AD_DEBUG
+printf("adopen: lun=%d adnlun=%d\n", lun, adnlun);
+#endif
+ if (lun >= adnlun || !(adp = adtab[lun]))
+ return ENXIO;
+
+ /* spinwait if anybody else is reading the disk label */
+ while (adp->flags & AD_F_LABELLING)
+ tsleep((caddr_t)&adp->flags, PZERO - 1, "adop1", 1);
+
+ /* protect agains label race */
+ adp->flags |= AD_F_LABELLING;
+
+ /* build disklabel and initilize slice tables */
+ bzero(&label, sizeof label);
+ label.d_secsize = DEV_BSIZE;
+ label.d_nsectors = adp->sectors;
+ label.d_ntracks = adp->heads;
+ label.d_ncylinders = adp->cylinders;
+ label.d_secpercyl = adp->sectors * adp->heads;
+ label.d_secperunit = adp->total_secs;
+
+ error = dsopen("ad", dev, fmt, 0, &adp->slices, &label, ad_strategy,
+ (ds_setgeom_t *)NULL, &ad_cdevsw);
+
+ adp->flags &= ~AD_F_LABELLING;
+ ad_sleep(adp, "adop2");
+ return error;
+}
+
+static int32_t
+adclose(dev_t dev, int32_t flags, int32_t fmt, struct proc *p)
+{
+ int32_t lun = UNIT(dev);
+ struct ad_softc *adp;
+
+#ifdef AD_DEBUG
+printf("adclose: lun=%d adnlun=%d\n", lun, adnlun);
+#endif
+ if (lun >= adnlun || !(adp = adtab[lun]))
+ return ENXIO;
+
+ dsclose(dev, fmt, adp->slices);
+ return 0;
+}
+
+static int32_t
+adread(dev_t dev, struct uio *uio, int32_t ioflag)
+{
+ return physio(adstrategy, NULL, dev, 1, minphys, uio);
+}
+
+static int32_t
+adwrite(dev_t dev, struct uio *uio, int32_t ioflag)
+{
+ return physio(adstrategy, NULL, dev, 0, minphys, uio);
+}
+
+static int32_t
+adioctl(dev_t dev, u_long cmd, caddr_t addr, int32_t flags, struct proc *p)
+{
+ struct ad_softc *adp;
+ int32_t lun = UNIT(dev);
+ int32_t error = 0;
+
+ if (lun >= adnlun || !(adp = adtab[lun]))
+ return ENXIO;
+
+ ad_sleep(adp, "adioct");
+ error = dsioctl("sd", dev, cmd, addr, flags, &adp->slices,
+ ad_strategy, (ds_setgeom_t *)NULL);
+
+ if (error != ENOIOCTL)
+ return error;
+ return ENOTTY;
+}
+
+static int32_t
+adpsize(dev_t dev)
+{
+ struct ad_softc *adp;
+ int32_t lun = UNIT(dev);
+
+ if (lun >= adnlun || !(adp = adtab[lun]))
+ return -1;
+ return (dssize(dev, &adp->slices, adopen, adclose));
+}
+
+static void
+adstrategy(struct buf *bp)
+{
+ struct ad_softc *adp;
+ int32_t lun = UNIT(bp->b_dev);
+ int32_t s;
+
+#ifdef AD_DEBUG
+printf("adstrategy: entered\n");
+#endif
+ if (lun >= adnlun || bp->b_blkno < 0 || !(adp = adtab[lun])
+ || bp->b_bcount % DEV_BSIZE != 0) {
+ bp->b_error = EINVAL;
+ bp->b_flags |= B_ERROR;
+ goto done;
+ }
+
+ if (dscheck(bp, adp->slices) <= 0)
+ goto done;
+
+ s = splbio();
+
+ /* hang around if somebody else is labelling */
+ if (adp->flags & AD_F_LABELLING)
+ ad_sleep(adp, "adlab");
+
+ bufqdisksort(&adp->queue, bp);
+
+ if (!adp->active)
+ ad_start(adp);
+
+ if (!adp->controller->active)
+ ata_start(adp->controller);
+
+ devstat_start_transaction(&adp->stats);
+
+ splx(s);
+ return;
+
+done:
+ s = splbio();
+ biodone(bp);
+ splx(s);
+}
+
+static void
+ad_strategy(struct buf *bp)
+{
+ adstrategy(bp);
+}
+
+static void
+ad_start(struct ad_softc *adp)
+{
+ struct buf *bp;
+
+#ifdef AD_DEBUG
+printf("ad_start:\n");
+#endif
+ /* newer called when adp->active != 0 SOS */
+ if (adp->active)
+ return;
+
+ if (!(bp = bufq_first(&adp->queue)))
+ return;
+
+ bp->b_driver1 = adp;
+ bufq_remove(&adp->queue, bp);
+
+ /* link onto controller queue */
+ bufq_insert_tail(&adp->controller->ata_queue, bp);
+
+ /* mark the drive as busy */
+ adp->active = 1;
+}
+
+void
+ad_transfer(struct buf *bp)
+{
+ struct ad_softc *adp;
+ u_int32_t blknum, secsprcyl;
+ u_int32_t cylinder, head, sector, count, command;
+
+ /* get request params */
+ adp = bp->b_driver1;
+
+ /* calculate transfer details */
+ blknum = bp->b_pblkno + (adp->donecount / DEV_BSIZE);
+
+#ifdef AD_DEBUG
+ printf("ad_transfer: blknum=%d\n", blknum);
+#endif
+ if (adp->donecount == 0) {
+
+ /* setup transfer parameters */
+ adp->bytecount = bp->b_bcount;
+ secsprcyl = adp->sectors * adp->heads;
+ cylinder = blknum / secsprcyl;
+ head = (blknum % secsprcyl) / adp->sectors;
+ sector = blknum % adp->sectors;
+ count = howmany(adp->bytecount, DEV_BSIZE);
+
+ if (count > 255) /* SOS */
+ printf("ad_transfer: count=%d\n", count);
+
+
+ /* setup transfer length if multible sector access present */
+ adp->currentsize = min(adp->bytecount, adp->transfersize);
+ if (adp->currentsize > DEV_BSIZE)
+ command = (bp->b_flags&B_READ) ? ATA_C_READ_MULTI:ATA_C_WRITE_MULTI;
+ else
+ command = (bp->b_flags&B_READ) ? ATA_C_READ : ATA_C_WRITE;
+
+ /* ready to issue command ? */
+ while (ata_wait(adp->controller, 0) < 0) {
+ printf("ad_transfer: timeout waiting to give command");
+ /*ata_unwedge(adp->controller); SOS */
+ }
+
+ outb(adp->controller->ioaddr + ATA_DRIVE, ATA_D_IBM | adp->unit | head);
+ outb(adp->controller->ioaddr + ATA_PRECOMP, 0); /* no precompensation */
+ outb(adp->controller->ioaddr + ATA_CYL_LSB, cylinder);
+ outb(adp->controller->ioaddr + ATA_CYL_MSB, cylinder >> 8);
+ outb(adp->controller->ioaddr + ATA_SECTOR, sector + 1);
+ outb(adp->controller->ioaddr + ATA_COUNT, count);
+/*
+ if (ata_wait(adp->controller, ATA_S_DRDY) < 0)
+ printf("ad_transfer: timeout waiting to send command");
+*/
+ outb(adp->controller->ioaddr + ATA_CMD, command);
+ }
+
+ /* if this is a read operation, return and wait for interrupt */
+ if (bp->b_flags & B_READ) {
+#ifdef AD_DEBUG
+ printf("ad_transfer: return waiting to read data\n");
+#endif
+ return;
+ }
+
+ /* ready to write data ? */
+ if (ata_wait(adp->controller, ATA_S_DRDY | ATA_S_DSC | ATA_S_DRQ) < 0) {
+ printf("ad_transfer: timeout waiting for DRQ");
+ }
+
+ /* calculate transfer length */
+ adp->currentsize = min(adp->bytecount, adp->transfersize);
+
+ /* output the data */
+#if 0
+ outsw(adp->controller->ioaddr + ATA_DATA,
+ (void *)((int32_t)bp->b_data + adp->donecount),
+ adp->currentsize / sizeof(int16_t));
+#else
+ outsl(adp->controller->ioaddr + ATA_DATA,
+ (void *)((int32_t)bp->b_data + adp->donecount),
+ adp->currentsize / sizeof(int32_t));
+#endif
+ adp->bytecount -= adp->currentsize;
+#ifdef AD_DEBUG
+ printf("ad_transfer: return wrote data\n");
+#endif
+}
+
+void
+ad_interrupt(struct buf *bp)
+{
+ struct ad_softc *adp = bp->b_driver1;
+
+ /* finish DMA stuff */
+
+ /* get drive status */
+ if (ata_wait(adp->controller, 0) < 0)
+ printf("ad_interrupt: timeout waiting for status");
+
+ if (adp->controller->status & (ATA_S_ERROR | ATA_S_CORR)) {
+oops:
+ printf("ad%d: status=%02x error=%02x\n",
+ adp->unit, adp->controller->status, adp->controller->error);
+ if (adp->controller->status & ATA_S_ERROR) {
+ printf("ad_interrupt: hard error");
+ bp->b_error = EIO;
+ bp->b_flags |= B_ERROR;
+ }
+ if (adp->controller->status & ATA_S_CORR)
+ printf("ad_interrupt: soft ECC");
+ }
+ /* if this was a read operation, get the data */
+ if (((bp->b_flags & (B_READ | B_ERROR)) == B_READ) && adp->active) {
+
+ /* ready to receive data? */
+ if ((adp->controller->status & (ATA_S_DRDY | ATA_S_DSC | ATA_S_DRQ))
+ != (ATA_S_DRDY | ATA_S_DSC | ATA_S_DRQ))
+ printf("ad_interrupt: read interrupt arrived early");
+
+ if (ata_wait(adp->controller, ATA_S_DRDY | ATA_S_DSC | ATA_S_DRQ) != 0){
+ printf("ad_interrupt: read error detected late");
+ goto oops;
+ }
+
+ /* calculate transfer length */
+ adp->currentsize = min(adp->bytecount, adp->currentsize);
+
+ /* data are ready, get them */
+#if 0
+ insw(adp->controller->ioaddr + ATA_DATA,
+ (void *)((int32_t)bp->b_data + adp->donecount),
+ adp->currentsize / sizeof(int16_t));
+#else
+ insl(adp->controller->ioaddr + ATA_DATA,
+ (void *)((int32_t)bp->b_data + adp->donecount),
+ adp->currentsize / sizeof(int32_t));
+#endif
+ adp->bytecount -= adp->currentsize;
+#ifdef AD_DEBUG
+ printf("ad_interrupt: read in data\n");
+#endif
+ }
+
+ /* finish up this tranfer, check for more work on this buffer */
+ if (adp->controller->active) {
+ if ((bp->b_flags & B_ERROR) == 0) {
+ adp->donecount += adp->currentsize;
+#ifdef AD_DEBUG
+ printf("ad_interrupt: %s operation OK\n", (bp->b_flags & B_READ)?"R":"W");
+#endif
+ if (adp->bytecount > 0) {
+ ad_transfer(bp); /* MESSY!! only needed for W */
+ return;
+ }
+ }
+ bufq_remove(&adp->controller->ata_queue, bp);
+ bp->b_resid = bp->b_bcount - adp->donecount;
+ adp->donecount = 0;
+ devstat_end_transaction(&adp->stats, bp->b_bcount - bp->b_resid,
+ DEVSTAT_TAG_NONE,
+ (bp->b_flags & B_READ) ?
+ DEVSTAT_READ : DEVSTAT_WRITE);
+ biodone(bp);
+ adp->active = 0;
+ }
+ adp->controller->active = ATA_IDLE;
+ ad_start(adp);
+#ifdef AD_DEBUG
+ printf("ad_interrupt: completed\n");
+#endif
+ ata_start(adp->controller);
+}
+
+static void
+ad_sleep(struct ad_softc *adp, int8_t *mesg)
+{
+ int32_t s = splbio();
+
+ while (adp->controller->active)
+ tsleep((caddr_t)&adp->controller->active, PZERO - 1, mesg, 1);
+ splx(s);
+}
+
+static int32_t
+ad_command(struct ad_softc *adp, u_int32_t command,
+ u_int32_t cylinder, u_int32_t head, u_int32_t sector,
+ u_int32_t count)
+{
+ /* ready to issue command ? */
+ while (ata_wait(adp->controller, 0) < 0) {
+ printf("ad_transfer: timeout waiting to give command");
+ return -1;
+ }
+
+ outb(adp->controller->ioaddr + ATA_DRIVE, ATA_D_IBM | adp->unit | head);
+ outb(adp->controller->ioaddr + ATA_PRECOMP, 0); /* no precompensation */
+ outb(adp->controller->ioaddr + ATA_CYL_LSB, cylinder);
+ outb(adp->controller->ioaddr + ATA_CYL_MSB, cylinder >> 8);
+ outb(adp->controller->ioaddr + ATA_SECTOR, sector + 1);
+ outb(adp->controller->ioaddr + ATA_COUNT, count);
+/*
+ if (ata_wait(adp->controller, ATA_S_DRDY) < 0) {
+ printf("ad_transfer: timeout waiting to send command");
+ return -1;
+ }
+*/
+ adp->controller->active = ATA_IGNORE_INTR;
+ outb(adp->controller->ioaddr + ATA_CMD, command);
+ return 0;
+}
+
+static int8_t
+ad_version(u_int16_t version)
+{
+ int32_t bit;
+
+ if (version == 0xffff)
+ return '?';
+ for (bit = 15; bit >= 0; bit--)
+ if (version & (1<<bit))
+ return ('0' + bit);
+ return '?';
+}
+
+static void
+ad_drvinit(void)
+{
+ static ad_devsw_installed = 0;
+
+ if (!ad_devsw_installed) {
+ cdevsw_add_generic(BDEV_MAJOR, CDEV_MAJOR, &ad_cdevsw);
+ ad_devsw_installed = 1;
+ }
+ /* register callback for when interrupts are enabled */
+ if (!(ad_attach_hook =
+ (struct intr_config_hook *)malloc(sizeof(struct intr_config_hook),
+ M_TEMP, M_NOWAIT))) {
+ printf("ad: malloc attach_hook failed\n");
+ return;
+ }
+ bzero(ad_attach_hook, sizeof(struct intr_config_hook));
+
+ ad_attach_hook->ich_func = ad_attach;
+ if (config_intrhook_establish(ad_attach_hook) != 0) {
+ printf("ad: config_intrhook_establish failed\n");
+ free(ad_attach_hook, M_TEMP);
+ }
+}
+
+SYSINIT(addev, SI_SUB_DRIVERS, SI_ORDER_SECOND, ad_drvinit, NULL)
+#endif /* NATA && NATADISK */