diff options
Diffstat (limited to 'drivers/s390/cio/ccwgroup.c')
-rw-r--r-- | drivers/s390/cio/ccwgroup.c | 482 |
1 files changed, 482 insertions, 0 deletions
diff --git a/drivers/s390/cio/ccwgroup.c b/drivers/s390/cio/ccwgroup.c new file mode 100644 index 00000000000..21a75ee28b8 --- /dev/null +++ b/drivers/s390/cio/ccwgroup.c @@ -0,0 +1,482 @@ +/* + * 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) + */ +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/ctype.h> +#include <linux/dcache.h> + +#include <asm/semaphore.h> +#include <asm/ccwdev.h> +#include <asm/ccwgroup.h> + +/* 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 + * one of its functions, namely the translation of single subchannels + * 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) +{ + struct ccwgroup_device *gdev; + struct ccwgroup_driver *gdrv; + + gdev = container_of(dev, struct ccwgroup_device, dev); + gdrv = container_of(drv, struct ccwgroup_driver, driver); + + if (gdev->creator_id == gdrv->driver_id) + return 1; + + return 0; +} +static int +ccwgroup_hotplug (struct device *dev, char **envp, int num_envp, char *buffer, + int buffer_size) +{ + /* TODO */ + return 0; +} + +static struct bus_type ccwgroup_bus_type = { + .name = "ccwgroup", + .match = ccwgroup_bus_match, + .hotplug = ccwgroup_hotplug, +}; + +static inline void +__ccwgroup_remove_symlinks(struct ccwgroup_device *gdev) +{ + int i; + char str[8]; + + 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"); + } + +} + +/* + * 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, const char *buf, size_t count) +{ + struct ccwgroup_device *gdev; + + gdev = to_ccwgroupdev(dev); + + if (gdev->state != CCWGROUP_OFFLINE) + return -EINVAL; + + __ccwgroup_remove_symlinks(gdev); + device_unregister(dev); + + return count; +} + +static DEVICE_ATTR(ungroup, 0200, NULL, ccwgroup_ungroup_store); + +static void +ccwgroup_release (struct device *dev) +{ + struct ccwgroup_device *gdev; + int i; + + gdev = to_ccwgroupdev(dev); + + for (i = 0; i < gdev->count; i++) { + gdev->cdev[i]->dev.driver_data = NULL; + put_device(&gdev->cdev[i]->dev); + } + kfree(gdev); +} + +static inline 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"); + if (rc) { + for (--i; i >= 0; i--) + sysfs_remove_link(&gdev->cdev[i]->dev.kobj, + "group_device"); + return rc; + } + } + 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); + if (rc) { + for (--i; i >= 0; i--) { + sprintf(str, "cdev%d", i); + sysfs_remove_link(&gdev->dev.kobj, str); + } + for (i = 0; i < gdev->count; i++) + sysfs_remove_link(&gdev->cdev[i]->dev.kobj, + "group_device"); + return rc; + } + } + 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. + */ +int +ccwgroup_create(struct device *root, + unsigned int creator_id, + struct ccw_driver *cdrv, + int argc, char *argv[]) +{ + struct ccwgroup_device *gdev; + int i; + int rc; + int del_drvdata; + + if (argc > 256) /* disallow dumb users */ + return -EINVAL; + + gdev = kmalloc(sizeof(*gdev) + argc*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 != + gdev->cdev[0]->id.driver_info) { + rc = -EINVAL; + goto free_dev; + } + /* Don't allow a device to belong to more than one group. */ + if (gdev->cdev[i]->dev.driver_data) { + rc = -EINVAL; + goto free_dev; + } + } + 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); + goto error; + } + + rc = __ccwgroup_create_symlinks(gdev); + if (!rc) { + 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]) { + put_device(&gdev->cdev[i]->dev); + if (del_drvdata) + gdev->cdev[i]->dev.driver_data = NULL; + } + kfree(gdev); + return rc; +} + +static int __init +init_ccwgroup (void) +{ + return bus_register (&ccwgroup_bus_type); +} + +static void __exit +cleanup_ccwgroup (void) +{ + bus_unregister (&ccwgroup_bus_type); +} + +module_init(init_ccwgroup); +module_exit(cleanup_ccwgroup); + +/************************** driver stuff ******************************/ + +static int +ccwgroup_set_online(struct ccwgroup_device *gdev) +{ + struct ccwgroup_driver *gdrv; + int ret; + + 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; + + gdev->state = CCWGROUP_ONLINE; + out: + atomic_set(&gdev->onoff, 0); + return ret; +} + +static int +ccwgroup_set_offline(struct ccwgroup_device *gdev) +{ + struct ccwgroup_driver *gdrv; + int ret; + + 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; +} + +static ssize_t +ccwgroup_online_store (struct device *dev, const char *buf, size_t count) +{ + struct ccwgroup_device *gdev; + struct ccwgroup_driver *gdrv; + unsigned int value; + int ret; + + gdev = to_ccwgroupdev(dev); + if (!dev->driver) + return count; + + gdrv = to_ccwgroupdrv (gdev->dev.driver); + if (!try_module_get(gdrv->owner)) + return -EINVAL; + + 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; +} + +static ssize_t +ccwgroup_online_show (struct device *dev, char *buf) +{ + int online; + + online = (to_ccwgroupdev(dev)->state == CCWGROUP_ONLINE); + + return sprintf(buf, online ? "1\n" : "0\n"); +} + +static DEVICE_ATTR(online, 0644, ccwgroup_online_show, ccwgroup_online_store); + +static int +ccwgroup_probe (struct device *dev) +{ + struct ccwgroup_device *gdev; + struct ccwgroup_driver *gdrv; + + int ret; + + gdev = to_ccwgroupdev(dev); + gdrv = to_ccwgroupdrv(dev->driver); + + 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); + + return ret; +} + +static int +ccwgroup_remove (struct device *dev) +{ + struct ccwgroup_device *gdev; + struct ccwgroup_driver *gdrv; + + 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) + gdrv->remove(gdev); + return 0; +} + +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, + }; + + return driver_register(&cdriver->driver); +} + +static inline struct device * +__get_next_ccwgroup_device(struct device_driver *drv) +{ + struct device *dev, *d; + + down_read(&drv->bus->subsys.rwsem); + dev = NULL; + list_for_each_entry(d, &drv->devices, driver_list) { + dev = get_device(d); + if (dev) + break; + } + up_read(&drv->bus->subsys.rwsem); + return dev; +} + +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); + while ((dev = __get_next_ccwgroup_device(&cdriver->driver))) { + __ccwgroup_remove_symlinks(to_ccwgroupdev(dev)); + device_unregister(dev); + put_device(dev); + }; + put_driver(&cdriver->driver); + driver_unregister(&cdriver->driver); +} + +int +ccwgroup_probe_ccwdev(struct ccw_device *cdev) +{ + return 0; +} + +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 (!list_empty(&gdev->dev.node)) + return gdev; + put_device(&gdev->dev); + } + return NULL; + } + return NULL; +} + +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); + } +} + +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); |