From 823d494ac11111064cf39abd4178ce299414c771 Mon Sep 17 00:00:00 2001 From: Sebastian Ott Date: Tue, 16 Jun 2009 10:30:20 +0200 Subject: [S390] pm: ccw bus power management callbacks Signed-off-by: Sebastian Ott Signed-off-by: Martin Schwidefsky --- arch/s390/include/asm/ccwdev.h | 18 +++- drivers/s390/cio/cmf.c | 5 + drivers/s390/cio/device.c | 237 +++++++++++++++++++++++++++++++++++++++++ drivers/s390/cio/device.h | 3 + drivers/s390/cio/device_fsm.c | 96 ++++++++--------- drivers/s390/cio/device_ops.c | 26 +++-- drivers/s390/cio/io_sch.h | 1 + 7 files changed, 321 insertions(+), 65 deletions(-) diff --git a/arch/s390/include/asm/ccwdev.h b/arch/s390/include/asm/ccwdev.h index ba007d8df941..18f0a7580926 100644 --- a/arch/s390/include/asm/ccwdev.h +++ b/arch/s390/include/asm/ccwdev.h @@ -1,11 +1,9 @@ /* - * include/asm-s390/ccwdev.h - * include/asm-s390x/ccwdev.h + * Copyright IBM Corp. 2002, 2009 * - * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, IBM Corporation - * Author(s): Arnd Bergmann + * Author(s): Arnd Bergmann * - * Interface for CCW device drivers + * Interface for CCW device drivers */ #ifndef _S390_CCWDEV_H_ #define _S390_CCWDEV_H_ @@ -104,6 +102,11 @@ struct ccw_device { * @set_offline: called when setting device offline * @notify: notify driver of device state changes * @shutdown: called at device shutdown + * @prepare: prepare for pm state transition + * @complete: undo work done in @prepare + * @freeze: callback for freezing during hibernation snapshotting + * @thaw: undo work done in @freeze + * @restore: callback for restoring after hibernation * @driver: embedded device driver structure * @name: device driver name */ @@ -116,6 +119,11 @@ struct ccw_driver { int (*set_offline) (struct ccw_device *); int (*notify) (struct ccw_device *, int); void (*shutdown) (struct ccw_device *); + int (*prepare) (struct ccw_device *); + void (*complete) (struct ccw_device *); + int (*freeze)(struct ccw_device *); + int (*thaw) (struct ccw_device *); + int (*restore)(struct ccw_device *); struct device_driver driver; char *name; }; diff --git a/drivers/s390/cio/cmf.c b/drivers/s390/cio/cmf.c index dc98b2c63862..30f516111307 100644 --- a/drivers/s390/cio/cmf.c +++ b/drivers/s390/cio/cmf.c @@ -1204,6 +1204,11 @@ static ssize_t cmb_enable_store(struct device *dev, DEVICE_ATTR(cmb_enable, 0644, cmb_enable_show, cmb_enable_store); +int ccw_set_cmf(struct ccw_device *cdev, int enable) +{ + return cmbops->set(cdev, enable ? 2 : 0); +} + /** * enable_cmf() - switch on the channel measurement for a specific device * @cdev: The ccw device to be enabled 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, }; /** diff --git a/drivers/s390/cio/device.h b/drivers/s390/cio/device.h index f1cbbd94ad4e..e3975107a578 100644 --- a/drivers/s390/cio/device.h +++ b/drivers/s390/cio/device.h @@ -87,6 +87,8 @@ int ccw_device_is_orphan(struct ccw_device *); int ccw_device_recognition(struct ccw_device *); int ccw_device_online(struct ccw_device *); int ccw_device_offline(struct ccw_device *); +void ccw_device_update_sense_data(struct ccw_device *); +int ccw_device_test_sense_data(struct ccw_device *); void ccw_device_schedule_sch_unregister(struct ccw_device *); int ccw_purge_blacklisted(void); @@ -133,5 +135,6 @@ extern struct bus_type ccw_bus_type; void retry_set_schib(struct ccw_device *cdev); void cmf_retry_copy_block(struct ccw_device *); int cmf_reenable(struct ccw_device *); +int ccw_set_cmf(struct ccw_device *cdev, int enable); extern struct device_attribute dev_attr_cmb_enable; #endif diff --git a/drivers/s390/cio/device_fsm.c b/drivers/s390/cio/device_fsm.c index e46049261561..3db88c52d287 100644 --- a/drivers/s390/cio/device_fsm.c +++ b/drivers/s390/cio/device_fsm.c @@ -177,29 +177,21 @@ ccw_device_cancel_halt_clear(struct ccw_device *cdev) panic("Can't stop i/o on subchannel.\n"); } -static int -ccw_device_handle_oper(struct ccw_device *cdev) +void ccw_device_update_sense_data(struct ccw_device *cdev) { - struct subchannel *sch; + memset(&cdev->id, 0, sizeof(cdev->id)); + cdev->id.cu_type = cdev->private->senseid.cu_type; + cdev->id.cu_model = cdev->private->senseid.cu_model; + cdev->id.dev_type = cdev->private->senseid.dev_type; + cdev->id.dev_model = cdev->private->senseid.dev_model; +} - sch = to_subchannel(cdev->dev.parent); - cdev->private->flags.recog_done = 1; - /* - * Check if cu type and device type still match. If - * not, it is certainly another device and we have to - * de- and re-register. - */ - if (cdev->id.cu_type != cdev->private->senseid.cu_type || - cdev->id.cu_model != cdev->private->senseid.cu_model || - cdev->id.dev_type != cdev->private->senseid.dev_type || - cdev->id.dev_model != cdev->private->senseid.dev_model) { - PREPARE_WORK(&cdev->private->kick_work, - ccw_device_do_unbind_bind); - queue_work(ccw_device_work, &cdev->private->kick_work); - return 0; - } - cdev->private->flags.donotify = 1; - return 1; +int ccw_device_test_sense_data(struct ccw_device *cdev) +{ + return cdev->id.cu_type == cdev->private->senseid.cu_type && + cdev->id.cu_model == cdev->private->senseid.cu_model && + cdev->id.dev_type == cdev->private->senseid.dev_type && + cdev->id.dev_model == cdev->private->senseid.dev_model; } /* @@ -233,7 +225,7 @@ static void ccw_device_recog_done(struct ccw_device *cdev, int state) { struct subchannel *sch; - int notify, old_lpm, same_dev; + int old_lpm; sch = to_subchannel(cdev->dev.parent); @@ -263,8 +255,12 @@ ccw_device_recog_done(struct ccw_device *cdev, int state) wake_up(&cdev->private->wait_q); return; } - notify = 0; - same_dev = 0; /* Keep the compiler quiet... */ + if (cdev->private->flags.resuming) { + cdev->private->state = state; + cdev->private->flags.recog_done = 1; + wake_up(&cdev->private->wait_q); + return; + } switch (state) { case DEV_STATE_NOT_OPER: CIO_MSG_EVENT(2, "SenseID : unknown device %04x on " @@ -273,34 +269,31 @@ ccw_device_recog_done(struct ccw_device *cdev, int state) sch->schid.ssid, sch->schid.sch_no); break; case DEV_STATE_OFFLINE: - if (cdev->online) { - same_dev = ccw_device_handle_oper(cdev); - notify = 1; + if (!cdev->online) { + ccw_device_update_sense_data(cdev); + /* Issue device info message. */ + CIO_MSG_EVENT(4, "SenseID : device 0.%x.%04x reports: " + "CU Type/Mod = %04X/%02X, Dev Type/Mod " + "= %04X/%02X\n", + cdev->private->dev_id.ssid, + cdev->private->dev_id.devno, + cdev->id.cu_type, cdev->id.cu_model, + cdev->id.dev_type, cdev->id.dev_model); + break; } - /* fill out sense information */ - memset(&cdev->id, 0, sizeof(cdev->id)); - cdev->id.cu_type = cdev->private->senseid.cu_type; - cdev->id.cu_model = cdev->private->senseid.cu_model; - cdev->id.dev_type = cdev->private->senseid.dev_type; - cdev->id.dev_model = cdev->private->senseid.dev_model; - if (notify) { - cdev->private->state = DEV_STATE_OFFLINE; - if (same_dev) { - /* Get device online again. */ - ccw_device_online(cdev); - wake_up(&cdev->private->wait_q); - } - return; + cdev->private->state = DEV_STATE_OFFLINE; + cdev->private->flags.recog_done = 1; + if (ccw_device_test_sense_data(cdev)) { + cdev->private->flags.donotify = 1; + ccw_device_online(cdev); + wake_up(&cdev->private->wait_q); + } else { + 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); } - /* Issue device info message. */ - CIO_MSG_EVENT(4, "SenseID : device 0.%x.%04x reports: " - "CU Type/Mod = %04X/%02X, Dev Type/Mod = " - "%04X/%02X\n", - cdev->private->dev_id.ssid, - cdev->private->dev_id.devno, - cdev->id.cu_type, cdev->id.cu_model, - cdev->id.dev_type, cdev->id.dev_model); - break; + return; case DEV_STATE_BOXED: CIO_MSG_EVENT(0, "SenseID : boxed device %04x on " " subchannel 0.%x.%04x\n", @@ -502,9 +495,6 @@ ccw_device_recognition(struct ccw_device *cdev) struct subchannel *sch; int ret; - if ((cdev->private->state != DEV_STATE_NOT_OPER) && - (cdev->private->state != DEV_STATE_BOXED)) - return -EINVAL; sch = to_subchannel(cdev->dev.parent); ret = cio_enable_subchannel(sch, (u32)(addr_t)sch); if (ret != 0) diff --git a/drivers/s390/cio/device_ops.c b/drivers/s390/cio/device_ops.c index bf0a24af39a0..2d0efee8a290 100644 --- a/drivers/s390/cio/device_ops.c +++ b/drivers/s390/cio/device_ops.c @@ -1,10 +1,8 @@ /* - * drivers/s390/cio/device_ops.c + * Copyright IBM Corp. 2002, 2009 * - * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, - * IBM Corporation - * Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com) - * Cornelia Huck (cornelia.huck@de.ibm.com) + * Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com) + * Cornelia Huck (cornelia.huck@de.ibm.com) */ #include #include @@ -116,12 +114,15 @@ int ccw_device_clear(struct ccw_device *cdev, unsigned long intparm) if (!cdev || !cdev->dev.parent) return -ENODEV; + sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.ena) + return -EINVAL; if (cdev->private->state == DEV_STATE_NOT_OPER) return -ENODEV; if (cdev->private->state != DEV_STATE_ONLINE && cdev->private->state != DEV_STATE_W4SENSE) return -EINVAL; - sch = to_subchannel(cdev->dev.parent); + ret = cio_clear(sch); if (ret == 0) cdev->private->intparm = intparm; @@ -162,6 +163,8 @@ int ccw_device_start_key(struct ccw_device *cdev, struct ccw1 *cpa, if (!cdev || !cdev->dev.parent) return -ENODEV; sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.ena) + return -EINVAL; if (cdev->private->state == DEV_STATE_NOT_OPER) return -ENODEV; if (cdev->private->state == DEV_STATE_VERIFY || @@ -337,12 +340,15 @@ int ccw_device_halt(struct ccw_device *cdev, unsigned long intparm) if (!cdev || !cdev->dev.parent) return -ENODEV; + sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.ena) + return -EINVAL; if (cdev->private->state == DEV_STATE_NOT_OPER) return -ENODEV; if (cdev->private->state != DEV_STATE_ONLINE && cdev->private->state != DEV_STATE_W4SENSE) return -EINVAL; - sch = to_subchannel(cdev->dev.parent); + ret = cio_halt(sch); if (ret == 0) cdev->private->intparm = intparm; @@ -369,6 +375,8 @@ int ccw_device_resume(struct ccw_device *cdev) if (!cdev || !cdev->dev.parent) return -ENODEV; sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.ena) + return -EINVAL; if (cdev->private->state == DEV_STATE_NOT_OPER) return -ENODEV; if (cdev->private->state != DEV_STATE_ONLINE || @@ -580,6 +588,8 @@ int ccw_device_tm_start_key(struct ccw_device *cdev, struct tcw *tcw, int rc; sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.ena) + return -EINVAL; if (cdev->private->state != DEV_STATE_ONLINE) return -EIO; /* Adjust requested path mask to excluded varied off paths. */ @@ -669,6 +679,8 @@ int ccw_device_tm_intrg(struct ccw_device *cdev) { struct subchannel *sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.ena) + return -EINVAL; if (cdev->private->state != DEV_STATE_ONLINE) return -EIO; if (!scsw_is_tm(&sch->schib.scsw) || diff --git a/drivers/s390/cio/io_sch.h b/drivers/s390/cio/io_sch.h index c4f3e7c9a854..0b8f381bd20e 100644 --- a/drivers/s390/cio/io_sch.h +++ b/drivers/s390/cio/io_sch.h @@ -107,6 +107,7 @@ struct ccw_device_private { unsigned int recog_done:1; /* dev. recog. complete */ unsigned int fake_irb:1; /* deliver faked irb */ unsigned int intretry:1; /* retry internal operation */ + unsigned int resuming:1; /* recognition while resume */ } __attribute__((packed)) flags; unsigned long intparm; /* user interruption parameter */ struct qdio_irq *qdio_data; -- cgit v1.2.3