1/* 2 * scsi_pm.c Copyright (C) 2010 Alan Stern 3 * 4 * SCSI dynamic Power Management 5 * Initial version: Alan Stern <stern@rowland.harvard.edu> 6 */ 7 8#include <linux/pm_runtime.h> 9#include <linux/export.h> 10#include <linux/async.h> 11 12#include <scsi/scsi.h> 13#include <scsi/scsi_device.h> 14#include <scsi/scsi_driver.h> 15#include <scsi/scsi_host.h> 16 17#include "scsi_priv.h" 18 19static int scsi_dev_type_suspend(struct device *dev, pm_message_t msg) 20{ 21 struct device_driver *drv; 22 int err; 23 24 err = scsi_device_quiesce(to_scsi_device(dev)); 25 if (err == 0) { 26 drv = dev->driver; 27 if (drv && drv->suspend) 28 err = drv->suspend(dev, msg); 29 } 30 dev_dbg(dev, "scsi suspend: %d\n", err); 31 return err; 32} 33 34static int scsi_dev_type_resume(struct device *dev) 35{ 36 struct device_driver *drv; 37 int err = 0; 38 39 drv = dev->driver; 40 if (drv && drv->resume) 41 err = drv->resume(dev); 42 scsi_device_resume(to_scsi_device(dev)); 43 dev_dbg(dev, "scsi resume: %d\n", err); 44 return err; 45} 46 47#ifdef CONFIG_PM_SLEEP 48 49static int scsi_bus_suspend_common(struct device *dev, pm_message_t msg) 50{ 51 int err = 0; 52 53 if (scsi_is_sdev_device(dev)) { 54 /* 55 * sd is the only high-level SCSI driver to implement runtime 56 * PM, and sd treats runtime suspend, system suspend, and 57 * system hibernate identically (but not system freeze). 58 */ 59 if (pm_runtime_suspended(dev)) { 60 if (msg.event == PM_EVENT_SUSPEND || 61 msg.event == PM_EVENT_HIBERNATE) 62 return 0; /* already suspended */ 63 64 /* wake up device so that FREEZE will succeed */ 65 pm_runtime_resume(dev); 66 } 67 err = scsi_dev_type_suspend(dev, msg); 68 } 69 return err; 70} 71 72static int scsi_bus_resume_common(struct device *dev) 73{ 74 int err = 0; 75 76 if (scsi_is_sdev_device(dev)) { 77 /* 78 * Parent device may have runtime suspended as soon as 79 * it is woken up during the system resume. 80 * 81 * Resume it on behalf of child. 82 */ 83 pm_runtime_get_sync(dev->parent); 84 err = scsi_dev_type_resume(dev); 85 pm_runtime_put_sync(dev->parent); 86 } 87 88 if (err == 0) { 89 pm_runtime_disable(dev); 90 pm_runtime_set_active(dev); 91 pm_runtime_enable(dev); 92 } 93 return err; 94} 95 96static int scsi_bus_prepare(struct device *dev) 97{ 98 if (scsi_is_sdev_device(dev)) { 99 /* sd probing uses async_schedule. Wait until it finishes. */ 100 async_synchronize_full(); 101 102 } else if (scsi_is_host_device(dev)) { 103 /* Wait until async scanning is finished */ 104 scsi_complete_async_scans(); 105 } 106 return 0; 107} 108 109static int scsi_bus_suspend(struct device *dev) 110{ 111 return scsi_bus_suspend_common(dev, PMSG_SUSPEND); 112} 113 114static int scsi_bus_freeze(struct device *dev) 115{ 116 return scsi_bus_suspend_common(dev, PMSG_FREEZE); 117} 118 119static int scsi_bus_poweroff(struct device *dev) 120{ 121 return scsi_bus_suspend_common(dev, PMSG_HIBERNATE); 122} 123 124#else /* CONFIG_PM_SLEEP */ 125 126#define scsi_bus_resume_common NULL 127#define scsi_bus_prepare NULL 128#define scsi_bus_suspend NULL 129#define scsi_bus_freeze NULL 130#define scsi_bus_poweroff NULL 131 132#endif /* CONFIG_PM_SLEEP */ 133 134#ifdef CONFIG_PM_RUNTIME 135 136static int scsi_runtime_suspend(struct device *dev) 137{ 138 int err = 0; 139 140 dev_dbg(dev, "scsi_runtime_suspend\n"); 141 if (scsi_is_sdev_device(dev)) { 142 err = scsi_dev_type_suspend(dev, PMSG_AUTO_SUSPEND); 143 if (err == -EAGAIN) 144 pm_schedule_suspend(dev, jiffies_to_msecs( 145 round_jiffies_up_relative(HZ/10))); 146 } 147 148 /* Insert hooks here for targets, hosts, and transport classes */ 149 150 return err; 151} 152 153static int scsi_runtime_resume(struct device *dev) 154{ 155 int err = 0; 156 157 dev_dbg(dev, "scsi_runtime_resume\n"); 158 if (scsi_is_sdev_device(dev)) 159 err = scsi_dev_type_resume(dev); 160 161 /* Insert hooks here for targets, hosts, and transport classes */ 162 163 return err; 164} 165 166static int scsi_runtime_idle(struct device *dev) 167{ 168 int err; 169 170 dev_dbg(dev, "scsi_runtime_idle\n"); 171 172 /* Insert hooks here for targets, hosts, and transport classes */ 173 174 if (scsi_is_sdev_device(dev)) 175 err = pm_schedule_suspend(dev, 100); 176 else 177 err = pm_runtime_suspend(dev); 178 return err; 179} 180 181int scsi_autopm_get_device(struct scsi_device *sdev) 182{ 183 int err; 184 185 err = pm_runtime_get_sync(&sdev->sdev_gendev); 186 if (err < 0 && err !=-EACCES) 187 pm_runtime_put_sync(&sdev->sdev_gendev); 188 else 189 err = 0; 190 return err; 191} 192EXPORT_SYMBOL_GPL(scsi_autopm_get_device); 193 194void scsi_autopm_put_device(struct scsi_device *sdev) 195{ 196 pm_runtime_put_sync(&sdev->sdev_gendev); 197} 198EXPORT_SYMBOL_GPL(scsi_autopm_put_device); 199 200void scsi_autopm_get_target(struct scsi_target *starget) 201{ 202 pm_runtime_get_sync(&starget->dev); 203} 204 205void scsi_autopm_put_target(struct scsi_target *starget) 206{ 207 pm_runtime_put_sync(&starget->dev); 208} 209 210int scsi_autopm_get_host(struct Scsi_Host *shost) 211{ 212 int err; 213 214 err = pm_runtime_get_sync(&shost->shost_gendev); 215 if (err < 0 && err !=-EACCES) 216 pm_runtime_put_sync(&shost->shost_gendev); 217 else 218 err = 0; 219 return err; 220} 221 222void scsi_autopm_put_host(struct Scsi_Host *shost) 223{ 224 pm_runtime_put_sync(&shost->shost_gendev); 225} 226 227#else 228 229#define scsi_runtime_suspend NULL 230#define scsi_runtime_resume NULL 231#define scsi_runtime_idle NULL 232 233#endif /* CONFIG_PM_RUNTIME */ 234 235const struct dev_pm_ops scsi_bus_pm_ops = { 236 .prepare = scsi_bus_prepare, 237 .suspend = scsi_bus_suspend, 238 .resume = scsi_bus_resume_common, 239 .freeze = scsi_bus_freeze, 240 .thaw = scsi_bus_resume_common, 241 .poweroff = scsi_bus_poweroff, 242 .restore = scsi_bus_resume_common, 243 .runtime_suspend = scsi_runtime_suspend, 244 .runtime_resume = scsi_runtime_resume, 245 .runtime_idle = scsi_runtime_idle, 246}; 247