diff options
author | Dan Williams <dan.j.williams@intel.com> | 2014-04-10 18:30:35 -0400 |
---|---|---|
committer | Dan Williams <dan.j.williams@intel.com> | 2014-04-10 18:30:35 -0400 |
commit | 3c31b52f96f7b559d950b16113c0f68c72a1985e (patch) | |
tree | 2ae13004870a1fed2e6f9fee49111fa947cb5d06 | |
parent | 455c6fdbd219161bd09b1165f11699d6d73de11c (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/Kconfig | 3 | ||||
-rw-r--r-- | drivers/scsi/scsi.c | 9 | ||||
-rw-r--r-- | drivers/scsi/scsi_pm.c | 128 | ||||
-rw-r--r-- | drivers/scsi/scsi_priv.h | 2 | ||||
-rw-r--r-- | drivers/scsi/scsi_scan.c | 2 | ||||
-rw-r--r-- | drivers/scsi/sd.c | 1 |
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 | |||
266 | menu "SCSI Transports" | 269 | menu "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); | |||
91 | ASYNC_DOMAIN(scsi_sd_probe_domain); | 91 | ASYNC_DOMAIN(scsi_sd_probe_domain); |
92 | EXPORT_SYMBOL(scsi_sd_probe_domain); | 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 | /* 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 | ||
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 | 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 | ||
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 | 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 | ||
48 | static int | 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 | 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 | ||
69 | static int | 111 | static void async_sdev_resume(void *dev, async_cookie_t cookie) |
70 | scsi_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)) | 116 | static 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) { | 121 | static void async_sdev_restore(void *dev, async_cookie_t cookie) |
122 | { | ||
123 | scsi_dev_type_resume(dev, do_scsi_restore); | ||
124 | } | ||
125 | |||
126 | static 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 | ||
85 | static int scsi_bus_prepare(struct device *dev) | 161 | static int scsi_bus_prepare(struct device *dev) |
@@ -97,38 +173,32 @@ static int scsi_bus_prepare(struct device *dev) | |||
97 | 173 | ||
98 | static int scsi_bus_suspend(struct device *dev) | 174 | static 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 | ||
104 | static int scsi_bus_resume(struct device *dev) | 179 | static 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 | ||
110 | static int scsi_bus_freeze(struct device *dev) | 184 | static 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 | ||
116 | static int scsi_bus_thaw(struct device *dev) | 189 | static 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 | ||
122 | static int scsi_bus_poweroff(struct device *dev) | 194 | static 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 | ||
128 | static int scsi_bus_restore(struct device *dev) | 199 | static 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 */ |
115 | extern char scsi_scan_type[]; | ||
115 | extern int scsi_complete_async_scans(void); | 116 | extern int scsi_complete_async_scans(void); |
116 | extern int scsi_scan_host_selected(struct Scsi_Host *, unsigned int, | 117 | extern 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; } | |||
166 | static inline void scsi_autopm_put_host(struct Scsi_Host *h) {} | 167 | static inline void scsi_autopm_put_host(struct Scsi_Host *h) {} |
167 | #endif /* CONFIG_PM_RUNTIME */ | 168 | #endif /* CONFIG_PM_RUNTIME */ |
168 | 169 | ||
170 | extern struct async_domain scsi_sd_pm_domain; | ||
169 | extern struct async_domain scsi_sd_probe_domain; | 171 | extern 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 | ||
100 | static char scsi_scan_type[6] = SCSI_SCAN_TYPE_DEFAULT; | 100 | char scsi_scan_type[6] = SCSI_SCAN_TYPE_DEFAULT; |
101 | 101 | ||
102 | module_param_string(scan, scsi_scan_type, sizeof(scsi_scan_type), S_IRUGO); | 102 | module_param_string(scan, scsi_scan_type, sizeof(scsi_scan_type), S_IRUGO); |
103 | MODULE_PARM_DESC(scan, "sync, async or none"); | 103 | 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) | |||
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); |