summaryrefslogtreecommitdiff
path: root/drivers/s390/cio/device.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/s390/cio/device.c')
-rw-r--r--drivers/s390/cio/device.c237
1 files changed, 237 insertions, 0 deletions
diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c
index 35441fa16be1..228a6c314d84 100644
--- a/drivers/s390/cio/device.c
+++ b/drivers/s390/cio/device.c
@@ -1895,6 +1895,242 @@ static void ccw_device_shutdown(struct device *dev)
disable_cmf(cdev);
}
+static int ccw_device_pm_prepare(struct device *dev)
+{
+ struct ccw_device *cdev = to_ccwdev(dev);
+
+ if (work_pending(&cdev->private->kick_work))
+ return -EAGAIN;
+ /* Fail while device is being set online/offline. */
+ if (atomic_read(&cdev->private->onoff))
+ return -EAGAIN;
+
+ if (cdev->online && cdev->drv && cdev->drv->prepare)
+ return cdev->drv->prepare(cdev);
+
+ return 0;
+}
+
+static void ccw_device_pm_complete(struct device *dev)
+{
+ struct ccw_device *cdev = to_ccwdev(dev);
+
+ if (cdev->online && cdev->drv && cdev->drv->complete)
+ cdev->drv->complete(cdev);
+}
+
+static int ccw_device_pm_freeze(struct device *dev)
+{
+ struct ccw_device *cdev = to_ccwdev(dev);
+ struct subchannel *sch = to_subchannel(cdev->dev.parent);
+ int ret, cm_enabled;
+
+ /* Fail suspend while device is in transistional state. */
+ if (!dev_fsm_final_state(cdev))
+ return -EAGAIN;
+ if (!cdev->online)
+ return 0;
+ if (cdev->drv && cdev->drv->freeze) {
+ ret = cdev->drv->freeze(cdev);
+ if (ret)
+ return ret;
+ }
+
+ spin_lock_irq(sch->lock);
+ cm_enabled = cdev->private->cmb != NULL;
+ spin_unlock_irq(sch->lock);
+ if (cm_enabled) {
+ /* Don't have the css write on memory. */
+ ret = ccw_set_cmf(cdev, 0);
+ if (ret)
+ return ret;
+ }
+ /* From here on, disallow device driver I/O. */
+ spin_lock_irq(sch->lock);
+ ret = cio_disable_subchannel(sch);
+ spin_unlock_irq(sch->lock);
+
+ return ret;
+}
+
+static int ccw_device_pm_thaw(struct device *dev)
+{
+ struct ccw_device *cdev = to_ccwdev(dev);
+ struct subchannel *sch = to_subchannel(cdev->dev.parent);
+ int ret, cm_enabled;
+
+ if (!cdev->online)
+ return 0;
+
+ spin_lock_irq(sch->lock);
+ /* Allow device driver I/O again. */
+ ret = cio_enable_subchannel(sch, (u32)(addr_t)sch);
+ cm_enabled = cdev->private->cmb != NULL;
+ spin_unlock_irq(sch->lock);
+ if (ret)
+ return ret;
+
+ if (cm_enabled) {
+ ret = ccw_set_cmf(cdev, 1);
+ if (ret)
+ return ret;
+ }
+
+ if (cdev->drv && cdev->drv->thaw)
+ ret = cdev->drv->thaw(cdev);
+
+ return ret;
+}
+
+static void __ccw_device_pm_restore(struct ccw_device *cdev)
+{
+ struct subchannel *sch = to_subchannel(cdev->dev.parent);
+ int ret;
+
+ if (cio_is_console(sch->schid))
+ goto out;
+ /*
+ * While we were sleeping, devices may have gone or become
+ * available again. Kick re-detection.
+ */
+ spin_lock_irq(sch->lock);
+ cdev->private->flags.resuming = 1;
+ ret = ccw_device_recognition(cdev);
+ spin_unlock_irq(sch->lock);
+ if (ret) {
+ CIO_MSG_EVENT(0, "Couldn't start recognition for device "
+ "%s (ret=%d)\n", dev_name(&cdev->dev), ret);
+ spin_lock_irq(sch->lock);
+ cdev->private->state = DEV_STATE_DISCONNECTED;
+ spin_unlock_irq(sch->lock);
+ /* notify driver after the resume cb */
+ goto out;
+ }
+ wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev) ||
+ cdev->private->state == DEV_STATE_DISCONNECTED);
+
+out:
+ cdev->private->flags.resuming = 0;
+}
+
+static int resume_handle_boxed(struct ccw_device *cdev)
+{
+ cdev->private->state = DEV_STATE_BOXED;
+ if (ccw_device_notify(cdev, CIO_BOXED))
+ return 0;
+ ccw_device_schedule_sch_unregister(cdev);
+ return -ENODEV;
+}
+
+static int resume_handle_disc(struct ccw_device *cdev)
+{
+ cdev->private->state = DEV_STATE_DISCONNECTED;
+ if (ccw_device_notify(cdev, CIO_GONE))
+ return 0;
+ ccw_device_schedule_sch_unregister(cdev);
+ return -ENODEV;
+}
+
+static int ccw_device_pm_restore(struct device *dev)
+{
+ struct ccw_device *cdev = to_ccwdev(dev);
+ struct subchannel *sch = to_subchannel(cdev->dev.parent);
+ int ret = 0, cm_enabled;
+
+ __ccw_device_pm_restore(cdev);
+ spin_lock_irq(sch->lock);
+ if (cio_is_console(sch->schid)) {
+ cio_enable_subchannel(sch, (u32)(addr_t)sch);
+ spin_unlock_irq(sch->lock);
+ goto out_restore;
+ }
+ cdev->private->flags.donotify = 0;
+ /* check recognition results */
+ switch (cdev->private->state) {
+ case DEV_STATE_OFFLINE:
+ break;
+ case DEV_STATE_BOXED:
+ ret = resume_handle_boxed(cdev);
+ spin_unlock_irq(sch->lock);
+ if (ret)
+ goto out;
+ goto out_restore;
+ case DEV_STATE_DISCONNECTED:
+ goto out_disc_unlock;
+ default:
+ goto out_unreg_unlock;
+ }
+ /* check if the device id has changed */
+ if (sch->schib.pmcw.dev != cdev->private->dev_id.devno) {
+ CIO_MSG_EVENT(0, "resume: sch %s: failed (devno changed from "
+ "%04x to %04x)\n", dev_name(&sch->dev),
+ cdev->private->dev_id.devno,
+ sch->schib.pmcw.dev);
+ goto out_unreg_unlock;
+ }
+ /* check if the device type has changed */
+ if (!ccw_device_test_sense_data(cdev)) {
+ ccw_device_update_sense_data(cdev);
+ PREPARE_WORK(&cdev->private->kick_work,
+ ccw_device_do_unbind_bind);
+ queue_work(ccw_device_work, &cdev->private->kick_work);
+ ret = -ENODEV;
+ goto out_unlock;
+ }
+ if (!cdev->online) {
+ ret = 0;
+ goto out_unlock;
+ }
+ ret = ccw_device_online(cdev);
+ if (ret)
+ goto out_disc_unlock;
+
+ cm_enabled = cdev->private->cmb != NULL;
+ spin_unlock_irq(sch->lock);
+
+ wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev));
+ if (cdev->private->state != DEV_STATE_ONLINE) {
+ spin_lock_irq(sch->lock);
+ goto out_disc_unlock;
+ }
+ if (cm_enabled) {
+ ret = ccw_set_cmf(cdev, 1);
+ if (ret) {
+ CIO_MSG_EVENT(2, "resume: cdev %s: cmf failed "
+ "(rc=%d)\n", dev_name(&cdev->dev), ret);
+ ret = 0;
+ }
+ }
+
+out_restore:
+ if (cdev->online && cdev->drv && cdev->drv->restore)
+ ret = cdev->drv->restore(cdev);
+out:
+ return ret;
+
+out_disc_unlock:
+ ret = resume_handle_disc(cdev);
+ spin_unlock_irq(sch->lock);
+ if (ret)
+ return ret;
+ goto out_restore;
+
+out_unreg_unlock:
+ ccw_device_schedule_sch_unregister(cdev);
+ ret = -ENODEV;
+out_unlock:
+ spin_unlock_irq(sch->lock);
+ return ret;
+}
+
+static struct dev_pm_ops ccw_pm_ops = {
+ .prepare = ccw_device_pm_prepare,
+ .complete = ccw_device_pm_complete,
+ .freeze = ccw_device_pm_freeze,
+ .thaw = ccw_device_pm_thaw,
+ .restore = ccw_device_pm_restore,
+};
+
struct bus_type ccw_bus_type = {
.name = "ccw",
.match = ccw_bus_match,
@@ -1902,6 +2138,7 @@ struct bus_type ccw_bus_type = {
.probe = ccw_device_probe,
.remove = ccw_device_remove,
.shutdown = ccw_device_shutdown,
+ .pm = &ccw_pm_ops,
};
/**