]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/s390/cio/device.c
[S390] qdio: FCP/SCSI write I/O stagnates on LPAR
[linux-2.6-omap-h63xx.git] / drivers / s390 / cio / device.c
index 30fe59cc28c9d60d7f9eb1203ec4ddc492616a06..fec004f62bcff78b5bc5e26ee12a2161ec65106a 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/list.h>
 #include <linux/device.h>
 #include <linux/workqueue.h>
+#include <linux/timer.h>
 
 #include <asm/ccwdev.h>
 #include <asm/cio.h>
 #include "ioasm.h"
 #include "io_sch.h"
 
+static struct timer_list recovery_timer;
+static DEFINE_SPINLOCK(recovery_lock);
+static int recovery_phase;
+static const unsigned long recovery_delay[] = { 3, 30, 300 };
+
 /******************* bus type handling ***********************/
 
 /* The Linux driver model distinguishes between a bus type and
@@ -125,6 +131,7 @@ static void io_subchannel_ioterm(struct subchannel *);
 static void io_subchannel_shutdown(struct subchannel *);
 
 static struct css_driver io_subchannel_driver = {
+       .owner = THIS_MODULE,
        .subchannel_type = SUBCHANNEL_TYPE_IO,
        .name = "io_subchannel",
        .irq = io_subchannel_irq,
@@ -141,6 +148,8 @@ struct workqueue_struct *ccw_device_notify_work;
 wait_queue_head_t ccw_device_init_wq;
 atomic_t ccw_device_init_count;
 
+static void recovery_func(unsigned long data);
+
 static int __init
 init_ccw_bus_type (void)
 {
@@ -148,6 +157,7 @@ init_ccw_bus_type (void)
 
        init_waitqueue_head(&ccw_device_init_wq);
        atomic_set(&ccw_device_init_count, 0);
+       setup_timer(&recovery_timer, recovery_func, 0);
 
        ccw_device_work = create_singlethread_workqueue("cio");
        if (!ccw_device_work)
@@ -773,7 +783,7 @@ static void sch_attach_device(struct subchannel *sch,
 {
        css_update_ssd_info(sch);
        spin_lock_irq(sch->lock);
-       sch->dev.driver_data = cdev;
+       sch_set_cdev(sch, cdev);
        cdev->private->schid = sch->schid;
        cdev->ccwlock = sch->lock;
        device_trigger_reprobe(sch);
@@ -795,7 +805,7 @@ static void sch_attach_disconnected_device(struct subchannel *sch,
                put_device(&other_sch->dev);
                return;
        }
-       other_sch->dev.driver_data = NULL;
+       sch_set_cdev(other_sch, NULL);
        /* No need to keep a subchannel without ccw device around. */
        css_sch_device_unregister(other_sch);
        put_device(&other_sch->dev);
@@ -831,12 +841,12 @@ static void sch_create_and_recog_new_device(struct subchannel *sch)
                return;
        }
        spin_lock_irq(sch->lock);
-       sch->dev.driver_data = cdev;
+       sch_set_cdev(sch, cdev);
        spin_unlock_irq(sch->lock);
        /* Start recognition for the new ccw device. */
        if (io_subchannel_recog(cdev, sch)) {
                spin_lock_irq(sch->lock);
-               sch->dev.driver_data = NULL;
+               sch_set_cdev(sch, NULL);
                spin_unlock_irq(sch->lock);
                if (cdev->dev.release)
                        cdev->dev.release(&cdev->dev);
@@ -940,7 +950,7 @@ io_subchannel_register(struct work_struct *work)
                              cdev->private->dev_id.devno, ret);
                put_device(&cdev->dev);
                spin_lock_irqsave(sch->lock, flags);
-               sch->dev.driver_data = NULL;
+               sch_set_cdev(sch, NULL);
                spin_unlock_irqrestore(sch->lock, flags);
                kfree (cdev->private);
                kfree (cdev);
@@ -1022,7 +1032,7 @@ io_subchannel_recog(struct ccw_device *cdev, struct subchannel *sch)
        int rc;
        struct ccw_device_private *priv;
 
-       sch->dev.driver_data = cdev;
+       sch_set_cdev(sch, cdev);
        sch->driver = &io_subchannel_driver;
        cdev->ccwlock = sch->lock;
 
@@ -1082,7 +1092,7 @@ static void ccw_device_move_to_sch(struct work_struct *work)
        }
        if (former_parent) {
                spin_lock_irq(former_parent->lock);
-               former_parent->dev.driver_data = NULL;
+               sch_set_cdev(former_parent, NULL);
                spin_unlock_irq(former_parent->lock);
                css_sch_device_unregister(former_parent);
                /* Reset intparm to zeroes. */
@@ -1100,7 +1110,7 @@ static void io_subchannel_irq(struct subchannel *sch)
 {
        struct ccw_device *cdev;
 
-       cdev = sch->dev.driver_data;
+       cdev = sch_get_cdev(sch);
 
        CIO_TRACE_EVENT(3, "IRQ");
        CIO_TRACE_EVENT(3, sch->dev.bus_id);
@@ -1116,13 +1126,13 @@ io_subchannel_probe (struct subchannel *sch)
        unsigned long flags;
        struct ccw_dev_id dev_id;
 
-       if (sch->dev.driver_data) {
+       cdev = sch_get_cdev(sch);
+       if (cdev) {
                /*
                 * This subchannel already has an associated ccw_device.
                 * Register it and exit. This happens for all early
                 * device, e.g. the console.
                 */
-               cdev = sch->dev.driver_data;
                cdev->dev.groups = ccwdev_attr_groups;
                device_initialize(&cdev->dev);
                ccw_device_register(cdev);
@@ -1173,7 +1183,7 @@ io_subchannel_probe (struct subchannel *sch)
        rc = io_subchannel_recog(cdev, sch);
        if (rc) {
                spin_lock_irqsave(sch->lock, flags);
-               sch->dev.driver_data = NULL;
+               sch_set_cdev(sch, NULL);
                spin_unlock_irqrestore(sch->lock, flags);
                if (cdev->dev.release)
                        cdev->dev.release(&cdev->dev);
@@ -1189,12 +1199,12 @@ io_subchannel_remove (struct subchannel *sch)
        struct ccw_device *cdev;
        unsigned long flags;
 
-       if (!sch->dev.driver_data)
+       cdev = sch_get_cdev(sch);
+       if (!cdev)
                return 0;
-       cdev = sch->dev.driver_data;
        /* Set ccw device to not operational and drop reference. */
        spin_lock_irqsave(cdev->ccwlock, flags);
-       sch->dev.driver_data = NULL;
+       sch_set_cdev(sch, NULL);
        cdev->private->state = DEV_STATE_NOT_OPER;
        spin_unlock_irqrestore(cdev->ccwlock, flags);
        ccw_device_unregister(cdev);
@@ -1207,7 +1217,7 @@ static int io_subchannel_notify(struct subchannel *sch, int event)
 {
        struct ccw_device *cdev;
 
-       cdev = sch->dev.driver_data;
+       cdev = sch_get_cdev(sch);
        if (!cdev)
                return 0;
        if (!cdev->drv)
@@ -1221,7 +1231,7 @@ static void io_subchannel_verify(struct subchannel *sch)
 {
        struct ccw_device *cdev;
 
-       cdev = sch->dev.driver_data;
+       cdev = sch_get_cdev(sch);
        if (cdev)
                dev_fsm_event(cdev, DEV_EVENT_VERIFY);
 }
@@ -1230,7 +1240,7 @@ static void io_subchannel_ioterm(struct subchannel *sch)
 {
        struct ccw_device *cdev;
 
-       cdev = sch->dev.driver_data;
+       cdev = sch_get_cdev(sch);
        if (!cdev)
                return;
        /* Internal I/O will be retried by the interrupt handler. */
@@ -1248,7 +1258,7 @@ io_subchannel_shutdown(struct subchannel *sch)
        struct ccw_device *cdev;
        int ret;
 
-       cdev = sch->dev.driver_data;
+       cdev = sch_get_cdev(sch);
 
        if (cio_is_console(sch->schid))
                return;
@@ -1476,6 +1486,7 @@ int ccw_driver_register(struct ccw_driver *cdriver)
 
        drv->bus = &ccw_bus_type;
        drv->name = cdriver->name;
+       drv->owner = cdriver->owner;
 
        return driver_register(drv);
 }
@@ -1501,6 +1512,71 @@ ccw_device_get_subchannel_id(struct ccw_device *cdev)
        return sch->schid;
 }
 
+static int recovery_check(struct device *dev, void *data)
+{
+       struct ccw_device *cdev = to_ccwdev(dev);
+       int *redo = data;
+
+       spin_lock_irq(cdev->ccwlock);
+       switch (cdev->private->state) {
+       case DEV_STATE_DISCONNECTED:
+               CIO_MSG_EVENT(3, "recovery: trigger 0.%x.%04x\n",
+                             cdev->private->dev_id.ssid,
+                             cdev->private->dev_id.devno);
+               dev_fsm_event(cdev, DEV_EVENT_VERIFY);
+               *redo = 1;
+               break;
+       case DEV_STATE_DISCONNECTED_SENSE_ID:
+               *redo = 1;
+               break;
+       }
+       spin_unlock_irq(cdev->ccwlock);
+
+       return 0;
+}
+
+static void recovery_work_func(struct work_struct *unused)
+{
+       int redo = 0;
+
+       bus_for_each_dev(&ccw_bus_type, NULL, &redo, recovery_check);
+       if (redo) {
+               spin_lock_irq(&recovery_lock);
+               if (!timer_pending(&recovery_timer)) {
+                       if (recovery_phase < ARRAY_SIZE(recovery_delay) - 1)
+                               recovery_phase++;
+                       mod_timer(&recovery_timer, jiffies +
+                                 recovery_delay[recovery_phase] * HZ);
+               }
+               spin_unlock_irq(&recovery_lock);
+       } else
+               CIO_MSG_EVENT(2, "recovery: end\n");
+}
+
+static DECLARE_WORK(recovery_work, recovery_work_func);
+
+static void recovery_func(unsigned long data)
+{
+       /*
+        * We can't do our recovery in softirq context and it's not
+        * performance critical, so we schedule it.
+        */
+       schedule_work(&recovery_work);
+}
+
+void ccw_device_schedule_recovery(void)
+{
+       unsigned long flags;
+
+       CIO_MSG_EVENT(2, "recovery: schedule\n");
+       spin_lock_irqsave(&recovery_lock, flags);
+       if (!timer_pending(&recovery_timer) || (recovery_phase != 0)) {
+               recovery_phase = 0;
+               mod_timer(&recovery_timer, jiffies + recovery_delay[0] * HZ);
+       }
+       spin_unlock_irqrestore(&recovery_lock, flags);
+}
+
 MODULE_LICENSE("GPL");
 EXPORT_SYMBOL(ccw_device_set_online);
 EXPORT_SYMBOL(ccw_device_set_offline);