+++ /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 */