Skip to content

Commit

Permalink
[SCSI] implement runtime Power Management
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Alan Stern authored and James Bottomley committed Jul 28, 2010
1 parent db5bd1e commit bc4f240
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 11 deletions.
10 changes: 8 additions & 2 deletions drivers/scsi/hosts.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <linux/completion.h>
#include <linux/transport_class.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>

#include <scsi/scsi_device.h>
#include <scsi/scsi_host.h>
Expand Down Expand Up @@ -156,6 +157,7 @@ EXPORT_SYMBOL(scsi_host_set_state);
void scsi_remove_host(struct Scsi_Host *shost)
{
unsigned long flags;

mutex_lock(&shost->scan_mutex);
spin_lock_irqsave(shost->host_lock, flags);
if (scsi_host_set_state(shost, SHOST_CANCEL))
Expand All @@ -165,6 +167,8 @@ void scsi_remove_host(struct Scsi_Host *shost)
return;
}
spin_unlock_irqrestore(shost->host_lock, flags);

scsi_autopm_get_host(shost);
scsi_forget_host(shost);
mutex_unlock(&shost->scan_mutex);
scsi_proc_host_rm(shost);
Expand Down Expand Up @@ -216,12 +220,14 @@ int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev,
shost->shost_gendev.parent = dev ? dev : &platform_bus;
shost->dma_dev = dma_dev;

device_enable_async_suspend(&shost->shost_gendev);

error = device_add(&shost->shost_gendev);
if (error)
goto out;

pm_runtime_set_active(&shost->shost_gendev);
pm_runtime_enable(&shost->shost_gendev);
device_enable_async_suspend(&shost->shost_gendev);

scsi_host_set_state(shost, SHOST_RUNNING);
get_device(shost->shost_gendev.parent);

Expand Down
16 changes: 15 additions & 1 deletion drivers/scsi/scsi_error.c
Original file line number Diff line number Diff line change
Expand Up @@ -1775,6 +1775,14 @@ int scsi_error_handler(void *data)
* what we need to do to get it up and online again (if we can).
* If we fail, we end up taking the thing offline.
*/
if (scsi_autopm_get_host(shost) != 0) {
SCSI_LOG_ERROR_RECOVERY(1,
printk(KERN_ERR "Error handler scsi_eh_%d "
"unable to autoresume\n",
shost->host_no));
continue;
}

if (shost->transportt->eh_strategy_handler)
shost->transportt->eh_strategy_handler(shost);
else
Expand All @@ -1788,6 +1796,7 @@ int scsi_error_handler(void *data)
* which are still online.
*/
scsi_restart_operations(shost);
scsi_autopm_put_host(shost);
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
Expand Down Expand Up @@ -1885,12 +1894,16 @@ scsi_reset_provider_done_command(struct scsi_cmnd *scmd)
int
scsi_reset_provider(struct scsi_device *dev, int flag)
{
struct scsi_cmnd *scmd = scsi_get_command(dev, GFP_KERNEL);
struct scsi_cmnd *scmd;
struct Scsi_Host *shost = dev->host;
struct request req;
unsigned long flags;
int rtn;

if (scsi_autopm_get_host(shost) < 0)
return FAILED;

scmd = scsi_get_command(dev, GFP_KERNEL);
blk_rq_init(NULL, &req);
scmd->request = &req;

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

scsi_next_command(scmd);
scsi_autopm_put_host(shost);
return rtn;
}
EXPORT_SYMBOL(scsi_reset_provider);
Expand Down
110 changes: 110 additions & 0 deletions drivers/scsi/scsi_pm.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ static int scsi_bus_resume_common(struct device *dev)

if (scsi_is_sdev_device(dev))
err = scsi_dev_type_resume(dev);

if (err == 0) {
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
}
return err;
}

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

#endif /* CONFIG_PM_SLEEP */

#ifdef CONFIG_PM_RUNTIME

static int scsi_runtime_suspend(struct device *dev)
{
int err = 0;

dev_dbg(dev, "scsi_runtime_suspend\n");
if (scsi_is_sdev_device(dev)) {
err = scsi_dev_type_suspend(dev, PMSG_AUTO_SUSPEND);
if (err == -EAGAIN)
pm_schedule_suspend(dev, jiffies_to_msecs(
round_jiffies_up_relative(HZ/10)));
}

/* Insert hooks here for targets, hosts, and transport classes */

return err;
}

static int scsi_runtime_resume(struct device *dev)
{
int err = 0;

dev_dbg(dev, "scsi_runtime_resume\n");
if (scsi_is_sdev_device(dev))
err = scsi_dev_type_resume(dev);

/* Insert hooks here for targets, hosts, and transport classes */

return err;
}

static int scsi_runtime_idle(struct device *dev)
{
int err;

dev_dbg(dev, "scsi_runtime_idle\n");

/* Insert hooks here for targets, hosts, and transport classes */

if (scsi_is_sdev_device(dev))
err = pm_schedule_suspend(dev, 100);
else
err = pm_runtime_suspend(dev);
return err;
}

int scsi_autopm_get_device(struct scsi_device *sdev)
{
int err;

err = pm_runtime_get_sync(&sdev->sdev_gendev);
if (err < 0)
pm_runtime_put_sync(&sdev->sdev_gendev);
else if (err > 0)
err = 0;
return err;
}
EXPORT_SYMBOL_GPL(scsi_autopm_get_device);

void scsi_autopm_put_device(struct scsi_device *sdev)
{
pm_runtime_put_sync(&sdev->sdev_gendev);
}
EXPORT_SYMBOL_GPL(scsi_autopm_put_device);

void scsi_autopm_get_target(struct scsi_target *starget)
{
pm_runtime_get_sync(&starget->dev);
}

void scsi_autopm_put_target(struct scsi_target *starget)
{
pm_runtime_put_sync(&starget->dev);
}

int scsi_autopm_get_host(struct Scsi_Host *shost)
{
int err;

err = pm_runtime_get_sync(&shost->shost_gendev);
if (err < 0)
pm_runtime_put_sync(&shost->shost_gendev);
else if (err > 0)
err = 0;
return err;
}

void scsi_autopm_put_host(struct Scsi_Host *shost)
{
pm_runtime_put_sync(&shost->shost_gendev);
}

#else

#define scsi_runtime_suspend NULL
#define scsi_runtime_resume NULL
#define scsi_runtime_idle NULL

#endif /* CONFIG_PM_RUNTIME */

const struct dev_pm_ops scsi_bus_pm_ops = {
.suspend = scsi_bus_suspend,
.resume = scsi_bus_resume_common,
.freeze = scsi_bus_freeze,
.thaw = scsi_bus_resume_common,
.poweroff = scsi_bus_poweroff,
.restore = scsi_bus_resume_common,
.runtime_suspend = scsi_runtime_suspend,
.runtime_resume = scsi_runtime_resume,
.runtime_idle = scsi_runtime_idle,
};
14 changes: 13 additions & 1 deletion drivers/scsi/scsi_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ struct request_queue;
struct request;
struct scsi_cmnd;
struct scsi_device;
struct scsi_target;
struct scsi_host_template;
struct Scsi_Host;
struct scsi_nl_hdr;
Expand Down Expand Up @@ -147,9 +148,20 @@ static inline void scsi_netlink_exit(void) {}
/* scsi_pm.c */
#ifdef CONFIG_PM_OPS
extern const struct dev_pm_ops scsi_bus_pm_ops;
#else
#else /* CONFIG_PM_OPS */
#define scsi_bus_pm_ops (*NULL)
#endif
#ifdef CONFIG_PM_RUNTIME
extern void scsi_autopm_get_target(struct scsi_target *);
extern void scsi_autopm_put_target(struct scsi_target *);
extern int scsi_autopm_get_host(struct Scsi_Host *);
extern void scsi_autopm_put_host(struct Scsi_Host *);
#else
static inline void scsi_autopm_get_target(struct scsi_target *t) {}
static inline void scsi_autopm_put_target(struct scsi_target *t) {}
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 */

/*
* internal scsi timeout functions: for use by mid-layer and transport
Expand Down
24 changes: 20 additions & 4 deletions drivers/scsi/scsi_scan.c
Original file line number Diff line number Diff line change
Expand Up @@ -1513,14 +1513,18 @@ struct scsi_device *__scsi_add_device(struct Scsi_Host *shost, uint channel,
starget = scsi_alloc_target(parent, channel, id);
if (!starget)
return ERR_PTR(-ENOMEM);
scsi_autopm_get_target(starget);

mutex_lock(&shost->scan_mutex);
if (!shost->async_scan)
scsi_complete_async_scans();

if (scsi_host_scan_allowed(shost))
if (scsi_host_scan_allowed(shost) && scsi_autopm_get_host(shost) == 0) {
scsi_probe_and_add_lun(starget, lun, NULL, &sdev, 1, hostdata);
scsi_autopm_put_host(shost);
}
mutex_unlock(&shost->scan_mutex);
scsi_autopm_put_target(starget);
scsi_target_reap(starget);
put_device(&starget->dev);

Expand Down Expand Up @@ -1574,6 +1578,7 @@ static void __scsi_scan_target(struct device *parent, unsigned int channel,
starget = scsi_alloc_target(parent, channel, id);
if (!starget)
return;
scsi_autopm_get_target(starget);

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

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

if (scsi_host_scan_allowed(shost))
if (scsi_host_scan_allowed(shost) && scsi_autopm_get_host(shost) == 0) {
__scsi_scan_target(parent, channel, id, lun, rescan);
scsi_autopm_put_host(shost);
}
mutex_unlock(&shost->scan_mutex);
}
EXPORT_SYMBOL(scsi_scan_target);
Expand Down Expand Up @@ -1686,14 +1694,15 @@ int scsi_scan_host_selected(struct Scsi_Host *shost, unsigned int channel,
if (!shost->async_scan)
scsi_complete_async_scans();

if (scsi_host_scan_allowed(shost)) {
if (scsi_host_scan_allowed(shost) && scsi_autopm_get_host(shost) == 0) {
if (channel == SCAN_WILD_CARD)
for (channel = 0; channel <= shost->max_channel;
channel++)
scsi_scan_channel(shost, channel, id, lun,
rescan);
else
scsi_scan_channel(shost, channel, id, lun, rescan);
scsi_autopm_put_host(shost);
}
mutex_unlock(&shost->scan_mutex);

Expand Down Expand Up @@ -1831,8 +1840,11 @@ static void do_scsi_scan_host(struct Scsi_Host *shost)
static int do_scan_async(void *_data)
{
struct async_scan_data *data = _data;
do_scsi_scan_host(data->shost);
struct Scsi_Host *shost = data->shost;

do_scsi_scan_host(shost);
scsi_finish_async_scan(data);
scsi_autopm_put_host(shost);
return 0;
}

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

if (strncmp(scsi_scan_type, "none", 4) == 0)
return;
if (scsi_autopm_get_host(shost) < 0)
return;

data = scsi_prep_async_scan(shost);
if (!data) {
do_scsi_scan_host(shost);
scsi_autopm_put_host(shost);
return;
}

p = kthread_run(do_scan_async, data, "scsi_scan_%d", shost->host_no);
if (IS_ERR(p))
do_scan_async(data);
/* scsi_autopm_put_host(shost) is called in do_scan_async() */
}
EXPORT_SYMBOL(scsi_scan_host);

Expand Down
20 changes: 18 additions & 2 deletions drivers/scsi/scsi_sysfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <linux/init.h>
#include <linux/blkdev.h>
#include <linux/device.h>
#include <linux/pm_runtime.h>

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

device_enable_async_suspend(&starget->dev);

error = device_add(&starget->dev);
if (error) {
dev_err(&starget->dev, "target device_add failed, error %d\n", error);
Expand All @@ -812,6 +811,10 @@ static int scsi_target_add(struct scsi_target *starget)
transport_add_device(&starget->dev);
starget->state = STARGET_RUNNING;

pm_runtime_set_active(&starget->dev);
pm_runtime_enable(&starget->dev);
device_enable_async_suspend(&starget->dev);

return 0;
}

Expand Down Expand Up @@ -841,7 +844,20 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev)
return error;

transport_configure_device(&starget->dev);

device_enable_async_suspend(&sdev->sdev_gendev);
scsi_autopm_get_target(starget);
pm_runtime_set_active(&sdev->sdev_gendev);
pm_runtime_forbid(&sdev->sdev_gendev);
pm_runtime_enable(&sdev->sdev_gendev);
scsi_autopm_put_target(starget);

/* The following call will keep sdev active indefinitely, until
* its driver does a corresponding scsi_autopm_pm_device(). Only
* drivers supporting autosuspend will do this.
*/
scsi_autopm_get_device(sdev);

error = device_add(&sdev->sdev_gendev);
if (error) {
printk(KERN_INFO "error 1\n");
Expand Down
Loading

0 comments on commit bc4f240

Please sign in to comment.