Linux-2.6.12-rc2

Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.

Let it rip!
diff --git a/drivers/s390/cio/ccwgroup.c b/drivers/s390/cio/ccwgroup.c
new file mode 100644
index 0000000..21a75ee
--- /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);