DragonFly kernel List (threaded) for 2004-11
[
Date Prev][
Date Next]
[
Thread Prev][
Thread Next]
[
Date Index][
Thread Index]
ATA Patch #6
Here is patch #6 (the one Jonathon McKitrick is running). John is
still getting some glitches (shoot!) but if it turns out that he is
getting a lot fewer glitches then before I think I would like to commit
it, so it needs wider testing.
This patch continues to clean up the ATA command setup and the
interrupt handling code.
(John: don't repatch, this is the same patch with only some mods to
the comments).
-Matt
Index: ata-all.c
===================================================================
RCS file: /cvs/src/sys/dev/disk/ata/ata-all.c,v
retrieving revision 1.21
diff -u -r1.21 ata-all.c
--- ata-all.c 17 Aug 2004 20:59:39 -0000 1.21
+++ ata-all.c 19 Nov 2004 20:37:14 -0000
@@ -26,7 +26,7 @@
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* $FreeBSD: src/sys/dev/ata/ata-all.c,v 1.50.2.45 2003/03/12 14:47:12 sos Exp $
- * $DragonFly: src/sys/dev/disk/ata/ata-all.c,v 1.21 2004/08/17 20:59:39 dillon Exp $
+ * $DragonFly$
*/
#include "opt_ata.h"
@@ -53,6 +53,7 @@
#include <machine/bus.h>
#include <machine/clock.h>
#include <sys/rman.h>
+#include <sys/thread2.h>
#ifdef __alpha__
#include <machine/md_var.h>
#endif
@@ -601,6 +602,7 @@
ata_intr(void *data)
{
struct ata_channel *ch = (struct ata_channel *)data;
+
/*
* on PCI systems we might share an interrupt line with another
* device or our twin ATA channel, so call ch->intr_func to figure
@@ -609,14 +611,86 @@
if (ch->intr_func && ch->intr_func(ch))
return;
- /* if drive is busy it didn't interrupt */
- if (ATA_INB(ch->r_altio, ATA_ALTSTAT) & ATA_S_BUSY) {
- DELAY(100);
- if (!(ATA_INB(ch->r_altio, ATA_ALTSTAT) & ATA_S_DRQ))
- return;
+ /*
+ * If we are interrupting mainline code that is in the middle of setting
+ * up we have a problem. Note that this case is checked before we figure
+ * out if the interrupt is real. The ATA interrupt should be disabled
+ * while ATA_INTERURPT_INTERLOCK is set so the interrupt must either not
+ * be for us, or no longer asserted if it was queued just prior to mainline
+ * code disabling it. Just return.
+ */
+ if (ch->flags & ATA_INTERRUPT_INTERLOCK) {
+ ata_printf(ch, -1, "warning, ignoring interrupt with interlock "
+ "set, active=%02x s=%02x\n",
+ ch->active, ch->status);
+ return;
}
- /* clear interrupt and get status */
+ /*
+ * Figure out if the interrupt is real. False interrupts can occur in a
+ * number of situations including:
+ *
+ * - The device asserts the interrupt prior to clearing BUSY or setting
+ * DRQ.
+ *
+ * - The device generated an interrupt before we were able to disable
+ * it and the interrupt thread was already queued.
+ *
+ * - Other devices share this interrupt vector.
+ *
+ * - Upon queueing the device command the device may delay before
+ * setting BUSY (this is handled by ata_command(), not here).
+ *
+ * To deal with the remaining races if ATA_S_BUSY is set we must wait
+ * a bit and check it again to see if it is still set.
+ *
+ * Having to spin in a delay loop is really bad but there is no way
+ * around it. The ATA spec is just plain broken.
+ */
+ if (ch->flags & ATA_DMA_ACTIVE) {
+ /*
+ * In the DMA case the operation is not complete until both BUSY and
+ * DRQ are clear. If either is found to be set we delay and recheck.
+ * If either is still set the interrupt was bogus and we return.
+ *
+ * The ATA spec says that DMA data may be transfered with BUSY=1 and
+ * DRQ=0, or BUSY=0 and DRQ=1.
+ */
+ if (ATA_INB(ch->r_altio, ATA_ALTSTAT) & (ATA_S_BUSY | ATA_S_DRQ)) {
+ DELAY(100);
+ if (ATA_INB(ch->r_altio, ATA_ALTSTAT) & (ATA_S_BUSY | ATA_S_DRQ))
+ return;
+ }
+ } else {
+ /*
+ * In the PIO case if the device intends to transfer data it will
+ * set DRQ but may or may not clear BUSY. If the device does not
+ * intend to transfer data (for example, an error occurs or there is
+ * no data to transfer) then the device will clear both BUSY and DRQ.
+ *
+ * What this means is we still need to do the delay+recheck due to
+ * the races mentioned above, but in the second test the interrupt
+ * is only false if BUSY is set and DRQ is clear.
+ *
+ * It may be possible to reduce the 100uS delay below to 10uS.
+ */
+ if (ATA_INB(ch->r_altio, ATA_ALTSTAT) & ATA_S_BUSY) {
+ DELAY(100);
+ if ((ATA_INB(ch->r_altio, ATA_ALTSTAT) & (ATA_S_BUSY | ATA_S_DRQ))
+ == ATA_S_BUSY
+ ) {
+ return;
+ }
+ }
+ }
+
+ /*
+ * As per ATA spec we must throw away data read above and re-read from
+ * the main status register to get good data and to clear the interrupt.
+ * Otherwise the other bits may have still been changing state when we
+ * determined that BUSY and DRQ were golden (the old device was writing to
+ * the status register at the same time we were reading from it problem).
+ */
ch->status = ATA_INB(ch->r_io, ATA_STATUS);
if (ch->status & ATA_S_ERROR)
@@ -832,7 +906,7 @@
* BUSY is released.
*/
DELAY(50000);
- ATA_OUTB(ch->r_altio, ATA_ALTSTAT, ATA_A_4BIT);
+ ATA_OUTB(ch->r_altio, ATA_ALTSTAT, ATA_A_IDS | ATA_A_4BIT);
if (stat0 & ATA_S_BUSY)
mask &= ~0x01;
@@ -990,13 +1064,26 @@
return ATA_OP_FINISHED;
}
+/*
+ * Wait for BUSY to clear and then for all bits specified by the mask
+ * to be set. The ATA spec says that we have to re-read the status register
+ * after we detect that BUSY has cleared to deal with garbage that may occur
+ * when the device is writing to the status register simultanious with our
+ * reading from it.
+ */
int
ata_wait(struct ata_device *atadev, u_int8_t mask)
{
- int timeout = 0;
-
- DELAY(1);
- while (timeout < 5000000) { /* timeout 5 secs */
+ sysclock_t last;
+ int timeout;
+
+ /*
+ * 5-second timeout
+ */
+ timeout = 5;
+ last = cputimer_count();
+
+ while (timeout) {
atadev->channel->status = ATA_INB(atadev->channel->r_io, ATA_STATUS);
/* if drive fails status, reselect the drive just to be sure */
@@ -1009,67 +1096,116 @@
return -1;
}
- /* are we done ? */
- if (!(atadev->channel->status & ATA_S_BUSY))
- break;
-
- if (timeout > 1000) {
- timeout += 1000;
- DELAY(1000);
+ /*
+ * When BUSY clears we have to re-read the status register to get
+ * non-junky data in case we raced the device writing to the status
+ * register.
+ */
+ if ((atadev->channel->status & ATA_S_BUSY) == 0) {
+ atadev->channel->status = ATA_INB(atadev->channel->r_io, ATA_STATUS);
+ if ((atadev->channel->status & ATA_S_BUSY) == 0)
+ break;
}
- else {
- timeout += 10;
- DELAY(10);
+
+ /*
+ * It is unclear whether we need to delay here or whether we can
+ * just poll status as fast as possible. DELAY a little.
+ */
+ DELAY(5);
+ if (cputimer_count() - last > cputimer_freq) {
+ --timeout;
+ last += cputimer_freq;
}
}
+
+ /*
+ * If status indicates error save the error register for future use,
+ * fail on timeout.
+ */
if (atadev->channel->status & ATA_S_ERROR)
atadev->channel->error = ATA_INB(atadev->channel->r_io, ATA_ERROR);
- if (timeout >= 5000000)
+
+ if (timeout == 0)
return -1;
- if (!mask)
+
+ /*
+ * If we are not waiting for any bits we were just waiting for BUSY to
+ * clear and return the error status.
+ */
+ if (mask == 0)
return (atadev->channel->status & ATA_S_ERROR);
-
- /* Wait 50 msec for bits wanted. */
- timeout = 5000;
- while (timeout--) {
- atadev->channel->status = ATA_INB(atadev->channel->r_io, ATA_STATUS);
- if ((atadev->channel->status & mask) == mask) {
- if (atadev->channel->status & ATA_S_ERROR)
- atadev->channel->error=ATA_INB(atadev->channel->r_io,ATA_ERROR);
- return (atadev->channel->status & ATA_S_ERROR);
- }
- DELAY (10);
- }
- return -1;
-}
+ /*
+ * After BUSY has cleared we wait up to 100ms for the desired bits.
+ * The bits should theoretically have already been loaded in. If
+ * we already have the data we do not have to reload since we already did
+ * that above.
+ */
+ if ((atadev->channel->status & mask) != mask) {
+ timeout = 100;
+ last = cputimer_count();
+ while (timeout) {
+ atadev->channel->status = ATA_INB(atadev->channel->r_io, ATA_STATUS);
+ if ((atadev->channel->status & mask) == mask)
+ break;
+ if (cputimer_count() - last > cputimer_freq / (1000 / 50)) {
+ --timeout;
+ last += cputimer_freq / (1000 / 50);
+ }
+ DELAY(5);
+ }
+ if (timeout == 0)
+ return(-1);
+ }
+ if (atadev->channel->status & ATA_S_ERROR)
+ atadev->channel->error = ATA_INB(atadev->channel->r_io,ATA_ERROR);
+ return (atadev->channel->status & ATA_S_ERROR);
+}
+
+/*
+ * Issue an ATA command and potentially wait for completion. If ATA_IMMEDIATE
+ * is used and no error occured, the caller must complete setup/operation and
+ * call ata_clear_interlock() on the channel if the caller desires an
+ * interrupt.
+ */
int
ata_command(struct ata_device *atadev, u_int8_t command,
u_int64_t lba, u_int16_t count, u_int8_t feature, int flags)
{
int error = 0;
+
#ifdef ATA_DEBUG
ata_prtdev(atadev, "ata_command: addr=%04lx, cmd=%02x, "
"lba=%lld, count=%d, feature=%d, flags=%02x\n",
rman_get_start(atadev->channel->r_io),
command, lba, count, feature, flags);
#endif
-
- /* select device */
+ /*
+ * Select the device, set ATA_INTERRUPT_INTERLOCK, and disable the
+ * device interrupt.
+ *
+ * The 10uS delay here could probably be reduced to 1 uS
+ */
+ crit_enter();
+ atadev->channel->flags |= ATA_INTERRUPT_INTERLOCK;
ATA_OUTB(atadev->channel->r_io, ATA_DRIVE, ATA_D_IBM | atadev->unit);
+ DELAY(10);
+ ATA_OUTB(atadev->channel->r_altio, ATA_ALTSTAT, ATA_A_IDS | ATA_A_4BIT);
+ crit_exit();
- /* disable interrupt from device */
- if (atadev->channel->flags & ATA_QUEUED)
- ATA_OUTB(atadev->channel->r_altio, ATA_ALTSTAT, ATA_A_IDS | ATA_A_4BIT);
-
- /* ready to issue command ? */
+ /*
+ * Wait for BUSY to clear
+ */
if (ata_wait(atadev, 0) < 0) {
ata_prtdev(atadev, "timeout sending command=%02x s=%02x e=%02x\n",
command, atadev->channel->status, atadev->channel->error);
+ crit_exit();
return -1;
}
- /* only use 48bit addressing if needed because of the overhead */
+ /*
+ * only use 48bit addressing if needed because of the overhead
+ */
if ((lba > 268435455 || count > 256) && atadev->param &&
atadev->param->support.address48) {
ATA_OUTB(atadev->channel->r_io, ATA_FEATURE, (feature>>8) & 0xff);
@@ -1084,7 +1220,9 @@
ATA_OUTB(atadev->channel->r_io, ATA_CYL_MSB, (lba>>16) & 0xff);
ATA_OUTB(atadev->channel->r_io, ATA_DRIVE, ATA_D_LBA | atadev->unit);
- /* translate command into 48bit version */
+ /*
+ * translate command into 48bit version
+ */
switch (command) {
case ATA_C_READ:
command = ATA_C_READ48; break;
@@ -1106,10 +1244,10 @@
command = ATA_C_FLUSHCACHE48; break;
default:
ata_prtdev(atadev, "can't translate cmd to 48bit version\n");
+ crit_exit();
return -1;
}
- }
- else {
+ } else {
ATA_OUTB(atadev->channel->r_io, ATA_FEATURE, feature);
ATA_OUTB(atadev->channel->r_io, ATA_COUNT, count);
ATA_OUTB(atadev->channel->r_io, ATA_SECTOR, lba & 0xff);
@@ -1123,33 +1261,43 @@
ATA_D_IBM | ATA_D_LBA | atadev->unit | ((lba>>24) &0xf));
}
+ /*
+ * Start the command rolling and wait at least 10uS for the device to
+ * bring up BUSY. Nasty, unfortunately, but at least the delay should
+ * run in parallel with the target device's processing of the command.
+ *
+ * NOTE: The ATA spec says that the status register is invalid for 400ns
+ * after writing the command byte.
+ */
+ atadev->channel->active |= flags & (ATA_WAIT_INTR | ATA_WAIT_READY);
+ ATA_OUTB(atadev->channel->r_io, ATA_CMD, command);
+ DELAY(10);
+
+ /*
+ * ATA_IMMEDIATE means that the caller is going to do more setup, do
+ * not reenable interrupts or clear the interlock.
+ *
+ * ATA_WAIT_INTR means that the caller wants to wait for the completion
+ * interrupt, reenable interrupts and clear the interlock.
+ *
+ * ATA_WAIT_READY is used during fixed initialization sequences and
+ * interrupts should be left disabled.
+ */
switch (flags & ATA_WAIT_MASK) {
case ATA_IMMEDIATE:
- ATA_OUTB(atadev->channel->r_io, ATA_CMD, command);
-
- /* enable interrupt */
- if (atadev->channel->flags & ATA_QUEUED)
- ATA_OUTB(atadev->channel->r_altio, ATA_ALTSTAT, ATA_A_4BIT);
break;
-
case ATA_WAIT_INTR:
- atadev->channel->active |= ATA_WAIT_INTR;
- ATA_OUTB(atadev->channel->r_io, ATA_CMD, command);
-
- /* enable interrupt */
- if (atadev->channel->flags & ATA_QUEUED)
- ATA_OUTB(atadev->channel->r_altio, ATA_ALTSTAT, ATA_A_4BIT);
-
+ crit_enter();
+ atadev->channel->flags &= ~ATA_INTERRUPT_INTERLOCK;
+ ATA_OUTB(atadev->channel->r_altio, ATA_ALTSTAT, ATA_A_4BIT);
+ crit_exit();
if (tsleep((caddr_t)atadev->channel, 0, "atacmd", 10 * hz)) {
ata_prtdev(atadev, "timeout waiting for interrupt\n");
atadev->channel->active &= ~ATA_WAIT_INTR;
error = -1;
}
break;
-
case ATA_WAIT_READY:
- atadev->channel->active |= ATA_WAIT_READY;
- ATA_OUTB(atadev->channel->r_io, ATA_CMD, command);
if (ata_wait(atadev, ATA_S_READY) < 0) {
ata_prtdev(atadev, "timeout waiting for cmd=%02x s=%02x e=%02x\n",
command, atadev->channel->status,atadev->channel->error);
@@ -1161,6 +1309,25 @@
return error;
}
+/*
+ * ata_command() physically disables the ATA interrupt and sets the
+ * ATA_INTERRUPT_INTERLOCK bit to prevent ata_intr() handler from touching
+ * the ATA I/O registers.
+ *
+ * This routine reenables the ATA interrupt and clears the interlock, and
+ * is called after the command has been completely queued including any
+ * required DMA setup/start. Anyone who calls ata_command() with ATA_IMMEDIATE
+ * who is not explicitly polling for the result must call this routine.
+ */
+void
+ata_clear_interlock(struct ata_device *atadev)
+{
+ crit_enter();
+ atadev->channel->flags &= ~ATA_INTERRUPT_INTERLOCK;
+ ATA_OUTB(atadev->channel->r_altio, ATA_ALTSTAT, ATA_A_4BIT);
+ crit_exit();
+}
+
static void
ata_enclosure_start(struct ata_device *atadev)
{
Index: ata-all.h
===================================================================
RCS file: /cvs/src/sys/dev/disk/ata/ata-all.h,v
retrieving revision 1.7
diff -u -r1.7 ata-all.h
--- ata-all.h 18 Feb 2004 02:47:38 -0000 1.7
+++ ata-all.h 16 Nov 2004 23:46:03 -0000
@@ -215,6 +215,7 @@
#define ATA_ATAPI_DMA_RO 0x04
#define ATA_QUEUED 0x08
#define ATA_DMA_ACTIVE 0x10
+#define ATA_INTERRUPT_INTERLOCK 0x20
struct ata_device device[2]; /* devices on this channel */
#define MASTER 0x00
@@ -268,6 +269,7 @@
int ata_reinit(struct ata_channel *);
int ata_wait(struct ata_device *, u_int8_t);
int ata_command(struct ata_device *, u_int8_t, u_int64_t, u_int16_t, u_int8_t, int);
+void ata_clear_interlock(struct ata_device *);
void ata_enclosure_leds(struct ata_device *, u_int8_t);
void ata_enclosure_print(struct ata_device *);
int ata_printf(struct ata_channel *, int, const char *, ...) __printflike(3, 4);
Index: ata-disk.c
===================================================================
RCS file: /cvs/src/sys/dev/disk/ata/ata-disk.c,v
retrieving revision 1.23
diff -u -r1.23 ata-disk.c
--- ata-disk.c 23 Sep 2004 11:50:03 -0000 1.23
+++ ata-disk.c 19 Nov 2004 20:33:34 -0000
@@ -54,6 +54,7 @@
#include "ata-raid.h"
#include <sys/proc.h>
#include <sys/buf2.h>
+#include <sys/thread2.h>
/* device structures */
static d_open_t adopen;
@@ -84,6 +85,7 @@
static void ad_requeue(struct ata_channel *, struct ad_request *);
static void ad_invalidatequeue(struct ad_softc *, struct ad_request *);
static int ad_tagsupported(struct ad_softc *);
+static void ad_start_timeout(struct ad_request *);
static void ad_timeout(struct ad_request *);
static void ad_free(struct ad_request *);
static int ad_version(u_int16_t);
@@ -462,6 +464,9 @@
u_int64_t lba;
u_int32_t count, max_count;
u_int8_t cmd;
+#if 0
+ u_int8_t status;
+#endif
int flags = ATA_IMMEDIATE;
/* get request params */
@@ -471,15 +476,6 @@
lba = request->blockaddr + (request->donecount / DEV_BSIZE);
if (request->donecount == 0) {
-
- /* start timeout for this transfer */
- if (dumping) {
- callout_stop(&request->callout);
- } else {
- callout_reset(&request->callout, 10 * hz,
- (void *)ad_timeout, request);
- }
-
/* setup transfer parameters */
count = howmany(request->bytecount, DEV_BSIZE);
max_count = adp->device->param->support.address48 ? 65536 : 256;
@@ -503,7 +499,18 @@
devstat_start_transaction(&adp->stats);
- /* does this drive & transfer work with DMA ? */
+ /*
+ * DMA OPERATION. A DMA based ATA command issues the command and
+ * then sets up and initiates DMA. Once the command is issued the
+ * device will set ATA_S_BUSY. For DMA transfers the device may
+ * or may not set ATA_S_DRQ. If it DOES set ATA_S_DRQ it might also
+ * clear ATA_S_BUSY. Either bit will be set while the operation is
+ * in progress but which one (or both) depends on the target device.
+ *
+ * If the device does play with DRQ it may set it immediately or it
+ * may wait until it actually needs the data (which could be several
+ * milliseconds), so we don't try to wait for it.
+ */
request->flags &= ~ADR_F_DMA_USED;
if (adp->device->mode >= ATA_DMA &&
!ata_dmasetup(adp->device, request->data, request->bytecount)) {
@@ -526,13 +533,14 @@
}
adp->outstanding++;
+ ad_start_timeout(request);
+
/* if ATA bus RELEASE check for SERVICE */
if (adp->flags & AD_F_TAG_ENABLED &&
ATA_INB(adp->device->channel->r_io, ATA_IREASON) &
ATA_I_RELEASE)
return ad_service(adp, 1);
- }
- else {
+ } else {
cmd = (request->flags & ADR_F_READ) ?
ATA_C_READ_DMA : ATA_C_WRITE_DMA;
@@ -540,31 +548,42 @@
ata_prtdev(adp->device, "error executing command");
goto transfer_failed;
}
-#if 0
+
/*
- * wait for data transfer phase
+ * For a DMA transfer the device will set either BUSY or
+ * DRQ to 1, or will set both bits to 1, or will set BUSY to
+ * 0 and DRQ to 1, or will do one of the above and then do
+ * another of the above. If an error occurs the device may
+ * clear both BUSY and DRQ even before we get to here.
*
- * well this should be here acording to specs, but older
- * promise controllers doesn't like it, they lockup!
+ * It is said that some much older devices may even clear
+ * BUSY *BEFORE* they set DRQ. The only way to deal with
+ * this is to not check the status register until an interrupt
+ * occurs, which is problematic at best since the interrupt
+ * might be shared.
+ *
+ * Basically its a mess.
*/
- if (ata_wait(adp->device, ATA_S_READY | ATA_S_DRQ)) {
- ata_prtdev(adp->device, "timeout waiting for data phase\n");
- goto transfer_failed;
- }
-#endif
}
- /* start transfer, return and wait for interrupt */
+ /*
+ * Physically start DMA rolling and setup our timeout, then
+ * clear the interrupt interlock and reenable interrupts. It
+ * is possible that this will generate an immediate interrupt.
+ */
ata_dmastart(adp->device, request->data, request->bytecount,
request->flags & ADR_F_READ);
+ ad_start_timeout(request);
+ ata_clear_interlock(adp->device);
return ATA_OP_CONTINUES;
}
- /* does this drive support multi sector transfers ? */
+ /*
+ * Fall through to PIO, initial command. Use a multi-sector transfer
+ * if it is supported.
+ */
if (request->currentsize > DEV_BSIZE)
cmd = request->flags&ADR_F_READ ? ATA_C_READ_MUL : ATA_C_WRITE_MUL;
-
- /* just plain old single sector transfer */
else
cmd = request->flags&ADR_F_READ ? ATA_C_READ : ATA_C_WRITE;
@@ -573,13 +592,20 @@
goto transfer_failed;
}
}
+
+ /*
+ * PIO initial command or continuance
+ */
/* calculate this transfer length */
request->currentsize = min(request->bytecount, adp->transfersize);
/* if this is a PIO read operation, return and wait for interrupt */
- if (request->flags & ADR_F_READ)
+ if (request->flags & ADR_F_READ) {
+ ad_start_timeout(request);
+ ata_clear_interlock(adp->device);
return ATA_OP_CONTINUES;
+ }
/* ready to write PIO data ? */
if (ata_wait(adp->device, (ATA_S_READY | ATA_S_DSC | ATA_S_DRQ)) < 0) {
@@ -596,17 +622,18 @@
ATA_OUTSL(adp->device->channel->r_io, ATA_DATA,
(void *)((uintptr_t)request->data + request->donecount),
request->currentsize / sizeof(int32_t));
+ ad_start_timeout(request);
+ ata_clear_interlock(adp->device);
return ATA_OP_CONTINUES;
transfer_failed:
- callout_stop(&request->callout);
ad_invalidatequeue(adp, request);
printf(" - resetting\n");
/* if retries still permit, reinject this request */
- if (request->retries++ < AD_MAX_RETRIES)
+ if (request->retries++ < AD_MAX_RETRIES) {
ad_requeue(adp->device->channel, request);
- else {
+ } else {
/* retries all used up, return error */
request->bp->b_error = EIO;
request->bp->b_flags |= B_ERROR;
@@ -625,20 +652,24 @@
struct ad_softc *adp = request->softc;
int dma_stat = 0;
+ /* disarm timeout for this transfer */
+ callout_stop(&request->callout);
+
/* finish DMA transfer */
if (request->flags & ADR_F_DMA_USED)
dma_stat = ata_dmadone(adp->device);
/* do we have a corrected soft error ? */
- if (adp->device->channel->status & ATA_S_CORR)
+ if (adp->device->channel->status & ATA_S_CORR) {
diskerr(request->bp, request->bp->b_dev,
"soft error (ECC corrected)", LOG_PRINTF,
request->blockaddr + (request->donecount / DEV_BSIZE),
&adp->disk.d_label);
+ }
/* did any real errors happen ? */
if ((adp->device->channel->status & ATA_S_ERROR) ||
- (request->flags & ADR_F_DMA_USED && dma_stat & ATA_BMSTAT_ERROR)) {
+ ((request->flags & ADR_F_DMA_USED) && (dma_stat & ATA_BMSTAT_ERROR))) {
adp->device->channel->error =
ATA_INB(adp->device->channel->r_io, ATA_ERROR);
diskerr(request->bp, request->bp->b_dev,
@@ -650,7 +681,6 @@
/* if this is a UDMA CRC error, reinject request */
if (request->flags & ADR_F_DMA_USED &&
adp->device->channel->error & ATA_E_ICRC) {
- callout_stop(&request->callout);
ad_invalidatequeue(adp, request);
if (request->retries++ < AD_MAX_RETRIES)
@@ -665,7 +695,6 @@
/* if using DMA, try once again in PIO mode */
if (request->flags & ADR_F_DMA_USED) {
- callout_stop(&request->callout);
ad_invalidatequeue(adp, request);
ata_dmainit(adp->device, ata_pmode(adp->device->param), -1, -1);
request->flags |= ADR_F_FORCE_PIO;
@@ -694,8 +723,7 @@
if (ata_wait(adp->device, (ATA_S_READY | ATA_S_DSC | ATA_S_DRQ)) != 0) {
ata_prtdev(adp->device, "read error detected (too) late");
request->flags |= ADR_F_ERROR;
- }
- else {
+ } else {
/* data ready, read in */
if (adp->device->channel->flags & ATA_USE_16BIT)
ATA_INSW(adp->device->channel->r_io, ATA_DATA,
@@ -712,8 +740,7 @@
if (request->flags & ADR_F_ERROR) {
request->bp->b_error = EIO;
request->bp->b_flags |= B_ERROR;
- }
- else {
+ } else {
request->bytecount -= request->currentsize;
request->donecount += request->currentsize;
if (request->bytecount > 0) {
@@ -722,9 +749,6 @@
}
}
- /* disarm timeout for this transfer */
- callout_stop(&request->callout);
-
request->bp->b_resid = request->bytecount;
devstat_end_transaction_buf(&adp->stats, request->bp);
@@ -821,6 +845,7 @@
}
ata_dmastart(adp->device, request->data, request->bytecount,
request->flags & ADR_F_READ);
+ ata_clear_interlock(adp->device);
return ATA_OP_CONTINUES;
}
return ATA_OP_FINISHED;
@@ -895,15 +920,38 @@
return 0;
}
+static
+void
+ad_start_timeout(struct ad_request *request)
+{
+ if (dumping == 0)
+ callout_reset(&request->callout, 10 * hz, (void *)ad_timeout, request);
+}
+
static void
ad_timeout(struct ad_request *request)
{
struct ad_softc *adp = request->softc;
+ u_int8_t status;
+ /*
+ * Handle races against ad_interrupt.
+ */
+ crit_enter();
+ if (request != adp->device->channel->running) {
+ printf("ad_timeout: request %p was ripped out from under us\n", request);
+ crit_exit();
+ return;
+ }
adp->device->channel->running = NULL;
- ata_prtdev(adp->device, "%s command timeout tag=%d serv=%d - resetting\n",
+ status = ATA_INB(adp->device->channel->r_io, ATA_STATUS);
+ crit_exit();
+ ata_prtdev(adp->device, "%s command timeout tag=%d serv=%d status %02x"
+ " dmastatus %02x - resetting\n",
(request->flags & ADR_F_READ) ? "READ" : "WRITE",
- request->tag, request->serv);
+ request->tag, request->serv, status,
+ (adp->device->mode >= ATA_DMA ?
+ ata_dmastatus(adp->device->channel) : 0xff));
if (request->flags & ADR_F_DMA_USED) {
ata_dmadone(adp->device);
Index: ata-dma.c
===================================================================
RCS file: /cvs/src/sys/dev/disk/ata/ata-dma.c,v
retrieving revision 1.25
diff -u -r1.25 ata-dma.c
--- ata-dma.c 1 Sep 2004 14:13:55 -0000 1.25
+++ ata-dma.c 16 Nov 2004 21:59:54 -0000
@@ -1336,13 +1336,18 @@
}
}
+/*
+ * Create the dma table for an ATA transfer. ATA_DMA_EOT must be set on
+ * the last entry. Setting it after the last entry rather then on the
+ * last entry may screw up EOT timing.
+ */
int
ata_dmasetup(struct ata_device *atadev, caddr_t data, int32_t count)
{
struct ata_channel *ch = atadev->channel;
struct ata_dmastate *ds = &atadev->dmastate;
- u_int32_t dma_count, dma_base;
- int i = 0;
+ u_int32_t dma_count;
+ int i;
if (((uintptr_t)data & ch->alignment) || (count & ch->alignment)) {
ata_prtdev(atadev, "non aligned DMA transfer attempted\n");
@@ -1353,27 +1358,20 @@
ata_prtdev(atadev, "zero length DMA transfer attempted\n");
return -1;
}
-
- dma_base = vtophys(data);
- dma_count = imin(count, (PAGE_SIZE - ((uintptr_t)data & PAGE_MASK)));
- data += dma_count;
- count -= dma_count;
-
- while (count) {
- ds->dmatab[i].base = dma_base;
- ds->dmatab[i].count = (dma_count & 0xffff);
- i++;
- if (i >= ATA_DMA_ENTRIES) {
- ata_prtdev(atadev, "too many segments in DMA table\n");
- return -1;
- }
- dma_base = vtophys(data);
- dma_count = imin(count, PAGE_SIZE);
- data += imin(count, PAGE_SIZE);
- count -= imin(count, PAGE_SIZE);
+
+ for (i = 0; count && i < ATA_DMA_ENTRIES; ++i) {
+ dma_count = imin(count, (PAGE_SIZE - ((uintptr_t)data & PAGE_MASK)));
+ ds->dmatab[i].base = vtophys(data);
+ ds->dmatab[i].count = dma_count & 0xffff;
+
+ data += dma_count;
+ count -= dma_count;
+ }
+ if (count) {
+ ata_prtdev(atadev, "too many segments in DMA table\n");
+ return -1;
}
- ds->dmatab[i].base = dma_base;
- ds->dmatab[i].count = (dma_count & 0xffff) | ATA_DMA_EOT;
+ ds->dmatab[i-1].count |= ATA_DMA_EOT;
return 0;
}
Index: atapi-all.c
===================================================================
RCS file: /cvs/src/sys/dev/disk/ata/atapi-all.c,v
retrieving revision 1.14
diff -u -r1.14 atapi-all.c
--- atapi-all.c 18 Sep 2004 18:33:38 -0000 1.14
+++ atapi-all.c 16 Nov 2004 23:51:50 -0000
@@ -319,8 +319,10 @@
request->flags & ATPR_F_READ);
/* command interrupt device ? just return */
- if (atadev->param->drq_type == ATAPI_DRQT_INTR)
+ if (atadev->param->drq_type == ATAPI_DRQT_INTR) {
+ ata_clear_interlock(atadev);
return ATA_OP_CONTINUES;
+ }
/* ready to write ATAPI command */
timout = 5000; /* might be less for fast devices */
@@ -346,6 +348,7 @@
/* send actual command */
ATA_OUTSW(atadev->channel->r_io, ATA_DATA, (int16_t *)request->ccb,
request->ccbsize / sizeof(int16_t));
+ ata_clear_interlock(atadev);
return ATA_OP_CONTINUES;
}
[
Date Prev][
Date Next]
[
Thread Prev][
Thread Next]
[
Date Index][
Thread Index]