ahci: more robustness against flaky devices

- if supported, override BSY/DRQ flags and start the port anyway if it
  times out with DET=3h status during device detection;
- no longer rely on the device signature to be set; unless a valid
  signature is obtained, try both ATA and ATAPI device identification.
This commit is contained in:
David van Moolenbroek 2013-02-13 14:29:44 +00:00
parent eb89b33d39
commit 9d89e72a5c
2 changed files with 60 additions and 25 deletions

View file

@ -127,6 +127,7 @@ static struct {
int nr_ports; /* addressable number of ports (1..NR_PORTS) */
int nr_cmds; /* maximum number of commands per port */
int has_ncq; /* NCQ support flag */
int has_clo; /* CLO support flag */
int irq; /* IRQ number */
int hook_id; /* IRQ hook ID */
@ -1232,6 +1233,25 @@ static void port_hardreset(struct port_state *ps)
port_write(ps, AHCI_PORT_SCTL, AHCI_PORT_SCTL_DET_NONE);
}
/*===========================================================================*
* port_override *
*===========================================================================*/
static void port_override(struct port_state *ps)
{
/* Override the port's BSY and/or DRQ flags. This may only be done
* prior to starting the port.
*/
u32_t cmd;
cmd = port_read(ps, AHCI_PORT_CMD);
port_write(ps, AHCI_PORT_CMD, cmd | AHCI_PORT_CMD_CLO);
SPIN_UNTIL(!(port_read(ps, AHCI_PORT_CMD) & AHCI_PORT_CMD_CLO),
PORTREG_DELAY);
dprintf(V_INFO, ("%s: overridden\n", ahci_portname(ps)));
}
/*===========================================================================*
* port_start *
*===========================================================================*/
@ -1350,9 +1370,21 @@ static void port_id_check(struct port_state *ps, int success)
ps->flags &= ~FLAG_BUSY;
cancel_timer(&ps->cmd_info[0].timer);
if (!success)
if (!success) {
if (!(ps->flags & FLAG_ATAPI) &&
port_read(ps, AHCI_PORT_SIG) != ATA_SIG_ATA) {
dprintf(V_INFO, ("%s: may not be ATA, trying ATAPI\n",
ahci_portname(ps)));
ps->flags |= FLAG_ATAPI;
(void) gen_identify(ps, FALSE /*blocking*/);
return;
}
dprintf(V_ERR,
("%s: unable to identify\n", ahci_portname(ps)));
}
/* If the identify command itself succeeded, check the results and
* store some properties.
@ -1436,24 +1468,6 @@ static void port_connect(struct port_state *ps)
return;
}
/* Check the port's signature. We only support the normal ATA and ATAPI
* signatures. We ignore devices reporting anything else.
*/
sig = port_read(ps, AHCI_PORT_SIG);
if (sig != ATA_SIG_ATA && sig != ATA_SIG_ATAPI) {
dprintf(V_ERR, ("%s: unsupported signature (%08x)\n",
ahci_portname(ps), sig));
port_stop(ps);
ps->state = STATE_BAD_DEV;
port_write(ps, AHCI_PORT_IE, AHCI_PORT_IE_PRCE);
ps->flags &= ~FLAG_BUSY;
return;
}
/* Clear all state flags except the busy flag, which may be relevant if
* a BDEV_OPEN call is waiting for the device to become ready; the
* barrier flag, which prevents access to the device until it is
@ -1461,6 +1475,12 @@ static void port_connect(struct port_state *ps)
*/
ps->flags &= (FLAG_BUSY | FLAG_BARRIER | FLAG_SUSPENDED);
/* Check the port's signature. We only use the signature to speed up
* identification; we will try both ATA and ATAPI if the signature is
* neither ATA nor ATAPI.
*/
sig = port_read(ps, AHCI_PORT_SIG);
if (sig == ATA_SIG_ATAPI)
ps->flags |= FLAG_ATAPI;
@ -1548,6 +1568,18 @@ static void port_dev_check(struct port_state *ps)
* device present at all. In all cases, we change to another state.
*/
if (status == AHCI_PORT_SSTS_DET_PHY) {
/* Some devices may not correctly clear BSY/DRQ. Upon timeout,
* if we can override these flags, do so and start the
* identification process anyway.
*/
if (hba_state.has_clo) {
port_override(ps);
port_connect(ps);
return;
}
/* A device is present and initialized, but not ready. */
ps->state = STATE_BAD_DEV;
port_write(ps, AHCI_PORT_IE, AHCI_PORT_IE_PRCE);
@ -1569,6 +1601,7 @@ static void port_intr(struct port_state *ps)
/* Process an interrupt on this port.
*/
u32_t smask, emask;
int success;
if (ps->state == STATE_NO_PORT) {
dprintf(V_ERR, ("%s: interrupt for invalid port!\n",
@ -1660,12 +1693,8 @@ static void port_intr(struct port_state *ps)
* later by obtaining per-command status results from the HBA.
*/
/* If we were waiting for ID verification, check now. */
if (ps->state == STATE_WAIT_ID) {
port_id_check(ps, !(port_read(ps, AHCI_PORT_TFD) &
(AHCI_PORT_TFD_STS_ERR |
AHCI_PORT_TFD_STS_DF)));
}
success = !(port_read(ps, AHCI_PORT_TFD) &
(AHCI_PORT_TFD_STS_ERR | AHCI_PORT_TFD_STS_DF));
/* Check now for failure. There are fatal failures, and there
* are failures that set the TFD.STS.ERR field using a D2H
@ -1677,6 +1706,10 @@ static void port_intr(struct port_state *ps)
(smask & AHCI_PORT_IS_RESTART)) {
port_restart(ps);
}
/* If we were waiting for ID verification, check now. */
if (ps->state == STATE_WAIT_ID)
port_id_check(ps, success);
}
}
@ -2087,6 +2120,7 @@ static void ahci_init(int devind)
/* Note that we currently use only one command anyway. */
cap = hba_read(AHCI_HBA_CAP);
hba_state.has_ncq = !!(cap & AHCI_HBA_CAP_SNCQ);
hba_state.has_clo = !!(cap & AHCI_HBA_CAP_SCLO);
hba_state.nr_cmds = MIN(NR_CMDS,
((cap >> AHCI_HBA_CAP_NCS_SHIFT) & AHCI_HBA_CAP_NCS_MASK) + 1);

View file

@ -192,6 +192,7 @@
#define AHCI_PORT_CMD_CR (1L << 15) /* Cmd List Running */
#define AHCI_PORT_CMD_FR (1L << 14) /* FIS Recv Running */
#define AHCI_PORT_CMD_FRE (1L << 4) /* FIS Recv Enabled */
#define AHCI_PORT_CMD_CLO (1L << 3) /* Cmd List Override */
#define AHCI_PORT_CMD_SUD (1L << 1) /* Spin-Up Device */
#define AHCI_PORT_CMD_ST (1L << 0) /* Start */
#define AHCI_PORT_TFD 8 /* Task File Data */