summaryrefslogtreecommitdiff
path: root/sys/dev/pci/ahci.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/pci/ahci.c')
-rw-r--r--sys/dev/pci/ahci.c155
1 files changed, 130 insertions, 25 deletions
diff --git a/sys/dev/pci/ahci.c b/sys/dev/pci/ahci.c
index f6afe32a513..7a95fba59c8 100644
--- a/sys/dev/pci/ahci.c
+++ b/sys/dev/pci/ahci.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ahci.c,v 1.85 2007/03/20 08:47:46 pascoe Exp $ */
+/* $OpenBSD: ahci.c,v 1.86 2007/03/20 11:22:40 pascoe Exp $ */
/*
* Copyright (c) 2006 David Gwynne <dlg@openbsd.org>
@@ -241,6 +241,7 @@ int ahcidebug = AHCI_D_VERBOSE;
"\001N"
#define AHCI_PREG_ACT 0x34 /* SATA Active */
#define AHCI_PREG_CI 0x38 /* Command Issue */
+#define AHCI_PREG_CI_ALL_SLOTS 0xffffffff
#define AHCI_PREG_SNTF 0x3c /* SNotification */
struct ahci_cmd_hdr {
@@ -339,6 +340,10 @@ struct ahci_port {
struct ahci_ccb *ap_ccbs;
TAILQ_HEAD(, ahci_ccb) ap_ccb_free;
+ u_int32_t ap_state;
+#define AP_S_NORMAL 0
+#define AP_S_FATAL_ERROR 1
+
#ifdef AHCI_DEBUG
char ap_name[16];
#define PORTNAME(_ap) ((_ap)->ap_name)
@@ -422,7 +427,7 @@ int ahci_poll(struct ahci_ccb *, int, void (*)(void *));
void ahci_start(struct ahci_ccb *);
int ahci_intr(void *);
-int ahci_port_intr(struct ahci_port *, u_int32_t);
+u_int32_t ahci_port_intr(struct ahci_port *, u_int32_t);
struct ahci_ccb *ahci_get_ccb(struct ahci_port *);
void ahci_put_ccb(struct ahci_ccb *);
@@ -1356,7 +1361,8 @@ ahci_intr(void *arg)
while (is) {
port = ffs(is) - 1;
if (sc->sc_ports[port])
- ahci_port_intr(sc->sc_ports[port], 0xffffffff);
+ ahci_port_intr(sc->sc_ports[port],
+ AHCI_PREG_CI_ALL_SLOTS);
is &= ~(1 << port);
}
@@ -1366,56 +1372,136 @@ ahci_intr(void *arg)
return (1);
}
-int
+u_int32_t
ahci_port_intr(struct ahci_port *ap, u_int32_t ci_mask)
{
struct ahci_softc *sc = ap->ap_sc;
- u_int32_t is, ci, cmd, tfd;
- int slot, processed = 0;
+ u_int32_t is, ci_saved, ci_masked, processed = 0;
+ int slot, need_restart = 0;
struct ahci_ccb *ccb;
is = ahci_pread(ap, AHCI_PREG_IS);
/* Ack port interrupt only if checking all command slots. */
- if (ci_mask == 0xffffffff)
+ if (ci_mask == AHCI_PREG_CI_ALL_SLOTS)
ahci_pwrite(ap, AHCI_PREG_IS, is);
if (is)
DPRINTF(AHCI_D_INTR, "%s: interrupt: %b\n", PORTNAME(ap),
is, AHCI_PFMT_IS);
- /* Check for fatal errors, shut down if any. */
+ /* Save CI */
+ ci_saved = ahci_pread(ap, AHCI_PREG_CI);
+
+ /* Command failed. See AHCI 1.1 spec 6.2.2.1. */
+ if (is & AHCI_PREG_IS_TFES) {
+ u_int32_t tfd, serr;
+ int err_slot;
+
+ tfd = ahci_pread(ap, AHCI_PREG_TFD);
+ serr = ahci_pread(ap, AHCI_PREG_SERR);
+
+ err_slot = AHCI_PREG_CMD_CCS(ahci_pread(ap,
+ AHCI_PREG_CMD));
+ ccb = &ap->ap_ccbs[err_slot];
+
+ /* Preserve received taskfile data from the RFIS. */
+ memcpy(&ccb->ccb_xa.cmd.rx_err.regs, ap->ap_rfis->rfis,
+ sizeof(struct ata_regs));
+
+ DPRINTF(AHCI_D_VERBOSE, "%s: errored slot %d, TFD: %b, SERR:"
+ " %b, DIAG: %b\n", PORTNAME(ap), err_slot, tfd,
+ AHCI_PFMT_TFD_STS, AHCI_PREG_SERR_ERR(serr),
+ AHCI_PFMT_SERR_ERR, AHCI_PREG_SERR_DIAG(serr),
+ AHCI_PFMT_SERR_DIAG);
+
+ /* Turn off ST to clear CI and SACT. */
+ ahci_port_stop(ap, 0);
+ need_restart = 1;
+
+ /* Clear SERR to enable capturing new errors. */
+ ahci_pwrite(ap, AHCI_PREG_SERR, serr);
+
+ /* Acknowledge the interrupts we can recover from. */
+ ahci_pwrite(ap, AHCI_PREG_IS, AHCI_PREG_IS_TFES |
+ AHCI_PREG_IS_IFS);
+ is = ahci_pread(ap, AHCI_PREG_IS);
+
+ /* If device hasn't cleared its busy status, try to idle it. */
+ if (ISSET(tfd, AHCI_PREG_TFD_STS_BSY | AHCI_PREG_TFD_STS_DRQ)) {
+ printf("%s: attempting to idle device\n", PORTNAME(ap));
+ if (ahci_port_softreset(ap)) {
+ printf("%s: failed to soft reset device\n",
+ PORTNAME(ap));
+ if (ahci_port_portreset(ap)) {
+ printf("%s: failed to port reset "
+ "device, give up on it\n",
+ PORTNAME(ap));
+ goto fatal;
+ }
+ }
+
+ /* Had to reset device, can't gather extended info. */
+ } else {
+ /* Didn't reset, could gather extended info from log. */
+ }
+
+ /* Clear the failed command in saved CI so completion runs. */
+ ci_saved &= ~(1 << err_slot);
+
+ /* Note the error in the ata_xfer. */
+ KASSERT(ccb->ccb_xa.state == ATA_S_ONCHIP);
+ ccb->ccb_xa.state = ATA_S_ERROR;
+ }
+
+ /* Check for remaining errors - they are fatal. */
if (is & (AHCI_PREG_IS_TFES | AHCI_PREG_IS_HBFS | AHCI_PREG_IS_IFS |
AHCI_PREG_IS_OFS | AHCI_PREG_IS_UFS)) {
printf("%s: unrecoverable errors (IS: %b), disabling port.\n",
PORTNAME(ap), is, AHCI_PFMT_IS);
- if (is & AHCI_PREG_IS_TFES) {
- cmd = ahci_pread(ap, AHCI_PREG_CMD);
- tfd = ahci_pread(ap, AHCI_PREG_TFD);
- DPRINTF(AHCI_D_INTR, "%s: current slot %d, TFD: %b\n",
- PORTNAME(ap), AHCI_PREG_CMD_CCS(cmd), tfd,
- AHCI_PFMT_TFD_STS);
+
+ /* XXX try recovery first */
+ goto fatal;
+ }
+
+ /* Fail all outstanding commands if we know the port won't recover. */
+ if (ap->ap_state == AP_S_FATAL_ERROR) {
+fatal:
+ ap->ap_state = AP_S_FATAL_ERROR;
+
+ /* Ensure port is shut down. */
+ ahci_port_stop(ap, 1);
+
+ /* Error all the active slots. */
+ ci_masked = ~ci_saved & ap->ap_active;
+ while (ci_masked) {
+ slot = ffs(ci_masked) - 1;
+ ccb = &ap->ap_ccbs[slot];
+ ci_masked &= ~(1 << slot);
+ ccb->ccb_xa.state = ATA_S_ERROR;
}
- ahci_port_stop(ap, 0);
- /*
- * XXX reissue commands individually and/or notify callbacks
- * about the failure.
- */
+ /* Run completion for all slots. */
+ ci_saved = 0;
+
+ /* Don't restart the port if our problems were deemed fatal. */
+ if (ap->ap_state == AP_S_FATAL_ERROR)
+ need_restart = 0;
}
+
/*
* CCB completion is detected by noticing its slot's bit in CI has
* changed to zero some time after we activated it.
* If we are polling, we may only be interested in particular slot(s).
*/
- ci = ~ahci_pread(ap, AHCI_PREG_CI) & ap->ap_active & ci_mask;
- while (ci) {
- slot = ffs(ci) - 1;
+ ci_masked = ~ci_saved & ap->ap_active & ci_mask;
+ while (ci_masked) {
+ slot = ffs(ci_masked) - 1;
DPRINTF(AHCI_D_INTR, "%s: slot %d is complete\n", PORTNAME(ap),
slot);
ccb = &ap->ap_ccbs[slot];
- ci &= ~(1 << slot);
+ ci_masked &= ~(1 << slot);
bus_dmamap_sync(sc->sc_dmat,
AHCI_DMA_MAP(ap->ap_dmamem_cmd_list),
@@ -1437,6 +1523,21 @@ ahci_port_intr(struct ahci_port *ap, u_int32_t ci_mask)
processed |= 1 << ccb->ccb_slot;
}
+ if (need_restart) {
+ /* Restart command DMA on the port */
+ ahci_port_start(ap, 0);
+
+ /* Re-enable outstanding commands on port. */
+ if (ci_saved) {
+ DPRINTF(AHCI_D_VERBOSE, "%s: ahci_port_intr "
+ "re-enabling slots %08x\n", PORTNAME(ap),
+ ci_saved);
+
+ ahci_pwrite(ap, AHCI_PREG_CI, ci_saved);
+ }
+
+ }
+
return (processed);
}
@@ -1668,6 +1769,9 @@ ahci_ata_cmd(struct ata_xfer *xa)
struct ahci_cmd_hdr *cmd_slot;
int s;
+ if (ccb->ccb_port->ap_state == AP_S_FATAL_ERROR)
+ goto failcmd;
+
ccb->ccb_done = ahci_ata_cmd_done;
ccb->ccb_cmd_table->cfis[0] = REGS_TYPE_REG_H2D;
@@ -1764,8 +1868,9 @@ ahci_ata_cmd_timeout(void *arg)
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));
+ printf("%s: failed to reset port during error recovery, "
+ "disabling it\n", PORTNAME(ap));
+ ap->ap_state = AP_S_FATAL_ERROR;
}
/* Restart any outstanding commands that were stopped by the reset. */