[S390] cio: Avoid machine check vs. not operational races.
[linux-block.git] / drivers / s390 / cio / device.c
index a23ff582db9ddf03b303aa6647d2d353f7e96294..39f02b48e4c7dbf46ac7cf8defe35d9cbb257f38 100644 (file)
@@ -21,6 +21,7 @@
 #include <asm/ccwdev.h>
 #include <asm/cio.h>
 #include <asm/param.h>         /* HZ */
+#include <asm/cmb.h>
 
 #include "cio.h"
 #include "cio_debug.h"
@@ -117,7 +118,10 @@ static int ccw_uevent(struct device *dev, char **envp, int num_envp,
        snprint_alias(modalias_buf, sizeof(modalias_buf), id, "");
        ret = add_uevent_var(envp, num_envp, &i, buffer, buffer_size, &len,
                             "MODALIAS=%s", modalias_buf);
-       return ret;
+       if (ret)
+               return ret;
+       envp[i] = NULL;
+       return 0;
 }
 
 struct bus_type ccw_bus_type;
@@ -129,7 +133,7 @@ static void io_subchannel_verify(struct device *);
 static void io_subchannel_ioterm(struct device *);
 static void io_subchannel_shutdown(struct subchannel *);
 
-struct css_driver io_subchannel_driver = {
+static struct css_driver io_subchannel_driver = {
        .subchannel_type = SUBCHANNEL_TYPE_IO,
        .drv = {
                .name = "io_subchannel",
@@ -272,7 +276,7 @@ modalias_show (struct device *dev, struct device_attribute *attr, char *buf)
        struct ccw_device_id *id = &(cdev->id);
        int len;
 
-       len = snprint_alias(buf, PAGE_SIZE, id, "\n") + 1;
+       len = snprint_alias(buf, PAGE_SIZE, id, "\n");
 
        return len > PAGE_SIZE ? PAGE_SIZE : len;
 }
@@ -296,34 +300,76 @@ static void ccw_device_unregister(struct ccw_device *cdev)
                device_del(&cdev->dev);
 }
 
+static void ccw_device_remove_orphan_cb(struct device *dev)
+{
+       struct ccw_device *cdev = to_ccwdev(dev);
+
+       ccw_device_unregister(cdev);
+       put_device(&cdev->dev);
+}
+
+static void ccw_device_remove_sch_cb(struct device *dev)
+{
+       struct subchannel *sch;
+
+       sch = to_subchannel(dev);
+       css_sch_device_unregister(sch);
+       /* Reset intparm to zeroes. */
+       sch->schib.pmcw.intparm = 0;
+       cio_modify(sch);
+       put_device(&sch->dev);
+}
+
 static void
 ccw_device_remove_disconnected(struct ccw_device *cdev)
 {
-       struct subchannel *sch;
        unsigned long flags;
+       int rc;
+
        /*
         * Forced offline in disconnected state means
         * 'throw away device'.
         */
        if (ccw_device_is_orphan(cdev)) {
-               /* Deregister ccw device. */
+               /*
+                * Deregister ccw device.
+                * Unfortunately, we cannot do this directly from the
+                * attribute method.
+                */
                spin_lock_irqsave(cdev->ccwlock, flags);
                cdev->private->state = DEV_STATE_NOT_OPER;
                spin_unlock_irqrestore(cdev->ccwlock, flags);
-               ccw_device_unregister(cdev);
-               put_device(&cdev->dev);
-               return ;
+               rc = device_schedule_callback(&cdev->dev,
+                                             ccw_device_remove_orphan_cb);
+               if (rc)
+                       CIO_MSG_EVENT(2, "Couldn't unregister orphan "
+                                     "0.%x.%04x\n",
+                                     cdev->private->dev_id.ssid,
+                                     cdev->private->dev_id.devno);
+               return;
        }
-       sch = to_subchannel(cdev->dev.parent);
-       css_sch_device_unregister(sch);
-       /* Reset intparm to zeroes. */
-       sch->schib.pmcw.intparm = 0;
-       cio_modify(sch);
-       put_device(&sch->dev);
+       /* Deregister subchannel, which will kill the ccw device. */
+       rc = device_schedule_callback(cdev->dev.parent,
+                                     ccw_device_remove_sch_cb);
+       if (rc)
+               CIO_MSG_EVENT(2, "Couldn't unregister disconnected device "
+                             "0.%x.%04x\n",
+                             cdev->private->dev_id.ssid,
+                             cdev->private->dev_id.devno);
 }
 
-int
-ccw_device_set_offline(struct ccw_device *cdev)
+/**
+ * ccw_device_set_offline() - disable a ccw device for I/O
+ * @cdev: target ccw device
+ *
+ * This function calls the driver's set_offline() function for @cdev, if
+ * given, and then disables @cdev.
+ * Returns:
+ *   %0 on success and a negative error value on failure.
+ * Context:
+ *  enabled, ccw device lock not held
+ */
+int ccw_device_set_offline(struct ccw_device *cdev)
 {
        int ret;
 
@@ -352,15 +398,28 @@ ccw_device_set_offline(struct ccw_device *cdev)
        if (ret == 0)
                wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev));
        else {
-               pr_debug("ccw_device_offline returned %d, device %s\n",
-                        ret, cdev->dev.bus_id);
+               CIO_MSG_EVENT(2, "ccw_device_offline returned %d, "
+                             "device 0.%x.%04x\n",
+                             ret, cdev->private->dev_id.ssid,
+                             cdev->private->dev_id.devno);
                cdev->online = 1;
        }
        return ret;
 }
 
-int
-ccw_device_set_online(struct ccw_device *cdev)
+/**
+ * ccw_device_set_online() - enable a ccw device for I/O
+ * @cdev: target ccw device
+ *
+ * This function first enables @cdev and then calls the driver's set_online()
+ * function for @cdev, if given. If set_online() returns an error, @cdev is
+ * disabled again.
+ * Returns:
+ *   %0 on success and a negative error value on failure.
+ * Context:
+ *  enabled, ccw device lock not held
+ */
+int ccw_device_set_online(struct ccw_device *cdev)
 {
        int ret;
 
@@ -375,8 +434,10 @@ ccw_device_set_online(struct ccw_device *cdev)
        if (ret == 0)
                wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev));
        else {
-               pr_debug("ccw_device_online returned %d, device %s\n",
-                        ret, cdev->dev.bus_id);
+               CIO_MSG_EVENT(2, "ccw_device_online returned %d, "
+                             "device 0.%x.%04x\n",
+                             ret, cdev->private->dev_id.ssid,
+                             cdev->private->dev_id.devno);
                return ret;
        }
        if (cdev->private->state != DEV_STATE_ONLINE)
@@ -390,9 +451,11 @@ ccw_device_set_online(struct ccw_device *cdev)
        spin_unlock_irq(cdev->ccwlock);
        if (ret == 0)
                wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev));
-       else 
-               pr_debug("ccw_device_offline returned %d, device %s\n",
-                        ret, cdev->dev.bus_id);
+       else
+               CIO_MSG_EVENT(2, "ccw_device_offline returned %d, "
+                             "device 0.%x.%04x\n",
+                             ret, cdev->private->dev_id.ssid,
+                             cdev->private->dev_id.devno);
        return (ret == 0) ? -ENODEV : ret;
 }
 
@@ -412,9 +475,10 @@ static int online_store_recog_and_online(struct ccw_device *cdev)
        if (cdev->id.cu_type == 0) {
                ret = ccw_device_recognition(cdev);
                if (ret) {
-                       printk(KERN_WARNING"Couldn't start recognition "
-                              "for device %s (ret=%d)\n",
-                              cdev->dev.bus_id, ret);
+                       CIO_MSG_EVENT(0, "Couldn't start recognition "
+                                     "for device 0.%x.%04x (ret=%d)\n",
+                                     cdev->private->dev_id.ssid,
+                                     cdev->private->dev_id.devno, ret);
                        return ret;
                }
                wait_event(cdev->private->wait_q,
@@ -434,8 +498,8 @@ static void online_store_handle_online(struct ccw_device *cdev, int force)
        if (force && cdev->private->state == DEV_STATE_BOXED) {
                ret = ccw_device_stlck(cdev);
                if (ret) {
-                       printk(KERN_WARNING"ccw_device_stlck for device %s "
-                              "returned %d!\n", cdev->dev.bus_id, ret);
+                       dev_warn(&cdev->dev,
+                                "ccw_device_stlck returned %d!\n", ret);
                        return;
                }
                if (cdev->id.cu_type == 0)
@@ -546,7 +610,7 @@ static struct attribute_group ccwdev_attr_group = {
        .attrs = ccwdev_attrs,
 };
 
-struct attribute_group *ccwdev_attr_groups[] = {
+static struct attribute_group *ccwdev_attr_groups[] = {
        &ccwdev_attr_group,
        NULL,
 };
@@ -866,8 +930,10 @@ io_subchannel_register(struct work_struct *work)
                        ret = device_reprobe(&cdev->dev);
                        if (ret)
                                /* We can't do much here. */
-                               dev_info(&cdev->dev, "device_reprobe() returned"
-                                        " %d\n", ret);
+                               CIO_MSG_EVENT(2, "device_reprobe() returned"
+                                             " %d for 0.%x.%04x\n", ret,
+                                             cdev->private->dev_id.ssid,
+                                             cdev->private->dev_id.devno);
                }
                goto out;
        }
@@ -880,8 +946,9 @@ io_subchannel_register(struct work_struct *work)
        /* make it known to the system */
        ret = ccw_device_register(cdev);
        if (ret) {
-               printk (KERN_WARNING "%s: could not register %s\n",
-                       __func__, cdev->dev.bus_id);
+               CIO_MSG_EVENT(0, "Could not register ccw dev 0.%x.%04x: %d\n",
+                             cdev->private->dev_id.ssid,
+                             cdev->private->dev_id.devno, ret);
                put_device(&cdev->dev);
                spin_lock_irqsave(sch->lock, flags);
                sch->dev.driver_data = NULL;
@@ -902,8 +969,7 @@ out:
                wake_up(&ccw_device_init_wq);
 }
 
-void
-ccw_device_call_sch_unregister(struct work_struct *work)
+static void ccw_device_call_sch_unregister(struct work_struct *work)
 {
        struct ccw_device_private *priv;
        struct ccw_device *cdev;
@@ -1056,6 +1122,7 @@ io_subchannel_probe (struct subchannel *sch)
                 * device, e.g. the console.
                 */
                cdev = sch->dev.driver_data;
+               cdev->dev.groups = ccwdev_attr_groups;
                device_initialize(&cdev->dev);
                ccw_device_register(cdev);
                /*
@@ -1281,8 +1348,19 @@ __ccwdev_check_busid(struct device *dev, void *id)
 }
 
 
-struct ccw_device *
-get_ccwdev_by_busid(struct ccw_driver *cdrv, const char *bus_id)
+/**
+ * get_ccwdev_by_busid() - obtain device from a bus id
+ * @cdrv: driver the device is owned by
+ * @bus_id: bus id of the device to be searched
+ *
+ * This function searches all devices owned by @cdrv for a device with a bus
+ * id matching @bus_id.
+ * Returns:
+ *  If a match is found, its reference count of the found device is increased
+ *  and it is returned; else %NULL is returned.
+ */
+struct ccw_device *get_ccwdev_by_busid(struct ccw_driver *cdrv,
+                                      const char *bus_id)
 {
        struct device *dev;
        struct device_driver *drv;
@@ -1334,7 +1412,6 @@ ccw_device_remove (struct device *dev)
        struct ccw_driver *cdrv = cdev->drv;
        int ret;
 
-       pr_debug("removing device %s\n", cdev->dev.bus_id);
        if (cdrv->remove)
                cdrv->remove(cdev);
        if (cdev->online) {
@@ -1347,24 +1424,44 @@ ccw_device_remove (struct device *dev)
                                   dev_fsm_final_state(cdev));
                else
                        //FIXME: we can't fail!
-                       pr_debug("ccw_device_offline returned %d, device %s\n",
-                                ret, cdev->dev.bus_id);
+                       CIO_MSG_EVENT(2, "ccw_device_offline returned %d, "
+                                     "device 0.%x.%04x\n",
+                                     ret, cdev->private->dev_id.ssid,
+                                     cdev->private->dev_id.devno);
        }
        ccw_device_set_timeout(cdev, 0);
        cdev->drv = NULL;
        return 0;
 }
 
+static void ccw_device_shutdown(struct device *dev)
+{
+       struct ccw_device *cdev;
+
+       cdev = to_ccwdev(dev);
+       if (cdev->drv && cdev->drv->shutdown)
+               cdev->drv->shutdown(cdev);
+       disable_cmf(cdev);
+}
+
 struct bus_type ccw_bus_type = {
        .name   = "ccw",
        .match  = ccw_bus_match,
        .uevent = ccw_uevent,
        .probe  = ccw_device_probe,
        .remove = ccw_device_remove,
+       .shutdown = ccw_device_shutdown,
 };
 
-int
-ccw_driver_register (struct ccw_driver *cdriver)
+/**
+ * ccw_driver_register() - register a ccw driver
+ * @cdriver: driver to be registered
+ *
+ * This function is mainly a wrapper around driver_register().
+ * Returns:
+ *   %0 on success and a negative error value on failure.
+ */
+int ccw_driver_register(struct ccw_driver *cdriver)
 {
        struct device_driver *drv = &cdriver->driver;
 
@@ -1374,8 +1471,13 @@ ccw_driver_register (struct ccw_driver *cdriver)
        return driver_register(drv);
 }
 
-void
-ccw_driver_unregister (struct ccw_driver *cdriver)
+/**
+ * ccw_driver_unregister() - deregister a ccw driver
+ * @cdriver: driver to be deregistered
+ *
+ * This function is mainly a wrapper around driver_unregister().
+ */
+void ccw_driver_unregister(struct ccw_driver *cdriver)
 {
        driver_unregister(&cdriver->driver);
 }