From: David van Moolenbroek Date: Wed, 13 Feb 2013 14:29:44 +0000 (+0000) Subject: ahci: more robustness against flaky devices X-Git-Tag: v3.2.1~7 X-Git-Url: http://zhaoyanbai.com/repos/nsupdate.html?a=commitdiff_plain;h=9d89e72a5ce02a11154b4011f4ccf6ae17a90072;p=minix.git 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. --- diff --git a/drivers/ahci/ahci.c b/drivers/ahci/ahci.c index cf2b628e1..033a76423 100644 --- a/drivers/ahci/ahci.c +++ b/drivers/ahci/ahci.c @@ -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); diff --git a/drivers/ahci/ahci.h b/drivers/ahci/ahci.h index b1ccd664d..5b77d9e63 100644 --- a/drivers/ahci/ahci.h +++ b/drivers/ahci/ahci.h @@ -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 */