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

Commit d7b5a4c9 authored by Cornelia Huck's avatar Cornelia Huck Committed by Martin Schwidefsky
Browse files

[S390] Support for disconnected devices reappearing on another subchannel.



- create a 'pseudo_subchannel' per channel subsystem (the 'orphanage')
- use the orphanage as a shelter for ccw_devices that can't remain on the same
  subchannel

Signed-off-by: default avatarCornelia Huck <cornelia.huck@de.ibm.com>
Cc: Greg KH <greg@kroah.com>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarMartin Schwidefsky <schwidefsky@de.ibm.com>
parent 2ec22984
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -415,6 +415,8 @@ cio_enable_subchannel (struct subchannel *sch, unsigned int isc)
	CIO_TRACE_EVENT (2, "ensch");
	CIO_TRACE_EVENT (2, sch->dev.bus_id);

	if (sch_is_pseudo_sch(sch))
		return -EINVAL;
	ccode = stsch (sch->schid, &sch->schib);
	if (ccode)
		return -ENODEV;
@@ -462,6 +464,8 @@ cio_disable_subchannel (struct subchannel *sch)
	CIO_TRACE_EVENT (2, "dissch");
	CIO_TRACE_EVENT (2, sch->dev.bus_id);

	if (sch_is_pseudo_sch(sch))
		return 0;
	ccode = stsch (sch->schid, &sch->schib);
	if (ccode == 3)		/* Not operational. */
		return -ENODEV;
@@ -496,7 +500,7 @@ cio_disable_subchannel (struct subchannel *sch)
	return ret;
}

static int cio_create_sch_lock(struct subchannel *sch)
int cio_create_sch_lock(struct subchannel *sch)
{
	sch->lock = kmalloc(sizeof(spinlock_t), GFP_KERNEL);
	if (!sch->lock)
+2 −0
Original line number Diff line number Diff line
@@ -131,6 +131,8 @@ extern int cio_set_options (struct subchannel *, int);
extern int cio_get_options (struct subchannel *);
extern int cio_modify (struct subchannel *);

int cio_create_sch_lock(struct subchannel *);

/* Use with care. */
#ifdef CONFIG_CCW_CONSOLE
extern struct subchannel *cio_probe_console(void);
+33 −4
Original line number Diff line number Diff line
@@ -580,12 +580,24 @@ css_cm_enable_store(struct device *dev, struct device_attribute *attr,

static DEVICE_ATTR(cm_enable, 0644, css_cm_enable_show, css_cm_enable_store);

static inline void __init
setup_css(int nr)
static inline int __init setup_css(int nr)
{
	u32 tod_high;
	int ret;

	memset(css[nr], 0, sizeof(struct channel_subsystem));
	css[nr]->pseudo_subchannel =
		kzalloc(sizeof(*css[nr]->pseudo_subchannel), GFP_KERNEL);
	if (!css[nr]->pseudo_subchannel)
		return -ENOMEM;
	css[nr]->pseudo_subchannel->dev.parent = &css[nr]->device;
	css[nr]->pseudo_subchannel->dev.release = css_subchannel_release;
	sprintf(css[nr]->pseudo_subchannel->dev.bus_id, "defunct");
	ret = cio_create_sch_lock(css[nr]->pseudo_subchannel);
	if (ret) {
		kfree(css[nr]->pseudo_subchannel);
		return ret;
	}
	mutex_init(&css[nr]->mutex);
	css[nr]->valid = 1;
	css[nr]->cssid = nr;
@@ -593,6 +605,7 @@ setup_css(int nr)
	css[nr]->device.release = channel_subsystem_release;
	tod_high = (u32) (get_clock() >> 32);
	css_generate_pgid(css[nr], tod_high);
	return 0;
}

/*
@@ -629,10 +642,12 @@ init_channel_subsystem (void)
			ret = -ENOMEM;
			goto out_unregister;
		}
		setup_css(i);
		ret = device_register(&css[i]->device);
		ret = setup_css(i);
		if (ret)
			goto out_free;
		ret = device_register(&css[i]->device);
		if (ret)
			goto out_free_all;
		if (css_characteristics_avail &&
		    css_chsc_characteristics.secm) {
			ret = device_create_file(&css[i]->device,
@@ -640,6 +655,9 @@ init_channel_subsystem (void)
			if (ret)
				goto out_device;
		}
		ret = device_register(&css[i]->pseudo_subchannel->dev);
		if (ret)
			goto out_file;
	}
	css_init_done = 1;

@@ -647,13 +665,19 @@ init_channel_subsystem (void)

	for_each_subchannel(__init_channel_subsystem, NULL);
	return 0;
out_file:
	device_remove_file(&css[i]->device, &dev_attr_cm_enable);
out_device:
	device_unregister(&css[i]->device);
out_free_all:
	kfree(css[i]->pseudo_subchannel->lock);
	kfree(css[i]->pseudo_subchannel);
out_free:
	kfree(css[i]);
out_unregister:
	while (i > 0) {
		i--;
		device_unregister(&css[i]->pseudo_subchannel->dev);
		if (css_characteristics_avail && css_chsc_characteristics.secm)
			device_remove_file(&css[i]->device,
					   &dev_attr_cm_enable);
@@ -665,6 +689,11 @@ init_channel_subsystem (void)
	return ret;
}

int sch_is_pseudo_sch(struct subchannel *sch)
{
	return sch == to_css(sch->dev.parent)->pseudo_subchannel;
}

/*
 * find a driver for a subchannel. They identify by the subchannel
 * type with the exception that the console subchannel driver has its own
+4 −0
Original line number Diff line number Diff line
@@ -160,6 +160,8 @@ struct channel_subsystem {
	int cm_enabled;
	void *cub_addr1;
	void *cub_addr2;
	/* for orphaned ccw devices */
	struct subchannel *pseudo_subchannel;
};
#define to_css(dev) container_of(dev, struct channel_subsystem, device)

@@ -187,6 +189,8 @@ void css_clear_subchannel_slow_list(void);
int css_slow_subchannels_exist(void);
extern int need_rescan;

int sch_is_pseudo_sch(struct subchannel *);

extern struct workqueue_struct *slow_path_wq;
extern struct work_struct slow_path_work;

+237 −50
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@
#include <asm/param.h>		/* HZ */

#include "cio.h"
#include "cio_debug.h"
#include "css.h"
#include "device.h"
#include "ioasm.h"
@@ -294,6 +295,11 @@ online_show (struct device *dev, struct device_attribute *attr, char *buf)
	return sprintf(buf, cdev->online ? "1\n" : "0\n");
}

int ccw_device_is_orphan(struct ccw_device *cdev)
{
	return sch_is_pseudo_sch(to_subchannel(cdev->dev.parent));
}

static void ccw_device_unregister(struct work_struct *work)
{
	struct ccw_device_private *priv;
@@ -310,10 +316,23 @@ static void
ccw_device_remove_disconnected(struct ccw_device *cdev)
{
	struct subchannel *sch;
	unsigned long flags;
	/*
	 * Forced offline in disconnected state means
	 * 'throw away device'.
	 */
	if (ccw_device_is_orphan(cdev)) {
		/* Deregister ccw device. */
		spin_lock_irqsave(cdev->ccwlock, flags);
		cdev->private->state = DEV_STATE_NOT_OPER;
		spin_unlock_irqrestore(cdev->ccwlock, flags);
		if (get_device(&cdev->dev)) {
			PREPARE_WORK(&cdev->private->kick_work,
				     ccw_device_unregister);
			queue_work(ccw_device_work, &cdev->private->kick_work);
		}
		return ;
	}
	sch = to_subchannel(cdev->dev.parent);
	css_sch_device_unregister(sch);
	/* Reset intparm to zeroes. */
@@ -474,6 +493,8 @@ available_show (struct device *dev, struct device_attribute *attr, char *buf)
	struct ccw_device *cdev = to_ccwdev(dev);
	struct subchannel *sch;

	if (ccw_device_is_orphan(cdev))
		return sprintf(buf, "no device\n");
	switch (cdev->private->state) {
	case DEV_STATE_BOXED:
		return sprintf(buf, "boxed\n");
@@ -574,11 +595,10 @@ match_devno(struct device * dev, void * data)

	cdev = to_ccwdev(dev);
	if ((cdev->private->state == DEV_STATE_DISCONNECTED) &&
	    !ccw_device_is_orphan(cdev) &&
	    ccw_dev_id_is_equal(&cdev->private->dev_id, &d->dev_id) &&
	    (cdev != d->sibling)) {
		cdev->private->state = DEV_STATE_NOT_OPER;
	    (cdev != d->sibling))
		return 1;
	}
	return 0;
}

@@ -595,6 +615,28 @@ static struct ccw_device * get_disc_ccwdev_by_dev_id(struct ccw_dev_id *dev_id,
	return dev ? to_ccwdev(dev) : NULL;
}

static int match_orphan(struct device *dev, void *data)
{
	struct ccw_dev_id *dev_id;
	struct ccw_device *cdev;

	dev_id = data;
	cdev = to_ccwdev(dev);
	return ccw_dev_id_is_equal(&cdev->private->dev_id, dev_id);
}

static struct ccw_device *
get_orphaned_ccwdev_by_dev_id(struct channel_subsystem *css,
			      struct ccw_dev_id *dev_id)
{
	struct device *dev;

	dev = device_find_child(&css->pseudo_subchannel->dev, dev_id,
				match_orphan);

	return dev ? to_ccwdev(dev) : NULL;
}

static void
ccw_device_add_changed(struct work_struct *work)
{
@@ -614,64 +656,19 @@ ccw_device_add_changed(struct work_struct *work)
	}
}

extern int css_get_ssd_info(struct subchannel *sch);

void
ccw_device_do_unreg_rereg(struct work_struct *work)
void ccw_device_do_unreg_rereg(struct work_struct *work)
{
	struct ccw_device_private *priv;
	struct ccw_device *cdev;
	struct subchannel *sch;
	int need_rename;

	priv = container_of(work, struct ccw_device_private, kick_work);
	cdev = priv->cdev;
	sch = to_subchannel(cdev->dev.parent);
	if (cdev->private->dev_id.devno != sch->schib.pmcw.dev) {
		/*
		 * The device number has changed. This is usually only when
		 * a device has been detached under VM and then re-appeared
		 * on another subchannel because of a different attachment
		 * order than before. Ideally, we should should just switch
		 * subchannels, but unfortunately, this is not possible with
		 * the current implementation.
		 * Instead, we search for the old subchannel for this device
		 * number and deregister so there are no collisions with the
		 * newly registered ccw_device.
		 * FIXME: Find another solution so the block layer doesn't
		 *        get possibly sick...
		 */
		struct ccw_device *other_cdev;
		struct ccw_dev_id dev_id;

		need_rename = 1;
		dev_id.devno = sch->schib.pmcw.dev;
		dev_id.ssid = sch->schid.ssid;
		other_cdev = get_disc_ccwdev_by_dev_id(&dev_id, cdev);
		if (other_cdev) {
			struct subchannel *other_sch;

			other_sch = to_subchannel(other_cdev->dev.parent);
			if (get_device(&other_sch->dev)) {
				stsch(other_sch->schid, &other_sch->schib);
				if (other_sch->schib.pmcw.dnv) {
					other_sch->schib.pmcw.intparm = 0;
					cio_modify(other_sch);
				}
				css_sch_device_unregister(other_sch);
			}
		}
		/* Update ssd info here. */
		css_get_ssd_info(sch);
		cdev->private->dev_id.devno = sch->schib.pmcw.dev;
	} else
		need_rename = 0;
	device_remove_files(&cdev->dev);
	if (test_and_clear_bit(1, &cdev->private->registered))
		device_del(&cdev->dev);
	if (need_rename)
		snprintf (cdev->dev.bus_id, BUS_ID_SIZE, "0.%x.%04x",
			  sch->schid.ssid, sch->schib.pmcw.dev);
	PREPARE_WORK(&cdev->private->kick_work,
		     ccw_device_add_changed);
	queue_work(ccw_device_work, &cdev->private->kick_work);
@@ -736,6 +733,131 @@ static struct ccw_device * io_subchannel_create_ccwdev(struct subchannel *sch)
	return cdev;
}

static int io_subchannel_recog(struct ccw_device *, struct subchannel *);

static void sch_attach_device(struct subchannel *sch,
			      struct ccw_device *cdev)
{
	spin_lock_irq(sch->lock);
	sch->dev.driver_data = cdev;
	cdev->private->schid = sch->schid;
	cdev->ccwlock = sch->lock;
	device_trigger_reprobe(sch);
	spin_unlock_irq(sch->lock);
}

static void sch_attach_disconnected_device(struct subchannel *sch,
					   struct ccw_device *cdev)
{
	struct subchannel *other_sch;
	int ret;

	other_sch = to_subchannel(get_device(cdev->dev.parent));
	ret = device_move(&cdev->dev, &sch->dev);
	if (ret) {
		CIO_MSG_EVENT(2, "Moving disconnected device 0.%x.%04x failed "
			      "(ret=%d)!\n", cdev->private->dev_id.ssid,
			      cdev->private->dev_id.devno, ret);
		put_device(&other_sch->dev);
		return;
	}
	other_sch->dev.driver_data = NULL;
	/* No need to keep a subchannel without ccw device around. */
	css_sch_device_unregister(other_sch);
	put_device(&other_sch->dev);
	sch_attach_device(sch, cdev);
}

static void sch_attach_orphaned_device(struct subchannel *sch,
				       struct ccw_device *cdev)
{
	int ret;

	/* Try to move the ccw device to its new subchannel. */
	ret = device_move(&cdev->dev, &sch->dev);
	if (ret) {
		CIO_MSG_EVENT(0, "Moving device 0.%x.%04x from orphanage "
			      "failed (ret=%d)!\n",
			      cdev->private->dev_id.ssid,
			      cdev->private->dev_id.devno, ret);
		return;
	}
	sch_attach_device(sch, cdev);
}

static void sch_create_and_recog_new_device(struct subchannel *sch)
{
	struct ccw_device *cdev;

	/* Need to allocate a new ccw device. */
	cdev = io_subchannel_create_ccwdev(sch);
	if (IS_ERR(cdev)) {
		/* OK, we did everything we could... */
		css_sch_device_unregister(sch);
		return;
	}
	spin_lock_irq(sch->lock);
	sch->dev.driver_data = cdev;
	spin_unlock_irq(sch->lock);
	/* Start recognition for the new ccw device. */
	if (io_subchannel_recog(cdev, sch)) {
		spin_lock_irq(sch->lock);
		sch->dev.driver_data = NULL;
		spin_unlock_irq(sch->lock);
		if (cdev->dev.release)
			cdev->dev.release(&cdev->dev);
		css_sch_device_unregister(sch);
	}
}


void ccw_device_move_to_orphanage(struct work_struct *work)
{
	struct ccw_device_private *priv;
	struct ccw_device *cdev;
	struct ccw_device *replacing_cdev;
	struct subchannel *sch;
	int ret;
	struct channel_subsystem *css;
	struct ccw_dev_id dev_id;

	priv = container_of(work, struct ccw_device_private, kick_work);
	cdev = priv->cdev;
	sch = to_subchannel(cdev->dev.parent);
	css = to_css(sch->dev.parent);
	dev_id.devno = sch->schib.pmcw.dev;
	dev_id.ssid = sch->schid.ssid;

	/*
	 * Move the orphaned ccw device to the orphanage so the replacing
	 * ccw device can take its place on the subchannel.
	 */
	ret = device_move(&cdev->dev, &css->pseudo_subchannel->dev);
	if (ret) {
		CIO_MSG_EVENT(0, "Moving device 0.%x.%04x to orphanage failed "
			      "(ret=%d)!\n", cdev->private->dev_id.ssid,
			      cdev->private->dev_id.devno, ret);
		return;
	}
	cdev->ccwlock = css->pseudo_subchannel->lock;
	/*
	 * Search for the replacing ccw device
	 * - among the disconnected devices
	 * - in the orphanage
	 */
	replacing_cdev = get_disc_ccwdev_by_dev_id(&dev_id, cdev);
	if (replacing_cdev) {
		sch_attach_disconnected_device(sch, replacing_cdev);
		return;
	}
	replacing_cdev = get_orphaned_ccwdev_by_dev_id(css, &dev_id);
	if (replacing_cdev) {
		sch_attach_orphaned_device(sch, replacing_cdev);
		return;
	}
	sch_create_and_recog_new_device(sch);
}

/*
 * Register recognized device.
 */
@@ -890,12 +1012,55 @@ io_subchannel_recog(struct ccw_device *cdev, struct subchannel *sch)
	return rc;
}

static void ccw_device_move_to_sch(struct work_struct *work)
{
	struct ccw_device_private *priv;
	int rc;
	struct subchannel *sch;
	struct ccw_device *cdev;
	struct subchannel *former_parent;

	priv = container_of(work, struct ccw_device_private, kick_work);
	sch = priv->sch;
	cdev = priv->cdev;
	former_parent = ccw_device_is_orphan(cdev) ?
		NULL : to_subchannel(get_device(cdev->dev.parent));
	mutex_lock(&sch->reg_mutex);
	/* Try to move the ccw device to its new subchannel. */
	rc = device_move(&cdev->dev, &sch->dev);
	mutex_unlock(&sch->reg_mutex);
	if (rc) {
		CIO_MSG_EVENT(2, "Moving device 0.%x.%04x to subchannel "
			      "0.%x.%04x failed (ret=%d)!\n",
			      cdev->private->dev_id.ssid,
			      cdev->private->dev_id.devno, sch->schid.ssid,
			      sch->schid.sch_no, rc);
		css_sch_device_unregister(sch);
		goto out;
	}
	if (former_parent) {
		spin_lock_irq(former_parent->lock);
		former_parent->dev.driver_data = NULL;
		spin_unlock_irq(former_parent->lock);
		css_sch_device_unregister(former_parent);
		/* Reset intparm to zeroes. */
		former_parent->schib.pmcw.intparm = 0;
		cio_modify(former_parent);
	}
	sch_attach_device(sch, cdev);
out:
	if (former_parent)
		put_device(&former_parent->dev);
	put_device(&cdev->dev);
}

static int
io_subchannel_probe (struct subchannel *sch)
{
	struct ccw_device *cdev;
	int rc;
	unsigned long flags;
	struct ccw_dev_id dev_id;

	if (sch->dev.driver_data) {
		/*
@@ -918,6 +1083,28 @@ io_subchannel_probe (struct subchannel *sch)
			get_device(&cdev->dev);
		return 0;
	}
	/*
	 * First check if a fitting device may be found amongst the
	 * disconnected devices or in the orphanage.
	 */
	dev_id.devno = sch->schib.pmcw.dev;
	dev_id.ssid = sch->schid.ssid;
	cdev = get_disc_ccwdev_by_dev_id(&dev_id, NULL);
	if (!cdev)
		cdev = get_orphaned_ccwdev_by_dev_id(to_css(sch->dev.parent),
						     &dev_id);
	if (cdev) {
		/*
		 * Schedule moving the device until when we have a registered
		 * subchannel to move to and succeed the probe. We can
		 * unregister later again, when the probe is through.
		 */
		cdev->private->sch = sch;
		PREPARE_WORK(&cdev->private->kick_work,
			     ccw_device_move_to_sch);
		queue_work(slow_path_wq, &cdev->private->kick_work);
		return 0;
	}
	cdev = io_subchannel_create_ccwdev(sch);
	if (IS_ERR(cdev))
		return PTR_ERR(cdev);
Loading