[S390] cio: handle busy subchannel in ccw_device_move_to_sch
authorSebastian Ott <sebott@linux.vnet.ibm.com>
Mon, 7 Dec 2009 11:51:37 +0000 (12:51 +0100)
committerMartin Schwidefsky <sky@mschwide.boeblingen.de.ibm.com>
Mon, 7 Dec 2009 11:51:32 +0000 (12:51 +0100)
Try to disable the old subchannel before we ask the driver core
to move the attached device to a new parent. This way we can use
the QUIESCE state during shutdown which prevents a possible use
after free situation in some error cases.

Signed-off-by: Sebastian Ott <sebott@linux.vnet.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
drivers/s390/cio/device.c

index 2b50f93b7fef4b12644e038804a5369f8bf3408c..af500aac24ef9a83d5b509c24785d6cc720e0068 100644 (file)
@@ -892,12 +892,27 @@ static int ccw_device_move_to_sch(struct ccw_device *cdev,
                                  struct subchannel *sch)
 {
        struct subchannel *old_sch;
-       int rc;
+       int rc, old_enabled = 0;
 
        old_sch = to_subchannel(cdev->dev.parent);
        /* Obtain child reference for new parent. */
        if (!get_device(&sch->dev))
                return -ENODEV;
+
+       if (!sch_is_pseudo_sch(old_sch)) {
+               spin_lock_irq(old_sch->lock);
+               old_enabled = old_sch->schib.pmcw.ena;
+               rc = 0;
+               if (old_enabled)
+                       rc = cio_disable_subchannel(old_sch);
+               spin_unlock_irq(old_sch->lock);
+               if (rc == -EBUSY) {
+                       /* Release child reference for new parent. */
+                       put_device(&sch->dev);
+                       return rc;
+               }
+       }
+
        mutex_lock(&sch->reg_mutex);
        rc = device_move(&cdev->dev, &sch->dev, DPM_ORDER_PARENT_BEFORE_DEV);
        mutex_unlock(&sch->reg_mutex);
@@ -906,6 +921,12 @@ static int ccw_device_move_to_sch(struct ccw_device *cdev,
                              cdev->private->dev_id.ssid,
                              cdev->private->dev_id.devno, sch->schid.ssid,
                              sch->schib.pmcw.dev, rc);
+               if (old_enabled) {
+                       /* Try to reenable the old subchannel. */
+                       spin_lock_irq(old_sch->lock);
+                       cio_enable_subchannel(old_sch, (u32)(addr_t)old_sch);
+                       spin_unlock_irq(old_sch->lock);
+               }
                /* Release child reference for new parent. */
                put_device(&sch->dev);
                return rc;
@@ -914,7 +935,6 @@ static int ccw_device_move_to_sch(struct ccw_device *cdev,
        if (!sch_is_pseudo_sch(old_sch)) {
                spin_lock_irq(old_sch->lock);
                sch_set_cdev(old_sch, NULL);
-               cio_disable_subchannel(old_sch);
                spin_unlock_irq(old_sch->lock);
                css_schedule_eval(old_sch->schid);
        }