ATA Patch #6

Matthew Dillon dillon at apollo.backplane.com
Fri Nov 19 12:39:26 PST 2004


    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;
 }
 





More information about the Kernel mailing list