Accelerate Storage Resume (2x or more)
Subsystems (3)
v6 cover Accelerate Storage Resume (2x or more)
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(-)
PATCH v6 1/3 libata, libsas: kill pm_result and related cleanup
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
--- 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(-)
drivers/ata/libata-core.c +60 -75
@@ -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
drivers/ata/libata-eh.c +2 -11
@@ -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 */
drivers/scsi/libsas/sas_ata.c +5 -30
@@ -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__); }/**
include/linux/libata.h +4 -7
@@ -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 );
include/scsi/libsas.h +0 -1
@@ -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;
Re: [PATCH v6 1/3] libata, libsas: kill pm_result and related cleanup
Quoted 27 lines · Dan Williams
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 anata_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 ato_ata_port()conversion 5/ Clearly marking the entry points that do async operations with an
_asyncsuffix.
Quoted 7 lines
Reference: http://marc.info/?l=linux-scsi&m=138995409532286&w=2
Cc Phillip Susi <[email protected]>Cc Alan Stern <[email protected]>Suggested-by Tejun Heo <[email protected]>Signed-off-by Todd Brandt <[email protected]>Signed-off-by Dan Williams <[email protected]>
Can somebody ack the sas part?
Thanks.
Signature · 1 line
tejun
Re: [PATCH v6 1/3] libata, libsas: kill pm_result and related cleanup
Quoted 30 lines · Tejun Heo
Quoted 29 lines · Dan Williams
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 anata_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 ato_ata_port()conversion 5/ Clearly marking the entry points that do async operations with an
_asyncsuffix.Reference: http://marc.info/?l=linux-scsi&m=138995409532286&w=2
Quoted 8 lines
Quoted 5 lines
Cc Phillip Susi <[email protected]>Cc Alan Stern <[email protected]>Suggested-by Tejun Heo <[email protected]>Signed-off-by Todd Brandt <[email protected]>Signed-off-by Dan Williams <[email protected]>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
Re: [PATCH v6 1/3] libata, libsas: kill pm_result and related cleanup
In reply to Dan WilliamsQuoted 27 lines · Dan Williams
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 anata_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 ato_ata_port()conversion 5/ Clearly marking the entry points that do async operations with an
_asyncsuffix.
Quoted 7 lines
Reference: http://marc.info/?l=linux-scsi&m=138995409532286&w=2
Cc Phillip Susi <[email protected]>Cc Alan Stern <[email protected]>Suggested-by Tejun Heo <[email protected]>Signed-off-by Todd Brandt <[email protected]>Signed-off-by Dan Williams <[email protected]>
Applied 1-2 to libata/for-3.15.
Thanks.
Signature · 1 line
tejun
PATCH v6 2/3 libata: async resume
In reply to Dan WilliamsFrom: Todd Brandt <[email protected]>
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
--- drivers/ata/libata-core.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-)
drivers/ata/libata-core.c +1 -1
@@ -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);
PATCH v6 3/3 scsi: async sd resume
In reply to Dan Williamsasync_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
--- 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(-)
drivers/scsi/Kconfig +3 -0
@@ -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
drivers/scsi/scsi.c +3 -0
@@ -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
drivers/scsi/scsi_pm.c +99 -29
192 lines not on this page · raw message
drivers/scsi/scsi_priv.h +2 -0
@@ -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;/*
drivers/scsi/scsi_scan.c +1 -1
@@ -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" );
drivers/scsi/sd.c +1 -0
@@ -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 );