Skip to content

Commit bc4f240

Browse files
AlanSternJames Bottomley
authored andcommitted
[SCSI] implement runtime Power Management
This patch (as1398b) adds runtime PM support to the SCSI layer. Only the machanism is provided; use of it is up to the various high-level drivers, and the patch doesn't change any of them. Except for sg -- the patch expicitly prevents a device from being runtime-suspended while its sg device file is open. The implementation is simplistic. In general, hosts and targets are automatically suspended when all their children are asleep, but for them the runtime-suspend code doesn't actually do anything. (A host's runtime PM status is propagated up the device tree, though, so a runtime-PM-aware lower-level driver could power down the host adapter hardware at the appropriate times.) There are comments indicating where a transport class might be notified or some other hooks added. LUNs are runtime-suspended by calling the drivers' existing suspend handlers (and likewise for runtime-resume). Somewhat arbitrarily, the implementation delays for 100 ms before suspending an eligible LUN. This is because there typically are occasions during bootup when the same device file is opened and closed several times in quick succession. The way this all works is that the SCSI core increments a device's PM-usage count when it is registered. If a high-level driver does nothing then the device will not be eligible for runtime-suspend because of the elevated usage count. If a high-level driver wants to use runtime PM then it can call scsi_autopm_put_device() in its probe routine to decrement the usage count and scsi_autopm_get_device() in its remove routine to restore the original count. Hosts, targets, and LUNs are not suspended while they are being probed or removed, or while the error handler is running. In fact, a fairly large part of the patch consists of code to make sure that things aren't suspended at such times. [jejb: fix up compile issues in PM config variations] Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: James Bottomley <James.Bottomley@suse.de>
1 parent db5bd1e commit bc4f240

File tree

8 files changed

+201
-11
lines changed

8 files changed

+201
-11
lines changed

drivers/scsi/hosts.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include <linux/completion.h>
3333
#include <linux/transport_class.h>
3434
#include <linux/platform_device.h>
35+
#include <linux/pm_runtime.h>
3536

3637
#include <scsi/scsi_device.h>
3738
#include <scsi/scsi_host.h>
@@ -156,6 +157,7 @@ EXPORT_SYMBOL(scsi_host_set_state);
156157
void scsi_remove_host(struct Scsi_Host *shost)
157158
{
158159
unsigned long flags;
160+
159161
mutex_lock(&shost->scan_mutex);
160162
spin_lock_irqsave(shost->host_lock, flags);
161163
if (scsi_host_set_state(shost, SHOST_CANCEL))
@@ -165,6 +167,8 @@ void scsi_remove_host(struct Scsi_Host *shost)
165167
return;
166168
}
167169
spin_unlock_irqrestore(shost->host_lock, flags);
170+
171+
scsi_autopm_get_host(shost);
168172
scsi_forget_host(shost);
169173
mutex_unlock(&shost->scan_mutex);
170174
scsi_proc_host_rm(shost);
@@ -216,12 +220,14 @@ int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev,
216220
shost->shost_gendev.parent = dev ? dev : &platform_bus;
217221
shost->dma_dev = dma_dev;
218222

219-
device_enable_async_suspend(&shost->shost_gendev);
220-
221223
error = device_add(&shost->shost_gendev);
222224
if (error)
223225
goto out;
224226

227+
pm_runtime_set_active(&shost->shost_gendev);
228+
pm_runtime_enable(&shost->shost_gendev);
229+
device_enable_async_suspend(&shost->shost_gendev);
230+
225231
scsi_host_set_state(shost, SHOST_RUNNING);
226232
get_device(shost->shost_gendev.parent);
227233

drivers/scsi/scsi_error.c

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1775,6 +1775,14 @@ int scsi_error_handler(void *data)
17751775
* what we need to do to get it up and online again (if we can).
17761776
* If we fail, we end up taking the thing offline.
17771777
*/
1778+
if (scsi_autopm_get_host(shost) != 0) {
1779+
SCSI_LOG_ERROR_RECOVERY(1,
1780+
printk(KERN_ERR "Error handler scsi_eh_%d "
1781+
"unable to autoresume\n",
1782+
shost->host_no));
1783+
continue;
1784+
}
1785+
17781786
if (shost->transportt->eh_strategy_handler)
17791787
shost->transportt->eh_strategy_handler(shost);
17801788
else
@@ -1788,6 +1796,7 @@ int scsi_error_handler(void *data)
17881796
* which are still online.
17891797
*/
17901798
scsi_restart_operations(shost);
1799+
scsi_autopm_put_host(shost);
17911800
set_current_state(TASK_INTERRUPTIBLE);
17921801
}
17931802
__set_current_state(TASK_RUNNING);
@@ -1885,12 +1894,16 @@ scsi_reset_provider_done_command(struct scsi_cmnd *scmd)
18851894
int
18861895
scsi_reset_provider(struct scsi_device *dev, int flag)
18871896
{
1888-
struct scsi_cmnd *scmd = scsi_get_command(dev, GFP_KERNEL);
1897+
struct scsi_cmnd *scmd;
18891898
struct Scsi_Host *shost = dev->host;
18901899
struct request req;
18911900
unsigned long flags;
18921901
int rtn;
18931902

1903+
if (scsi_autopm_get_host(shost) < 0)
1904+
return FAILED;
1905+
1906+
scmd = scsi_get_command(dev, GFP_KERNEL);
18941907
blk_rq_init(NULL, &req);
18951908
scmd->request = &req;
18961909

@@ -1947,6 +1960,7 @@ scsi_reset_provider(struct scsi_device *dev, int flag)
19471960
scsi_run_host_queues(shost);
19481961

19491962
scsi_next_command(scmd);
1963+
scsi_autopm_put_host(shost);
19501964
return rtn;
19511965
}
19521966
EXPORT_SYMBOL(scsi_reset_provider);

drivers/scsi/scsi_pm.c

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ static int scsi_bus_resume_common(struct device *dev)
5959

6060
if (scsi_is_sdev_device(dev))
6161
err = scsi_dev_type_resume(dev);
62+
63+
if (err == 0) {
64+
pm_runtime_disable(dev);
65+
pm_runtime_set_active(dev);
66+
pm_runtime_enable(dev);
67+
}
6268
return err;
6369
}
6470

@@ -86,11 +92,115 @@ static int scsi_bus_poweroff(struct device *dev)
8692

8793
#endif /* CONFIG_PM_SLEEP */
8894

95+
#ifdef CONFIG_PM_RUNTIME
96+
97+
static int scsi_runtime_suspend(struct device *dev)
98+
{
99+
int err = 0;
100+
101+
dev_dbg(dev, "scsi_runtime_suspend\n");
102+
if (scsi_is_sdev_device(dev)) {
103+
err = scsi_dev_type_suspend(dev, PMSG_AUTO_SUSPEND);
104+
if (err == -EAGAIN)
105+
pm_schedule_suspend(dev, jiffies_to_msecs(
106+
round_jiffies_up_relative(HZ/10)));
107+
}
108+
109+
/* Insert hooks here for targets, hosts, and transport classes */
110+
111+
return err;
112+
}
113+
114+
static int scsi_runtime_resume(struct device *dev)
115+
{
116+
int err = 0;
117+
118+
dev_dbg(dev, "scsi_runtime_resume\n");
119+
if (scsi_is_sdev_device(dev))
120+
err = scsi_dev_type_resume(dev);
121+
122+
/* Insert hooks here for targets, hosts, and transport classes */
123+
124+
return err;
125+
}
126+
127+
static int scsi_runtime_idle(struct device *dev)
128+
{
129+
int err;
130+
131+
dev_dbg(dev, "scsi_runtime_idle\n");
132+
133+
/* Insert hooks here for targets, hosts, and transport classes */
134+
135+
if (scsi_is_sdev_device(dev))
136+
err = pm_schedule_suspend(dev, 100);
137+
else
138+
err = pm_runtime_suspend(dev);
139+
return err;
140+
}
141+
142+
int scsi_autopm_get_device(struct scsi_device *sdev)
143+
{
144+
int err;
145+
146+
err = pm_runtime_get_sync(&sdev->sdev_gendev);
147+
if (err < 0)
148+
pm_runtime_put_sync(&sdev->sdev_gendev);
149+
else if (err > 0)
150+
err = 0;
151+
return err;
152+
}
153+
EXPORT_SYMBOL_GPL(scsi_autopm_get_device);
154+
155+
void scsi_autopm_put_device(struct scsi_device *sdev)
156+
{
157+
pm_runtime_put_sync(&sdev->sdev_gendev);
158+
}
159+
EXPORT_SYMBOL_GPL(scsi_autopm_put_device);
160+
161+
void scsi_autopm_get_target(struct scsi_target *starget)
162+
{
163+
pm_runtime_get_sync(&starget->dev);
164+
}
165+
166+
void scsi_autopm_put_target(struct scsi_target *starget)
167+
{
168+
pm_runtime_put_sync(&starget->dev);
169+
}
170+
171+
int scsi_autopm_get_host(struct Scsi_Host *shost)
172+
{
173+
int err;
174+
175+
err = pm_runtime_get_sync(&shost->shost_gendev);
176+
if (err < 0)
177+
pm_runtime_put_sync(&shost->shost_gendev);
178+
else if (err > 0)
179+
err = 0;
180+
return err;
181+
}
182+
183+
void scsi_autopm_put_host(struct Scsi_Host *shost)
184+
{
185+
pm_runtime_put_sync(&shost->shost_gendev);
186+
}
187+
188+
#else
189+
190+
#define scsi_runtime_suspend NULL
191+
#define scsi_runtime_resume NULL
192+
#define scsi_runtime_idle NULL
193+
194+
#endif /* CONFIG_PM_RUNTIME */
195+
89196
const struct dev_pm_ops scsi_bus_pm_ops = {
90197
.suspend = scsi_bus_suspend,
91198
.resume = scsi_bus_resume_common,
92199
.freeze = scsi_bus_freeze,
93200
.thaw = scsi_bus_resume_common,
94201
.poweroff = scsi_bus_poweroff,
95202
.restore = scsi_bus_resume_common,
203+
.runtime_suspend = scsi_runtime_suspend,
204+
.runtime_resume = scsi_runtime_resume,
205+
.runtime_idle = scsi_runtime_idle,
96206
};

drivers/scsi/scsi_priv.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ struct request_queue;
77
struct request;
88
struct scsi_cmnd;
99
struct scsi_device;
10+
struct scsi_target;
1011
struct scsi_host_template;
1112
struct Scsi_Host;
1213
struct scsi_nl_hdr;
@@ -147,9 +148,20 @@ static inline void scsi_netlink_exit(void) {}
147148
/* scsi_pm.c */
148149
#ifdef CONFIG_PM_OPS
149150
extern const struct dev_pm_ops scsi_bus_pm_ops;
150-
#else
151+
#else /* CONFIG_PM_OPS */
151152
#define scsi_bus_pm_ops (*NULL)
152153
#endif
154+
#ifdef CONFIG_PM_RUNTIME
155+
extern void scsi_autopm_get_target(struct scsi_target *);
156+
extern void scsi_autopm_put_target(struct scsi_target *);
157+
extern int scsi_autopm_get_host(struct Scsi_Host *);
158+
extern void scsi_autopm_put_host(struct Scsi_Host *);
159+
#else
160+
static inline void scsi_autopm_get_target(struct scsi_target *t) {}
161+
static inline void scsi_autopm_put_target(struct scsi_target *t) {}
162+
static inline int scsi_autopm_get_host(struct Scsi_Host *h) { return 0; }
163+
static inline void scsi_autopm_put_host(struct Scsi_Host *h) {}
164+
#endif /* CONFIG_PM_RUNTIME */
153165

154166
/*
155167
* internal scsi timeout functions: for use by mid-layer and transport

drivers/scsi/scsi_scan.c

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1513,14 +1513,18 @@ struct scsi_device *__scsi_add_device(struct Scsi_Host *shost, uint channel,
15131513
starget = scsi_alloc_target(parent, channel, id);
15141514
if (!starget)
15151515
return ERR_PTR(-ENOMEM);
1516+
scsi_autopm_get_target(starget);
15161517

15171518
mutex_lock(&shost->scan_mutex);
15181519
if (!shost->async_scan)
15191520
scsi_complete_async_scans();
15201521

1521-
if (scsi_host_scan_allowed(shost))
1522+
if (scsi_host_scan_allowed(shost) && scsi_autopm_get_host(shost) == 0) {
15221523
scsi_probe_and_add_lun(starget, lun, NULL, &sdev, 1, hostdata);
1524+
scsi_autopm_put_host(shost);
1525+
}
15231526
mutex_unlock(&shost->scan_mutex);
1527+
scsi_autopm_put_target(starget);
15241528
scsi_target_reap(starget);
15251529
put_device(&starget->dev);
15261530

@@ -1574,6 +1578,7 @@ static void __scsi_scan_target(struct device *parent, unsigned int channel,
15741578
starget = scsi_alloc_target(parent, channel, id);
15751579
if (!starget)
15761580
return;
1581+
scsi_autopm_get_target(starget);
15771582

15781583
if (lun != SCAN_WILD_CARD) {
15791584
/*
@@ -1599,6 +1604,7 @@ static void __scsi_scan_target(struct device *parent, unsigned int channel,
15991604
}
16001605

16011606
out_reap:
1607+
scsi_autopm_put_target(starget);
16021608
/* now determine if the target has any children at all
16031609
* and if not, nuke it */
16041610
scsi_target_reap(starget);
@@ -1633,8 +1639,10 @@ void scsi_scan_target(struct device *parent, unsigned int channel,
16331639
if (!shost->async_scan)
16341640
scsi_complete_async_scans();
16351641

1636-
if (scsi_host_scan_allowed(shost))
1642+
if (scsi_host_scan_allowed(shost) && scsi_autopm_get_host(shost) == 0) {
16371643
__scsi_scan_target(parent, channel, id, lun, rescan);
1644+
scsi_autopm_put_host(shost);
1645+
}
16381646
mutex_unlock(&shost->scan_mutex);
16391647
}
16401648
EXPORT_SYMBOL(scsi_scan_target);
@@ -1686,14 +1694,15 @@ int scsi_scan_host_selected(struct Scsi_Host *shost, unsigned int channel,
16861694
if (!shost->async_scan)
16871695
scsi_complete_async_scans();
16881696

1689-
if (scsi_host_scan_allowed(shost)) {
1697+
if (scsi_host_scan_allowed(shost) && scsi_autopm_get_host(shost) == 0) {
16901698
if (channel == SCAN_WILD_CARD)
16911699
for (channel = 0; channel <= shost->max_channel;
16921700
channel++)
16931701
scsi_scan_channel(shost, channel, id, lun,
16941702
rescan);
16951703
else
16961704
scsi_scan_channel(shost, channel, id, lun, rescan);
1705+
scsi_autopm_put_host(shost);
16971706
}
16981707
mutex_unlock(&shost->scan_mutex);
16991708

@@ -1831,8 +1840,11 @@ static void do_scsi_scan_host(struct Scsi_Host *shost)
18311840
static int do_scan_async(void *_data)
18321841
{
18331842
struct async_scan_data *data = _data;
1834-
do_scsi_scan_host(data->shost);
1843+
struct Scsi_Host *shost = data->shost;
1844+
1845+
do_scsi_scan_host(shost);
18351846
scsi_finish_async_scan(data);
1847+
scsi_autopm_put_host(shost);
18361848
return 0;
18371849
}
18381850

@@ -1847,16 +1859,20 @@ void scsi_scan_host(struct Scsi_Host *shost)
18471859

18481860
if (strncmp(scsi_scan_type, "none", 4) == 0)
18491861
return;
1862+
if (scsi_autopm_get_host(shost) < 0)
1863+
return;
18501864

18511865
data = scsi_prep_async_scan(shost);
18521866
if (!data) {
18531867
do_scsi_scan_host(shost);
1868+
scsi_autopm_put_host(shost);
18541869
return;
18551870
}
18561871

18571872
p = kthread_run(do_scan_async, data, "scsi_scan_%d", shost->host_no);
18581873
if (IS_ERR(p))
18591874
do_scan_async(data);
1875+
/* scsi_autopm_put_host(shost) is called in do_scan_async() */
18601876
}
18611877
EXPORT_SYMBOL(scsi_scan_host);
18621878

drivers/scsi/scsi_sysfs.c

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <linux/init.h>
1212
#include <linux/blkdev.h>
1313
#include <linux/device.h>
14+
#include <linux/pm_runtime.h>
1415

1516
#include <scsi/scsi.h>
1617
#include <scsi/scsi_device.h>
@@ -802,8 +803,6 @@ static int scsi_target_add(struct scsi_target *starget)
802803
if (starget->state != STARGET_CREATED)
803804
return 0;
804805

805-
device_enable_async_suspend(&starget->dev);
806-
807806
error = device_add(&starget->dev);
808807
if (error) {
809808
dev_err(&starget->dev, "target device_add failed, error %d\n", error);
@@ -812,6 +811,10 @@ static int scsi_target_add(struct scsi_target *starget)
812811
transport_add_device(&starget->dev);
813812
starget->state = STARGET_RUNNING;
814813

814+
pm_runtime_set_active(&starget->dev);
815+
pm_runtime_enable(&starget->dev);
816+
device_enable_async_suspend(&starget->dev);
817+
815818
return 0;
816819
}
817820

@@ -841,7 +844,20 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev)
841844
return error;
842845

843846
transport_configure_device(&starget->dev);
847+
844848
device_enable_async_suspend(&sdev->sdev_gendev);
849+
scsi_autopm_get_target(starget);
850+
pm_runtime_set_active(&sdev->sdev_gendev);
851+
pm_runtime_forbid(&sdev->sdev_gendev);
852+
pm_runtime_enable(&sdev->sdev_gendev);
853+
scsi_autopm_put_target(starget);
854+
855+
/* The following call will keep sdev active indefinitely, until
856+
* its driver does a corresponding scsi_autopm_pm_device(). Only
857+
* drivers supporting autosuspend will do this.
858+
*/
859+
scsi_autopm_get_device(sdev);
860+
845861
error = device_add(&sdev->sdev_gendev);
846862
if (error) {
847863
printk(KERN_INFO "error 1\n");

0 commit comments

Comments
 (0)