* [PATCH v6 0/3] Accelerate Storage Resume (2x or more)
@ 2014-03-14 20:52 Dan Williams
2014-03-14 20:52 ` [PATCH v6 1/3] libata, libsas: kill pm_result and related cleanup Dan Williams
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Dan Williams @ 2014-03-14 20:52 UTC (permalink / raw)
To: tj, JBottomley
Cc: Len Brown, linux-scsi, Phillip Susi, linux-ide, Alan Stern,
Todd Brandt, Todd Brandt
Changes since v5:
* Per Tejun, fixed up the naming of the ata_port helper routines
* Cleaned up the return value of ata_port_pm helper routines to be 'void'
* Per Tejun, added a check of whether async scanning is disabled to gate
whether async resume will be performed out of caution for systems that
do not handle staggered spin-up outside of the kernel. Clarified this
in the changelog and added commentary in the code.
===
It is not everyday that a kernel operation gets > 2x faster! Great find
by Todd and his AnalyzeSuspend tool [1].
Todd is on vacation so I am taking care of these patches to make sure
they get in the queue for 3.15.
The significant changes from Todd's last submission [2]:
1/ Split out the pure cleanup into its own patch, and expand it to clean
up libsas as well
2/ Move the entirety of scsi_device resume to an async context. As
written, v4 of the patchset overlapped the scsi-start-stop command
with the scsi_device_resume(). Now in v5 the queue restart is
properly ordered with the completion of the scsi-start-stop command.
Resume completion time as reported by:
"echo devices > /sys/power/pm_test && echo mem > /sys/power/state"
BEFORE: PM: resume of devices complete after 2271.097 msecs
AFTER: PM: resume of devices complete after 1057.404 msecs
On a system with two SSDs attached via AHCI.
[1]: Todd's testing showing up to 12x resume latency improvement in some
cases:
https://01.org/suspendresume/blogs/tebrandt/2013/hard-disk-resume-optimization-simpler-approach
[2]: Todd's v4 patchset:
[PATCH v4 0/2] http://marc.info/?l=linux-ide&m=138984698103487&w=2
[PATCH v4 1/2] http://marc.info/?l=linux-ide&m=138984713203515&w=2
[PATCH v4 2/2] http://marc.info/?l=linux-ide&m=138984722003539&w=2
---
Dan Williams (2):
libata, libsas: kill pm_result and related cleanup
scsi: async sd resume
Todd Brandt (1):
libata: async resume
drivers/ata/libata-core.c | 135 ++++++++++++++++++-----------------------
drivers/ata/libata-eh.c | 13 +---
drivers/scsi/Kconfig | 3 +
drivers/scsi/libsas/sas_ata.c | 35 ++---------
drivers/scsi/scsi.c | 3 +
drivers/scsi/scsi_pm.c | 128 ++++++++++++++++++++++++++++++---------
drivers/scsi/scsi_priv.h | 2 +
drivers/scsi/scsi_scan.c | 2 -
drivers/scsi/sd.c | 1
include/linux/libata.h | 11 +--
include/scsi/libsas.h | 1
11 files changed, 180 insertions(+), 154 deletions(-)
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v6 1/3] libata, libsas: kill pm_result and related cleanup
2014-03-14 20:52 [PATCH v6 0/3] Accelerate Storage Resume (2x or more) Dan Williams
@ 2014-03-14 20:52 ` Dan Williams
2014-03-18 16:03 ` Tejun Heo
2014-03-18 20:18 ` Tejun Heo
2014-03-14 20:52 ` [PATCH v6 2/3] libata: async resume Dan Williams
2014-03-14 20:52 ` [PATCH v6 3/3] scsi: async sd resume Dan Williams
2 siblings, 2 replies; 7+ messages in thread
From: Dan Williams @ 2014-03-14 20:52 UTC (permalink / raw)
To: tj, JBottomley
Cc: Phillip Susi, Todd Brandt, Alan Stern, linux-scsi, linux-ide
Tejun says:
"At least for libata, worrying about suspend/resume failures don't make
whole lot of sense. If suspend failed, just proceed with suspend. If
the device can't be woken up afterwards, that's that. There isn't
anything we could have done differently anyway. The same for resume, if
spinup fails, the device is dud and the following commands will invoke
EH actions and will eventually fail. Again, there really isn't any
*choice* to make. Just making sure the errors are handled gracefully
(ie. don't crash) and the following commands are handled correctly
should be enough."
The only libata user that actually cares about the result from a suspend
operation is libsas. However, it only cares about whether queuing a new
operation collides with an in-flight one. All libsas does with the
error is retry, but we can just let libata wait for the previous
operation before continuing.
Other cleanups include:
1/ Unifying all ata port pm operations on an ata_port_pm_ prefix
2/ Marking all ata port pm helper routines as returning void, only
ata_port_pm_ entry points need to fake a 0 return value.
3/ Killing ata_port_{suspend|resume}_common() in favor of calling
ata_port_request_pm() directly
4/ Killing the wrappers that just do a to_ata_port() conversion
5/ Clearly marking the entry points that do async operations with an
_async suffix.
Reference: http://marc.info/?l=linux-scsi&m=138995409532286&w=2
Cc: Phillip Susi <psusi@ubuntu.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Suggested-by: Tejun Heo <tj@kernel.org>
Signed-off-by: Todd Brandt <todd.e.brandt@intel.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
drivers/ata/libata-core.c | 135 ++++++++++++++++++-----------------------
drivers/ata/libata-eh.c | 13 +---
drivers/scsi/libsas/sas_ata.c | 35 ++---------
include/linux/libata.h | 11 +--
include/scsi/libsas.h | 1
5 files changed, 71 insertions(+), 124 deletions(-)
diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c
index 1a3dbd1b196e..66110ed2c1c0 100644
--- a/drivers/ata/libata-core.c
+++ b/drivers/ata/libata-core.c
@@ -5351,22 +5351,17 @@ bool ata_link_offline(struct ata_link *link)
}
#ifdef CONFIG_PM
-static int ata_port_request_pm(struct ata_port *ap, pm_message_t mesg,
- unsigned int action, unsigned int ehi_flags,
- int *async)
+static void ata_port_request_pm(struct ata_port *ap, pm_message_t mesg,
+ unsigned int action, unsigned int ehi_flags,
+ bool async)
{
struct ata_link *link;
unsigned long flags;
- int rc = 0;
/* Previous resume operation might still be in
* progress. Wait for PM_PENDING to clear.
*/
if (ap->pflags & ATA_PFLAG_PM_PENDING) {
- if (async) {
- *async = -EAGAIN;
- return 0;
- }
ata_port_wait_eh(ap);
WARN_ON(ap->pflags & ATA_PFLAG_PM_PENDING);
}
@@ -5375,11 +5370,6 @@ static int ata_port_request_pm(struct ata_port *ap, pm_message_t mesg,
spin_lock_irqsave(ap->lock, flags);
ap->pm_mesg = mesg;
- if (async)
- ap->pm_result = async;
- else
- ap->pm_result = &rc;
-
ap->pflags |= ATA_PFLAG_PM_PENDING;
ata_for_each_link(link, ap, HOST_FIRST) {
link->eh_info.action |= action;
@@ -5390,87 +5380,81 @@ static int ata_port_request_pm(struct ata_port *ap, pm_message_t mesg,
spin_unlock_irqrestore(ap->lock, flags);
- /* wait and check result */
if (!async) {
ata_port_wait_eh(ap);
WARN_ON(ap->pflags & ATA_PFLAG_PM_PENDING);
}
-
- return rc;
}
-static int __ata_port_suspend_common(struct ata_port *ap, pm_message_t mesg, int *async)
+/*
+ * On some hardware, device fails to respond after spun down for suspend. As
+ * the device won't be used before being resumed, we don't need to touch the
+ * device. Ask EH to skip the usual stuff and proceed directly to suspend.
+ *
+ * http://thread.gmane.org/gmane.linux.ide/46764
+ */
+static const unsigned int ata_port_suspend_ehi = ATA_EHI_QUIET
+ | ATA_EHI_NO_AUTOPSY
+ | ATA_EHI_NO_RECOVERY;
+
+static void ata_port_suspend(struct ata_port *ap, pm_message_t mesg)
{
- /*
- * On some hardware, device fails to respond after spun down
- * for suspend. As the device won't be used before being
- * resumed, we don't need to touch the device. Ask EH to skip
- * the usual stuff and proceed directly to suspend.
- *
- * http://thread.gmane.org/gmane.linux.ide/46764
- */
- unsigned int ehi_flags = ATA_EHI_QUIET | ATA_EHI_NO_AUTOPSY |
- ATA_EHI_NO_RECOVERY;
- return ata_port_request_pm(ap, mesg, 0, ehi_flags, async);
+ ata_port_request_pm(ap, mesg, 0, ata_port_suspend_ehi, false);
}
-static int ata_port_suspend_common(struct device *dev, pm_message_t mesg)
+static void ata_port_suspend_async(struct ata_port *ap, pm_message_t mesg)
{
- struct ata_port *ap = to_ata_port(dev);
-
- return __ata_port_suspend_common(ap, mesg, NULL);
+ ata_port_request_pm(ap, mesg, 0, ata_port_suspend_ehi, true);
}
-static int ata_port_suspend(struct device *dev)
+static int ata_port_pm_suspend(struct device *dev)
{
+ struct ata_port *ap = to_ata_port(dev);
+
if (pm_runtime_suspended(dev))
return 0;
- return ata_port_suspend_common(dev, PMSG_SUSPEND);
+ ata_port_suspend(ap, PMSG_SUSPEND);
+ return 0;
}
-static int ata_port_do_freeze(struct device *dev)
+static int ata_port_pm_freeze(struct device *dev)
{
+ struct ata_port *ap = to_ata_port(dev);
+
if (pm_runtime_suspended(dev))
return 0;
- return ata_port_suspend_common(dev, PMSG_FREEZE);
+ ata_port_suspend(ap, PMSG_FREEZE);
+ return 0;
}
-static int ata_port_poweroff(struct device *dev)
+static int ata_port_pm_poweroff(struct device *dev)
{
- return ata_port_suspend_common(dev, PMSG_HIBERNATE);
+ ata_port_suspend(to_ata_port(dev), PMSG_HIBERNATE);
+ return 0;
}
-static int __ata_port_resume_common(struct ata_port *ap, pm_message_t mesg,
- int *async)
-{
- int rc;
+static const unsigned int ata_port_resume_ehi = ATA_EHI_NO_AUTOPSY
+ | ATA_EHI_QUIET;
- rc = ata_port_request_pm(ap, mesg, ATA_EH_RESET,
- ATA_EHI_NO_AUTOPSY | ATA_EHI_QUIET, async);
- return rc;
+static void ata_port_resume(struct ata_port *ap, pm_message_t mesg)
+{
+ ata_port_request_pm(ap, mesg, ATA_EH_RESET, ata_port_resume_ehi, false);
}
-static int ata_port_resume_common(struct device *dev, pm_message_t mesg)
+static void ata_port_resume_async(struct ata_port *ap, pm_message_t mesg)
{
- struct ata_port *ap = to_ata_port(dev);
-
- return __ata_port_resume_common(ap, mesg, NULL);
+ ata_port_request_pm(ap, mesg, ATA_EH_RESET, ata_port_resume_ehi, true);
}
-static int ata_port_resume(struct device *dev)
+static int ata_port_pm_resume(struct device *dev)
{
- int rc;
-
- rc = ata_port_resume_common(dev, PMSG_RESUME);
- if (!rc) {
- pm_runtime_disable(dev);
- pm_runtime_set_active(dev);
- pm_runtime_enable(dev);
- }
-
- return rc;
+ ata_port_resume(to_ata_port(dev), PMSG_RESUME);
+ pm_runtime_disable(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ return 0;
}
/*
@@ -5499,21 +5483,23 @@ static int ata_port_runtime_idle(struct device *dev)
static int ata_port_runtime_suspend(struct device *dev)
{
- return ata_port_suspend_common(dev, PMSG_AUTO_SUSPEND);
+ ata_port_suspend(to_ata_port(dev), PMSG_AUTO_SUSPEND);
+ return 0;
}
static int ata_port_runtime_resume(struct device *dev)
{
- return ata_port_resume_common(dev, PMSG_AUTO_RESUME);
+ ata_port_resume(to_ata_port(dev), PMSG_AUTO_RESUME);
+ return 0;
}
static const struct dev_pm_ops ata_port_pm_ops = {
- .suspend = ata_port_suspend,
- .resume = ata_port_resume,
- .freeze = ata_port_do_freeze,
- .thaw = ata_port_resume,
- .poweroff = ata_port_poweroff,
- .restore = ata_port_resume,
+ .suspend = ata_port_pm_suspend,
+ .resume = ata_port_pm_resume,
+ .freeze = ata_port_pm_freeze,
+ .thaw = ata_port_pm_resume,
+ .poweroff = ata_port_pm_poweroff,
+ .restore = ata_port_pm_resume,
.runtime_suspend = ata_port_runtime_suspend,
.runtime_resume = ata_port_runtime_resume,
@@ -5525,18 +5511,17 @@ static const struct dev_pm_ops ata_port_pm_ops = {
* level. sas suspend/resume is async to allow parallel port recovery
* since sas has multiple ata_port instances per Scsi_Host.
*/
-int ata_sas_port_async_suspend(struct ata_port *ap, int *async)
+void ata_sas_port_suspend(struct ata_port *ap)
{
- return __ata_port_suspend_common(ap, PMSG_SUSPEND, async);
+ ata_port_suspend_async(ap, PMSG_SUSPEND);
}
-EXPORT_SYMBOL_GPL(ata_sas_port_async_suspend);
+EXPORT_SYMBOL_GPL(ata_sas_port_suspend);
-int ata_sas_port_async_resume(struct ata_port *ap, int *async)
+void ata_sas_port_resume(struct ata_port *ap)
{
- return __ata_port_resume_common(ap, PMSG_RESUME, async);
+ ata_port_resume_async(ap, PMSG_RESUME);
}
-EXPORT_SYMBOL_GPL(ata_sas_port_async_resume);
-
+EXPORT_SYMBOL_GPL(ata_sas_port_resume);
/**
* ata_host_suspend - suspend host
diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c
index 6d8757008318..b4d0596f30a8 100644
--- a/drivers/ata/libata-eh.c
+++ b/drivers/ata/libata-eh.c
@@ -4069,7 +4069,7 @@ static void ata_eh_handle_port_suspend(struct ata_port *ap)
ata_acpi_set_state(ap, ap->pm_mesg);
out:
- /* report result */
+ /* update the flags */
spin_lock_irqsave(ap->lock, flags);
ap->pflags &= ~ATA_PFLAG_PM_PENDING;
@@ -4078,11 +4078,6 @@ static void ata_eh_handle_port_suspend(struct ata_port *ap)
else if (ap->pflags & ATA_PFLAG_FROZEN)
ata_port_schedule_eh(ap);
- if (ap->pm_result) {
- *ap->pm_result = rc;
- ap->pm_result = NULL;
- }
-
spin_unlock_irqrestore(ap->lock, flags);
return;
@@ -4134,13 +4129,9 @@ static void ata_eh_handle_port_resume(struct ata_port *ap)
/* tell ACPI that we're resuming */
ata_acpi_on_resume(ap);
- /* report result */
+ /* update the flags */
spin_lock_irqsave(ap->lock, flags);
ap->pflags &= ~(ATA_PFLAG_PM_PENDING | ATA_PFLAG_SUSPENDED);
- if (ap->pm_result) {
- *ap->pm_result = rc;
- ap->pm_result = NULL;
- }
spin_unlock_irqrestore(ap->lock, flags);
}
#endif /* CONFIG_PM */
diff --git a/drivers/scsi/libsas/sas_ata.c b/drivers/scsi/libsas/sas_ata.c
index d2895836f9fa..766098af4eb7 100644
--- a/drivers/scsi/libsas/sas_ata.c
+++ b/drivers/scsi/libsas/sas_ata.c
@@ -700,46 +700,26 @@ void sas_probe_sata(struct asd_sas_port *port)
}
-static bool sas_ata_flush_pm_eh(struct asd_sas_port *port, const char *func)
+static void sas_ata_flush_pm_eh(struct asd_sas_port *port, const char *func)
{
struct domain_device *dev, *n;
- bool retry = false;
list_for_each_entry_safe(dev, n, &port->dev_list, dev_list_node) {
- int rc;
-
if (!dev_is_sata(dev))
continue;
sas_ata_wait_eh(dev);
- rc = dev->sata_dev.pm_result;
- if (rc == -EAGAIN)
- retry = true;
- else if (rc) {
- /* since we don't have a
- * ->port_{suspend|resume} routine in our
- * ata_port ops, and no entanglements with
- * acpi, suspend should just be mechanical trip
- * through eh, catch cases where these
- * assumptions are invalidated
- */
- WARN_ONCE(1, "failed %s %s error: %d\n", func,
- dev_name(&dev->rphy->dev), rc);
- }
/* if libata failed to power manage the device, tear it down */
if (ata_dev_disabled(sas_to_ata_dev(dev)))
sas_fail_probe(dev, func, -ENODEV);
}
-
- return retry;
}
void sas_suspend_sata(struct asd_sas_port *port)
{
struct domain_device *dev;
- retry:
mutex_lock(&port->ha->disco_mutex);
list_for_each_entry(dev, &port->dev_list, dev_list_node) {
struct sata_device *sata;
@@ -751,20 +731,17 @@ void sas_suspend_sata(struct asd_sas_port *port)
if (sata->ap->pm_mesg.event == PM_EVENT_SUSPEND)
continue;
- sata->pm_result = -EIO;
- ata_sas_port_async_suspend(sata->ap, &sata->pm_result);
+ ata_sas_port_suspend(sata->ap);
}
mutex_unlock(&port->ha->disco_mutex);
- if (sas_ata_flush_pm_eh(port, __func__))
- goto retry;
+ sas_ata_flush_pm_eh(port, __func__);
}
void sas_resume_sata(struct asd_sas_port *port)
{
struct domain_device *dev;
- retry:
mutex_lock(&port->ha->disco_mutex);
list_for_each_entry(dev, &port->dev_list, dev_list_node) {
struct sata_device *sata;
@@ -776,13 +753,11 @@ void sas_resume_sata(struct asd_sas_port *port)
if (sata->ap->pm_mesg.event == PM_EVENT_ON)
continue;
- sata->pm_result = -EIO;
- ata_sas_port_async_resume(sata->ap, &sata->pm_result);
+ ata_sas_port_resume(sata->ap);
}
mutex_unlock(&port->ha->disco_mutex);
- if (sas_ata_flush_pm_eh(port, __func__))
- goto retry;
+ sas_ata_flush_pm_eh(port, __func__);
}
/**
diff --git a/include/linux/libata.h b/include/linux/libata.h
index bec6dbe939a0..5c09e86982c9 100644
--- a/include/linux/libata.h
+++ b/include/linux/libata.h
@@ -848,7 +848,6 @@ struct ata_port {
struct completion park_req_pending;
pm_message_t pm_mesg;
- int *pm_result;
enum ata_lpm_policy target_lpm_policy;
struct timer_list fastdrain_timer;
@@ -1140,16 +1139,14 @@ extern bool ata_link_offline(struct ata_link *link);
#ifdef CONFIG_PM
extern int ata_host_suspend(struct ata_host *host, pm_message_t mesg);
extern void ata_host_resume(struct ata_host *host);
-extern int ata_sas_port_async_suspend(struct ata_port *ap, int *async);
-extern int ata_sas_port_async_resume(struct ata_port *ap, int *async);
+extern void ata_sas_port_suspend(struct ata_port *ap);
+extern void ata_sas_port_resume(struct ata_port *ap);
#else
-static inline int ata_sas_port_async_suspend(struct ata_port *ap, int *async)
+static inline void ata_sas_port_suspend(struct ata_port *ap)
{
- return 0;
}
-static inline int ata_sas_port_async_resume(struct ata_port *ap, int *async)
+static inline void ata_sas_port_async_resume(struct ata_port *ap)
{
- return 0;
}
#endif
extern int ata_ratelimit(void);
diff --git a/include/scsi/libsas.h b/include/scsi/libsas.h
index f843dd8722a9..ef7872c20da9 100644
--- a/include/scsi/libsas.h
+++ b/include/scsi/libsas.h
@@ -172,7 +172,6 @@ struct sata_device {
enum ata_command_set command_set;
struct smp_resp rps_resp; /* report_phy_sata_resp */
u8 port_no; /* port number, if this is a PM (Port) */
- int pm_result;
struct ata_port *ap;
struct ata_host ata_host;
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v6 2/3] libata: async resume
2014-03-14 20:52 [PATCH v6 0/3] Accelerate Storage Resume (2x or more) Dan Williams
2014-03-14 20:52 ` [PATCH v6 1/3] libata, libsas: kill pm_result and related cleanup Dan Williams
@ 2014-03-14 20:52 ` Dan Williams
2014-03-14 20:52 ` [PATCH v6 3/3] scsi: async sd resume Dan Williams
2 siblings, 0 replies; 7+ messages in thread
From: Dan Williams @ 2014-03-14 20:52 UTC (permalink / raw)
To: tj, JBottomley
Cc: Len Brown, linux-scsi, Phillip Susi, linux-ide, Alan Stern,
Todd Brandt
From: Todd Brandt <todd.e.brandt@linux.intel.com>
Improve overall system resume time by making libata link recovery
actions asynchronous relative to other resume events.
Link resume operations are performed using the scsi_eh thread, so
commands, particularly the sd resume start/stop command, will be held
off until the device exits error handling. Libata already flushes eh
with ata_port_wait_eh() in the port teardown paths, so there are no
concerns with async operation colliding with the end-of-life of the
ata_port object. Also, libata-core is already careful to flush
in-flight pm operations before another round of pm starts on the given
ata_port.
Reference: https://01.org/suspendresume/blogs/tebrandt/2013/hard-disk-resume-optimization-simpler-approach
Cc: Len Brown <len.brown@intel.com>
Cc: Phillip Susi <psusi@ubuntu.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Todd Brandt <todd.e.brandt@linux.intel.com>
[djbw: rebase on cleanup patch, changelog wordsmithing]
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
drivers/ata/libata-core.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c
index 66110ed2c1c0..c37eb02c7136 100644
--- a/drivers/ata/libata-core.c
+++ b/drivers/ata/libata-core.c
@@ -5450,7 +5450,7 @@ static void ata_port_resume_async(struct ata_port *ap, pm_message_t mesg)
static int ata_port_pm_resume(struct device *dev)
{
- ata_port_resume(to_ata_port(dev), PMSG_RESUME);
+ ata_port_resume_async(to_ata_port(dev), PMSG_RESUME);
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v6 3/3] scsi: async sd resume
2014-03-14 20:52 [PATCH v6 0/3] Accelerate Storage Resume (2x or more) Dan Williams
2014-03-14 20:52 ` [PATCH v6 1/3] libata, libsas: kill pm_result and related cleanup Dan Williams
2014-03-14 20:52 ` [PATCH v6 2/3] libata: async resume Dan Williams
@ 2014-03-14 20:52 ` Dan Williams
2 siblings, 0 replies; 7+ messages in thread
From: Dan Williams @ 2014-03-14 20:52 UTC (permalink / raw)
To: tj, JBottomley
Cc: Len Brown, linux-scsi, Phillip Susi, linux-ide, Alan Stern,
Todd Brandt
async_schedule() sd resume work to allow disks and other devices to
resume in parallel.
This moves the entirety of scsi_device resume to an async context to
ensure that scsi_device_resume() remains ordered with respect to the
completion of the start/stop command. For the duration of the resume,
new command submissions (that do not originate from the scsi-core) will
be deferred (BLKPREP_DEFER).
It adds a new ASYNC_DOMAIN_EXCLUSIVE(scsi_sd_pm_domain) as a container
of these operations. Like scsi_sd_probe_domain it is flushed at
sd_remove() time to ensure async ops do not continue past the
end-of-life of the sdev. The implementation explicitly refrains from
reusing scsi_sd_probe_domain directly for this purpose as it is flushed
at the end of dpm_resume(), potentially defeating some of the benefit.
Given sdevs are quiesced it is permissible for these resume operations
to bleed past the async_synchronize_full() calls made by the driver
core.
We defer the resolution of which pm callback to call until
scsi_dev_type_{suspend|resume} time and guarantee that the callback
parameter is never NULL. With this in place the type of resume
operation is encoded in the async function identifier.
There is a concern that async resume could trigger PSU overload. In the
enterprise, storage enclosures enforce staggered spin-up regardless of
what the kernel does making async scanning safe by default. Outside of
that context a user can disable asynchronous scanning via a kernel
command line or CONFIG_SCSI_SCAN_ASYNC. Honor that setting when
deciding whether to do resume asynchronously.
Inspired by Todd's analysis and initial proposal [2]:
https://01.org/suspendresume/blogs/tebrandt/2013/hard-disk-resume-optimization-simpler-approach
Cc: Len Brown <len.brown@intel.com>
Cc: Phillip Susi <psusi@ubuntu.com>
[alan: bug fix and clean up suggestion]
Acked-by: Alan Stern <stern@rowland.harvard.edu>
Suggested-by: Todd Brandt <todd.e.brandt@linux.intel.com>
[djbw: kick all resume work to the async queue]
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
drivers/scsi/Kconfig | 3 +
drivers/scsi/scsi.c | 3 +
drivers/scsi/scsi_pm.c | 128 ++++++++++++++++++++++++++++++++++++----------
drivers/scsi/scsi_priv.h | 2 +
drivers/scsi/scsi_scan.c | 2 -
drivers/scsi/sd.c | 1
6 files changed, 109 insertions(+), 30 deletions(-)
diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig
index c8bd092fc945..02832d64d918 100644
--- a/drivers/scsi/Kconfig
+++ b/drivers/scsi/Kconfig
@@ -263,6 +263,9 @@ config SCSI_SCAN_ASYNC
You can override this choice by specifying "scsi_mod.scan=sync"
or async on the kernel's command line.
+ Note that this setting also affects whether resuming from
+ system suspend will be performed asynchronously.
+
menu "SCSI Transports"
depends on SCSI
diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c
index d8afec8317cf..990d4a302788 100644
--- a/drivers/scsi/scsi.c
+++ b/drivers/scsi/scsi.c
@@ -91,6 +91,9 @@ EXPORT_SYMBOL(scsi_logging_level);
ASYNC_DOMAIN(scsi_sd_probe_domain);
EXPORT_SYMBOL(scsi_sd_probe_domain);
+ASYNC_DOMAIN_EXCLUSIVE(scsi_sd_pm_domain);
+EXPORT_SYMBOL(scsi_sd_pm_domain);
+
/* NB: These are exposed through /proc/scsi/scsi and form part of the ABI.
* You may not alter any existing entry (although adding new ones is
* encouraged once assigned by ANSI/INCITS T10
diff --git a/drivers/scsi/scsi_pm.c b/drivers/scsi/scsi_pm.c
index 001e9ceda4c3..7454498c4091 100644
--- a/drivers/scsi/scsi_pm.c
+++ b/drivers/scsi/scsi_pm.c
@@ -18,35 +18,77 @@
#ifdef CONFIG_PM_SLEEP
-static int scsi_dev_type_suspend(struct device *dev, int (*cb)(struct device *))
+static int do_scsi_suspend(struct device *dev, const struct dev_pm_ops *pm)
{
+ return pm && pm->suspend ? pm->suspend(dev) : 0;
+}
+
+static int do_scsi_freeze(struct device *dev, const struct dev_pm_ops *pm)
+{
+ return pm && pm->freeze ? pm->freeze(dev) : 0;
+}
+
+static int do_scsi_poweroff(struct device *dev, const struct dev_pm_ops *pm)
+{
+ return pm && pm->poweroff ? pm->poweroff(dev) : 0;
+}
+
+static int do_scsi_resume(struct device *dev, const struct dev_pm_ops *pm)
+{
+ return pm && pm->resume ? pm->resume(dev) : 0;
+}
+
+static int do_scsi_thaw(struct device *dev, const struct dev_pm_ops *pm)
+{
+ return pm && pm->thaw ? pm->thaw(dev) : 0;
+}
+
+static int do_scsi_restore(struct device *dev, const struct dev_pm_ops *pm)
+{
+ return pm && pm->restore ? pm->restore(dev) : 0;
+}
+
+static int scsi_dev_type_suspend(struct device *dev,
+ int (*cb)(struct device *, const struct dev_pm_ops *))
+{
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
int err;
+ /* flush pending in-flight resume operations, suspend is synchronous */
+ async_synchronize_full_domain(&scsi_sd_pm_domain);
+
err = scsi_device_quiesce(to_scsi_device(dev));
if (err == 0) {
- if (cb) {
- err = cb(dev);
- if (err)
- scsi_device_resume(to_scsi_device(dev));
- }
+ err = cb(dev, pm);
+ if (err)
+ scsi_device_resume(to_scsi_device(dev));
}
dev_dbg(dev, "scsi suspend: %d\n", err);
return err;
}
-static int scsi_dev_type_resume(struct device *dev, int (*cb)(struct device *))
+static int scsi_dev_type_resume(struct device *dev,
+ int (*cb)(struct device *, const struct dev_pm_ops *))
{
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
int err = 0;
- if (cb)
- err = cb(dev);
+ err = cb(dev, pm);
scsi_device_resume(to_scsi_device(dev));
dev_dbg(dev, "scsi resume: %d\n", err);
+
+ if (err == 0) {
+ pm_runtime_disable(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ }
+
return err;
}
static int
-scsi_bus_suspend_common(struct device *dev, int (*cb)(struct device *))
+scsi_bus_suspend_common(struct device *dev,
+ int (*cb)(struct device *, const struct dev_pm_ops *))
{
int err = 0;
@@ -66,20 +108,54 @@ scsi_bus_suspend_common(struct device *dev, int (*cb)(struct device *))
return err;
}
-static int
-scsi_bus_resume_common(struct device *dev, int (*cb)(struct device *))
+static void async_sdev_resume(void *dev, async_cookie_t cookie)
{
- int err = 0;
+ scsi_dev_type_resume(dev, do_scsi_resume);
+}
- if (scsi_is_sdev_device(dev))
- err = scsi_dev_type_resume(dev, cb);
+static void async_sdev_thaw(void *dev, async_cookie_t cookie)
+{
+ scsi_dev_type_resume(dev, do_scsi_thaw);
+}
- if (err == 0) {
+static void async_sdev_restore(void *dev, async_cookie_t cookie)
+{
+ scsi_dev_type_resume(dev, do_scsi_restore);
+}
+
+static int scsi_bus_resume_common(struct device *dev,
+ int (*cb)(struct device *, const struct dev_pm_ops *))
+{
+ async_func_t fn;
+
+ if (!scsi_is_sdev_device(dev))
+ fn = NULL;
+ else if (cb == do_scsi_resume)
+ fn = async_sdev_resume;
+ else if (cb == do_scsi_thaw)
+ fn = async_sdev_thaw;
+ else if (cb == do_scsi_restore)
+ fn = async_sdev_restore;
+ else
+ fn = NULL;
+
+ if (fn) {
+ async_schedule_domain(fn, dev, &scsi_sd_pm_domain);
+
+ /*
+ * If a user has disabled async probing a likely reason
+ * is due to a storage enclosure that does not inject
+ * staggered spin-ups. For safety, make resume
+ * synchronous as well in that case.
+ */
+ if (strncmp(scsi_scan_type, "async", 5) != 0)
+ async_synchronize_full_domain(&scsi_sd_pm_domain);
+ } else {
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
}
- return err;
+ return 0;
}
static int scsi_bus_prepare(struct device *dev)
@@ -97,38 +173,32 @@ static int scsi_bus_prepare(struct device *dev)
static int scsi_bus_suspend(struct device *dev)
{
- const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
- return scsi_bus_suspend_common(dev, pm ? pm->suspend : NULL);
+ return scsi_bus_suspend_common(dev, do_scsi_suspend);
}
static int scsi_bus_resume(struct device *dev)
{
- const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
- return scsi_bus_resume_common(dev, pm ? pm->resume : NULL);
+ return scsi_bus_resume_common(dev, do_scsi_resume);
}
static int scsi_bus_freeze(struct device *dev)
{
- const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
- return scsi_bus_suspend_common(dev, pm ? pm->freeze : NULL);
+ return scsi_bus_suspend_common(dev, do_scsi_freeze);
}
static int scsi_bus_thaw(struct device *dev)
{
- const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
- return scsi_bus_resume_common(dev, pm ? pm->thaw : NULL);
+ return scsi_bus_resume_common(dev, do_scsi_thaw);
}
static int scsi_bus_poweroff(struct device *dev)
{
- const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
- return scsi_bus_suspend_common(dev, pm ? pm->poweroff : NULL);
+ return scsi_bus_suspend_common(dev, do_scsi_poweroff);
}
static int scsi_bus_restore(struct device *dev)
{
- const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
- return scsi_bus_resume_common(dev, pm ? pm->restore : NULL);
+ return scsi_bus_resume_common(dev, do_scsi_restore);
}
#else /* CONFIG_PM_SLEEP */
diff --git a/drivers/scsi/scsi_priv.h b/drivers/scsi/scsi_priv.h
index f079a598bed4..48e5b657e79f 100644
--- a/drivers/scsi/scsi_priv.h
+++ b/drivers/scsi/scsi_priv.h
@@ -112,6 +112,7 @@ extern void scsi_exit_procfs(void);
#endif /* CONFIG_PROC_FS */
/* scsi_scan.c */
+extern char scsi_scan_type[];
extern int scsi_complete_async_scans(void);
extern int scsi_scan_host_selected(struct Scsi_Host *, unsigned int,
unsigned int, unsigned int, int);
@@ -166,6 +167,7 @@ static inline int scsi_autopm_get_host(struct Scsi_Host *h) { return 0; }
static inline void scsi_autopm_put_host(struct Scsi_Host *h) {}
#endif /* CONFIG_PM_RUNTIME */
+extern struct async_domain scsi_sd_pm_domain;
extern struct async_domain scsi_sd_probe_domain;
/*
diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c
index 307a81137607..6b2f51f52af6 100644
--- a/drivers/scsi/scsi_scan.c
+++ b/drivers/scsi/scsi_scan.c
@@ -97,7 +97,7 @@ MODULE_PARM_DESC(max_luns,
#define SCSI_SCAN_TYPE_DEFAULT "sync"
#endif
-static char scsi_scan_type[6] = SCSI_SCAN_TYPE_DEFAULT;
+char scsi_scan_type[6] = SCSI_SCAN_TYPE_DEFAULT;
module_param_string(scan, scsi_scan_type, sizeof(scsi_scan_type), S_IRUGO);
MODULE_PARM_DESC(scan, "sync, async or none");
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index 470954aba728..700c595c603e 100644
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -3020,6 +3020,7 @@ static int sd_remove(struct device *dev)
devt = disk_devt(sdkp->disk);
scsi_autopm_get_device(sdkp->device);
+ async_synchronize_full_domain(&scsi_sd_pm_domain);
async_synchronize_full_domain(&scsi_sd_probe_domain);
blk_queue_prep_rq(sdkp->device->request_queue, scsi_prep_fn);
blk_queue_unprep_rq(sdkp->device->request_queue, NULL);
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v6 1/3] libata, libsas: kill pm_result and related cleanup
2014-03-14 20:52 ` [PATCH v6 1/3] libata, libsas: kill pm_result and related cleanup Dan Williams
@ 2014-03-18 16:03 ` Tejun Heo
2014-03-18 16:48 ` Dan Williams
2014-03-18 20:18 ` Tejun Heo
1 sibling, 1 reply; 7+ messages in thread
From: Tejun Heo @ 2014-03-18 16:03 UTC (permalink / raw)
To: Dan Williams
Cc: JBottomley, Phillip Susi, Todd Brandt, Alan Stern, linux-scsi,
linux-ide
On Fri, Mar 14, 2014 at 01:52:48PM -0700, Dan Williams wrote:
> Tejun says:
> "At least for libata, worrying about suspend/resume failures don't make
> whole lot of sense. If suspend failed, just proceed with suspend. If
> the device can't be woken up afterwards, that's that. There isn't
> anything we could have done differently anyway. The same for resume, if
> spinup fails, the device is dud and the following commands will invoke
> EH actions and will eventually fail. Again, there really isn't any
> *choice* to make. Just making sure the errors are handled gracefully
> (ie. don't crash) and the following commands are handled correctly
> should be enough."
>
> The only libata user that actually cares about the result from a suspend
> operation is libsas. However, it only cares about whether queuing a new
> operation collides with an in-flight one. All libsas does with the
> error is retry, but we can just let libata wait for the previous
> operation before continuing.
>
> Other cleanups include:
> 1/ Unifying all ata port pm operations on an ata_port_pm_ prefix
> 2/ Marking all ata port pm helper routines as returning void, only
> ata_port_pm_ entry points need to fake a 0 return value.
> 3/ Killing ata_port_{suspend|resume}_common() in favor of calling
> ata_port_request_pm() directly
> 4/ Killing the wrappers that just do a to_ata_port() conversion
> 5/ Clearly marking the entry points that do async operations with an
> _async suffix.
>
> Reference: http://marc.info/?l=linux-scsi&m=138995409532286&w=2
>
> Cc: Phillip Susi <psusi@ubuntu.com>
> Cc: Alan Stern <stern@rowland.harvard.edu>
> Suggested-by: Tejun Heo <tj@kernel.org>
> Signed-off-by: Todd Brandt <todd.e.brandt@intel.com>
> Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Can somebody ack the sas part?
Thanks.
--
tejun
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v6 1/3] libata, libsas: kill pm_result and related cleanup
2014-03-18 16:03 ` Tejun Heo
@ 2014-03-18 16:48 ` Dan Williams
0 siblings, 0 replies; 7+ messages in thread
From: Dan Williams @ 2014-03-18 16:48 UTC (permalink / raw)
To: Tejun Heo
Cc: James Bottomley, Phillip Susi, Todd Brandt, Alan Stern,
linux-scsi, IDE/ATA development list
On Tue, Mar 18, 2014 at 9:03 AM, Tejun Heo <tj@kernel.org> wrote:
> On Fri, Mar 14, 2014 at 01:52:48PM -0700, Dan Williams wrote:
>> Tejun says:
>> "At least for libata, worrying about suspend/resume failures don't make
>> whole lot of sense. If suspend failed, just proceed with suspend. If
>> the device can't be woken up afterwards, that's that. There isn't
>> anything we could have done differently anyway. The same for resume, if
>> spinup fails, the device is dud and the following commands will invoke
>> EH actions and will eventually fail. Again, there really isn't any
>> *choice* to make. Just making sure the errors are handled gracefully
>> (ie. don't crash) and the following commands are handled correctly
>> should be enough."
>>
>> The only libata user that actually cares about the result from a suspend
>> operation is libsas. However, it only cares about whether queuing a new
>> operation collides with an in-flight one. All libsas does with the
>> error is retry, but we can just let libata wait for the previous
>> operation before continuing.
>>
>> Other cleanups include:
>> 1/ Unifying all ata port pm operations on an ata_port_pm_ prefix
>> 2/ Marking all ata port pm helper routines as returning void, only
>> ata_port_pm_ entry points need to fake a 0 return value.
>> 3/ Killing ata_port_{suspend|resume}_common() in favor of calling
>> ata_port_request_pm() directly
>> 4/ Killing the wrappers that just do a to_ata_port() conversion
>> 5/ Clearly marking the entry points that do async operations with an
>> _async suffix.
>>
>> Reference: http://marc.info/?l=linux-scsi&m=138995409532286&w=2
>>
>> Cc: Phillip Susi <psusi@ubuntu.com>
>> Cc: Alan Stern <stern@rowland.harvard.edu>
>> Suggested-by: Tejun Heo <tj@kernel.org>
>> Signed-off-by: Todd Brandt <todd.e.brandt@intel.com>
>> Signed-off-by: Dan Williams <dan.j.williams@intel.com>
>
> Can somebody ack the sas part?
>
Hmm I'd just as soon self-ack at this point. I'm cleaning up code I
wrote a couple years ago, and libsas bugs at this point are
self-inflicted.
Top-5 in drivers/scsi/libsas by changeset in the past 5 years:
75 Dan Williams
12 James Bottomley
9 Linus Torvalds
7 Tejun Heo
4 Sergei Shtylyov
--
Dan
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v6 1/3] libata, libsas: kill pm_result and related cleanup
2014-03-14 20:52 ` [PATCH v6 1/3] libata, libsas: kill pm_result and related cleanup Dan Williams
2014-03-18 16:03 ` Tejun Heo
@ 2014-03-18 20:18 ` Tejun Heo
1 sibling, 0 replies; 7+ messages in thread
From: Tejun Heo @ 2014-03-18 20:18 UTC (permalink / raw)
To: Dan Williams
Cc: JBottomley, Phillip Susi, Todd Brandt, Alan Stern, linux-scsi,
linux-ide
On Fri, Mar 14, 2014 at 01:52:48PM -0700, Dan Williams wrote:
> Tejun says:
> "At least for libata, worrying about suspend/resume failures don't make
> whole lot of sense. If suspend failed, just proceed with suspend. If
> the device can't be woken up afterwards, that's that. There isn't
> anything we could have done differently anyway. The same for resume, if
> spinup fails, the device is dud and the following commands will invoke
> EH actions and will eventually fail. Again, there really isn't any
> *choice* to make. Just making sure the errors are handled gracefully
> (ie. don't crash) and the following commands are handled correctly
> should be enough."
>
> The only libata user that actually cares about the result from a suspend
> operation is libsas. However, it only cares about whether queuing a new
> operation collides with an in-flight one. All libsas does with the
> error is retry, but we can just let libata wait for the previous
> operation before continuing.
>
> Other cleanups include:
> 1/ Unifying all ata port pm operations on an ata_port_pm_ prefix
> 2/ Marking all ata port pm helper routines as returning void, only
> ata_port_pm_ entry points need to fake a 0 return value.
> 3/ Killing ata_port_{suspend|resume}_common() in favor of calling
> ata_port_request_pm() directly
> 4/ Killing the wrappers that just do a to_ata_port() conversion
> 5/ Clearly marking the entry points that do async operations with an
> _async suffix.
>
> Reference: http://marc.info/?l=linux-scsi&m=138995409532286&w=2
>
> Cc: Phillip Susi <psusi@ubuntu.com>
> Cc: Alan Stern <stern@rowland.harvard.edu>
> Suggested-by: Tejun Heo <tj@kernel.org>
> Signed-off-by: Todd Brandt <todd.e.brandt@intel.com>
> Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Applied 1-2 to libata/for-3.15.
Thanks.
--
tejun
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2014-03-18 20:18 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2014-03-14 20:52 [PATCH v6 0/3] Accelerate Storage Resume (2x or more) Dan Williams
2014-03-14 20:52 ` [PATCH v6 1/3] libata, libsas: kill pm_result and related cleanup Dan Williams
2014-03-18 16:03 ` Tejun Heo
2014-03-18 16:48 ` Dan Williams
2014-03-18 20:18 ` Tejun Heo
2014-03-14 20:52 ` [PATCH v6 2/3] libata: async resume Dan Williams
2014-03-14 20:52 ` [PATCH v6 3/3] scsi: async sd resume Dan Williams
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox