Skip to content

Commit

Permalink
[S390] pm: ccw bus power management callbacks
Browse files Browse the repository at this point in the history
Signed-off-by: Sebastian Ott <sebott@linux.vnet.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
  • Loading branch information
Sebastian Ott authored and Martin Schwidefsky committed Jun 16, 2009
1 parent 03347e2 commit 823d494
Show file tree
Hide file tree
Showing 7 changed files with 321 additions and 65 deletions.
18 changes: 13 additions & 5 deletions arch/s390/include/asm/ccwdev.h
Original file line number Diff line number Diff line change
@@ -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 <arndb@de.ibm.com>
* Author(s): Arnd Bergmann <arndb@de.ibm.com>
*
* Interface for CCW device drivers
* Interface for CCW device drivers
*/
#ifndef _S390_CCWDEV_H_
#define _S390_CCWDEV_H_
Expand Down Expand Up @@ -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
*/
Expand All @@ -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;
};
Expand Down
5 changes: 5 additions & 0 deletions drivers/s390/cio/cmf.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
237 changes: 237 additions & 0 deletions drivers/s390/cio/device.c
Original file line number Diff line number Diff line change
Expand Up @@ -1895,13 +1895,250 @@ 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,
.uevent = ccw_uevent,
.probe = ccw_device_probe,
.remove = ccw_device_remove,
.shutdown = ccw_device_shutdown,
.pm = &ccw_pm_ops,
};

/**
Expand Down
3 changes: 3 additions & 0 deletions drivers/s390/cio/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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
Loading

0 comments on commit 823d494

Please sign in to comment.