Fedora kernel-2.6.17-1.2142_FC4 patched with stable patch-2.6.17.4-vs2.0.2-rc26.diff
[linux-2.6.git] / drivers / s390 / cio / device_fsm.c
index 508f1a7..49ec562 100644 (file)
@@ -4,16 +4,18 @@
  *
  *    Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
  *                      IBM Corporation
- *    Author(s): Cornelia Huck(cohuck@de.ibm.com)
+ *    Author(s): Cornelia Huck (cornelia.huck@de.ibm.com)
  *              Martin Schwidefsky (schwidefsky@de.ibm.com)
  */
 
 #include <linux/module.h>
 #include <linux/config.h>
 #include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/string.h>
 
 #include <asm/ccwdev.h>
-#include <asm/qdio.h>
+#include <asm/cio.h>
 
 #include "cio.h"
 #include "cio_debug.h"
 #include "device.h"
 #include "chsc.h"
 #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 +56,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;
 }
 
@@ -93,6 +106,18 @@ ccw_device_set_timeout(struct ccw_device *cdev, int expires)
        add_timer(&cdev->private->timer);
 }
 
+/* Kill any pending timers after machine check. */
+void
+device_kill_pending_timer(struct subchannel *sch)
+{
+       struct ccw_device *cdev;
+
+       if (!sch->dev.driver_data)
+               return;
+       cdev = sch->dev.driver_data;
+       ccw_device_set_timeout(cdev, 0);
+}
+
 /*
  * Cancel running i/o. This is called repeatedly since halt/clear are
  * asynchronous operations. We do one try with cio_cancel, two tries
@@ -108,7 +133,7 @@ ccw_device_cancel_halt_clear(struct ccw_device *cdev)
        int ret;
 
        sch = to_subchannel(cdev->dev.parent);
-       ret = stsch(sch->irq, &sch->schib);
+       ret = stsch(sch->schid, &sch->schib);
        if (ret || !sch->schib.pmcw.dnv)
                return -ENODEV; 
        if (!sch->schib.pmcw.ena || sch->schib.scsw.actl == 0)
@@ -142,7 +167,7 @@ ccw_device_cancel_halt_clear(struct ccw_device *cdev)
        panic("Can't stop i/o on subchannel.\n");
 }
 
-static void
+static int
 ccw_device_handle_oper(struct ccw_device *cdev)
 {
        struct subchannel *sch;
@@ -162,11 +187,10 @@ ccw_device_handle_oper(struct ccw_device *cdev)
                PREPARE_WORK(&cdev->private->kick_work,
                             ccw_device_do_unreg_rereg, (void *)cdev);
                queue_work(ccw_device_work, &cdev->private->kick_work);
-               return;
+               return 0;
        }
        cdev->private->flags.donotify = 1;
-       /* Get device online again. */
-       ccw_device_online(cdev);
+       return 1;
 }
 
 /*
@@ -196,7 +220,7 @@ static void
 ccw_device_recog_done(struct ccw_device *cdev, int state)
 {
        struct subchannel *sch;
-       int notify, old_lpm;
+       int notify, old_lpm, same_dev;
 
        sch = to_subchannel(cdev->dev.parent);
 
@@ -207,11 +231,14 @@ ccw_device_recog_done(struct ccw_device *cdev, int state)
         * through ssch() and the path information is up to date.
         */
        old_lpm = sch->lpm;
-       stsch(sch->irq, &sch->schib);
+       stsch(sch->schid, &sch->schib);
        sch->lpm = sch->schib.pmcw.pim &
                sch->schib.pmcw.pam &
                sch->schib.pmcw.pom &
                sch->opm;
+       /* Check since device may again have become not operational. */
+       if (!sch->schib.pmcw.dnv)
+               state = DEV_STATE_NOT_OPER;
        if (cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID)
                /* Force reprobe on all chpids. */
                old_lpm = 0;
@@ -226,40 +253,52 @@ ccw_device_recog_done(struct ccw_device *cdev, int state)
                /* Boxed devices don't need extra treatment. */
        }
        notify = 0;
+       same_dev = 0; /* Keep the compiler quiet... */
        switch (state) {
        case DEV_STATE_NOT_OPER:
                CIO_DEBUG(KERN_WARNING, 2,
-                         "SenseID : unknown device %04x on subchannel %04x\n",
-                         cdev->private->devno, sch->irq);
+                         "SenseID : unknown device %04x on subchannel "
+                         "0.%x.%04x\n", cdev->private->devno,
+                         sch->schid.ssid, sch->schid.sch_no);
                break;
        case DEV_STATE_OFFLINE:
-               if (cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID)
+               if (cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID) {
+                       same_dev = ccw_device_handle_oper(cdev);
                        notify = 1;
-               else  /* fill out sense information */
-                       cdev->id = (struct ccw_device_id) {
-                               .cu_type   = cdev->private->senseid.cu_type,
-                               .cu_model  = cdev->private->senseid.cu_model,
-                               .dev_type  = cdev->private->senseid.dev_type,
-                               .dev_model = cdev->private->senseid.dev_model,
-                       };
+               }
+               /* fill out sense information */
+               cdev->id = (struct ccw_device_id) {
+                       .cu_type   = cdev->private->senseid.cu_type,
+                       .cu_model  = cdev->private->senseid.cu_model,
+                       .dev_type  = cdev->private->senseid.dev_type,
+                       .dev_model = cdev->private->senseid.dev_model,
+               };
+               if (notify) {
+                       cdev->private->state = DEV_STATE_OFFLINE;
+                       if (same_dev) {
+                               /* Get device online again. */
+                               ccw_device_online(cdev);
+                               wake_up(&cdev->private->wait_q);
+                       }
+                       return;
+               }
                /* Issue device info message. */
-               CIO_DEBUG(KERN_INFO, 2, "SenseID : device %04x reports: "
+               CIO_DEBUG(KERN_INFO, 2, "SenseID : device 0.%x.%04x reports: "
                          "CU  Type/Mod = %04X/%02X, Dev Type/Mod = "
-                         "%04X/%02X\n", cdev->private->devno,
+                         "%04X/%02X\n",
+                         cdev->private->ssid, cdev->private->devno,
                          cdev->id.cu_type, cdev->id.cu_model,
                          cdev->id.dev_type, cdev->id.dev_model);
                break;
        case DEV_STATE_BOXED:
                CIO_DEBUG(KERN_WARNING, 2,
-                         "SenseID : boxed device %04x on subchannel %04x\n",
-                         cdev->private->devno, sch->irq);
+                         "SenseID : boxed device %04x on subchannel "
+                         "0.%x.%04x\n", cdev->private->devno,
+                         sch->schid.ssid, sch->schid.sch_no);
                break;
        }
        cdev->private->state = state;
-       if (notify && state == DEV_STATE_OFFLINE)
-               ccw_device_handle_oper(cdev);
-       else
-               io_subchannel_recog_done(cdev);
+       io_subchannel_recog_done(cdev);
        if (state != DEV_STATE_NOT_OPER)
                wake_up(&cdev->private->wait_q);
 }
@@ -323,7 +362,7 @@ ccw_device_done(struct ccw_device *cdev, int state)
        if (state == DEV_STATE_BOXED)
                CIO_DEBUG(KERN_WARNING, 2,
                          "Boxed device %04x on subchannel %04x\n",
-                         cdev->private->devno, sch->irq);
+                         cdev->private->devno, sch->schid.sch_no);
 
        if (cdev->private->flags.donotify) {
                cdev->private->flags.donotify = 0;
@@ -448,11 +487,13 @@ ccw_device_nopath_notify(void *data)
                                             (void *)cdev);
                                queue_work(ccw_device_work,
                                           &cdev->private->kick_work);
-                       }
+                       } else
+                               put_device(&sch->dev);
                }
        } 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);
        }
@@ -467,6 +508,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);
@@ -539,6 +595,8 @@ ccw_device_offline(struct ccw_device *cdev)
        struct subchannel *sch;
 
        sch = to_subchannel(cdev->dev.parent);
+       if (stsch(sch->schid, &sch->schib) || !sch->schib.pmcw.dnv)
+               return -ENODEV;
        if (cdev->private->state != DEV_STATE_ONLINE) {
                if (sch->schib.scsw.actl != 0)
                        return -EBUSY;
@@ -598,9 +656,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);
 }
 
@@ -616,6 +676,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;
@@ -626,9 +687,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);
 }
 
@@ -647,6 +710,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->schid, &sch->schib);
+
        if (sch->schib.scsw.actl != 0 ||
            (cdev->private->irb.scsw.stctl & SCSW_STCTL_STATUS_PEND)) {
                /*
@@ -672,8 +741,20 @@ ccw_device_irq(struct ccw_device *cdev, enum dev_event dev_event)
 
        irb = (struct irb *) __LC_IRB;
        /* Check for unsolicited interrupt. */
-       if (irb->scsw.stctl ==
-                       (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
+       if ((irb->scsw.stctl ==
+                       (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS))
+           && (!irb->scsw.cc)) {
+               if ((irb->scsw.dstat & DEV_STAT_UNIT_CHECK) &&
+                   !irb->esw.esw0.erw.cons) {
+                       /* Unit check but no sense data. Need basic sense. */
+                       if (ccw_device_do_sense(cdev, irb) != 0)
+                               goto call_handler_unsol;
+                       memcpy(&cdev->private->irb, irb, sizeof(struct irb));
+                       cdev->private->state = DEV_STATE_W4SENSE;
+                       cdev->private->intparm = 0;
+                       return;
+               }
+call_handler_unsol:
                if (cdev->handler)
                        cdev->handler (cdev, 0, irb);
                return;
@@ -735,13 +816,28 @@ ccw_device_w4sense(struct ccw_device *cdev, enum dev_event dev_event)
        /* Check for unsolicited interrupt. */
        if (irb->scsw.stctl ==
                        (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
-               if (cdev->handler)
-                       cdev->handler (cdev, 0, irb);
                if (irb->scsw.cc == 1)
                        /* Basic sense hasn't started. Try again. */
                        ccw_device_do_sense(cdev, irb);
+               else {
+                       printk("Huh? %s(%s): unsolicited interrupt...\n",
+                              __FUNCTION__, cdev->dev.bus_id);
+                       if (cdev->handler)
+                               cdev->handler (cdev, 0, irb);
+               }
                return;
        }
+       /*
+        * Check if a halt or clear has been issued in the meanwhile. If yes,
+        * only deliver the halt/clear interrupt to the device driver as if it
+        * had killed the original request.
+        */
+       if (irb->scsw.fctl & (SCSW_FCTL_CLEAR_FUNC | SCSW_FCTL_HALT_FUNC)) {
+               cdev->private->flags.dosense = 0;
+               memset(&cdev->private->irb, 0, sizeof(struct irb));
+               ccw_device_accumulate_irb(cdev, irb);
+               goto call_handler;
+       }
        /* Add basic sense info to irb. */
        ccw_device_accumulate_basic_sense(cdev, irb);
        if (cdev->private->flags.dosense) {
@@ -749,6 +845,7 @@ ccw_device_w4sense(struct ccw_device *cdev, enum dev_event dev_event)
                ccw_device_do_sense(cdev, irb);
                return;
        }
+call_handler:
        cdev->private->state = DEV_STATE_ONLINE;
        /* Call the handler. */
        if (ccw_device_call_handler(cdev) && cdev->private->flags.doverify)
@@ -762,13 +859,6 @@ ccw_device_clear_verify(struct ccw_device *cdev, enum dev_event dev_event)
        struct irb *irb;
 
        irb = (struct irb *) __LC_IRB;
-       /* Check for unsolicited interrupt. */
-       if (irb->scsw.stctl ==
-                       (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
-               if (cdev->handler)
-                       cdev->handler (cdev, 0, irb);
-               return;
-       }
        /* Accumulate status. We don't do basic sense. */
        ccw_device_accumulate_irb(cdev, irb);
        /* Try to start delayed device verification. */
@@ -834,15 +924,6 @@ ccw_device_wait4io_irq(struct ccw_device *cdev, enum dev_event dev_event)
        struct subchannel *sch;
 
        irb = (struct irb *) __LC_IRB;
-       /* Check for unsolicited interrupt. */
-       if (irb->scsw.stctl ==
-                       (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
-               if (cdev->handler)
-                       cdev->handler (cdev, 0, irb);
-               if (irb->scsw.cc == 1)
-                       goto call_handler;
-               return;
-       }
        /*
         * Accumulate status and find out if a basic sense is needed.
         * This is fine since we have already adapted the lpm.
@@ -854,10 +935,10 @@ ccw_device_wait4io_irq(struct ccw_device *cdev, enum dev_event dev_event)
                }
                return;
        }
-call_handler:
+
        /* Iff device is idle, reset timeout. */
        sch = to_subchannel(cdev->dev.parent);
-       if (!stsch(sch->irq, &sch->schib))
+       if (!stsch(sch->schid, &sch->schib))
                if (sch->schib.scsw.actl == 0)
                        ccw_device_set_timeout(cdev, 0);
        /* Call the handler. */
@@ -923,8 +1004,9 @@ ccw_device_stlck_done(struct ccw_device *cdev, enum dev_event dev_event)
        case DEV_EVENT_INTERRUPT:
                irb = (struct irb *) __LC_IRB;
                /* Check for unsolicited interrupt. */
-               if (irb->scsw.stctl ==
-                   (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS))
+               if ((irb->scsw.stctl ==
+                    (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) &&
+                   (!irb->scsw.cc))
                        /* FIXME: we should restart stlck here, but this
                         * is extremely unlikely ... */
                        goto out_wakeup;
@@ -960,21 +1042,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->schid, &sch->schib))
                return;
-       }
+
        /*
         * The pim, pam, pom values may not be accurate, but they are the best
         * we have before performing device selection :/
@@ -990,8 +1068,8 @@ device_trigger_reprobe(struct subchannel *sch)
        if ((sch->lpm & (sch->lpm - 1)) != 0)
                sch->schib.pmcw.mp = 1;
        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
@@ -1184,8 +1262,8 @@ io_subchannel_irq (struct device *pdev)
 
        CIO_TRACE_EVENT (3, "IRQ");
        CIO_TRACE_EVENT (3, pdev->bus_id);
-
-       dev_fsm_event(cdev, DEV_EVENT_INTERRUPT);
+       if (cdev)
+               dev_fsm_event(cdev, DEV_EVENT_INTERRUPT);
 }
 
 EXPORT_SYMBOL_GPL(ccw_device_set_timeout);