vserver 2.0 rc7
[linux-2.6.git] / drivers / s390 / cio / device_fsm.c
index cacebad..9b7f6f5 100644 (file)
 #include "ioasm.h"
 #include "qdio.h"
 
+int
+device_is_online(struct subchannel *sch)
+{
+       struct ccw_device *cdev;
+
+       if (!sch->dev.driver_data)
+               return 0;
+       cdev = sch->dev.driver_data;
+       return (cdev->private->state == DEV_STATE_ONLINE);
+}
+
 int
 device_is_disconnected(struct subchannel *sch)
 {
@@ -44,6 +55,7 @@ device_set_disconnected(struct subchannel *sch)
                return;
        cdev = sch->dev.driver_data;
        ccw_device_set_timeout(cdev, 0);
+       cdev->private->flags.fake_irb = 0;
        cdev->private->state = DEV_STATE_DISCONNECTED;
 }
 
@@ -474,6 +486,7 @@ ccw_device_nopath_notify(void *data)
        } else {
                cio_disable_subchannel(sch);
                ccw_device_set_timeout(cdev, 0);
+               cdev->private->flags.fake_irb = 0;
                cdev->private->state = DEV_STATE_DISCONNECTED;
                wake_up(&cdev->private->wait_q);
        }
@@ -488,6 +501,21 @@ ccw_device_verify_done(struct ccw_device *cdev, int err)
                cdev->private->options.pgroup = 0;
        case 0:
                ccw_device_done(cdev, DEV_STATE_ONLINE);
+               /* Deliver fake irb to device driver, if needed. */
+               if (cdev->private->flags.fake_irb) {
+                       memset(&cdev->private->irb, 0, sizeof(struct irb));
+                       cdev->private->irb.scsw = (struct scsw) {
+                               .cc = 1,
+                               .fctl = SCSW_FCTL_START_FUNC,
+                               .actl = SCSW_ACTL_START_PEND,
+                               .stctl = SCSW_STCTL_STATUS_PEND,
+                       };
+                       cdev->private->flags.fake_irb = 0;
+                       if (cdev->handler)
+                               cdev->handler(cdev, cdev->private->intparm,
+                                             &cdev->private->irb);
+                       memset(&cdev->private->irb, 0, sizeof(struct irb));
+               }
                break;
        case -ETIME:
                ccw_device_done(cdev, DEV_STATE_BOXED);
@@ -560,6 +588,8 @@ ccw_device_offline(struct ccw_device *cdev)
        struct subchannel *sch;
 
        sch = to_subchannel(cdev->dev.parent);
+       if (stsch(sch->irq, &sch->schib) || !sch->schib.pmcw.dnv)
+               return -ENODEV;
        if (cdev->private->state != DEV_STATE_ONLINE) {
                if (sch->schib.scsw.actl != 0)
                        return -EBUSY;
@@ -619,9 +649,11 @@ ccw_device_offline_notoper(struct ccw_device *cdev, enum dev_event dev_event)
 
        cdev->private->state = DEV_STATE_NOT_OPER;
        sch = to_subchannel(cdev->dev.parent);
-       device_unregister(&sch->dev);
-       sch->schib.pmcw.intparm = 0;
-       cio_modify(sch);
+       if (get_device(&cdev->dev)) {
+               PREPARE_WORK(&cdev->private->kick_work,
+                            ccw_device_call_sch_unregister, (void *)cdev);
+               queue_work(ccw_device_work, &cdev->private->kick_work);
+       }
        wake_up(&cdev->private->wait_q);
 }
 
@@ -637,6 +669,7 @@ ccw_device_online_notoper(struct ccw_device *cdev, enum dev_event dev_event)
        if (sch->driver->notify &&
            sch->driver->notify(&sch->dev, sch->lpm ? CIO_GONE : CIO_NO_PATH)) {
                        ccw_device_set_timeout(cdev, 0);
+                       cdev->private->flags.fake_irb = 0;
                        cdev->private->state = DEV_STATE_DISCONNECTED;
                        wake_up(&cdev->private->wait_q);
                        return;
@@ -647,9 +680,11 @@ ccw_device_online_notoper(struct ccw_device *cdev, enum dev_event dev_event)
                // FIXME: not-oper indication to device driver ?
                ccw_device_call_handler(cdev);
        }
-       device_unregister(&sch->dev);
-       sch->schib.pmcw.intparm = 0;
-       cio_modify(sch);
+       if (get_device(&cdev->dev)) {
+               PREPARE_WORK(&cdev->private->kick_work,
+                            ccw_device_call_sch_unregister, (void *)cdev);
+               queue_work(ccw_device_work, &cdev->private->kick_work);
+       }
        wake_up(&cdev->private->wait_q);
 }
 
@@ -668,6 +703,12 @@ ccw_device_online_verify(struct ccw_device *cdev, enum dev_event dev_event)
                return;
        }
        sch = to_subchannel(cdev->dev.parent);
+       /*
+        * Since we might not just be coming from an interrupt from the
+        * subchannel we have to update the schib.
+        */
+       stsch(sch->irq, &sch->schib);
+
        if (sch->schib.scsw.actl != 0 ||
            (cdev->private->irb.scsw.stctl & SCSW_STCTL_STATUS_PEND)) {
                /*
@@ -982,21 +1023,17 @@ void
 device_trigger_reprobe(struct subchannel *sch)
 {
        struct ccw_device *cdev;
-       unsigned long flags;
 
        if (!sch->dev.driver_data)
                return;
        cdev = sch->dev.driver_data;
-       spin_lock_irqsave(&sch->lock, flags);
-       if (cdev->private->state != DEV_STATE_DISCONNECTED) {
-               spin_unlock_irqrestore(&sch->lock, flags);
+       if (cdev->private->state != DEV_STATE_DISCONNECTED)
                return;
-       }
+
        /* Update some values. */
-       if (stsch(sch->irq, &sch->schib)) {
-               spin_unlock_irqrestore(&sch->lock, flags);
+       if (stsch(sch->irq, &sch->schib))
                return;
-       }
+
        /*
         * The pim, pam, pom values may not be accurate, but they are the best
         * we have before performing device selection :/
@@ -1014,7 +1051,6 @@ device_trigger_reprobe(struct subchannel *sch)
        sch->schib.pmcw.intparm = (__u32)(unsigned long)sch;
        /* We should also udate ssd info, but this has to wait. */
        ccw_device_start_id(cdev, 0);
-       spin_unlock_irqrestore(&sch->lock, flags);
 }
 
 static void