]> git.neil.brown.name Git - history.git/commitdiff
[PATCH] s390: common i/o layer
authorAndrew Morton <akpm@osdl.org>
Mon, 12 Apr 2004 06:42:02 +0000 (23:42 -0700)
committerLinus Torvalds <torvalds@ppc970.osdl.org>
Mon, 12 Apr 2004 06:42:02 +0000 (23:42 -0700)
From: Martin Schwidefsky <schwidefsky@de.ibm.com>

Common i/o layer changes:
 - Avoid de-registering a ccwgroup device multiple times.
 - Remove check for channel path objects in get_subchannel_by_schid.
   Channel patch objects are never in the bus list.
 - Avoid NULL pointer deref. in qdio_unmark_q.
 - Fix reference counting on subchannel objects.
 - Add shutdown function to terminate i/o and disable subchannels at reipl.
 - Remove all ccwgroup devices if the ccwgroup driver is unregistered.

drivers/s390/cio/ccwgroup.c
drivers/s390/cio/chsc.c
drivers/s390/cio/css.c
drivers/s390/cio/device.c
drivers/s390/cio/device.h
drivers/s390/cio/device_fsm.c
drivers/s390/cio/qdio.c

index af5c16cb36e965e39f0d6cc534c6ef0e6dde2c10..6d0179f6a56242be240a4bb06942fa43c2019e1e 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  drivers/s390/cio/ccwgroup.c
  *  bus driver for ccwgroup
- *   $Revision: 1.25 $
+ *   $Revision: 1.27 $
  *
  *    Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
  *                       IBM Corporation
@@ -397,9 +397,35 @@ ccwgroup_driver_register (struct ccwgroup_driver *cdriver)
        return driver_register(&cdriver->driver);
 }
 
+static inline struct device *
+__get_next_ccwgroup_device(struct device_driver *drv)
+{
+       struct device *dev, *d;
+
+       down_read(&drv->bus->subsys.rwsem);
+       dev = NULL;
+       list_for_each_entry(d, &drv->devices, driver_list) {
+               dev = get_device(d);
+               if (dev)
+                       break;
+       }
+       up_read(&drv->bus->subsys.rwsem);
+       return dev;
+}
+
 void
 ccwgroup_driver_unregister (struct ccwgroup_driver *cdriver)
 {
+       struct device *dev;
+
+       /* We don't want ccwgroup devices to live longer than their driver. */
+       get_driver(&cdriver->driver);
+       while ((dev = __get_next_ccwgroup_device(&cdriver->driver))) {
+               __ccwgroup_remove_symlinks(to_ccwgroupdev(dev));
+               device_unregister(dev);
+               put_device(dev);
+       };
+       put_driver(&cdriver->driver);
        driver_unregister(&cdriver->driver);
 }
 
@@ -416,8 +442,11 @@ __ccwgroup_get_gdev_by_cdev(struct ccw_device *cdev)
 
        if (cdev->dev.driver_data) {
                gdev = (struct ccwgroup_device *)cdev->dev.driver_data;
-               if (get_device(&gdev->dev))
-                       return gdev;
+               if (get_device(&gdev->dev)) {
+                       if (!list_empty(&gdev->dev.node))
+                               return gdev;
+                       put_device(&gdev->dev);
+               }
                return NULL;
        }
        return NULL;
index 9f5598669c78318b2d17eb1c25c9a4de2fbb1e37..35c467f5b867861b63ec64e46e84c5e1e9c0c7e0 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  drivers/s390/cio/chsc.c
  *   S/390 common I/O routines -- channel subsystem call
- *   $Revision: 1.105 $
+ *   $Revision: 1.107 $
  *
  *    Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH,
  *                           IBM Corporation
@@ -819,8 +819,10 @@ s390_vary_chpid( __u8 chpid, int on)
                struct schib schib;
 
                sch = get_subchannel_by_schid(irq);
-               if (sch)
+               if (sch) {
+                       put_device(&sch->dev);
                        continue;
+               }
                if (stsch(irq, &schib))
                        /* We're through */
                        break;
index 45aa6a0d1dcd9010206c00009b68ea2d18c7d8cb..41030505851c0bb3ad0962cc26055e800bd57e7c 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  drivers/s390/cio/css.c
  *  driver for channel subsystem
- *   $Revision: 1.69 $
+ *   $Revision: 1.72 $
  *
  *    Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
  *                      IBM Corporation
@@ -163,11 +163,6 @@ get_subchannel_by_schid(int irq)
                                              struct device, bus_list));
                if (!dev)
                        continue;
-               /* Skip channel paths. */
-               if (dev->release != &css_subchannel_release) {
-                       put_device(dev);
-                       continue;
-               }
                sch = to_subchannel(dev);
                if (sch->irq == irq)
                        break;
@@ -206,10 +201,16 @@ css_evaluate_subchannel(int irq, int slow)
 
        sch = get_subchannel_by_schid(irq);
        disc = sch ? device_is_disconnected(sch) : 0;
-       if (disc && slow)
+       if (disc && slow) {
+               if (sch)
+                       put_device(&sch->dev);
                return 0; /* Already processed. */
-       if (!disc && !slow)
+       }
+       if (!disc && !slow) {
+               if (sch)
+                       put_device(&sch->dev);
                return -EAGAIN; /* Will be done on the slow path. */
+       }
        event = css_get_subchannel_status(sch, irq);
        switch (event) {
        case CIO_GONE:
index 17c7785fac5526f7cc685cdab61431bcd7ec1217..4c9b8256a9af6c37a52e4c9948ad23bb8e9d091c 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  drivers/s390/cio/device.c
  *  bus driver for ccw devices
- *   $Revision: 1.110 $
+ *   $Revision: 1.113 $
  *
  *    Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
  *                      IBM Corporation
@@ -120,6 +120,7 @@ void io_subchannel_irq (struct device *);
 static int io_subchannel_notify(struct device *, int);
 static void io_subchannel_verify(struct device *);
 static void io_subchannel_ioterm(struct device *);
+static void io_subchannel_shutdown(struct device *);
 
 struct css_driver io_subchannel_driver = {
        .subchannel_type = SUBCHANNEL_TYPE_IO,
@@ -128,6 +129,7 @@ struct css_driver io_subchannel_driver = {
                .bus  = &css_bus_type,
                .probe = &io_subchannel_probe,
                .remove = &io_subchannel_remove,
+               .shutdown = &io_subchannel_shutdown,
        },
        .irq = io_subchannel_irq,
        .notify = io_subchannel_notify,
@@ -766,6 +768,37 @@ io_subchannel_ioterm(struct device *dev)
                              ERR_PTR(-EIO));
 }
 
+static void
+io_subchannel_shutdown(struct device *dev)
+{
+       struct subchannel *sch;
+       struct ccw_device *cdev;
+       int ret;
+
+       sch = to_subchannel(dev);
+       cdev = dev->driver_data;
+
+       if (cio_is_console(sch->irq))
+               return;
+       if (!sch->schib.pmcw.ena)
+               /* Nothing to do. */
+               return;
+       ret = cio_disable_subchannel(sch);
+       if (ret != -EBUSY)
+               /* Subchannel is disabled, we're done. */
+               return;
+       cdev->private->state = DEV_STATE_QUIESCE;
+       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));
+       }
+       cio_disable_subchannel(sch);
+}
+
 #ifdef CONFIG_CCW_CONSOLE
 static struct ccw_device console_cdev;
 static struct ccw_device_private console_private;
index 29e24baf5bb34fd24f2ae9eec2dbf80615aaca3c..c8ff5ebcd69b5499f4781421aeb138f0f5608ca8 100644 (file)
@@ -18,6 +18,7 @@ enum dev_state {
        DEV_STATE_CLEAR_VERIFY,
        DEV_STATE_TIMEOUT_KILL,
        DEV_STATE_WAIT4IO,
+       DEV_STATE_QUIESCE,
        /* special states for devices gone not operational */
        DEV_STATE_DISCONNECTED,
        DEV_STATE_DISCONNECTED_SENSE_ID,
@@ -68,6 +69,8 @@ extern struct workqueue_struct *ccw_device_work;
 
 void io_subchannel_recog_done(struct ccw_device *cdev);
 
+int ccw_device_cancel_halt_clear(struct ccw_device *);
+
 int ccw_device_register(struct ccw_device *);
 void ccw_device_do_unreg_rereg(void *);
 
index e4d781e2ae493a1e5e8e808cb61fd078b87fa8c9..eac048a1991cbd6ad223a347b4eb8cbc3ffd6724 100644 (file)
@@ -101,7 +101,7 @@ ccw_device_set_timeout(struct ccw_device *cdev, int expires)
  * -EBUSY if an interrupt is expected (either from halt/clear or from a
  * status pending).
  */
-static int
+int
 ccw_device_cancel_halt_clear(struct ccw_device *cdev)
 {
        struct subchannel *sch;
@@ -438,10 +438,13 @@ ccw_device_nopath_notify(void *data)
        ret = (sch->driver && sch->driver->notify) ?
                sch->driver->notify(&sch->dev, CIO_NO_PATH) : 0;
        if (!ret) {
-               /* Driver doesn't want to keep device. */
-               device_unregister(&sch->dev);
-               sch->schib.pmcw.intparm = 0;
-               cio_modify(sch);
+               if (get_device(&sch->dev)) {
+                       /* Driver doesn't want to keep device. */
+                       device_unregister(&sch->dev);
+                       sch->schib.pmcw.intparm = 0;
+                       cio_modify(sch);
+                       put_device(&sch->dev);
+               }
        } else {
                ccw_device_set_timeout(cdev, 0);
                cdev->private->state = DEV_STATE_DISCONNECTED;
@@ -710,9 +713,17 @@ ccw_device_online_timeout(struct ccw_device *cdev, enum dev_event dev_event)
                cdev->private->state = DEV_STATE_TIMEOUT_KILL;
                return;
        }
-       if (ret == -ENODEV)
-               dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
-       else if (cdev->handler)
+       if (ret == -ENODEV) {
+               struct subchannel *sch;
+
+               sch = to_subchannel(cdev->dev.parent);
+               if (!sch->lpm) {
+                       PREPARE_WORK(&cdev->private->kick_work,
+                                    ccw_device_nopath_notify, (void *)cdev);
+                       queue_work(ccw_device_work, &cdev->private->kick_work);
+               } else
+                       dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
+       } else if (cdev->handler)
                cdev->handler(cdev, cdev->private->intparm,
                              ERR_PTR(-ETIMEDOUT));
 }
@@ -808,8 +819,8 @@ ccw_device_killing_timeout(struct ccw_device *cdev, enum dev_event dev_event)
                        PREPARE_WORK(&cdev->private->kick_work,
                                     ccw_device_nopath_notify, (void *)cdev);
                        queue_work(ccw_device_work, &cdev->private->kick_work);
-               }
-               dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
+               } else
+                       dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
                return;
        }
        //FIXME: Can we get here?
@@ -868,6 +879,7 @@ ccw_device_wait4io_timeout(struct ccw_device *cdev, enum dev_event dev_event)
        int ret;
        struct subchannel *sch;
 
+       sch = to_subchannel(cdev->dev.parent);
        ccw_device_set_timeout(cdev, 0);
        ret = ccw_device_cancel_halt_clear(cdev);
        if (ret == -EBUSY) {
@@ -876,16 +888,17 @@ ccw_device_wait4io_timeout(struct ccw_device *cdev, enum dev_event dev_event)
                return;
        }
        if (ret == -ENODEV) {
-               PREPARE_WORK(&cdev->private->kick_work,
-                            ccw_device_nopath_notify, (void *)cdev);
-               queue_work(ccw_device_work, &cdev->private->kick_work);
-               dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
+               if (!sch->lpm) {
+                       PREPARE_WORK(&cdev->private->kick_work,
+                                    ccw_device_nopath_notify, (void *)cdev);
+                       queue_work(ccw_device_work, &cdev->private->kick_work);
+               } else
+                       dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
                return;
        }
        if (cdev->handler)
                cdev->handler(cdev, cdev->private->intparm,
                              ERR_PTR(-ETIMEDOUT));
-       sch = to_subchannel(cdev->dev.parent);
        if (!sch->lpm) {
                PREPARE_WORK(&cdev->private->kick_work,
                             ccw_device_nopath_notify, (void *)cdev);
@@ -1005,6 +1018,37 @@ ccw_device_change_cmfstate(struct ccw_device *cdev, enum dev_event dev_event)
 }
 
 
+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;
+       wake_up(&cdev->private->wait_q);
+}
+
+static void
+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:
+               cdev->private->state = DEV_STATE_NOT_OPER;
+               wake_up(&cdev->private->wait_q);
+               break;
+       default:
+               ccw_device_set_timeout(cdev, HZ/10);
+       }
+}
+
 /*
  * No operation action. This is used e.g. to ignore a timeout event in
  * state offline.
@@ -1102,6 +1146,12 @@ fsm_func_t *dev_jumptable[NR_DEV_STATES][NR_DEV_EVENTS] = {
                [DEV_EVENT_TIMEOUT]     ccw_device_wait4io_timeout,
                [DEV_EVENT_VERIFY]      ccw_device_wait4io_verify,
        },
+       [DEV_STATE_QUIESCE] {
+               [DEV_EVENT_NOTOPER]     ccw_device_quiesce_done,
+               [DEV_EVENT_INTERRUPT]   ccw_device_quiesce_done,
+               [DEV_EVENT_TIMEOUT]     ccw_device_quiesce_timeout,
+               [DEV_EVENT_VERIFY]      ccw_device_nop,
+       },
        /* special states for devices gone not operational */
        [DEV_STATE_DISCONNECTED] {
                [DEV_EVENT_NOTOPER]     ccw_device_nop,
index d4e88181d1204b39cf8d04deff55b302501f19a7..3188cde89326beb3d931e83cae3aa0991a54edb4 100644 (file)
@@ -56,7 +56,7 @@
 #include "ioasm.h"
 #include "chsc.h"
 
-#define VERSION_QDIO_C "$Revision: 1.78 $"
+#define VERSION_QDIO_C "$Revision: 1.79 $"
 
 /****************** MODULE PARAMETER VARIABLES ********************/
 MODULE_AUTHOR("Utz Bacher <utz.bacher@de.ibm.com>");
@@ -392,6 +392,11 @@ qdio_unmark_q(struct qdio_q *q)
        if ((q->is_thinint_q)&&(q->is_input_q)) {
                /* iQDIO */
                spin_lock_irqsave(&ttiq_list_lock,flags);
+               /* in case cleanup has done this already and simultanously
+                * qdio_unmark_q is called from the interrupt handler, we've
+                * got to check this in this specific case again */
+               if ((!q->list_prev)||(!q->list_next))
+                       goto out;
                if (q->list_next==q) {
                        /* q was the only interesting q */
                        tiq_list=NULL;
@@ -404,6 +409,7 @@ qdio_unmark_q(struct qdio_q *q)
                        q->list_next=NULL;
                        q->list_prev=NULL;
                }
+out:
                spin_unlock_irqrestore(&ttiq_list_lock,flags);
        }
 }