Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

scsi: async sd resume

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>

+115 -30
+3
drivers/scsi/Kconfig
··· 263 263 You can override this choice by specifying "scsi_mod.scan=sync" 264 264 or async on the kernel's command line. 265 265 266 + Note that this setting also affects whether resuming from 267 + system suspend will be performed asynchronously. 268 + 266 269 menu "SCSI Transports" 267 270 depends on SCSI 268 271
+9
drivers/scsi/scsi.c
··· 91 91 ASYNC_DOMAIN(scsi_sd_probe_domain); 92 92 EXPORT_SYMBOL(scsi_sd_probe_domain); 93 93 94 + /* 95 + * Separate domain (from scsi_sd_probe_domain) to maximize the benefit of 96 + * asynchronous system resume operations. It is marked 'exclusive' to avoid 97 + * being included in the async_synchronize_full() that is invoked by 98 + * dpm_resume() 99 + */ 100 + ASYNC_DOMAIN_EXCLUSIVE(scsi_sd_pm_domain); 101 + EXPORT_SYMBOL(scsi_sd_pm_domain); 102 + 94 103 /* NB: These are exposed through /proc/scsi/scsi and form part of the ABI. 95 104 * You may not alter any existing entry (although adding new ones is 96 105 * encouraged once assigned by ANSI/INCITS T10
+99 -29
drivers/scsi/scsi_pm.c
··· 18 18 19 19 #ifdef CONFIG_PM_SLEEP 20 20 21 - static int scsi_dev_type_suspend(struct device *dev, int (*cb)(struct device *)) 21 + static int do_scsi_suspend(struct device *dev, const struct dev_pm_ops *pm) 22 22 { 23 + return pm && pm->suspend ? pm->suspend(dev) : 0; 24 + } 25 + 26 + static int do_scsi_freeze(struct device *dev, const struct dev_pm_ops *pm) 27 + { 28 + return pm && pm->freeze ? pm->freeze(dev) : 0; 29 + } 30 + 31 + static int do_scsi_poweroff(struct device *dev, const struct dev_pm_ops *pm) 32 + { 33 + return pm && pm->poweroff ? pm->poweroff(dev) : 0; 34 + } 35 + 36 + static int do_scsi_resume(struct device *dev, const struct dev_pm_ops *pm) 37 + { 38 + return pm && pm->resume ? pm->resume(dev) : 0; 39 + } 40 + 41 + static int do_scsi_thaw(struct device *dev, const struct dev_pm_ops *pm) 42 + { 43 + return pm && pm->thaw ? pm->thaw(dev) : 0; 44 + } 45 + 46 + static int do_scsi_restore(struct device *dev, const struct dev_pm_ops *pm) 47 + { 48 + return pm && pm->restore ? pm->restore(dev) : 0; 49 + } 50 + 51 + static int scsi_dev_type_suspend(struct device *dev, 52 + int (*cb)(struct device *, const struct dev_pm_ops *)) 53 + { 54 + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; 23 55 int err; 56 + 57 + /* flush pending in-flight resume operations, suspend is synchronous */ 58 + async_synchronize_full_domain(&scsi_sd_pm_domain); 24 59 25 60 err = scsi_device_quiesce(to_scsi_device(dev)); 26 61 if (err == 0) { 27 - if (cb) { 28 - err = cb(dev); 29 - if (err) 30 - scsi_device_resume(to_scsi_device(dev)); 31 - } 62 + err = cb(dev, pm); 63 + if (err) 64 + scsi_device_resume(to_scsi_device(dev)); 32 65 } 33 66 dev_dbg(dev, "scsi suspend: %d\n", err); 34 67 return err; 35 68 } 36 69 37 - static int scsi_dev_type_resume(struct device *dev, int (*cb)(struct device *)) 70 + static int scsi_dev_type_resume(struct device *dev, 71 + int (*cb)(struct device *, const struct dev_pm_ops *)) 38 72 { 73 + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; 39 74 int err = 0; 40 75 41 - if (cb) 42 - err = cb(dev); 76 + err = cb(dev, pm); 43 77 scsi_device_resume(to_scsi_device(dev)); 44 78 dev_dbg(dev, "scsi resume: %d\n", err); 79 + 80 + if (err == 0) { 81 + pm_runtime_disable(dev); 82 + pm_runtime_set_active(dev); 83 + pm_runtime_enable(dev); 84 + } 85 + 45 86 return err; 46 87 } 47 88 48 89 static int 49 - scsi_bus_suspend_common(struct device *dev, int (*cb)(struct device *)) 90 + scsi_bus_suspend_common(struct device *dev, 91 + int (*cb)(struct device *, const struct dev_pm_ops *)) 50 92 { 51 93 int err = 0; 52 94 ··· 108 66 return err; 109 67 } 110 68 111 - static int 112 - scsi_bus_resume_common(struct device *dev, int (*cb)(struct device *)) 69 + static void async_sdev_resume(void *dev, async_cookie_t cookie) 113 70 { 114 - int err = 0; 71 + scsi_dev_type_resume(dev, do_scsi_resume); 72 + } 115 73 116 - if (scsi_is_sdev_device(dev)) 117 - err = scsi_dev_type_resume(dev, cb); 74 + static void async_sdev_thaw(void *dev, async_cookie_t cookie) 75 + { 76 + scsi_dev_type_resume(dev, do_scsi_thaw); 77 + } 118 78 119 - if (err == 0) { 79 + static void async_sdev_restore(void *dev, async_cookie_t cookie) 80 + { 81 + scsi_dev_type_resume(dev, do_scsi_restore); 82 + } 83 + 84 + static int scsi_bus_resume_common(struct device *dev, 85 + int (*cb)(struct device *, const struct dev_pm_ops *)) 86 + { 87 + async_func_t fn; 88 + 89 + if (!scsi_is_sdev_device(dev)) 90 + fn = NULL; 91 + else if (cb == do_scsi_resume) 92 + fn = async_sdev_resume; 93 + else if (cb == do_scsi_thaw) 94 + fn = async_sdev_thaw; 95 + else if (cb == do_scsi_restore) 96 + fn = async_sdev_restore; 97 + else 98 + fn = NULL; 99 + 100 + if (fn) { 101 + async_schedule_domain(fn, dev, &scsi_sd_pm_domain); 102 + 103 + /* 104 + * If a user has disabled async probing a likely reason 105 + * is due to a storage enclosure that does not inject 106 + * staggered spin-ups. For safety, make resume 107 + * synchronous as well in that case. 108 + */ 109 + if (strncmp(scsi_scan_type, "async", 5) != 0) 110 + async_synchronize_full_domain(&scsi_sd_pm_domain); 111 + } else { 120 112 pm_runtime_disable(dev); 121 113 pm_runtime_set_active(dev); 122 114 pm_runtime_enable(dev); 123 115 } 124 - return err; 116 + return 0; 125 117 } 126 118 127 119 static int scsi_bus_prepare(struct device *dev) ··· 173 97 174 98 static int scsi_bus_suspend(struct device *dev) 175 99 { 176 - const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; 177 - return scsi_bus_suspend_common(dev, pm ? pm->suspend : NULL); 100 + return scsi_bus_suspend_common(dev, do_scsi_suspend); 178 101 } 179 102 180 103 static int scsi_bus_resume(struct device *dev) 181 104 { 182 - const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; 183 - return scsi_bus_resume_common(dev, pm ? pm->resume : NULL); 105 + return scsi_bus_resume_common(dev, do_scsi_resume); 184 106 } 185 107 186 108 static int scsi_bus_freeze(struct device *dev) 187 109 { 188 - const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; 189 - return scsi_bus_suspend_common(dev, pm ? pm->freeze : NULL); 110 + return scsi_bus_suspend_common(dev, do_scsi_freeze); 190 111 } 191 112 192 113 static int scsi_bus_thaw(struct device *dev) 193 114 { 194 - const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; 195 - return scsi_bus_resume_common(dev, pm ? pm->thaw : NULL); 115 + return scsi_bus_resume_common(dev, do_scsi_thaw); 196 116 } 197 117 198 118 static int scsi_bus_poweroff(struct device *dev) 199 119 { 200 - const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; 201 - return scsi_bus_suspend_common(dev, pm ? pm->poweroff : NULL); 120 + return scsi_bus_suspend_common(dev, do_scsi_poweroff); 202 121 } 203 122 204 123 static int scsi_bus_restore(struct device *dev) 205 124 { 206 - const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; 207 - return scsi_bus_resume_common(dev, pm ? pm->restore : NULL); 125 + return scsi_bus_resume_common(dev, do_scsi_restore); 208 126 } 209 127 210 128 #else /* CONFIG_PM_SLEEP */
+2
drivers/scsi/scsi_priv.h
··· 112 112 #endif /* CONFIG_PROC_FS */ 113 113 114 114 /* scsi_scan.c */ 115 + extern char scsi_scan_type[]; 115 116 extern int scsi_complete_async_scans(void); 116 117 extern int scsi_scan_host_selected(struct Scsi_Host *, unsigned int, 117 118 unsigned int, unsigned int, int); ··· 167 166 static inline void scsi_autopm_put_host(struct Scsi_Host *h) {} 168 167 #endif /* CONFIG_PM_RUNTIME */ 169 168 169 + extern struct async_domain scsi_sd_pm_domain; 170 170 extern struct async_domain scsi_sd_probe_domain; 171 171 172 172 /*
+1 -1
drivers/scsi/scsi_scan.c
··· 97 97 #define SCSI_SCAN_TYPE_DEFAULT "sync" 98 98 #endif 99 99 100 - static char scsi_scan_type[6] = SCSI_SCAN_TYPE_DEFAULT; 100 + char scsi_scan_type[6] = SCSI_SCAN_TYPE_DEFAULT; 101 101 102 102 module_param_string(scan, scsi_scan_type, sizeof(scsi_scan_type), S_IRUGO); 103 103 MODULE_PARM_DESC(scan, "sync, async or none");
+1
drivers/scsi/sd.c
··· 3020 3020 devt = disk_devt(sdkp->disk); 3021 3021 scsi_autopm_get_device(sdkp->device); 3022 3022 3023 + async_synchronize_full_domain(&scsi_sd_pm_domain); 3023 3024 async_synchronize_full_domain(&scsi_sd_probe_domain); 3024 3025 blk_queue_prep_rq(sdkp->device->request_queue, scsi_prep_fn); 3025 3026 blk_queue_unprep_rq(sdkp->device->request_queue, NULL);