Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 823d494a authored by Sebastian Ott's avatar Sebastian Ott Committed by Martin Schwidefsky
Browse files

[S390] pm: ccw bus power management callbacks

parent 03347e25
Loading
Loading
Loading
Loading
+13 −5
Original line number Diff line number Diff line
/*
 *  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>
 *
 * Interface for CCW device drivers
@@ -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;
};
+5 −0
Original line number Diff line number Diff line
@@ -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
+237 −0
Original line number Diff line number Diff line
@@ -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,
};

/**
+3 −0
Original line number Diff line number Diff line
@@ -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
+43 −53
Original line number Diff line number Diff line
@@ -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;

	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;
	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;
}
	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;
		}
		/* 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;
		}
		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",
				      "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;
		}
		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);
		}
		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)
Loading