aboutsummaryrefslogtreecommitdiff
path: root/drivers/s390/cio/ccwgroup.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/s390/cio/ccwgroup.c')
-rw-r--r--drivers/s390/cio/ccwgroup.c747
1 files changed, 459 insertions, 288 deletions
diff --git a/drivers/s390/cio/ccwgroup.c b/drivers/s390/cio/ccwgroup.c
index 91ea8e4777f..e443b0d0b23 100644
--- a/drivers/s390/cio/ccwgroup.c
+++ b/drivers/s390/cio/ccwgroup.c
@@ -1,12 +1,10 @@
/*
- * drivers/s390/cio/ccwgroup.c
* bus driver for ccwgroup
- * $Revision: 1.29 $
*
- * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
- * IBM Corporation
- * Author(s): Arnd Bergmann (arndb@de.ibm.com)
- * Cornelia Huck (cohuck@de.ibm.com)
+ * Copyright IBM Corp. 2002, 2012
+ *
+ * Author(s): Arnd Bergmann (arndb@de.ibm.com)
+ * Cornelia Huck (cornelia.huck@de.ibm.com)
*/
#include <linux/module.h>
#include <linux/errno.h>
@@ -17,10 +15,14 @@
#include <linux/ctype.h>
#include <linux/dcache.h>
-#include <asm/semaphore.h>
+#include <asm/cio.h>
#include <asm/ccwdev.h>
#include <asm/ccwgroup.h>
+#include "device.h"
+
+#define CCW_BUS_ID_SIZE 10
+
/* 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
@@ -28,96 +30,220 @@
* to devices that use multiple subchannels.
*/
-/* a device matches a driver if all its slave devices match the same
- * entry of the driver */
-static int
-ccwgroup_bus_match (struct device * dev, struct device_driver * drv)
+static struct bus_type ccwgroup_bus_type;
+
+static void __ccwgroup_remove_symlinks(struct ccwgroup_device *gdev)
{
- struct ccwgroup_device *gdev;
- struct ccwgroup_driver *gdrv;
+ int i;
+ char str[8];
- gdev = container_of(dev, struct ccwgroup_device, dev);
- gdrv = container_of(drv, struct ccwgroup_driver, driver);
+ for (i = 0; i < gdev->count; i++) {
+ sprintf(str, "cdev%d", i);
+ sysfs_remove_link(&gdev->dev.kobj, str);
+ sysfs_remove_link(&gdev->cdev[i]->dev.kobj, "group_device");
+ }
+}
- if (gdev->creator_id == gdrv->driver_id)
- return 1;
+/*
+ * Remove references from ccw devices to ccw group device and from
+ * ccw group device to ccw devices.
+ */
+static void __ccwgroup_remove_cdev_refs(struct ccwgroup_device *gdev)
+{
+ struct ccw_device *cdev;
+ int i;
- return 0;
+ for (i = 0; i < gdev->count; i++) {
+ cdev = gdev->cdev[i];
+ if (!cdev)
+ continue;
+ spin_lock_irq(cdev->ccwlock);
+ dev_set_drvdata(&cdev->dev, NULL);
+ spin_unlock_irq(cdev->ccwlock);
+ gdev->cdev[i] = NULL;
+ put_device(&cdev->dev);
+ }
}
-static int
-ccwgroup_hotplug (struct device *dev, char **envp, int num_envp, char *buffer,
- int buffer_size)
+
+/**
+ * ccwgroup_set_online() - enable a ccwgroup device
+ * @gdev: target ccwgroup device
+ *
+ * This function attempts to put the ccwgroup device into the online state.
+ * Returns:
+ * %0 on success and a negative error value on failure.
+ */
+int ccwgroup_set_online(struct ccwgroup_device *gdev)
{
- /* TODO */
- return 0;
+ struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);
+ int ret = -EINVAL;
+
+ if (atomic_cmpxchg(&gdev->onoff, 0, 1) != 0)
+ return -EAGAIN;
+ if (gdev->state == CCWGROUP_ONLINE)
+ goto out;
+ if (gdrv->set_online)
+ ret = gdrv->set_online(gdev);
+ if (ret)
+ goto out;
+
+ gdev->state = CCWGROUP_ONLINE;
+out:
+ atomic_set(&gdev->onoff, 0);
+ return ret;
}
+EXPORT_SYMBOL(ccwgroup_set_online);
-static struct bus_type ccwgroup_bus_type = {
- .name = "ccwgroup",
- .match = ccwgroup_bus_match,
- .hotplug = ccwgroup_hotplug,
-};
+/**
+ * ccwgroup_set_offline() - disable a ccwgroup device
+ * @gdev: target ccwgroup device
+ *
+ * This function attempts to put the ccwgroup device into the offline state.
+ * Returns:
+ * %0 on success and a negative error value on failure.
+ */
+int ccwgroup_set_offline(struct ccwgroup_device *gdev)
+{
+ struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);
+ int ret = -EINVAL;
+
+ if (atomic_cmpxchg(&gdev->onoff, 0, 1) != 0)
+ return -EAGAIN;
+ if (gdev->state == CCWGROUP_OFFLINE)
+ goto out;
+ if (gdrv->set_offline)
+ ret = gdrv->set_offline(gdev);
+ if (ret)
+ goto out;
+
+ gdev->state = CCWGROUP_OFFLINE;
+out:
+ atomic_set(&gdev->onoff, 0);
+ return ret;
+}
+EXPORT_SYMBOL(ccwgroup_set_offline);
-static inline void
-__ccwgroup_remove_symlinks(struct ccwgroup_device *gdev)
+static ssize_t ccwgroup_online_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
{
- int i;
- char str[8];
+ struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
+ unsigned long value;
+ int ret;
- for (i = 0; i < gdev->count; i++) {
- sprintf(str, "cdev%d", i);
- sysfs_remove_link(&gdev->dev.kobj, str);
- sysfs_remove_link(&gdev->cdev[i]->dev.kobj, "group_device");
+ device_lock(dev);
+ if (!dev->driver) {
+ ret = -EINVAL;
+ goto out;
}
-
+
+ ret = kstrtoul(buf, 0, &value);
+ if (ret)
+ goto out;
+
+ if (value == 1)
+ ret = ccwgroup_set_online(gdev);
+ else if (value == 0)
+ ret = ccwgroup_set_offline(gdev);
+ else
+ ret = -EINVAL;
+out:
+ device_unlock(dev);
+ return (ret == 0) ? count : ret;
+}
+
+static ssize_t ccwgroup_online_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
+ int online;
+
+ online = (gdev->state == CCWGROUP_ONLINE) ? 1 : 0;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", online);
}
/*
* Provide an 'ungroup' attribute so the user can remove group devices no
* longer needed or accidentially created. Saves memory :)
*/
-static ssize_t
-ccwgroup_ungroup_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+static void ccwgroup_ungroup(struct ccwgroup_device *gdev)
{
- struct ccwgroup_device *gdev;
-
- gdev = to_ccwgroupdev(dev);
+ mutex_lock(&gdev->reg_mutex);
+ if (device_is_registered(&gdev->dev)) {
+ __ccwgroup_remove_symlinks(gdev);
+ device_unregister(&gdev->dev);
+ __ccwgroup_remove_cdev_refs(gdev);
+ }
+ mutex_unlock(&gdev->reg_mutex);
+}
- if (gdev->state != CCWGROUP_OFFLINE)
- return -EINVAL;
+static ssize_t ccwgroup_ungroup_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
+ int rc = 0;
- __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;
+ }
+ if (device_remove_file_self(dev, attr))
+ ccwgroup_ungroup(gdev);
+ else
+ rc = -ENODEV;
+out:
+ if (rc) {
+ /* Release onoff "lock" when ungrouping failed. */
+ atomic_set(&gdev->onoff, 0);
+ return rc;
+ }
return count;
}
-
static DEVICE_ATTR(ungroup, 0200, NULL, ccwgroup_ungroup_store);
+static DEVICE_ATTR(online, 0644, ccwgroup_online_show, ccwgroup_online_store);
+
+static struct attribute *ccwgroup_attrs[] = {
+ &dev_attr_online.attr,
+ &dev_attr_ungroup.attr,
+ NULL,
+};
+static struct attribute_group ccwgroup_attr_group = {
+ .attrs = ccwgroup_attrs,
+};
+static const struct attribute_group *ccwgroup_attr_groups[] = {
+ &ccwgroup_attr_group,
+ NULL,
+};
-static void
-ccwgroup_release (struct device *dev)
+static void ccwgroup_ungroup_workfn(struct work_struct *work)
{
- struct ccwgroup_device *gdev;
- int i;
+ struct ccwgroup_device *gdev =
+ container_of(work, struct ccwgroup_device, ungroup_work);
- gdev = to_ccwgroupdev(dev);
+ ccwgroup_ungroup(gdev);
+ put_device(&gdev->dev);
+}
- for (i = 0; i < gdev->count; i++) {
- gdev->cdev[i]->dev.driver_data = NULL;
- put_device(&gdev->cdev[i]->dev);
- }
- kfree(gdev);
+static void ccwgroup_release(struct device *dev)
+{
+ kfree(to_ccwgroupdev(dev));
}
-static inline int
-__ccwgroup_create_symlinks(struct ccwgroup_device *gdev)
+static int __ccwgroup_create_symlinks(struct ccwgroup_device *gdev)
{
char str[8];
int i, rc;
for (i = 0; i < gdev->count; i++) {
- rc = sysfs_create_link(&gdev->cdev[i]->dev.kobj, &gdev->dev.kobj,
- "group_device");
+ rc = sysfs_create_link(&gdev->cdev[i]->dev.kobj,
+ &gdev->dev.kobj, "group_device");
if (rc) {
for (--i; i >= 0; i--)
sysfs_remove_link(&gdev->cdev[i]->dev.kobj,
@@ -127,8 +253,8 @@ __ccwgroup_create_symlinks(struct ccwgroup_device *gdev)
}
for (i = 0; i < gdev->count; i++) {
sprintf(str, "cdev%d", i);
- rc = sysfs_create_link(&gdev->dev.kobj, &gdev->cdev[i]->dev.kobj,
- str);
+ rc = sysfs_create_link(&gdev->dev.kobj,
+ &gdev->cdev[i]->dev.kobj, str);
if (rc) {
for (--i; i >= 0; i--) {
sprintf(str, "cdev%d", i);
@@ -143,113 +269,182 @@ __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_id(const char **buf, struct ccw_dev_id *id)
+{
+ unsigned int cssid, ssid, devno;
+ int ret = 0, 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) {
+ if (sscanf(start, "%2x.%1x.%04x", &cssid, &ssid, &devno) != 3)
+ ret = -EINVAL;
+ } else
+ ret = -EINVAL;
+
+ if (!ret) {
+ id->ssid = ssid;
+ id->devno = devno;
+ }
+ *buf = end;
+ return ret;
+}
+
+/**
+ * ccwgroup_create_dev() - create and register a ccw group device
+ * @parent: parent device for the new device
+ * @gdrv: driver for the new group device
+ * @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 @parent. Slave
+ * devices are obtained from the list of bus ids given in @buf.
+ * 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_dev(struct device *parent, struct ccwgroup_driver *gdrv,
+ int num_devices, const char *buf)
{
struct ccwgroup_device *gdev;
- int i;
- int rc;
- int del_drvdata;
-
- if (argc > 256) /* disallow dumb users */
- return -EINVAL;
+ struct ccw_dev_id dev_id;
+ int rc, i;
- gdev = kmalloc(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;
- memset(gdev, 0, sizeof(*gdev) + argc*sizeof(gdev->cdev[0]));
atomic_set(&gdev->onoff, 0);
-
- del_drvdata = 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 */
- if (!gdev->cdev[i]
- || gdev->cdev[i]->id.driver_info !=
+ mutex_init(&gdev->reg_mutex);
+ mutex_lock(&gdev->reg_mutex);
+ INIT_WORK(&gdev->ungroup_work, ccwgroup_ungroup_workfn);
+ gdev->count = num_devices;
+ gdev->dev.bus = &ccwgroup_bus_type;
+ gdev->dev.parent = parent;
+ gdev->dev.release = ccwgroup_release;
+ device_initialize(&gdev->dev);
+
+ for (i = 0; i < num_devices && buf; i++) {
+ rc = __get_next_id(&buf, &dev_id);
+ if (rc != 0)
+ goto error;
+ gdev->cdev[i] = get_ccwdev_by_dev_id(&dev_id);
+ /*
+ * All devices have to be of the same type in
+ * order to be grouped.
+ */
+ if (!gdev->cdev[i] || !gdev->cdev[i]->drv ||
+ gdev->cdev[i]->drv != gdev->cdev[0]->drv ||
+ 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) {
+ spin_lock_irq(gdev->cdev[i]->ccwlock);
+ if (dev_get_drvdata(&gdev->cdev[i]->dev)) {
+ spin_unlock_irq(gdev->cdev[i]->ccwlock);
rc = -EINVAL;
- goto free_dev;
+ goto error;
}
+ dev_set_drvdata(&gdev->cdev[i]->dev, gdev);
+ spin_unlock_irq(gdev->cdev[i]->ccwlock);
}
- for (i = 0; i < argc; i++)
- gdev->cdev[i]->dev.driver_data = gdev;
- del_drvdata = 1;
-
- gdev->creator_id = creator_id;
- gdev->count = argc;
- gdev->dev = (struct device ) {
- .bus = &ccwgroup_bus_type,
- .parent = root,
- .release = ccwgroup_release,
- };
-
- snprintf (gdev->dev.bus_id, BUS_ID_SIZE, "%s",
- gdev->cdev[0]->dev.bus_id);
-
- rc = device_register(&gdev->dev);
-
- if (rc)
- goto free_dev;
- get_device(&gdev->dev);
- rc = device_create_file(&gdev->dev, &dev_attr_ungroup);
-
- if (rc) {
- device_unregister(&gdev->dev);
+ /* Check for sufficient number of bus ids. */
+ if (i < num_devices) {
+ rc = -EINVAL;
goto error;
}
+ /* Check for trailing stuff. */
+ if (i == num_devices && strlen(buf) > 0) {
+ rc = -EINVAL;
+ goto error;
+ }
+
+ dev_set_name(&gdev->dev, "%s", dev_name(&gdev->cdev[0]->dev));
+ gdev->dev.groups = ccwgroup_attr_groups;
+ if (gdrv) {
+ gdev->dev.driver = &gdrv->driver;
+ rc = gdrv->setup ? gdrv->setup(gdev) : 0;
+ if (rc)
+ goto error;
+ }
+ rc = device_add(&gdev->dev);
+ if (rc)
+ goto error;
rc = __ccwgroup_create_symlinks(gdev);
- if (!rc) {
- put_device(&gdev->dev);
- return 0;
+ if (rc) {
+ device_del(&gdev->dev);
+ goto error;
}
- device_remove_file(&gdev->dev, &dev_attr_ungroup);
- device_unregister(&gdev->dev);
+ mutex_unlock(&gdev->reg_mutex);
+ return 0;
error:
- for (i = 0; i < argc; i++)
+ for (i = 0; i < num_devices; i++)
if (gdev->cdev[i]) {
+ spin_lock_irq(gdev->cdev[i]->ccwlock);
+ if (dev_get_drvdata(&gdev->cdev[i]->dev) == gdev)
+ dev_set_drvdata(&gdev->cdev[i]->dev, NULL);
+ spin_unlock_irq(gdev->cdev[i]->ccwlock);
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]) {
- put_device(&gdev->cdev[i]->dev);
- if (del_drvdata)
- gdev->cdev[i]->dev.driver_data = NULL;
- }
- kfree(gdev);
- return rc;
}
+EXPORT_SYMBOL(ccwgroup_create_dev);
+
+static int ccwgroup_notifier(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ struct ccwgroup_device *gdev = to_ccwgroupdev(data);
+
+ if (action == BUS_NOTIFY_UNBIND_DRIVER) {
+ get_device(&gdev->dev);
+ schedule_work(&gdev->ungroup_work);
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block ccwgroup_nb = {
+ .notifier_call = ccwgroup_notifier
+};
-static int __init
-init_ccwgroup (void)
+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);
@@ -257,214 +452,190 @@ module_exit(cleanup_ccwgroup);
/************************** driver stuff ******************************/
-static int
-ccwgroup_set_online(struct ccwgroup_device *gdev)
+static int ccwgroup_remove(struct device *dev)
{
- struct ccwgroup_driver *gdrv;
- int ret;
+ struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
+ struct ccwgroup_driver *gdrv = to_ccwgroupdrv(dev->driver);
- if (atomic_compare_and_swap(0, 1, &gdev->onoff))
- return -EAGAIN;
- if (gdev->state == CCWGROUP_ONLINE) {
- ret = 0;
- goto out;
- }
- if (!gdev->dev.driver) {
- ret = -EINVAL;
- goto out;
- }
- gdrv = to_ccwgroupdrv (gdev->dev.driver);
- if ((ret = gdrv->set_online(gdev)))
- goto out;
+ if (!dev->driver)
+ return 0;
+ if (gdrv->remove)
+ gdrv->remove(gdev);
- gdev->state = CCWGROUP_ONLINE;
- out:
- atomic_set(&gdev->onoff, 0);
- return ret;
+ return 0;
}
-static int
-ccwgroup_set_offline(struct ccwgroup_device *gdev)
+static void ccwgroup_shutdown(struct device *dev)
{
- struct ccwgroup_driver *gdrv;
- int ret;
+ struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
+ struct ccwgroup_driver *gdrv = to_ccwgroupdrv(dev->driver);
- if (atomic_compare_and_swap(0, 1, &gdev->onoff))
- return -EAGAIN;
- if (gdev->state == CCWGROUP_OFFLINE) {
- ret = 0;
- goto out;
- }
- if (!gdev->dev.driver) {
- ret = -EINVAL;
- goto out;
- }
- gdrv = to_ccwgroupdrv (gdev->dev.driver);
- if ((ret = gdrv->set_offline(gdev)))
- goto out;
-
- gdev->state = CCWGROUP_OFFLINE;
- out:
- atomic_set(&gdev->onoff, 0);
- return ret;
+ if (!dev->driver)
+ return;
+ if (gdrv->shutdown)
+ gdrv->shutdown(gdev);
}
-static ssize_t
-ccwgroup_online_store (struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+static int ccwgroup_pm_prepare(struct device *dev)
{
- struct ccwgroup_device *gdev;
- struct ccwgroup_driver *gdrv;
- unsigned int value;
- int ret;
+ struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
+ struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);
- gdev = to_ccwgroupdev(dev);
- if (!dev->driver)
- return count;
+ /* Fail while device is being set online/offline. */
+ if (atomic_read(&gdev->onoff))
+ return -EAGAIN;
- gdrv = to_ccwgroupdrv (gdev->dev.driver);
- if (!try_module_get(gdrv->owner))
- return -EINVAL;
+ if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
+ return 0;
- value = simple_strtoul(buf, 0, 0);
- ret = count;
- if (value == 1)
- ccwgroup_set_online(gdev);
- else if (value == 0)
- ccwgroup_set_offline(gdev);
- else
- ret = -EINVAL;
- module_put(gdrv->owner);
- return ret;
+ return gdrv->prepare ? gdrv->prepare(gdev) : 0;
}
-static ssize_t
-ccwgroup_online_show (struct device *dev, struct device_attribute *attr, char *buf)
+static void ccwgroup_pm_complete(struct device *dev)
{
- int online;
+ struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
+ struct ccwgroup_driver *gdrv = to_ccwgroupdrv(dev->driver);
- online = (to_ccwgroupdev(dev)->state == CCWGROUP_ONLINE);
+ if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
+ return;
- return sprintf(buf, online ? "1\n" : "0\n");
+ if (gdrv->complete)
+ gdrv->complete(gdev);
}
-static DEVICE_ATTR(online, 0644, ccwgroup_online_show, ccwgroup_online_store);
-
-static int
-ccwgroup_probe (struct device *dev)
+static int ccwgroup_pm_freeze(struct device *dev)
{
- struct ccwgroup_device *gdev;
- struct ccwgroup_driver *gdrv;
+ struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
+ struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);
- int ret;
+ if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
+ return 0;
- gdev = to_ccwgroupdev(dev);
- gdrv = to_ccwgroupdrv(dev->driver);
+ return gdrv->freeze ? gdrv->freeze(gdev) : 0;
+}
- if ((ret = device_create_file(dev, &dev_attr_online)))
- return ret;
+static int ccwgroup_pm_thaw(struct device *dev)
+{
+ struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
+ struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);
- 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);
+ if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
+ return 0;
- return ret;
+ return gdrv->thaw ? gdrv->thaw(gdev) : 0;
}
-static int
-ccwgroup_remove (struct device *dev)
+static int ccwgroup_pm_restore(struct device *dev)
{
- struct ccwgroup_device *gdev;
- struct ccwgroup_driver *gdrv;
+ struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
+ struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);
- gdev = to_ccwgroupdev(dev);
- gdrv = to_ccwgroupdrv(dev->driver);
+ if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
+ return 0;
- pr_debug("%s: device %s\n", __func__, gdev->dev.bus_id);
+ return gdrv->restore ? gdrv->restore(gdev) : 0;
+}
- device_remove_file(dev, &dev_attr_online);
+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,
+};
- if (gdrv && gdrv->remove)
- gdrv->remove(gdev);
- return 0;
-}
+static struct bus_type ccwgroup_bus_type = {
+ .name = "ccwgroup",
+ .remove = ccwgroup_remove,
+ .shutdown = ccwgroup_shutdown,
+ .pm = &ccwgroup_pm_ops,
+};
-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 = (struct device_driver) {
- .bus = &ccwgroup_bus_type,
- .name = cdriver->name,
- .probe = ccwgroup_probe,
- .remove = ccwgroup_remove,
- };
+ cdriver->driver.bus = &ccwgroup_bus_type;
return driver_register(&cdriver->driver);
}
+EXPORT_SYMBOL(ccwgroup_driver_register);
-static int
-__ccwgroup_driver_unregister_device(struct device *dev, void *data)
+static int __ccwgroup_match_all(struct device *dev, void *data)
{
- __ccwgroup_remove_symlinks(to_ccwgroupdev(dev));
- device_unregister(dev);
- put_device(dev);
- return 0;
+ 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;
+
/* We don't want ccwgroup devices to live longer than their driver. */
- get_driver(&cdriver->driver);
- driver_for_each_device(&cdriver->driver, NULL, NULL,
- __ccwgroup_driver_unregister_device);
- put_driver(&cdriver->driver);
+ while ((dev = driver_find_device(&cdriver->driver, NULL, NULL,
+ __ccwgroup_match_all))) {
+ struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
+
+ ccwgroup_ungroup(gdev);
+ put_device(dev);
+ }
driver_unregister(&cdriver->driver);
}
+EXPORT_SYMBOL(ccwgroup_driver_unregister);
-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;
}
+EXPORT_SYMBOL(ccwgroup_probe_ccwdev);
-static inline 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;
- if (get_device(&gdev->dev)) {
- if (klist_node_attached(&gdev->dev.knode_bus))
- return gdev;
- put_device(&gdev->dev);
- }
- return NULL;
- }
- 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;
/* Ignore offlining errors, device is gone anyway. */
ccw_device_set_offline(cdev);
/* If one of its devices is gone, the whole group is done for. */
- gdev = __ccwgroup_get_gdev_by_cdev(cdev);
- if (gdev) {
- __ccwgroup_remove_symlinks(gdev);
- device_unregister(&gdev->dev);
- put_device(&gdev->dev);
+ spin_lock_irq(cdev->ccwlock);
+ gdev = dev_get_drvdata(&cdev->dev);
+ if (!gdev) {
+ spin_unlock_irq(cdev->ccwlock);
+ return;
}
+ /* Get ccwgroup device reference for local processing. */
+ get_device(&gdev->dev);
+ spin_unlock_irq(cdev->ccwlock);
+ /* Unregister group device. */
+ ccwgroup_ungroup(gdev);
+ /* Release ccwgroup device reference for local processing. */
+ put_device(&gdev->dev);
}
-
-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);
+MODULE_LICENSE("GPL");