]> git.ipfire.org Git - people/teissler/ipfire-2.x.git/blobdiff - src/patches/suse-2.6.27.31/patches.arch/s390-06-07-cio-attach_detach.patch
Move xen patchset to new version's subdir.
[people/teissler/ipfire-2.x.git] / src / patches / suse-2.6.27.31 / patches.arch / s390-06-07-cio-attach_detach.patch
diff --git a/src/patches/suse-2.6.27.31/patches.arch/s390-06-07-cio-attach_detach.patch b/src/patches/suse-2.6.27.31/patches.arch/s390-06-07-cio-attach_detach.patch
new file mode 100644 (file)
index 0000000..3c2a81a
--- /dev/null
@@ -0,0 +1,324 @@
+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 */