Refactor hard-reset implementation in ahci(4).

Instead of spinning in a tight loop for up to 15 seconds, polling for device
readiness while it spins up, return reset completion just after PHY reports
"connect well" or 100ms connection timeout. If device was found, use callout
for checking device readiness with 100ms period up to full 31 second timeout.

This fixes system freeze for 5-10 seconds on drives hot plug-in.
This commit is contained in:
Alexander Motin 2011-04-12 20:50:57 +00:00
parent 73aab6768c
commit df1439e31f
2 changed files with 74 additions and 14 deletions

View File

@ -33,6 +33,7 @@ __FBSDID("$FreeBSD$");
#include <sys/kernel.h>
#include <sys/ata.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/endian.h>
#include <sys/malloc.h>
#include <sys/lock.h>
@ -89,7 +90,7 @@ static void ahci_stop_fr(device_t dev);
static int ahci_sata_connect(struct ahci_channel *ch);
static int ahci_sata_phy_reset(device_t dev);
static int ahci_wait_ready(device_t dev, int t);
static int ahci_wait_ready(device_t dev, int t, int t0);
static void ahci_issue_recovery(device_t dev);
static void ahci_process_read_log(device_t dev, union ccb *ccb);
@ -889,6 +890,7 @@ ahci_ch_attach(device_t dev)
device_get_unit(dev), "pm_level", &ch->pm_level);
if (ch->pm_level > 3)
callout_init_mtx(&ch->pm_timer, &ch->mtx, 0);
callout_init_mtx(&ch->reset_timer, &ch->mtx, 0);
/* Limit speed for my onboard JMicron external port.
* It is not eSATA really. */
if (pci_get_devid(ctlr->dev) == 0x2363197b &&
@ -1005,6 +1007,11 @@ ahci_ch_detach(device_t dev)
mtx_lock(&ch->mtx);
xpt_async(AC_LOST_DEVICE, ch->path, NULL);
/* Forget about reset. */
if (ch->resetting) {
ch->resetting = 0;
xpt_release_simq(ch->sim, TRUE);
}
xpt_free_path(ch->path);
xpt_bus_deregister(cam_sim_path(ch->sim));
cam_sim_free(ch->sim, /*free_devq*/TRUE);
@ -1012,6 +1019,7 @@ ahci_ch_detach(device_t dev)
if (ch->pm_level > 3)
callout_drain(&ch->pm_timer);
callout_drain(&ch->reset_timer);
bus_teardown_intr(dev, ch->r_irq, ch->ih);
bus_release_resource(dev, SYS_RES_IRQ, ATA_IRQ_RID, ch->r_irq);
@ -1077,6 +1085,12 @@ ahci_ch_suspend(device_t dev)
mtx_lock(&ch->mtx);
xpt_freeze_simq(ch->sim, 1);
/* Forget about reset. */
if (ch->resetting) {
ch->resetting = 0;
callout_stop(&ch->reset_timer);
xpt_release_simq(ch->sim, TRUE);
}
while (ch->oslots)
msleep(ch, &ch->mtx, PRIBIO, "ahcisusp", hz/100);
ahci_ch_deinit(dev);
@ -2320,7 +2334,7 @@ ahci_start_fr(device_t dev)
}
static int
ahci_wait_ready(device_t dev, int t)
ahci_wait_ready(device_t dev, int t, int t0)
{
struct ahci_channel *ch = device_get_softc(dev);
int timeout = 0;
@ -2328,18 +2342,49 @@ ahci_wait_ready(device_t dev, int t)
while ((val = ATA_INL(ch->r_mem, AHCI_P_TFD)) &
(ATA_S_BUSY | ATA_S_DRQ)) {
DELAY(1000);
if (timeout++ > t) {
device_printf(dev, "device is not ready (timeout %dms) "
"tfd = %08x\n", t, val);
if (timeout > t) {
if (t != 0) {
device_printf(dev,
"AHCI reset: device not ready after %dms "
"(tfd = %08x)\n",
MAX(t, 0) + t0, val);
}
return (EBUSY);
}
}
DELAY(1000);
timeout++;
}
if (bootverbose)
device_printf(dev, "ready wait time=%dms\n", timeout);
device_printf(dev, "AHCI reset: device ready after %dms\n",
timeout + t0);
return (0);
}
static void
ahci_reset_to(void *arg)
{
device_t dev = arg;
struct ahci_channel *ch = device_get_softc(dev);
if (ch->resetting == 0)
return;
ch->resetting--;
if (ahci_wait_ready(dev, ch->resetting == 0 ? -1 : 0,
(310 - ch->resetting) * 100) == 0) {
ch->resetting = 0;
xpt_release_simq(ch->sim, TRUE);
return;
}
if (ch->resetting == 0) {
ahci_stop(dev);
ahci_clo(dev);
ahci_start(dev, 1);
xpt_release_simq(ch->sim, TRUE);
return;
}
callout_schedule(&ch->reset_timer, hz / 10);
}
static void
ahci_reset(device_t dev)
{
@ -2350,6 +2395,12 @@ ahci_reset(device_t dev)
xpt_freeze_simq(ch->sim, 1);
if (bootverbose)
device_printf(dev, "AHCI reset...\n");
/* Forget about previous reset. */
if (ch->resetting) {
ch->resetting = 0;
callout_stop(&ch->reset_timer);
xpt_release_simq(ch->sim, TRUE);
}
/* Requeue freezed command. */
if (ch->frozen) {
union ccb *fccb = ch->frozen;
@ -2390,7 +2441,7 @@ ahci_reset(device_t dev)
if (!ahci_sata_phy_reset(dev)) {
if (bootverbose)
device_printf(dev,
"AHCI reset done: phy reset found no device\n");
"AHCI reset: device not found\n");
ch->devices = 0;
/* Enable wanted port interrupts */
ATA_OUTL(ch->r_mem, AHCI_P_IE,
@ -2398,9 +2449,15 @@ ahci_reset(device_t dev)
xpt_release_simq(ch->sim, TRUE);
return;
}
if (bootverbose)
device_printf(dev, "AHCI reset: device found\n");
/* Wait for clearing busy status. */
if (ahci_wait_ready(dev, 15000))
ahci_clo(dev);
if (ahci_wait_ready(dev, dumping ? 31000 : 0, 0)) {
if (dumping)
ahci_clo(dev);
else
ch->resetting = 310;
}
ahci_start(dev, 1);
ch->devices = 1;
/* Enable wanted port interrupts */
@ -2410,9 +2467,10 @@ ahci_reset(device_t dev)
((ch->pm_level == 0) ? AHCI_P_IX_PRC | AHCI_P_IX_PC : 0) |
AHCI_P_IX_DP | AHCI_P_IX_UF | (ctlr->ccc ? 0 : AHCI_P_IX_SDB) |
AHCI_P_IX_DS | AHCI_P_IX_PS | (ctlr->ccc ? 0 : AHCI_P_IX_DHR)));
if (bootverbose)
device_printf(dev, "AHCI reset done: device found\n");
xpt_release_simq(ch->sim, TRUE);
if (ch->resetting)
callout_reset(&ch->reset_timer, hz / 10, ahci_reset_to, dev);
else
xpt_release_simq(ch->sim, TRUE);
}
static int

View File

@ -412,8 +412,10 @@ struct ahci_channel {
int fatalerr; /* Fatal error happend */
int lastslot; /* Last used slot */
int taggedtarget; /* Last tagged target */
int resetting; /* Hard-reset in progress. */
union ccb *frozen; /* Frozen command */
struct callout pm_timer; /* Power management events */
struct callout reset_timer; /* Hard-reset timeout */
struct ahci_device user[16]; /* User-specified settings */
struct ahci_device curr[16]; /* Current settings */