ATA Patch #6

看板DFBSD_kernel作者時間21年前 (2004/11/20 05:01), 編輯推噓0(000)
留言0則, 0人參與, 最新討論串1/16 (看更多)
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; }
文章代碼(AID): #11dbyG00 (DFBSD_kernel)
文章代碼(AID): #11dbyG00 (DFBSD_kernel)