const: constify remaining dev_pm_ops
[safe/jmp/linux-2.6] / drivers / s390 / cio / ccwgroup.c
index 38954f5..5f97ea2 100644 (file)
@@ -1,11 +1,10 @@
 /*
- *  drivers/s390/cio/ccwgroup.c
  *  bus driver for ccwgroup
  *
- *    Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
- *                       IBM Corporation
- *    Author(s): Arnd Bergmann (arndb@de.ibm.com)
- *               Cornelia Huck (cornelia.huck@de.ibm.com)
+ *  Copyright IBM Corp. 2002, 2009
+ *
+ *  Author(s): Arnd Bergmann (arndb@de.ibm.com)
+ *            Cornelia Huck (cornelia.huck@de.ibm.com)
  */
 #include <linux/module.h>
 #include <linux/errno.h>
 #include <linux/ctype.h>
 #include <linux/dcache.h>
 
-#include <asm/semaphore.h>
 #include <asm/ccwdev.h>
 #include <asm/ccwgroup.h>
 
+#define CCW_BUS_ID_SIZE                20
+
 /* In Linux 2.4, we had a channel device layer called "chandev"
  * that did all sorts of obscure stuff for networking devices.
  * This is another driver that serves as a replacement for just
@@ -35,8 +35,8 @@ ccwgroup_bus_match (struct device * dev, struct device_driver * drv)
        struct ccwgroup_device *gdev;
        struct ccwgroup_driver *gdrv;
 
-       gdev = container_of(dev, struct ccwgroup_device, dev);
-       gdrv = container_of(drv, struct ccwgroup_driver, driver);
+       gdev = to_ccwgroupdev(dev);
+       gdrv = to_ccwgroupdrv(drv);
 
        if (gdev->creator_id == gdrv->driver_id)
                return 1;
@@ -44,8 +44,7 @@ ccwgroup_bus_match (struct device * dev, struct device_driver * drv)
        return 0;
 }
 static int
-ccwgroup_uevent (struct device *dev, char **envp, int num_envp, char *buffer,
-                 int buffer_size)
+ccwgroup_uevent (struct device *dev, struct kobj_uevent_env *env)
 {
        /* TODO */
        return 0;
@@ -53,7 +52,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 +70,44 @@ __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);
+       if (device_is_registered(&gdev->dev)) {
+               __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);
-
+       /* Prevent concurrent online/offline processing and ungrouping. */
+       if (atomic_cmpxchg(&gdev->onoff, 0, 1) != 0)
+               return -EAGAIN;
+       if (gdev->state != CCWGROUP_OFFLINE) {
+               rc = -EINVAL;
+               goto out;
+       }
+       /* 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);
+out:
+       if (rc) {
+               if (rc != -EAGAIN)
+                       /* Release onoff "lock" when ungrouping failed. */
+                       atomic_set(&gdev->onoff, 0);
+               return rc;
+       }
        return count;
 }
 
@@ -98,13 +122,16 @@ ccwgroup_release (struct device *dev)
        gdev = to_ccwgroupdev(dev);
 
        for (i = 0; i < gdev->count; i++) {
-               gdev->cdev[i]->dev.driver_data = NULL;
-               put_device(&gdev->cdev[i]->dev);
+               if (gdev->cdev[i]) {
+                       if (dev_get_drvdata(&gdev->cdev[i]->dev) == gdev)
+                               dev_set_drvdata(&gdev->cdev[i]->dev, NULL);
+                       put_device(&gdev->cdev[i]->dev);
+               }
        }
        kfree(gdev);
 }
 
-static inline int
+static int
 __ccwgroup_create_symlinks(struct ccwgroup_device *gdev)
 {
        char str[8];
@@ -138,62 +165,125 @@ __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.
+static int __get_next_bus_id(const char **buf, char *bus_id)
+{
+       int rc, len;
+       char *start, *end;
+
+       start = (char *)*buf;
+       end = strchr(start, ',');
+       if (!end) {
+               /* Last entry. Strip trailing newline, if applicable. */
+               end = strchr(start, '\n');
+               if (end)
+                       *end = '\0';
+               len = strlen(start) + 1;
+       } else {
+               len = end - start + 1;
+               end++;
+       }
+       if (len < CCW_BUS_ID_SIZE) {
+               strlcpy(bus_id, start, len);
+               rc = 0;
+       } else
+               rc = -EINVAL;
+       *buf = end;
+       return rc;
+}
+
+static int __is_valid_bus_id(char bus_id[CCW_BUS_ID_SIZE])
+{
+       int cssid, ssid, devno;
+
+       /* Must be of form %x.%x.%04x */
+       if (sscanf(bus_id, "%x.%1x.%04x", &cssid, &ssid, &devno) != 3)
+               return 0;
+       return 1;
+}
+
+/**
+ * ccwgroup_create_from_string() - 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
+ * @num_devices: number of slave devices
+ * @buf: buffer containing comma separated 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 @buf 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_from_string(struct device *root, unsigned int creator_id,
+                               struct ccw_driver *cdrv, int num_devices,
+                               const char *buf)
 {
        struct ccwgroup_device *gdev;
-       int i;
-       int rc;
+       int rc, i;
+       char tmp_bus_id[CCW_BUS_ID_SIZE];
+       const char *curr_buf;
 
-       if (argc > 256) /* disallow dumb users */
-               return -EINVAL;
-
-       gdev = kzalloc(sizeof(*gdev) + argc*sizeof(gdev->cdev[0]), GFP_KERNEL);
+       gdev = kzalloc(sizeof(*gdev) + num_devices * sizeof(gdev->cdev[0]),
+                      GFP_KERNEL);
        if (!gdev)
                return -ENOMEM;
 
        atomic_set(&gdev->onoff, 0);
-
-       for (i = 0; i < argc; i++) {
-               gdev->cdev[i] = get_ccwdev_by_busid(cdrv, argv[i]);
-
-               /* all devices have to be of the same type in
-                * order to be grouped */
+       mutex_init(&gdev->reg_mutex);
+       mutex_lock(&gdev->reg_mutex);
+       gdev->creator_id = creator_id;
+       gdev->count = num_devices;
+       gdev->dev.bus = &ccwgroup_bus_type;
+       gdev->dev.parent = root;
+       gdev->dev.release = ccwgroup_release;
+       device_initialize(&gdev->dev);
+
+       curr_buf = buf;
+       for (i = 0; i < num_devices && curr_buf; i++) {
+               rc = __get_next_bus_id(&curr_buf, tmp_bus_id);
+               if (rc != 0)
+                       goto error;
+               if (!__is_valid_bus_id(tmp_bus_id)) {
+                       rc = -EINVAL;
+                       goto error;
+               }
+               gdev->cdev[i] = get_ccwdev_by_busid(cdrv, tmp_bus_id);
+               /*
+                * All devices have to be of the same type in
+                * order to be grouped.
+                */
                if (!gdev->cdev[i]
                    || 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) {
+               if (dev_get_drvdata(&gdev->cdev[i]->dev)) {
                        rc = -EINVAL;
-                       goto free_dev;
+                       goto error;
                }
-               gdev->cdev[i]->dev.driver_data = gdev;
+               dev_set_drvdata(&gdev->cdev[i]->dev, gdev);
+       }
+       /* Check for sufficient number of bus ids. */
+       if (i < num_devices && !curr_buf) {
+               rc = -EINVAL;
+               goto error;
+       }
+       /* Check for trailing stuff. */
+       if (i == num_devices && strlen(curr_buf) > 0) {
+               rc = -EINVAL;
+               goto error;
        }
 
-       gdev->creator_id = creator_id;
-       gdev->count = argc;
-       gdev->dev.bus = &ccwgroup_bus_type;
-       gdev->dev.parent = root;
-       gdev->dev.release = ccwgroup_release;
-
-       snprintf (gdev->dev.bus_id, BUS_ID_SIZE, "%s",
-                       gdev->cdev[0]->dev.bus_id);
+       dev_set_name(&gdev->dev, "%s", dev_name(&gdev->cdev[0]->dev));
 
-       rc = device_register(&gdev->dev);
-       
+       rc = device_add(&gdev->dev);
        if (rc)
-               goto free_dev;
+               goto error;
        get_device(&gdev->dev);
        rc = device_create_file(&gdev->dev, &dev_attr_ungroup);
 
@@ -204,40 +294,52 @@ 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++)
+       for (i = 0; i < num_devices; i++)
                if (gdev->cdev[i]) {
+                       if (dev_get_drvdata(&gdev->cdev[i]->dev) == gdev)
+                               dev_set_drvdata(&gdev->cdev[i]->dev, NULL);
                        put_device(&gdev->cdev[i]->dev);
-                       gdev->cdev[i]->dev.driver_data = NULL;
+                       gdev->cdev[i] = NULL;
                }
+       mutex_unlock(&gdev->reg_mutex);
        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);
-       return rc;
 }
+EXPORT_SYMBOL(ccwgroup_create_from_string);
+
+static int ccwgroup_notifier(struct notifier_block *nb, unsigned long action,
+                            void *data);
 
-static int __init
-init_ccwgroup (void)
+static struct notifier_block ccwgroup_nb = {
+       .notifier_call = ccwgroup_notifier
+};
+
+static int __init init_ccwgroup(void)
 {
-       return bus_register (&ccwgroup_bus_type);
+       int ret;
+
+       ret = bus_register(&ccwgroup_bus_type);
+       if (ret)
+               return ret;
+
+       ret = bus_register_notifier(&ccwgroup_bus_type, &ccwgroup_nb);
+       if (ret)
+               bus_unregister(&ccwgroup_bus_type);
+
+       return ret;
 }
 
-static void __exit
-cleanup_ccwgroup (void)
+static void __exit cleanup_ccwgroup(void)
 {
-       bus_unregister (&ccwgroup_bus_type);
+       bus_unregister_notifier(&ccwgroup_bus_type, &ccwgroup_nb);
+       bus_unregister(&ccwgroup_bus_type);
 }
 
 module_init(init_ccwgroup);
@@ -302,27 +404,31 @@ ccwgroup_online_store (struct device *dev, struct device_attribute *attr, const
 {
        struct ccwgroup_device *gdev;
        struct ccwgroup_driver *gdrv;
-       unsigned int value;
+       unsigned long value;
        int ret;
 
-       gdev = to_ccwgroupdev(dev);
        if (!dev->driver)
-               return count;
+               return -ENODEV;
+
+       gdev = to_ccwgroupdev(dev);
+       gdrv = to_ccwgroupdrv(dev->driver);
 
-       gdrv = to_ccwgroupdrv (gdev->dev.driver);
        if (!try_module_get(gdrv->owner))
                return -EINVAL;
 
-       value = simple_strtoul(buf, NULL, 0);
-       ret = count;
+       ret = strict_strtoul(buf, 0, &value);
+       if (ret)
+               goto out;
+
        if (value == 1)
-               ccwgroup_set_online(gdev);
+               ret = ccwgroup_set_online(gdev);
        else if (value == 0)
-               ccwgroup_set_offline(gdev);
+               ret = ccwgroup_set_offline(gdev);
        else
                ret = -EINVAL;
+out:
        module_put(gdrv->owner);
-       return ret;
+       return (ret == 0) ? count : ret;
 }
 
 static ssize_t
@@ -351,7 +457,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);
@@ -365,32 +470,139 @@ ccwgroup_remove (struct device *dev)
        struct ccwgroup_device *gdev;
        struct ccwgroup_driver *gdrv;
 
-       gdev = to_ccwgroupdev(dev);
-       gdrv = to_ccwgroupdrv(dev->driver);
+       device_remove_file(dev, &dev_attr_online);
+       device_remove_file(dev, &dev_attr_ungroup);
 
-       pr_debug("%s: device %s\n", __func__, gdev->dev.bus_id);
+       if (!dev->driver)
+               return 0;
 
-       device_remove_file(dev, &dev_attr_online);
+       gdev = to_ccwgroupdev(dev);
+       gdrv = to_ccwgroupdrv(dev->driver);
 
-       if (gdrv && gdrv->remove)
+       if (gdrv->remove)
                gdrv->remove(gdev);
+
        return 0;
 }
 
+static void ccwgroup_shutdown(struct device *dev)
+{
+       struct ccwgroup_device *gdev;
+       struct ccwgroup_driver *gdrv;
+
+       if (!dev->driver)
+               return;
+
+       gdev = to_ccwgroupdev(dev);
+       gdrv = to_ccwgroupdrv(dev->driver);
+
+       if (gdrv->shutdown)
+               gdrv->shutdown(gdev);
+}
+
+static int ccwgroup_pm_prepare(struct device *dev)
+{
+       struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
+       struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);
+
+       /* Fail while device is being set online/offline. */
+       if (atomic_read(&gdev->onoff))
+               return -EAGAIN;
+
+       if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
+               return 0;
+
+       return gdrv->prepare ? gdrv->prepare(gdev) : 0;
+}
+
+static void ccwgroup_pm_complete(struct device *dev)
+{
+       struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
+       struct ccwgroup_driver *gdrv = to_ccwgroupdrv(dev->driver);
+
+       if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
+               return;
+
+       if (gdrv->complete)
+               gdrv->complete(gdev);
+}
+
+static int ccwgroup_pm_freeze(struct device *dev)
+{
+       struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
+       struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);
+
+       if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
+               return 0;
+
+       return gdrv->freeze ? gdrv->freeze(gdev) : 0;
+}
+
+static int ccwgroup_pm_thaw(struct device *dev)
+{
+       struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
+       struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);
+
+       if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
+               return 0;
+
+       return gdrv->thaw ? gdrv->thaw(gdev) : 0;
+}
+
+static int ccwgroup_pm_restore(struct device *dev)
+{
+       struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
+       struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);
+
+       if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
+               return 0;
+
+       return gdrv->restore ? gdrv->restore(gdev) : 0;
+}
+
+static const struct dev_pm_ops ccwgroup_pm_ops = {
+       .prepare = ccwgroup_pm_prepare,
+       .complete = ccwgroup_pm_complete,
+       .freeze = ccwgroup_pm_freeze,
+       .thaw = ccwgroup_pm_thaw,
+       .restore = ccwgroup_pm_restore,
+};
+
 static struct bus_type ccwgroup_bus_type = {
        .name   = "ccwgroup",
        .match  = ccwgroup_bus_match,
        .uevent = ccwgroup_uevent,
        .probe  = ccwgroup_probe,
        .remove = ccwgroup_remove,
+       .shutdown = ccwgroup_shutdown,
+       .pm = &ccwgroup_pm_ops,
 };
 
-int
-ccwgroup_driver_register (struct ccwgroup_driver *cdriver)
+
+static int ccwgroup_notifier(struct notifier_block *nb, unsigned long action,
+                            void *data)
+{
+       struct device *dev = data;
+
+       if (action == BUS_NOTIFY_UNBIND_DRIVER)
+               device_schedule_callback(dev, ccwgroup_ungroup_callback);
+
+       return NOTIFY_OK;
+}
+
+
+/**
+ * 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;
        cdriver->driver.name = cdriver->name;
+       cdriver->driver.owner = cdriver->owner;
 
        return driver_register(&cdriver->driver);
 }
@@ -401,8 +613,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,30 +627,44 @@ 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;
 
-       if (cdev->dev.driver_data) {
-               gdev = (struct ccwgroup_device *)cdev->dev.driver_data;
+       gdev = dev_get_drvdata(&cdev->dev);
+       if (gdev) {
                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 +672,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 +691,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);
        }
 }
@@ -460,6 +699,5 @@ ccwgroup_remove_ccwdev(struct ccw_device *cdev)
 MODULE_LICENSE("GPL");
 EXPORT_SYMBOL(ccwgroup_driver_register);
 EXPORT_SYMBOL(ccwgroup_driver_unregister);
-EXPORT_SYMBOL(ccwgroup_create);
 EXPORT_SYMBOL(ccwgroup_probe_ccwdev);
 EXPORT_SYMBOL(ccwgroup_remove_ccwdev);