summaryrefslogtreecommitdiffstats
path: root/sys/dev/ata/ata-lowlevel.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/ata/ata-lowlevel.c')
-rw-r--r--sys/dev/ata/ata-lowlevel.c787
1 files changed, 787 insertions, 0 deletions
diff --git a/sys/dev/ata/ata-lowlevel.c b/sys/dev/ata/ata-lowlevel.c
new file mode 100644
index 0000000..d38037e
--- /dev/null
+++ b/sys/dev/ata/ata-lowlevel.c
@@ -0,0 +1,787 @@
+/*-
+ * Copyright (c) 1998 - 2003 Søren Schmidt <sos@FreeBSD.org>
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#include "opt_ata.h"
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/ata.h>
+#include <sys/kernel.h>
+#include <sys/conf.h>
+#include <sys/bus.h>
+#include <sys/mutex.h>
+#include <sys/taskqueue.h>
+#include <machine/bus.h>
+#include <sys/rman.h>
+#include <dev/ata/ata-all.h>
+
+/* prototypes */
+static int ata_transaction(struct ata_request *request);
+static void ata_interrupt(void *data);
+static void ata_reset(struct ata_channel *ch);
+static int ata_wait(struct ata_device *atadev, u_int8_t mask);
+static int ata_command(struct ata_device *atadev, u_int8_t command, u_int64_t lba, u_int16_t count, u_int16_t feature);
+static void ata_pio_read(struct ata_request *request, int length);
+static void ata_pio_write(struct ata_request *request, int length);
+
+/* local vars */
+static int atadebug = 0;
+
+/*
+ * low level ATA functions
+ */
+void
+ata_generic_hw(struct ata_channel *ch)
+{
+ ch->hw.reset = ata_reset;
+ ch->hw.transaction = ata_transaction;
+ ch->hw.interrupt = ata_interrupt;
+}
+
+/* must be called with ATA channel locked */
+static int
+ata_transaction(struct ata_request *request)
+{
+ /* record the request as running */
+ request->device->channel->running = request;
+
+ /* disable ATAPI DMA writes if HW doesn't support it */
+ if (request->flags & (ATA_R_ATAPI | ATA_R_DMA | ATA_R_WRITE) &&
+ request->device->channel->flags & ATA_ATAPI_DMA_RO)
+ request->flags &= ~ATA_R_DMA;
+
+ switch (request->flags & (ATA_R_ATAPI | ATA_R_DMA)) {
+
+ /* ATA PIO data transfer and control commands */
+ default:
+ {
+ /* record command direction here as our request might be done later */
+ int write = (request->flags & ATA_R_WRITE);
+
+ /* issue command */
+ if (ata_command(request->device, request->u.ata.command,
+ request->u.ata.lba, request->u.ata.count,
+ request->u.ata.feature)) {
+ ata_prtdev(request->device, "error issueing PIO command\n");
+ request->result = EIO;
+ return ATA_OP_FINISHED;
+ }
+
+ /* if write command output the data */
+ if (write) {
+ if (ata_wait(request->device,
+ (ATA_S_READY | ATA_S_DSC | ATA_S_DRQ)) < 0) {
+ ata_prtdev(request->device,"timeout waiting for write DRQ");
+ request->result = EIO;
+ return ATA_OP_FINISHED;
+ }
+ ata_pio_write(request, request->transfersize);
+ }
+ }
+ /* return and wait for interrupt */
+ return ATA_OP_CONTINUES;
+
+ /* ATA DMA data transfer commands */
+ case ATA_R_DMA:
+ /* check sanity and setup DMA engine */
+ if (request->device->channel->dma->setup(request->device,
+ request->data,
+ request->bytecount)) {
+ ata_prtdev(request->device, "setting up DMA failed\n");
+ request->result = EIO;
+ return ATA_OP_FINISHED;
+ }
+
+ /* issue command */
+ if (ata_command(request->device, request->u.ata.command,
+ request->u.ata.lba, request->u.ata.count,
+ request->u.ata.feature)) {
+ ata_prtdev(request->device, "error issuing DMA command\n");
+ request->result = EIO;
+ return ATA_OP_FINISHED;
+ }
+
+ /* start DMA engine */
+ if (request->device->channel->dma->start(request->device->channel,
+ request->data,
+ request->bytecount,
+ request->flags & ATA_R_READ)) {
+ request->result = EIO;
+ return ATA_OP_FINISHED;
+ }
+ /* return and wait for interrupt */
+ return ATA_OP_CONTINUES;
+
+ /* ATAPI PIO commands */
+ case ATA_R_ATAPI:
+ /* is this just a POLL DSC command ? */
+ if (request->u.atapi.ccb[0] == ATAPI_POLL_DSC) {
+ ATA_IDX_OUTB(request->device->channel, ATA_DRIVE,
+ ATA_D_IBM | request->device->unit);
+ DELAY(10);
+ if (!(ATA_IDX_INB(request->device->channel, ATA_ALTSTAT)&ATA_S_DSC))
+ request->result = EBUSY;
+ return ATA_OP_FINISHED;
+ }
+
+ /* start ATAPI operation */
+ if (ata_command(request->device, ATA_PACKET_CMD,
+ request->transfersize << 8, 0, 0)) {
+ ata_prtdev(request->device, "error issuing ATA PACKET command\n");
+ request->result = EIO;
+ return ATA_OP_FINISHED;
+ }
+
+ /* command interrupt device ? just return and wait for interrupt */
+ if ((request->device->param->config & ATA_DRQ_MASK) == ATA_DRQ_INTR)
+ return ATA_OP_CONTINUES;
+
+ /* wait for ready to write ATAPI command block */
+ {
+ int timeout = 5000; /* might be less for fast devices */
+ while (timeout--) {
+ int reason = ATA_IDX_INB(request->device->channel, ATA_IREASON);
+ int status = ATA_IDX_INB(request->device->channel, ATA_STATUS);
+
+ if (((reason & (ATA_I_CMD | ATA_I_IN)) |
+ (status & (ATA_S_DRQ | ATA_S_BUSY))) == ATAPI_P_CMDOUT)
+ break;
+ DELAY(20);
+ }
+ if (timeout <= 0) {
+ ata_prtdev(request->device,
+ "timeout waiting for ATAPI ready\n");
+ request->result = EIO;
+ return ATA_OP_FINISHED;
+ }
+ }
+
+ /* this seems to be needed for some (slow) devices */
+ DELAY(10);
+
+ /* output actual command block */
+ ATA_IDX_OUTSW_STRM(request->device->channel, ATA_DATA,
+ (int16_t *)request->u.atapi.ccb,
+ (request->device->param->config & ATA_PROTO_MASK) ==
+ ATA_PROTO_ATAPI_12 ? 6 : 8);
+
+ /* return and wait for interrupt */
+ return ATA_OP_CONTINUES;
+
+ case ATA_R_ATAPI|ATA_R_DMA:
+ /* is this just a POLL DSC command ? */
+ if (request->u.atapi.ccb[0] == ATAPI_POLL_DSC) {
+ ATA_IDX_OUTB(request->device->channel, ATA_DRIVE,
+ ATA_D_IBM | request->device->unit);
+ DELAY(10);
+ if (!(ATA_IDX_INB(request->device->channel, ATA_ALTSTAT)&ATA_S_DSC))
+ request->result = EBUSY;
+ return ATA_OP_FINISHED;
+ }
+
+ /* check sanity and setup DMA engine */
+ if (request->device->channel->dma->setup(request->device,
+ request->data,
+ request->bytecount)) {
+ ata_prtdev(request->device, "setting up DMA failed\n");
+ request->result = EIO;
+ return ATA_OP_FINISHED;
+ }
+
+ /* start ATAPI operation */
+ if (ata_command(request->device, ATA_PACKET_CMD, 0, 0, ATA_F_DMA)) {
+ ata_prtdev(request->device, "error issuing ATAPI packet command\n");
+ request->result = EIO;
+ return ATA_OP_FINISHED;
+ }
+
+ /* wait for ready to write ATAPI command block */
+ {
+ int timeout = 5000; /* might be less for fast devices */
+ while (timeout--) {
+ int reason = ATA_IDX_INB(request->device->channel, ATA_IREASON);
+ int status = ATA_IDX_INB(request->device->channel, ATA_STATUS);
+
+ if (((reason & (ATA_I_CMD | ATA_I_IN)) |
+ (status & (ATA_S_DRQ | ATA_S_BUSY))) == ATAPI_P_CMDOUT)
+ break;
+ DELAY(20);
+ }
+ if (timeout <= 0) {
+ ata_prtdev(request->device,
+ "timeout waiting for ATAPI ready\n");
+ request->result = EIO;
+ return ATA_OP_FINISHED;
+ }
+ }
+
+ /* this seems to be needed for some (slow) devices */
+ DELAY(10);
+
+ /* output actual command block */
+ ATA_IDX_OUTSW_STRM(request->device->channel, ATA_DATA,
+ (int16_t *)request->u.atapi.ccb,
+ (request->device->param->config & ATA_PROTO_MASK) ==
+ ATA_PROTO_ATAPI_12 ? 6 : 8);
+
+ /* start DMA engine */
+ if (request->device->channel->dma->start(request->device->channel,
+ request->data,
+ request->bytecount,
+ request->flags & ATA_R_READ)) {
+ request->result = EIO;
+ return ATA_OP_FINISHED;
+ }
+
+ /* return and wait for interrupt */
+ return ATA_OP_CONTINUES;
+ }
+}
+
+static void
+ata_interrupt(void *data)
+{
+ struct ata_channel *ch = (struct ata_channel *)data;
+ struct ata_request *request = ch->running;
+ int length;
+
+ /* if we dont have a running request shout and ignore this interrupt */
+ if (request == NULL) {
+ if (bootverbose) {
+ printf("ata%d: spurious interrupt - ", device_get_unit(ch->dev));
+ if (request)
+ printf("request OK - ");
+ printf("status=0x%02x error=0x%02x reason=0x%02x\n",
+ ATA_IDX_INB(ch, ATA_ALTSTAT), ATA_IDX_INB(ch, ATA_ERROR),
+ ATA_IDX_INB(ch, ATA_IREASON));
+ }
+ return;
+ }
+
+ /* if device is busy it didn't interrupt */
+ if (ATA_IDX_INB(ch, ATA_ALTSTAT) & ATA_S_BUSY) {
+ DELAY(100);
+ if (!(ATA_IDX_INB(ch, ATA_ALTSTAT) & ATA_S_DRQ))
+ return;
+ }
+
+ /* clear interrupt and get status */
+ request->status = ATA_IDX_INB(ch, ATA_STATUS);
+
+ switch (request->flags & (ATA_R_ATAPI | ATA_R_DMA)) {
+
+ /* ATA PIO data transfer and control commands */
+ default:
+
+ /* if we got an error we are done with the HW */
+ if (request->status & ATA_S_ERROR) {
+ request->error = ATA_IDX_INB(ch, ATA_ERROR);
+ break;
+ }
+
+ /* if read data get it */
+ if (request->flags & ATA_R_READ)
+ ata_pio_read(request, request->transfersize);
+
+ /* update how far we've gotten */
+ request->donecount += request->transfersize;
+
+ /* do we need a scoop more ? */
+ if (request->bytecount > request->donecount) {
+
+ /* set this transfer size according to HW capabilities */
+ request->transfersize =
+ min((request->bytecount-request->donecount),
+ request->transfersize);
+
+ /* if data write command, output the data */
+ if (request->flags & ATA_R_WRITE) {
+ /* if we get an error here we are done with the HW */
+ if (ata_wait(request->device,
+ (ATA_S_READY | ATA_S_DSC | ATA_S_DRQ)) < 0) {
+ ata_prtdev(request->device,"timeout waiting for write DRQ");
+ request->status = ATA_IDX_INB(ch, ATA_STATUS);
+ break;
+ }
+ else {
+ /* output data and return waiting for new interrupt */
+ ata_pio_write(request, request->transfersize);
+ return;
+ }
+ }
+
+ /* if data read command, return & wait for interrupt */
+ else if (request->flags & ATA_R_READ) {
+ return;
+ }
+ else
+ ata_prtdev(request->device,
+ "FAILURE - %s shouldn't loop on control cmd\n",
+ ata_cmd2str(request));
+ }
+ /* done with HW */
+ break;
+
+ /* ATA DMA data transfer commands */
+ case ATA_R_DMA:
+ /* stop DMA engine and get status */
+ request->dmastat = ch->dma->stop(ch);
+
+ /* did we get error or data */
+ if (request->status & ATA_S_ERROR)
+ request->error = ATA_IDX_INB(ch, ATA_ERROR);
+ else if (request->dmastat & ATA_BMSTAT_ERROR)
+ request->status |= ATA_S_ERROR;
+ else
+ request->donecount = request->bytecount;
+
+ /* done with HW */
+ break;
+
+ /* ATAPI PIO commands */
+ case ATA_R_ATAPI:
+ length = ATA_IDX_INB(ch, ATA_CYL_LSB)|(ATA_IDX_INB(ch, ATA_CYL_MSB)<<8);
+
+ switch ((ATA_IDX_INB(ch, ATA_IREASON) & (ATA_I_CMD | ATA_I_IN)) |
+ (request->status & ATA_S_DRQ)) {
+
+ case ATAPI_P_CMDOUT:
+ /* this seems to be needed for some (slow) devices */
+ DELAY(10);
+
+ if (!(request->status & ATA_S_DRQ)) {
+ ata_prtdev(request->device, "command interrupt without DRQ\n");
+ request->status = ATA_S_ERROR;
+ break;
+ }
+ ATA_IDX_OUTSW_STRM(ch, ATA_DATA, (int16_t *)request->u.atapi.ccb,
+ (request->device->param->config &
+ ATA_PROTO_MASK)== ATA_PROTO_ATAPI_12 ? 6 : 8);
+ /* return wait for interrupt */
+ return;
+
+ case ATAPI_P_WRITE:
+ if (request->flags & ATA_R_READ) {
+ request->status = ATA_S_ERROR;
+ ata_prtdev(request->device,
+ "%s trying to write on read buffer\n",
+ ata_cmd2str(request));
+ break;
+ }
+ ata_pio_write(request, length);
+ request->donecount += length;
+
+ /* set next transfer size according to HW capabilities */
+ request->transfersize = min((request->bytecount-request->donecount),
+ request->transfersize);
+ /* return wait for interrupt */
+ return;
+
+ case ATAPI_P_READ:
+ if (request->flags & ATA_R_WRITE) {
+ request->status = ATA_S_ERROR;
+ ata_prtdev(request->device,
+ "%s trying to read on write buffer\n",
+ ata_cmd2str(request));
+ break;
+ }
+ ata_pio_read(request, length);
+ request->donecount += length;
+
+ /* set next transfer size according to HW capabilities */
+ request->transfersize = min((request->bytecount-request->donecount),
+ request->transfersize);
+ /* return wait for interrupt */
+ return;
+
+ case ATAPI_P_DONEDRQ:
+ ata_prtdev(request->device,
+ "WARNING - %s DONEDRQ non conformant device\n",
+ ata_cmd2str(request));
+ if (request->flags & ATA_R_READ) {
+ ata_pio_read(request, length);
+ request->donecount += length;
+ }
+ else if (request->flags & ATA_R_WRITE) {
+ ata_pio_write(request, length);
+ request->donecount += length;
+ }
+ else
+ request->status = ATA_S_ERROR;
+ /* FALLTHROUGH */
+
+ case ATAPI_P_ABORT:
+ case ATAPI_P_DONE:
+ if (request->status & (ATA_S_ERROR | ATA_S_DWF))
+ request->error = ATA_IDX_INB(ch, ATA_ERROR);
+ break;
+
+ default:
+ ata_prtdev(request->device, "unknown transfer phase\n");
+ request->status = ATA_S_ERROR;
+ }
+ /* done with HW */
+ break;
+
+ /* ATAPI DMA commands */
+ case ATA_R_ATAPI|ATA_R_DMA:
+
+ /* stop the engine and get engine status */
+ request->dmastat = ch->dma->stop(ch);
+
+ /* did we get error or data */
+ if (request->status & (ATA_S_ERROR | ATA_S_DWF))
+ request->error = ATA_IDX_INB(ch, ATA_ERROR);
+ else if (request->dmastat & ATA_BMSTAT_ERROR)
+ request->status |= ATA_S_ERROR;
+ else
+ request->donecount = request->bytecount;
+
+ /* done with HW */
+ break;
+ }
+
+ ata_finish(request);
+
+ /* unlock the ATA HW for new work */
+ ch->running = NULL;
+ ATA_UNLOCK_CH(ch);
+ ch->locking(ch, ATA_LF_UNLOCK);
+}
+
+/* must be called with ATA channel locked */
+static void
+ata_reset(struct ata_channel *ch)
+{
+ u_int8_t lsb, msb, ostat0, ostat1;
+ u_int8_t stat0 = 0, stat1 = 0;
+ int mask = 0, timeout;
+
+ /* do we have any signs of ATA/ATAPI HW being present ? */
+ ATA_IDX_OUTB(ch, ATA_DRIVE, ATA_D_IBM | ATA_MASTER);
+ DELAY(10);
+ ostat0 = ATA_IDX_INB(ch, ATA_STATUS);
+ if ((ostat0 & 0xf8) != 0xf8 && ostat0 != 0xa5) {
+ stat0 = ATA_S_BUSY;
+ mask |= 0x01;
+ }
+
+ ATA_IDX_OUTB(ch, ATA_DRIVE, ATA_D_IBM | ATA_SLAVE);
+ DELAY(10);
+ ostat1 = ATA_IDX_INB(ch, ATA_STATUS);
+ /* in some setups we dont want to test for a slave */
+ if (!(ch->flags & ATA_NO_SLAVE)) {
+ if ((ostat1 & 0xf8) != 0xf8 && ostat1 != 0xa5) {
+ stat1 = ATA_S_BUSY;
+ mask |= 0x02;
+ }
+ }
+
+ /* if nothing showed up no need to get any further */
+ /* SOS is that too strong?, we just might loose devices here XXX */
+ ch->devices = 0;
+ if (!mask)
+ return;
+
+ if (bootverbose)
+ ata_printf(ch, -1, "pre reset mask=%02x ostat0=%02x ostat2=%02x\n",
+ mask, ostat0, ostat1);
+
+ /* reset channel */
+ ATA_IDX_OUTB(ch, ATA_DRIVE, ATA_D_IBM | ATA_MASTER);
+ DELAY(10);
+ ATA_IDX_OUTB(ch, ATA_ALTSTAT, ATA_A_IDS | ATA_A_RESET);
+ DELAY(10000);
+ ATA_IDX_OUTB(ch, ATA_ALTSTAT, ATA_A_IDS);
+ DELAY(100000);
+ ATA_IDX_INB(ch, ATA_ERROR);
+
+ /* wait for BUSY to go inactive */
+ for (timeout = 0; timeout < 310000; timeout++) {
+ if (stat0 & ATA_S_BUSY) {
+ ATA_IDX_OUTB(ch, ATA_DRIVE, ATA_D_IBM | ATA_MASTER);
+ DELAY(10);
+
+ /* check for ATAPI signature while its still there */
+ lsb = ATA_IDX_INB(ch, ATA_CYL_LSB);
+ msb = ATA_IDX_INB(ch, ATA_CYL_MSB);
+ stat0 = ATA_IDX_INB(ch, ATA_STATUS);
+ if (!(stat0 & ATA_S_BUSY)) {
+ if (bootverbose)
+ ata_printf(ch, ATA_MASTER, "ATAPI %02x %02x\n", lsb, msb);
+ if (lsb == ATAPI_MAGIC_LSB && msb == ATAPI_MAGIC_MSB)
+ ch->devices |= ATA_ATAPI_MASTER;
+ }
+ }
+ if (stat1 & ATA_S_BUSY) {
+ ATA_IDX_OUTB(ch, ATA_DRIVE, ATA_D_IBM | ATA_SLAVE);
+ DELAY(10);
+
+ /* check for ATAPI signature while its still there */
+ lsb = ATA_IDX_INB(ch, ATA_CYL_LSB);
+ msb = ATA_IDX_INB(ch, ATA_CYL_MSB);
+ stat1 = ATA_IDX_INB(ch, ATA_STATUS);
+ if (!(stat1 & ATA_S_BUSY)) {
+ if (bootverbose)
+ ata_printf(ch, ATA_SLAVE, "ATAPI %02x %02x\n", lsb, msb);
+ if (lsb == ATAPI_MAGIC_LSB && msb == ATAPI_MAGIC_MSB)
+ ch->devices |= ATA_ATAPI_SLAVE;
+ }
+ }
+ if (mask == 0x01) /* wait for master only */
+ if (!(stat0 & ATA_S_BUSY))
+ break;
+ if (mask == 0x02) /* wait for slave only */
+ if (!(stat1 & ATA_S_BUSY))
+ break;
+ if (mask == 0x03) /* wait for both master & slave */
+ if (!(stat0 & ATA_S_BUSY) && !(stat1 & ATA_S_BUSY))
+ break;
+ DELAY(100);
+ }
+ DELAY(10);
+ ATA_IDX_OUTB(ch, ATA_ALTSTAT, ATA_A_4BIT);
+
+ if (stat0 & ATA_S_BUSY)
+ mask &= ~0x01;
+ if (stat1 & ATA_S_BUSY)
+ mask &= ~0x02;
+ if (bootverbose)
+ ata_printf(ch, -1, "after reset mask=%02x stat0=%02x stat1=%02x\n",
+ mask, stat0, stat1);
+ if (!mask)
+ return;
+
+ if (mask & 0x01 && ostat0 != 0x00 && !(ch->devices & ATA_ATAPI_MASTER)) {
+ ATA_IDX_OUTB(ch, ATA_DRIVE, ATA_D_IBM | ATA_MASTER);
+ DELAY(10);
+ ATA_IDX_OUTB(ch, ATA_ERROR, 0x58);
+ ATA_IDX_OUTB(ch, ATA_CYL_LSB, 0xa5);
+ lsb = ATA_IDX_INB(ch, ATA_ERROR);
+ msb = ATA_IDX_INB(ch, ATA_CYL_LSB);
+ if (bootverbose)
+ ata_printf(ch, ATA_MASTER, "ATA %02x %02x\n", lsb, msb);
+ if (lsb != 0x58 && msb == 0xa5)
+ ch->devices |= ATA_ATA_MASTER;
+ }
+ if (mask & 0x02 && ostat1 != 0x00 && !(ch->devices & ATA_ATAPI_SLAVE)) {
+ ATA_IDX_OUTB(ch, ATA_DRIVE, ATA_D_IBM | ATA_SLAVE);
+ DELAY(10);
+ ATA_IDX_OUTB(ch, ATA_ERROR, 0x58);
+ ATA_IDX_OUTB(ch, ATA_CYL_LSB, 0xa5);
+ lsb = ATA_IDX_INB(ch, ATA_ERROR);
+ msb = ATA_IDX_INB(ch, ATA_CYL_LSB);
+ if (bootverbose)
+ ata_printf(ch, ATA_SLAVE, "ATA %02x %02x\n", lsb, msb);
+ if (lsb != 0x58 && msb == 0xa5)
+ ch->devices |= ATA_ATA_SLAVE;
+ }
+ if (bootverbose)
+ ata_printf(ch, -1, "devices=%02x\n", ch->devices);
+}
+
+static int
+ata_wait(struct ata_device *atadev, u_int8_t mask)
+{
+ int timeout = 0;
+ u_int8_t status;
+
+ DELAY(1);
+ while (timeout < 5000000) { /* timeout 5 secs */
+ status = ATA_IDX_INB(atadev->channel, ATA_STATUS);
+
+ /* if drive fails status, reselect the drive just to be sure */
+ if (status == 0xff) {
+ ata_prtdev(atadev, "WARNING no status, reselecting device\n");
+ ATA_IDX_OUTB(atadev->channel, ATA_DRIVE, ATA_D_IBM | atadev->unit);
+ DELAY(10);
+ status = ATA_IDX_INB(atadev->channel, ATA_STATUS);
+ if (status == 0xff)
+ return -1;
+ }
+
+ /* are we done ? */
+ if (!(status & ATA_S_BUSY))
+ break;
+
+ if (timeout > 1000) {
+ timeout += 1000;
+ DELAY(1000);
+ }
+ else {
+ timeout += 10;
+ DELAY(10);
+ }
+ }
+ if (timeout >= 5000000)
+ return -1;
+ if (!mask)
+ return (status & ATA_S_ERROR);
+
+ /* wait 50 msec for bits wanted. */
+ timeout = 5000;
+ while (timeout--) {
+ status = ATA_IDX_INB(atadev->channel, ATA_STATUS);
+ if ((status & mask) == mask)
+ return (status & ATA_S_ERROR);
+ DELAY (10);
+ }
+ return -1;
+}
+
+static int
+ata_command(struct ata_device *atadev, u_int8_t command,
+ u_int64_t lba, u_int16_t count, u_int16_t feature)
+{
+ if (atadebug)
+ ata_prtdev(atadev, "ata_command: addr=%04lx, command=%02x, "
+ "lba=%jd, count=%d, feature=%d\n",
+ rman_get_start(atadev->channel->r_io[ATA_DATA].res),
+ command, (intmax_t)lba, count, feature);
+
+ /* select device */
+ ATA_IDX_OUTB(atadev->channel, ATA_DRIVE, ATA_D_IBM | atadev->unit);
+
+ /* ready to issue command ? */
+ if (ata_wait(atadev, 0) < 0) {
+ ata_prtdev(atadev, "timeout sending command=%02x\n", command);
+ return -1;
+ }
+
+ /* only use 48bit addressing if needed (avoid bugs and overhead) */
+ if ((lba > 268435455 || count > 256) && atadev->param &&
+ atadev->param->support.command2 & ATA_SUPPORT_ADDRESS48) {
+
+ /* translate command into 48bit version */
+ switch (command) {
+ case ATA_READ:
+ command = ATA_READ48; break;
+ case ATA_READ_MUL:
+ command = ATA_READ_MUL48; break;
+ case ATA_READ_DMA:
+ command = ATA_READ_DMA48; break;
+ case ATA_READ_DMA_QUEUED:
+ command = ATA_READ_DMA_QUEUED48; break;
+ case ATA_WRITE:
+ command = ATA_WRITE48; break;
+ case ATA_WRITE_MUL:
+ command = ATA_WRITE_MUL48; break;
+ case ATA_WRITE_DMA:
+ command = ATA_WRITE_DMA48; break;
+ case ATA_WRITE_DMA_QUEUED:
+ command = ATA_WRITE_DMA_QUEUED48; break;
+ case ATA_FLUSHCACHE:
+ command = ATA_FLUSHCACHE48; break;
+ default:
+ ata_prtdev(atadev, "can't translate cmd to 48bit version\n");
+ return -1;
+ }
+ ATA_IDX_OUTB(atadev->channel, ATA_FEATURE, (feature>>8) & 0xff);
+ ATA_IDX_OUTB(atadev->channel, ATA_FEATURE, feature & 0xff);
+ ATA_IDX_OUTB(atadev->channel, ATA_COUNT, (count>>8) & 0xff);
+ ATA_IDX_OUTB(atadev->channel, ATA_COUNT, count & 0xff);
+ ATA_IDX_OUTB(atadev->channel, ATA_SECTOR, (lba>>24) & 0xff);
+ ATA_IDX_OUTB(atadev->channel, ATA_SECTOR, lba & 0xff);
+ ATA_IDX_OUTB(atadev->channel, ATA_CYL_LSB, (lba>>32) & 0xff);
+ ATA_IDX_OUTB(atadev->channel, ATA_CYL_LSB, (lba>>8) & 0xff);
+ ATA_IDX_OUTB(atadev->channel, ATA_CYL_MSB, (lba>>40) & 0xff);
+ ATA_IDX_OUTB(atadev->channel, ATA_CYL_MSB, (lba>>16) & 0xff);
+ ATA_IDX_OUTB(atadev->channel, ATA_DRIVE, ATA_D_LBA | atadev->unit);
+ atadev->channel->flags |= ATA_48BIT_ACTIVE;
+ }
+ else {
+ ATA_IDX_OUTB(atadev->channel, ATA_FEATURE, feature);
+ ATA_IDX_OUTB(atadev->channel, ATA_COUNT, count);
+ ATA_IDX_OUTB(atadev->channel, ATA_SECTOR, lba & 0xff);
+ ATA_IDX_OUTB(atadev->channel, ATA_CYL_LSB, (lba>>8) & 0xff);
+ ATA_IDX_OUTB(atadev->channel, ATA_CYL_MSB, (lba>>16) & 0xff);
+ if (atadev->flags & ATA_D_USE_CHS)
+ ATA_IDX_OUTB(atadev->channel, ATA_DRIVE,
+ ATA_D_IBM | atadev->unit | ((lba>>24) & 0xf));
+ else
+ ATA_IDX_OUTB(atadev->channel, ATA_DRIVE,
+ ATA_D_IBM | ATA_D_LBA | atadev->unit|((lba>>24)&0xf));
+ atadev->channel->flags &= ~ATA_48BIT_ACTIVE;
+ }
+
+ /* issue command to controller */
+ ATA_IDX_OUTB(atadev->channel, ATA_CMD, command);
+
+ return 0;
+}
+
+static void
+ata_pio_read(struct ata_request *request, int length)
+{
+ int size = min(request->transfersize, length);
+ struct ata_channel *ch = request->device->channel;
+ int resid;
+
+ if (ch->flags & ATA_USE_16BIT || (size % sizeof(int32_t)))
+ ATA_IDX_INSW_STRM(ch, ATA_DATA,
+ (void*)((uintptr_t)request->data+request->donecount),
+ size / sizeof(int16_t));
+ else
+ ATA_IDX_INSL_STRM(ch, ATA_DATA,
+ (void*)((uintptr_t)request->data+request->donecount),
+ size / sizeof(int32_t));
+
+ if (request->transfersize < length) {
+ ata_prtdev(request->device, "WARNING - %s read data overrun %d/%d\n",
+ ata_cmd2str(request), length, request->transfersize);
+ for (resid = request->transfersize; resid < length;
+ resid += sizeof(int16_t))
+ ATA_IDX_INW(ch, ATA_DATA);
+ }
+}
+
+static void
+ata_pio_write(struct ata_request *request, int length)
+{
+ int size = min(request->transfersize, length);
+ struct ata_channel *ch = request->device->channel;
+ int resid;
+
+ if (ch->flags & ATA_USE_16BIT || (size % sizeof(int32_t)))
+ ATA_IDX_OUTSW_STRM(ch, ATA_DATA,
+ (void*)((uintptr_t)request->data+request->donecount),
+ size / sizeof(int16_t));
+ else
+ ATA_IDX_OUTSL_STRM(ch, ATA_DATA,
+ (void*)((uintptr_t)request->data+request->donecount),
+ size / sizeof(int32_t));
+
+ if (request->transfersize < length) {
+ ata_prtdev(request->device, "WARNING - %s write data underrun %d/%d\n",
+ ata_cmd2str(request), length, request->transfersize);
+ for (resid = request->transfersize; resid < length;
+ resid += sizeof(int16_t))
+ ATA_IDX_OUTW(ch, ATA_DATA, 0);
+ }
+}
OpenPOWER on IntegriCloud