From 6e49949c5e9e04d64e16df3723dd3f5bd25a29e2 Mon Sep 17 00:00:00 2001 From: Mike Christie Date: Sun, 25 Apr 2010 07:03:57 -0500 Subject: [SCSI] Log msg when getting Unit Attention If the user accidentally changes LUN mappings or it occurs due to a bug, then it can cause data corruption that can take months and months to track down. This patch adds a log message when getting REPORT_LUNS_DATA_CHANGED and it adds a generic message for other Unit Attentions with asc == 0x3f. We are working on adding support for handling of these errors, but I think until then we should at least log a message so tracking down problems as a result of one of these changes is a little easier. Signed-off-by: Mike Christie Signed-off-by: James Bottomley --- drivers/scsi/scsi_error.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'drivers/scsi/scsi_error.c') diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c index a5d630f5f519..c60cffbefa3c 100644 --- a/drivers/scsi/scsi_error.c +++ b/drivers/scsi/scsi_error.c @@ -307,6 +307,19 @@ static int scsi_check_sense(struct scsi_cmnd *scmd) (sshdr.asc == 0x04) && (sshdr.ascq == 0x02)) return FAILED; + if (sshdr.asc == 0x3f && sshdr.ascq == 0x0e) + scmd_printk(KERN_WARNING, scmd, + "Warning! Received an indication that the " + "LUN assignments on this target have " + "changed. The Linux SCSI layer does not " + "automatically remap LUN assignments.\n"); + else if (sshdr.asc == 0x3f) + scmd_printk(KERN_WARNING, scmd, + "Warning! Received an indication that the " + "operating parameters on this target have " + "changed. The Linux SCSI layer does not " + "automatically adjust these parameters.\n"); + if (blk_barrier_rq(scmd->request)) /* * barrier requests should always retry on UA -- cgit v1.2.3 From bc4f24014de58f045f169742701a6598884d93db Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Thu, 17 Jun 2010 10:41:42 -0400 Subject: [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 Signed-off-by: James Bottomley --- drivers/scsi/hosts.c | 10 ++++- drivers/scsi/scsi_error.c | 16 ++++++- drivers/scsi/scsi_pm.c | 110 +++++++++++++++++++++++++++++++++++++++++++++ drivers/scsi/scsi_priv.h | 14 +++++- drivers/scsi/scsi_scan.c | 24 ++++++++-- drivers/scsi/scsi_sysfs.c | 20 ++++++++- drivers/scsi/sg.c | 10 ++++- include/scsi/scsi_device.h | 8 ++++ 8 files changed, 201 insertions(+), 11 deletions(-) (limited to 'drivers/scsi/scsi_error.c') diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c index a2b1414da288..8a8f803439e1 100644 --- a/drivers/scsi/hosts.c +++ b/drivers/scsi/hosts.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -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)) @@ -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); @@ -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); diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c index c60cffbefa3c..2bf98469dc4c 100644 --- a/drivers/scsi/scsi_error.c +++ b/drivers/scsi/scsi_error.c @@ -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 @@ -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); @@ -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; @@ -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); diff --git a/drivers/scsi/scsi_pm.c b/drivers/scsi/scsi_pm.c index cd83758ce0a2..d70e91ae60af 100644 --- a/drivers/scsi/scsi_pm.c +++ b/drivers/scsi/scsi_pm.c @@ -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; } @@ -86,6 +92,107 @@ 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, @@ -93,4 +200,7 @@ const struct dev_pm_ops scsi_bus_pm_ops = { .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, }; diff --git a/drivers/scsi/scsi_priv.h b/drivers/scsi/scsi_priv.h index dddacc732550..026295e2c539 100644 --- a/drivers/scsi/scsi_priv.h +++ b/drivers/scsi/scsi_priv.h @@ -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; @@ -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 diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c index 1c027a97d8b9..3d0a1e6e9c48 100644 --- a/drivers/scsi/scsi_scan.c +++ b/drivers/scsi/scsi_scan.c @@ -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); @@ -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) { /* @@ -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); @@ -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); @@ -1686,7 +1694,7 @@ 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++) @@ -1694,6 +1702,7 @@ int scsi_scan_host_selected(struct Scsi_Host *shost, unsigned int channel, rescan); else scsi_scan_channel(shost, channel, id, lun, rescan); + scsi_autopm_put_host(shost); } mutex_unlock(&shost->scan_mutex); @@ -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; } @@ -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); diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c index 5f85f8e831f3..562fb3bce261 100644 --- a/drivers/scsi/scsi_sysfs.c +++ b/drivers/scsi/scsi_sysfs.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -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); @@ -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; } @@ -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"); diff --git a/drivers/scsi/sg.c b/drivers/scsi/sg.c index d4549092400c..2968c6b83ddb 100644 --- a/drivers/scsi/sg.c +++ b/drivers/scsi/sg.c @@ -245,6 +245,10 @@ sg_open(struct inode *inode, struct file *filp) if (retval) goto sg_put; + retval = scsi_autopm_get_device(sdp->device); + if (retval) + goto sdp_put; + if (!((flags & O_NONBLOCK) || scsi_block_when_processing_errors(sdp->device))) { retval = -ENXIO; @@ -302,8 +306,11 @@ sg_open(struct inode *inode, struct file *filp) } retval = 0; error_out: - if (retval) + if (retval) { + scsi_autopm_put_device(sdp->device); +sdp_put: scsi_device_put(sdp->device); + } sg_put: if (sdp) sg_put_dev(sdp); @@ -327,6 +334,7 @@ sg_release(struct inode *inode, struct file *filp) sdp->exclude = 0; wake_up_interruptible(&sdp->o_excl_wait); + scsi_autopm_put_device(sdp->device); kref_put(&sfp->f_ref, sg_remove_sfp); return 0; } diff --git a/include/scsi/scsi_device.h b/include/scsi/scsi_device.h index d80b6dbed1ca..50cb34ffef11 100644 --- a/include/scsi/scsi_device.h +++ b/include/scsi/scsi_device.h @@ -381,6 +381,14 @@ extern int scsi_execute_req(struct scsi_device *sdev, const unsigned char *cmd, struct scsi_sense_hdr *, int timeout, int retries, int *resid); +#ifdef CONFIG_PM_RUNTIME +extern int scsi_autopm_get_device(struct scsi_device *); +extern void scsi_autopm_put_device(struct scsi_device *); +#else +static inline int scsi_autopm_get_device(struct scsi_device *d) { return 0; } +static inline void scsi_autopm_put_device(struct scsi_device *d) {} +#endif /* CONFIG_PM_RUNTIME */ + static inline int __must_check scsi_device_reprobe(struct scsi_device *sdev) { return device_reprobe(&sdev->sdev_gendev); -- cgit v1.2.3