]> err.no Git - linux-2.6/blobdiff - drivers/s390/cio/ccwgroup.c
[S390] cio: Disable channel measurements (cmf) on shutdown/reboot.
[linux-2.6] / drivers / s390 / cio / ccwgroup.c
index 38954f5cd14c8d542c92999ddcdf3fc7f6d337ef..9c3b9ea1e66f9de6cd6c1f11c128822587523762 100644 (file)
@@ -53,7 +53,7 @@ ccwgroup_uevent (struct device *dev, char **envp, int num_envp, char *buffer,
 
 static struct bus_type ccwgroup_bus_type;
 
-static inline void
+static void
 __ccwgroup_remove_symlinks(struct ccwgroup_device *gdev)
 {
        int i;
@@ -71,19 +71,33 @@ __ccwgroup_remove_symlinks(struct ccwgroup_device *gdev)
  * Provide an 'ungroup' attribute so the user can remove group devices no
  * longer needed or accidentially created. Saves memory :)
  */
+static void ccwgroup_ungroup_callback(struct device *dev)
+{
+       struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
+
+       mutex_lock(&gdev->reg_mutex);
+       __ccwgroup_remove_symlinks(gdev);
+       device_unregister(dev);
+       mutex_unlock(&gdev->reg_mutex);
+}
+
 static ssize_t
 ccwgroup_ungroup_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
 {
        struct ccwgroup_device *gdev;
+       int rc;
 
        gdev = to_ccwgroupdev(dev);
 
        if (gdev->state != CCWGROUP_OFFLINE)
                return -EINVAL;
 
-       __ccwgroup_remove_symlinks(gdev);
-       device_unregister(dev);
-
+       /* Note that we cannot unregister the device from one of its
+        * attribute methods, so we have to use this roundabout approach.
+        */
+       rc = device_schedule_callback(dev, ccwgroup_ungroup_callback);
+       if (rc)
+               count = rc;
        return count;
 }
 
@@ -104,7 +118,7 @@ ccwgroup_release (struct device *dev)
        kfree(gdev);
 }
 
-static inline int
+static int
 __ccwgroup_create_symlinks(struct ccwgroup_device *gdev)
 {
        char str[8];
@@ -138,16 +152,24 @@ __ccwgroup_create_symlinks(struct ccwgroup_device *gdev)
        return 0;
 }
 
-/*
- * try to add a new ccwgroup device for one driver
- * argc and argv[] are a list of bus_id's of devices
- * belonging to the driver.
+/**
+ * ccwgroup_create() - create and register a ccw group device
+ * @root: parent device for the new device
+ * @creator_id: identifier of creating driver
+ * @cdrv: ccw driver of slave devices
+ * @argc: number of slave devices
+ * @argv: bus ids of slave devices
+ *
+ * Create and register a new ccw group device as a child of @root. Slave
+ * devices are obtained from the list of bus ids given in @argv[] and must all
+ * belong to @cdrv.
+ * Returns:
+ *  %0 on success and an error code on failure.
+ * Context:
+ *  non-atomic
  */
-int
-ccwgroup_create(struct device *root,
-               unsigned int creator_id,
-               struct ccw_driver *cdrv,
-               int argc, char *argv[])
+int ccwgroup_create(struct device *root, unsigned int creator_id,
+                   struct ccw_driver *cdrv, int argc, char *argv[])
 {
        struct ccwgroup_device *gdev;
        int i;
@@ -161,7 +183,8 @@ ccwgroup_create(struct device *root,
                return -ENOMEM;
 
        atomic_set(&gdev->onoff, 0);
-
+       mutex_init(&gdev->reg_mutex);
+       mutex_lock(&gdev->reg_mutex);
        for (i = 0; i < argc; i++) {
                gdev->cdev[i] = get_ccwdev_by_busid(cdrv, argv[i]);
 
@@ -171,12 +194,12 @@ ccwgroup_create(struct device *root,
                    || gdev->cdev[i]->id.driver_info !=
                    gdev->cdev[0]->id.driver_info) {
                        rc = -EINVAL;
-                       goto free_dev;
+                       goto error;
                }
                /* Don't allow a device to belong to more than one group. */
                if (gdev->cdev[i]->dev.driver_data) {
                        rc = -EINVAL;
-                       goto free_dev;
+                       goto error;
                }
                gdev->cdev[i]->dev.driver_data = gdev;
        }
@@ -191,9 +214,8 @@ ccwgroup_create(struct device *root,
                        gdev->cdev[0]->dev.bus_id);
 
        rc = device_register(&gdev->dev);
-       
        if (rc)
-               goto free_dev;
+               goto error;
        get_device(&gdev->dev);
        rc = device_create_file(&gdev->dev, &dev_attr_ungroup);
 
@@ -204,27 +226,21 @@ ccwgroup_create(struct device *root,
 
        rc = __ccwgroup_create_symlinks(gdev);
        if (!rc) {
+               mutex_unlock(&gdev->reg_mutex);
                put_device(&gdev->dev);
                return 0;
        }
        device_remove_file(&gdev->dev, &dev_attr_ungroup);
        device_unregister(&gdev->dev);
 error:
-       for (i = 0; i < argc; i++)
-               if (gdev->cdev[i]) {
-                       put_device(&gdev->cdev[i]->dev);
-                       gdev->cdev[i]->dev.driver_data = NULL;
-               }
-       put_device(&gdev->dev);
-       return rc;
-free_dev:
        for (i = 0; i < argc; i++)
                if (gdev->cdev[i]) {
                        if (gdev->cdev[i]->dev.driver_data == gdev)
                                gdev->cdev[i]->dev.driver_data = NULL;
                        put_device(&gdev->cdev[i]->dev);
                }
-       kfree(gdev);
+       mutex_unlock(&gdev->reg_mutex);
+       put_device(&gdev->dev);
        return rc;
 }
 
@@ -351,7 +367,6 @@ ccwgroup_probe (struct device *dev)
        if ((ret = device_create_file(dev, &dev_attr_online)))
                return ret;
 
-       pr_debug("%s: device %s\n", __func__, gdev->dev.bus_id);
        ret = gdrv->probe ? gdrv->probe(gdev) : -ENODEV;
        if (ret)
                device_remove_file(dev, &dev_attr_online);
@@ -368,8 +383,6 @@ ccwgroup_remove (struct device *dev)
        gdev = to_ccwgroupdev(dev);
        gdrv = to_ccwgroupdrv(dev->driver);
 
-       pr_debug("%s: device %s\n", __func__, gdev->dev.bus_id);
-
        device_remove_file(dev, &dev_attr_online);
 
        if (gdrv && gdrv->remove)
@@ -385,8 +398,13 @@ static struct bus_type ccwgroup_bus_type = {
        .remove = ccwgroup_remove,
 };
 
-int
-ccwgroup_driver_register (struct ccwgroup_driver *cdriver)
+/**
+ * ccwgroup_driver_register() - register a ccw group driver
+ * @cdriver: driver to be registered
+ *
+ * This function is mainly a wrapper around driver_register().
+ */
+int ccwgroup_driver_register(struct ccwgroup_driver *cdriver)
 {
        /* register our new driver with the core */
        cdriver->driver.bus = &ccwgroup_bus_type;
@@ -401,8 +419,13 @@ __ccwgroup_match_all(struct device *dev, void *data)
        return 1;
 }
 
-void
-ccwgroup_driver_unregister (struct ccwgroup_driver *cdriver)
+/**
+ * ccwgroup_driver_unregister() - deregister a ccw group driver
+ * @cdriver: driver to be deregistered
+ *
+ * This function is mainly a wrapper around driver_unregister().
+ */
+void ccwgroup_driver_unregister(struct ccwgroup_driver *cdriver)
 {
        struct device *dev;
 
@@ -410,21 +433,33 @@ ccwgroup_driver_unregister (struct ccwgroup_driver *cdriver)
        get_driver(&cdriver->driver);
        while ((dev = driver_find_device(&cdriver->driver, NULL, NULL,
                                         __ccwgroup_match_all))) {
-               __ccwgroup_remove_symlinks(to_ccwgroupdev(dev));
+               struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
+
+               mutex_lock(&gdev->reg_mutex);
+               __ccwgroup_remove_symlinks(gdev);
                device_unregister(dev);
+               mutex_unlock(&gdev->reg_mutex);
                put_device(dev);
        }
        put_driver(&cdriver->driver);
        driver_unregister(&cdriver->driver);
 }
 
-int
-ccwgroup_probe_ccwdev(struct ccw_device *cdev)
+/**
+ * ccwgroup_probe_ccwdev() - probe function for slave devices
+ * @cdev: ccw device to be probed
+ *
+ * This is a dummy probe function for ccw devices that are slave devices in
+ * a ccw group device.
+ * Returns:
+ *  always %0
+ */
+int ccwgroup_probe_ccwdev(struct ccw_device *cdev)
 {
        return 0;
 }
 
-static inline struct ccwgroup_device *
+static struct ccwgroup_device *
 __ccwgroup_get_gdev_by_cdev(struct ccw_device *cdev)
 {
        struct ccwgroup_device *gdev;
@@ -432,8 +467,10 @@ __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)) {
+                       mutex_lock(&gdev->reg_mutex);
                        if (device_is_registered(&gdev->dev))
                                return gdev;
+                       mutex_unlock(&gdev->reg_mutex);
                        put_device(&gdev->dev);
                }
                return NULL;
@@ -441,8 +478,15 @@ __ccwgroup_get_gdev_by_cdev(struct ccw_device *cdev)
        return NULL;
 }
 
-void
-ccwgroup_remove_ccwdev(struct ccw_device *cdev)
+/**
+ * ccwgroup_remove_ccwdev() - remove function for slave devices
+ * @cdev: ccw device to be removed
+ *
+ * This is a remove function for ccw devices that are slave devices in a ccw
+ * group device. It sets the ccw device offline and also deregisters the
+ * embedding ccw group device.
+ */
+void ccwgroup_remove_ccwdev(struct ccw_device *cdev)
 {
        struct ccwgroup_device *gdev;
 
@@ -453,6 +497,7 @@ ccwgroup_remove_ccwdev(struct ccw_device *cdev)
        if (gdev) {
                __ccwgroup_remove_symlinks(gdev);
                device_unregister(&gdev->dev);
+               mutex_unlock(&gdev->reg_mutex);
                put_device(&gdev->dev);
        }
 }