--- /dev/null
+From: Gerald Schaefer <geraldsc@de.ibm.com>
+Subject: cio: Crashes when repeatetly attaching/detaching devices.
+References: bnc#458339
+
+Symptom: Oops in dmesg after attaching/detaching a device, subsequent
+ calls to lscss hang.
+Problem: Incorrect reference counting of subchannel in relation to
+ ccw devices, missing check for delayed registering of ccw
+ devices and incorrectly failing the probe function for I/O
+ subchannels (which leads to unbound subchannels that can't
+ be unregistered).
+Solution: Make sure that the ccw device holds a reference of the
+ subchannel, that it is not registered if the subchannel is
+ not registered anymore, and schedule unregistering an I/O
+ subchannel if probing encounters an error.
+
+Acked-by: John Jolly <jjolly@suse.de>
+---
+ drivers/s390/cio/cio.h | 1
+ drivers/s390/cio/device.c | 128 ++++++++++++++++++++++++++++++++++------------
+ 2 files changed, 98 insertions(+), 31 deletions(-)
+
+--- linux-sles11.orig/drivers/s390/cio/device.c
++++ linux-sles11/drivers/s390/cio/device.c
+@@ -716,6 +716,8 @@ ccw_device_release(struct device *dev)
+ struct ccw_device *cdev;
+
+ cdev = to_ccwdev(dev);
++ /* Release reference of parent subchannel. */
++ put_device(cdev->dev.parent);
+ kfree(cdev->private);
+ kfree(cdev);
+ }
+@@ -790,37 +792,55 @@ static void sch_attach_disconnected_devi
+ struct subchannel *other_sch;
+ int ret;
+
+- other_sch = to_subchannel(get_device(cdev->dev.parent));
++ /* Get reference for new parent. */
++ if (!get_device(&sch->dev))
++ return;
++ other_sch = to_subchannel(cdev->dev.parent);
++ /* Note: device_move() changes cdev->dev.parent */
+ ret = device_move(&cdev->dev, &sch->dev);
+ if (ret) {
+ CIO_MSG_EVENT(0, "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);
++ /* Put reference for new parent. */
++ put_device(&sch->dev);
+ return;
+ }
+ sch_set_cdev(other_sch, 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);
++ /* Put reference for old parent. */
++ put_device(&other_sch->dev);
+ }
+
+ static void sch_attach_orphaned_device(struct subchannel *sch,
+ struct ccw_device *cdev)
+ {
+ int ret;
++ struct subchannel *pseudo_sch;
+
+- /* Try to move the ccw device to its new subchannel. */
++ /* Get reference for new parent. */
++ if (!get_device(&sch->dev))
++ return;
++ pseudo_sch = to_subchannel(cdev->dev.parent);
++ /*
++ * Try to move the ccw device to its new subchannel.
++ * Note: device_move() changes cdev->dev.parent
++ */
+ 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);
++ /* Put reference for new parent. */
++ put_device(&sch->dev);
+ return;
+ }
+ sch_attach_device(sch, cdev);
++ /* Put reference on pseudo subchannel. */
++ put_device(&pseudo_sch->dev);
+ }
+
+ static void sch_create_and_recog_new_device(struct subchannel *sch)
+@@ -842,9 +862,11 @@ static void sch_create_and_recog_new_dev
+ spin_lock_irq(sch->lock);
+ sch_set_cdev(sch, NULL);
+ spin_unlock_irq(sch->lock);
+- if (cdev->dev.release)
+- cdev->dev.release(&cdev->dev);
+ css_sch_device_unregister(sch);
++ /* Put reference from io_subchannel_create_ccwdev(). */
++ put_device(&sch->dev);
++ /* Give up initial reference. */
++ put_device(&cdev->dev);
+ }
+ }
+
+@@ -866,15 +888,20 @@ void ccw_device_move_to_orphanage(struct
+ dev_id.devno = sch->schib.pmcw.dev;
+ dev_id.ssid = sch->schid.ssid;
+
++ /* Increase refcount for pseudo subchannel. */
++ get_device(&css->pseudo_subchannel->dev);
+ /*
+ * Move the orphaned ccw device to the orphanage so the replacing
+ * ccw device can take its place on the subchannel.
++ * Note: device_move() changes cdev->dev.parent
+ */
+ 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);
++ /* Decrease refcount for pseudo subchannel again. */
++ put_device(&css->pseudo_subchannel->dev);
+ return;
+ }
+ cdev->ccwlock = css->pseudo_subchannel->lock;
+@@ -886,14 +913,20 @@ void ccw_device_move_to_orphanage(struct
+ replacing_cdev = get_disc_ccwdev_by_dev_id(&dev_id, cdev);
+ if (replacing_cdev) {
+ sch_attach_disconnected_device(sch, replacing_cdev);
++ /* Release reference of subchannel from old cdev. */
++ put_device(&sch->dev);
+ return;
+ }
+ replacing_cdev = get_orphaned_ccwdev_by_dev_id(css, &dev_id);
+ if (replacing_cdev) {
+ sch_attach_orphaned_device(sch, replacing_cdev);
++ /* Release reference of subchannel from old cdev. */
++ put_device(&sch->dev);
+ return;
+ }
+ sch_create_and_recog_new_device(sch);
++ /* Release reference of subchannel from old cdev. */
++ put_device(&sch->dev);
+ }
+
+ /*
+@@ -911,6 +944,14 @@ io_subchannel_register(struct work_struc
+ priv = container_of(work, struct ccw_device_private, kick_work);
+ cdev = priv->cdev;
+ sch = to_subchannel(cdev->dev.parent);
++ /*
++ * Check if subchannel is still registered. It may have become
++ * unregistered if a machine check hit us after finishing
++ * device recognition but before the register work could be
++ * queued.
++ */
++ if (!device_is_registered(&sch->dev))
++ goto out_err;
+ css_update_ssd_info(sch);
+ /*
+ * io_subchannel_register() will also be called after device
+@@ -942,22 +983,19 @@ io_subchannel_register(struct work_struc
+ CIO_MSG_EVENT(0, "Could not register ccw dev 0.%x.%04x: %d\n",
+ cdev->private->dev_id.ssid,
+ cdev->private->dev_id.devno, ret);
+- put_device(&cdev->dev);
+ spin_lock_irqsave(sch->lock, flags);
+ sch_set_cdev(sch, NULL);
+ spin_unlock_irqrestore(sch->lock, flags);
+- kfree (cdev->private);
+- kfree (cdev);
+- put_device(&sch->dev);
+- if (atomic_dec_and_test(&ccw_device_init_count))
+- wake_up(&ccw_device_init_wq);
+- return;
++ /* Release initial device reference. */
++ put_device(&cdev->dev);
++ goto out_err;
+ }
+- put_device(&cdev->dev);
+ out:
+ cdev->private->flags.recog_done = 1;
+- put_device(&sch->dev);
+ wake_up(&cdev->private->wait_q);
++out_err:
++ /* Release reference for workqueue processing. */
++ put_device(&cdev->dev);
+ if (atomic_dec_and_test(&ccw_device_init_count))
+ wake_up(&ccw_device_init_wq);
+ }
+@@ -1068,10 +1106,15 @@ static void ccw_device_move_to_sch(struc
+ 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));
++ former_parent = to_subchannel(cdev->dev.parent);
++ /* Get reference for new parent. */
++ if (!get_device(&sch->dev))
++ return;
+ mutex_lock(&sch->reg_mutex);
+- /* Try to move the ccw device to its new subchannel. */
++ /*
++ * Try to move the ccw device to its new subchannel.
++ * Note: device_move() changes cdev->dev.parent
++ */
+ rc = device_move(&cdev->dev, &sch->dev);
+ mutex_unlock(&sch->reg_mutex);
+ if (rc) {
+@@ -1081,9 +1124,11 @@ static void ccw_device_move_to_sch(struc
+ cdev->private->dev_id.devno, sch->schid.ssid,
+ sch->schid.sch_no, rc);
+ css_sch_device_unregister(sch);
++ /* Put reference for new parent again. */
++ put_device(&sch->dev);
+ goto out;
+ }
+- if (former_parent) {
++ if (!sch_is_pseudo_sch(former_parent)) {
+ spin_lock_irq(former_parent->lock);
+ sch_set_cdev(former_parent, NULL);
+ spin_unlock_irq(former_parent->lock);
+@@ -1094,8 +1139,8 @@ static void ccw_device_move_to_sch(struc
+ }
+ sch_attach_device(sch, cdev);
+ out:
+- if (former_parent)
+- put_device(&former_parent->dev);
++ /* Put reference for old parent. */
++ put_device(&former_parent->dev);
+ put_device(&cdev->dev);
+ }
+
+@@ -1137,6 +1182,30 @@ static void io_subchannel_init_fields(st
+ sch->schib.mba = 0;
+ }
+
++static void io_subchannel_do_unreg(struct work_struct *work)
++{
++ struct subchannel *sch;
++
++ sch = container_of(work, struct subchannel, work);
++ css_sch_device_unregister(sch);
++ /* Reset intparm to zeroes. */
++ sch->schib.pmcw.intparm = 0;
++ cio_modify(sch);
++ put_device(&sch->dev);
++}
++
++/* Schedule unregister if we have no cdev. */
++static void io_subchannel_schedule_removal(struct subchannel *sch)
++{
++ get_device(&sch->dev);
++ INIT_WORK(&sch->work, io_subchannel_do_unreg);
++ queue_work(slow_path_wq, &sch->work);
++}
++
++/*
++ * Note: We always return 0 so that we bind to the device even on error.
++ * This is needed so that our remove function is called on unregister.
++ */
+ static int io_subchannel_probe(struct subchannel *sch)
+ {
+ struct ccw_device *cdev;
+@@ -1186,14 +1255,12 @@ static int io_subchannel_probe(struct su
+ rc = sysfs_create_group(&sch->dev.kobj,
+ &io_subchannel_attr_group);
+ if (rc)
+- return rc;
++ goto out_schedule;
+ /* Allocate I/O subchannel private data. */
+ sch->private = kzalloc(sizeof(struct io_subchannel_private),
+ GFP_KERNEL | GFP_DMA);
+- if (!sch->private) {
+- rc = -ENOMEM;
++ if (!sch->private)
+ goto out_err;
+- }
+ cdev = get_disc_ccwdev_by_dev_id(&dev_id, NULL);
+ if (!cdev)
+ cdev = get_orphaned_ccwdev_by_dev_id(to_css(sch->dev.parent),
+@@ -1211,24 +1278,23 @@ static int io_subchannel_probe(struct su
+ return 0;
+ }
+ cdev = io_subchannel_create_ccwdev(sch);
+- if (IS_ERR(cdev)) {
+- rc = PTR_ERR(cdev);
++ if (IS_ERR(cdev))
+ goto out_err;
+- }
+ rc = io_subchannel_recog(cdev, sch);
+ if (rc) {
+ spin_lock_irqsave(sch->lock, flags);
+ sch_set_cdev(sch, NULL);
++ io_subchannel_recog_done(cdev);
+ spin_unlock_irqrestore(sch->lock, flags);
+- if (cdev->dev.release)
+- cdev->dev.release(&cdev->dev);
+- goto out_err;
+ }
+ return 0;
+ out_err:
+ kfree(sch->private);
+ sysfs_remove_group(&sch->dev.kobj, &io_subchannel_attr_group);
+ return rc;
++out_schedule:
++ io_subchannel_schedule_removal(sch);
++ return 0;
+ }
+
+ static int
+--- linux-sles11.orig/drivers/s390/cio/cio.h
++++ linux-sles11/drivers/s390/cio/cio.h
+@@ -82,6 +82,7 @@ struct subchannel {
+ struct device dev; /* entry in device tree */
+ struct css_driver *driver;
+ void *private; /* private per subchannel type data */
++ struct work_struct work;
+ } __attribute__ ((aligned(8)));
+
+ #define IO_INTERRUPT_TYPE 0 /* I/O interrupt type */