diff options
author | Tejun Heo <htejun@gmail.com> | 2006-07-03 11:07:26 +0400 |
---|---|---|
committer | Jeff Garzik <jeff@garzik.org> | 2006-07-06 06:16:28 +0400 |
commit | d6f26d1f1f1128a896f38a7f8426daed0a1205a2 (patch) | |
tree | ee86bb8f6fff0e1c8df78978ae7f402fbc1dc454 /drivers/scsi/libata-scsi.c | |
parent | 02670bf379267f55a43aa57f6895689697e90eb3 (diff) | |
download | linux-d6f26d1f1f1128a896f38a7f8426daed0a1205a2.tar.xz |
[PATCH] libata: reimplement per-dev PM
Reimplement per-dev PM. The original implementation directly put the
device into suspended mode and didn't synchronize w/ EH operations
including hotplug. This patch reimplements ata_scsi_device_suspend()
and ata_scsi_device_resume() such that they request EH to perform the
respective operations. Both functions synchronize with hotplug such
that it doesn't operate on detached devices.
Suspend waits for completion but resume just issues request and
returns. This allows parallel wake up of devices and thus speeds up
system resume.
Due to sdev detach synchronization, it's not feasible to separate out
EH requesting from sdev handling; thus, ata_device_suspend/resume()
are removed and everything is implemented in the respective
libata-scsi functions.
Signed-off-by: Tejun Heo <htejun@gmail.com>
Signed-off-by: Jeff Garzik <jeff@garzik.org>
Diffstat (limited to 'drivers/scsi/libata-scsi.c')
-rw-r--r-- | drivers/scsi/libata-scsi.c | 121 |
1 files changed, 115 insertions, 6 deletions
diff --git a/drivers/scsi/libata-scsi.c b/drivers/scsi/libata-scsi.c index ba3e515f2b9d..7ced41ecde86 100644 --- a/drivers/scsi/libata-scsi.c +++ b/drivers/scsi/libata-scsi.c @@ -397,20 +397,129 @@ void ata_dump_status(unsigned id, struct ata_taskfile *tf) } } -int ata_scsi_device_resume(struct scsi_device *sdev) +/** + * ata_scsi_device_suspend - suspend ATA device associated with sdev + * @sdev: the SCSI device to suspend + * @state: target power management state + * + * Request suspend EH action on the ATA device associated with + * @sdev and wait for the operation to complete. + * + * LOCKING: + * Kernel thread context (may sleep). + * + * RETURNS: + * 0 on success, -errno otherwise. + */ +int ata_scsi_device_suspend(struct scsi_device *sdev, pm_message_t state) { struct ata_port *ap = ata_shost_to_port(sdev->host); - struct ata_device *dev = __ata_scsi_find_dev(ap, sdev); + struct ata_device *dev = ata_scsi_find_dev(ap, sdev); + unsigned long flags; + unsigned int action; + int rc = 0; + + if (!dev) + goto out; + + spin_lock_irqsave(ap->lock, flags); + + /* wait for the previous resume to complete */ + while (dev->flags & ATA_DFLAG_SUSPENDED) { + spin_unlock_irqrestore(ap->lock, flags); + ata_port_wait_eh(ap); + spin_lock_irqsave(ap->lock, flags); + } + + /* if @sdev is already detached, nothing to do */ + if (sdev->sdev_state == SDEV_OFFLINE || + sdev->sdev_state == SDEV_CANCEL || sdev->sdev_state == SDEV_DEL) + goto out_unlock; + + /* request suspend */ + action = ATA_EH_SUSPEND; + if (state.event != PM_EVENT_SUSPEND) + action |= ATA_EH_PM_FREEZE; + ap->eh_info.dev_action[dev->devno] |= action; + ap->eh_info.flags |= ATA_EHI_QUIET; + ata_port_schedule_eh(ap); + + spin_unlock_irqrestore(ap->lock, flags); + + /* wait for EH to do the job */ + ata_port_wait_eh(ap); + + spin_lock_irqsave(ap->lock, flags); + + /* If @sdev is still attached but the associated ATA device + * isn't suspended, the operation failed. + */ + if (sdev->sdev_state != SDEV_OFFLINE && + sdev->sdev_state != SDEV_CANCEL && sdev->sdev_state != SDEV_DEL && + !(dev->flags & ATA_DFLAG_SUSPENDED)) + rc = -EIO; - return ata_device_resume(dev); + out_unlock: + spin_unlock_irqrestore(ap->lock, flags); + out: + if (rc == 0) + sdev->sdev_gendev.power.power_state = state; + return rc; } -int ata_scsi_device_suspend(struct scsi_device *sdev, pm_message_t state) +/** + * ata_scsi_device_resume - resume ATA device associated with sdev + * @sdev: the SCSI device to resume + * + * Request resume EH action on the ATA device associated with + * @sdev and return immediately. This enables parallel + * wakeup/spinup of devices. + * + * LOCKING: + * Kernel thread context (may sleep). + * + * RETURNS: + * 0. + */ +int ata_scsi_device_resume(struct scsi_device *sdev) { struct ata_port *ap = ata_shost_to_port(sdev->host); - struct ata_device *dev = __ata_scsi_find_dev(ap, sdev); + struct ata_device *dev = ata_scsi_find_dev(ap, sdev); + struct ata_eh_info *ehi = &ap->eh_info; + unsigned long flags; + unsigned int action; + + if (!dev) + goto out; + + spin_lock_irqsave(ap->lock, flags); + + /* if @sdev is already detached, nothing to do */ + if (sdev->sdev_state == SDEV_OFFLINE || + sdev->sdev_state == SDEV_CANCEL || sdev->sdev_state == SDEV_DEL) + goto out_unlock; - return ata_device_suspend(dev, state); + /* request resume */ + action = ATA_EH_RESUME; + if (sdev->sdev_gendev.power.power_state.event == PM_EVENT_SUSPEND) + __ata_ehi_hotplugged(ehi); + else + action |= ATA_EH_PM_FREEZE | ATA_EH_SOFTRESET; + ehi->dev_action[dev->devno] |= action; + + /* We don't want autopsy and verbose EH messages. Disable + * those if we're the only device on this link. + */ + if (ata_port_max_devices(ap) == 1) + ehi->flags |= ATA_EHI_NO_AUTOPSY | ATA_EHI_QUIET; + + ata_port_schedule_eh(ap); + + out_unlock: + spin_unlock_irqrestore(ap->lock, flags); + out: + sdev->sdev_gendev.power.power_state = PMSG_ON; + return 0; } /** |