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:
parent
73aab6768c
commit
df1439e31f
@ -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
|
||||
|
@ -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 */
|
||||
|
Loading…
Reference in New Issue
Block a user