]> git.karo-electronics.de Git - linux-beck.git/commitdiff
[S390] Support for disconnected devices reappearing on another subchannel.
authorCornelia Huck <cornelia.huck@de.ibm.com>
Fri, 8 Dec 2006 14:54:28 +0000 (15:54 +0100)
committerMartin Schwidefsky <schwidefsky@de.ibm.com>
Fri, 8 Dec 2006 14:54:28 +0000 (15:54 +0100)
- create a 'pseudo_subchannel' per channel subsystem (the 'orphanage')
- use the orphanage as a shelter for ccw_devices that can't remain on the same
  subchannel

Signed-off-by: Cornelia Huck <cornelia.huck@de.ibm.com>
Cc: Greg KH <greg@kroah.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
drivers/s390/cio/cio.c
drivers/s390/cio/cio.h
drivers/s390/cio/css.c
drivers/s390/cio/css.h
drivers/s390/cio/device.c
drivers/s390/cio/device.h
drivers/s390/cio/device_fsm.c

index e8d331493fd812c594696f0fcad8bc9ac62cf3f5..7835a714a405c90018dc8fa2aea49f415c829851 100644 (file)
@@ -415,6 +415,8 @@ cio_enable_subchannel (struct subchannel *sch, unsigned int isc)
        CIO_TRACE_EVENT (2, "ensch");
        CIO_TRACE_EVENT (2, sch->dev.bus_id);
 
+       if (sch_is_pseudo_sch(sch))
+               return -EINVAL;
        ccode = stsch (sch->schid, &sch->schib);
        if (ccode)
                return -ENODEV;
@@ -462,6 +464,8 @@ cio_disable_subchannel (struct subchannel *sch)
        CIO_TRACE_EVENT (2, "dissch");
        CIO_TRACE_EVENT (2, sch->dev.bus_id);
 
+       if (sch_is_pseudo_sch(sch))
+               return 0;
        ccode = stsch (sch->schid, &sch->schib);
        if (ccode == 3)         /* Not operational. */
                return -ENODEV;
@@ -496,7 +500,7 @@ cio_disable_subchannel (struct subchannel *sch)
        return ret;
 }
 
-static int cio_create_sch_lock(struct subchannel *sch)
+int cio_create_sch_lock(struct subchannel *sch)
 {
        sch->lock = kmalloc(sizeof(spinlock_t), GFP_KERNEL);
        if (!sch->lock)
index 7e7369b21a93380a8ab9de621a2a6f283e00eac5..35154a21035720f9f9ffd355577e7288cbb92761 100644 (file)
@@ -131,6 +131,8 @@ extern int cio_set_options (struct subchannel *, int);
 extern int cio_get_options (struct subchannel *);
 extern int cio_modify (struct subchannel *);
 
+int cio_create_sch_lock(struct subchannel *);
+
 /* Use with care. */
 #ifdef CONFIG_CCW_CONSOLE
 extern struct subchannel *cio_probe_console(void);
index 65939e2eb41558bc78b2d6fe7bd6859db1df8a70..0bf7166193782241ae67d48cf991e747947583e9 100644 (file)
@@ -580,12 +580,24 @@ css_cm_enable_store(struct device *dev, struct device_attribute *attr,
 
 static DEVICE_ATTR(cm_enable, 0644, css_cm_enable_show, css_cm_enable_store);
 
-static inline void __init
-setup_css(int nr)
+static inline int __init setup_css(int nr)
 {
        u32 tod_high;
+       int ret;
 
        memset(css[nr], 0, sizeof(struct channel_subsystem));
+       css[nr]->pseudo_subchannel =
+               kzalloc(sizeof(*css[nr]->pseudo_subchannel), GFP_KERNEL);
+       if (!css[nr]->pseudo_subchannel)
+               return -ENOMEM;
+       css[nr]->pseudo_subchannel->dev.parent = &css[nr]->device;
+       css[nr]->pseudo_subchannel->dev.release = css_subchannel_release;
+       sprintf(css[nr]->pseudo_subchannel->dev.bus_id, "defunct");
+       ret = cio_create_sch_lock(css[nr]->pseudo_subchannel);
+       if (ret) {
+               kfree(css[nr]->pseudo_subchannel);
+               return ret;
+       }
        mutex_init(&css[nr]->mutex);
        css[nr]->valid = 1;
        css[nr]->cssid = nr;
@@ -593,6 +605,7 @@ setup_css(int nr)
        css[nr]->device.release = channel_subsystem_release;
        tod_high = (u32) (get_clock() >> 32);
        css_generate_pgid(css[nr], tod_high);
+       return 0;
 }
 
 /*
@@ -629,10 +642,12 @@ init_channel_subsystem (void)
                        ret = -ENOMEM;
                        goto out_unregister;
                }
-               setup_css(i);
-               ret = device_register(&css[i]->device);
+               ret = setup_css(i);
                if (ret)
                        goto out_free;
+               ret = device_register(&css[i]->device);
+               if (ret)
+                       goto out_free_all;
                if (css_characteristics_avail &&
                    css_chsc_characteristics.secm) {
                        ret = device_create_file(&css[i]->device,
@@ -640,6 +655,9 @@ init_channel_subsystem (void)
                        if (ret)
                                goto out_device;
                }
+               ret = device_register(&css[i]->pseudo_subchannel->dev);
+               if (ret)
+                       goto out_file;
        }
        css_init_done = 1;
 
@@ -647,13 +665,19 @@ init_channel_subsystem (void)
 
        for_each_subchannel(__init_channel_subsystem, NULL);
        return 0;
+out_file:
+       device_remove_file(&css[i]->device, &dev_attr_cm_enable);
 out_device:
        device_unregister(&css[i]->device);
+out_free_all:
+       kfree(css[i]->pseudo_subchannel->lock);
+       kfree(css[i]->pseudo_subchannel);
 out_free:
        kfree(css[i]);
 out_unregister:
        while (i > 0) {
                i--;
+               device_unregister(&css[i]->pseudo_subchannel->dev);
                if (css_characteristics_avail && css_chsc_characteristics.secm)
                        device_remove_file(&css[i]->device,
                                           &dev_attr_cm_enable);
@@ -665,6 +689,11 @@ out:
        return ret;
 }
 
+int sch_is_pseudo_sch(struct subchannel *sch)
+{
+       return sch == to_css(sch->dev.parent)->pseudo_subchannel;
+}
+
 /*
  * find a driver for a subchannel. They identify by the subchannel
  * type with the exception that the console subchannel driver has its own
index ac845c1ebf83188609a9fd854914a73e20bd58f4..71fe380f86f6ecb6233374d7553c7419435481f9 100644 (file)
@@ -160,6 +160,8 @@ struct channel_subsystem {
        int cm_enabled;
        void *cub_addr1;
        void *cub_addr2;
+       /* for orphaned ccw devices */
+       struct subchannel *pseudo_subchannel;
 };
 #define to_css(dev) container_of(dev, struct channel_subsystem, device)
 
@@ -187,6 +189,8 @@ void css_clear_subchannel_slow_list(void);
 int css_slow_subchannels_exist(void);
 extern int need_rescan;
 
+int sch_is_pseudo_sch(struct subchannel *);
+
 extern struct workqueue_struct *slow_path_wq;
 extern struct work_struct slow_path_work;
 
index 9a31239fe028ae7c9c8e1c8b31b427156173a870..7fe1ccdc7812b5ffcf60f7580e30b9fb44626d65 100644 (file)
@@ -23,6 +23,7 @@
 #include <asm/param.h>         /* HZ */
 
 #include "cio.h"
+#include "cio_debug.h"
 #include "css.h"
 #include "device.h"
 #include "ioasm.h"
@@ -294,6 +295,11 @@ online_show (struct device *dev, struct device_attribute *attr, char *buf)
        return sprintf(buf, cdev->online ? "1\n" : "0\n");
 }
 
+int ccw_device_is_orphan(struct ccw_device *cdev)
+{
+       return sch_is_pseudo_sch(to_subchannel(cdev->dev.parent));
+}
+
 static void ccw_device_unregister(struct work_struct *work)
 {
        struct ccw_device_private *priv;
@@ -310,10 +316,23 @@ static void
 ccw_device_remove_disconnected(struct ccw_device *cdev)
 {
        struct subchannel *sch;
+       unsigned long flags;
        /*
         * Forced offline in disconnected state means
         * 'throw away device'.
         */
+       if (ccw_device_is_orphan(cdev)) {
+               /* Deregister ccw device. */
+               spin_lock_irqsave(cdev->ccwlock, flags);
+               cdev->private->state = DEV_STATE_NOT_OPER;
+               spin_unlock_irqrestore(cdev->ccwlock, flags);
+               if (get_device(&cdev->dev)) {
+                       PREPARE_WORK(&cdev->private->kick_work,
+                                    ccw_device_unregister);
+                       queue_work(ccw_device_work, &cdev->private->kick_work);
+               }
+               return ;
+       }
        sch = to_subchannel(cdev->dev.parent);
        css_sch_device_unregister(sch);
        /* Reset intparm to zeroes. */
@@ -474,6 +493,8 @@ available_show (struct device *dev, struct device_attribute *attr, char *buf)
        struct ccw_device *cdev = to_ccwdev(dev);
        struct subchannel *sch;
 
+       if (ccw_device_is_orphan(cdev))
+               return sprintf(buf, "no device\n");
        switch (cdev->private->state) {
        case DEV_STATE_BOXED:
                return sprintf(buf, "boxed\n");
@@ -574,11 +595,10 @@ match_devno(struct device * dev, void * data)
 
        cdev = to_ccwdev(dev);
        if ((cdev->private->state == DEV_STATE_DISCONNECTED) &&
+           !ccw_device_is_orphan(cdev) &&
            ccw_dev_id_is_equal(&cdev->private->dev_id, &d->dev_id) &&
-           (cdev != d->sibling)) {
-               cdev->private->state = DEV_STATE_NOT_OPER;
+           (cdev != d->sibling))
                return 1;
-       }
        return 0;
 }
 
@@ -595,6 +615,28 @@ static struct ccw_device * get_disc_ccwdev_by_dev_id(struct ccw_dev_id *dev_id,
        return dev ? to_ccwdev(dev) : NULL;
 }
 
+static int match_orphan(struct device *dev, void *data)
+{
+       struct ccw_dev_id *dev_id;
+       struct ccw_device *cdev;
+
+       dev_id = data;
+       cdev = to_ccwdev(dev);
+       return ccw_dev_id_is_equal(&cdev->private->dev_id, dev_id);
+}
+
+static struct ccw_device *
+get_orphaned_ccwdev_by_dev_id(struct channel_subsystem *css,
+                             struct ccw_dev_id *dev_id)
+{
+       struct device *dev;
+
+       dev = device_find_child(&css->pseudo_subchannel->dev, dev_id,
+                               match_orphan);
+
+       return dev ? to_ccwdev(dev) : NULL;
+}
+
 static void
 ccw_device_add_changed(struct work_struct *work)
 {
@@ -614,64 +656,19 @@ ccw_device_add_changed(struct work_struct *work)
        }
 }
 
-extern int css_get_ssd_info(struct subchannel *sch);
-
-void
-ccw_device_do_unreg_rereg(struct work_struct *work)
+void ccw_device_do_unreg_rereg(struct work_struct *work)
 {
        struct ccw_device_private *priv;
        struct ccw_device *cdev;
        struct subchannel *sch;
-       int need_rename;
 
        priv = container_of(work, struct ccw_device_private, kick_work);
        cdev = priv->cdev;
        sch = to_subchannel(cdev->dev.parent);
-       if (cdev->private->dev_id.devno != sch->schib.pmcw.dev) {
-               /*
-                * The device number has changed. This is usually only when
-                * a device has been detached under VM and then re-appeared
-                * on another subchannel because of a different attachment
-                * order than before. Ideally, we should should just switch
-                * subchannels, but unfortunately, this is not possible with
-                * the current implementation.
-                * Instead, we search for the old subchannel for this device
-                * number and deregister so there are no collisions with the
-                * newly registered ccw_device.
-                * FIXME: Find another solution so the block layer doesn't
-                *        get possibly sick...
-                */
-               struct ccw_device *other_cdev;
-               struct ccw_dev_id dev_id;
-
-               need_rename = 1;
-               dev_id.devno = sch->schib.pmcw.dev;
-               dev_id.ssid = sch->schid.ssid;
-               other_cdev = get_disc_ccwdev_by_dev_id(&dev_id, cdev);
-               if (other_cdev) {
-                       struct subchannel *other_sch;
-
-                       other_sch = to_subchannel(other_cdev->dev.parent);
-                       if (get_device(&other_sch->dev)) {
-                               stsch(other_sch->schid, &other_sch->schib);
-                               if (other_sch->schib.pmcw.dnv) {
-                                       other_sch->schib.pmcw.intparm = 0;
-                                       cio_modify(other_sch);
-                               }
-                               css_sch_device_unregister(other_sch);
-                       }
-               }
-               /* Update ssd info here. */
-               css_get_ssd_info(sch);
-               cdev->private->dev_id.devno = sch->schib.pmcw.dev;
-       } else
-               need_rename = 0;
+
        device_remove_files(&cdev->dev);
        if (test_and_clear_bit(1, &cdev->private->registered))
                device_del(&cdev->dev);
-       if (need_rename)
-               snprintf (cdev->dev.bus_id, BUS_ID_SIZE, "0.%x.%04x",
-                         sch->schid.ssid, sch->schib.pmcw.dev);
        PREPARE_WORK(&cdev->private->kick_work,
                     ccw_device_add_changed);
        queue_work(ccw_device_work, &cdev->private->kick_work);
@@ -736,6 +733,131 @@ static struct ccw_device * io_subchannel_create_ccwdev(struct subchannel *sch)
        return cdev;
 }
 
+static int io_subchannel_recog(struct ccw_device *, struct subchannel *);
+
+static void sch_attach_device(struct subchannel *sch,
+                             struct ccw_device *cdev)
+{
+       spin_lock_irq(sch->lock);
+       sch->dev.driver_data = cdev;
+       cdev->private->schid = sch->schid;
+       cdev->ccwlock = sch->lock;
+       device_trigger_reprobe(sch);
+       spin_unlock_irq(sch->lock);
+}
+
+static void sch_attach_disconnected_device(struct subchannel *sch,
+                                          struct ccw_device *cdev)
+{
+       struct subchannel *other_sch;
+       int ret;
+
+       other_sch = to_subchannel(get_device(cdev->dev.parent));
+       ret = device_move(&cdev->dev, &sch->dev);
+       if (ret) {
+               CIO_MSG_EVENT(2, "Moving disconnected device 0.%x.%04x failed "
+                             "(ret=%d)!\n", cdev->private->dev_id.ssid,
+                             cdev->private->dev_id.devno, ret);
+               put_device(&other_sch->dev);
+               return;
+       }
+       other_sch->dev.driver_data = NULL;
+       /* No need to keep a subchannel without ccw device around. */
+       css_sch_device_unregister(other_sch);
+       put_device(&other_sch->dev);
+       sch_attach_device(sch, cdev);
+}
+
+static void sch_attach_orphaned_device(struct subchannel *sch,
+                                      struct ccw_device *cdev)
+{
+       int ret;
+
+       /* Try to move the ccw device to its new subchannel. */
+       ret = device_move(&cdev->dev, &sch->dev);
+       if (ret) {
+               CIO_MSG_EVENT(0, "Moving device 0.%x.%04x from orphanage "
+                             "failed (ret=%d)!\n",
+                             cdev->private->dev_id.ssid,
+                             cdev->private->dev_id.devno, ret);
+               return;
+       }
+       sch_attach_device(sch, cdev);
+}
+
+static void sch_create_and_recog_new_device(struct subchannel *sch)
+{
+       struct ccw_device *cdev;
+
+       /* Need to allocate a new ccw device. */
+       cdev = io_subchannel_create_ccwdev(sch);
+       if (IS_ERR(cdev)) {
+               /* OK, we did everything we could... */
+               css_sch_device_unregister(sch);
+               return;
+       }
+       spin_lock_irq(sch->lock);
+       sch->dev.driver_data = 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;
+               spin_unlock_irq(sch->lock);
+               if (cdev->dev.release)
+                       cdev->dev.release(&cdev->dev);
+               css_sch_device_unregister(sch);
+       }
+}
+
+
+void ccw_device_move_to_orphanage(struct work_struct *work)
+{
+       struct ccw_device_private *priv;
+       struct ccw_device *cdev;
+       struct ccw_device *replacing_cdev;
+       struct subchannel *sch;
+       int ret;
+       struct channel_subsystem *css;
+       struct ccw_dev_id dev_id;
+
+       priv = container_of(work, struct ccw_device_private, kick_work);
+       cdev = priv->cdev;
+       sch = to_subchannel(cdev->dev.parent);
+       css = to_css(sch->dev.parent);
+       dev_id.devno = sch->schib.pmcw.dev;
+       dev_id.ssid = sch->schid.ssid;
+
+       /*
+        * Move the orphaned ccw device to the orphanage so the replacing
+        * ccw device can take its place on the subchannel.
+        */
+       ret = device_move(&cdev->dev, &css->pseudo_subchannel->dev);
+       if (ret) {
+               CIO_MSG_EVENT(0, "Moving device 0.%x.%04x to orphanage failed "
+                             "(ret=%d)!\n", cdev->private->dev_id.ssid,
+                             cdev->private->dev_id.devno, ret);
+               return;
+       }
+       cdev->ccwlock = css->pseudo_subchannel->lock;
+       /*
+        * Search for the replacing ccw device
+        * - among the disconnected devices
+        * - in the orphanage
+        */
+       replacing_cdev = get_disc_ccwdev_by_dev_id(&dev_id, cdev);
+       if (replacing_cdev) {
+               sch_attach_disconnected_device(sch, replacing_cdev);
+               return;
+       }
+       replacing_cdev = get_orphaned_ccwdev_by_dev_id(css, &dev_id);
+       if (replacing_cdev) {
+               sch_attach_orphaned_device(sch, replacing_cdev);
+               return;
+       }
+       sch_create_and_recog_new_device(sch);
+}
+
 /*
  * Register recognized device.
  */
@@ -890,12 +1012,55 @@ io_subchannel_recog(struct ccw_device *cdev, struct subchannel *sch)
        return rc;
 }
 
+static void ccw_device_move_to_sch(struct work_struct *work)
+{
+       struct ccw_device_private *priv;
+       int rc;
+       struct subchannel *sch;
+       struct ccw_device *cdev;
+       struct subchannel *former_parent;
+
+       priv = container_of(work, struct ccw_device_private, kick_work);
+       sch = priv->sch;
+       cdev = priv->cdev;
+       former_parent = ccw_device_is_orphan(cdev) ?
+               NULL : to_subchannel(get_device(cdev->dev.parent));
+       mutex_lock(&sch->reg_mutex);
+       /* Try to move the ccw device to its new subchannel. */
+       rc = device_move(&cdev->dev, &sch->dev);
+       mutex_unlock(&sch->reg_mutex);
+       if (rc) {
+               CIO_MSG_EVENT(2, "Moving device 0.%x.%04x to subchannel "
+                             "0.%x.%04x failed (ret=%d)!\n",
+                             cdev->private->dev_id.ssid,
+                             cdev->private->dev_id.devno, sch->schid.ssid,
+                             sch->schid.sch_no, rc);
+               css_sch_device_unregister(sch);
+               goto out;
+       }
+       if (former_parent) {
+               spin_lock_irq(former_parent->lock);
+               former_parent->dev.driver_data = NULL;
+               spin_unlock_irq(former_parent->lock);
+               css_sch_device_unregister(former_parent);
+               /* Reset intparm to zeroes. */
+               former_parent->schib.pmcw.intparm = 0;
+               cio_modify(former_parent);
+       }
+       sch_attach_device(sch, cdev);
+out:
+       if (former_parent)
+               put_device(&former_parent->dev);
+       put_device(&cdev->dev);
+}
+
 static int
 io_subchannel_probe (struct subchannel *sch)
 {
        struct ccw_device *cdev;
        int rc;
        unsigned long flags;
+       struct ccw_dev_id dev_id;
 
        if (sch->dev.driver_data) {
                /*
@@ -918,6 +1083,28 @@ io_subchannel_probe (struct subchannel *sch)
                        get_device(&cdev->dev);
                return 0;
        }
+       /*
+        * First check if a fitting device may be found amongst the
+        * disconnected devices or in the orphanage.
+        */
+       dev_id.devno = sch->schib.pmcw.dev;
+       dev_id.ssid = sch->schid.ssid;
+       cdev = get_disc_ccwdev_by_dev_id(&dev_id, NULL);
+       if (!cdev)
+               cdev = get_orphaned_ccwdev_by_dev_id(to_css(sch->dev.parent),
+                                                    &dev_id);
+       if (cdev) {
+               /*
+                * Schedule moving the device until when we have a registered
+                * subchannel to move to and succeed the probe. We can
+                * unregister later again, when the probe is through.
+                */
+               cdev->private->sch = sch;
+               PREPARE_WORK(&cdev->private->kick_work,
+                            ccw_device_move_to_sch);
+               queue_work(slow_path_wq, &cdev->private->kick_work);
+               return 0;
+       }
        cdev = io_subchannel_create_ccwdev(sch);
        if (IS_ERR(cdev))
                return PTR_ERR(cdev);
index d5fe95e04cfe4a5bb9779c919f2779331161b2df..29db6341d632ce0bd5a9f78b6982c3367a388349 100644 (file)
@@ -80,6 +80,8 @@ int ccw_device_cancel_halt_clear(struct ccw_device *);
 
 void ccw_device_do_unreg_rereg(struct work_struct *);
 void ccw_device_call_sch_unregister(struct work_struct *);
+void ccw_device_move_to_orphanage(struct work_struct *);
+int ccw_device_is_orphan(struct ccw_device *);
 
 int ccw_device_recognition(struct ccw_device *);
 int ccw_device_online(struct ccw_device *);
index a487fb0e7d3d5c2d79453cc987f04d05ed960553..eed14572fc3b01e3445ee30a46e30bae6a15a006 100644 (file)
@@ -186,13 +186,12 @@ ccw_device_handle_oper(struct ccw_device *cdev)
        /*
         * Check if cu type and device type still match. If
         * not, it is certainly another device and we have to
-        * de- and re-register. Also check here for non-matching devno.
+        * de- and re-register.
         */
        if (cdev->id.cu_type != cdev->private->senseid.cu_type ||
            cdev->id.cu_model != cdev->private->senseid.cu_model ||
            cdev->id.dev_type != cdev->private->senseid.dev_type ||
-           cdev->id.dev_model != cdev->private->senseid.dev_model ||
-           cdev->private->dev_id.devno != sch->schib.pmcw.dev) {
+           cdev->id.dev_model != cdev->private->senseid.dev_model) {
                PREPARE_WORK(&cdev->private->kick_work,
                             ccw_device_do_unreg_rereg);
                queue_work(ccw_device_work, &cdev->private->kick_work);
@@ -676,6 +675,10 @@ ccw_device_offline(struct ccw_device *cdev)
 {
        struct subchannel *sch;
 
+       if (ccw_device_is_orphan(cdev)) {
+               ccw_device_done(cdev, DEV_STATE_OFFLINE);
+               return 0;
+       }
        sch = to_subchannel(cdev->dev.parent);
        if (stsch(sch->schid, &sch->schib) || !sch->schib.pmcw.dnv)
                return -ENODEV;
@@ -1121,7 +1124,13 @@ device_trigger_reprobe(struct subchannel *sch)
                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);
+       /* Check if this is another device which appeared on the same sch. */
+       if (sch->schib.pmcw.dev != cdev->private->dev_id.devno) {
+               PREPARE_WORK(&cdev->private->kick_work,
+                            ccw_device_move_to_orphanage);
+               queue_work(ccw_device_work, &cdev->private->kick_work);
+       } else
+               ccw_device_start_id(cdev, 0);
 }
 
 static void