aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Williams <dan.j.williams@intel.com>2014-04-10 18:30:35 -0400
committerDan Williams <dan.j.williams@intel.com>2014-04-10 18:30:35 -0400
commit3c31b52f96f7b559d950b16113c0f68c72a1985e (patch)
tree2ae13004870a1fed2e6f9fee49111fa947cb5d06
parent455c6fdbd219161bd09b1165f11699d6d73de11c (diff)
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>
-rw-r--r--drivers/scsi/Kconfig3
-rw-r--r--drivers/scsi/scsi.c9
-rw-r--r--drivers/scsi/scsi_pm.c128
-rw-r--r--drivers/scsi/scsi_priv.h2
-rw-r--r--drivers/scsi/scsi_scan.c2
-rw-r--r--drivers/scsi/sd.c1
6 files changed, 115 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
263 You can override this choice by specifying "scsi_mod.scan=sync" 263 You can override this choice by specifying "scsi_mod.scan=sync"
264 or async on the kernel's command line. 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
266menu "SCSI Transports" 269menu "SCSI Transports"
267 depends on SCSI 270 depends on SCSI
268 271
diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c
index d8afec8317cf..1b345bf41a91 100644
--- a/drivers/scsi/scsi.c
+++ b/drivers/scsi/scsi.c
@@ -91,6 +91,15 @@ EXPORT_SYMBOL(scsi_logging_level);
91ASYNC_DOMAIN(scsi_sd_probe_domain); 91ASYNC_DOMAIN(scsi_sd_probe_domain);
92EXPORT_SYMBOL(scsi_sd_probe_domain); 92EXPORT_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 */
100ASYNC_DOMAIN_EXCLUSIVE(scsi_sd_pm_domain);
101EXPORT_SYMBOL(scsi_sd_pm_domain);
102
94/* NB: These are exposed through /proc/scsi/scsi and form part of the ABI. 103/* NB: These are exposed through /proc/scsi/scsi and form part of the ABI.
95 * You may not alter any existing entry (although adding new ones is 104 * You may not alter any existing entry (although adding new ones is
96 * encouraged once assigned by ANSI/INCITS T10 105 * 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 @@
18 18
19#ifdef CONFIG_PM_SLEEP 19#ifdef CONFIG_PM_SLEEP
20 20
21static int scsi_dev_type_suspend(struct device *dev, int (*cb)(struct device *)) 21static 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
26static 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
31static 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
36static 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
41static 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
46static 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
51static 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 int err; 55 int err;
24 56
57 /* flush pending in-flight resume operations, suspend is synchronous */
58 async_synchronize_full_domain(&scsi_sd_pm_domain);
59
25 err = scsi_device_quiesce(to_scsi_device(dev)); 60 err = scsi_device_quiesce(to_scsi_device(dev));
26 if (err == 0) { 61 if (err == 0) {
27 if (cb) { 62 err = cb(dev, pm);
28 err = cb(dev); 63 if (err)
29 if (err) 64 scsi_device_resume(to_scsi_device(dev));
30 scsi_device_resume(to_scsi_device(dev));
31 }
32 } 65 }
33 dev_dbg(dev, "scsi suspend: %d\n", err); 66 dev_dbg(dev, "scsi suspend: %d\n", err);
34 return err; 67 return err;
35} 68}
36 69
37static int scsi_dev_type_resume(struct device *dev, int (*cb)(struct device *)) 70static 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 int err = 0; 74 int err = 0;
40 75
41 if (cb) 76 err = cb(dev, pm);
42 err = cb(dev);
43 scsi_device_resume(to_scsi_device(dev)); 77 scsi_device_resume(to_scsi_device(dev));
44 dev_dbg(dev, "scsi resume: %d\n", err); 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 return err; 86 return err;
46} 87}
47 88
48static int 89static int
49scsi_bus_suspend_common(struct device *dev, int (*cb)(struct device *)) 90scsi_bus_suspend_common(struct device *dev,
91 int (*cb)(struct device *, const struct dev_pm_ops *))
50{ 92{
51 int err = 0; 93 int err = 0;
52 94
@@ -66,20 +108,54 @@ scsi_bus_suspend_common(struct device *dev, int (*cb)(struct device *))
66 return err; 108 return err;
67} 109}
68 110
69static int 111static void async_sdev_resume(void *dev, async_cookie_t cookie)
70scsi_bus_resume_common(struct device *dev, int (*cb)(struct device *))
71{ 112{
72 int err = 0; 113 scsi_dev_type_resume(dev, do_scsi_resume);
114}
73 115
74 if (scsi_is_sdev_device(dev)) 116static void async_sdev_thaw(void *dev, async_cookie_t cookie)
75 err = scsi_dev_type_resume(dev, cb); 117{
118 scsi_dev_type_resume(dev, do_scsi_thaw);
119}
76 120
77 if (err == 0) { 121static void async_sdev_restore(void *dev, async_cookie_t cookie)
122{
123 scsi_dev_type_resume(dev, do_scsi_restore);
124}
125
126static int scsi_bus_resume_common(struct device *dev,
127 int (*cb)(struct device *, const struct dev_pm_ops *))
128{
129 async_func_t fn;
130
131 if (!scsi_is_sdev_device(dev))
132 fn = NULL;
133 else if (cb == do_scsi_resume)
134 fn = async_sdev_resume;
135 else if (cb == do_scsi_thaw)
136 fn = async_sdev_thaw;
137 else if (cb == do_scsi_restore)
138 fn = async_sdev_restore;
139 else
140 fn = NULL;
141
142 if (fn) {
143 async_schedule_domain(fn, dev, &scsi_sd_pm_domain);
144
145 /*
146 * If a user has disabled async probing a likely reason
147 * is due to a storage enclosure that does not inject
148 * staggered spin-ups. For safety, make resume
149 * synchronous as well in that case.
150 */
151 if (strncmp(scsi_scan_type, "async", 5) != 0)
152 async_synchronize_full_domain(&scsi_sd_pm_domain);
153 } else {
78 pm_runtime_disable(dev); 154 pm_runtime_disable(dev);
79 pm_runtime_set_active(dev); 155 pm_runtime_set_active(dev);
80 pm_runtime_enable(dev); 156 pm_runtime_enable(dev);
81 } 157 }
82 return err; 158 return 0;
83} 159}
84 160
85static int scsi_bus_prepare(struct device *dev) 161static int scsi_bus_prepare(struct device *dev)
@@ -97,38 +173,32 @@ static int scsi_bus_prepare(struct device *dev)
97 173
98static int scsi_bus_suspend(struct device *dev) 174static int scsi_bus_suspend(struct device *dev)
99{ 175{
100 const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; 176 return scsi_bus_suspend_common(dev, do_scsi_suspend);
101 return scsi_bus_suspend_common(dev, pm ? pm->suspend : NULL);
102} 177}
103 178
104static int scsi_bus_resume(struct device *dev) 179static int scsi_bus_resume(struct device *dev)
105{ 180{
106 const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; 181 return scsi_bus_resume_common(dev, do_scsi_resume);
107 return scsi_bus_resume_common(dev, pm ? pm->resume : NULL);
108} 182}
109 183
110static int scsi_bus_freeze(struct device *dev) 184static int scsi_bus_freeze(struct device *dev)
111{ 185{
112 const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; 186 return scsi_bus_suspend_common(dev, do_scsi_freeze);
113 return scsi_bus_suspend_common(dev, pm ? pm->freeze : NULL);
114} 187}
115 188
116static int scsi_bus_thaw(struct device *dev) 189static int scsi_bus_thaw(struct device *dev)
117{ 190{
118 const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; 191 return scsi_bus_resume_common(dev, do_scsi_thaw);
119 return scsi_bus_resume_common(dev, pm ? pm->thaw : NULL);
120} 192}
121 193
122static int scsi_bus_poweroff(struct device *dev) 194static int scsi_bus_poweroff(struct device *dev)
123{ 195{
124 const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; 196 return scsi_bus_suspend_common(dev, do_scsi_poweroff);
125 return scsi_bus_suspend_common(dev, pm ? pm->poweroff : NULL);
126} 197}
127 198
128static int scsi_bus_restore(struct device *dev) 199static int scsi_bus_restore(struct device *dev)
129{ 200{
130 const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; 201 return scsi_bus_resume_common(dev, do_scsi_restore);
131 return scsi_bus_resume_common(dev, pm ? pm->restore : NULL);
132} 202}
133 203
134#else /* CONFIG_PM_SLEEP */ 204#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);
112#endif /* CONFIG_PROC_FS */ 112#endif /* CONFIG_PROC_FS */
113 113
114/* scsi_scan.c */ 114/* scsi_scan.c */
115extern char scsi_scan_type[];
115extern int scsi_complete_async_scans(void); 116extern int scsi_complete_async_scans(void);
116extern int scsi_scan_host_selected(struct Scsi_Host *, unsigned int, 117extern int scsi_scan_host_selected(struct Scsi_Host *, unsigned int,
117 unsigned int, unsigned int, int); 118 unsigned int, unsigned int, int);
@@ -166,6 +167,7 @@ static inline int scsi_autopm_get_host(struct Scsi_Host *h) { return 0; }
166static inline void scsi_autopm_put_host(struct Scsi_Host *h) {} 167static inline void scsi_autopm_put_host(struct Scsi_Host *h) {}
167#endif /* CONFIG_PM_RUNTIME */ 168#endif /* CONFIG_PM_RUNTIME */
168 169
170extern struct async_domain scsi_sd_pm_domain;
169extern struct async_domain scsi_sd_probe_domain; 171extern struct async_domain scsi_sd_probe_domain;
170 172
171/* 173/*
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,
97#define SCSI_SCAN_TYPE_DEFAULT "sync" 97#define SCSI_SCAN_TYPE_DEFAULT "sync"
98#endif 98#endif
99 99
100static char scsi_scan_type[6] = SCSI_SCAN_TYPE_DEFAULT; 100char scsi_scan_type[6] = SCSI_SCAN_TYPE_DEFAULT;
101 101
102module_param_string(scan, scsi_scan_type, sizeof(scsi_scan_type), S_IRUGO); 102module_param_string(scan, scsi_scan_type, sizeof(scsi_scan_type), S_IRUGO);
103MODULE_PARM_DESC(scan, "sync, async or none"); 103MODULE_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)
3020 devt = disk_devt(sdkp->disk); 3020 devt = disk_devt(sdkp->disk);
3021 scsi_autopm_get_device(sdkp->device); 3021 scsi_autopm_get_device(sdkp->device);
3022 3022
3023 async_synchronize_full_domain(&scsi_sd_pm_domain);
3023 async_synchronize_full_domain(&scsi_sd_probe_domain); 3024 async_synchronize_full_domain(&scsi_sd_probe_domain);
3024 blk_queue_prep_rq(sdkp->device->request_queue, scsi_prep_fn); 3025 blk_queue_prep_rq(sdkp->device->request_queue, scsi_prep_fn);
3025 blk_queue_unprep_rq(sdkp->device->request_queue, NULL); 3026 blk_queue_unprep_rq(sdkp->device->request_queue, NULL);