[PATCH] libata: implement PM EH actions

Implement two PM per-dev EH actions - ATA_EH_SUSPEND and
ATA_EH_RESUME.  Each action puts the target device into suspended mode
and resumes from it respectively.

Once a device is put to suspended mode, no EH operations other than
RESUME is allowed on the device.  The device will stay suspended till
it gets resumed and thus reset and revalidated.  To implement this, a
new device state helper - ata_dev_ready() - is implemented and used in
EH action implementations to make them operate only on attached &
running devices.

If all possible devices on a port are suspended, reset is skipped too.
This prevents spurious events including hotplug events from disrupting
suspended devices.

Signed-off-by: Tejun Heo <htejun@gmail.com>
Signed-off-by: Jeff Garzik <jeff@garzik.org>
This commit is contained in:
Tejun Heo 2006-07-03 16:07:26 +09:00 committed by Jeff Garzik
parent c0b6c0377c
commit 02670bf379
3 changed files with 198 additions and 4 deletions

View file

@ -2146,7 +2146,7 @@ int ata_set_mode(struct ata_port *ap, struct ata_device **r_failed_dev)
* return error code and failing device on failure. * return error code and failing device on failure.
*/ */
for (i = 0; i < ATA_MAX_DEVICES; i++) { for (i = 0; i < ATA_MAX_DEVICES; i++) {
if (ata_dev_enabled(&ap->device[i])) { if (ata_dev_ready(&ap->device[i])) {
ap->ops->set_mode(ap); ap->ops->set_mode(ap);
break; break;
} }
@ -2212,7 +2212,8 @@ int ata_set_mode(struct ata_port *ap, struct ata_device **r_failed_dev)
for (i = 0; i < ATA_MAX_DEVICES; i++) { for (i = 0; i < ATA_MAX_DEVICES; i++) {
dev = &ap->device[i]; dev = &ap->device[i];
if (!ata_dev_enabled(dev)) /* don't udpate suspended devices' xfer mode */
if (!ata_dev_ready(dev))
continue; continue;
rc = ata_dev_set_mode(dev); rc = ata_dev_set_mode(dev);

View file

@ -1610,7 +1610,7 @@ static int ata_eh_revalidate_and_attach(struct ata_port *ap,
dev = &ap->device[i]; dev = &ap->device[i];
action = ata_eh_dev_action(dev); action = ata_eh_dev_action(dev);
if (action & ATA_EH_REVALIDATE && ata_dev_enabled(dev)) { if (action & ATA_EH_REVALIDATE && ata_dev_ready(dev)) {
if (ata_port_offline(ap)) { if (ata_port_offline(ap)) {
rc = -EIO; rc = -EIO;
break; break;
@ -1653,6 +1653,164 @@ static int ata_eh_revalidate_and_attach(struct ata_port *ap,
return rc; return rc;
} }
/**
* ata_eh_suspend - handle suspend EH action
* @ap: target host port
* @r_failed_dev: result parameter to indicate failing device
*
* Handle suspend EH action. Disk devices are spinned down and
* other types of devices are just marked suspended. Once
* suspended, no EH action to the device is allowed until it is
* resumed.
*
* LOCKING:
* Kernel thread context (may sleep).
*
* RETURNS:
* 0 on success, -errno otherwise
*/
static int ata_eh_suspend(struct ata_port *ap, struct ata_device **r_failed_dev)
{
struct ata_device *dev;
int i, rc = 0;
DPRINTK("ENTER\n");
for (i = 0; i < ATA_MAX_DEVICES; i++) {
unsigned long flags;
unsigned int action, err_mask;
dev = &ap->device[i];
action = ata_eh_dev_action(dev);
if (!ata_dev_enabled(dev) || !(action & ATA_EH_SUSPEND))
continue;
WARN_ON(dev->flags & ATA_DFLAG_SUSPENDED);
ata_eh_about_to_do(ap, dev, ATA_EH_SUSPEND);
if (dev->class == ATA_DEV_ATA && !(action & ATA_EH_PM_FREEZE)) {
/* flush cache */
rc = ata_flush_cache(dev);
if (rc)
break;
/* spin down */
err_mask = ata_do_simple_cmd(dev, ATA_CMD_STANDBYNOW1);
if (err_mask) {
ata_dev_printk(dev, KERN_ERR, "failed to "
"spin down (err_mask=0x%x)\n",
err_mask);
rc = -EIO;
break;
}
}
spin_lock_irqsave(ap->lock, flags);
dev->flags |= ATA_DFLAG_SUSPENDED;
spin_unlock_irqrestore(ap->lock, flags);
ata_eh_done(ap, dev, ATA_EH_SUSPEND);
}
if (rc)
*r_failed_dev = dev;
DPRINTK("EXIT\n");
return 0;
}
/**
* ata_eh_prep_resume - prep for resume EH action
* @ap: target host port
*
* Clear SUSPENDED in preparation for scheduled resume actions.
* This allows other parts of EH to access the devices being
* resumed.
*
* LOCKING:
* Kernel thread context (may sleep).
*/
static void ata_eh_prep_resume(struct ata_port *ap)
{
struct ata_device *dev;
unsigned long flags;
int i;
DPRINTK("ENTER\n");
for (i = 0; i < ATA_MAX_DEVICES; i++) {
unsigned int action;
dev = &ap->device[i];
action = ata_eh_dev_action(dev);
if (!ata_dev_enabled(dev) || !(action & ATA_EH_RESUME))
continue;
spin_lock_irqsave(ap->lock, flags);
dev->flags &= ~ATA_DFLAG_SUSPENDED;
spin_unlock_irqrestore(ap->lock, flags);
}
DPRINTK("EXIT\n");
}
/**
* ata_eh_resume - handle resume EH action
* @ap: target host port
* @r_failed_dev: result parameter to indicate failing device
*
* Handle resume EH action. Target devices are already reset and
* revalidated. Spinning up is the only operation left.
*
* LOCKING:
* Kernel thread context (may sleep).
*
* RETURNS:
* 0 on success, -errno otherwise
*/
static int ata_eh_resume(struct ata_port *ap, struct ata_device **r_failed_dev)
{
struct ata_device *dev;
int i, rc = 0;
DPRINTK("ENTER\n");
for (i = 0; i < ATA_MAX_DEVICES; i++) {
unsigned int action, err_mask;
dev = &ap->device[i];
action = ata_eh_dev_action(dev);
if (!ata_dev_enabled(dev) || !(action & ATA_EH_RESUME))
continue;
ata_eh_about_to_do(ap, dev, ATA_EH_RESUME);
if (dev->class == ATA_DEV_ATA && !(action & ATA_EH_PM_FREEZE)) {
err_mask = ata_do_simple_cmd(dev,
ATA_CMD_IDLEIMMEDIATE);
if (err_mask) {
ata_dev_printk(dev, KERN_ERR, "failed to "
"spin up (err_mask=0x%x)\n",
err_mask);
rc = -EIO;
break;
}
}
ata_eh_done(ap, dev, ATA_EH_RESUME);
}
if (rc)
*r_failed_dev = dev;
DPRINTK("EXIT\n");
return 0;
}
static int ata_port_nr_enabled(struct ata_port *ap) static int ata_port_nr_enabled(struct ata_port *ap)
{ {
int i, cnt = 0; int i, cnt = 0;
@ -1678,6 +1836,18 @@ static int ata_eh_skip_recovery(struct ata_port *ap)
struct ata_eh_context *ehc = &ap->eh_context; struct ata_eh_context *ehc = &ap->eh_context;
int i; int i;
/* skip if all possible devices are suspended */
for (i = 0; i < ata_port_max_devices(ap); i++) {
struct ata_device *dev = &ap->device[i];
if (ata_dev_absent(dev) || ata_dev_ready(dev))
break;
}
if (i == ata_port_max_devices(ap))
return 1;
/* always thaw frozen port and recover failed devices */
if (ap->pflags & ATA_PFLAG_FROZEN || ata_port_nr_enabled(ap)) if (ap->pflags & ATA_PFLAG_FROZEN || ata_port_nr_enabled(ap))
return 0; return 0;
@ -1752,6 +1922,9 @@ static int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
if (ap->pflags & ATA_PFLAG_UNLOADING) if (ap->pflags & ATA_PFLAG_UNLOADING)
goto out; goto out;
/* prep for resume */
ata_eh_prep_resume(ap);
/* skip EH if possible. */ /* skip EH if possible. */
if (ata_eh_skip_recovery(ap)) if (ata_eh_skip_recovery(ap))
ehc->i.action = 0; ehc->i.action = 0;
@ -1779,6 +1952,11 @@ static int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
if (rc) if (rc)
goto dev_fail; goto dev_fail;
/* resume devices */
rc = ata_eh_resume(ap, &dev);
if (rc)
goto dev_fail;
/* configure transfer mode if the port has been reset */ /* configure transfer mode if the port has been reset */
if (ehc->i.flags & ATA_EHI_DID_RESET) { if (ehc->i.flags & ATA_EHI_DID_RESET) {
rc = ata_set_mode(ap, &dev); rc = ata_set_mode(ap, &dev);
@ -1788,6 +1966,11 @@ static int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
} }
} }
/* suspend devices */
rc = ata_eh_suspend(ap, &dev);
if (rc)
goto dev_fail;
goto out; goto out;
dev_fail: dev_fail:

View file

@ -131,6 +131,7 @@ enum {
ATA_DFLAG_CFG_MASK = (1 << 8) - 1, ATA_DFLAG_CFG_MASK = (1 << 8) - 1,
ATA_DFLAG_PIO = (1 << 8), /* device currently in PIO mode */ ATA_DFLAG_PIO = (1 << 8), /* device currently in PIO mode */
ATA_DFLAG_SUSPENDED = (1 << 9), /* device suspended */
ATA_DFLAG_INIT_MASK = (1 << 16) - 1, ATA_DFLAG_INIT_MASK = (1 << 16) - 1,
ATA_DFLAG_DETACH = (1 << 16), ATA_DFLAG_DETACH = (1 << 16),
@ -253,9 +254,13 @@ enum {
ATA_EH_REVALIDATE = (1 << 0), ATA_EH_REVALIDATE = (1 << 0),
ATA_EH_SOFTRESET = (1 << 1), ATA_EH_SOFTRESET = (1 << 1),
ATA_EH_HARDRESET = (1 << 2), ATA_EH_HARDRESET = (1 << 2),
ATA_EH_SUSPEND = (1 << 3),
ATA_EH_RESUME = (1 << 4),
ATA_EH_PM_FREEZE = (1 << 5),
ATA_EH_RESET_MASK = ATA_EH_SOFTRESET | ATA_EH_HARDRESET, ATA_EH_RESET_MASK = ATA_EH_SOFTRESET | ATA_EH_HARDRESET,
ATA_EH_PERDEV_MASK = ATA_EH_REVALIDATE, ATA_EH_PERDEV_MASK = ATA_EH_REVALIDATE | ATA_EH_SUSPEND |
ATA_EH_RESUME | ATA_EH_PM_FREEZE,
/* ata_eh_info->flags */ /* ata_eh_info->flags */
ATA_EHI_HOTPLUGGED = (1 << 0), /* could have been hotplugged */ ATA_EHI_HOTPLUGGED = (1 << 0), /* could have been hotplugged */
@ -944,6 +949,11 @@ static inline unsigned int ata_dev_absent(const struct ata_device *dev)
return ata_class_absent(dev->class); return ata_class_absent(dev->class);
} }
static inline unsigned int ata_dev_ready(const struct ata_device *dev)
{
return ata_dev_enabled(dev) && !(dev->flags & ATA_DFLAG_SUSPENDED);
}
/* /*
* port helpers * port helpers
*/ */