diff options
author | Christopher Pascoe <pascoe@cvs.openbsd.org> | 2007-03-20 08:47:47 +0000 |
---|---|---|
committer | Christopher Pascoe <pascoe@cvs.openbsd.org> | 2007-03-20 08:47:47 +0000 |
commit | 51a8fb1fe16c246d747d3f9a90a40798dfbe71da (patch) | |
tree | 93f3135bb9bb2543baa0dc31826870a1b499dbf3 /sys | |
parent | db37fa8ff4a01b1922ba0e20426bafa804f0b806 (diff) |
Add timeout handling for ATA commands.
Diffstat (limited to 'sys')
-rw-r--r-- | sys/dev/ata/atascsi.h | 4 | ||||
-rw-r--r-- | sys/dev/pci/ahci.c | 100 |
2 files changed, 93 insertions, 11 deletions
diff --git a/sys/dev/ata/atascsi.h b/sys/dev/ata/atascsi.h index 1334d2103f5..61fc35d342b 100644 --- a/sys/dev/ata/atascsi.h +++ b/sys/dev/ata/atascsi.h @@ -1,4 +1,4 @@ -/* $OpenBSD: atascsi.h,v 1.12 2007/03/20 07:45:11 dlg Exp $ */ +/* $OpenBSD: atascsi.h,v 1.13 2007/03/20 08:47:46 pascoe Exp $ */ /* * Copyright (c) 2007 David Gwynne <dlg@openbsd.org> @@ -121,6 +121,8 @@ struct ata_xfer { #define ATA_S_PENDING 1 #define ATA_S_COMPLETE 2 #define ATA_S_ERROR 3 +#define ATA_S_TIMEOUT 4 +#define ATA_S_ONCHIP 5 #define ATA_S_PUT 6 void *atascsi_private; diff --git a/sys/dev/pci/ahci.c b/sys/dev/pci/ahci.c index c33da5a666c..f6afe32a513 100644 --- a/sys/dev/pci/ahci.c +++ b/sys/dev/pci/ahci.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ahci.c,v 1.84 2007/03/20 07:38:45 pascoe Exp $ */ +/* $OpenBSD: ahci.c,v 1.85 2007/03/20 08:47:46 pascoe Exp $ */ /* * Copyright (c) 2006 David Gwynne <dlg@openbsd.org> @@ -36,7 +36,8 @@ #define AHCI_DEBUG #ifdef AHCI_DEBUG -#define DPRINTF(m, f...) do { if (ahcidebug & (m)) printf(f); } while (0) +#define DPRINTF(m, f...) do { if ((ahcidebug & (m)) == (m)) printf(f); } while (0) +#define AHCI_D_TIMEOUT 0x00 #define AHCI_D_VERBOSE 0x01 #define AHCI_D_INTR 0x02 #define AHCI_D_XFER 0x08 @@ -417,7 +418,7 @@ int ahci_port_portreset(struct ahci_port *); int ahci_load_prdt(struct ahci_ccb *); void ahci_unload_prdt(struct ahci_ccb *); -int ahci_poll(struct ahci_ccb *, int); +int ahci_poll(struct ahci_ccb *, int, void (*)(void *)); void ahci_start(struct ahci_ccb *); int ahci_intr(void *); @@ -465,6 +466,7 @@ struct atascsi_methods ahci_atascsi_methods = { /* ccb completions */ void ahci_ata_cmd_done(struct ahci_ccb *); +void ahci_ata_cmd_timeout(void *); void ahci_empty_done(struct ahci_ccb *); const struct ahci_device * @@ -1106,7 +1108,8 @@ ahci_port_softreset(struct ahci_port *ap) cmd_slot->flags |= htole16(AHCI_CMD_LIST_FLAG_R); /* Reset */ cmd_slot->flags |= htole16(AHCI_CMD_LIST_FLAG_W); /* Write */ - if (ahci_poll(ccb, 1000) != 0) + ccb->ccb_xa.state = ATA_S_PENDING; + if (ahci_poll(ccb, 1000, NULL) != 0) goto err; /* Prep second D2H command to read status and complete reset sequence */ @@ -1120,7 +1123,8 @@ ahci_port_softreset(struct ahci_port *ap) cmd_slot->flags = htole16(5); /* FIS length: 5 DWORDS */ cmd_slot->flags |= htole16(AHCI_CMD_LIST_FLAG_W); - if (ahci_poll(ccb, 1000) != 0) + ccb->ccb_xa.state = ATA_S_PENDING; + if (ahci_poll(ccb, 1000, NULL) != 0) goto err; if (ahci_pwait_clr(ap, AHCI_PREG_TFD, AHCI_PREG_TFD_STS_BSY | @@ -1275,7 +1279,7 @@ ahci_unload_prdt(struct ahci_ccb *ccb) int -ahci_poll(struct ahci_ccb *ccb, int timeout) +ahci_poll(struct ahci_ccb *ccb, int timeout, void (*timeout_fn)(void *)) { struct ahci_port *ap = ccb->ccb_port; int s; @@ -1290,9 +1294,12 @@ ahci_poll(struct ahci_ccb *ccb, int timeout) delay(1000); } while (--timeout > 0); - splx(s); - /* XXX cleanup! */ + /* Run timeout while at splbio, otherwise ahci_intr could interfere. */ + if (timeout_fn != NULL) + timeout_fn(ccb); + + splx(s); return (1); } @@ -1318,6 +1325,7 @@ ahci_start(struct ahci_ccb *ccb) bus_dmamap_sync(sc->sc_dmat, AHCI_DMA_MAP(ap->ap_dmamem_rfis), 0, sizeof(struct ahci_rfis), BUS_DMASYNC_PREREAD); + ccb->ccb_xa.state = ATA_S_ONCHIP; ap->ap_active |= 1 << ccb->ccb_slot; ahci_pwrite(ap, AHCI_PREG_CI, 1 << ccb->ccb_slot); } @@ -1672,14 +1680,18 @@ ahci_ata_cmd(struct ata_xfer *xa) if (ahci_load_prdt(ccb) != 0) goto failcmd; + timeout_set(&xa->stimeout, ahci_ata_cmd_timeout, ccb); + xa->state = ATA_S_PENDING; if (xa->flags & ATA_F_POLL) { - if (ahci_poll(ccb, 1000) != 0) + if (ahci_poll(ccb, 1000, ahci_ata_cmd_timeout) != 0) return (ATA_ERROR); return (ATA_COMPLETE); } + timeout_add(&xa->stimeout, (xa->timeout * hz) / 1000); + s = splbio(); ahci_start(ccb); splx(s); @@ -1698,10 +1710,78 @@ ahci_ata_cmd_done(struct ahci_ccb *ccb) { struct ata_xfer *xa = &ccb->ccb_xa; + timeout_del(&xa->stimeout); + ahci_unload_prdt(ccb); - xa->state = ATA_S_COMPLETE; + if (xa->state == ATA_S_ONCHIP) + xa->state = ATA_S_COMPLETE; + if (xa->state != ATA_S_TIMEOUT) + xa->complete(xa); +} + +void +ahci_ata_cmd_timeout(void *arg) +{ + struct ahci_ccb *ccb = arg; + struct ata_xfer *xa = &ccb->ccb_xa; + struct ahci_port *ap = ccb->ccb_port; + int s; + + s = splbio(); + + if (ccb->ccb_xa.state == ATA_S_PENDING) { + DPRINTF(AHCI_D_TIMEOUT, "%s: command for slot %d timed out " + "before it got on chip\n", PORTNAME(ap), ccb->ccb_slot); + } else if (ccb->ccb_xa.state == ATA_S_ONCHIP && ahci_port_intr(ap, + 1 << ccb->ccb_slot)) { + DPRINTF(AHCI_D_TIMEOUT, "%s: final poll of port completed " + "command in slot %d\n", PORTNAME(ap), ccb->ccb_slot); + goto ret; + } else if (ccb->ccb_xa.state != ATA_S_ONCHIP) { + DPRINTF(AHCI_D_TIMEOUT, "%s: command slot %d already " + "handled%s\n", PORTNAME(ap), ccb->ccb_slot, + ISSET(ap->ap_active, 1 << ccb->ccb_slot) ? + " but slot is still active?" : "."); + goto ret; + } else if (!ISSET(ahci_pread(ap, AHCI_PREG_CI), 1 << ccb->ccb_slot) && + ISSET(ap->ap_active, 1 << ccb->ccb_slot)) { + DPRINTF(AHCI_D_TIMEOUT, "%s: command slot %d completed but " + "IRQ handler didn't detect it. Why?\n", PORTNAME(ap), + ccb->ccb_slot); + ap->ap_active &= ~(1 << ccb->ccb_slot); + ccb->ccb_done(ccb); + goto ret; + } + + /* Complete the slot with timeout flag set. */ + ccb->ccb_xa.state = ATA_S_TIMEOUT; + ap->ap_active &= ~(1 << ccb->ccb_slot); + DPRINTF(AHCI_D_TIMEOUT, "%s: run completion (1)\n", PORTNAME(ap)); + ccb->ccb_done(ccb); + + /* Reset port. */ + DPRINTF(AHCI_D_TIMEOUT, "%s: resetting port to abort command in slot %d, " + "active %08x\n", PORTNAME(ap), ccb->ccb_slot, ap->ap_active); + if (ahci_port_softreset(ap) != 0 && ahci_port_portreset(ap) != 0) { + DPRINTF(AHCI_D_TIMEOUT, "%s: failed to reset port\n", + PORTNAME(ap)); + } + + /* Restart any outstanding commands that were stopped by the reset. */ + if (ap->ap_active) { + DPRINTF(AHCI_D_TIMEOUT, "%s: re-enabling slots %08x\n", + PORTNAME(ap), ap->ap_active); + ahci_pwrite(ap, AHCI_PREG_CI, ap->ap_active); + } + + /* Complete the timed out ata_xfer I/O (may generate new I/O). */ + DPRINTF(AHCI_D_TIMEOUT, "%s: run completion (2)\n", PORTNAME(ap)); xa->complete(xa); + + DPRINTF(AHCI_D_TIMEOUT, "%s: splx\n", PORTNAME(ap)); +ret: + splx(s); } void |