aboutsummaryrefslogtreecommitdiff
path: root/drivers/s390/cio
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/s390/cio
Linux-2.6.12-rc2v2.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!
Diffstat (limited to 'drivers/s390/cio')
-rw-r--r--drivers/s390/cio/Makefile10
-rw-r--r--drivers/s390/cio/airq.c87
-rw-r--r--drivers/s390/cio/airq.h10
-rw-r--r--drivers/s390/cio/blacklist.c351
-rw-r--r--drivers/s390/cio/blacklist.h6
-rw-r--r--drivers/s390/cio/ccwgroup.c482
-rw-r--r--drivers/s390/cio/chsc.c1114
-rw-r--r--drivers/s390/cio/chsc.h66
-rw-r--r--drivers/s390/cio/cio.c860
-rw-r--r--drivers/s390/cio/cio.h143
-rw-r--r--drivers/s390/cio/cio_debug.h32
-rw-r--r--drivers/s390/cio/cmf.c1042
-rw-r--r--drivers/s390/cio/css.c575
-rw-r--r--drivers/s390/cio/css.h155
-rw-r--r--drivers/s390/cio/device.c1135
-rw-r--r--drivers/s390/cio/device.h115
-rw-r--r--drivers/s390/cio/device_fsm.c1250
-rw-r--r--drivers/s390/cio/device_id.c355
-rw-r--r--drivers/s390/cio/device_ops.c603
-rw-r--r--drivers/s390/cio/device_pgid.c448
-rw-r--r--drivers/s390/cio/device_status.c385
-rw-r--r--drivers/s390/cio/ioasm.h228
-rw-r--r--drivers/s390/cio/qdio.c3468
-rw-r--r--drivers/s390/cio/qdio.h648
24 files changed, 13568 insertions, 0 deletions
diff --git a/drivers/s390/cio/Makefile b/drivers/s390/cio/Makefile
new file mode 100644
index 00000000000..c490c2a1c2f
--- /dev/null
+++ b/drivers/s390/cio/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for the S/390 common i/o drivers
+#
+
+obj-y += airq.o blacklist.o chsc.o cio.o css.o
+ccw_device-objs += device.o device_fsm.o device_ops.o
+ccw_device-objs += device_id.o device_pgid.o device_status.o
+obj-y += ccw_device.o cmf.o
+obj-$(CONFIG_CCWGROUP) += ccwgroup.o
+obj-$(CONFIG_QDIO) += qdio.o
diff --git a/drivers/s390/cio/airq.c b/drivers/s390/cio/airq.c
new file mode 100644
index 00000000000..3720e77b465
--- /dev/null
+++ b/drivers/s390/cio/airq.c
@@ -0,0 +1,87 @@
+/*
+ * drivers/s390/cio/airq.c
+ * S/390 common I/O routines -- support for adapter interruptions
+ *
+ * $Revision: 1.12 $
+ *
+ * Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH,
+ * IBM Corporation
+ * Author(s): Ingo Adlung (adlung@de.ibm.com)
+ * Cornelia Huck (cohuck@de.ibm.com)
+ * Arnd Bergmann (arndb@de.ibm.com)
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/rcupdate.h>
+
+#include "cio_debug.h"
+#include "airq.h"
+
+static adapter_int_handler_t adapter_handler;
+
+/*
+ * register for adapter interrupts
+ *
+ * With HiperSockets the zSeries architecture provides for
+ * means of adapter interrups, pseudo I/O interrupts that are
+ * not tied to an I/O subchannel, but to an adapter. However,
+ * it doesn't disclose the info how to enable/disable them, but
+ * to recognize them only. Perhaps we should consider them
+ * being shared interrupts, and thus build a linked list
+ * of adapter handlers ... to be evaluated ...
+ */
+int
+s390_register_adapter_interrupt (adapter_int_handler_t handler)
+{
+ int ret;
+ char dbf_txt[15];
+
+ CIO_TRACE_EVENT (4, "rgaint");
+
+ if (handler == NULL)
+ ret = -EINVAL;
+ else
+ ret = (cmpxchg(&adapter_handler, NULL, handler) ? -EBUSY : 0);
+ if (!ret)
+ synchronize_kernel();
+
+ sprintf (dbf_txt, "ret:%d", ret);
+ CIO_TRACE_EVENT (4, dbf_txt);
+
+ return ret;
+}
+
+int
+s390_unregister_adapter_interrupt (adapter_int_handler_t handler)
+{
+ int ret;
+ char dbf_txt[15];
+
+ CIO_TRACE_EVENT (4, "urgaint");
+
+ if (handler == NULL)
+ ret = -EINVAL;
+ else {
+ adapter_handler = NULL;
+ synchronize_kernel();
+ ret = 0;
+ }
+ sprintf (dbf_txt, "ret:%d", ret);
+ CIO_TRACE_EVENT (4, dbf_txt);
+
+ return ret;
+}
+
+void
+do_adapter_IO (void)
+{
+ CIO_TRACE_EVENT (6, "doaio");
+
+ if (adapter_handler)
+ (*adapter_handler) ();
+}
+
+EXPORT_SYMBOL (s390_register_adapter_interrupt);
+EXPORT_SYMBOL (s390_unregister_adapter_interrupt);
diff --git a/drivers/s390/cio/airq.h b/drivers/s390/cio/airq.h
new file mode 100644
index 00000000000..7d6be3fdcd6
--- /dev/null
+++ b/drivers/s390/cio/airq.h
@@ -0,0 +1,10 @@
+#ifndef S390_AINTERRUPT_H
+#define S390_AINTERRUPT_H
+
+typedef int (*adapter_int_handler_t)(void);
+
+extern int s390_register_adapter_interrupt(adapter_int_handler_t handler);
+extern int s390_unregister_adapter_interrupt(adapter_int_handler_t handler);
+extern void do_adapter_IO (void);
+
+#endif
diff --git a/drivers/s390/cio/blacklist.c b/drivers/s390/cio/blacklist.c
new file mode 100644
index 00000000000..4a06c7d0e5e
--- /dev/null
+++ b/drivers/s390/cio/blacklist.c
@@ -0,0 +1,351 @@
+/*
+ * drivers/s390/cio/blacklist.c
+ * S/390 common I/O routines -- blacklisting of specific devices
+ * $Revision: 1.33 $
+ *
+ * Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH,
+ * IBM Corporation
+ * Author(s): Ingo Adlung (adlung@de.ibm.com)
+ * Cornelia Huck (cohuck@de.ibm.com)
+ * Arnd Bergmann (arndb@de.ibm.com)
+ */
+
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/vmalloc.h>
+#include <linux/slab.h>
+#include <linux/proc_fs.h>
+#include <linux/ctype.h>
+#include <linux/device.h>
+
+#include <asm/cio.h>
+#include <asm/uaccess.h>
+
+#include "blacklist.h"
+#include "cio.h"
+#include "cio_debug.h"
+#include "css.h"
+
+/*
+ * "Blacklisting" of certain devices:
+ * Device numbers given in the commandline as cio_ignore=... won't be known
+ * to Linux.
+ *
+ * These can be single devices or ranges of devices
+ */
+
+/* 65536 bits to indicate if a devno is blacklisted or not */
+#define __BL_DEV_WORDS (__MAX_SUBCHANNELS + (8*sizeof(long) - 1) / \
+ (8*sizeof(long)))
+static unsigned long bl_dev[__BL_DEV_WORDS];
+typedef enum {add, free} range_action;
+
+/*
+ * Function: blacklist_range
+ * (Un-)blacklist the devices from-to
+ */
+static inline void
+blacklist_range (range_action action, unsigned int from, unsigned int to)
+{
+ if (!to)
+ to = from;
+
+ if (from > to || to > __MAX_SUBCHANNELS) {
+ printk (KERN_WARNING "Invalid blacklist range "
+ "0x%04x to 0x%04x, skipping\n", from, to);
+ return;
+ }
+ for (; from <= to; from++) {
+ if (action == add)
+ set_bit (from, bl_dev);
+ else
+ clear_bit (from, bl_dev);
+ }
+}
+
+/*
+ * Function: blacklist_busid
+ * Get devno/busid from given string.
+ * Shamelessly grabbed from dasd_devmap.c.
+ */
+static inline int
+blacklist_busid(char **str, int *id0, int *id1, int *devno)
+{
+ int val, old_style;
+ char *sav;
+
+ sav = *str;
+
+ /* check for leading '0x' */
+ old_style = 0;
+ if ((*str)[0] == '0' && (*str)[1] == 'x') {
+ *str += 2;
+ old_style = 1;
+ }
+ if (!isxdigit((*str)[0])) /* We require at least one hex digit */
+ goto confused;
+ val = simple_strtoul(*str, str, 16);
+ if (old_style || (*str)[0] != '.') {
+ *id0 = *id1 = 0;
+ if (val < 0 || val > 0xffff)
+ goto confused;
+ *devno = val;
+ if ((*str)[0] != ',' && (*str)[0] != '-' &&
+ (*str)[0] != '\n' && (*str)[0] != '\0')
+ goto confused;
+ return 0;
+ }
+ /* New style x.y.z busid */
+ if (val < 0 || val > 0xff)
+ goto confused;
+ *id0 = val;
+ (*str)++;
+ if (!isxdigit((*str)[0])) /* We require at least one hex digit */
+ goto confused;
+ val = simple_strtoul(*str, str, 16);
+ if (val < 0 || val > 0xff || (*str)++[0] != '.')
+ goto confused;
+ *id1 = val;
+ if (!isxdigit((*str)[0])) /* We require at least one hex digit */
+ goto confused;
+ val = simple_strtoul(*str, str, 16);
+ if (val < 0 || val > 0xffff)
+ goto confused;
+ *devno = val;
+ if ((*str)[0] != ',' && (*str)[0] != '-' &&
+ (*str)[0] != '\n' && (*str)[0] != '\0')
+ goto confused;
+ return 0;
+confused:
+ strsep(str, ",\n");
+ printk(KERN_WARNING "Invalid cio_ignore parameter '%s'\n", sav);
+ return 1;
+}
+
+static inline int
+blacklist_parse_parameters (char *str, range_action action)
+{
+ unsigned int from, to, from_id0, to_id0, from_id1, to_id1;
+
+ while (*str != 0 && *str != '\n') {
+ range_action ra = action;
+ while(*str == ',')
+ str++;
+ if (*str == '!') {
+ ra = !action;
+ ++str;
+ }
+
+ /*
+ * Since we have to parse the proc commands and the
+ * kernel arguments we have to check four cases
+ */
+ if (strncmp(str,"all,",4) == 0 || strcmp(str,"all") == 0 ||
+ strncmp(str,"all\n",4) == 0 || strncmp(str,"all ",4) == 0) {
+ from = 0;
+ to = __MAX_SUBCHANNELS;
+ str += 3;
+ } else {
+ int rc;
+
+ rc = blacklist_busid(&str, &from_id0,
+ &from_id1, &from);
+ if (rc)
+ continue;
+ to = from;
+ to_id0 = from_id0;
+ to_id1 = from_id1;
+ if (*str == '-') {
+ str++;
+ rc = blacklist_busid(&str, &to_id0,
+ &to_id1, &to);
+ if (rc)
+ continue;
+ }
+ if (*str == '-') {
+ printk(KERN_WARNING "invalid cio_ignore "
+ "parameter '%s'\n",
+ strsep(&str, ",\n"));
+ continue;
+ }
+ if ((from_id0 != to_id0) || (from_id1 != to_id1)) {
+ printk(KERN_WARNING "invalid cio_ignore range "
+ "%x.%x.%04x-%x.%x.%04x\n",
+ from_id0, from_id1, from,
+ to_id0, to_id1, to);
+ continue;
+ }
+ }
+ /* FIXME: ignoring id0 and id1 here. */
+ pr_debug("blacklist_setup: adding range "
+ "from 0.0.%04x to 0.0.%04x\n", from, to);
+ blacklist_range (ra, from, to);
+ }
+ return 1;
+}
+
+/* Parsing the commandline for blacklist parameters, e.g. to blacklist
+ * bus ids 0.0.1234, 0.0.1235 and 0.0.1236, you could use any of:
+ * - cio_ignore=1234-1236
+ * - cio_ignore=0x1234-0x1235,1236
+ * - cio_ignore=0x1234,1235-1236
+ * - cio_ignore=1236 cio_ignore=1234-0x1236
+ * - cio_ignore=1234 cio_ignore=1236 cio_ignore=0x1235
+ * - cio_ignore=0.0.1234-0.0.1236
+ * - cio_ignore=0.0.1234,0x1235,1236
+ * - ...
+ */
+static int __init
+blacklist_setup (char *str)
+{
+ CIO_MSG_EVENT(6, "Reading blacklist parameters\n");
+ return blacklist_parse_parameters (str, add);
+}
+
+__setup ("cio_ignore=", blacklist_setup);
+
+/* Checking if devices are blacklisted */
+
+/*
+ * Function: is_blacklisted
+ * Returns 1 if the given devicenumber can be found in the blacklist,
+ * otherwise 0.
+ * Used by validate_subchannel()
+ */
+int
+is_blacklisted (int devno)
+{
+ return test_bit (devno, bl_dev);
+}
+
+#ifdef CONFIG_PROC_FS
+/*
+ * Function: s390_redo_validation
+ * Look for no longer blacklisted devices
+ * FIXME: there must be a better way to do this */
+static inline void
+s390_redo_validation (void)
+{
+ unsigned int irq;
+
+ CIO_TRACE_EVENT (0, "redoval");
+ for (irq = 0; irq < __MAX_SUBCHANNELS; irq++) {
+ int ret;
+ struct subchannel *sch;
+
+ sch = get_subchannel_by_schid(irq);
+ if (sch) {
+ /* Already known. */
+ put_device(&sch->dev);
+ continue;
+ }
+ ret = css_probe_device(irq);
+ if (ret == -ENXIO)
+ break; /* We're through. */
+ if (ret == -ENOMEM)
+ /*
+ * Stop validation for now. Bad, but no need for a
+ * panic.
+ */
+ break;
+ }
+}
+
+/*
+ * Function: blacklist_parse_proc_parameters
+ * parse the stuff which is piped to /proc/cio_ignore
+ */
+static inline void
+blacklist_parse_proc_parameters (char *buf)
+{
+ if (strncmp (buf, "free ", 5) == 0) {
+ blacklist_parse_parameters (buf + 5, free);
+ } else if (strncmp (buf, "add ", 4) == 0) {
+ /*
+ * We don't need to check for known devices since
+ * css_probe_device will handle this correctly.
+ */
+ blacklist_parse_parameters (buf + 4, add);
+ } else {
+ printk (KERN_WARNING "cio_ignore: Parse error; \n"
+ KERN_WARNING "try using 'free all|<devno-range>,"
+ "<devno-range>,...'\n"
+ KERN_WARNING "or 'add <devno-range>,"
+ "<devno-range>,...'\n");
+ return;
+ }
+
+ s390_redo_validation ();
+}
+
+/* FIXME: These should be real bus ids and not home-grown ones! */
+static int cio_ignore_read (char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ const unsigned int entry_size = 18; /* "0.0.ABCD-0.0.EFGH\n" */
+ long devno;
+ int len;
+
+ len = 0;
+ for (devno = off; /* abuse the page variable
+ * as counter, see fs/proc/generic.c */
+ devno <= __MAX_SUBCHANNELS && len + entry_size < count; devno++) {
+ if (!test_bit(devno, bl_dev))
+ continue;
+ len += sprintf(page + len, "0.0.%04lx", devno);
+ if (test_bit(devno + 1, bl_dev)) { /* print range */
+ while (++devno < __MAX_SUBCHANNELS)
+ if (!test_bit(devno, bl_dev))
+ break;
+ len += sprintf(page + len, "-0.0.%04lx", --devno);
+ }
+ len += sprintf(page + len, "\n");
+ }
+
+ if (devno <= __MAX_SUBCHANNELS)
+ *eof = 1;
+ *start = (char *) (devno - off); /* number of checked entries */
+ return len;
+}
+
+static int cio_ignore_write(struct file *file, const char __user *user_buf,
+ unsigned long user_len, void *data)
+{
+ char *buf;
+
+ if (user_len > 65536)
+ user_len = 65536;
+ buf = vmalloc (user_len + 1); /* maybe better use the stack? */
+ if (buf == NULL)
+ return -ENOMEM;
+ if (strncpy_from_user (buf, user_buf, user_len) < 0) {
+ vfree (buf);
+ return -EFAULT;
+ }
+ buf[user_len] = '\0';
+
+ blacklist_parse_proc_parameters (buf);
+
+ vfree (buf);
+ return user_len;
+}
+
+static int
+cio_ignore_proc_init (void)
+{
+ struct proc_dir_entry *entry;
+
+ entry = create_proc_entry ("cio_ignore", S_IFREG | S_IRUGO | S_IWUSR,
+ &proc_root);
+ if (!entry)
+ return 0;
+
+ entry->read_proc = cio_ignore_read;
+ entry->write_proc = cio_ignore_write;
+
+ return 1;
+}
+
+__initcall (cio_ignore_proc_init);
+
+#endif /* CONFIG_PROC_FS */
diff --git a/drivers/s390/cio/blacklist.h b/drivers/s390/cio/blacklist.h
new file mode 100644
index 00000000000..fb42cafbe57
--- /dev/null
+++ b/drivers/s390/cio/blacklist.h
@@ -0,0 +1,6 @@
+#ifndef S390_BLACKLIST_H
+#define S390_BLACKLIST_H
+
+extern int is_blacklisted (int devno);
+
+#endif
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);
diff --git a/drivers/s390/cio/chsc.c b/drivers/s390/cio/chsc.c
new file mode 100644
index 00000000000..b35fe12e6bf
--- /dev/null
+++ b/drivers/s390/cio/chsc.c
@@ -0,0 +1,1114 @@
+/*
+ * drivers/s390/cio/chsc.c
+ * S/390 common I/O routines -- channel subsystem call
+ * $Revision: 1.119 $
+ *
+ * Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH,
+ * IBM Corporation
+ * Author(s): Ingo Adlung (adlung@de.ibm.com)
+ * Cornelia Huck (cohuck@de.ibm.com)
+ * Arnd Bergmann (arndb@de.ibm.com)
+ */
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/device.h>
+
+#include <asm/cio.h>
+
+#include "css.h"
+#include "cio.h"
+#include "cio_debug.h"
+#include "ioasm.h"
+#include "chsc.h"
+
+static struct channel_path *chps[NR_CHPIDS];
+
+static void *sei_page;
+
+static int new_channel_path(int chpid);
+
+static inline void
+set_chp_logically_online(int chp, int onoff)
+{
+ chps[chp]->state = onoff;
+}
+
+static int
+get_chp_status(int chp)
+{
+ return (chps[chp] ? chps[chp]->state : -ENODEV);
+}
+
+void
+chsc_validate_chpids(struct subchannel *sch)
+{
+ int mask, chp;
+
+ for (chp = 0; chp <= 7; chp++) {
+ mask = 0x80 >> chp;
+ if (!get_chp_status(sch->schib.pmcw.chpid[chp]))
+ /* disable using this path */
+ sch->opm &= ~mask;
+ }