[S390] cio: fix quiesce state
authorSebastian Ott <sebott@linux.vnet.ibm.com>
Mon, 7 Dec 2009 11:51:35 +0000 (12:51 +0100)
committerMartin Schwidefsky <sky@mschwide.boeblingen.de.ibm.com>
Mon, 7 Dec 2009 11:51:32 +0000 (12:51 +0100)
DEV_STATE_QUIESCE is used to stop all IO on a busy subchannel.
This patch fixes the following problems related to the QUIESCE
state:

* Fix a potential race condition which could occur when the
resulting state was DEV_STATE_OFFLINE.

* Add missing locking around cio_disable_subchannel,
ccw_device_cancel_halt_clear and the cdev's handler.

* Loop until we know for sure that the subchannel is disabled.

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

index e8cb99a63cc61f5bc7140f8592d8e8c4245772c8..2b50f93b7fef4b12644e038804a5369f8bf3408c 100644 (file)
@@ -1130,33 +1130,36 @@ static int io_subchannel_chp_event(struct subchannel *sch,
        return 0;
 }
 
-static void
-io_subchannel_shutdown(struct subchannel *sch)
+static void io_subchannel_shutdown(struct subchannel *sch)
 {
        struct ccw_device *cdev;
        int ret;
 
+       spin_lock_irq(sch->lock);
        cdev = sch_get_cdev(sch);
-
        if (cio_is_console(sch->schid))
-               return;
+               goto out_unlock;
        if (!sch->schib.pmcw.ena)
-               /* Nothing to do. */
-               return;
+               goto out_unlock;
        ret = cio_disable_subchannel(sch);
        if (ret != -EBUSY)
-               /* Subchannel is disabled, we're done. */
-               return;
-       cdev->private->state = DEV_STATE_QUIESCE;
+               goto out_unlock;
        if (cdev->handler)
-               cdev->handler(cdev, cdev->private->intparm,
-                             ERR_PTR(-EIO));
-       ret = ccw_device_cancel_halt_clear(cdev);
-       if (ret == -EBUSY) {
-               ccw_device_set_timeout(cdev, HZ/10);
-               wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev));
+               cdev->handler(cdev, cdev->private->intparm, ERR_PTR(-EIO));
+       while (ret == -EBUSY) {
+               cdev->private->state = DEV_STATE_QUIESCE;
+               ret = ccw_device_cancel_halt_clear(cdev);
+               if (ret == -EBUSY) {
+                       ccw_device_set_timeout(cdev, HZ/10);
+                       spin_unlock_irq(sch->lock);
+                       wait_event(cdev->private->wait_q,
+                                  cdev->private->state != DEV_STATE_QUIESCE);
+                       spin_lock_irq(sch->lock);
+               }
+               ret = cio_disable_subchannel(sch);
        }
-       cio_disable_subchannel(sch);
+out_unlock:
+       spin_unlock_irq(sch->lock);
 }
 
 static int device_is_disconnected(struct ccw_device *cdev)
index 7d42417bc2c7f8d89938bca37a08588a4a014448..862b89ef3ee6b54be1c376af4f143096749b28c6 100644 (file)
@@ -911,10 +911,7 @@ static void
 ccw_device_quiesce_done(struct ccw_device *cdev, enum dev_event dev_event)
 {
        ccw_device_set_timeout(cdev, 0);
-       if (dev_event == DEV_EVENT_NOTOPER)
-               cdev->private->state = DEV_STATE_NOT_OPER;
-       else
-               cdev->private->state = DEV_STATE_OFFLINE;
+       cdev->private->state = DEV_STATE_NOT_OPER;
        wake_up(&cdev->private->wait_q);
 }
 
@@ -924,17 +921,11 @@ ccw_device_quiesce_timeout(struct ccw_device *cdev, enum dev_event dev_event)
        int ret;
 
        ret = ccw_device_cancel_halt_clear(cdev);
-       switch (ret) {
-       case 0:
-               cdev->private->state = DEV_STATE_OFFLINE;
-               wake_up(&cdev->private->wait_q);
-               break;
-       case -ENODEV:
+       if (ret == -EBUSY) {
+               ccw_device_set_timeout(cdev, HZ/10);
+       } else {
                cdev->private->state = DEV_STATE_NOT_OPER;
                wake_up(&cdev->private->wait_q);
-               break;
-       default:
-               ccw_device_set_timeout(cdev, HZ/10);
        }
 }